C++基础专栏:http://t.csdnimg.cn/4FdOH
目录
1.引言
2.修饰变量
2.1.修饰普通变量
2.2.修饰指针
2.3.修饰对象
3.修饰函数形参
4.修饰函数返回值
5.修饰类成员函数
6.const与constexpr区别
7.总结
1.引言
在面试的过程,面试官经常都会问到const关键字有哪些用法?在C/C++中各有些什么作用?今天就在这里我们好好的总结一下const的全方位用法。
2.修饰变量
2.1.修饰普通变量
在普通变量前面加上const,表示该变量是常量,不能随便更改值的变量。const 类型的变量必须在定义时进行初始化,之后不能对const型的变量赋值。示例如下:
在变量定义前加关键字const,修饰该变量为常量,不可修改
const int year= 12;
const char p = 'f';
注意:在函数内部定义的const局部变量和全局定义的const变量还是有很大的不同的?函数内部定义的局部const变量存放在栈区中,会分配内存(也就是说可以通过地址间接修改变量的值), 如:
void func()
{
const int a = 10;
//a += 1; //编译错误,常量是不能修改的
int* p = &a;
*p = 20; //a = 20, 通过指针间接是可以修改a的值的
}
全局const变量存放在只读数据段(不能通过地址修改,会发生写入错误), 默认为外部联编,可以给其他源文件使用(需要用extern关键字修饰)。如:
const int a = 10;
//const int b = a; //不能用全局变量全局初始化const变量,编译不通过,表达式必须含有常量值
void func()
{
int* p = &a;
*p = 20;//编译通过,但运行时会发生写入错误
}
如果是在C++类中定义的const成员变量,那么必须在初始化列表中赋值,如:
//类定义
class CBase
{
public:
explicit CBase();
private:
const int m_a;
};
//类构造函数
CBase::CBase()
: m_a(100)
{
//m_a = 200; //在这里赋初值会出现编译错误,因为这里不是m_a不是赋初值的地方
}
2.2.修饰指针
const修饰指针有几种情况:
① const修饰指针 --- 常量指针
语法:const 数据类型 * 变量名;如:const int* p;
② const修饰常量 --- 指针常量
语法:数据类型 * const 变量名;如:int* cont p;
③ const即修饰指针,又修饰常量
语法:const 数据类型 * const 变量名;如:const int* const p;
综合示例如下:
int func() {
int a = 10;
int b = 10;
//1)常量指针:const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
//2)指针常量:const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
//3)const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
return 0;
}
技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
2.3.修饰对象
const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
示例如下:
class CMyTest
{
public:
CMyTest() : m_a2(10),m_b2(20) {}
void func1(){
m_b1 = 10; //OK
//m_b2 = 30; //ERROR: m_b2是常量不能修改
m_a3 = 40; //OK
}
void func2() const{
//m_b1 = 10; //ERROR: const成员函数不能修改非const成员变量
//m_b2 = 60; //ERROR: m_b2是常量不能修改
m_a3 = 80; //OK
}
public:
int m_a1;
const int m_a2;
mutable int m_a3;
private:
int m_b1;
const int m_b2;
mutable int m_b3;
};
int main()
{
const CMyTest test;
//test.m_a1 = 1000; //ERROR : 编译错误,常对象不能非const成员变量
//test.m_a2 = 2000; ; //ERROR : 编译错误,常对象不能修改const成员变量
test.m_a3 = 3000; ; //OK,常对象能修改mutable成员变量
return 0;
}
3.修饰函数形参
在函数参数前面加上const,表示在函数内部不能修改该参数的值,用const来防止误操作。如:
struct student {int num};
void func(const student &stu){
stu.num = 56; //这句就错误了,不能修改stu中的内容
}
student abc;
abc.num = 100;
func(abc);
std::cout << abc.num << std::endl; //100
4.修饰函数返回值
返回基本数据类型的常量值:当函数返回基本数据类型(如 int
、float
等)时,使用 const
修饰返回值通常没有太大意义。因为基本类型的返回值通常是按值返回,即返回的是一个副本。修改这个副本并不会影响原始值。如:
const int getValue() {
return 11000;
}
返回对象或复合类型的常量引用:当函数返回对象或复合类型(如自定义类、结构体等)时,使用 const
修饰返回值可以防止返回的对象或复合类型被修改。这是一种常见且有用的做法,特别是在返回类的成员变量时。如:
const MyClass& getClass() const {
return myObject;
}
getClass
函数返回 MyClass
类型对象的常量引用。这意味着调用者可以读取返回的对象,但不能修改它。这种做法既保证了数据的安全性,又避免了不必要的复制。
class MyClass {
private:
int value;
public:
const int& getValue() const {
return value;
}
};
// 在使用时
MyClass obj;
int a = obj.getValue(); // a 不是 const
// obj.getValue() = 10; // 错误,不能通过 const 引用修改值
getValue
方法返回 value
成员的常量引用,这样就保护了内部数据不被外部修改。
5.修饰类成员函数
成员函数后加const后我们称为这个函数为常函数;const成员函数,能够访问所有成员变量,但是在函数体内不能直接修改变量的值(包括普通成员变量),如果需要在函数体内修改普通成员变量的值,需要在变量定义的前面添加mutable关键字,或者通过地址间接修改。
注意:const成员函数只能被该类的const对象访问。
示例如下:
class CMyTest
{
public:
void func() const{
//a = 40; //没有加mutable修饰的变量在const成员函数内不能直接修改
//间接修改a的值
int* p = const_cast<int*>(a);
*p = 50;
//用mutable修饰的变量直接通过
c = 60;
}
private:
int a = 10;
const int b = 20;
mutable int c = 30;
};
6.const与constexpr区别
constexpr
和 const
都是 C++ 中用于声明常量的关键字,但它们在使用上有一些重要的区别:
const
const
用于声明一个常量,表示变量的值不可修改。const
可以用于各种类型的变量,包括基本数据类型、对象、指针等。const
变量的值可以在运行时确定。这意味着它可以被初始化为在编译时未知的值,比如函数返回值或者用户输入。const
更加通用,适用于任何需要防止修改的场景。
constexpr
constexpr
用于声明表达式为编译时常量,意味着它的值必须在编译时就已知。constexpr
常量可以用在需要编译时常量表达式的上下文中,如数组大小、整数模板参数等。constexpr
函数能在编译时对其输入进行计算,只要所有输入也都是编译时已知的常量。- 使用
constexpr
声明的变量或函数表示你希望编译器验证它们能够在编译时求值。 constexpr
要求其初始化表达式必须是一个编译时常量表达式。
主要区别
- 编译时 vs. 运行时:
constexpr
确保变量或函数的值能在编译时被确定,而const
变量的值可以在运行时确定。 - 使用场景:
constexpr
适用于需要在编译时进行计算的场景,比如作为模板参数或数组大小。const
更适用于程序运行中不需要修改的值。
示例如下:
//使用const
const int max_size = getSizeFromUser(); // 运行时获取大小
const double pi = 3.14159;
//使用constexpr
constexpr int max_array_size = 100; // 编译时已知的数组大小
constexpr double computeArea(double radius) {
return 3.14159 * radius * radius; // 编译时计算面积的函数
}
7.总结
使用const还有很多好处:
1) 类型安全:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
2) 可以节省空间,避免不必要的内存分配: const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象宏一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而宏定义的常量在内存中有若干个拷贝。
3) 优化性能:对于基本数据类型,const对象可能会被编译器优化,因为它们的值在程序的执行过程中不会改变。const方法和constexpr函数可以在编译时进行计算和优化,这可以提高程序的运行效率。
4) 增加代码的可维护性:通过将某些变量或方法标记为const,你可以更容易地重构和修改代码,同时保持对原始设计意图的忠实。const正确性(const-correctness)是一种良好的编程实践,它有助于减少错误并增加代码的稳定性。
5) 为函数重载提供了一个参考:const修饰的函数可以看作是对同名函数的重载。如:
clas MyTest
{
public:
void func(int a, int b);
void func(int a, int b) const;
};
6) 接口设计:在设计类和函数接口时,使用const可以明确哪些操作是安全的,哪些操作可能会修改对象状态。这对于库的设计尤其重要,因为它允许库的使用者更加清晰地理解如何与库进行交互。
7) 保护数据:在多线程环境中,将数据声明为const可以确保它不会被多个线程同时修改,从而避免数据竞争和未定义行为。当然,在多线程环境中仅仅使用const是不够的,但它是一个有用的工具。
8) 兼容性和转换:const对象可以安全地转换为非const对象(在不需要修改对象的情况下),这提供了更大的灵活性。
参考:
const (C++) | Microsoft Learn