const成员变量
const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表.
构造函数的一项重要功能是对成员变量进行初始化,为了达到这个目的,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。
初始化列表可以用于全部成员变量,也可以只用于部分成员变量
成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。
构造函数初始化列表还有一个很重要的作用,那就是初始化 const 成员变量。初始化 const 成员变量的唯一方法就是使用初始化列表。例如 VS/VC 不支持变长数组(数组长度不能是变量),我们自己定义了一个 VLA 类,用于模拟变长数组,请看下面的代码:
class VLA{
private:
const int m_len;
int *m_arr;
public:
VLA(int len);
};
//必须使用初始化列表来初始化 m_len
VLA::VLA(int len): m_len(len)
{
m_arr = new int[len];
}
const成员函数(常成员函数)
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。const 成员函数也称为常成员函数。
非const成员函数可以调用const成员函数,const成员函数不能调用非const成员函数
我们通常将 get 函数设置为常成员函数。读取成员变量的函数的名字通常以get
开头,后跟成员变量的名字,所以通常将它们称为 get 函数。
常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字,请看下面的例子:
class Student{
public:
Student(char *name, int age, float score);
void show();
//声明常成员函数
char *getname() const;
int getage() const;
float getscore() const;
private:
char *m_name;
int m_age;
float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义常成员函数
char * Student::getname() const{
return m_name;
}
int Student::getage() const{
return m_age;
}
float Student::getscore() const{
return m_score;
}
getname()、getage()、getscore() 三个函数的功能都很简单,仅仅是为了获取成员变量的值,没有任何修改成员变量的企图,所以我们加了 const 限制,这是一种保险的做法,同时也使得语义更加明显。
需要强调的是,必须在成员函数的声明和定义处同时加上 const 关键字。char *getname() const
和char *getname()
是两个不同的函数原型,如果只在一个地方加 const 会导致声明和定义处的函数原型冲突。
const对象:
在 C++中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了。
定义常对象的语法和定义常量的语法类似:
const class object(params);
class const object(params);
当然你也可以定义 const 指针:
const class *p = new class(params);
class const *p = new class(params);
class
为类名,object
为对象名,params
为实参列表,p
为指针名。两种方式定义出来的对象都是常对象。
一旦将对象定义为常对象之后,不管是哪种形式,该对象就只能访问被 const 修饰的成员变量和 c成员函数了,因为非 const 成员可能会修改对象的数据(编译器也会这样假设),C++禁止这样做。
const 和非 const 类型转换
当一个指针变量 str1 被 const 限制时,并且类似const char *str1
这种形式,说明指针指向的数据不能被修改;如果将 str1 赋值给另外一个未被 const 修饰的指针变量 str2,就有可能发生危险。因为通过 str1 不能修改数据,而赋值后通过 str2 能够修改数据了,意义发生了转变,所以编译器不提倡这种行为,会给出错误或警告。const char *
和char *
是不同的类型,不能将const char *
类型的数据赋值给char *
类型的变量(类型限制多的只能在赋值号左边),但反过来是可以的,编译器允许将char *
类型的数据赋值给const char *
类型的变量。char *
指向的数据有读取和写入权限,而const char *
指向的数据只有读取权限,降低数据的权限不会带来任何问题,但提升数据的权限就有可能发生危险。
C语言标准库中很多函数的参数都被 const 限制了,但我们在以前的编码过程中并没有注意这个问题,经常将非 const 类型的数据传递给 const 类型的形参,这样做从未引发任何副作用,原因就是上面讲到的,将非 const 类型转换为 const 类型是允许的。
class Screen {
public:
char get(int x,int y);
char get(int x,int y) const;
};
int main()
{
const Screen cs;
Screen cc2;
char ch = cs.get(0, 0); // 调用const成员函数
ch = cs2.get(0, 0); // 调用非const成员函数
}
在这种情况下,类对象的常量性决定调用哪一个函数:
非const成员函数可以调用const成员函数,const成员函数不能调用非const成员函数(实际应用中基本不会用到,也不会这么干)。
非const成员函数、const成员函数可以任意访问const成员变量、普通变量(const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值)。
const对象只可以调用const成员函数,非const对象任意调用成员函数。
const修饰的是谁?
const成员函数的写法有两种
1、void fun(int a,int b) const{}
2、void const fun(int a,int b){}
这两种写法的本质是:void fun (const 类 *this, int a,int b);
const修饰的不是形参a和b;const修饰的是属性this->a和this->b。与const所写的位置无关。
为什么?
因为c++对类的this指针做了隐藏,本质上,const指针修饰的是被隐藏的this指针所指向的内存空间,修饰的是this指针。
总结:
类中的const成员变量需要在构造函数初始化列表中初始化。
const 对象:在该对象生命周期内,必须保证没有任何成员变量被改变。const对象只能调用const成员函数和成员变量。
const成员函数: void fun() const ... 不仅能被const对象调用,也能被非const对象调用,因此,如果确认一个任何成员函数不改变任何成员变量,应该习惯性将该函数定义成const类型。