参数
参数分为形参和实参,形参是在函数定义的形参表中进行定义,是一个变量,其作用域为整个函数。而实参出现在函数调用中,是一个表达式,用传递给函数的实参对形参进行初始化。
函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字void的形参表来表示。
C++是一种静态强类型语言,对于每一次函数调用,编译时都会检查其实参。
每次函数调用时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名。
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参的副本初始化形参时,函数并没有访问调用所传递 的实参的本身,因此不会修改实参的值。
函数的形参可以为指针,此时将复制实参指针。与其他费引用类型的形参一样,该类形参的任何改变也仅仅作用于局部副本。
指针形参是指向const类型还是飞const类型,将影响函数调用所使用的参数。可以将指向const对象的指针初始化为指向非const对象,但是不可以让指向非const对象的指针指向const对象。
与所有引用一样,引用形参直接关联到其所绑定的对象,而并非这些对象的副本。另外,如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为const引用。
关于传递指向指针引用,这个对于我来说,比较难理解啊,总想不明白为啥这样定义。下面举个例子:
假如想编写一个交换两个整数的swap函数,实现两个指针的交换。已知需用*定义指针,用&定义引用。现在,问题是如何将这两个操作符结合起来以获得指向指针的引用。
void swap(int *&v1, int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
形参int *&v1
顶定义从右往左理解:v1是一个引用,与指向int型对象的指针相关联。也就是说,v1只是传递进swap函数的任意指针的别名。
返回值
这里要记录的是自己需要注意的地方。
(1)返回非引用类型
用函数返回值初始化临时对象与用实参初始化形参是一样的。如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。当函数返回非引用类型时,其返回值即可以是局部对象,也可以是求解表达式的结果。
(2)返回引用
当函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。考虑下面的函数,此函数返回两个string类型形参中较短的那个字符串的引用:
const string &shortstring(const string &s1, const string &s2)
{
return s1.size() < s2.size() ? s1 : s2;
}
形参和返回类型都是指向const string类型的引用,调用函数和返回结果时,都没有复制这些string对象。
记住:千万不要返回局部变量的引用。(3)引用返回左值
返回引用的函数返回一个左值。因此,这样的函数可用于任何要求使用左值的地方:
char &get_val(string &str, string::size_type ix)
{
return str[ix];
}
int main()
{
string s("a value");
cout << s << endl;
get_val(s, 0) = 'A'; //change s[0] to A
cout << s << endl;
return 0;
}
给函数返回值赋值可能让人惊讶,由于函数返回的是一个引用,因此是正确的。
如果不希望引用返回值被修改,返回值应该声明为:const char &get_val(.....)。
(5)千万不要返回指向局部变量的指针
一旦函数结束,局部对象被释放,返回的指针就变成了指向不在存在的对象的悬垂指针。
默认实参
当一个函数具有多个参数时,定义默认参数的方向是从右向左的,即以最后一个参数定位的;而匹配参数的方向是从左向右的,即以第一个参数定位的,如下图所示。
上图来自网友,具体出处忘记了,感谢。
如果我们要定义默认参数,那么我们必须从最后一个参数定义起,并且逐渐向前(左)定义,不可以跳过某个参数,直到所有的形参都被定义了默认值。
如果我们调用了一个定义了默认参数的函数,那么我们填写的第一个参数一定是和最左边的形参匹配,并且逐渐向后(右)匹配,不可以中途省略某一个参数,直到所有未被设置默认值的形参都已经有参数。
于是,调用函数时,用户向右自定义的实参至少要和向左来的已经定义的默认参数的形参相邻,函数才能被成功调用。否则这个函数就是缺少参数的。
指定默认实参的约束:
既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。下面的例子是错误的:
//ff.h
int ff(int = 0);
//ff.cc
#include "ff.h"
int ff(int = 0){....} //error
通常,应该在函数声明中指定默认实参,并将该声明放在合适的头文件中。
指向函数的指针
函数指针是指指向函数而非指向对象的指针。
bool (*pf)(const string &, const string &);
这个语句将pf声明为指向函数的指针,它所指向的函数待遇两个const string&类型的形参和bool类型的返回值。
(1)用typedef简化函数指针的定义
使用typedef为指针定义同义词,可以使函数指针的使用大大的简化:
typedef bool (*cmpfcn)(const string &, const string &);
该定义表示了cmpfcn是一种指向函数的指针类型的名字。在要使用这种函数指针的类型时,只需要直接使用cmpfcn就可以了。
(2)指向函数的指针的初始化和赋值
**在引用函数名单又没有调用该函数时,函数名将被自动解释为指向函数的指针。假如有函数:
bool lengthcompare(const string &, const string &);
除了用作函数调用的左操作数外,对lengthcompare的任何使用都被解释为如下类型的指针:
bool (*)(const string &, const string &);
可以使用函数名对函数指针进行初始化或赋值:
cmpfcn pf1 = 0; //ok:unbound pointer to function
cmpfcn pf2 = lengthcompare; //ok:pointer type matches function's type
pf1 = lengthcompare; //ok:pointer type matches function's type
pf2 = pf1; //ok:pointer types match
此时,直接引用函数名等效于在函数名上应用取地址操作符:
cmpfcn pf1 = lengthcompare;
cm[fcn pf2 = &lengthcompare;
记住:函数指针只能通过同类型的函数或者函数指针或0值常量表达式进行初始化或赋值。
将函数指针初始化为0,表示该指针不指向任何函数。
指向不同函数类型的指针之间不存在转换:
string::size_type sunlength(const string &, const string &)'
bool cstringcmpare(char *, char *);
cmpfcn pf;
pf = sunlength; //error:return type deffers
pf = cstringcompare;//error:parameter types differ
pf = lengthcompare; //ok:function and pointer types match exactly
(3)通过指针调用函数
指向函数的指针可用于调用它指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数:
cmpfcn pf = lengthcompare;
lengthcompare("hi","bye");
pf("hi", "bye");
(*pf)("hi", "bye");
(4)函数指针形参
函数的形参可以是指向函数的指针。这种形参可以用以下两种形式编写:
void useBigger(const string &, const string &,bool (const string &, const string &));
void useBigger(const string &, const string &, bool (*)(const string &, const string &));
(5)返回指向函数的指针
函数可以返回指向函数的指针,但是,正确的写出这种返回类型相当不容易:
int (*ff(int))(int *, int *);
提醒:阅读函数指针声明的最佳方式是从声明的名字开始由里而外理解。
要理解该声明的含义,首先观察:
ff(int)
将ff声明为一个函数,它带有一个int型的参数。该函数返回
int (*)(int *, int);
它是一个指向函数的指针,所指向的函数返回int型并带有两个分别为int *型和int型的形参。使用typedef可使该定义更简明易懂:
typedef int (*pf)(int *, int);
pf ff(int);