C++ 引用

张开发
2026/4/8 17:04:47 15 分钟阅读

分享文章

C++ 引用
C 引用1. 参数传递的三种方式1.1 按值传递特点形参是实参的副本修改形参不影响实参。内存需要分配空间并拷贝数据。#includeiostreamvoidmodifyValue(intx){x100;// 只修改形参}intmain(){inta10;modifyValue(a);std::couta;// 输出 10实参未变return0;}1.2 引用传递特点形参是实参的别名直接操作实参不分配新空间。内存不分配额外空间共享实参空间。#includeiostreamvoidmodifyRef(intx){x100;// 直接修改实参}intmain(){inta10;modifyRef(a);std::couta;// 输出 100return0;}1.3 指针传递特点本质按值传递传递指针的副本但可通过解引用修改所指对象。注意要修改指针本身使其指向另一地址需用int**或int*。#includeiostreamvoidmodifyPtr(int*p){*p100;// 修改所指对象pnullptr;// 只修改形参指针实参指针不受影响}intmain(){inta10;int*ptra;modifyPtr(ptr);std::couta;// 输出 100std::cout(ptrnullptr?null:not null);// not nullreturn0;}// 修改指针本身使用引用传递指针voidchangePointer(int*p,int*newPtr){pnewPtr;// 修改实参指针}2. 引用的核心概念5点(1) 引用是变量的别名没有独立的空间inta5;intrefa;// ref 是 a 的别名// ref 与 a 完全相同(2) 引用需要与它引用的变量共享空间inta5;intrefa;ref10;// 修改 ref 即修改 a// a 变为 10(3) 对引用所做的改变实际上是对它所引用的变量做出改变inta5;intrefa;ref;// a 变为 6(4) 引用在定义的时候必须初始化inta5;intrefa;// 正确// int ref2; // 错误必须初始化(5) 引用一经初始化不能重新引用其它变量inta5,b10;intrefa;refb;// 这不是让 ref 引用 b而是把 b 的值赋给 a// ref 仍然引用 aa 的值变为 103. const 引用4点(1) const 引用是指向 const 对象的引用constinta5;constintrefa;// 正确// int ref2 a; // 错误非 const 引用不能引用 const 对象(2) const 引用可以引用非 const 对象但非 const 引用不能引用 const 对象inta5;constintrefa;// 正确ref 不能修改 a但 a 本身可以修改// int ref2 ref; // 错误非 const 引用不能引用 const 对象(3) const 引用的对象值是常量不能修改inta5;constintrefa;// ref 10; // 错误ref 是 const 引用不能通过它修改a10;// 但 a 本身可以修改(4) const 引用可以引用不同类型的变量但会产生临时变量inta5;constdoublerefa;// 隐式转换为 double 临时量ref 绑定到该临时量// double ref2 a; // 错误非 const 引用不能绑定到不同类型4. 引用作为函数参数优点避免拷贝直接修改实参语法简洁。#includeiostreamvoidswap(intx,inty){inttempx;xy;ytemp;}intmain(){inta1,b2;swap(a,b);std::couta b;// 2 1return0;}5. 引用作为函数返回值规则可以返回引用但绝不能返回局部变量的引用悬垂引用。正确用法返回全局变量、静态变量、传入的引用参数、类成员等。#includeiostreamintmax(inta,intb){return(ab)?a:b;}intmain(){intx5,y10;max(x,y)100;// 将较大者y改为 100std::couty;// 输出 100return0;}// 错误示例返回局部变量的引用intbadFunc(){intlocal5;returnlocal;// 危险local 在函数结束时销毁}6. 引用与指针的区别4点(1) 访问引用是直接访问访问指针是间接访问inta5;intrefa;int*ptra;ref10;// 直接修改 a*ptr10;// 间接修改 a需解引用(2) 引用不单独分配内存空间指针拥有自己的内存空间inta5;intrefa;int*ptra;// ref 等于 a// ptr 是单独的地址(3) 引用一经初始化不能再引用其它变量指针可以inta5,b10;intrefa;// ref b; // 这是赋值不是改变引用int*ptra;ptrb;// 指针可以指向别处(4) 尽可能使用引用不得已时使用指针引用更安全无空引用无需判空语法更简洁。指针更灵活可重新指向、可为空、支持指针运算。7. 补充C11 右值引用与移动语义右值引用绑定到临时对象右值语法T。用途移动构造函数、移动赋值、完美转发避免深拷贝。#includeiostream#includevector#includecstring// for strlenclassMyString{public:std::vectorchardata;// 构造函数从 C 字符串拷贝字符到 vector深拷贝MyString(constchar*s):data(s,sstrlen(s)){std::coutConstructor\n;}// 移动构造函数MyString(MyStringother)noexcept:data(std::move(other.data))// 关键移动 other.data{std::coutMove constructor\n;}};intmain(){MyStringa(hello);// ① 调用普通构造函数MyStringb(std::move(a));// ② 调用移动构造函数return0;}什么是移动语义假设MyString没有移动构造函数只有拷贝构造函数通常由编译器自动生成会拷贝vector的所有元素。那么MyString ba;// 拷贝会把 a.data 的 5 个字符逐个复制到新内存拷贝完成后a和b各自拥有一份独立的数据内存布局如下a.data 指针 → [H][e][l][l][o] (堆内存 A) b.data 指针 → [H][e][l][l][o] (堆内存 B内容相同)如果a马上就要被销毁例如作为函数返回值那么堆内存 A 将被释放白白浪费了复制和释放的开销。移动语义就是为了解决这个问题把资源如堆内存直接从a“偷”给b避免复制。移动后a进入有效但未指定的状态通常是空不再拥有原来的资源。左值与右值左值 (lvalue)有名字、可取地址的表达式例如变量a。右值 (rvalue)临时对象、字面量、即将销毁的对象例如MyString(tmp)或std::move(a)。右值引用T只能绑定到右值。它的意思是“我引用的这个对象即将被销毁你可以安全地抢走它的资源”。MyStringrrefMyString(temp);// 右值引用绑定临时对象MyStringrref2std::move(a);// std::move 将 a 转为右值引用std::move到底做了什么std::move并不移动任何东西它只是一个类型转换将左值a转换为右值引用类型MyString。std::move(a)// 返回 static_castMyString(a)转换后编译器在重载解析时就会优先选择移动构造函数参数为MyString而不是拷贝构造函数参数为const MyString。完整的内存变化过程步骤 ①MyString a(hello);调用构造函数a.data指向堆上的一块内存包含H,e,l,l,o。a.data.指针 → 堆内存: [H][e][l][l][o] a.data.大小 5步骤 ②MyString b(std::move(a));std::move(a)将a转换为MyString。调用移动构造函数参数other绑定到a。初始化列表data(std::move(other.data))调用vector的移动构造b.data.指针 a.data.指针b.data.大小 a.data.大小然后a.data.指针 nullptra.data.大小 0打印 “Move constructor”。结果b.data.指针 → 原堆内存: [H][e][l][l][o] b.data.大小 5 a.data.指针 nullptr a.data.大小 0步骤 ③main结束析构b被析构释放堆内存。a被析构但a.data是空指针不会重复释放。概念解释右值引用T绑定到临时对象或即将销毁的对象表示“可以安全地偷走资源”。std::move只是类型转换将左值变成右值引用以便触发移动语义。移动构造函数参数为T从 other 夺取资源留下 other 为有效但未指定状态。移动后源对象仍可析构、赋值但不应再解引用其原资源如访问data可能为空。避免深拷贝移动只转移指针O(1) 时间拷贝需要复制每个元素O(n) 时间。noexcept承诺不抛异常让标准库优先使用移动而不是拷贝从而获得高性能8. 补充引用折叠用于模板和auto推导。规则T →TT →TT →TT →TtemplatetypenameTvoidfunc(Tparam){// T 可能是 int、int、int 等}inta5;func(a);// a 是左值T intparam 类型折叠为 intfunc(5);// 5 是右值T intparam 类型为 int9. 补充数组的引用数组引用保留数组大小信息而指针会退化为首元素地址。#includeiostreamvoidprintArray(int(arr)[5]){// 只能接收大小为 5 的 int 数组for(inti:arr)std::couti ;}intmain(){intarr[5]{1,2,3,4,5};printArray(arr);// 正确// int brr[6] {1,2,3,4,5,6};// printArray(brr); // 错误类型不匹配return0;}10. 补充函数的引用函数引用与函数指针类似但语法更自然。#includeiostreamvoidhello(){std::coutHello\n;}voidcallFunc(void(func)()){// 函数引用参数func();}intmain(){void(ref)()hello;// 函数引用ref();// 调用callFunc(hello);// 传递函数引用return0;}

更多文章