C++函数参数和返回值三种传递方式:值传递、指针传递和引用传递。
函数的值的传递
函数调用的机制:
函数的调用过程实际是对栈空间的操作过程(先进后出)。因为调用函数是用栈空间来保存信息的。函数调用过程大致描述如下:
1)建立被调用函数的栈空间;
2)保存调用函数的运行状态和返回地址;
3)传递函数参给形参;
4)执行被调用函数的函数体内语句;
5)将控制权或返回值交给调用函数。
1、值传递
#include <iostream>
using namespace std;
void(int a,int b);
void main(){
int x=5,y=10;
cout<<"x="<<x<<"y="<<y<<endl;
swap(x,y);
cout<<"x="<<x<<"y="<<y<<endl;
}
void swap(int a,int b){
int t;
t=a;
a=b;
b=t;
}
swap()和main()的栈区是相互独立的。在main()中不能访问swap()中的局部变量a,b,和t在被调用函数中,改变了形参的值,而不能改变调用函数实参值。这就是C++函数的参数传值调用的特性。
2、函数的传址调用
传址调用:是将实参的地址值传递给形参,这时形参应是指针,即让形参指针指向实参地址,这里不再是将实参拷贝一个副本给形参,而是让形参直接指向实参。于是,这就提供了一种可以改变实参变量值的方法:在被调用函数中改变形参所指向的变量值。这便是传址调用的特点,它与传值调用的特点。
#include <iostream>
using namespace std;
void(int *a,int *b);
void main(){
int x=5,y=10;
cout<<"x="<<x<<"y="<<y<<endl;
swap(&x,&y);
cout<<"x="<<x<<"y="<<y<<endl;
}
void swap(int *a,int *b){
int t;
t=*a;
*a=*b;
*b=t;
}
3、引用传递
使用引用作函数参数时,要求实参变量名,将实参变量名赋给形参引用,即形参实际上成了实参的别名。引用起到了传了传址调用的作用,即不仅可以不传递实参的副本,还可以在被调用函数中参通过形参改变实参值。在引用调用中,实参直接用变量名,形参用引用名,免去使用指针带来的麻烦。因此,使用引用调用比传址调 用更为简单,并且可读性好。所以,在C++语言中,较多地使用引用调用来替代传址调用,以简捷的方式达到了传址调用的效果。
#include <iostream>
using namespace std;
void(int &a,int &b);
void main(){
int x=5,y=10;
cout<<"x="<<x<<"y="<<y<<endl;
swap(x,y);
cout<<"x="<<x<<"y="<<y<<endl;
}
void swap(int &a,int &b){
int t;
t=a;
a=b;
b=t;
}
4、函数参数的压栈顺序
函数参数顺序一般是按照从右到左的顺序进行压栈,可能不同的编译器有所不同。
下面程序在VS2010环境中编译:
#include <iostream>
using namespace std;
void func1(int a,int b,int c){
cout<<a<<" "<<b<<" "<<c<<endl;
}
int main(){
int i=0;
func1(i++,i++,i++); //从右到左进行计算后入栈
int j=0;
func1(++j,++j,++j); //前置++先都计算完成 再入栈
int x=0;
func1(x,x++,x);
int y=0;
func1(y++,y,++y); //从右到左进行计算 y++是计算完成以后立马入栈 ++y或y等等所有计算完成再统一入栈
int z=0;
func1(z,++z,z++);
system("pause");
}
函数返回值
1、值返回
在带有返回值的函数中,需要使用return语句返回一个表达式的值,如:return 表达式;一般函数返回值时都要建立临时变量,即用来拷贝副本。
(1)函数的返回值用于初始化在调用函数时创建的临时对象(temporary object),如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。
(2)在求解表达式的时候,如果需要一个地方存储其运算结果,编译器会创建一个没命名的对象,这就是临时对象。C++程序员通常用temporary这个术语来代替temporary object。
(3)用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。
(4)当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。
调用完成以后,局部变量和临时变量都会被销毁。
2、引用返回类型
引用返回值时,不产生值的副本,而是将其返回值直接传递给接收函数返回值的变量或对象。
当函数返回引用类型时,没有复制返回值创建临时变量,相反,返回的是对象本身。
千万不要返回局部对象的引用!千万不要返回指向局部对象的指针!
当函数执行完毕时,将释放分配给局部对象的存储空间。此时对局部对象的引用就会指向不确定的内存!返回指向局部对象的指针也是一样的,当函数结束时,局部对象被释放,返回的指针就变成了不再存在的对象的悬垂指针。
返回引用时,要求在函数的参数中,包含有以引用方式或指针方式存在的、需要被返回的参数。
如:
int& abc(int a, int b, int c, int& result){
result = a + b + c;
return result;
}
下面通过程序来说明这个问题:
1
#include <iostream>
using namespace std;
int func(int a, int b, int c){
int d=a+b+c;
cout<<d<<"的地址为:"<<&d<<endl;
return d;
}
int main(){
int d=func(1,2,3);
cout<<d<<"的地址为:"<<&d<<endl;
system("pause");
}
程序解析:
可以看出func函数中产生局部变量d,在返回的过程中产生临时变量tmp,把变量d的值拷贝到tmp,在main函数中创建变量d,把临时变量赋值给main中的变量d。调用完成以后把局部变量d和临时变量tmp销毁。
2
#include <iostream>
using namespace std;
int& func(int a, int b, int c){
int d=a+b+c;
cout<<d<<"的地址为:"<<&d<<endl;
return d;
}
int main(){
int d=func(1,2,3);
cout<<d<<"的地址为:"<<&d<<endl;
system("pause");
}
程序解析:
可以看出func函数中产生局部变量d,在返回的过程中不产生产生临时变量,而直接把变量d的值本身返回,在main函数中创建变量d,把局部变量d赋值给main中的变量d。调用完成以后局部变量d销毁。
3
#include <iostream>
using namespace std;
int& func(int a, int b, int c){
int d=a+b+c;
cout<<d<<"的地址为:"<<&d<<endl;
return d;
}
int main(){
int &d=func(1,2,3);
cout<<d<<"的地址为:"<<&d<<endl;
system("pause");
}
程序解析:
可以看出func函数中产生局部变量d,在返回的过程中不产生产生临时变量,而直接把变量d的值本身返回,在main函数中创建变量引用d,引用变量d直接指向局部变量d。因此打印的地址是完全相同的。理论上这样写是不可取的,因为局部变量在函数调用完成就已经销毁了,但是在VS2010环境下任然可以使用,VS2010编译器应该做过某些优化吧。
4
#include <iostream>
using namespace std;
int func(int a, int b, int c){
int d=a+b+c;
cout<<d<<"的地址为:"<<&d<<endl;
return d;
}
int main(){
int &d=func(1,2,3);
cout<<d<<"的地址为:"<<&d<<endl;
system("pause");
}
程序解析:
程序无法编译通过,func函数中产生局部变量d,在返回的过程中产生临时变量tmp,把变量d的值拷贝到tmp,在main函数中创建变量引用d,引用变量d直接指向临时变量tmp。而临时变量在函数调用完成就会销毁,因此在main中用引用变量指向临时变量tmp是不可取的。
#include <iostream>
using namespace std;
int& func(int p){
p=p+2;
cout<<&p<<endl;
return p; //返回参数引用 正确
}
int main(){
int a=0;
cout<<&func(1)<<endl;
system("pause");
}
因此返回引用时,要求在函数的参数中,包含有以引用方式需要被返回的参数。
比如:
#include <iostream>
using namespace std;
int*& func(){
int *d=new int;
*d=3;
cout<<d<<endl;
return d; //返回局部指针变量 不可取
}
int main(){
int* &d=func();
cout<<*d<<" "<<d<<endl;
*d=5; //运行报错 因为指针变量d在func中是一个局部变量 调用完成会销毁
cout<<*d<<endl;
system("pause");
}
#include <iostream>
using namespace std;
int*& func(int* &d){
d=new int;
*d=3;
cout<<d<<endl;
return d; //返回参数引用 正确
}
int main(){
int *a;
int* &d=func(a);
cout<<*d<<" "<<d<<" "<<a<<endl;
*d=5;
cout<<*d<<endl;
system("pause");
}
什么时候用引用返回:
当返回结果需要做为左值时,就要用引用返回。即重载函数的返回结果需要出现在=左边时,必须用引用返回。如果不用引用返回,那么重载函数的返回结果会是一个临时变量,临时变量是不能放在=左边的。
比如:
#include <iostream>
using namespace std;
int func(int p){
p=p+2;
return p; //返回参数引用 正确
}
int main(){
int a=0;
func(1)=10; //临时对象作为左值 会报错
system("pause");
}
#include <iostream>
using namespace std;
int& func(int p){
p=p+2;
return p; //返回参数引用 正确
}
int main(){
int a=0;
func(1)=10; //加上引用作为左值 编译通过
system("pause");
}
#include <iostream>
using namespace std;
class A{
public:
int a;
A(int n):a(n){}
};
A func(){
return A(2);
}
int main(){
A b(3);
func()=b; //如果是对象 也可以编译通过 在main中创建一个对象 但是没有名字
system("pause"); //b赋值给这个没有名字的对象
}
#include <iostream>
using namespace std;
int& func(int &p){
p=p+2;
return p; //返回参数引用 正确
}
int main(){
int a=0;
func(a)+=3; //相当于a的引用作为左值
cout<<a<<endl; //打印5
system("pause");
}
如果是返回类对象:
解释:首先Play(5)隐式调用有参构造函数生成函数参数形参b对象,然后在执行return b语句过程中调用拷贝构造函数生成一个临时对象tmp,最后退出Play函数,销毁临时对象tmp以及b,先析构tmp,再析构b
解释:首先Play(5)隐式调用有参构造函数生成函数参数形参b对象。 在执行return b语句过程中调用拷贝构造函数生成一个临时对象tmp,理论上会再调用一次拷贝构造函数用临时对象初始化t1对象,但是实际上并非如此,编译器会做一个优化,离开Play函数时不撤销临时对象tmp,而是让t1对象和tmp临时对象关联起来,t1和tmp应该是一致的。因此在离开Play函数时会析构b对象,在离开excute函数时再析构t1对象。
解释:首先Play(5)隐式调用有参构造函数生成函数参数形参b对象。 由于函数Play返回值类型时引用,因此在return的过程不再调用拷贝构造函数生成临时变量,而直接返回b对象的本身,在离开Play函数时析构b对象。
解析:首先Play(5)隐式调用有参构造函数生成函数参数形参b对象。 由于函数Play返回值类型时引用,因此在return的过程不再调用拷贝构造函数。 生成临时变量,而直接返回b对象的本身,在离开Play函数时析构b对象。然后使用b对象通过拷贝构造函数初始化t2对象,在离开excute函数时析构t2对象。
例题:下面是一个段有错误的代码,找出其中的错误。
错误代码
#include <iostream>
using namespace std;
int val()
{
int i = 1;
return i;
}
int & ref()
{
int &i = j;
return i;
}
int main()
{
int vv = val();
int & rv = val();
int vr = ref();
int & rr = ref();
return 0;
}
正确代码
#include <iostream>
using namespace std;
int j=3; //j是全局变量
int val()
{
int i = 1;
return i;
}
int & ref()
{
// int j=3;j不能是局部变量!
int &i = j;
return i; //不能返回局部对象的引用
}
int main()
{
int vv = val();
int rv = val();//int &rv = val()错误!val()返回的是一个int型的数,而给引用&rv 赋值的必须是一个同类型的变量。
int vr = ref();
int & rr = ref();
cout<<vv<<endl;
cout<<rv<<endl;
cout<<vr<<endl;
cout<<rr<<endl;
return 0;
}
const返回值
1、普通函数返回值加const类型
#include <iostream>
using namespace std;
const int func(){
int p=3;
return p;
}
int main(){
int s=2;
const int n=s; //普通变量可以初始化const
int t=n; //const也可以初始化普通变量
cout<<s<<" "<<n<<" "<<t<<endl;
int &y=s; //引用指向普通变量
int x=y; //引用也可以初始化普通变量
cout<<x<<" "<<y<<endl;
int num=func(); //这里加const和普通函数并无区别
func()=3; //编译不通过
cout<<num<<endl;
system("pause");
}
1、函数返回值不想其立即修改的
函数返回值为const,只有用在函数返回为引用的情况。函数返回值引用常量表示不能将函数调用表达式作为左值使用。
比如:
const int & abc(int a,int b,int &re)
{
re = a+b;
return re;
}
int main()
{
int a = 1,b =2,c;
abc(a,b,c)++; /////////////////////错误
c++; /////////////////////正确
cout<<c<<endl;
return 0;
}
2、重载运算符符合逻辑
class A
{
public:
int a;
A(int b):a(b){}
friend const A operator +(const A& lft,const A& rgt)
{
return A(lft.a + rgt.a);
}
};
int main()
{
A a(1),b(3),c(7);
a+b = c; //错误
return 0;
}
3、通过函数创建指向常量的指针
常量指针可以指向常变量,也可以指向普通变量,但是不能用普通指针赋值常量指针。
#include <iostream>
using namespace std;
const char *helpFun()
{
char * p =new char[3];
p[0]='a';
p[1]='b';
p[2]='\0';
return p;
}
int main()
{
const char * p = helpFun();
cout<<p<<endl;
delete p;
system("pause");
}
const参数,const返回值与const函数总结
在C++程序中,经常用const 来限制对一个对象的操作,例如,将一个变量定义为const 的:
const int n=3;
则这个变量的值不能被修改,即不能对变量赋值。const 这个关键字经常出现在函数的定义中,而且会出现在不同的位置,比如:
int strcmp(const char *str1,const char *str2);
const int &min (int &,int &);
void printMessage(char *msg) const;
1.const 参数
出现在函数参数中的const表示在函数体中不能对这个参数做修改。比如上面的例子中strcmp()函数用来比较两个字符串的大小,在函数体中不应该改变两个参数的值,所以将它定义为是const的。const通常用来限制函数的指针参数,引用和数组参数,而一般形式的参数因为形参和实参本来就不在同一内存空间,所以对形参的修改不会影响实参,因此也没有必要限制函数体不能对参数进行修改。
下面是一些使用函数const参数的例子:
(1)、函数strcpy()将src字符串的内容复制到targ字符串中,为保证src字符串不被修改,将它定义为const参数:
void strcpy(const char *src,char *targ);
(2)、函数max()从数组array中找出具有最大值的数组元素并返回这个最大元素的值,为保证数组元素不会在函数中被修改, 将它定义为const参数:
int max(const int array[],int size);
(3)、函数outputObject()将类Myclass的对象obj的内容输出。对象定义为const引用,即可以保证对象不会在函数体中有所改变,又可以节省对象传递的开销:
void outputObject(const Myclass &obj);
PS:const指针可以接受const和非const地址,但是非const指针只能接受非const地址。所以const 指针的能力更强一些,所以尽量多用const指针,这是一种习惯。
2.const 返回值
函数返回值为const只有用在函数返回为引用的情况。函数返回值引用常量表示不能将函数调用表达式作为左值使用。例如前面讲的返回引用的函数min()。
int &min(int &i,int &j);
可以对函数调用进行赋值,因为它返回的是左值:min(a,b)=4;
但是,如果对函数的返回值限定为const的:const int &min(int &i, int &j);
那么,就不能对min(a,b)调用进行赋值了。
3.const 函数
在类中,可以为类的成员函数进行如下形式的定义:
class classname{
int member ;
public:
int getMember() const;
};
这里,在函数定义头后面加上的cons 表示这个函数是一个“只读函数”,函数不能改变类对象的状态,不能改变对象的成员变量的值。如在函数体中不能这么写:
classname::getmember(){
member = 4; //修改了变量的值
return member;
}
const成员函数也不能在函数中调用其他非const的函数。
例如:
#include <iostream>
#include <string>
using namespace std;
class Student {
string name;
int score;
public:
Student(){}
Student(const string& nm,int sc=0):name(nm),score(sc){}
void set_student(const string& nm, int sc=0){ //后面不能有const 因为要改变变量
name = nm;
score = sc;
}
const string& get_name() const{
return name;
}
int get_score() const{
return score;
}
};
void output_student(const Student& student){ //输出学生名字和分数
cout<<student.get_name()<<" ";
cout<<student.get_score()<<endl;
}
int main() {
Student stu("Wang",85);
output_student(stu);
}
1、为什么get_name()前面也加const。如果没有前后两个const的话,get_name()返回的是对私有数据成员name的引用,所以通过这个引用可以改变私有成员name的值,如:
Student stu("Wang",85);
stu.get_name() = "Li"; //引用可以作为左值
即把name由原来的”Wang”变成了”Li”,而这不是我们希望的发生的。所以在get_name()前面加const避免这种情况的发生。
2、get_name()和get_score()这两个后面应该加const的成员函数,如果没有const修饰的话可不可以呢?回答是可以!但是这样做的代价是:const对象将不能再调用这两个非const成员函数了。如:
const string& get_name();
int get_score(); //这两个函数都应该设成const型
void output_student(const Student& student){
cout<<student.get_name()<<" ";
cout<<student.get_score()<<endl;
//如果get_name()和get_score()是非const成员函数,这两句调用都是错误的
}
由于参数student表示的是一个对const Student型对象的引用,所以student不能调用非const成员函数如set_student()。如果get_name()和get_score()成员函数也变成非const型,那么上面的student.get_name()和student.get_score()的使用就是非法的,这样就会给我们处理问题造成困难。
因此,我们没有理由反对使用const,该加const时就应该加上const,这样使成员函数除了非const的对象之外,const 对象也能够调用它。
#include <iostream>
using namespace std;
class A{
public:
A(int i){
n=i;
}
void getNum() const{
cout<<n<<endl;
}
void setNum(int j){
n=j;
}
int n;
};
int main(){
const A a(2);
//cout<<a.n<<endl;
a.getNum(); //打印2
//a.setNum(4);
system("pause");
}
满足对const成员函数的调用:const类型的对象,不能调用自身的非const成员函数,但是可以调用自己的const成员函数。
class A {
public:
A():num(2){}
void setnum(){}
void getnum() const{}
private:
int num;
};
int main(){
const A b;
b.getnum();
b.setnum(); //错误
return 0;
}
class A{
public:
A():num(2){}
void setnum(){num = 10;}
void getnum() const{
printf("%d\\n",num);
}
private:
int num;
};
class B
{
public:
//A类是B类运算中产生的隐藏变量,为了调用A类中的const函数,B类生产A类的函数返回类型需要加const。
const A* get(){
A *p = new A();
return p;
}
};
int main(){
B b;
b.get()->getnum();
b.get()->setnum();
return 0;
}
const成员函数的返回类型是引用时候,需要加const约束:
const int& fun() const;
class Test
{
public :
Test(int a):value(a){}
const int & GetValue()const
{
return value;
}
private:
const int value;
};
int main()
{
Test t(3);
const int &a = t.GetValue(); //const返回值引用只能用const引用指向
cout<<a<<endl;
return 0;
}
如果const成员函数返回的值指针呢?这与返回int类型是一样是赋值给接受对象,而引用因为没有了复制所以必须有const,例如下面代码可以编译运行,同时发现绕开了类的private约束。
class Test
{
public :
Test(int a):value(a){ p = &value; }
void setValue(){value =10;}
int * GetValue()const
{
return p;
}
int getv(){return value;}
private:
int value;
int * p;
};
int main()
{
Test t(3);
int *a = t.GetValue();
cout<<*a<<endl;
*a = 5;
cout<<t.getv()<<endl;
delete a;
return 0;
}
#include <iostream>
using namespace std;
class A{
public:
A(int i):n(i){}
int& get(){
return n;
}
void print(){
cout<<n<<endl;
}
private:
int n;
};
int main(){
A a(1);
a.print(); //打印1
int &b=a.get(); //获取私有变量的引用
b=2; //不通过公有接口修改私有变量
a.print(); //打印2
system("pause");
}
#include <iostream>
using namespace std;
class A{
public:
A(int i):n(i){}
int* get(){
return &n;
}
void print(){
cout<<n<<endl;
}
private:
int n;
};
int main(){
A a(1);
a.print(); //打印1
int *b=a.get(); //获取私有变量的地址
*b=2; //不通过公有接口修改私有变量
a.print(); //打印2
system("pause");
}