非引用形参
1.指针形参
参数是以复制形式传递的,函数内部对参数修改不会对调用函数的实参产生影响。函数的形参可以是指针(第 4.2节),此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。但可通过指针实现赋值,修改指针所指向对象的值:
void reset(int *ip)
{
*ip = 0; // changes thevalue of the object to which ip points
ip = 0; // changes only thelocal value of ip; theargument is unchanged
}
调用 reset后,实参依然保持原来的值,但它所指向的对象的值将变为 0。
下面用一个小程序来说明指针形参的几个注意点:
#include<stdio.h>
#include <malloc.h>
#include <string.h>
void GetMemory1(char *p)
{
//该函数的形参为字符指针,在函数内部修改形参并不能真正改变传入形参的实参值。
//因此在主函数中执行完下面两句之后
//char*str1 = NULL;
//GetMemory1(str1);
//str1仍然为NULL,因此会报内存错误,没有输出结果。
//printf(" address ofp itself: %x\n." , &p);
p= (char *)malloc(100);
//要记得使用指针变量时,每次分配空间后要判断是否分配成功。而且在主函数中使用之后记得释放内存,并置空
if(p == NULL)
{
printf("errormemory");
}
}
/*但是上面的函数参数变为*char *&p就可以在主函数中正常输出了。
指针做形参也是采用值传递的方式,也就是会把指针的值-地址传过去,所以可以改变这个地址里的内容,
但是你不能改变指针的值,也就是指向的地址。但是引用就是变量的别名,所以可以改变指针的值,
所以就可以在函数里申请空间*/
//参数是值传递方式,改变形参的地址,传递的实参的地址确不会因此改变
void GetMemory2(int n,char *pName)
{
char*a[4] ={"aaa","bbb","ccc","ddd"};
pName= a[n];
}
int main()
{
intn=2;
char*str1 = NULL;
char*str2 = NULL;
//GetMemory1(str1);
//strcpy(str1,"Helloworld");
//printf("%s\n",str1);
str2= (char *)malloc(21);
strcpy(str2,"helloworld");
printf("%s\n",str2); //输出helloworld
GetMemory2(n , str2 );
printf("%s\n",str2); //输出helloworld
system("pause");
return0;
}
2.const形参
当形参是const时,必须要注意关于顶层const的讨论。如前所述,顶层const的作用于对象本身:
const int ci=42; //不能改变ci,const是顶层的
int i=ci; //正确:当拷贝ci时,忽略了它的顶层const
int *constp=&i; //const是顶层的,不能给p赋值
*p=0; //正确:通过p改变对象的内容是允许的,现在i变成了0
和其他初始化过程一样,当用实参初始化形参时会忽略掉顶层const。换句话说,形参的顶层const被忽略掉了。当形参有顶层const时,传给它的常量对象或者非常量对象都是可以的:
void fcn(constint i){ /*fcn能够读取i,但是不能向i写值*/}
//调用fcn函数时,既可以传入const int也可以传入int。忽略掉形参的顶层const可以产生意想不到的结果:
void fcn(constint i) {/*fcn能够读取i,但是不能向i写值*/}
void fcn(int i) {/*....*/}//错误:重复定义了fdn(int)
在C++语言中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的区别。因为顶层const被忽略了,所以在上面的代码中传入两个fcn函数的参数可以完全一样。因此第二个fcn是错误的,尽管形式上由差异,但实际上它的形参和第一个fcn的形参没什么不同。
引用形参
1. 形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
#include<iostream>
using namespace std;
//值传递
void change1(int n){
cout<<"值传递--函数操作地址"<<&n<<endl; //显示的是拷贝的地址而不是源地址
n++;
}
//引用传递
void change2(int & n){
cout<<"引用传递--函数操作地址"<<&n<<endl;
n++;
}
//指针传递
void change3(int *n){
cout<<"指针传递--函数操作地址"<<n<<endl;
cout<<"指针本身的地址: " << &n << endl;
*n=*n+1;
}
int main(){
intn=10;
//int*p = &n;
cout<<"实参的地址"<<&n<<endl;
//cout<<"指向实参的指针的地址"<<p<<endl;
//cout<<"指针本身的地址: " << &p << endl;
change1(n);
cout<<"afterchange1() n="<<n<<endl;
change2(n);
cout<<"afterchange2() n="<<n<<endl;
//change3(p);
//cout<<"afterchange3() n="<<n<<endl;
change3(&n);
cout<<"afterchange3() n="<<n<<endl;
system("pause");
return0;
}
可以看出,实参的地址为0x18FF28.
采用值传递的时候,函数操作的地址是0x18FE54并不是实参本身,所以对它进行操作并不能改变实参的值.
再看引用传递,操作地址就是实参地址,只是相当于实参的一个别名,对它的操作就是对实参的操作.
接下来是指针传递,也可发现操作地址是实参地址.
那么,引用传递和指针传递有什么区别吗?
引用的规则:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
指针传递的实质:
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数(也就是指针本身当作了参数,上面的例子是change3的n)作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本(也就是说在栈中开辟了内存空间来存放change3的n)。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)如果理解不了大可跳过这段.
指针传递和引用传递一般适用于:
函数内部修改参数并且希望改动影响调用者。对比指针/引用传递可以将改变由形参“传给”实参(实际上就是直接在实参的内存上修改,不像值传递将实参的值拷贝到另外的内存地址中才修改)。
另外一种用法是:当一个函数实际需要返回多个值,而只能显式返回一个值时,可以将另外需要返回的变量以指针/引用传递给函数,这样在函数内部修改并且返回后,调用者可以拿到被修改过后的变量,也相当于一个隐式的返回值传递吧。
指针或引用形参与const
形参的初始化方式和变量的初始化方式是一样的,所以回顾通用的初始化规则有助于理解下面的知识。我们可以使用非常量初始化一个底层的const对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化。
int i=42;
const int*cp=&i; //正确:但是cp不能改变i
const int&r=i; //正确:但是r不能改变i
const int&r2=42; //正确
int *p=cp; //错误:p的类型和cp的类型不匹配
int &r3=r; //错误:r3的类型和r的类型不匹配
int &r4=42; //错误:不能用字面值初始化一个非常量引用
将同样的初始化规则应用到参数传递上可得如下形式:
int i=0;
const int ci=i;
string::size_typectr=0;
void reset(int&i);
reset(&i); //调用形参类型是int *的reset函数
reset(&ci); //错误:不能用指向const int对象的指针初始化int *
reset(i); //调用参数类型是int&的reset函数
reset(ci); //错误:不能把普通引用绑定到const对象ci上
reset(42); //错误:不能把普通引用绑定到字面值上
reset(ctr); //错误:类型不匹配,ctr是无符号类型
//find_char的第一个形参是对常量的引用
find_char("helloworld",'o',ctr);//可以绑定到字面值常量上
要想调用引用版本的reset,只能使用int类型的对象,而不能使用字面值、求值结果为int的表达式、需要转换的对象或者const int类型的对象。类似的,要想调用指针版本的reset只能使用int*。
另一方面,我们能传递一个字符串字面值作为find_char的第一个实参,这是因为改函数的引用形参是常量引用,而C++允许我们用字面值初始化常量引用。
尽量使用常量引用
把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带来给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。就像刚刚看到的,我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。