目录
一、引用
定义
引用是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
与之前学过的取地址共用符号:&在类型前加&
举例
特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
如举例所示
3. 引用一旦引用一个实体,再不能引用其他实体
常引用
在变量前 或 引用前加 const ,使其变成常量后加引用或变成常引用
取别名原则
对原引用的变量,权限只能缩小或相等,不能放大。
权限放大
取别名相当于改,是增大权限的操作,常量是不支持的,因此会报错。
权限相等
权限缩小
注意
1.常量直接加别名需要加const
常量是不可改的,不加const相当于权限的放大,加const是权限相等的操作
2.给变量赋予不同类型的别名要加const
原因:不同类型间的相互转换,会发生截断或者提升,截断或提升之后的值是存放在临时变量中的。
而临时变量具有常性,上述例子中,c其实是临时变量的别名,具有常性,不加const属于权限的放大,加上const则是权限相等的操作。
使用场景
做参数
如下:可配合函数重载使用
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } int main() { int a = 5, b = 3; Swap(a,b); double c = 2.3, d = 5.5; Swap(c,d); return 0; }
可以减少拷贝,提高效率
做返回值
函数的返回值是一个临时变量
先来看一个例子:
Count函数的返回值,直接引用是不通过的
因为函数是先调用,完成后n会销毁,因此传给ret的是一个临时变量
而临时变量具有常性,要引用前面必须加const
如下:
引用作为返回值
函数返回类型是一个引用的话,那么返回的就相当于是 n 的别名,别名是不用开辟临时变量的。
用引用 ret 接收函数的返回值,此时的 ret 相当于是返回值 n 别名的别名。ret 和 n 是同一个地址。
证明:
但是上述代码是不正确的
存在非法访问!!!!
函数先调用,调用完后赋上新的别名,但是注意,函数调用完后,变量n就已经销毁了,空间已经还给系统了,但是我们通过别名访问到了已经还给系统的空间,属于非法访问
再次调用,就会发现同样的地址,其中的内容已被系统更新
通过上述内容,我们知道,引用返回可以减少拷贝,提高效率,但是我们如何防止这种 “野引用”问题的出现:
保证返回变量不销毁,内存空间不还给系统即可,可以用到static函数
总结
如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回,防止出现越界问题。
和指针的比较
区别
1.引用在定义时必须初始化,指针无要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 没有NULL引用,但有NULL指针
4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全底层相同
二、内联函数
概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
在c语言中,如果一个函数多次调用,那么每次调用都要开辟栈帧,效率会大幅降低。c语言的解决方案是用宏来实现函数的功能,在调用时直接替换展开,就没有了压栈开销,提高效率。
为什么要用inline?
在c中,函数是容易写的,但是用宏来实现函数,往往会出现问题,最常见的就是替换后的优先级问题。
例如:ADD函数
int ADD(int x,int y)
{
int sum = x + y;
return sum;
}
用宏来实现:
#define ADD(x,y) ((x)+(y))
错误示范:
//和函数搞混 ,分号滥用
//#define ADD(int x,int y) return x+y;
//#define ADD(x,y) return x+y;
//#define ADD(x,y) (x+y);
//不带括号,会出现优先级问题
//#define ADD(x,y) x+y
而c++中 inline 的出现就是为了解决宏难理解,且容易写错的问题。
使用示例
直接在函数前加 inline 即可,不容易写错。
inline int ADD(int x,int y)
{
int sum = x + y;
return sum;
}
inline可调试
c语言中宏是不支持调试的
但c++中的inline是可以调试的,需要改一些属性。
在Debug版本下,inline 和 宏 在编译中都不会展开,直接是 call ADD
更改如下
完成上述操作后,再次进行调试,转到反汇编:
对宏,没有什么改变
对inline,可支持调试
特性
1.inline是一种以空间换时间的做法,省去调用函数开辟栈帧。代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2.编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。解决方案,将声明和定义写在一起
或者
再调用
三、auto关键字
定义
在c和早期c++中auto关键字几乎不怎么用,只是用来修饰局部变量,并且常被忽略,如下:
在c++11中,auto 有了新的功能:
作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
意思就是,当我们不知道一个变量的类型时,将他前面的类型替换为auto,编译器会根据自动推导出此数据的类型。
用法
一般例子
#include <iostream>
using namespace std;
int Test()
{
return 6;
}
int main()
{
//这里编译器会自动识别a是int类型
auto a = 2;
double b = 3.8;
//编译器会自动识别出c是double类型
auto c = b;
//自动推出ret是int类型
auto ret = Test();
return 0;
}
注意
不能直接auto+变量,不允许没有初始化的变量使用auto
例如:
即使直接使用a也不行
输出变量类型
如何判定编译器已经识别出来变量的类型呢?
记住输出变量类型的固定格式:
typeid(变量名).name()
指定auto的类型
指定auto为指针和引用
同一行定义多个变量
变量必须是相同的类型
否则会报错编译器会推导出第一个的变量的类型并使用,会产生冲突
定义时变量须是相同类型
auto正确用法
1.当类型特别长时,可以使用auto让编译器直接进行推导,这里在之后的笔记中会记录下来。
2.范围for循环
范围for循环
依次自动取数组中的数据,赋值给变量e,自动结束,更加方便
在这里可以对比一下更改数组内容时的操作
用c语言的for更改数组的值
那么c++中局部for循环可以直接做到吗?
原因:
e是一个变量,对变量进行操作,是不影响原数组中的数据的,因此这样写,数组中的数据不会被影响。
变量可以随便取,不用非得是e
解决方法:引用
既然e是变量,对原内容无影响,那么可以将变量变为数组元素的别名
对e操作即是对数组内容的操作
方法如下:
范围for循环必须使用auto吗?
不是的,只要是数组元素类型即可,整型数组--int ,字符类型--char ……
什么数组类型都可以用auto,更加统一方便
auto不能推导的场景
1.不能做函数参数和函数返回值
// 编译失败 void Test(auto a) {} //auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
2. auto不能直接用来声明数组
void Test() { int a[] = {1,2,3}; auto b[] = {4,5,6}; }
四、指针空值nullptr(C++11)
c++中空指针推荐使用 nullptr
因为在c++中,NULL和 0是不规范的
NULL实际是一个宏,在传统的C头文件(stddef.h)中会有:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
因此c++中NULL和0是一个东西,会有缺陷,比如:
void f(int x)
{
cout << 5 << endl;
}
void f(int* x)
{
cout << 6 << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
在我们的认识中,0是整型 ,输出5
NULL 是空指针,应输出6
结果并非预期值
用nullptr可以解决这个问题
因此在c++中写空指针的话,推荐使用nullptr