Re:从零开始的 C++ 入門篇(二)基础精讲·中篇:引用

Re:从零开始的 C++ 入門篇(二)基础精讲·中篇:引用

◆ 博主名称: 晓此方-ZEEKLOG博客

大家好,欢迎来到晓此方的博客。

⭐️个人专栏:

◆数据结构系列

此方玩转算法与数据结构_晓此方的博客-ZEEKLOG博客

专治数据结构与算法疑难杂症_晓此方的博客-ZEEKLOG博客

◆C语言系列

专治C语言疑难杂症_晓此方的博客-ZEEKLOG博客

◆C++系列

此方带你玩转C++_晓此方的博客-ZEEKLOG博客

 ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰


目录

0.1前言&概述

一、引用

1.1引用的概念和定义

1.2引用的使用方式

1.2.3例

1.3取别名的方式

1.3.1一个变量可以有多个别名

1.3.2别名的别名

1.4引用的应用场景

1.4.1引用传参

1.4.1.1部分情况下代替一级指针

1.4.1.2与指针交错使用取代二级指针

1.4.2引用传返回值

1.4.2.1引用返回的优势

1.4.2.2引用返回的注意事项

1.5引用的特性

1.5.1不开辟空间

1.5.2引用的初始化

1.5.3引用的专一性

1.6扩展   

二,const引用

2.1概念补充

2.1.1权限

2.1.1.1权限放大与权限缩小

2.1.2临时对象和中间变量

2.1.2.1临时对象的常见创建情况

2.1.3常量与常化

2.2const引用的使用方式

2.2.1从常见的问题开始

2.2.1.1解决方法

2.2.2const引用与常量

2.2.3const与临时对象

2.2.3.1例一:隐式类型转换

2.2.3.2例二:表达式

2.2.3.3可能存在的误解

2.3const引用的应用价值——传参

三,指针和引用的区别

3.1是否开辟空间

3.2错误使用

3.3底层

3.3.1底层汇编代码逐句分析:

3.3.1.1为函数建立栈帧:(不用管)

3.3.1.3创建a变量

3.3.1.4指针操作

3.3.1.5引用操作

3.3.1.6主函数返回(不用管)


0.1前言&概述

        C++对C语言中的许多方面做出了重要修改,让语言变得易写且高效。如增加引用分担指针的工作,优化了代码的复杂性,增加了内联函数取代宏函数避免了很多宏替换问题同时提升运行效率。本期将接续上篇继续为大家带来C++基础部分更加深入的内容。讲解深入骨髓,细节无微不至。以真诚换真心,倾尽全力做到最好现在,让我们开始吧。


一、引用

1.1引用的概念和定义

        引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:水壶传中李逵,宋江叫"铁牛",江湖上人称"黑旋风";林冲,外号豹子头;一句话:引用就是取别名

1.2引用的使用方式

"类型&引用别名=引用对象"

和C语言也公用了一个符号:&(取地址)

引用在对象前面(例:int* b=&a)还是取地址
引用在类型后面(例:int&rb=b)变成了引用

1.2.3例

int& b=a; 

1.3取别名的方式

1.3.1一个变量可以有多个别名

引用就是取别名

一个整型a开辟4个字节空间,这四个字节的空间的别名可以是b,c也可以是d。

int a=0; int& b=a; int& c=a; int& d=a; d++;

      此时a变量所指向的整型空间有三个别名。由于别名都指向同一块空间改变别名就可以改变原值,对d++,同时会让a,b,c,d同时改变。

1.3.2别名的别名

可以给别名取别名。

int a=0; int& b=a; int& d=b;

此时:b是a的别名,d是b的别名,d相当于a的别名。

1.4引用的应用场景

        引用在实践中主要是于引用传参引用做返回值减少拷贝提高效率和改变引用对象时同时改变被引用对象

1.4.1引用传参

1.4.1.1部分情况下代替一级指针
//指针传递法 void swap(int* x,int* y) { int swp=*x; *x=*y; *y=swp; } //引用传递法 void swap(int& rx,int& ry) { int swp=rx; rx=ry; ry=swp; } int main() { int a = 5; int b = 6; swap ( &a , &b ) ;//指针传递 swap ( a , b ) ;//引用传递 }

C语言传递指针的方法弊端:

  1.      1、取地址和解引用的繁琐步骤
  2.      2、使用不透明性——感受上并非操作变量本身
  3. C++引用传参方法的优势:
  4.     1、传递即取别名,直接操作别名不需要解引用
  5.     2、使用透明性——感受直接操作变量本身
1.4.1.2与指针交错使用取代二级指针
//链表结构体 typedef struct ListNode { int val; struct ListNode* next; } LTNode, * PNode;
void ListPushBack(LTNode** pphead, int x)//最初的写法 { assert(pphead); ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); if (!newNode) { perror("malloc:fail"); exit(1); } newNode->val = x; newNode->next = NULL; if (*ppHead == NULL) { *ppHead = newNode; } else { ListNode* tail = *ppHead; while (tail->next) { tail = tail->next; } tail->next = newNode; } }

C语言二级指针的弊端:

1,不安全,需要assert断言

2,解引用复杂。新手不易理解

C++优化方法:

直接对链表的指针引用取别名:修改链表指针的别名就是修改链表指针本身

void ListPushBack(PNode& head, int x)//纯引用 { void ListPushBack_v3(PNode& pHead, int x) { PNode newNode = new ListNode; newNode->val = x; newNode->next = NULL; if (pHead == NULL) { pHead = newNode; } else { PNode tail = pHead; while (tail->next) { tail = tail->next; } tail->next = newNode; } } }

其实还有一种介于两者之间的方法:

  1. 二级指针法是指针的指针;
  2. 纯引用的法是指针的引用;
  3. 该方法时引用的指针;

既没有完全脱离指针使用的复杂,又不能更好的发挥引用的优势,不建议使用。

void ListPushBack(LTNode*& phead, int x)//指针-引用混合

1.4.2引用传返回值

       引用返回值的场景相对比较复杂,我们在这里简单讲了一下场景,还有一些内容后续类和对象章节中会继续深入讲解。
1.4.2.1引用返回的优势

传值返回:STTop返回值不可直接被修改

原理:

       传值返回返回值不会直接给到STTop调用点,而是先创建并传递给一个中间变量,这个中间变量具有常性质——不可修改,然后再传递给调用函数作为返回值。
int STTop(ST& rs) { assert(rs.top > 0); return rs.a[rs.top-1]; } STTop(st1) = 3;

传引用返回:STTop返回值可直接被修改

原理:

      传引用返回不经过中间变量,直接传递别名,可以直接修改别名
int& STTop(ST& rs) { assert(rs.top > 0); return rs.a[rs.top-1]; } STTop(st1) = 3;
1.4.2.2引用返回的注意事项

引用虽好,但是不是什么时候都可以用引用

int& func() { int a-0; int& ra=a; return ra; }

使用ra必然引发报错,a的栈帧已经被销毁。可以参考野指针。

: warning C4172: 返回局部变量或临时变量的地址

总结:

  1. 引用返回返回值可以直接被修改
  2. 直接返回引用同时还可以减少拷贝的支出
  3. 返回的值在堆上的时候可以用引用
  4. 返回值在栈上的时候不可以用引用

1.5引用的特性

1.5.1不开辟空间

引用是不开辟空间的,只对现有的一块空间给他一个别名

1.5.2引用的初始化

由于引用不开辟空间的特性,与指针不同,引用必须初始化。

1.5.3引用的专一性

引用一旦引用一个实体,再不能引用其他任何实体

int& b = a; int c = 20; // 这里并非让b引用c,因为C++引用不能改变指向, // 这里是一个赋值 b = c;

b始终是a的外号,就像江湖人称豹子头就是指林冲。不指别人

1.6扩展   

C++的引用和java的引用是完全不同的。

C++JAVA
C++的引用不能改变指向,而是“一块空间有多个名字”。java的引用更像指针,引用可以初始化也可以不初始化。
C++的引用是要和指针相辅相成使用JAVA的引用是C++的指针+引用的结合

在C++中如链表的指针结点next,仍然必须要用指针解决,因为指针可以改变指向

总结

对C++   :可以改变引用引用的东西但不能改变引用引用什么。

对JAVA:可以改变引用引用的东西又可以改变引用引用什么。

二,const引用

2.1概念补充

在理解const引用之前,首先需要补充一些官方或非官方概念:

2.1.1权限

        非正式术语,指一个类成员基于其访问说明符 所在类的继承关系以及访问上下文所拥有的可访问性 (accessibility)。它决定了程序的其他部分(如类的成员函数、派生类、友元或外部代码)是否能够访问该成员,以及访问时的范围(例如,是完全无法访问、仅能读取,还是可以读取和修改)。

(现在看不懂没关系,只需要知道最后一句话)

2.1.1.1权限放大与权限缩小
权限放大:

        非正式术语,
指在特定上下文通过特定机制扩大或提升了继承自基类的成员可访问范围,使得原本访问受限的成员变得更容易被访问。

例如:拥有只读权限限制的变量被使用某种方式使其使其看起来像是权限从只读变成了可读可写。

权限缩小:

       非正式术语
,指在特定上下文通过特定机制限制或降低了继承自基类的成员可访问范围,使得原本可以访问的成员变得难以或无法被访问

例如:拥有可读可写权限的变量被使用某种方式使其变得看起来像是从可读可写变成只读。

2.1.2临时对象和中间变量

中间变量:

       非正式术语,
通常指在代码执行过程中,为了计算最终结果或完成一系列操作而创建的、具有名称的变量。它可以是用户显式声明的局部变量,也可以是编译器优化过程中引入的临时存储。
临时对象

        正式术语,这是 C++ 标准中的正式术语。临时对象是不具有名称的、生命周期短暂的对象,通常在表达式求值过程中为了保存中间结果而创建。
2.1.2.1临时对象的常见创建情况

根据C++的底层需求来考量:

  1.   1、隐式类型转换
  2.   2、表达式赋值
  3.   3、调用函数的参数传递
  4.   4、函数传值返回

2.1.3常量与常化

常量:

       值在初始化后不能被修改的对象或表达式
常化:

        将某个对象、引用、指针或函数参数标记为 const 或 constexpr 的过程,以表明其不可变性或使其成为编译时常量

2.2const引用的使用方式

2.2.1从常见的问题开始

const int a = 10; int& ra = a;

发生错误:error C2440: “初始化”: 无法从“const int”转换为“int &”

这是经典的权限放大错误:const设置了a只能读不能写。所以不能用一个可以读可以写的别名来引用a,不然a权限就放大了。

2.2.1.1解决方法

      权限不可以放大:对于一个const限制的对象,需要一个const(相同权限)的别名来引用。

const int a = 10; const int& ra = a;

      权限可以缩小:别名ra的权限相对于a缩小了,但是a本身的权限不变,ra++会报错。

int a = 10; const int& ra = a; a++; ra++;

2.2.2const引用与常量

       不可以对常量进行引用,常量具有常性,储存在内存中的只读区域。对常量进行引用同样会导致权限扩大

       const引用可以解决这个问题

const int& ra = 30;

2.2.3const与临时对象

取别名是对临时对象取别名

注意,临时对象在被引用后它的声明周期会跟着引用走。不会销毁,只有引用的别名被销毁它才同时销毁。(声明周期绑定

2.2.3.1例一:隐式类型转换
int a = 10; int b = 20; const int& ra = a+b;

这里的a由于隐式类型转换会先给一个临时对象,这里的ra引用的本质是引用一个临时对象。

临时对象具有常性,只能读不能写。如果给它一个别名,会导致权限放大。必须使用const。

2.2.3.2例二:表达式
int a = 10; int b = 20; const int& rc = a+b; 

同理,表达式的运算结果会先放在一个临时对象中,临时对象不能被权限扩大

2.2.3.3可能存在的误解
const int a=10; int ra =a;

这只是单纯的拷贝。不是权限扩大。

2.3const引用的应用价值——传参

       以后我们会学一种东西叫模板——函数模板

template <class T> void func (const T& val){ }

       首先,模板的体量较大,传值传参拷贝消耗大,所以采用引用,但是引用又存在权限放大限制即——能传进来的东西非常有限。(常数、表达式、隐式类型转换的变量等都不能传递),引入const引用就变得格外重要——扩大了传递参数的范围

在C++的STL库中也有这样的设计;

顺序表的插入函数同样采用const引用来扩展参数传递的范围,提升函数的通用性


三,指针和引用的区别

指针和引用的区别非常容易在面试中考到!

3.1是否开辟空间

       引用不开辟空间(语法层面上我们应该这么认为)引用指向一个空间,而指针开辟一块空间并拷贝入指向对象的地址。

       因此会导致第二个区别:引用在初始化时引用一个对象后就不能再引用其他对象;而指针可以改变指向

3.2错误使用

       引用比指针更加安全,不需要assert断言。指针很容易出现空指针和野指针的问题,引用相对更加安全。

但是不代表引用绝对安全,引用也有"野引用"和"空引用"的情况,但是不多见:“野引用”的问题在前文中有所提及——函数返回栈空间引用

此外,“空引用”也会出现:

int* ptr =NULL; int& rb =*ptr; rb++;

3.3底层

虽然引用和指针有很多的区别,但是这些区别全都是在语法层面上的。

实际上:引用的底层和语法存在巨大的割裂——引用的底层和指针完全一致。

3.3.1底层汇编代码逐句分析:

//测试代码 int main() { int a = 0; int* pa = &a; (*pa)=1; int& ra = a; ra=2; return 0; }

转反汇编:

3.3.1.1为函数建立栈帧:(不用管)
00007FF7168B14D0 push rdi 00007FF7168B14D2 sub rsp,60h 00007FF7168B14D6 lea rdi,[rsp+20h] 00007FF7168B14DB mov ecx,10h 00007FF7168B14E0 mov eax,0CCCCCCCCh 00007FF7168B14E5 rep stos dword ptr [rdi] 00007FF7168B14E7 mov rax,qword ptr [__security_cookie (07FF7168BC080h)] 00007FF7168B14EE xor rax,rsp 00007FF7168B14F1 mov qword ptr [rsp+50h],rax 00007FF7168B14F6 lea rcx,[__DE5F0DB2_第一个输入输出@cpp (07FF7168C0076h)] 00007FF7168B14FD call __CheckForDebuggerJustMyCode (07FF7168B122Bh) 
3.3.1.3创建a变量
  1. 00007FF7168B1502  是创建的a变量所在地址
  2. []符号的作用类似于解引用——不直接访问[]内的对象名,而是访问[]内的对象地址指向的东西
  3. dword ptr开辟一个64位的空间创建变量a
  4. 指令:mov(move)移动。将值0移动给[a](a所指向的对象)赋值操作
 int a = 0; 00007FF7168B1502 mov dword ptr [a],0 
3.3.1.4指针操作
  1. lea:把[a]的值取地址传给寄存器rax
  2. mov:把rax寄存器中的值传给指针指向的值[pa]
  3. mov:把指针pa的值传递给rax寄存器
  4. mov:把1赋值给[rax]——实现解引用赋值
 int* pa = &a; 00007FF7168B150A lea rax,[a] 00007FF7168B150F mov qword ptr [pa],rax (*pa)=1; 00007FF7168B1514 mov rax,qword ptr [pa] 00007FF7168B1519 mov dword ptr [rax],1 
3.3.1.5引用操作

可见——与指针操作完全一致:

 int& ra = a; 00007FF7168B151F lea rax,[a] 00007FF7168B1524 mov qword ptr [ra],rax ra=2; 00007FF7168B1529 mov rax,qword ptr [ra] 00007FF7168B152E mov dword ptr [rax],2 
3.3.1.6主函数返回(不用管)
 return 0; 00007FF7168B1534 xor eax,eax
       C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成功能有重叠性,但是各有自己的特点,互相不可替代

Read more

数据结构:⼆叉树(1)

数据结构:⼆叉树(1)

目录 前言  树部分知识: 一.树的概念和结构 二.树的一些相关术语和定义  三.树的实现结构(了解部分) 四、树的应用场景 二叉树部分知识讲解: 一.二叉树概念与结构 二.特殊二叉树类型 1.满二叉树 2.完全二叉树 3.性质补充 三、⼆叉树存储结构 顺序结构: 编辑应用: 链式结构: 四、堆的概念与结构 1.实现顺序结构⼆叉树: 2.堆的概念与结构 (重点) 3.堆的实现 五、堆的实现代码部分 1.堆的初始化:(本次实现选取大堆为例) 2.堆的销毁: 3.堆的插入数据 : 4.堆打印值 : 六、

By Ne0inhk
21届智能车雁过留痕备战指南|龙邱科技STC+神眼摄像头处理 高效搜线算法思路分享

21届智能车雁过留痕备战指南|龙邱科技STC+神眼摄像头处理 高效搜线算法思路分享

今年STC单片机首次增设摄像头组别,相信不少备战的同学想要知道这颗新U是否能够快速上手并能够像传统摄像头组别一样,高效完成图像处理,提高车模控制系统上限。 其中最突出的痛点的是:有同学搭建完核心算法组合后,可能感觉到略微卡顿或系统延迟,影响车模调试上限,我们第一次搭建完经过测试单帧处理耗时高达20多ms,这导致车辆运行稳定性和反应速度受限、甚至可能有冲出赛道的情况发生,导致调试陷入瓶颈,提速困难,短时间内难以找到有效突破方向。 针对这一高频痛点,我们结合备战同学的实际调试场景,经过反复测试、迭代优化,整理出一套实用性极强的帧率优化思路,实测验证有效,优化后单帧处理耗时可稳定降至9-11ms,彻底解决卡顿难题,这里将图像处理和以西优化思路分享给大家,希望能够帮助到更多的同学! 实测数据对比,直观呈现优化效果 图像处理方案单帧采集+处理耗时未优化(采集+处理)20ms-25ms(能感觉到慢,上限较低)优化后(采集+处理)9ms-11ms(流畅稳定,提高了上限) 同学们遇到的卡顿问题,核心症结主要集中在两点:一是内存资源不足,二是算法计算耗时过长。在拆解具体优化方法前,我

By Ne0inhk
详解常见排序

详解常见排序

目录 编辑 插入排序 希尔排序(缩小增量排序) 选择排序 冒泡排序 堆排序 快速排序 hoare版  挖坑法 前后指针法 非递归版 归并排序 递归版 非递归版 计数排序 声明:以下排序代码由Java实现!!! 插入排序 步骤: 1.我们可以认为数组的第一个元素已经被排好序,因此只需考虑对后面的元素进行插入排序; 2.取下一个位置的元素val,让它和它之前的元素进行比较,顺序为从右向左; 3.如果该元素大于val,则将该元素移动到该元素所处位置的下一个位置; 4.重复步骤3,知道找到已排好序的序列中小于等于val的元素; 5.将值val放到该位置的下一个位置,如果已排好序的所有元素的值都大于val,则将val存放到数组下标为0的位置; 6.重复2~5步骤。 动画演示: 代码如下: public static void insertSort(int[] array){ for(

By Ne0inhk
【保姆级】TrendRadar本地部署:告别算法推荐,打造个人专属AI热点情报局

【保姆级】TrendRadar本地部署:告别算法推荐,打造个人专属AI热点情报局

【保姆级】TrendRadar本地部署:告别算法推荐,打造个人专属AI热点情报局 摘要 本文基于 TrendRadar 项目,详细拆解如何通过 Docker 本地部署一套个人专属的 AI 热点情报系统。涵盖从环境准备、Docker 镜像拉取、局域网访问配置、飞书推送修复到 Cherry Studio MCP 服务对接的全流程。通过 Nvidia 免费模型 API,实现零成本的深度热点分析与趋势追踪。 关键词: TrendRadar, Docker部署, AI热点分析, MCP服务, Cherry Studio 1. 背景与需求:为什么我们需要 TrendRadar? 在这个大数据算法横行的时代,我们每天被头条、抖音等平台“投喂”大量信息,不仅容易陷入信息茧房,还浪费了大量时间筛选有效资讯。 我的核心需求很简单: 1. 拒绝算法绑架:需要一个个人定制化的新闻推送服务。 2.

By Ne0inhk