6.引用
知识点:
6.1引用的定义:
引用不是新定义一个变量,而是给已存在变量取了一个别名,在语法层面上编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。(就像绰号,叫真名和绰号是一样的)
引用的基本语法:引用的对象的类型+&+引用名 =引用的对象
//整形
int a = 0;
int& b = a;
//指针
int * ptr = NULL;
int*& rp = ptr;
细节(注意点):
1、在创建引用定义时必须初始化:int& b = a;
2、一个变量可以用多个引用(一个人在每个阶段都可能有不同的绰号,但都是指同一个人)
int main() { int a = 0; int& b = a; int& c = a; // b 、 c 都是a的引用 }
3、只能一开始进行引用的初始化指定引用对象、后面无法进行修改。(后面修改也只是赋值操作)
6.2使用场景:
引用做参数
输出型参数 ,像我们之前在C语言写链表来举例,当需要改变phead时就需要传一个二级指针,但此时我们用到了引用就不用再使用二级指针。
提高效率,因为引用的物理意义是一个变量的别名,在传参时并不用为其开辟栈帧所以可以提高一定的效率。
引用做返回值
但要小心,引用返回的对象不能在栈里面,否则当出栈帧时就会导致非法访问,一般用于静态区,堆区上的
可以修改返回值
提高效率,当引用做返回值的时候,返回的就是一个变量的别名,此时在传递回去的过程中就不需要创建临时变量(放到寄存器)的返回,这样就能减少损耗
练习:
1. 通过引用来进行数值的交换(在C语言中我们需要用到指针才能完成)
//指针时:void Swap(int * a, int * b) void Swap(int& a, int& b)//此时的a 、 b 是参数的引用 { int tmp = a; a = b; b = tmp; } int main() { int a = 10; int b = 20; cout << a << ' ' << b << endl; Swap(a,b); cout << a << ' ' << b << endl; return 0; }
6.3常引用问题
知识点:
对常数的引用时要注意的点(此处主要讨论的是const修饰的常变量)
在引用时,引用的类型相对于引用对象的类型来说只能平移或者降低(注意看注释,若看不懂后面有一个附加知识点)
一种特殊情况:
附:当需要整形提升/截断时他们在赋值前都会先创建一个临时变量,把这个提升、截断后的值先放到这个临时变量(寄存器)内,再把这个临时变量的值放到所要赋值的空间中(这样就避免把被赋值的值改变)
就是因为一个临时变量具有常性,所以需要加上const来修饰
临时变量具有常性 ,同理对于函数返回时也是一样的,当我们返回一个临时变量时是有常性的所以需要用到常变量const来修饰
此时我们还需要注意的点是:当返回值是引用时就不会创建临时变量了。
6.4引用的总结:
知识点:
在语法层面上我们理解的是引用并不开辟空间,但是在底层来说其原理和指针一样也是需要开辟空间的(我们理解了解底层即可平时就记住语法层面 : 就像老婆饼里没老婆)
底层逻辑一样
指针和引用的区别
没有NULL引用这种概念,而指针有空指针的概念
sizeof中指针和引用有不同,引用的结果就是引用对象类型的大小,而指针时地址的大小
引用和指针的加减不一样
有多级指针,没有多级引用
指针需要解引用,而引用操作系统自动处理了
引用比指针更加安全(指针可能会有野指针的问题)
7.关键字auto
知识点:
一种可以根据左边表达式自动推到出其类型然后定义给变量
具体就用实例来描述:
int main() { int x = 10; auto a = &x;//因为地址,所以auto是指针类型 auto* b = &x; auto& c = x; cout << typeid(a).name() << endl;//int * cout << typeid(b).name() << endl;//int * cout << typeid(c).name() << endl;//int (引用的类型不用加上&) *a = 20; *b = 30; c = 40; return 0; }
此处的 auto 和 auto * 其实是一样的
附:typeid(变量名). name() 这个可以识别输入变量的类型
细节(注意点):
auto的价值是:一般适用于对一些比较长的类型进行auto自动识别化
使用auto定义变量必须有初始化(在编译阶段编译器根据初始化表达式推倒出表示的类型,其实auto并不是一个类型,在编译后会将其改成实际的类型如#define定义宏类似)
auto也可以一次性声明多个变量,但是这几个变量类型必须是相同的(因编译器只对一个类型进行推导)
auto不能用在形参部分来识别类型(编译器无法对参数的实际类型进行推导)(c++14往后的可以作返回值)
auto不能直接对数组进行声明
8.范围for
知识点:
对数组进行遍历
语法: for (数组类型 变量名 : 数组名)
细节(注意点):
当把一个数组传进函数时其本质是一个指针(数组名首元素地址)所以在该函数内是不能去使用这个范围for的
通过练习描述:
int main() { int a[] = { 1,2,3,3,4,5,6,7,8,9,10 }; for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) { cout << a[i] << ' '; } cout << endl; for (auto x : a)// for (数组类型 语法for的变量名 : 数组名) { cout << x << ' '; } cout << endl; }
此时只是依次将数组a中的数据拷贝给x(所以拷贝的无法改变其数组内容)
若想改变在数组a的值就需要加上引用 如下:
9.内联函数
知识点:
内联函数的设立是为了减少一些重复创建栈帧的开销,以inline修饰的函数被叫做内联函数,该函数不会去开辟栈帧,会直接在调用内联函数的地方展开使用。(在C语言中这种简单重复使用的函数一般会用宏来实现,但宏也有其自己的缺点所以c++进行优化创造出来内联函数)
细节:
内联函数的具体展现:
当没有使用内联函数时:就会正常的去调用函数,开辟栈帧...
当使用内联函数后:调用内联函数的地方会直接展开使用(此时就没用call调用函数)。
内联函数对于编译器来说是一个建议性的操作,最后是否实现取决于编译器(对于编译器来说:如果是递归函数/比较长函数他们就不会同意变成内联函数)
注意对于一个较大的函数来说,不能使用内联函数,因为内联函数会导致程序的指令变多(直接正常调用此时指令就在函数内并不会变多,而内联函数会将函数展开就会导致指令反而变多,如:假如一个函数的指令是50个,然后又100处地方要调用这个函数,此时对于直接调用函数的指令就是100 + 50 ,假如是内联函数就变成了 100 * 50)
假如你要分源管理,此时内联函数不要声明和定义分离(因为内联函数不需要要调用就不会生成地址而这样就不会进入到符号表,若声明和定义分开就会导致链接错误找不到,直接就写定义即可)
10.指针空值nullptr
在c++中NULL并不表示空指针而是表示为0(NULL实际是一个宏代表0),所以在c++11中就新引入了一个关键字表空指针nullptr
本章完。预知后事如何,暂听下回分解。