C++中const的应用(1)之const 形参和实参以及成员函数的 const 和重载
1. 基本概念
- 形参(formal parameter):定义函数时括号里写的变量名。
- 实参(actual parameter):调用函数时传进去的具体值或变量。
加上 const
后:
类型 | 意义 |
---|---|
const 形参 | 承诺函数内部不会修改传进来的参数 |
const 实参 | 传入的值本身是常量或常量引用,防止被改动 |
2. 不同情况讲解
2.1 基本类型的 const 形参
如果是传值(不是引用或指针),const
修饰其实没太大意义(因为传值拷贝了一份,本来也不会影响原数据)。
void func1(const int x) {// x是拷贝来的副本,虽然加了const,但实际没必要
}
传值的话,const
是多余的,只能防止你在函数里改它。
2.2 指针或引用的 const 形参
更常见、更有意义的是指针或引用,加上 const
:
2.2.1 const 引用
void func2(const int& x) {// x是引用,但不能修改
}
- 优点:避免复制(尤其是大对象)
- 安全:保证不会改动传进来的数据
传对象也类似:
void printString(const std::string& str) {std::cout << str << std::endl;
}
常见于大对象传参,比如 std::string
、std::vector
。
2.2.2 const 指针
指针本身是常量:
void func3(int* const p) {// p是常量指针(地址不能改,但*p可以改)
}
指向常量:
void func4(const int* p) {// *p是常量(不能通过p修改*p)
}
组合例子:
void func5(const int* const p) {// 地址不能改,指向的数据也不能改
}
3. 传实参时
- 如果形参是
const T&
,那么实参可以是:- 普通变量
- 临时变量
- const变量
例子:
void show(const std::string& str) {std::cout << str << std::endl;
}int main() {std::string s = "hello";show(s); // 普通变量show(std::string("world"));// 临时变量const std::string cs = "const";show(cs); // const变量
}
4. 小结口诀
- 传值:小对象直接传,不必加
const
- 传引用:大对象加
const &
,又快又安全 - 指针:明确谁是常量(指针本身,还是指向的内容)
- 实参:只要类型兼容,都能传给
const
形参
5. 总结表
场景 | 写法 | 意义 |
---|---|---|
小对象传值 | void foo(int x) | 不用 const ,没影响 |
大对象传引用 | void foo(const std::vector<int>& v) | 节省拷贝,安全 |
修改指针指向内容 | void foo(int* p) | 可以改指向的内容 |
保护指针指向内容 | void foo(const int* p) | 不能改内容 |
固定指针地址 | void foo(int* const p) | 不能改指针本身 |
6. 成员函数的 const
和重载
6.1 为什么要加 const
成员函数?
如果一个成员函数不会修改对象的成员变量,就应该在函数后面加 const
,告诉编译器和使用者:
这个函数承诺不会改对象。
写法是:
class MyClass {
public:int getValue() const; // const成员函数
};
注意这里的 const
写在函数声明末尾,不是在参数里!
如果不加 const
,那么用const对象调用时就出错了。
6.2 举例子:const和非const版本
来看个标准例子(比如一个自定义数组类):
#include <iostream>
#include <vector>class MyArray {
private:std::vector<int> data;
public:MyArray(std::initializer_list<int> list) : data(list) {}// 非const版本,允许修改元素int& operator[](size_t idx) {return data[idx];}// const版本,只读const int& operator[](size_t idx) const {return data[idx];}
};int main() {MyArray arr = {1, 2, 3, 4};arr[0] = 10; // 非const对象,调用非const版本,可以修改const MyArray arr2 = {5, 6, 7};std::cout << arr2[1] << std::endl; // const对象,只能调用const版本,只读
}
关键点总结
区别 | 解释 |
---|---|
int& operator[](size_t idx) | 返回非常量引用,可以赋值修改 |
const int& operator[](size_t idx) const | 返回常量引用,只能读,不能改 |
只有写了const版本,才能让 const对象
正常调用函数!
6.3 小扩展:普通成员函数 vs const成员函数
如果你有一个函数比如 getSize()
这样的:
size_t getSize() const {return data.size(); // 只读操作,安全
}
如果写成没有 const
的:
size_t getSize() {return data.size();
}
那么 const
对象就不能调用了!比如下面的写法会报错:
const MyArray arr = {1, 2, 3};
arr.getSize(); // ❌ 没有const版本会报错
所以原则是:
- 不改对象的成员变量 ➔ 写成
const
成员函数 ✅ - 会改对象的成员变量 ➔ 正常函数,不加
const
7. 小总结
特性 | 说明 |
---|---|
const 成员函数 | 声明时在函数末尾加 const |
重载 const 和非const | 常见于 operator[] 、at() 、begin()/end() |
const对象 只能调用 const成员函数 | 所以经常要写两版 |
8. 延伸思考
如果类很大,想避免重复代码怎么办?可以const_cast 优雅复用(稍高级,后面进行讲解!)
比如写非const版本,然后const版本内部调用非const版。