汉扬编程 C语言入门 嵌入式开发-C语言-函数的参数传递

嵌入式开发-C语言-函数的参数传递

1、函数的参数传递传递方式

嵌入式开发-C语言-函数的参数传递

函数之间的参数传递方式:全局变量(不建议使)-全局变量变是在函数体外说明的变量,它在程序中的每个函数里都是可见的-全局变量一经定义后就会在程序的任何地方可见,函数调用位置不同,程序的执行结果可能会受影响,不建议使用。值传递方式-调用函数将实参传递给被调用的函数,被调用函数将创建同类型的形参并用实参初始化-形参是新开辟的存储空间,因此,在函数中变形参的值,不会影响到实参。地址传递方式(建议使用)-按地址传递,实参为变量的地址,而形参为同类型的指针-被调用函数中对形参的操作,将直接改就实参的值,(被调用函数对指针的目标操作相当于对实参本体的操作)例: x和y交换#include <stdio.h>void swap (int *x,int *y){ int t; t =*x; *x =*y; *y=t;}int main(){ int a=10; int b=20; swap(&a,&b); printf("x=%d y=%d\\n",a,b); return 0;}输出:x=20 y=10程序实例:

编写一个函数,统计字符串中小写字母的个数,并把字符串中的小写字母转化为大转字母#include <stdio.h>int str_fun (char * p){ int num=0; while(*p){ //*p!='\\0' if(*p <='z'&&*p>='a'){ *p-=32; num++; } p++; } return num;}int main(){ char s[]="fafsfe12f45ef15ew5e5f1ew51"; int n = str_fun(s); printf("%d %s\\n",n,s); return 0;}执行输出: 15 FAFSFE12F45EF15EW5E5F1EW51总结函数的参数传递,包括全局变量、值传递和地址传递三种方式思考值传递和地址传递有什么区别?如何编程可以实现地址传递方式也不能改变实参?

C++|深入理解函数参数传递与返回

1 函数的封装与代回为什么需要函数?对于一些重复性的功能实现,为避免复制、粘贴带来的麻烦,而提炼出函数(并通过参数实现一般化),是分治算法的一种实现,代码整体上也更加模块化。所以要理解函数的种种参数传递与返回机制,要能考虑如何提炼出函数和如何将函数打散后再代回原调用处。

2 普通变量、指针变量、引用变量之间的地址语义和值语义变量的二重属性:变量首先是一个内存单元地址的名称化,这是变量的地址语义,内存单元的比特值根据类型实现其值语义;

2.1 普通变量:显式使用其值语义,隐式使用其地址语义;

2.2 指针变量:显式使用其地址语义,隐式使用其值语义;

2.3 引用变量:声明、定义、初始化时显式使用其地址语义(使用上类似指针变量),此外的其他应用场合显式使用其值语义(使用上类似普通变量);所以说,引用变量其实质是一个有常量属性的实现了自动解引用的特殊指针。所以引用相对于指针,有其使用上的简洁性、安全性,但也有其模糊性。

int i=5; int* p = &i; int& r = i; i=++*p; r++; cout<<i;//7以上的赋值方式,同样也适用于实参与形参的结合,以及函数返回值的语法机制。

应该使用指针的情况: 可能存在不指向任何对象的可能性,需要在不同的时刻指向不同的对象(此时,你能够改变指针的指向) 。返回函数体中new出的内存空间的地址。多态中使用指针。

应该使用引用的情况: 如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,使用此时应使用引用。

数组用作函数形式参数时会丢失数组元素个数的信息,即退化为指针;

函数返回数组名,实际返回的是指向数组首元素的指针。

3 主调函数caller与被调函数callee之间的关系主调函数caller与被调函数callee之间的关系主要有两种:

一是两者相互独立;

二是两者通过实参和形参的结合来建立联系,实现内存单元的共享,可读也可写共享的内存单元。

实现的机制是通过副本机制(包括传参和返回)来实现的。

3.1 值传递:副本保存的是值(不是地址和引用),主调函数和被函数之间相互独立;

3.2 指针传递和返回:副本保存的是地址(不是值),主调函数和被函数之间通过传递的参数变量相互影响;

3.3 引用传递和返回:副本保存的是地址(不是值),主调函数和被函数之间通过传递的参数变量相互影响;但引用在初始化时有指针语义,在使用时有普通变量语义(自动解引用);

函数的调用与嵌套调用通过栈来实现回溯,函数内可以用{}来嵌套块作用域。

4 指针和引用做参数及返回时,对应数据处理的方式当指针做参数时,函数要处理的往往不是指针本身,而是指针所指向的数据,所以通常会以解引用的形式做为左值或右值出现在函数体中,而当指针做为左值出现时,通常是用于指针的修改或移动(更多的情形是将形参指针赋给一个临时变量的指针,以避免形参指针的变更)。

当引用做参数时,函数体中对引用所指向的变量的处理,因为其特殊的语义,使用上就像普通变量。

5 实参与形参结合及函数返回值的理解5.1 实参与形参结合:局部变量的声明、定义、初始化;

5.2 函数返回值:可以理解为函数名做为一个全局变量,类型就是函数的类型,值就是其返回值;

#include <iostream>using namespace std;int& fr(int b[],int i){ return b[i];}//int& fr = b[i];int* fp(int b[],int i){ return &b[i];}//int* fp = &b[i]int main(){ //调用时 int a[] = {1,2,3,4,5}; int n = fr(a,3);// 实参与形参结构相当于:int b[]; b = a,int i = 3; fr(a,3) = 14;//相当于a[3]; cout<<*fp(a,2)<<endl;// 实参与形参结构相当于:int b[]; b = a,int i = 3; system(\”pause\”); return 0;}5.3 两者都可能会有隐式类型转换,当是类类型和对象时,会自动调用拷贝构造函数。

6 指针做为参数时,注意区分函数体对指针本身或指针所指向的对象的处理且两种情形的作用域不同,指针参数的作用域是本函数体,指针所指向的对象的作用域在本函数体以外。

#include <iostream>using namespace std;void f(char* p){ *p = \’A\’; //对指针指向的内存单元的操作(解引用) p++; //对指针本身的操作,p=p+1,指针值发生了变化,相当于指针移到了下一个位置, //这里是演示用,通常是用一个临时指针来做指针移动 *p = \’B\’; cout<<p<<endl;//Bc}int main(){ char arr[] = \”abc\”; f(arr); system(\”pause\”); return 0;}7 可以返回指针或引用的数据结构因为函数调用是通过栈机制来实现的,函数实现了一个独立的作用域机制,所以函数不能返回函数体内定义的非静态局部变量的指针或引用。

可以返回指针或引用的数据结构包括:

7.1 引用或指针参数指向内容(包括数组);

#include <stdio.h>#include <stdlib.h>#include <string.h>int *func(int *p) { (*p)++; printf(\”%p\\n\”,p); return p; //p本身虽是局部变量,但其值却是从主调函数传过来的地址值}/*int& func(int& p) { p++; return p; }*/int main(){ int i=10; int *p=&i; printf(\”%p\\n\”,p); printf(\”%d\\n\”, *(func(p))); system(\”pause\”); return 0;}/*0012FF440012FF4411*/数组也是如此:

int* f2(int a[],int i){ return &a[i];int& f2(int a[],int i){ return a[i]; } 7.2 静态局部变量;

int& func(){ static int i=10; //因为是全局的静态区 int& p=i; p++; return p; //OK:可以做为返回值}7.3 全局变量;

7.4 堆上数据;

避免返回函数内部new分配的内存的引用,但可以返回new的指针(也违背“谁申请,谁释放”的原则,存在内存泄漏的安全隐患)。

7.5 成员函数对私有数据成员的引用

#include<iostream>using namespace std; class C{ public: //返回一个引用,也就是n的引用 int& getReFN() { return n; } int getN() { return n; } private: int n;}c; int main(){ //将返回的引用赋值给k,k和n是一样的 //尽管n声明为私有,但是执行下条语句时候,就可以在外界通过k随意访问该变量 int& k = c.getReFN(); k = 7; cout<<c.getN()<<endl;//7 c.getReFN() = 9; cout<<c.getN()<<endl;//9 system(\”pause\”); return 0; }8 二级指针或指针引用做函数参数8.1 二级指针或指针引用做函数参数:

#include <iostream>using namespace std;void GetMemory(char* *pp,int n){ *pp = new char[n];//pp的解引用是*pp}int main(){ char* p = NULL; GetMemory(&p,100);//注意二级指针的赋值char**p = &p; strcpy(p,\”Hello!\”); cout<<p; delete[]p; p=NULL; system(\”pause\”); return 0;}以上为什么不能用一级指针作为参数?还是要回到前面讨论的在函数体内当用指针做形参时,在函数体操作指针本身与指针指向的对象的区别。也就是指针也其解引用的区别。

8.2 指针引用做函数参数

相对于二级指针更简洁:

#include <iostream>using namespace std;void GetMemory(char* &pp,int n){ pp = new char[n];}int main(){ char* p = NULL; GetMemory(p,100);//注意指针引用的赋值char*&p = p; strcpy(p,\”Hello!\”); cout<<p; delete[]p; p=NULL; system(\”pause\”); return 0;}8.3 当然也可以用指针函数

#include <iostream>using namespace std;char* GetMemory(int n){ char* pp = new char[n]; return pp;}int main(){ char* p = NULL; p=GetMemory(100);//注意指针引用的赋值char*&p = p; strcpy(p,\”Hello!\”); cout<<p; delete[]p; p=NULL; system(\”pause\”); return 0;}以上美中不足的是,违背了“谁创建、谁释放”的原则,容易造成内存泄漏。

但有时却不得已为之:

const int MAX = 55;typedef struct Heap{ int sizeHeap; int* heapData;}HEAP,*LPHEAP;LPHEAP createHeap(){ LPHEAP heap=(LPHEAP)malloc(sizeof(HEAP)); heap->sizeHeap=0; heap->heapData=(int*)malloc(sizeof(int)*MAX); return heap;}8.4 最常见的是链表操作:

struct node{ char data; node * next;}void Insert(node * &head,char keyWord,char newdata) //keyWord是查找关键字符{ node *newnode=new node; //新建结点 newnode->data=newdata; //newdata是新结点的数据 node *pGuard=Search(head,keyWord); //pGuard是插入位置前的结点指针 if (head==NULL || pGuard==NULL) //如果链表没有结点或找不到关键字结点 { //则插入表头位置 newnode->next=head; //先连 head=newnode; //后断 } else //否则 { //插入在pGuard之后 newnode->next=pGuard->next; //先连 pGuard->next=newnode; //后断 }}9 三种传递方式的效率比较如果是单个的基本数据类型,值传递、指针传递、引用传递三者的区别不是很大,但当传递的是复杂的数组、结构体、类对象时,区别就很大了。

#include <stdio.h>#include <string>#include <iostream>#include <sstream>#include <time.h>using namespace std;string test = \”origion\” ;string changed = \”changed\” ;string getTime(){ time_t tt =time(NULL); tm* t =localtime(&tt); string mon; stringstream ss; ss<<t->tm_mon; ss>>mon; ss.clear(); string day; ss<<(t->tm_mday); ss>>day; string hour; ss.clear(); ss<<(t->tm_hour); ss>>hour; string min; ss.clear(); ss<<(t->tm_min); ss>>min; string sec; ss.clear(); ss<<(t->tm_sec); ss>>sec; string times = mon+\”-\”+day+\”:\”+hour+\”:\”+min+\”:\”+sec ; return times ;}void func1(string s)//值传递{ s = changed ;}void func2(string& s)//引用传递{ s = changed ;}void func3(string* s)//指针传递{ *s = changed;}int main(){ cout<<getTime()<<endl; for (int i = 0; i < 3000000; i++) { func1(test); } cout<<getTime()<<endl; for (i = 0; i < 3000000; i++) { func2(test); } cout<<getTime()<<endl; for (i = 0; i < 3000000; i++) { func3(&test); } cout<<getTime()<<endl; //func3(&test); system(\”pause\”); return 0 ;}/*9-26:18:29:509-26:18:29:549-26:18:29:569-26:18:29:58*/如果参数是一个较复杂类对象,较效果更明显。

10 赋值运算符重载与引用返回赋值运算符重载一般使用引用作为返回,简洁,高效,还可以实现链式表达式操作:

String 的赋值函数 operator = 的实现如下: String & String::operator=(const String &other) { if (this == &other) //自己赋值给自己会形成循环调用构造函数的错误 return *this; delete m_data; //因为赋值相当于更新,原来的空间释放,使用新的堆空间 m_data = new char[strlen(other.data)+1]; strcpy(m_data, other.data); return *this; // 返回的是 *this 的引用,无需拷贝过程 }this是隐含的指向函数调用对象的指针,一般是隐式使用,在特殊场合下可以显式使用。

为什么是return *this而不是return this?要注意引用在地址语义和值语义上的的语法特性,引用是用一个变量名(或对象名)而不是指针来初始化的。

看下面实例:

int& at(){ return m_data_; //副本机制是变量地址}int at(){ return m_data_; //副本机制是变量值}11 其它11.1 复制构造函数的参数为什么一定要用引用传递,而不能用值传递?

值传递的参数在参数传递时有一个构造过程,即用实际参数的值构造形式参数,这个构造过程是由复制构造函数完成的。如果将复制构造函数的参数设计成值传递,会引起复制构造函数的递归调用。

11.2 下标运算符重载函数为什么要用引用返回?

下标运算符的第一个运算数是数组名,即当前类的对象。将下标运算符重载成成员函数时,编译器会将程序中诸如a[i]的下标变量的引用改为a.operator[](i)。如果a不是当前类的对象,编译器就会报错。下标变量是左值,所以必须用引用返回。

11.3 链式表达式的实现要用可以做为左值的引用或指针做为函数返回值,使用引用更为简洁。 重载操作符>>或<<时,返回的类型应该是一个流类型,而且返回的类型必须是引用,也是为了链式表达式实现的需要。

11.4 一般用指针参数做为输出参数,放在参数列表的右边(输入参数放在左边),而函数的参数有时用于输出逻辑值。

11.5 当为了避免副本机制而使用址传递或返回而又不需修改共享内存时,可以附加const修饰,以增强安全性。

-End-

本文来自网络,不代表汉扬编程立场,转载请注明出处:http://www.hyzlch.com/cjia/6096.html

C程序设计谭浩强第4版考研教材及真题视频讲解——才聪学习网

网卡灯闪烁是表示什么意思?

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

返回顶部