目录
函数基础
函数一般由返回值、函数名、参数列表和函数体组成。
string printTest(string name)
{
return "Hello " + name;
}
调用函数
调用函数形式为:函数名(参数列表)
printTest("yao");
函数调用起到两个作用:一是中断当前主调函数,而是转到被调函数执行。
形参与实参
实参是用于函数调用时,初始化对应的形参。尽管实参与形参有着对应关系,但是却没有求值顺序。
函数返回类型
函数返回类型不能使数组类型或函数类型,但是可以是指向数组或函数的指针。
返回类型可以是void,但是不可以不写。
1.局部对象
变量(对象)都有各自的生命周期和作用域,对于函数的形参及函数内变量,其生命周期及作用域仅存在于函数体内,这样的变量统称为局部变量。
局部变量会隐藏外部作用域声明的所有同名变量。
自动对象
我们把只存在执行期间的对象成为自动对象。即在进入块时创建,当块语句运行结束后,创建的自动变量销毁,其值变为未定义。
局部静态变量
在局部变量声明前加上 static 限定词,则该自动变量变为一个局部静态变量。
局部静态变量仅在第一次经过对象定义语句时初始化,后续则不会销毁也不会再重新初始化,所以每次在函数体内变化后,下次调用还会是上次函数结束时的值。
2.声明
函数声明可以多次,与定义的区别为没有函数体,用一个分号代替。
函数声明也叫函数原型。
参数传递
参数传递分为值传递和引用传递。
值传递是指实参和形参是两个独立的对象,实参将值拷贝给形参。
引用传递是指形参为引用类型,传参时,形参绑定到实参上。
1.传值参数
将普通类型(非指针)实参传递给形参时,形参的操作不会影响到实参的值。
指针形参
当形参类型为指针时,实参也需要传递一个同类型指针,由于指针存在间址访问功能,所以此时形参也可以影响到实参。
2.传引用参数
传引用参数有两个优点:
- 当参数为容器类或者结构体等较大类型时,使用传值拷贝操作效率很低,而传引用则仅是名字的绑定
- 当需要返回多个参数时,可以使用引用参数,在函数体内修改的形参会反应到实参
3.const形参或实参
对于顶层const,在函数调用时是可以忽略的(即形参为顶层const,实参可以是无const类型)。
但是对于底层const,则传实参时必须与形参类型保持一致(包含const)。
注意:在我们对于函数的形参不需要进行修改时,尽量使用常量引用。
4.数组形参
void print(int *);
void print(int[]);
void print(int[10]);
以上三种形式都为数组参数,其中第三个的10并不代表真实的数组维度。
对于数组参数的长度一般有三种表示方式
- 特殊的结束标记。在数组内以特定的结束符标记(如'\0','\n'等)
- 标准库规范。相仿于标准库,传递数组的首元素和尾后元素
- 传入长度参数。
复杂数组形式
void print(int (&arr)[]);//数组引用形参
void print(int (*p)[]);//多维数组
5.可变形参
initializer_list
返回类型与return语句
同函数调用一样,return语句也有两个类似的作用:一是结束当前的被调函数,二是将控制权交给主调函数。
return语句返回的类型必须与函数返回类型保持一致,或者能隐式转换,否则会报错。
1.无返回值函数
没有返回值的return只能用于返回类型为void的函数中。但是返回类型为void的函数不一定非要return,因为再其函数最后会隐式执行return语句。
2.有返回值函数
return语句返回的类型必须与函数返回类型保持一致,或者能隐式转换,否则会报错。
值是如何被返回的
如同初始化类似,函数返回后,会在函数调用点初始化一个临时量,这个临时量就是函数返回的结果。
对于普通类型局部变量,返回的是其副本。
对于复杂类型(引用、指针),则需要注意不能返回局部量。对于两者来说一方面返回的是一个局部量的绑定,另一方面返回的是一个指向局部作用域的指针,在函数返回后,函数内作用域被销毁,而此时两者指向的内容均在无效的函数作用域内。
引用返回左值
一个返回值为引用的函数,返回值可以作为一个左值,其他类型返回值为右值。
递归
一个函数如果调用了他自身,则称该函数为递归函数。递归函数一般需要有一个限制条件(即递归的次数),否则该函数将一直递归下去。
int factorial (int val)
{
if(val>1)
return factorial(val-1)*val;
return 1;
}
3.返回数组指针
函数重载
几个函数名字相同但是形参列表不同,则称这几个函数为重载函数。
注意:形参中顶层const参数和普通参数无法区分开来。
const_cast和重载
对于一般设计的引用参数,不需要修改实参值时,我们会使用顶层const限制形参。这时我们可以使用const_cast(去const)进行重载。
const string &shorterstring(const string &str1, const string &str2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
string &shorterstring(string &str1, string &str2)
{
auto &r = shorterstring(str1, str2);
return const_cast<string &>(r);
}
函数匹配
在函数调用时,会对各重载函数进行对比,并关联起最优的一个函数,这个过程叫做函数匹配。具体匹配过程在函数匹配章节讲到。
特殊用途语言特性
1.默认实参
再设计函数时,有些参数在大部分情况下都是一个值,而只有少数情况才会赋为其他值,我们就可以在声明时将其赋值,这样的在声明时赋值的参数叫做默认参数。
默认参数与在调用函数时若对其不赋值,则使用默认参数的值,若对其赋值,则使用调用时的值。
由于默认参数可以在函数调用时可以赋值也可以不赋值,所以如果将其放在形参列表的中间位置会造成歧义,所以规定,默认实参后不允许有非默认参数。
因为函数可以有多个声明,所以对于同一个函数有多个声明时,需要注意在给定作用域内,一个形参只能被赋予一次默认实参。
2.内联函数和constexpr函数
内联函数
对于规模较小,使用次数较多的函数或者一段表达式可以写成内联函数的方式。内联函数通常就是在调用点"内联的"展开,所以相对于普通的函数调用,速度更快、运行开销更小;同样的作为一个函数比一段表达式更美观、简洁。
内联函数定义在函数返回类型前加上inline关键字即可。
constexpr函数
是指可以用于常量表达式的函数。(是内联函数的一种)与普通函数定义类似,不过需要注意几点:
- 函数返回类型及所有形参类型都是字面值类型
- 函数体中只能有一条return语句
此外,由于内联函数和constexpr函数可以定义多次,而且因为会获取其内容内容的关系仅用头文件中的声明是不够的,所以一般把这两个函数放在头文件中。
函数匹配
候选函数:与调用函数同名,且在调用函数处可见的函数。
可行函数:候选函数中形参数目相同,且各形参与调用函数的实参类型相同或可以转换的函数。
最佳匹配:与调用函数最接近的函数,基本思想是实参类型与形参类型越接近,匹配的越好。如果匹配成功应满足以下条件
- 该函数的每个实参的匹配不劣于其他可行函数的匹配
- 该函数至少有一个实参的匹配优于其他可行函数的匹配
1.实参类型转换
实参到形参的匹配过程分为几个等级:
- 精确匹配,分为以下几种情况:实参类型与形参类型相同;实参从数组或函数类型转为对应指针类型;实参中添加顶层const或形参中删除顶层const
- 通过const转换实现的匹配
- 通过类型提升实现的匹配
- 通过算术转换实现的匹配
- 通过类类型转换实现的匹配
函数指针
函数指针指向的是函数而非对象。函数的类型由返回类型和形参类型决定,与函数名无关。如
bool lenthCompare(const string&, const string &);
类型就是去掉函数名,想要声明指向该函数的指针,将函数名替换为指针就行。
bool (*p)(const string&, const string &);
使用函数指针
当把函数名作为一个值使用时,会自动转换为指针。
当使用指向函数的指针时,不用解引用。下面三种调用等价:
bool b1 = pf("hello", "goodbyte");
bool b2 = (*pf)("hello", "goodbyte");
bool b3 = lengthCompare("hello", "goodbyte");