const作用

const关键字在C++中真是无处不在,无论是函数参数,还是函数返回值,还是函数末尾都经常会看到const关键字,这表明C++中的const关键字是非常灵活的,
合理地使用const关键字能大大提高我们程序的健壮性。
被const修饰的即表明是常量性的、只读性的,不可随意修改的。因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化。

const在C和C++中的区别

虽然在C中const也表示不可修改的意思,但是它的校验却没有C++中那么严格,比如在C中被const修改的变量我们可以通过间接绕过的方式修改,但是在C++中却是不行的,例如:

main.c
int main() {
const int a = 100;
a = 50; // 编译错误
int *p = &a;
*p = 50; // 编译成功,可以修改a的值
return 0;
}

main.cpp
int main() {
const int a = 100;
a = 50; // 编译错误,a的值无法修改
int *p = &a; // 无法编译通过
const int *p = &a; // 可以编译通过
*p = 50; // 无法编译通过,不能通过指针修改a的值
return 0;
}

const到底修饰谁

const在不同的位置修饰的内容不一样,对于const到底修饰的是谁,谁才是不可变的这个问题有一个简单的规则就是​​const离谁近,谁就不能被修改;。​
对于const修饰的变量从右往左看,const修饰谁就表示谁不可变 **(注意const不能修饰*)**。也就是const离变量名近就是用来修饰指针变量的,便是这个变量不可变,离变量名远就是用来修饰这个数据的,表示这个数据不可改变。我们通过下面的例子来分析下const到底修饰的是谁:

main.cpp
int main() {
int i = 10;
const int *p1; // const距离int比较近,说明const修饰的是指针p1指向的数据不可变
*p1 = 100; // 错误
int const *p2; // 从右往左看,因为const不可以修饰星号,所以const修饰的是int,因此这种写法与p1是一致的
*p2 = 100; // 错误
int * const p3 = &i; // 这里const距离p3比较近,所以修饰的是p3变量,所以必须初始化
*p3 = 100; // 可以,虽然p3不可以变,但是其指向内容是可变的
int j = 20;
p3 = &j; //错误,p3被const修饰,不可重新指向

const int * const p4 = &i; // 双重修饰,p4不可变,p4指向的内存也不可变
p4 = &j; // 错误
*p4 = 100; //错误
return 0;
}

const与函数之间的那点事

先说下知识点:
1、const可以构成函数重载,如果const构成函数重载,const对象只能调用const函数,非const对象优先调用非const函数。
2、const放在函数末端const修饰类的成员函数表示在本函数内部不会修改类内的数据成员,不会调用其它非const成员函数
3、const函数只能调用const函数,const对象,只能调用const成员函数,非const函数可以调用const函数。
4、const修饰函数的返回值,如果该函数是以值的方式返回,则使用const修饰是没有意义的,如果该函数返回的是指针,则表示返回的指针的内容是不可修改的,需要使用const修饰来接收。
下面是具体的例子:

using namespace std;

class Person{
public:

Person(int a):age(a),name(""){

}

virtual ~Person(){

}

int getAge() const{
name = "hell0"; // 错误,末端带const修饰,表示承诺在该函数内不会改变this对象的内容
std::cout << "getAge const" << std::endl;
return age;
}

int getAge(){
// 与带const的构成函数重载
std::cout << "getAge" << std::endl;
return age;
}

string getName(){
return name;
}

public:
const int age; // 因为有const成员变量的存在,所以这个变量必须要初始化,所以需要自定义构造函数
string name;
};

void printConst(const Person& person){
std::cout << "printConst" << std::endl;
person.name = "hello"; // 错误,不可以修改person
}

void print(Person& person){
std::cout << "print const" << std::endl;
person.name = "hello"; // 正确
}

// 这里加的const是没有价值的
const Person getPerson1(){
return Person(1);
}

// 返回的指针内容不可修改,需要用const接收
const Person* getPerson2(){
return new Person(2);
}

int main() {
Person person1 = getPerson1();
const Person *person2 = getPerson2(); // 因为getPerson2的返回值被const修饰,所以返回的指针的内容是不可修改的,所以不能去掉const
person1.getAge(); // 调用不带const的getAge()
person2->getAge(); // 调用带有const的getAge()
print(*person2); // 错误const对象只能调用const函数
printConst(*person2); // 正确,const对象只能调用const函数
person1.getName(); // 可以
person2->getName(); // 错误,const对象只能调用const函数
return 0;
}

相信结合代码注释和以上四点知识点应该还是挺好理解的,纸上得来终觉浅,要想真正掌握还是需要实打实地自己敲敲实践一把。

const与mutable

mutable可以说是与const为敌的一个关键字了,如果使用mutable关键字修饰类的成员变量,那么在那些被const修饰的函数体内,也是可以修改这个成员变量的,例如:

using namespace std;

class Person {
public:

Person(int a) : age(a), name("") {

}

virtual ~Person() {

}

int getAge() const {
name = "hell0"; // 可以,因为name被mutable修饰了
age = 20; // 不行,age没有被mutable修饰
std::cout << "getAge const" << std::endl;
return age;
}

public:
const int age; // 因为有const成员变量的存在,所以这个变量必须要初始化,所以需要自定义构造函数
mutable string name;
};

void printConst(const Person &person) {
std::cout << "printConst" << std::endl;
person.name = "hello"; // 可以name被mutable修饰了
}

int main() {
Person person(1);
person.getAge();
printConst(person);
return 0;
}

什么时候该用const

对于这个问题,笔者的意见是不管三七二十一,先把能用const修饰的地方都用上const,等等真正使用这些变量或者函数提示无法编译通过的时候再去详细斟酌是需要删除const修饰呢。还是自己的程序设计调用有问题。
相信很多C/C++都使用过宏,特别是一些C/C++高手更是把宏玩得出神入化,但是如果一个宏比较复杂,自己又对宏的掌握不是那么深入的话,往往会程序员们产生一些意想不到的困扰,在《Effective C++》一书的第第二和第三条款作者就提倡:

Item 2: 用 const, enums 和 inlines 取代 #defines

Item 3: 只要可能就用 const

关注我,一起进步,人生不止coding!!!
公粽号:思想觉悟