[C++]函数总结

内联函数

编译时,编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率,但是占用更多内存。

  • 当执行函数代码时间比处理函数调用机制时间长时,则节省的时间比例不高,不必声明为内联。当函数代码少,并且函数经常被调用,声明为内联可以提升程序运行效率。
  • 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;
这两段代码的反汇编是一模一样的

默认参数

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现
  3. 缺省值必须是常量或者全局变量

函数重载

C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表参数个数 或 类型 或 顺序必须不同,可以通过函数重载实现编译时多态。

注意事项
  1. 由于函数传参为值拷贝,所以在没有最合适的匹配函数时,会生成临时变量来匹配对应实参。
void Fuc(int a) {}

int main() {
	Fuc(3.14); // 生成临时变量3
	return 0;
}
  1. 当没有最合适匹配,但能匹配的函数不唯一时,编译器会报错。
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语言只是单纯的函数名。因此当工程中存在相同函数名的函数时,就会产生冲突。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值