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

深入探索Spring:Bean管理与Spring Boot自动配置原理

深入探索Spring:Bean管理与Spring Boot自动配置原理

目录 * Spring 原理深入探索 * 1. Bean 的作用域和生命周期 * 1.1 Bean 的作用域 * 1.2 Bean 的生命周期 * 2. Spring Boot 自动配置流程 * 3.总结 Spring 原理深入探索 1. Bean 的作用域和生命周期 1.1 Bean 的作用域 在Spring中,Bean的作用域(Scope)决定了Bean的实例化方式以及其生命周期。以下是Spring中常见的Bean作用域: 作用域说明singleton每个Spring IoC容器内同名称的bean只有⼀个实例(单例)(默认 )prototype每次使用该bean时会创建新的实例(非单例)request每个HTTP 请求生命周期内, 创建新的实例session每个HTTP Session生命周期内, 创建新的实例application每个ServletContext生命周期内, 创建新的实例websocket每个WebSocket生命周期内, 创建新的实例 我们直接上代码 后面根据运行结果观察Bean的作用域。

By Ne0inhk
【MySQL】从连接数据库开始:JDBC 编程入门指南

【MySQL】从连接数据库开始:JDBC 编程入门指南

个人主页:♡喜欢做梦 欢迎  👍点赞  ➕关注  ❤️收藏  💬评论 目录 🌟一、什么是JDBC? 🌟二、JDBC编程的步骤 ✨使用步骤 ✨DriverManger 💫定义 💫DriverManger的主要功能 💫DriverManger的核心方法 💫使用 ✨DataSource 💥定义 💥使用  💥代码优化 ✨DriverManger和DataSource的区别 🌟一、什么是JDBC? JDBC(Java Data Base Connectivity,Java数据库连接)是Java程序和数据库之间的桥梁,也就是Java语言操作数据库的标准API,他提供了一套用于执行SQL语句的Java接口。JDBC的主要作用就是:与数据库连接、发送SQL语句和处理数据库执行结果。 🌟二、JDBC编程的步骤 ✨使用步骤 1.加载数据库厂商的驱动包 2.建立连接,用户名,密码 3.创建Statement 4.发送要执行的SQL语句

By Ne0inhk
Flutter 组件 http_retry 的适配 鸿蒙Harmony 深度进阶 - 驾驭分布式负载感知重试、实现鸿蒙端高可靠通讯与协议幂等性审计方案

Flutter 组件 http_retry 的适配 鸿蒙Harmony 深度进阶 - 驾驭分布式负载感知重试、实现鸿蒙端高可靠通讯与协议幂等性审计方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 http_retry 的适配 鸿蒙Harmony 深度进阶 - 驾驭分布式负载感知重试、实现鸿蒙端高可靠通讯与协议幂等性审计方案 前言 在前文中,我们探讨了 http_retry 在鸿蒙(OpenHarmony)生态中解决单一移动终端弱网重试的基础实战。但在真正的“分布式工业物联网集成”、“跨设备协同办公资产同步”以及“需要对接具备动态压力管控的超大规模云原生后端”场景中。简单的指数退避往往难以应对复杂的网络分位震荡。面对一个需要在鸿蒙手机、智能穿戴设备与边缘网关之间,根据当前全网的平均负载压力(Load Pressure)动态调节重试节奏,并且要求在执行涉及核心资产变更(如:支付订单、库存锁定)的重试时执行绝对严密的协议幂等性(Idempotency)校验的高阶需求。如果缺乏一套具备分布式感知的重试调度模型。不仅会导致后端服务在故障恢复瞬间遭遇“重试波峰”引发再次崩溃,更会因为对非幂等操作的盲目重试。引发严重的业务资产错乱。 我们需要

By Ne0inhk
【Spring国际化(i18n)】1、核心原理详解:吃透这4个核心组件,搞定企业级多语言开发

【Spring国际化(i18n)】1、核心原理详解:吃透这4个核心组件,搞定企业级多语言开发

Spring国际化核心原理详解:吃透这4个核心组件,搞定企业级多语言开发 前言:为什么需要国际化?企业级项目多语言场景痛点 在全球化业务扩张和多区域部署的背景下,企业级Spring应用的“多语言适配”已从“加分项”变成“必选项”。你是否遇到过这些痛点: * 硬编码的提示语、异常信息散落在代码中,新增语言时需要逐行修改代码,效率低且易出错; * 不同国家/地区用户使用系统时,看到的仍是固定语言,体验差; * 微服务场景下,服务间调用的异常信息语言不一致,排查问题成本高; * 想切换语言但不知道底层逻辑,只能照搬网上的配置,遇到问题无从下手。 Spring框架提供了一套成熟的国际化(i18n,Internationalization的缩写,因首字母I和尾字母N之间有18个字母得名)解决方案,其核心是通过4个核心组件实现“消息解耦+动态加载”,让多语言适配变得简单、可维护。本文作为系列开篇,将从底层原理到入门实操,彻底讲透Spring国际化的核心逻辑,帮你从“知其然”到“知其所以然”。 Spring国际化核心设计思想:基于Locale的消息解耦与动态解析 Spring

By Ne0inhk