const限定词的用法一直是我学习、使用C/C++时遇到的困扰之一,想着在这里慢慢记录每一次遇到的问题吧。
主要是const在各个位置所代表的含义与区别。
在本文中参考到的文章链接:
https://blog.csdn.net/liyuan123zhouhui/article/details/51513382
https://bbs.csdn.net/topics/70273340
https://blog.csdn.net/jisuanji198509/article/details/80557333
文章目录
1.声明中的const
Example 1:
int n = 5;
int* pint = &n;
const int* cpint = &n;
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
n = 6; // 正确:没有限定n
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
*pint = 7; // 正确:没有限定pint
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
// *cpint = 8; // 错误:cpint被限定为const,无法修改指向内容的值
Out (Example 1):
n = 5, *pint = 5, *cpint = 5, pint = 0000002A39AFDFC4
n = 6, *pint = 6, *cpint = 6, pint = 0000002A39AFDFC4
n = 7, *pint = 7, *cpint = 7, pint = 0000002A39AFDFC4
// 若加上注释行,则报错为
// C3892 “cpint”: 不能给常量赋值
Example 2:
int m = 4;
int n = 5;
int* pint = &n;
int* const cpint = &n;
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
n = 6; // 正确:没有限定n
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
*pint = 7; // 正确:没有限定pint
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
*cpint = 8; // 正确:cpint的指向被限定为const,但当前指向的值可修改
cout << "n = " << n
<< ", *pint = " << *pint
<< ", *cpint = " << *cpint
<< ", pint = " << pint << endl;
//cpint = &m; // 错误:cpint的指向被限定为const,无法更改指向
Out (Example 2)【总结】:
n = 5, *pint = 5, *cpint = 5, pint = 0000000F651EDD44
n = 6, *pint = 6, *cpint = 6, pint = 0000000F651EDD44
n = 7, *pint = 7, *cpint = 7, pint = 0000000F651EDD44
n = 8, *pint = 8, *cpint = 8, pint = 0000000F651EDD44
// 若加上注释行,则报错为
// C3892 “cpint”: 不能给常量赋值
TIP:
对于上述两个用法实际区分的是const在*的左边还是右边,因此有如下三种使用使用方法都是正确的:
int n = 5, m = 3;
const int* cpint1 = &n; // const数据,非const指针
int const* cpint2 = &n; // const数据,非const指针
int* const cpint3 = &n; // 非const数据,const指针
const int* const cpint4 = &n; // const数据,const指针
// *cpint1 = 3; // 报错
// cpint3 = &m; // 报错
2.函数中的const
Example 1:
int func(const int& val) {
int n = val + 1; // 正确:val作为右值可用于计算
val = 3; // 错误:val被const限定,无法修改值
return 1;
}
TIP:
在此处使用const的目的是,让编程人员清楚的知道,此处使用引用传递,保证该参数在函数中不允许被修改。相应地,当编程人员想要修改引用的变量时,一般使用指针进行传值。
Example 2:
const int func(int val) {
return 1;
}
这里使用const,是保证函数的返回值不被修改,也许你会质疑这种可能性,但是这种可能性确实存在,
详细情形如下: (摘自effective c++)
const rational operator*(const rational& lhs,
const rational& rhs);
很多程序员第一眼看到它会纳闷:为什么operator*的返回结果是一个const对象?因为如果不是这样,用户就可以做下面这样的坏事:
rational a, b, c;
...
(a * b) = c; // 对a*b的结果赋值
我不知道为什么有些程序员会想到对两个数的运算结果直接赋值,但我却知道:如果a,b和c是固定类型,这样做显然是不合法的。一个好的用户自定义类型的特征是,它会避免那种没道理的与固定类型不兼容的行为。对我来说,对两个数的运算结果赋值是非常没道理的。声明operator*的返回值为const可以防止这种情况,所以这样做才是正确的。
Example 3.1:
class foo{
private:
int m_member;
public:
int func() const; // 在函数形参的括号外面有一个const
};
我最不明白的就是这个const的限定,网上的介绍如下:
(1)const对象的访问只能靠const成员函数
(2)const成员函数不被允许修改它所在对象的任何一个数据成员。
最初我的理解就是,这个const只用于类成员函数的后面,用来限定:不能在函数中修改类成员变量的值(因为正常访问修改类成员变量,只能通过类成员函数)。
后来遇到一个问题,发现这个const在用const(数据)修饰的类指针中有特定的用处:
Example 3.2:
说明:
我最初想写一个类test(其功能可以是存储一些特定的数据类型),在func.h和func.cpp文件中使用test类的数组来显示数据(总之就是不会更改类成员),因此在使用该类的数组/指针时应该在前面加上数据const的限定来告知程序员这一点。但是在实际操作时发现,这样使用时,在foo子程序中无法调用类成员函数。伪代码如下:
// test.h
class test{
private:
int i;
public:
test(int i);
void print();
};
// func.h
// func.cpp
#include "test.h"
void foo(const test* p_test,const int n){
for(int i = 0; i < n; i++)
p_test[i].print(); // 这里无法调用 print() 函数
}
其原因是,使用了const(数据)修饰指针,使得编译器认为你的类成员变量是无法修改的,即使是类成员函数也不应该修改类成员变量,但是编译器默认每一个类成员函数是有资格修改类成员变量的!除非你使用const限定来告知编译器,而这个const就加在类成员函数的最后。伪代码如下:
// test.h
class test{
private:
int i;
public:
test(int i);
void print() const; // 这个const告诉编译器,这个成员函数是不修改成员变量的
// 因而可以被const数据保护的类变量调用
};
// func.h
// func.cpp
#include "test.h"
void foo(const test* p_test,const int n){
for(int i = 0; i < n; i++)
p_test[i].print(); // 编译器已被告知 print() 函数不会修改成员变量,符合const数据的限定
// 因此这里可以通过编译
}
总结:
对于const限定的类变量,只能调用const限定的类成员函数(该const加在函数声明的末尾);而const限定的类成员函数则可以被任意类变量所调用。(代码示意如下)
(关于这个const,若有其他用处和注意点,欢迎留言。)
// test.h
class test{
private:
int i;
public:
test(int i);
void print1();
void print2() const;
};
// main.cpp
#include "test.h"
int main(){
test obj1(123);
const test obj2(1234);
obj1.print1(); // 正确,非const数据保护的类对象任意调用类成员函数
obj1.print2(); // 正确,非const数据保护的类对象任意调用类成员函数
obj2.print1(); // 错误!const数据保护的类对象 不能 调用 非const限定 的类成员函数
obj2.print2(); // 正确,const数据保护的类对象 只能 调用 const限定 的类成员函数
return 1;
}
3.使用const代替#define
const double pi = 3.1415926; // 推荐
#define pi 3.1415926 // 不推荐
这也是网络上很多人推崇的做法,原因是:使用define(宏)实际上是在编译时,将所有的对应字符进行替换,从而会导致产生一些意料外的错误。我印象中老师给我们举过这样一个例子:
#define foo 3 + 4
int n;
int ans;
n = 2;
ans = n * foo;
对于大部分人的直观理解来说,或许编程人员想要的结果是ans = n * (3 + 4);即输出结果为ans = 14。
但实际上,编译器处理时会替换成:
ans = n * 3 + 4; // 处理结果为 ans = 10
而这一点在复杂的程序中一般能通过编译,却不易发现错误之处。