概述
第六章 函数
函数基础
- 函数定义:包括返回类型、函数名字和0个或者多个形参(parameter)组成的列表和函数体。
- 调用运算符:调用运算符的形式是一对圆括号
()
,作用于一个表达式,该表达式是函数或者指向函数的指针。 - 圆括号内是用逗号隔开的实参(argument)列表。
- 函数调用过程:
- 1.主调函数(calling function)的执行被中断。
- 2.被调函数(called function)开始执行。
- 形参和实参:形参和实参的个数和类型必须匹配上。
- 返回类型:
void
表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。 - 名字:名字的作用于是程序文本的一部分,名字在其中可见。
局部对象
- 生命周期:对象的生命周期是程序执行过程中该对象存在的一段时间。
- 局部变量(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是隐藏的。
- 自动对象:只存在于块执行期间的对象。当块的执行结束后,它的值就变成未定义的了。
- 局部静态对象:
static
类型的局部变量,生命周期贯穿函数调用前后,如果没有显式初始化,那么内置类型将会默认为0
函数声明
- 函数声明:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称函数原型。
- 在头文件中进行函数声明:建议变量在头文件中声明;在源文件中定义。
- 分离编译:
CC a.cc b.cc
直接编译生成可执行文件;CC -c a.cc b.cc
编译生成对象代码a.o b.o
;CC a.o b.o
编译生成可执行文件。
参数传递
- 形参初始化的机理和变量初始化一样。
- 引用传递(passed by reference):又称传引用调用(called by reference),指形参是引用类型,引用形参是它对应的实参的别名。
- 值传递(passed by value):又称传值调用(called by value),指实参的值是通过拷贝传递给形参。
传值参数
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
- 函数对形参做的所有操作都不会影响实参。
- 指针形参:常用在C中,
C++
建议使用引用类型的形参代替指针。
传引用参数
- 通过使用引用形参,允许函数改变一个或多个实参的值。
- 引用形参直接关联到绑定的对象,而非对象的副本。
- 使用引用形参可以用于返回额外的信息。
- 经常用引用形参来避免不必要的复制。
void swap(int &v1, int &v2)
- 如果无需改变引用形参的值,最好将其声明为常量引用。
const形参和实参
- 形参的顶层
const
被忽略。void func(const int i);
调用时既可以传入const int
也可以传入int
。 - 我们可以使用非常量初始化一个底层
const
对象,但是反过来不行。 - 在函数中,不能改变实参的局部副本。
- 尽量使用常量引用。
数组形参
- 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
- 要注意数组的实际长度,不能越界。
main处理命令行选项
int main(int argc, char *argv[]){...}
- 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。
可变形参
initializer_list
提供的操作(C++11
):
操作 | 解释 |
---|---|
initializer_list<T> lst; | 默认初始化;T 类型元素的空列表 |
initializer_list<T> lst{a,b,c...}; | lst 的元素数量和初始值一样多;lst 的元素是对应初始值的副本;列表中的元素是const 。 |
lst2(lst) | 拷贝或赋值一个initializer_list 对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 |
lst2 = lst | 同上 |
lst.size() | 列表中的元素数量 |
lst.begin() | 返回指向lst 中首元素的指针 |
lst.end() | 返回指向lst 中微元素下一位置的指针 |
initializer_list
使用demo:
void err_msg(ErrCode e, initializer_list<string> il){
cout << e.msg << endl;
for (auto bed = il.begin(); beg != il.end(); ++ beg)
cout << *beg << " ";
cout << endl;
}
err_msg(ErrCode(0), {"functionX", "okay"});
- 所有实参类型相同,可以使用
initializer_list
的标准库类型。 - 实参类型不同,可以使用
可变参数模板
。 - 省略形参符:
...
,便于C++
访问某些C代码,这些C代码使用了varargs
的C标准功能。
返回类型和return语句
无返回值函数
没有返回值的 return
语句只能用在返回类型是 void
的函数中,返回 void
的函数不要求非得有 return
语句,因为这类函数会隐式执行return
有返回值函数
return
语句的返回值的类型必须和函数的返回类型相同,或者能够隐式地转换成函数的返回类型。- 值的返回:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
- 切记!不要返回局部对象的引用或指针。
- 引用返回左值:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。
- 列表初始化返回值:函数可以返回花括号包围的值的列表。(
C++11
) - 主函数main的返回值:如果结尾没有
return
,编译器将隐式地插入一条返回0的return
语句。返回0代表执行成功
返回数组指针
Type (*function (parameter_list))[dimension]
- 使用类型别名:
typedef int arrT[10];
或者using arrT = int[10;]
,然后arrT* func() {...}
- 使用
decltype
:decltype(odd) *arrPtr(int i) {...}
- 尾置返回类型: 如果确实是要声明复杂的函数定义,那么可以在形参列表后面以一个
->
开始,这表示实际的返回类型在->
之后,而本该是声明返回类型的地方要放置auto
,如:auto func(int i) -> int(*)[10]
(C++11
)
函数重载
- 重载:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
- 形参个数不同、类型不同、底层
const
和类型可以转换才能发生重载,否则不行,特别注意,返回值不同是不能发生重载的 main
函数不能重载。- 重载和const形参:
- 一个有顶层
const
的形参和没有顶层const
的函数无法区分。Record lookup(Phone* const)
和Record lookup(Phone*)
无法区分。 - 如果形参是某种类型的指针或引用,那么是可以通过区分其指向的是常量还是非常量对象可以实现重载,此时的
const
视为底层const
,如Record lookup(Account*)
和Record lookup(const Account*)
可以区分。
- 一个有顶层
- 重载和作用域:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
特殊用途语言特性
默认实参
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
- 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。
- 一个函数只能声明一次,但是可以多次声明同一个函数,值得注意的是,在给定的作用域内一个形参只能被赋予一次默认参数,所以,如果多次为同一个函数声明,那么后续的只能为之前没有默认值的参数添加默认参数。
- 局部变量不能作为默认参数,除此之外,只要表达式的类型能够转换成形参所需的类型,就可以作为默认参数
内联(inline)函数
- 普通函数的缺点:调用函数比求解等价表达式要慢得多,因为调用前要先保存寄存器,并在返回时恢复,可能需要拷贝实参,程序需要转向一个新的位置执行
inline
函数可以避免函数调用的开销,可以让编译器在编译时内联地展开该函数,类似include
包含头文件inline
函数应该在头文件中定义,因为inline函数
可以多次定义,只是每次定义必须完全一致- 内联只是向编译器发出了一个请求,编译器可以忽略这个请求
constexpr函数
constexpr函数
:指能用于常量表达式的函数。constexpr int new_sz() {return 42;}
- 函数的返回类型及所有形参类型都要是字面值类型。
constexpr
函数应该在头文件中定义,因为constexpr函数
可以多次定义,只是每次定义必须完全一致constexpr
:是个关键字可以修饰变量和函数- constexpr函数在调用的时候,会被隐式的指定为内联函数
调试帮助
assert
预处理宏(preprocessor macro):assert(expr);
开关调试状态:
CC -D NDEBUG main.c
可以定义这个变量NDEBUG
。
void print(){
#ifndef NDEBUG
cerr << __func__ << "..." << endl;
#endif
}
函数匹配
- 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
- 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function),要求同名以及函数的声明在调用点可见
- 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function),要求形参和实参数量一致,且实参类型和形参类型相同或能够转换
- 寻找最佳匹配:基本思想:实参类型和形参类型越接近,它们匹配地越好,二者的转换的优先级
见p246
- 特别的,需要注意算术类型转换的潜规则:往大者转换,所以会有以下场景
void ff(int);
void ff(short);
ff('a'); //char 将会被直接提升至int,因而答案是调用f(int)
函数匹配和const实参
- 如果重载函数的区别在于他们的引用类型的形参是否引用了
const
或者指针类型的形参是否指向了const
,那么当发生调用的时候编译器通过实参是否是常量来决定选择哪个函数,如
Record lookup(Account&); // function that takes a reference to`
Record lookup(const Account&); // new function that takes a const
const Account a;
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
函数指针
- 函数指针:是指向函数的指针。
bool (*pf)(const string &, const string &);
注:两端的括号不可少。- 函数指针形参:
- 形参中使用函数定义或者函数指针定义效果一样。
- 使用类型别名或者
decltype
。
- 返回指向函数的指针:1.使用类型别名;2.使用尾置返回类型。
- 如何快速声明一个函数指针,只需要:
int f(int a);
int (*f)(int a); //在函数名字上加个*并且括号括起来
- 特别的,把函数名作为一个值使用时,改函数名自动转换成指针,所以我们可以直接使用指向函数的指针来调用函数,无须提前解引用指针,当然,即使解引用也无妨,如
bool b1 = pf("hello", "goodbye"); // calls lengthCompare
bool b2 = (*pf)("hello", "goodbye"); // equivalent call
bool b3 = lengthCompare("hello", "goodbye"); // equivalent call
函数指针形参
- 虽然不能定义函数类型的形参,但是形参可以是指向函数的指针,所以,有以下的情况
// third parameter is a function type and is automatically treated as a pointer to function
void useBigger(const string &s1, const string &s2,
bool pf(const string &, const string &)); //第三个参数自动转换成指向函数的指针
// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &s1, const string &s2,
bool (*pf)(const string &, const string &));
- 可以直接把函数作为实参来用,此时它会自动转换成指针
使用decltype
- 我们可以使用decltype来简化书写函数指针,如
string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);
// depending on the value of its string parameter,
// getFcn returns a pointer to sumLength or to largerLength
decltype(sumLength) *getFcn(const string &);
- 值得注意点是,当我们将decltype作用于某个函数时,它返回函数类型而非指针类型,因此需要显式的加上
*
来表明我们需要返回指针,而非函数本身,如上例
最后
以上就是坚强酸奶为你收集整理的【c++ primer第五版】第六章笔记第六章 函数的全部内容,希望文章能够帮你解决【c++ primer第五版】第六章笔记第六章 函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复