前面我们学了指针——C++ 中最强大也最容易出错的特性。这一节我们来学两个让代码更安全、更易读的工具:引用和const 限定符。
1. 引用(Reference)
1.1 什么是引用?
引用是变量的「别名」——给一个已存在的变量取另一个名字。对引用的操作,就是对原变量的操作。
inta=10;int&b=a;// b 是 a 的引用(别名)b=20;// 修改 b,就是修改 acout<<a;// 输出 20关键点:
- 引用必须在声明时初始化
- 引用一旦绑定到某个变量,就不能再绑定到其他变量
- 引用不是新变量,它不占用额外的存储空间(概念上)
1.2 引用 vs 指针
引用和指针都能间接访问变量,但有本质区别:
inta=10;int*p=&a;// 指针:需要取地址,需要解引用int&r=a;// 引用:直接用,语法更简洁*p=20;// 指针需要 * 来解引用r=30;// 引用直接用,就像原变量一样| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须在声明时初始化 | 可以不初始化(但不推荐) |
| 可否为空 | 不能为 null | 可以为 null |
| 可否改变指向 | 不能 | 可以 |
| 语法 | 和原变量一样 | 需要*和& |
| 安全性 | 更安全 | 容易出错 |
简单规则:能用引用就不用指针。
1.3 引用作为函数参数
引用最常见的用途是作为函数参数,实现「传引用」:
// 传值:函数内修改不影响外部voidswapBad(inta,intb){inttemp=a;a=b;b=temp;// 外部的 a 和 b 不会变!}// 传引用:函数内修改影响外部voidswapGood(int&a,int&b){inttemp=a;a=b;b=temp;// 外部的 a 和 b 交换了!}intmain(){intx=10,y=20;swapGood(x,y);cout<<x<<" "<<y;// 输出:20 10}1.4 引用作为函数返回值
函数可以返回引用,但要注意:不要返回局部变量的引用!
// ❌ 错误:返回局部变量的引用int&badFunction(){intlocal=42;returnlocal;// local 在函数结束后被销毁,引用变成悬空引用}// ✅ 正确:返回全局变量或静态变量的引用intglobalVar=100;int&getGlobal(){returnglobalVar;// 全局变量不会被销毁}// ✅ 正确:返回参数的引用int&getElement(vector<int>&arr,intindex){returnarr[index];// 数组元素在调用者的作用域中}返回引用的函数调用可以出现在赋值号左边:
vector<int>arr={1,2,3,4,5};getElement(arr,2)=99;// arr[2] 变成 99cout<<arr[2];// 输出 991.5 const 引用
用const修饰引用,表示「只读」:
voidprintValue(constint&x){// x = 100; // ❌ 编译错误:不能修改 const 引用cout<<x;// ✅ 只能读取}intmain(){inta=42;printValue(a);// 正常传入printValue(100);// 也可以传入字面量!// 普通引用不能绑定到字面量,const 引用可以}const 引用的好处:
- 防止函数意外修改参数
- 避免拷贝,提高效率
- 可以绑定到字面量和临时对象
2. const 限定符
2.1 const 变量
const表示「常量」,一旦初始化就不能修改:
constintMAX_SIZE=100;// MAX_SIZE = 200; // ❌ 编译错误conststring NAME="Alice";// NAME = "Bob"; // ❌ 编译错误const 变量必须在声明时初始化,因为之后无法赋值。
2.2 const 与指针
const 和指针的组合是初学者最容易混淆的地方。记住一个口诀:const 在*左边,指向的内容不能改;const 在*右边,指针本身不能改。
inta=10,b=20;// 1. 指向 const 的指针(内容只读)constint*p1=&a;// *p1 = 30; // ❌ 不能通过 p1 修改 ap1=&b;// ✅ p1 可以指向其他变量a=30;// ✅ 直接修改 a 是可以的// 2. const 指针(指针本身只读)int*constp2=&a;*p2=30;// ✅ 可以通过 p2 修改 a// p2 = &b; // ❌ p2 不能指向其他变量// 3. 指向 const 的 const 指针(都不能改)constint*constp3=&a;// *p3 = 30; // ❌// p3 = &b; // ❌记忆方法:
const int*:const 修饰 int,所以 int 不能改 → 内容只读int* const:const 修饰int*,所以指针不能改 → 指针只读
2.3 const 成员函数
在类中,const 放在函数声明末尾,表示这个函数不会修改对象的状态:
classCircle{private:doubleradius;public:Circle(doubler):radius(r){}// const 成员函数:不会修改对象doublegetArea()const{// radius = 10; // ❌ 不能在 const 函数中修改成员变量return3.14159*radius*radius;}// 非 const 成员函数:可以修改对象voidsetRadius(doubler){radius=r;}};voidprintArea(constCircle&c){// c 只能调用 const 成员函数cout<<c.getArea()<<endl;// c.setRadius(5); // ❌ 不能调用非 const 函数}规则:如果一个函数不修改对象状态,就把它声明为const。这样它就能被const引用和const指针调用。
2.4 常量表达式(constexpr)
C++11 引入了constexpr,比const更严格:
constintx=10;// x 是常量constexprinty=20;// y 是编译期常量constintz=getSomeValue();// ✅ 运行时确定的常量也可以// constexpr int w = getSomeValue(); // ❌ 必须编译期能确定constexprintsquare(intn){returnn*n;}constexprintresult=square(5);// 编译期计算,result = 25constexpr告诉编译器:这个值在编译期就能确定,可以做更多优化。
2.5 mutable 关键字
有时候你需要在 const 成员函数中修改某些特定的成员变量(比如缓存、计数器)。mutable就是为此设计的:
classDataFetcher{private:mutableintaccessCount=0;// 即使在 const 函数中也能修改string data;public:stringgetData()const{accessCount++;// ✅ mutable 变量可以在 const 函数中修改returndata;}intgetAccessCount()const{returnaccessCount;}};3. 引用与 const 的实际应用
3.1 函数参数的最佳实践
// ✅ 最佳实践:按需选择参数传递方式// 基本类型(int、double 等):传值voidprocessInt(intx){/* ... */}// 不需要修改的大对象:const 引用voidprintVector(constvector<int>&vec){for(intx:vec)cout<<x<<" ";}// 需要修改的参数:引用voidsortVector(vector<int>&vec){sort(vec.begin(),vec.end());}// 可能为空的参数:指针voidprocessMaybeNull(int*ptr){if(ptr!=nullptr){// 处理数据}}3.2 返回值的最佳实践
classMatrix{private:vector<vector<int>>data;public:// 返回 const 引用:只读访问constvector<int>&getRow(inti)const{returndata[i];}// 返回非 const 引用:可修改访问vector<int>&getRow(inti){returndata[i];}// 返回值:小对象或需要返回新对象时intgetElement(inti,intj)const{returndata[i][j];}};3.3 范围 for 循环中的引用
vector<int>arr={1,2,3,4,5};// 只读遍历:const 引用for(constint&x:arr){cout<<x<<" ";// 高效,不拷贝}// 修改遍历:非 const 引用for(int&x:arr){x*=2;// 原地修改每个元素}// 不用引用:会拷贝每个元素(对于大对象效率低)for(intx:arr){cout<<x<<" ";// 基本类型没问题,string 等大对象会有额外拷贝}4. 常见陷阱
4.1 悬空引用
int&getDangling(){intlocal=42;returnlocal;// ⚠️ 返回局部变量的引用}intmain(){int&ref=getDangling();cout<<ref;// 未定义行为!可能输出垃圾值}4.2 const 丢失
constinta=10;constint*p=&a;// int* bad = p; // ❌ 编译错误:不能丢弃 constint*bad=const_cast<int*>(p);// ⚠️ 强制转换,但修改 a 是未定义行为4.3 引用的引用
inta=10;int&r1=a;int&r2=r1;// r2 是 a 的引用,不是 r1 的引用r2=20;cout<<a;// 输出 205. 小结
本节我们学习了引用和 const 限定符:
- 引用是变量的别名,必须初始化,不能重新绑定
- const 引用可以绑定到字面量,防止意外修改
- const 指针有两种:指向内容的 const 和指针本身的 const
- const 成员函数表示不修改对象状态
- constexpr是编译期常量,比 const 更严格
最佳实践:
- 函数参数优先用
const 引用(只读)或引用(需修改) - 不修改对象的成员函数都声明为
const - 能用引用就不用指针
- 用
constexpr代替const定义编译期常量
下一节我们将学习类与对象基础——C++ 面向对象编程的起点。