当前位置: 首页 > news >正文

【C++】一文搞懂引用特性,附带顺序表完整代码实现

📌 相关专栏

  • 【Linux专栏】
  • 【C语言专栏】
  • 【测试专栏】
  • 【MySQL专栏】
  • 【C++ 专栏】

📌 相关文章推荐

  • 【C++】一文吃透const引用、inline、nullptr三大语法
  • 【C语言】一文吃透C语言分支循环中的rand、srand与time函数
  • 【Linux】一文吃透Cookie和Session:会话跟踪技术核心原理详解
  • 【Linux】玩转Socket,快速掌握UDP数据传输

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • 一、从"交换两个数"说起
  • 二、引用的核心概念
    • 2.1 什么是引用?
    • 2.2 引用的三大特性
    • 2.3 引用 vs 指针
  • 三、引用的典型应用
    • 3.1 引用传参(会修改实参)
    • 3.2 引用作为返回值(可修改返回对象)
  • 四、顺序表(SeqList)完整实现
    • 4.1 结构体定义(SeqList.h)
    • 4.2 初始化(带缺省参数)
    • 4.3 尾插
    • 4.4 查找
    • 4.5 按位访问(返回引用)
    • 4.6 修改
  • 五、栈(Stack)的动态扩容实现
  • 六、三种传参方式对比
  • 七、知识点汇总
  • 八、本文全部代码

前言

在学习C++的过程中,我们可能会遇到这样的困惑:

🐾 C语言已经有指针了,为什么C++还要搞出一个“引用”?

🐾 引用到底能做什么?

🐾 它和指针有什么区别?

接下来,我们就从一段真实的顺序表代码出发,一边看代码一边理解引用:

  • 引用如何让函数传参更简洁
  • 引用作为返回值有什么用
  • 引用和指针到底该选哪个

一、从"交换两个数"说起

在C语言中,如果我们想写一个交换两个整数的函数,必须传指针:

voidswap(int*px,int*py){inttmp=*px;*px=*py;*py=tmp;}

🐾 C++ 提供了一种更优雅的方式:引用。

voidswap(int&rx,int&ry){inttmp=rx;rx=ry;ry=tmp;}
💡 :调用时直接传变量名,语法更自然,底层效果和传指针一样。

二、引用的核心概念

2.1 什么是引用?

引用就是一个变量的别名。操作引用 = 操作原变量。

类型& + 别名 = 引用对象

inta=0;int&b=a;// b 是 a 的引用(别名)int&c=b;// c 也是 a 的引用cout<<&a<<endl;// 三个地址完全相同cout<<&b<<endl;cout<<&c<<endl;

2.2 引用的三大特性

特性说明
必须初始化声明引用时必须指定它引用哪个变量
不能改变指向一旦引用了一个变量,不能再引用别的
不占用独立内存和原变量共用同一块内存空间

2.3 引用 vs 指针

对比项引用指针
是否可为空不可以可以(NULL/nullptr)
能否改变指向不能可以
语法简洁度更简洁需要解引用
是否占内存不占(语法层面)占4/8字节
inta=10,c=20;int&b=a;b=c;// 这不是让b引用c,而是把c的值赋给a(b还是a的别名)cout<<&a<<" "<<&b<<endl;// 地址相同cout<<a<<endl;// 20(a的值被改成了c的值)

三、引用的典型应用

3.1 引用传参(会修改实参)

voidswap1(int&rx,int&ry){// 引用传递inttmp=rx;rx=ry;ry=tmp;}voidswap2(intrx,intry){// 值传递(无效)inttmp=rx;rx=ry;ry=tmp;}intmain(){intx=1,y=2;swap2(x,y);// 无效,x=1, y=2swap1(x,y);// 有效,x=2, y=1}

3.2 引用作为返回值(可修改返回对象)

int&STTop(ST&rs)//int& :返回栈顶元素的引用,这样调用既可以读取栈顶值,也可以直接修改它(比如 STTop(st1) = 100; ){assert(rs.top>0);returnrs.a[rs.top-1];// 返回栈顶元素的引用}// 使用示例STPush(st1,1);STPush(st1,2);cout<<STTop(st1)<<endl;// 输出 2STTop(st1)+=10;// 直接修改栈顶元素cout<<STTop(st1)<<endl;// 输出 12

🐾 好处:既能读取,也能直接修改,语法像操作普通变量一样自然。

💡 注意:不要返回局部变量的引用(局部变量函数结束就销毁了)。

四、顺序表(SeqList)完整实现

4.1 结构体定义(SeqList.h)

typedefintSLDatatype;// 将int重定义为SLDatatype,这样后边需要改类型,直接把int换掉就行typedefstructSeqList{SLDatatype*a;// 动态数组指针,a是SLDatatype类型的指针变量intsize;// 当前元素个数intcapacity;// 当前容量}SL;

4.2 初始化(带缺省参数)

//初始化顺序表// SL*是指向SL结构体的指针//pls是结构体SL的指针,指向SL,指针类型是SL*,用来在函数内部修改原结构体的数据//int n=4:默认参数,指定容量为4(若调用时不传入---设新的内存容量,则自动使用4)voidSLInit(SL*pls,intn=4){pls->a=(int*)malloc(n*sizeof(int));pls->size=0;pls->capacity=n;}

4.3 尾插

voidSLPushBack(SL*pls,intx){pls->a[pls->size++]=x;}

🐾 注意:正式实现需要加上扩容逻辑(参考后面的栈代码)。


4.4 查找

//查找//int x:要查找的目标元素//int i=0:默认参数,指定从下标i开始查找intSLFind(SL*pls,intx,inti=0);

4.5 按位访问(返回引用)

int&Slat(SL*pls,inti){assert(i>=0&&i<pls->size);returnpls->a[i];// 返回引用,可读可写}// 使用示例for(inti=0;i<10;i++){cout<<Slat(&s,i)<<" ";// 读取}Slat(&s,0)=100;// 修改

4.6 修改

//修改//int i:要修改的元素下标//int x:新的元素值voidModify(SL*pls,inti,intx);

五、栈(Stack)的动态扩容实现

voidSTInit(ST&rs,intn=4){rs.a=(STDatatype*)malloc(n*sizeof(STDatatype));rs.top=0;rs.capacity=n;}voidSTPush(ST&rs,STDatatype x){// 检查是否需要扩容if(rs.top==rs.capacity){intnewcapacity=rs.capacity==0?4:2*rs.capacity;STDatatype*tmp=(STDatatype*)realloc(rs.a,newcapacity*sizeof(STDatatype));if(tmp==NULL){printf("realloc fail!");return;}rs.a=tmp;rs.capacity=newcapacity;}rs.a[rs.top++]=x;}

🐾 扩容策略:每次容量满时,扩容为原来的 2 倍(均摊时间复杂度 O(1))。


六、三种传参方式对比

方式语法能否修改实参效率
值传递void f(int x)拷贝数据
地址传递void f(int* x)拷贝地址(4/8字节)
引用传递void f(int& x)拷贝地址(4/8字节)

🐾 结论:需要修改实参或避免拷贝时,用引用传递更简洁。


七、知识点汇总

知识点核心要点
引用定义类型& 别名 = 变量名
引用传参形参修改直接影响实参
引用返回可修改返回对象,但不能返回局部变量
顺序表动态数组 + size + capacity
动态扩容realloc + 2倍扩容策略
断言assert() 用于调试期检查边界

八、本文全部代码

🐾 SeqList.h

#pragmaonce#include<stdlib.h>typedefintSLDatatype;//将int重定义为SLDatatype,这样后边需要改类型,直接把int换掉就行typedefstructSeqList{SLDatatype*a;//a是SLDatatype类型的指针变量intsize;intcapacity;}SL;//初始化顺序表// SL*是指向SL结构体的指针//pls是结构体SL的指针,指向SL,指针类型是SL*,用来在函数内部修改原结构体的数据//int n=4:默认参数,指定容量为4(若调用时不传入---设新的内存容量,则自动使用4)voidSLInit(SL*pls,intn=4);//尾插//int x:要尾插的元素值voidSLPushBack(SL*pls,intx);//查找//int x:要查找的目标元素//int i=0:默认参数,指定从下标i开始查找intSLFind(SL*pls,intx,inti=0);//"按位访问"函数//int i:要访问元素的下标int&Slat(SL*pls,inti);//修改//int i:要修改的元素下标//int x:新的元素值voidModify(SL*pls,inti,intx);

🐾 SeqList.cpp

#define_CRT_SECURE_NO_WARNINGS1#include"SeqList.h"#include"assert.h"#include<stdio.h>//初始化voidSLInit(SL*pls,intn){pls->a=(int*)malloc(n*sizeof(int));pls->size=0;pls->capacity=n;}//尾插voidSLPushBack(SL*pls,intx){pls->a[pls->size++]=x;//size从0开始,一直++}//查找intSLFind(SL*pls,intx,inti){assert(pls);assert(i>=0&&i<pls->size);for(;i<pls->size;i++){if(pls->a[i]=x){printf("找到了!\n");returnpls->a[i];}i++;}printf("未找到该元素!\n");return-1;}//修改voidSLModify(SL*pls,inti,intx){assert(pls);assert(i>=0&&i<pls->size);pls->a[i]=x;}

🐾 test.cpp

//引用//概念:引用不是新定义一个变量,而是给已存在变量取了一个别名,////////编译器不会为引用变量开辟内存空间, 它和它引用的变量共用同一块内存空间(基于引用的语法)//#include<iostream>usingnamespacestd;intmain(){inta=0;int&b=a;//类型& 引用别名 = 引用对象int&c=b;// b是a的别名,c是b的别名,所以c是a的别名cout<<&a<<endl;// 输出的结果一样,一串地址数字cout<<&b<<endl;// 引用acout<<&c<<endl;return0;}------------------//一个变量可以有多个引用intmain(){inta=10;int&b=a;//一旦引用了实体(10)就不能在引用其他的intc=20;//这里并非让b引用c,因为C++引用不能改变指向//这里是赋值,b还是a的别名,地址一样,但是b的值变成了20,a的值还是10b=c;cout<<&a<<endl;cout<<&b<<endl;//a和b的输出结果是一样的,因为b是a的别名,地址没变cout<<&c<<endl;return0;}///////////////////////////////////////////////////////////////////////////////////////////////////////////引用传参---地址传递////传地址,形参就可以改变实参#include<iostream>usingnamespacestd;voidswap1(int&rx,int&ry)//rx,ry->形参{inttmp=rx;//先把rx里的值弄走给tmp,再把ry的值丢给rx,然后再把tmp的值丢给ryrx=ry;ry=tmp;}//普通传参---值传递//这里值传递,形参的改变是不会对实参造成影响,结果还是 1 2voidswap2(intrx,intry){inttmp=rx;ry=rx;tmp=ry;}intmain(){intx=1,y=2;//实参cout<<x<<" "<<y<<endl;//1 2swap2(x,y);//这里值传递,形参的改变是不会对实参造成影响,结果还是1 2cout<<x<<" "<<y<<endl;//1 2swap1(x,y);//调用函数swap1,传参:把x和y的地址分别传给rx和ry,实现x和y的值的调换//此时x=2,y=1,所以下面使用值传递时,输出还是2 1,因为x和y值已经被改变了cout<<x<<" "<<y<<endl;//2 1////x=2,y=1//swap2(x, y); //这里值传递,形参的改变是不会对实参造成影响,结果还是2 1//cout << x << " " << y << endl;// 2 1return0;}/////////////////////////////////////////////////////////////////////////////////////////////////////#include<iostream>#include<assert.h>usingnamespacestd;typedefintSTDatatype;typedefstructstack{STDatatype*a;//a是指针变量,用来存数据inttop;//栈顶intcapacity;//内存空间}ST;//初始化voidSTInit(ST&rs,intn=4)//rs是ST的别名,n是系统开辟的4个整型字节//ST* rs,rs是指向ST的地址的指针,类型是ST*{rs.a=(STDatatype*)malloc(n*sizeof(STDatatype));rs.top=0;rs.capacity=n;//开辟四个整型大小的内存空间}//栈顶voidSTPush(ST&rs,STDatatype x){//满了,扩容if(rs.top==rs.capacity){//如果为假,则说明刚初始化还没开辟空间;如果为真,说明空间已满intnewcapacity=rs.capacity==0?4:2*rs.capacity;//开始申请空间//realloc(a,b)->表示要开辟什么类型的空间;a表示开辟给谁;b表示开辟的空间大小STDatatype*tmp=(STDatatype*)realloc(rs.a,newcapacity*sizeof(STDatatype));//判断申请空间是否成功if(tmp==NULL){printf("realloc fail!");return;}rs.a=tmp;rs.capacity=newcapacity;//右边赋值给左边}rs.a[rs.top]=x;rs.top++;}//int& :返回栈顶元素的引用,这样调用者既可以读取栈顶值,//也可以直接修改它(比如 STTop(st1) = 100; )。int&STTop(ST&rs){assert(rs.top>0);returnrs.a[rs.top-1];}intmain(){ST st1;STInit(st1);STPush(st1,1);STPush(st1,2);cout<<STTop(st1)<<endl;STTop(st1)+=10;cout<<STTop(st1)<<endl;return0;}/////////////////////////////////////////////////////////////////////////////////////////////////////#include"SeqList.h"intmain(){SL s;//定义一个顺序变量sSLInit(&s,10);//调用初始化函数为变量s预分配10内存容量的空间for(size_t i=0;i<10;i++){SLPushBack(&s,i);//调用尾插函数,给s尾插从0到9的元素}for(size_t i=0;i<10;i++){cout<<Slat(&s,i)<<" ";//调用按位访问函数,逐个打印访问过s的元素}cout<<endl;return0;}

🐶 🐾 ✨ 🐾 🐶


谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

http://www.zskr.cn/news/1418670.html

相关文章:

  • Cortex-M中断处理机制与调试技巧详解
  • 别再死记硬背公式了!用Python手写线性回归,从MSE、R²到梯度下降一次搞懂
  • Bootstrap方法避坑指南:什么时候用?什么时候千万别用?(附R代码验证)
  • 从安装到第一个视觉项目:Halcon20.11环境搭建与‘Hello World’实战
  • 华为BGP选路实战:用这3个属性(PrefVal、Local_Pref、MED)轻松搞定网络流量调度
  • 告别‘丑地图’!用ArcGIS Pro的视觉效果和后处理,轻松打造高级感分析图
  • RAG 04:向量数据库与索引算法
  • Shader - 水体(保姆级)
  • 鼎捷Tiptop ERP 5.3版本下,手把手教你用SoapUI测试一个用户登录WebService接口
  • RAG 技术体系:从向量检索到生产级 Pipeline
  • 保姆级教程:用PyTorch Geometric搭建GCN,实战DEAP脑电情绪分类(附完整代码)
  • 大数据处理:Spark与分布式计算
  • 论文降AI率工具怎么选?2026年4款降AI软件实测一次选对
  • 告别双系统安装噩梦:Intel RST模式下无损切换AHCI,保住Windows再装Ubuntu
  • 从零开发游戏需要学习的c#模块,第二十九章(经验值与升级系统)
  • MySQL—隔离级别和MVCC
  • 百度网盘提取码智能查询:3步告别资源获取烦恼的终极指南
  • 不是所有 AI 产品都适合出海,真需求和全球化幻觉差在哪? | 嗨点小圆桌
  • Docker 网络进阶:容器间通信与 DNS 解析
  • Arduino旋转电位器应用:从模拟信号读取到Processing数据可视化
  • 北斗导航“指路”申通西安转运中心让特产寄递跑出“加速度”
  • Arduino电子钢琴DIY:从电路设计到C++编程的嵌入式音乐项目实践
  • 别只盯着地图!深度解析ArcGIS Pro内容窗格的5个隐藏选项卡(选择、编辑、捕捉…)
  • 0104摩尔定律死亡终审:性能提升唯一路径——放弃几何微缩,转向场域升维+时间重构
  • 新手也能搞定的TPS5430电源设计:从24V到15V,手把手教你选对每个元器件(附完整BOM清单)
  • ArcMap新手必看:三种要素选择方法(按属性、位置、图形)的保姆级图文教程
  • Arm CoreLink NIC-400与NI/NoC动态调频技术详解
  • 从实验室到产线:Imatest枯叶图在摄像头批量质检中的实战应用与自动化脚本思路
  • 告别死板教程!用ShaderGraph复刻《和平精英》动态海面,这5个参数调好了效果直接翻倍
  • C语言在嵌入式Linux系统开发中的实战应用