注意:引用的价值无法再这一节中全部说完,引用的更大价值在类和对象当中有所体现
前置知识点:函数栈帧的复用
首先请大家看一句话:
时间是一去不复返的
而空间是可以重复利用的
结合我们的日常生活,这句话没毛病,同样的,在C和C++中也是如此
其中空间是可以重复利用的这一点就被函数栈帧的复用所深刻地体现出来了
其中函数栈帧的销毁并不是说把这块内存空间销毁了,而是把这块内存空间的管理权归还给操作系统了,而申请内存空间就是向操作系统申请一块内存空间的管理权
释放空间就像是酒店退房间一样,退了的房间还能再租给下一个客人
下面写一份代码让大家更清晰的看一下
如图我们可以看出test1函数跟test2函数相继调用,在test1函数的栈帧销毁之后,再建立了test2函数的栈帧
我们发现a和b的地址相同,这也就说明了函数栈帧的复用
为什么a和b的地址会相同呢?
1.函数栈帧的复用
2.a和b的都是同大小的变量
如果我们修改一下test2函数的代码
我们发现,a和x的地址相同,但是a跟b的地址不相同了,
这个说明了即使函数内部所使用的空间大小不同,但是依然会进行函数栈帧的复用
所有的函数相继调用时都会复用上一个战帧 只不过开的栈帧的大小不同
这里先介绍一下函数栈帧的复用,为了后面讲解引用作为返回值的地方打下基础
前置知识点:类型转换时产生的临时变量
类型转换会产生临时变量!!!,临时变量具有常性,也就是不能再被修改了
1.含义
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
C++对C语言最大的改进:引用
因为C++设计者极其不喜欢指针,认为指针太麻烦
在语法上讲:引用就是取别名
2.代码形式
int main() { int a = 1; int b = a;//把a的值拷贝给b //c是a的别名 //c和a共用一块内存空间 int& c = a; b++; cout << a << endl;//1 c--; cout << a << endl;//0 //取多少别名都可以 //也就是说一个对象可以有多个引用 int& d = a; //这么取别名也可以(也可以给别名取别名) int& e = d; //a,c,d,e的地址是一样的 cout << &a << endl; cout << &c << endl; cout << &d << endl; cout << &e << endl; /* 0031FC38 0031FC38 0031FC38 0031FC38 */ return 0; }
3.引用的价值
1.传参数
引用还可以再传参的时候提升效率,不用再去额外的开辟空间
比方说我们要实现一个Swap函数交换两个整形变量
void Swap(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } //这两个函数构成重载 void Swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } int main() { int num1 = 1, num2 = 2; Swap(&num1, &num2); cout << num1 << " " << num2 << endl; Swap(num1, num2); cout << num1 << " " << num2 << endl; /* 2 1 1 2 */ return 0; }
学习了引用之后,我们就能对单链表进行优化了
单链表是不可以传入一级指针建表的,除非你虚拟一个哨兵位的头节点,但是不推荐这样做
这里传入的实参是一个一级指针,如果传入的那个一级指针是一个NULL指针的话,如果我们的形参也是一个一级指针的话,只能改变这个结构体的成员
(可以改变next指针,增长这个链表的长度)
无法改变这个指针本身(因为形参的改变不会影响实参)
也就是说如果传入的这个链表不是空链表的话,传一级指针可以
但是如果传入的是一个空链表,想要改变这个空链表,那么只能传二级指针
这里SListPushBack(LNode** pphead,int x);
中的*pphead就是传入的plist
但是传二级指针未免有些麻烦了吧,但是引用可以让我们继续只需要传一级指针
typedef struct ListNode { struct ListNode* next; int val; }LNode,*PLNode; //PLNode:结点指针的typedef,也就是一个结构体指针 LNode* CreateNode(int x) { LNode* newnode = (LNode*)malloc(sizeof(LNode)); if (newnode == NULL) { perror("malloc fail"); exit(-1); } newnode->val = x; newnode->next = NULL; return newnode; } //二级指针版本 void SListPushBack(LNode** pphead, int x) { LNode* newnode = CreateNode(x); if (*pphead == NULL) { //没有头节点 *pphead = newnode; } else { //找尾指针,再链接newnode LNode* tail = *pphead; while (tail->next) { tail = tail->next; } tail->next = newnode; } } //phead是plist2的一个别名,也就是说phead就是plist2 void SListPushBack1(PLNode& phead, int x) { PLNode newnode = CreateNode(x); if (phead == NULL) { //没有头节点 phead = newnode; } else { //找尾指针,再链接newnode PLNode tail = phead; while (tail->next) { tail = tail->next; } tail->next = newnode; } }; //phead是plist3的一个别名,也就是说phead就是plist3 void SListPushBack2(LNode*& phead, int x) { LNode* newnode = CreateNode(x); if (phead == NULL) { //没有头节点 phead = newnode; } else { //找尾指针,再链接newnode LNode* tail = phead; while (tail->next) { tail = tail->next; } tail->next = newnode; } }; void SListPrint(LNode* phead) { LNode* cur = phead; while (cur) { cout << cur->val << " -> "; cur = cur->next; } cout << endl; } int main() { LNode* plist1 = NULL; SListPushBack(&plist1, 1); SListPushBack(&plist1, 2); SListPushBack(&plist1, 3); SListPushBack(&plist1, 4); SListPrint(plist1); PLNode plist2 = NULL; SListPushBack1(plist2, 1); SListPushBack1(plist2, 2); SListPushBack1(plist2, 3); SListPushBack1(plist2, 4); SListPrint(plist2); LNode* plist3 = NULL; SListPushBack2(plist3, 1); SListPushBack2(plist3, 2); SListPushBack2(plist3, 3); SListPushBack2(plist3, 4); SListPrint(plist3); /* 1 -> 2 -> 3 -> 4 -> 1 -> 2 -> 3 -> 4 -> 1 -> 2 -> 3 -> 4 -> */ return 0; }
传参效率测试
那么引用传参比起值传参来效率的提升能有多大呢?
我们来测试一下:
可见引用传参比起值传参还是有一定效率提升的,不过比起指针传参来,效率提升并不是很大,因为传指针也就多开辟4或者8个字节而已