ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C++引用概念及用法全解

C++引用概念及用法全解 一、什么是引用引用是C中的一种语法特性可以理解为变量的别名。引用本身不占用独立的内存空间它和被引用的变量共享同一块内存。引用的基本用法123456789101112intmain() {inta 10;int ra a;// ra是a的别名a 10;cout ra endl;// 输出20ra 10;cout a endl;// 输出30return0;}关键点对引用的操作就是对原变量的操作两者完全等价。二、引用的三大特点1. 定义引用时必须初始化12int ra;// ❌ 错误引用必须初始化int ra a;// ✅ 正确原因引用是变量的别名别名必须依附于某个已存在的变量不能独立存在。2. 不存在空引用12int ra NULL;// ❌ 错误不能给引用赋空值int* p NULL;// ✅ 指针可以为空原因引用必须绑定到一个有效的变量不能指向空地址。指针是独立的变量可以存储空地址但引用不是变量它只是别名。3. 引用一旦绑定不能改变指向123inta 10, b 20;int ra a;ra b;// 这不是让ra指向b而是把b的值赋给a即a 20原因引用在初始化时绑定了目标变量之后所有对引用的操作都直接作用于该变量无法重新绑定到其他变量。对比指针1234inta 10, b 20;int* p a;// 指针可以改变指向p b;// 现在p指向b*p 100;// 修改b的值为100三、引用作为函数参数引用的本质引用是变量的别名不是副本。对引用的操作就是对原变量的操作。基于这个特性引用最常用的场景是作为函数参数主要解决两个问题1. 在函数内部修改外部变量C语言中函数参数默认是传值形参是实参的副本函数内部修改形参外面的实参不受影响。123456789101112// C语言传值交换失败voidSwap(inta,intb) {inttmp a;a b;b tmp;}intmain() {intx 10, y 20;Swap(x, y);// x和y还是10和20没变}想要修改实参必须传指针123456// C语言传指针交换成功voidSwap(int* a,int* b) {inttmp *a;*a *b;*b tmp;}C的引用可以做到同样的事语法更简洁123456789101112// C传引用交换成功voidSwap(int a,int b) {inttmp a;a b;b tmp;}intmain() {intx 10, y 20;Swap(x, y);// 调用时和传值一样但实参被改了cout x y endl;// 输出20 10}优点调用时不用取地址函数内部不用解引用代码更清晰。2. 避免拷贝提高效率当参数是大型对象如结构体、类时传值会拷贝整个对象开销很大。传引用只传递地址4/8字节效率更高。12345// 传值拷贝整个Student对象几十字节voidPrintStudent(Student s) { ... }// 传引用只传递地址4/8字节不拷贝voidPrintStudent(constStudent s) { ... }注意对于int、char这类小型数据传值反而更快因为直接复制到寄存器不需要间接访问。所以小型数据用传值大型对象用const引用。3. 三种参数传递方式对比方式本质能否修改实参效率小型数据效率大型对象传值副本❌ 不能高低传指针地址✅ 能中高传引用别名✅ 能中高四、普通引用 vs const引用1. 普通引用可读可写1234inta 10;int ra a;// 普通引用ra 100;// ✅ 可以修改cout ra;// ✅ 可以读取2. const引用只读1234inta 10;constint cra a;// 常引用只读cout cra;// ✅ 可以读取cra 100;// ❌ 错误不能通过常引用修改3. 权限规则看原变量的权限引用的权限不能超过原变量。原变量是普通变量可读可写→ 引用可以是普通引用也可以是常引用原变量是常量只读→ 引用只能是常引用12345678inta 10;// 普通变量可读可写constintb 20;// 常量只读int ra a;// ✅ 普通引用引用普通变量constint cra a;// ✅ 常引用引用普通变量int rb b;// ❌ 错误普通引用不能引用常量权限超了constint crb b;// ✅ 常引用引用常量一句话变量是什么权限引用就不能超过这个权限。五、const引用的万能特性const引用可以引用任何东西普通变量、常变量、字面常量甚至临时对象。1. 引用普通变量12inta 10;constint cra a;// ✅2. 引用常变量12constintb 20;constint crb b;// ✅3. 引用字面常量1constint crx 30;// ✅ 可以背后原理当const引用绑定到字面常量时编译器会生成一个临时变量来保存这个常量然后让引用指向这个临时变量。123// 编译器实际做的事inttmp 30;constint crx tmp;注意引用本身不占内存但被引用的对象临时变量需要占内存。这个临时变量由编译器自动创建和管理它的生命周期和引用一致。4. 引用表达式结果12inta 10, b 20;constint cr a b;// ✅ 表达式结果是临时变量const引用可以绑定六、引用的常见误区误区1引用可以重新绑定1234inta 10, b 20;int ra a;// ra是a的别名ra b;// ❌ 不是让ra指向b而是把b的值赋给aa变成20误区2引用占用空间引用本身不占用独立内存它只是原变量的别名。sizeof引用的结果是原变量的大小。123inta 10;int ra a;cout sizeof(ra);// 输出4int的大小不是指针的大小原因sizeof作用于引用时返回的是被引用对象的大小。误区3引用可以指向引用123inta 10;int ra a;int rra ra;// ❌ 错误不存在引用的引用原因引用是别名不是对象。不能给别名再起别名吗语法上不允许。七、不能返回局部变量的引用或地址先看一个错误示例123456789Student func() {Student sa{S001,Alice,F, 20 };returnsa;// 返回局部变量的引用}intmain() {Student p func();printf(%s, p.s_name);// 可能崩溃可能乱码可能碰巧正确}为什么错了困惑1返回地址本身有什么错返回地址本身没错。return sa返回的是一个数字内存地址这个操作是合法的。错的是函数结束后你还去用这个地址访问内存。困惑2为什么函数结束后就不能用了函数调用时系统会在栈上给局部变量分配空间。函数结束后这块空间被系统回收标记为“可重用”。但回收不是清零这块内存还在只是不再属于你了。下次调用其他函数时系统可能会把这块空间分配给别的变量里面的内容随时可能被覆盖。所以你去访问它结果是不可预测的。困惑3返回值和返回引用有什么区别1234567891011// 返回引用返回地址危险Student func1() {Student sa{ ... };returnsa;// 返回地址sa销毁后地址失效 ❌}// 返回值返回副本安全Student func2() {Student sa{ ... };returnsa;// 返回副本sa销毁不影响副本 ✅}区别返回引用把原件的地址告诉别人原件没了地址就成了空地址返回值复印一份交给别人原件没了复印件还在困惑4返回局部变量的地址和返回局部变量本身有什么区别看返回类型返回类型是Student或Student*返回的是地址危险返回类型是Student返回的是副本安全123Student func1() {returnsa; }// 返回类型是引用 → 地址Student* func2() {returnsa; }// 返回类型是指针 → 地址Student func3() {returnsa; }// 返回类型是对象 → 副本类比理解你在酒店开房sa就是你住的房间返回引用或地址你把房间号告诉别人退房函数结束房间被回收不再属于你别人拿着房间号回去找房间可能住了别人可能在打扫可能已经被改成仓库去用这个房间号 → 结果不可预测返回值你退房时把房间里的东西复印一份带走原来的房间怎么变都跟你无关。正确的做法1. 返回值拷贝1234Student func() {Student sa{ ... };returnsa;// 返回副本}2. 返回静态局部变量1234567Student func() {staticStudent sa{ ... };// 静态变量生命周期贯穿整个程序returnsa;}Student func() {staticStudent sa{ ... };// 静态变量生命周期贯穿整个程序returnsa;}3. 在堆上分配1234Student* func() {Student* p newStudent{ ... };// 堆上分配手动管理returnp;}一句话总结返回局部变量的地址本身没错错的是函数结束后你还去用这个地址。因为这块内存已经被系统回收不再属于你了。八、能不能定义引用数组结论不能定义“元素为引用的数组”但可以定义“数组的引用”。1. 不能定义元素为引用的数组1int rbr[5];// ❌ 错误不能定义引用数组原因数组是一段连续的内存空间每个元素都需要占用内存。引用本身不占内存它只是别名无法作为数组元素存在。2. 可以定义数组的引用12intar[5] {1,2,3,4,5};int(rar)[5] ar;// ✅ 正确rar是数组ar的引用含义rar是整个数组ar的别名不是元素为引用的数组。3. 对比写法含义是否正确int rbr[5]元素为引用的数组❌ 错误int(rar)[5]数组的引用✅ 正确记忆技巧括号位置决定一切。靠近数组名表示引用整个数组靠近类型表示元素是引用。九、数组名退化问题1. 数组名退化的规则数组名在三种情况下不会退化保持为整个数组sizeof(ar)ar取数组地址引用数组int(rar)[n] ar其他情况数组名退化为数组首元素的地址。1234567intar[5] {1,2,3,4,5};cout sizeof(ar);// 205*4未退化int(*p)[5] ar;// 取数组地址未退化int(rar)[5] ar;// 引用数组未退化int* q ar;// 退化ar变成int*2. 数组名作为函数参数退化问题C中数组名作为参数传递给函数时会退化为指针丢失数组大小信息。1234voidfuna(intbr[5]) {cout sizeof(br);// 输出8指针大小不是20// 函数内部不知道数组有多大只能通过额外参数传入长度}如果希望函数内部能知道数组的大小可以用数组的引用作为参数1234voidfunb(int(rar)[5]) {cout sizeof(rar);// 输出20保留了数组大小// 函数内部可以直接用sizeof获取数组长度}关键点用数组的引用作为参数可以保留数组的大小信息避免退化。十、引用与指针的区别1. 语法规则上的区别对比项引用指针本质变量的别名存储变量的地址内存分配不分配独立内存分配4/8字节内存使用方式直接使用需要解引用*能否改变指向不能能必须初始化是否是否可为空不能可以NULL/nullptrsizeof结果原变量的大小指针本身的大小4/8多级无无引用的引用有二级、n级指针操作改变原变量的值改变指向的地址1234567// 演示操作的区别intar[5] {10,20,30,40,50};int ra ar[0];int* p ar[0];ra;// ar[0]变成11p;// p指向ar[1]2. 汇编层面的本质从汇编角度看指针和引用没有区别都是地址。1234inta 10;int ra a;int* p a;// 底层实现ra和p都是存储a的地址引用在底层也是用指针实现的只是编译器做了语法糖让使用更方便、更安全。3. 一句话总结维度引用指针语法层面别名更安全地址更灵活汇编层面都是地址都是地址使用建议能用引用就用引用需要改变指向或可为空时再用指针。总结知识点核心要点引用本质变量的别名不占独立内存三大特点必须初始化、不能为空、不能改指向const引用只读可引用普通变量/常变量/字面常量权限规则引用权限不能超过原变量const引用万能可绑定字面常量和临时对象编译器自动生成临时变量引用作为参数可修改外部变量、避免大对象拷贝返回局部变量不能返回局部变量的引用或地址函数结束后内存被回收数组的引用可定义数组引用int(rar)[5]不能定义引用数组int rbr[5]数组名退化sizeof//数组引用 三种情况不退化函数参数会退化引用 vs 指针引用更安全不能为空、不能改指向指针更灵活到此这篇关于C引用全解的文章就介绍到这了
返回列表