6-1.面试题:
- 为什么C语言支持函数重载,而C++支持函数重载?
- extern "C'的作用
6-1-1.为什么C语言不支持重载,C++支持?C++是如何支持的?---函数名修饰规则不同
备注:这里由于博主还没有干到LInux,就不能给大佬们演示linux下函数名修饰规则的具体内容了:
C 语言中:
C++中:
6-1-2.extern "C'的作用
6-1-2-1.什么是中间件程序?为什么会有extern "C"?
在写项目的时候,有的时候会用到中间件程序(配合可执行程序的一些组件):
通常我们就会把它编译成静态库或动态库(比如.dll).
如果这个中间件程序是用C++写的,但是整体的程序时用C语言写的,虽然在编译成二进制的指令的时候,C和C语言都没太大差异(因为此时已经经历了各自编译器的编译),但是由于C语言和C++的函数名修饰规则,整体程序在找中间件程序(组件)中的函数的时候就会表示找不到.这时extern "C"的作用就凸显出来了.
6-1-2-2.extern "C"的作用和为什么可以通过extern "C" 解决这个问题?
extern "C" 的作用:让C++用C的函数名规则去找函数地址.
基石:C++兼容C的语法,C++知道C语言的函数名规则,所以在有C和C++的函数名规则冲突的时候,在C++程序中使用extern "C" +函数声明 ,就可以解决这个问题.
6-1-2-3.extern "C"的使用场景举例:
下面以谷歌自己用C++写的tcmalloc代替mallc ,然后写成了一个中间件程序,后来一个C语言程序想用这个中间件程序代替mallc时他遇到的问题和解决办法:
变式: 如果加完了extern "C",有同时有整体C++程序想使用这个被extern "C"修饰过了的中间件.这就可以将这个整体C++程序前加上extern "C".
7.引用
7-1.引用的基本使用(reference)
#include<iostream> int main() { int a = 10; int& b = a;//b是a的别名,b是a的引用 printf("%d\n", b); b = 100; printf("%d\n", a); }
注意:int& b=a;是取别名
而int* b=&a;是取地址
#include<iostream> using namespace std; void Swap(int* m, int* n) { int temp = *m; *m = *n; *n = temp; } void Swap(int& m, int& n) { int temp = m; m = n; n = temp; } int main() { int a = 10; int b = 20; //传地址交换 Swap(&a, &b); printf("a=%d\tb=%d\n", a, b); //传引用交换 Swap(a, b); printf("a=%d\tb=%d\n", a, b); return 0; }
7-2.引用的特性 :
- 一个变量可以有多个别名
- 引用必须初始化(但是指针没有初始化的规定)
- 引用一旦引用了一个实体后,就不能再引用其他实体
int main() { int a = 10; //一个变量可以有多个别名b,c,d int& b = a; int& c = a; int& d = a; //引用必须初始化 int& e = a; //不是让e变成r的别名,而是把r赋值给e int r = 20; e = r; printf("%d\n", e);//20 return 0; }
7-3.常引用
int main() { //const权限 const int a = 10;//这里的a是可读不可写 int& ra = a;//错,权限的放大不允许 //错在把可读不可写的变量a给一个可读可写的引用 const int& ra = a;//对 int b = 10;//-可读可写 const int& rb = b;//对,权限的缩小允许-可读不可写 return 0; }
只要是有类型差异在赋值转换时都会产生临时变量
转换:转换的是中间的临时变量,而不是c
//隐式转换(权限的应用) int c = 10; double d = 1.1; d = c;//对,c语言隐式类型转换,但还是一样是有临时变量(double)类型 //double& rc = c;//错,错因是因为是const double 类型的临时变量给了double类型的变量 const double& rc = c;//对
备注:这里rc引用的不是C,因为类型差异(字节都不一样),rc引用的其实时中间的那个临时变量.
7-4.引用的场景
7-4-1.作参数
7-4-2做返回值(传引用返回)
先看看之前我们学过的传值返回:
传值返回返回的是对象c的拷贝
这里说明了实际上是函数返回值是通过产生一个临时变量(const修饰)来临时保存,然后赋值给ret。
传引用返回:
传引用返回的是对象c的引用
这里返回值是int&,也就是返回的是c的引用,也就是c的别名,然后c的别名又是ret的别名
函数栈帧问题:
int& Add(int a, int b) { int c = a + b; return c; } int main() { const int& ret = Add(1, 2); Add(5, 7); cout << ret << endl;//12 return 0; }
下面即是在main函数里没有用ret接收Add(5,7)的返回值,ret还是被改为了12,那是对因为ret是栈上已经销毁的变量c的引用 。
但是如果是传值返回:调用了Add(5,7),还是3
或者把c定义在static,静态常量区上:
越界不一定报错:
1.越界读基本不报错,因为编译器检查不出来
2.越界写,可能报错,而且是抽查,就像查酒驾,一般是查可能性大的地界查--抽查
传引用返回的优点:
因为传值返回传的是对象的拷贝,但是传引用返回是返回的是对象的别名,可以提高效率,这和传值调用和传址调用很像。
指针和引用的异同:
int main() { int a = 10; //语法上,这里是给a起了一个别名,而是新定义了一个符号,并没有额外开空间 int& ra = a; ra = 20; //语法上,这里是定义了内存是4个字节的变量存放a的地址 int* pa = &a; *pa = 20; return 0; }
实际从汇编实现的角度,引用的本质类似指针取地址的方式实现的(语法层和底层是隔离开的)---了解即可
指针和引用的不同点::
内存开辟角度(概念上)
初始化角度
实体对象更改角度
空指针角度
多级指针角度
引用更安全角度
8.内联函数
由C语言引入:
//C语言为了避免小函数开辟函数栈帧的开销--->提供了宏,预处理阶段展开 #define Add(x,y) ((x)+(y)) int main() { int x = 1, y = 2; int ret = Add(1, 2); printf("%d\n", ret); return 0; }
C++推荐使用频繁的小函数,定义成inline函数,没有函数的开销,只是在调用的时候展开
内联函数:这里结合了宏没有函数开销的优点,同时又丢弃了宏复杂和不支持调试的缺点。
inline int Add(int a, int b) { return a + b; } int main() { int x = 1, y = 2; int ret = Add(1, 2); printf("%d\n", ret); return 0; }
为什么不是将所有的函数定义成内联函数?(内联的缺陷)
1.因为内联函数的本质是通过通过牺牲展开函数,增加主函数代码量(指令变多,导致编译出来的程序变大,备注:指令变多不一定耗时长)来提高效率,而减少函数调用的开销,从而提高效率的。------>空间换时间所以适合将那些函数内部代码量比较少且频繁被调用的的函数定义成内联。当把大函数定义成内联时,编译器直接不搭理你的定义内联。备注:当调用1000次时,内联展开和调用函数的指令数是截然不同的。
2.内联不建议声明和定义分离,因为内联函数没有地址(直接展开了),会导致链接时找不到。