C/C++函数参数的类型

来源:互联网 发布:淘宝店铺流失人数 编辑:程序博客网 时间:2024/06/13 01:42

函数传参问题一直是一个难点,很多地方没懂会导致很多的问题,所以这里我将总结一下
函数的参数类型
函数的参数类型有:值,地址,引用
首先我们要知道在main函数里调用其他函数时,会在栈里开辟一段空间,最开始入栈的是当前代码的下一行代码的地址,然后依次是形参,函数的局部变量,函数的代码在代码段,当函数返回时,释放栈空间,最后出栈的是下一行代码的地址,又回到了main函数里函数开始的地方继续执行,很多人这个时候回提出一个问题,为什么栈里的变量释放了,函数还可以返回呢?这是因为编译器会为函数生成一个临时变量,用来存放函数的返回值,这里后面会详细讲到
1.值传递
这是实参把值传给形参,先看下一个程序

#include <stdio.h>int fun(int a){      a++;    printf("a=%d\n",a);    return a;}int main(int argc, char const *argv[]){   int var=5;   fun(var);   printf("var=%d\n",var);    return 0;}

结果为

[root@localhost gongxiang]# ./a.outa=6 var=5

这是一个很简单程序,fun函数的目的是var++,但是结果并没有让var++,而是形参a++,如果形参++,对于我们的程序是没有任何意义的,这是因为值传递时只是传递了实参的值给形参,用实参初始化形参,就相当于在调用fun函数时,首先进行的是 a=var;然后对a++,var的值当然不会有变化
2.地址传递
对于上面的程序可以改写

#include <stdio.h>int fun(int *a){      (*a)++;    printf("*a=%d \n",*a);    return *a;}int main(int argc, char const *argv[]){   int var=5;   fun(&var);   printf("var=%d\n",var);    return 0;}

结果为

[root@localhost gongxiang]# ./a.out*a=6 var=6

地址传递本质也是值传递,只是它传递的是地址值,函数调用的时候有int* a=&var,把变量var的地址传给指针a,这个时候指针a指向变量var,对*a的操作即是对var的操作,函数返回时指针a会被释放,但是返回来此时a的地址

3.引用传递
在C++中还可以通过引用来改变var的值

#include <iostream>using namespace std;int fun(int& a){      a++;    printf("a=%d \n",a);    return a;}int main(int argc, char const *argv[]){   int var=5;   fun(var);   printf("var=%d\n",var);    return 0;}

结果为

[root@localhost c++]# ./a.outa=6 var=6

引用即是给变量起一个别名,实际上是直接对变量进行操作,现在大家对函数参数有一点理解了吧

注意:函数不可以返回局部指针变量的值,因为函数返回时,就会释放栈,这句话很多人都容易误解,我之前也是,以为只要返回局部变量就是不行的,可是实际上不是的

请看下面一段代码

#include <iostream>using namespace std;char* fun(char* p){     return ++p;}int main(int argc, char const *argv[]){   char* str="1234567";  //“1234567”存放在字符串常量区   //char str[]="1234567"; //字符串和str都存放在栈   char* tmp=fun(str);   //tmp存放在栈   printf("tmp=%s\n",tmp);    return 0;}

结果如下:

[root@localhost c++]# g++ test.cpp[root@localhost c++]# ./a.outtmp=234567

这个地方有人会觉得不是说不可以返回局部变量指针的值吗?可是为什么可以返回呢!
回到前面说的地址传递的时候,传递指针的值,这个时候p与str指向同一个地址空间,函数里p++后指针向后移一位,指向字符‘2’,返回的时候,编译器会生成一个临时变量存放此时p的地址,返回给指针tmp,此时tmp指向常量区的字符串,自然可以输出啦!注意常量字符串区的值只读不可修改

再看下面的代码

#include <iostream>using namespace std;char* fun(){       char* p="abcdefg";    return ++p;}int main(int argc, char const *argv[]){   char* tmp=fun();   printf("tmp=%s\n",tmp);    return 0;}

结果为:

[root@localhost c++]# ./a.outtmp=bcdefg

这里可以返回局部变量指针主要是因为给指针初始化的字符串“abcdefg”存放在常量区,函数释放后并没有释放常量区的内容

再修改一下

#include <iostream>using namespace std;char* fun(){       char p[]="abcdefg";    return ++p;}int main(int argc, char const *argv[]){   char* tmp=fun();   printf("tmp=%s\n",tmp);    return 0;}

看看结果

[root@localhost c++]# g++ test.cpptest.cpp:14:2: 警告:文件未以空白行结束test.cpp: In function ‘char* fun()’:test.cpp:6: 错误:赋值运算中的左值无效test.cpp:5: 警告:返回了局部变量的 ‘p’ 的地址

这里出错了,是因为返回了局部指针变量的地址,字符串也存放在栈里,函数返回后,栈里的东西不存在了,即这时返回指针是错误的,解决办法可以分配堆内存,不需要用时再手动释放

以上讲的都是基础数据类型,在C++中函数参数可能是对象,之中又有很多注意的地方

下面看一下对象中值得注意的情况:

#include <iostream>using namespace std;class B{public:      B()      {        cout<<"default constructor"<<endl;      }      ~B()      {        cout<<"destructed"<<endl;      }      B(int i) :data(i)      {        cout<<"constructed by para "<<data<<endl;      }private:      int data;};B play(B b){  return b;}//第一个main函数// int main(int argc, char const *argv[])// {//     B t1=play(5);//     B t2=play(t1);//    return 0;// }//第二个main函数int main(int argc, char const *argv[]){    B t1=play(5);    B t2=play(10);    return 0;}

第一个main函数下执行的结果为

[root@localhost c++]# ./a.outconstructed by para 5(调用带参数的构造函数,在fun内生成临时对象)destructed           (5传入fun时生成的临时对象析构)destructed           (t1传入fun时产生的临时变量析构)destructed           (t2析构)destructed           (t1析构)

第二个main函数下执行的结果为

[root@localhost c++]# ./a.outconstructed by para 5 (调用带参数构造函数,在fun内生成临时对象)destructed            (5传入fun时生成的临时对象析构)constructed by para 10(调用带参数构造函数,在fun内生成临时对象)destructed            (10传入fun时生成的临时对象析构)destructed            (t2析构)destructed            (t1析构)

这里调用play()函数时,有两种参数类型的传递方式:
(1)如果传递的参数是整数,那么在函数栈中首先调用带参数的构造函数,然后产生一个临时变量,函数返回时调用复制构造函数,生成临时对象,最后这个临时对象会在函数返回后析构
(2)如果传递的是B类对象,首先会调用复制构造函数初始化形参对象,后面的步骤一样
可以看出,两种情况的不同就是调用不同的构造函数初始化形参,一个是调用带参数的构造函数,一个是调用复制构造函数

补充

什么是临时对象?临时对象正在什么情况下产生?
临时对象是看不见的,它不会出现在程序中,大多情况下它会影响程序的执行效率,所以有时想避免临时对象的产生
它通常在以下两种情况下产生:
1.参数按值传递
2.返回值按值传递
如何避免呢?
可以按引用传递代替值传递,引用必须有一个实在的,可引用的对象,否则引用是错误的

在产生临时对象的时候,需要调用复制构造函数
在什么情况下会调用复制构造函数呢?
1.一个对象以值传递的方式传入函数体
2.一个对象以值传递的方式从函数返回
3.一个对象需要另一个对象进行初始化

2 0