- 1.引用
- 1.引用概念
- 2.引用特性
- 3.const和引用的搭配
- 4.为什么const要和引用一起使用的使用场景
- 5.引用的效率和比较
- 6.引用和指针的不同
- 7.引用和指针的汇编代码一样吗?
- 2.内联函数
- 1.内联概念
- 2.为什么要用内联,不用宏
- 3.什么时候用内联
- 4.内联的特性
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:英雄联盟的"亚索",打的不好叫"孤儿索",也有的人叫他"剑豪"等等…这些就是别名
我们接下来就用代码和调试来下了解引用吧
代码
#includeusing namespace std; int main() { int a = 10; int& b = a; int* p = &b; int& d = b; return 0; }
c++里面的引用和c的&共有一个符号,放在类型后的&代表引用
我们接下来用调试看看引用了有什么效果
根据图片我们看到取别名的和被取别名的地址是一样的,这样就代表着任意加减一个别名的值,其他的值也会被加减
代码
#includeusing namespace std; int main() { int a = 10; int& b = a; int* p = &b; b += 1; int& c = b; return 0; }
调试结果
打个比喻:我玩的亚索五杀了,那么别人说:我的孤儿索五杀了。那么我的亚索是不是五杀了。
2.引用特性1.引用在定义时必须初始化
看图可以看到编译器给了红色警告
-
一个变量可以有多个引用
这个就不用演示了,之前的代表就已经解释了
-
引用一旦引用一个实体,再不能引用其他实体
代码
#includeusing namespace std; int main() { int a = 10; int& b = a; int* p = &b; int& c = b; int d = 20; b = d; return 0; }
运行结果
可以看到这里只是赋值,而不是改引用的地址,因为这里只是把d=20的值赋给abc而abc的地址不等于d的地址,所以引用一旦引用一个实体,再不能引用其他实体。这一点就和指针很大的区别,因为指针可能指向其他的地址而引用不能。
在这图片可以看到编译器报错了,为什么加了const过后就不能这样子引用了呢
因为加了const代表可读,而报错的代码就是改成可读可写,根据取别名原则,权限是不能放大的,所以就报错了
再来验证下取别名原则
代码
#includeusing namespace std; int main() { int a = 0; int& b = a;//权限不变 //取别名原则:对于原引用变量,权限只能缩小,不能放大 const int x = 20; //int& y = x;//权限放大 const int& y = x;//权限不变 int c = 40; const int& d = c;//权限缩小 return 0; }
运行结果
可以看到运行成功,取别名原则是没有问题的。
不能给常量取别名,看下面图片的报错
但是如果加上了const
可以看到没有问题
这个还不算奇怪最奇怪的是
这样子报错问题很明显,毕竟double和int的类型不同,报错很应该但是,加上了const
居然可以运行,虽然有警告。
那么为什么可以运行呢?是因为
e引用的是临时变量,而临时变量具有常性所以加了const可以运行。
而没加const等于之前的权限放大,有const修饰的变成没const修饰的这不就是权限放大
这个临时变量会一直存在,什么时候消失呢?e消失了临时变量也消失了,因为它的生命周期是和
e一样的
图片
可以看到f(a)后面的代码都有报错,而这些报错就是我们前面讲的权限放大问题,而加const就不会出现这些问题,而不加const就会出现不能全部值都接收只能接收部分值
当然有的人可能会问一个问题为什么要用引用,不用引用不行吗?
答案是可以,但是用了引用可以减少对参数的临时拷贝
1.引用做参数
代码
#includeusing namespace std; void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } void swap(double& a, double& b) { double tmp = a; a = b; b = tmp; } int main() { int a = 10, b = 20; swap(a, b); double c =1.1, d = 2.2; swap(c, d); return 0; }
可以看到用起来引用和重载,感觉很爽,不用解引用,也让人感觉二个swap调用的是同一个函数,虽然调用的不是同一个函数
5.引用的效率和比较代码
#includeusing namespace std; #include struct A { int a[10000]; }; void TestFunc1(A a) { } void TestFunc2(A& a) { } void TestRefAndValue() { A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } int main() { TestRefAndValue(); return 0; }
运行结果
可以看到用了引用的效率和没用引用的效率的区别
2.引用做返回值
c++返回值的作法
而为什么不直接用a返回要加个临时拷贝呢?
因为这是局部变量出了局部就会销毁
那么我们来证明一下使用了临时拷贝吧!
证明
可以看到用了引用报错了,这说明了用了临时变量,不过这样不严谨,因为没完全满足要求
我们来用上const,因为临时变量具有常性
没有报错这样子就完全证明了,返回值是由临时拷贝的
那么我们来讲引用返回的作用
代码
int& count()
{
static int a = 0;
a++;
return a;
}
int main()
{
int ret = count();
return 0;
}
这样子相当于
返回了一个别名,怎么证明
报错了因为引用不能变成临时变量的别名
加上引用呢?
没有报错证明完毕
那么这样子就代表了ret=a的别名那么它们的地址应该是一样的
图片
那么用引用的好处一样是,用返回值会有一个拷贝,用引用没有,因为返回值就是返回变量的别名
上面的那个代码是有问题的,什么问题野指针访问的问题,上面的a都销毁返回空间了,但是你还在用着它
代码
#includeusing namespace std; int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :" << ret << endl; return 0; }
打印结果
是7而不是3为什么,因为出栈后销毁,add(3,4)产生了新的栈帧所以打印7;
这里再打印一次就是随机值为什么,一样是栈帧销毁,重新调用栈帧所以是随机值
比较引用返回值效率的代码
#includeusing namespace std; #include struct A { int a[10000]; }; void TestFunc1(A a) { } void TestFunc2(A& a) { } void TestRefAndValue() { A a; // 以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } int main() { TestRefAndValue(); return 0; }
运行结果
6.引用和指针的不同- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
代码和解释
int main()
{
int a = 10;
//从语法的角度:ra是a的别名
//从底层的角度:它们用的是一样方式实现的
int& ra = a;
ra = 20;
//从语法的角度:pa存储了a的地址,开辟了4/8个字节
//从底层的角度:它们用的是一样方式实现的
int* pa = &a;
*pa = 20;
return 0;
}
证明图片
可以看到是一模一样的
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,
内联函数提升程序运行的效率。
为什么用内联,不用宏
宏的缺点
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
而出来内联就是为了解决宏的问题宏可以调式,还是函数,不容易错,还有安全性的检查
在频繁使用使用短小的函数的时候可以使用,只要在函数前面加上inline就是内联
代码
#includeusing namespace std; inline int add(int left, int right) { int z = left + right; return z; } int main() { add(1, 2); add(1, 2); add(1, 2); add(1, 2); add(1, 2); add(1, 2); add(1, 2); add(1, 2); add(1, 2); return 0; }
在汇编看到
这是debug模式下没有优化的inline ,注意看着这个call等下优化就没没有了
打开属性改成程序数据库
在改成只适用于inline(/ob1)
可以看到优化了call
为什么说内联是展开的,而不是进入函数里面运行
证明
自己调试用F11看能不能进去
- inline是一种以空间换时间的做法,省去调用函数额开销(建立栈帧)。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。(大概有多长建议10行左右,取决于编译器)
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到



