内联函数
编译时,编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率,但是占用更多内存。
- 当执行函数代码时间比处理函数调用机制时间长时,则节省的时间比例不高,不必声明为内联。当函数代码少,并且函数经常被调用,声明为内联可以提升程序运行效率。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果函数体内有循环/递归等,编译器优化时会忽略掉内联。
- inline在类外定义时,只需在类实现部分中使用inline限定符,必须在每个使用它们的文件中都定义,防止链接错误,因为inline被展开,就没有函数地址了,链接就会找不到。所以直接将定义放在头文件中最简单。
引用变量
引用是已定义的变量的别名,它和它引用的变量共用同一块内存空间。
其主要用途是用作函数的形参,通过这种用法使用原始数据而不是副本。
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
临时变量
如果引用参数是const,则编译器将在下面两种情况下生成临时变量:
int main() {
const int& a = 1 + 3; // 1. 实参类型正确,但不是左值
// int& a = 1 + 3; 错误
const int& b = 3.14; // 2. 实参类型不正确,但可以转换成正确类型
// int& b = 3.14; 错误
return 0;
}
这两种情况下编译器都生成一个临时匿名变量,这些临时变量出了作用域后自动消失。
为什么常量引用这种行为可以,其他情况不行?
从编译器的角度来看,不难发现,如果不加const显然就有可能对变量进行修改,如果此时引用了临时变量,就无法对原来的变量进行修改,所以编译器默认只能用const的引用来指向临时变量。
C++11 右值引用
这种引用可以指向右值,使用&&声明
int main() {
int&& a = 3 + 1;
int&& b = 3.14 + 3;
cout << a << endl; // 4
cout << b << endl; // 6
return 0;
}
引用和指针
语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间;
底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int a = 0; int& ra = a; ra = 1;
int a = 0; int* pa = &a; *a = 1;
这两段代码的反汇编是一模一样的
默认参数
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现
- 缺省值必须是常量或者全局变量
函数重载
C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表参数个数 或 类型 或 顺序必须不同,可以通过函数重载实现编译时多态。
注意事项
- 由于函数传参为值拷贝,所以在没有最合适的匹配函数时,会生成临时变量来匹配对应实参。
void Fuc(int a) {}
int main() {
Fuc(3.14); // 生成临时变量3
return 0;
}
- 当没有最合适匹配,但能匹配的函数不唯一时,编译器会报错。
void Fuc(long a) {}
void Fuc(int a) {}
int main() {
Fuc(3.14); // 此时编译器并不知道调用哪个fuc函数,会出错
return 0;
}
所以匹配成功的前提是:能匹配的函数唯一确定。或者有多个能匹配,但只有一个最合适匹配
如:
void Fuc(const double a) {};
void Fuc(double a) {};
int main() {
Fuc(3.14); // 匹配时,不区分const,有两个最合适匹配,会出错
return 0;
}
void Fuc(const int a) {}
void Fuc(double a) {}
int main() {
Fuc(3.14); // 两个函数都能匹配,但是第二个函数最匹配
return 0;
}
重载引用/指针参数
void Fuc(int* a) {}
void Fuc(const int* a) {}
int main() {
const int a = 3;
int b = 1;
Fuc(&a); // 只有第二个最匹配,调用fuc(const int* a)
Fuc(&b); // 两个都匹配,但第一个最匹配,调用void fuc(int* a)
return 0;
}
void Fuc(double& a) {}
void Fuc(const double& a) {}
void Fuc(double && a) {}
int main() {
double a = 3.14;
int b = 3;
Fuc(a); // 可以匹配前两个,但是第二个最匹配,调用Fuc(double& a)
Fuc(b); // 只能匹配第二个,调用Fuc(const double& a)
Fuc(3.14 + 1); // 可以和二三匹配,但是和第三个最匹配,调用Fuc(double && a)
Fuc(3.14); // vs下调用Fuc(double && a) linux下会报错,应避免这样用
return 0;
}
原理
拿这段代码来说
void Print(int a, double b, char c) {}
void Print(int* a, double* b, char* c) {}
int main() {
Print(3, 3.14, 'a');
return 0;
}
在linux下执行命令objdump -d a.out |less反汇编。仅展示部分:
000000000040062d <_Z5Printidc>:
40062d: 55 push %rbp
40062e: 48 89 e5 mov %rsp,%rbp
400631: 89 7d fc mov %edi,-0x4(%rbp)
400634: f2 0f 11 45 f0 movsd %xmm0,-0x10(%rbp)
400639: 89 f0 mov %esi,%eax
40063b: 88 45 f8 mov %al,-0x8(%rbp)
40063e: 5d pop %rbp
40063f: c3 retq
0000000000400640 <_Z5PrintPiPdPc>:
400640: 55 push %rbp
400641: 48 89 e5 mov %rsp,%rbp
400644: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400648: 48 89 75 f0 mov %rsi,-0x10(%rbp)
40064c: 48 89 55 e8 mov %rdx,-0x18(%rbp)
400650: 5d pop %rbp
400651: c3 retq
0000000000400652 <main>:
400652: 55 push %rbp
400653: 48 89 e5 mov %rsp,%rbp
400656: 48 83 ec 08 sub $0x8,%rsp
40065a: 48 b8 1f 85 eb 51 b8 movabs $0x40091eb851eb851f,%rax
400661: 1e 09 40
400664: be 61 00 00 00 mov $0x61,%esi
400669: 48 89 45 f8 mov %rax,-0x8(%rbp)
40066d: f2 0f 10 45 f8 movsd -0x8(%rbp),%xmm0
400672: bf 03 00 00 00 mov $0x3,%edi
400677: e8 b1 ff ff ff callq 40062d <_Z5Printidc>
40067c: b8 00 00 00 00 mov $0x0,%eax
400681: c9 leaveq
400682: c3 retq
Print(int a, double b, char c)的函数签名变为<_Z5Printidc>
Print(int* a, double* b, char* c)的函数签名变为<_Z5PrintPiPdPc>
验证其他代码后,可以发现大概是int->i,long->l,char->c,string->Ss…基本上都是用首字母代表。
编译器实际在底层使用被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。
linux 下函数名修饰规则:_Z + 名称空间 + 函数字符个数 + 函数名 + 类型首字符
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,
将该函数按照C语言规则来编译。
extern"C" void Print(int a, double b, char c) {}
int main() {
Print(3, 3.14, 'a');
return 0;
}
在linux下执行命令objdump -d a.out |less反汇编。仅展示部分:
000000000040062d <Print>:
40062d: 55 push %rbp
40062e: 48 89 e5 mov %rsp,%rbp
400631: 89 7d fc mov %edi,-0x4(%rbp)
400634: f2 0f 11 45 f0 movsd %xmm0,-0x10(%rbp)
400639: 89 f0 mov %esi,%eax
40063b: 88 45 f8 mov %al,-0x8(%rbp)
40063e: 5d pop %rbp
40063f: c3 retq
由于C语言只是单纯的函数名。因此当工程中存在相同函数名的函数时,就会产生冲突。