【C++初阶】第一站:C++入门基础(中)-1
https://developer.aliyun.com/article/1456970?spm=a2c6h.13148508.setting.29.2e124f0eXM1nTd
引用特性
🚩引用在定义时必须初始化
🚩一个变量可以有多个引用
int main() { int a = 0; int& b = a; int& c = a; int& d = b;//给别名取别名,实际上是同一块空间。 int x = 1; //赋值 b = x; return 0; }
代码执行变化:
🚩引用一旦引用一个实体,再不能引用其他实体
int main() { int a = 0; int& b = a; int x = 1; int&b = x; return 0; }
代码执行:
使用场景
传引用返回和传值返回:
以下的两者返回的方式有什么区别呢?
答:这两种情况的区别,在于传值调用在函数销毁时有寄存器,传引用调用没有寄存器保存值,因为是同一个空间,引用不同于传值和传址,既然直接传的就是这个空间本身,因此既不用创建临时拷贝,也不需要传变量地址。而是直接变量进行赋值。
💥先来看传值返回:
用了一个全局的寄存器eax把返回值保存起来,待Count函数栈帧销毁后,回到主函数main,再将寄存器里面的值赋值给ret
💨传引用返回:
这个n的别名,出了作用域就销毁(这意味着返回的值是对已被销毁的变量的引用),还给操作系统了,在还给操作系统的时候,可能将这块空间里面的值给清理了,变成随机值了。由于Count函数的返回值是无效的(引用已被销毁),所以打印ret的值将导致未定义的行为。它可能打印1,也可能打印随机值,或者可能导致程序崩溃。
💢那如果对代码再修改一下呢:
这段代码是非法的。当函数 Count() 执行完毕后,局部变量 n 将被销毁,引用 ret 将会成为悬空引用(dangling reference),它指向了已经归还给操作系统的空间,该空间里面的值可能已经被初始化为随机值。因此,将对n的引用返回给调用函数是无效的,导致未定义的行为。
总结:
传引用的第一个示例还是第二个示例中,代码都是非法的,并且会导致未定义的行为
如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。
只有当出了这个作用域,这个对象还在的情况下,才可以加引用比如便用static将变量设成静态变量,或是全局变量,函数生命周期就不会影响到引用
代码示例:
//传引用调用 int& Count() { int n = 0; n++; return n; } int main() { int& ret = Count(); //这里打印的结果可能是1,也可能是随机值 cout << ret << endl; cout << ret << endl; return 0; } //传值调用 int Count() { int n = 0; n++; return n; } int main() { int ret = Count(); cout << ret << endl; cout << ret << endl; return 0; }
当变量前面有很大一块空间被占用时,有可能不会被覆盖:
写一个相加两个变量值的代码:
代码实现:
#include<iostream> #include<assert.h> 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; }
解析:
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效
率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
传引用传参(任何时候都可以用) 1、提高效率 2、输出型参数(形参的修改,影响的实参)
#include <time.h> #include<iostream> struct A { int a[10000]; }; A a;//全局变量!!! // 值返回 A TestFunc1() { return a; }//全局变量是可以使用传引用返回的!!! // 引用返回 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作为函数的返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); return 0; }
值和引用的作为返回值类型的性能比较
传引用返回(出了函数作用域对象还在才可以用)-- static修饰的,全局变量,堆空间等等 1、提高效率 2、修改返回对象
#include <time.h> struct A { int a[10000]; }; A a; // 值返回 A TestFunc1() { return a; } // 引用返回 A& TestFunc2() { return a; } void TestReturnByRefOrValue() { // 以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 以引用作为函数的返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { TestReturnByRefOrValue(); return 0; }
关于顺序表的读取与修改
c语言接口
struct SeqList { int a[10]; int size; }; //C的接口设计 //读取第i个位置 int SLAT(struct SeqList* ps, int i) { assert(i<ps->size);//防止越界 //... return ps->a[i]; } //修改第i个位置的值 void SLModify(struct SeqList* ps, int i, int x) { assert(i< ps->size); // ... ps->a[i] = x; }
可以看待以上代码,C语言实现读取和修改结构体成员--数组元素时,是非常繁琐的,实现功能就要编写一个功能函数,但是如果换成c++的引用,那就可以一个函数实现两个功能
Cpp的接口设计:
代码示例:
CPP接口设计 //读 or 修改第i个位置的值 #include<iostream> #include<assert.h> int& SLAT(struct SeqList& ps, int i) { assert(i < ps.size); return(ps.a[i]); } int main() { struct SeqList s; s.size = 3; SLAT(s, 0) = 10; SLAT(s, 1) = 20; SLAT(s, 2) = 30; cout << SLAT(s, 0) << endl; cout << SLAT(s, 1) << endl; cout << SLAT(s, 2) << endl; return 0; }
特别注意:
常引用
在引用的过程中: 1.权限可以平移 2.权限可以缩小 3.权限不能放大!!!
int main() { const int a = 0; //权限的放大 int& b = a;//这个是不行的!!! //权限的平移 const int& c = a; //权限的缩小 //形象地理解: int x = 0;//齐天大圣 const int& y = x;//戴上紧箍咒的孙悟空 return 0; }
赋值:
int b = a;//可以的,因为这里是赋值拷贝,b修改不影响a
类型转换:
在c/c++里面有个规定:表达式转换的时候会产生一个临时变量,具有常性
以及函数返回的时候也会产生一个临时对象