一、const
修饰普通变量
const
修饰的变量不可修改(常量),必须在定义时初始化:
const int x = 10; // x 不可修改
x = 20; // 编译错误!
二、const
修饰指针
2.1、指向常量的指针(指针指向的内容不可变)
不能通过该指针修改其指向的变量,又叫底层 const
int a = 10, b = 20;
const int* ptr = &a; // 或 int const* ptr;
// *ptr = 30; // 错误!不能通过ptr修改a的值
a = 30; // 合法,直接修改a
ptr = &b; // 合法,可以改变指针指向
2.2、常量指针(指针本身不可变)
指针一旦初始化后就不能再指向其他地址,可以通过该指针修改它所指向的值。又叫顶层const
int a = 10, b = 20;
int* const ptr = &a;
*ptr = 30; // 合法,可以修改指向的值
// ptr = &b; // 错误!不能改变指针指向
2.3、指向常量的常量指针(指针和指向的内容都不可变)
指针不能改变指向,也不能通过该指针修改它所指向的值
int a = 10, b = 20;
const int* const ptr = &a;
// *ptr = 30; // 错误!不能修改指向的值
// ptr = &b; // 错误!不能改变指针指向
a = 30; // 合法,直接修改a
三、const
修饰引用
const
引用绑定后不能通过引用修改原变量
int x = 10;
const int& ref = x;
// ref = 20; // 编译错误!
四、const
修饰函数形参
const
修饰函数参数,防止参数在函数内被修改
void print(const std::string& str) {// str[0] = 'X'; // 编译错误!std::cout << str << std::endl;
}
五、const
修饰函数返回值
const
修饰返回值时,表示返回值不可被修改
const int* getPointer() {static int x = 10;return &x;
}
// *(getPointer()) = 20; // 编译错误!
六、const
修饰成员变量
表示该成员变量在对象生命周期内不可被修改,必须在构造函数的初始化列表中进行初始化,每个类实例都有自己的 const
成员变量副本,这点与 static
修饰的成员变量不一致,静态成员变量是类的实例共享的。
class MyClass {
public:const int value;MyClass(int v) : value(v) {} // 必须在初始化列表中初始化
};
不能在构造函数内赋值
class Example {const int x;
public:Example() { x = 10; } // 错误!不能在构造函数体内赋值
};
七、const
修饰成员函数
const
修饰成员函数时,表示该函数不会修改类的成员变量(除非成员变量被 mutable
修饰)
class MyClass {
public:int a;mutable int b;MyClass(int x, int y): a(x), b(y) {}int getA() const { return a; }int getB() const { return b; }// void setA(int v) const { a = v; } // 编译错误!void setB(int v) const { b = v; }
};int main() {MyClass myClass(1, 2);myClass.setB(3);std::cout << myClass.getA() << std::endl;std::cout << myClass.getB() << std::endl;return 0;
}
const
对象只能调用const
成员函数- 非
const
对象优先调用非const
版本,也可调用const
版本
八、const
修饰类对象
const
类对象只能调用 const
成员函数(对象状态不可变)
class MyClass {
public:int a;mutable int b;MyClass(int x, int y): a(x), b(y) {}int getA() const { return a; }int getB() const { return b; }// void setA(int v) const { a = v; } // 编译错误!void setB(int v) const { b = v; }
};int main() {const MyClass myClass(1, 2);myClass.setB(3);std::cout << myClass.getA() << std::endl;std::cout << myClass.getB() << std::endl;return 0;
}
九、static const
成员变量
static
特性:
- 属于类而非对象(所有对象共享同一份拷贝)
- 生命周期贯穿整个程序运行期间
- 需要在类外单独定义(C++17 前)
const
特性:
- 值不可以修改
- 必须在定义时初始化
9.1、C++11 前的 static const
class MyClass {
public:static const int a = 100; // 声明并初始化(仅整型和枚举类型允许)static const double b; // 非整型必须类外定义static const std::string c; // 非整型必须类外定义
};const int MyClass::a; // C++11 之前 int 也需要类外定义
const double MyClass::b = 100.100; // 非整型必须在类外定义并初始化
const std::string MyClass::c = "hahaha"; // 非整型必须在类外定义并初始化int main() {std::cout << MyClass::a << std::endl;std::cout << MyClass::b << std::endl;std::cout << MyClass::c << std::endl;return 0;
}
9.2、C++11 的 static const
C++11 引入 constexpr
关键字,可以直接在类内初始化
class MyClass {
public:static const int a = 100;static constexpr double b = 10.0;static constexpr const char* c = "hahaha"; // 字符串常量
};int main() {std::cout << MyClass::a << std::endl;std::cout << MyClass::b << std::endl;std::cout << MyClass::c << std::endl;return 0;
}
9.3、C++17 的 static const
C++17 引入了 inline
的其他用法,用于消除 ORD 问题
在 C++17 前,static const
成员变量需要在类外单独定义(通常在 .cpp 文件中),使用 inline
后可以完全在类定义内完成:
class MyClass {
public:inline static const int a = 100;inline static constexpr double b = 10.0;inline static constexpr const char* c = "hahaha"; // 字符串常量
};int main() {std::cout << MyClass::a << std::endl;std::cout << MyClass::b << std::endl;std::cout << MyClass::c << std::endl;return 0;
}
十、考点
10.1、const
可以修饰局部变量嘛?
可以,全局 const
变量通常存储在只读数据段,而局部 const
变量通常存储在栈上(但编译器可能优化)。在C中,const
局部变量不是真正的常量(不能用于数组大小等需要常量表达式的场合),而在C++中,const
局部变量可以用于常量表达式(如果使用常量表达式初始化)。
10.2、const
成员函数和普通成员函数可以重载嘛?
可以,const
修饰的成员函数可以与普通成员函数构成重载,因为const
成员函数实际上具有不同的函数签名。
class MyClass {
public:// 普通成员函数void display() { std::cout << "Non-const display" << std::endl; }// const成员函数(构成重载)void display() const { std::cout << "Const display" << std::endl; }// 可以修改成员变量void modify() { value = 10; }// 不能修改成员变量void readOnly() const {// value = 20; // 错误!const成员函数不能修改成员变量std::cout << value << std::endl;}int value = 0;
};int main() {MyClass obj1; // 非const对象const MyClass obj2; // const对象obj1.display(); // 调用非const版本obj2.display(); // 调用const版本obj1.modify(); // 合法// obj2.modify(); // 错误!const对象不能调用非const成员函数obj1.readOnly(); // 合法,非const对象可以调用const成员函数obj2.readOnly(); // 合法
}
10.3、下面这个成员函数是什么意思
class DataContainer {
public:std::vector<int> data;const int* getData() const {return data.empty() ? nullptr : &data[0];}
};
- 第一个
const
修饰返回值(返回的指针不能用于修改指向的内容) - 第二个
const
修饰成员函数(不能修改成员变量)
所以这个成员函数的两个 const
修饰的意义是不一样的。
10.4、const
与 #define
的区别
特性 | const | #define |
---|---|---|
调试支持 | 可调试 | 不可调试 |
内存分配 | 分配内存 | 不分配内存 |
类型检查 | 有 | 无 |
作用域 | 有作用域 | 无作用域(全局) |
是否可取消 | 不可取消 | 可通过#undef 取消 |
10.5、const
与引用的关系
常量引用:
int y = 30;
const int &cr = y; // 常量引用
// cr = 40; // 错误!不能通过cr修改y
y = 40; // 但可以直接修改y
引用常量:
int z = 30;
int& const cr = z; // 错误引用本身就不能重新绑定
这么写是没有意义的,因为引用本身就不能重新绑定,只能在初始化的时候绑定一次,所以之前也有一种说法:引用的底层就是指针常量
10.6、以下代码哪些是错误的?为什么?
const int a = 10;
int *p = &a; // 错误,普通指针不能指向const变量
const int *p2 = &a; // 正确
int* const p3 = &a; // 错误,同第一个
const int* const p4 = &a; // 正确
10.7、为什么static
成员函数不能是const
?
因为static
成员函数没有this
指针,不属于特定对象,而const
是承诺不修改所属对象。