const
1. 指针
const修饰 * 号,则不能把解引用作为左值;
const修饰指针名,则不能把指针名作为左值。
1.1 const修饰指针——常量指针
在 const修饰指针的情况下,指针的指向可以更改,但是不能通过指针去改变它所指向的变量的值。
int a = 10;
int b = 20;
const int *p = &a;//const修饰的指针,常量指针
p = &b;//正确,常量指针可以更改其指向
*p = 30; //错误,无法通过解引用修改其指向的变量的值
1.2 const修饰常量——指针常量
由于const修饰的是常量,因此在这种情况下,指针的指向不可以改,但是指向的值可以改。
int * const p = &a;//const修饰p,是一个指针常量
p = &b;//错误,指针的指向不能修改,即p的值不变
*p = 30;//正确,可以解引用修改a的值
1.3 const同时修饰指针和常量
既不可以修改指针的指向,也不能修改指针的指向的内容。
const int * const p = &a;
p = &b;
*p = 30;//均错误
2. 结构体
struct student
{
string name;
int age;
int score;
}
void PrintStudent(const student *p)
{
cout << p->name << p->age << p->score << ednl;
}
const 在结构体中的应用基本就是这样一个场景。
如果 PrintStudent 的形参不是指针而是 student p , 由于是值传递的形式,程序会拷贝一份副本,倘若实参很大或者不方便拷贝,则会导致效率低下甚至报错。因此这里选择使用指针,只需传递一份地址即可。
由于指针传递会修改实参的值,我们在编写这个函数的时候并不希望它修改结构体的值,因此使用 const 修饰指针,这样就无法通过解引用或者 -> 运算符修改实参的值了。
3. 类
与类有关的const可出现的位置很多,包括有:
- 常成员变量
- 常成员函数
- 常对象
3.1 常成员变量
class p
{
public:
const int m_h;
int m_m;
};
上面p的成员m_h就是一个常成员变量,常成员变量进可以使用构造函数的初始化列表赋值,且不能再以后的使用过程中进行修改。
p::p(int h, int m) : m_h(h),m_m(m){} //这是合法的,使用参数列表进行初始化
p::p(int h, int m)
{
this->m_h = h;
this->m_m = m;
} //非法,不可以使用赋值的方法初始化常成员变量
3.2 常成员函数
常成员函数指的是在形参列表后用const修饰的成员函数,如下。常成员函数允许读取对象中的成员,但是不允许修改常对象的值。
常函数的const意在表明这个函数不会修改任何一个成员的值。
void p::show() const
{
cout << this->m_h << endl;
}
3.3 常对象
当我们将对象定义为常对象的时候,默认在所有的成员中添加了const关键词,这导致了该对象不能修改任何成员变量。
因此,常对象只能调用常成员函数,非常成员函数无法调用,会报错。
int& p::set(int &m)
{
this->m_m = m;
}
const p p1;
p1.set(m); //报错,因为常对象无法调用非常成员函数
由于存在了const,就产生了一些访问权限的问题。
3.4 方法对成员的访问规则
成员 | 非const方法 | const方法 |
---|---|---|
非const成员 | 可引用 可改变值 | 可引用 可改变值 |
const成员 | 可引用 不可改变值 | 可引用 可改变值 |
const对象的成员 | 不可引用 不可改变值 | 可引用 不可改变值 |
3.5 遇到的问题
我遇到过这样的一个问题。我定义了一个类,其中有一个成员函数Getname()作用是获取其隐私权限下的一个成员。如果我像下面这样写的话,就会报错。
class Player
{
public:
Player(){}
Player(string name, int age) :m_name(name), m_age(age){}
string& Getname()
{
return this->m_name;
}
int& Getage()
{
return this->m_age;
}
bool operator==(const Player& p)
{
return (this->Getage() == p.Getage() && this->Getname() == p.Getname());
}
private:
string m_name;
int m_age;
};
报错信息为:
IntelliSense: 对象包含与成员函数不兼容的类型限定符
对象类型是: const Player
报错行是operator==()函数。这是因为operator==()的形参为常量引用,根据上面的规则,常量引用的形参无法调用非常量函数,因此p.Getname()报错。
出于这个考虑,我尝试将Getname()函数定义成常函数。
string& Getname() const
{
return this->m_name;
}
int& Getage() const
{
return this->m_age;
}
但是依然报错,此时的报错信息为:
error C2440: “return”: 无法从“const std::string”转换为“std::string &”
error C2440: “return”: 无法从“const int”转换为“int &”
报错的就是这两个函数,经过考虑后我认为原因是这个函数return的量在const修饰下变成了常量,但是返回值类型string& 是个非常量引用。我们知道,将常量绑定到非常量引用上是非法的,导致了限定符const被丢弃,所以在这里报错了。
解决办法可以是将返回值类型string& 变为 const string&。对于这个函数而言,目的就是获取成员,倘若返回一个常量似乎也是合情合理的。但是我对这个办法总是觉得难以接受,我不知道我是否会遇到一个返回值应当允许改变的常成员函数。,我在想遇到这样的情况的话应该怎么办。所以有点不是很接受这个解决办法。
但是目前就先这样吧。以后在这种不需要修改返回值的情况下不仅形参写成const,可能还需要函数本身被const修饰,以及返回类型也是const。
4. Primer C++上的一小节
下面引用Primer C++上面的一小节内容,讲的是常量引用以及可能出现的问题。
尽量使用常量引用
把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用非常量引用也会极大的限制函数所能接受的实参类型。就像刚刚看到的,我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。
这种错误绝不像看起来那么简单,它可能造成出人意料的后果。以6.2.2节的find_char函数为例,那个函数(正确地)将它的string类型的形参定义成常量引用。假如我们把它定义成普通的string&://不良设计,第一个形参应该是const string& string::size_type find_char(string& s,char c, string::size_type& occurs)
则只能将find_char函数作用域string对象。类似下面的调用:
find_char("Hello World",'o',ctr);
将在编译时发生错误。
还有一个更难察觉的问题,加入其他函数(正确地)将它们的形参定义成常量引用,那么第二个版本的find_char无法在此类函数中正常使用。举个例子,我们希望在一个判断string对象是否是句子的函数中使用find_char:bool is_sentence(const string& s) { //如果在s的末尾有且只有一个句号,则s是一个句子 string::size_type ctr = 0; return (find_char(s,'.',ctr) == s.size() - 1 && ctr == 1); }
如果find_char的第一个形参类型是string&,那面上面这条调用find_char的语句将在编译时发生错误。原因在于s是常量引用,但是find_char被(不正确地)定义成只接受普通引用。
解决该问题的一种思路是修改is_sentence的形参类型,但是这么做只不过转移了错误而已,结果是is_sentence函数的调用者只能接受非常量string对象了。
正确的修改思路是改正find_char函数的形参。如果实在不能修改find_char,就在is_sentence内部定义一个string类型的变量,令其为s的副本,然后办这个string对象传递给find_char。