1. C++语言使用调用操作符(一对括号)实现函数调用。调用操作符的操作数是函数名和参数列表,返回值是函数的返回值。(醍醐灌顶啊~~)
2. 函数调用时的行为:
- 实参入栈:默认行为是从右到左入栈
- 现场保护:返回地址、当前函数的栈基址、各种寄存器入栈
- 执行函数
- 恢复现场:各种寄存器出栈(恢复寄存器)、栈基址出栈(恢复栈基址)、返回地址出栈,以及各种清理工作
- 在返回地址继续执行原来的函数
3. 形参的名字是可选的。所以在函数声明时可以一个形参名都不给,在函数定义时,函数体中不使用的参数也可以不起名。但是注意在调用的时候,即使形参没有名字也必须给它个实参,否则调用失败。
void func1(int, int, int); //ok,没名字也可以
void func1(int a, int b, int) //ok,不使用就没问题
{
cout<<a<<b<<endl;
}
func1(1,2,3); //必须传入3个实参,不能因为第三个没有名字就不传
4. 自动对象、局部对象和局部静态对象:
- 自动对象:只存在于语句块执行期间的对象被称为自动对象。由于自动对象出了块之后会被自动销毁,所以千万不要返回自动对象的指针或者引用。
- 局部对象:函数形参和函数体内部定义的对象统称为局部对象。非静态的局部对象都是自动对象。
- 静态局部对象:局部对象前加static就得到了静态局部对象。静态局部对象在程序第一次运行过它的定义时创建,直到程序退出时才被销毁,包含它定义的函数退出也不会对它造成影响。静态局部变量会被默认初始化为0.
string& func(int a) //a是局部对象也是自动对象
{
string b = "abc"; //b是局部对象也是自动对象
static int cnt = 0; //静态局部对象,非自动对象
cnt++; //函数每被调用一次,就累加一次,不受函数退出的影响
return b; //错误,b已被销毁
}
5.传值、传引用、传常量引用
参数 | 行为 | 接收范围 | |
值 | 可修改,但不影响原值 | 拷贝 | 对象,表达式,常量值/表达式 |
引用 | 可修改,影响原值 | 传入地址,节省空间 | 对象 |
常量引用 | 不可修改 | 传入地址,节省空间 | 对象,表达式,常量值/表达式 |
所以在参数不被修改的情况下,对于Class和Struct的对象(除STL迭代器和函数对象之外)尽量使用const引用形参,因为它可以接受更大的传入范围,同时与其他库函数配合的更好。此外,由于常量引用实际是传递一个32位/64位的地址,相对于内置类型(char)占用空间更多,因此对于内置类型传递值更高效。
6. 当我们为函数传入一个数组时,实际上传入的是数组首元素的指针。
void func(char a[]);
void func(char *a); //与上面的等价
当我们传入多维数组时,编译器同样会把它转化为数组首元素的指针,也就是转化成一个指向固定大小数组的指针。所以数组的第二维以及后面的数字都不能省略,否则无法通过编译。
void func(int matrix[][10]);
void func(int (*matrix)[10]); //与上面的等价
void func(int matrix[][]); //错误,不能省略第二维
7. 含有可变长度参数的函数:
- C++11风格:利用列表初始化和新增的initializer_list类型(对比vector,成员都为常量并且本身也无法被修改(根本没有修改的成员函数...))实现可变参,唯一需要注意的是initializer_list是个模板,它要求传入的数据都为同一类型。注意:必须使用列表的形式向initializer_list传递值。
void func(int i, initializer_list<int> il){...} func(0,{1,2,3,4,5}); //ok,编译器使用{}和它里面的东西初始化initializer_list
- C语言风格:int function(int para, ...)
8. 如果函数返回指针、引用或者类对象,我们可以在它的调用后面直接跟上点操作符或者箭头操作符;如果函数返回的是引用,那么函数调用可以作为左值放在复制表达式的左边。
string &GetString(vector<string> &buffer, size_t pos)
{
return buffer[pos];
}
auto sz = GetString(buffer, pos).size(); //ok,左结合
GetString(buffer, pos) = “replacement”; //ok,返回的引用可以作为左值
9. 因为c++11中新增了列表初始化,所以可以在函数中return 一个{}包围起来的列表,只要它能够初始化返回值即可。
vector<string> func()
{
return {"This","is","a","test."};
}
10. 在cstdlib中定义了两个main函数的返回值:EXIT_SUCCESS和EXIT_FAILURE,它们两个是预处理变量(也就是#define出来的),与机器无关,能更好的保障跨平台编译。(话说:我真没用过,知错能改,马上使用~~)
11. 返回数组指针的函数:
int (*p) [10]; //指向大小为10的int型数组的指针
int (*func(int i)) []; //返回数组指针的函数
又一个必须从中间向两边读取的奇葩...在c++11中,提供了两种方式来简化这个定义,让人类更加容易读懂:
- 尾置返回类型:(清晰多了~~)
auto func(int i) -> int *[10]; //把返回类型放在最后,用一个->指向它,在返回类型本该出现的地方放一个auto
- 使用decltype简化:(临时typedef的感觉?)注意:decltype一个数组会得到一个数组类型,函数无法返回数组类型,所以必须在后面加个*,变成对应的指针类型
int a[10]; decltype(a) *func(int i); //注意加*
12. 尾递归:在函数的return语句中调用函数(函数不能在表达式中,单独的调用)被称为尾调用,如果调用的是自身,则称为尾递归。有些编译器支持尾递归,函数使用尾递归时调用函数不入栈,而是位置直接被被调用函数覆盖,所以即使递归层数很深也不会引起栈溢出。
13. 重载的函数在程序员看来是同一个函数名,但是编译之后其实是两个名字。例:int test(int a, int b)和float test(float a, float b),编译之后函数名可能是_int_int_test和_float_float_test。
14.main函数不能重载(汗!)
15.仅仅返回值类型不同的两个函数不构成重载,非引用/指针形参与const非引用/指针形参不构成重载,例:
int func(int)
bool func(int)
//不构成重载,只有返回值类型不同
int func(int)
int func(const int)
//不构成重载,int和const int对于func来说效果一样
int func(int&)
int func(const int&)
//构成重载,int& 与 const int&是不同的类型
16. 重载函数应该在同一个作用域中声明,否则会造成屏蔽而不是重载。
17.确定重载函数的步骤:
1).寻找与调用同名的函数,作为候选函数;
2).选择具有与实参个数相同形参的,类型匹配或者可转换的函数,作为可行函数;
3).寻找最佳匹配(a精确匹配,bconst转换,c类型提升(无精度损失),d类型转换(有精度损失),e类类型转换(单参构造函数))
4).找到多个最佳匹配即产生二义性,报错。
18. 默认实参:如果一个形参具有默认实参,那么它后面的所有形参都必须具有默认形参,具有默认实参的函数最好定义在头文件中以减少冲突。注意:不能使用局部变量作为默认实参;该函数在调用时才会计算默认实参的值。(也就是说,在调用前修改作为默认实参的全局变量,会修改调用时默认的值)。
extern int i;
void func(int j = i){...} //ok,全局变量可以作为默认实参
void dosth()
{
i = 10;
func(); //默认参数现在变为10
}
19. inline(内联)函数会在被调用点上展开它的代码,这个展开通常在编译期实现,所以它应当被定义在头文件中以帮助编译器更快的找到并减少冲突;另外,在类的声明中定义的函数被默认为内联函数。不要滥用inline,因为这会导致代码规模急剧的膨胀。注意,只有在开启编译器优化的时候才有inline的行为(g++ -O0就没有,而-O2就有,并且用启发式算法决定函数是否值得inline,同时考虑文件大小,而-O3则不考虑文件大小。)#pragma auto_inline(on/off)只有在编译器开启inline的情况之下才会有作用。
20. constexpr函数:在C++11中,可以使用关键字 constexpr修饰函数,允许用户保证这个函数是编译期常数。constexpr函数要求:其返回值和形参都是字面值(返回值不能是void);函数体中只能包含一个return语句(不是只能返回一个常量,也可以返回一个编译期可知的表达式)。当使用constexpr函数时,编译器会负责检查它是否符合要求,如果你传入一个局部变量,那么编译器会报错。constexpr函数默认是inline的,所以最好也定义在头文件中。
const int c = 2;
constexpr int get_c(int i){return i*c;} //ok
constexpr int get_cc(int i){return i*get_c(i);} //ok
21. assert:assert是个预处理的宏,接受一个表达式,如果表达式为假,则打印信息并终止程序的运行。(天~~我一直以为是个函数呢....)
22. NDEBUG:#define了NDEBUG之后assert就不再工作了,我们也可以编写类似的语句来让NDEBUG统一阻止调试语句的执行。预处理器定义了_ _func_ _、_ _FILE_ _、_ _LINE_ _、_ _TIME_ _和_ _DATE_ _几个const char*类型的名字,以帮助我们打印调试信息。(这个好用啊~~)
23.函数指针:
//声明
bool (*pf) (int, int);
//初始化
bool comp(int a, int b){...}
pf = comp;
int a, b;
(*pf)(a, b);
//数组
bool (*pf[10]) (int, int);
//做参数
int func(int, int, bool(int, int));
//做返回值
bool (*func(int, int)) (int, int)
//使用typedef能简化
typedef bool(*cmpfnc) (int, int)
cmpfnc pf;
int func(int, int, cmpfnc)
cmpfnc func(int, int)
//使用decltype简化,注意decltype返回的是函数类型,需要加个*才是指针
decltype(comp) *func(int,int)