作者:慧科集团华东校区-朱家聪老师,转载请注明出处及本链接。
const关键词的使用
const和类
const成员变量
在定义类中的成员变量时,可以使用const关键词进行修饰。被const关键词所修饰的成员变量会以常量的形式储存在内存中。在程序的运行过程中,const变量的值不能被改变。只能够在构造函数的初始化列表中对这个值进行初始化。并且只能够在初始化列表中进行赋值,初始化函数内部并不行。
class User{
private:
const int userID;
string username;
public:
User(string name, int uid):username(name), userID(uid){};
};
const成员函数
定义一个类的成员函数时,在函数的末尾加上const关键词。当这个函数在运行过程中,const改变了函数中this指针的类型,使得this指针指向了一个const对象。导致了函数执行过程中只能获取到对象中的各种成员变量的值,不能进行修改。在实际编程过程中,任何不需要对当前对象中成员变量的值进行更改的函数都应该添加const关键字。
bool login(string user_name, string pass_word) const {
// username = "12345"; 在const函数中无法修改成员变量的值
cout<<username<<endl; //可以获取到当前对象中成员变量的值
return true;
}
const函数在声明和定义时,都需要添加const关键词。在调用时不需要添加。
//声明
bool login(string user_name, string pass_word) const;
//定义
bool User::login(string user_name, string pass_word) const{
return true;
};
### ```
### mutable关键词
在const成员函数中,无法修改成员变量的值。但是在某些特殊情况下,仅仅希望改变其中一个成员变量的值。这时候可以使用mutable来标记这个成员变量,让其能够在const函数中改变值。
```cpp
class User{
private:
string username;
mutable int m_data;
public:
void set_data(int data) const{
//username = "Hello"; //不能改变没有被mutable标记的变量
m_data = data; //能够改变m_data的值
};
void print_data(){
cout<<m_data<<endl;
}
};
const和指针
指向const对象的指针
const User *u2 = new User("1234567", 20);
u2->set_data(10);
在实例化一个类对象时,使用const关键词可以创建一个const类型的对象。这个对象是一个常量,不能被改变。const类型的对象有以下个特点:
1. 不允许将一个const类型的对象的地址赋值给一个指向非const对象的指针。
2. 允许将一个非const类型的对象的地址赋值给一个指向const对象的指针,
3. 不能通过const指针来修改其所指向的对象的值。
4. 常在函数的行参中使用const类型对象的指针,来保证实际对象在函数执行过程中不被修改。
5. 行参是一个指向const对象的指针时,实参可以是const对象的地址,也可以是非const类型。
const类型的指针
指针除了能够指向const类型的对象外,指针本身也可以是const类型的。这样表示这个指针是一个常量指针,其本身所储存的内存地址无法被改变,需要做创建时进行初始化。也就是说这个指针在创建时指向了某一个对象,然后在使用过程中不能再被指向其他对象。而const指针所指向的对象能否被修改取决于这个对象本身的类型是否为const。
User *const u1;
const引用
对于引用类型,也可以使用const来进行修饰。
1. 非const引用不能绑定const对象。
2. 非const引用只能绑定同类型的非const对象。
3. const类型的引用能够作为右值绑定到能够互相隐式转化的cosnt类型中去。
//1
const int num1 = 10;
int &num2 = num1; //错误
//2
int num3 = 5; //非const类型变量
int &num4 = num3; //正确
doble &num5 = num3; //错误
//3
const double &num6 = 2.5;
const int &num7 = num6; //int和double之间隐式转化
友元
友元机制允许一个类将其对非公有成员的访问权限授予指定的函数或者类。友元机制会破坏面向对象的封装性。使用friend来声明一个友元类或函数。使得在指定的类或者指定的函数中能够使用当前类中的私有成员。
private:
int b_data;
public:
friend class A; //将类A设置为B的友元类,使得A中能够访问到B中的b_data
friend void A::func(); //将A类中的func函数设置为友元
};
class A{
private:
int a_data;
B b;
public:
void func(){
cout<<a_data<<endl;
cout<<b.b_data<<endl; //因为A是B的友元类,所以能够访问B的私有成员
}
};
友元关系是单向的。在B中声明友元类A表示A中能够使用B中的私有变量,但是B中并不能使用A中的私有变量。这句话可以结合友元的定义来理解,友元的本质是一种访问权限的授予,这种授予是单向的。
友元关系不能传递,B是A的友元类,C是B的友元类,但是C和A之间不存在友元关系。
运算符重载
对运算符进行重载能够快速的处理类中数据的一些运算关系,以两点相加为例。首先定义Point类:
class Point{
private:
double x;
double y;
public:
Point(double a = 0, double b = 0):x(a),y(b){};
Point(const Point &other):x(other.x),y(other.y){};
void display(){
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
}
};
算术运算符
以两点相加为例:点A和点B相加得点C,点C的x值等于两点X只的和,y值同理。完成两点相加的操作熟悉需要重载加法运算符。在Point类中定义加法运算函数的友元,然后在友元函数中实现两点相加的操作。完成加法运算符的重载之后,就可以直接使用加法符号来计算两个Point类型的对象了。
public:
friend Point operator+(const Point &p1, const Point &p2);
//类的外部
Point operator + (const Point &p1, const Point &p2){
Point p;
p.x = p1.x+p2.x;
p.y = p1.y+p2.y;
return p;
}
关系运算符
关系运算符的重载和算术运算符类似,也需要用到友元函数。只不过函数的返回值为一个运算结果。这里以判断相等为例展示代码。
public:
friend bool operator == (const Point &p1, const Point &p2);
//类的外部
bool operator == (const Point &p1, const Point &p2){
if (p1.x == p2.x && p1.y == p2.y) {
return true;
} else {
return false;
}
}
赋值操作符
赋值操作符必须定义为成员函数,以便编译器能够知道是否需要自动生成一个默认的赋值操作符。当类中存在指针成员变量时必须显式定义赋值操作符。为了能够实现连续赋值,返回值必须是当前对象的引用。
Point &operator = (const Point &other){
x = other.x;
y = other.y;
return *this;
}
复合赋值运算符
对于算术运算符和关系运算符,都使用外部函数进行友元授权的形式来实现。而赋值运算符则需要使用成员函数的形式来实现。可以理解为,算术运算符和关系运算符都是由两个数进行计算,得出一个结果。这个结果不是两个运算数中的任何一个。而赋值运算符(例如:+=,-=),运算结果需要对左值进行修改的,也就是说需要对this指针进行操作,所以需要在类的内部进行实现。
Point &operator += (const Point &other){
this->x += other.x;
this->y += other.y;
return *this;
};
输入输出操作符
输出操作符
重载输出操作符能够改变当前在cout输出时的格式。因为cout输出对象不是当前类中的对象,所以不能使用成员函数的形式进行重载,必须要以友元函数的形式来重载。
Point p1;
cout<<p1<<endl;
//在实际执行过程中,执行为以下形式的函数
operator<<(cout,p1);
//重载输出操作符
friend ostream &operator << (ostream& out, const Point &p){
out<<"("<<p.x<<","<<p.y<<")";
return out;
}
在重载输出操作符之前,首先需要理解系统对于输出操作符的定义。系统定义了 operator<<(cout,p1);
函数来处理输出操作。函数的第一个参数为输出的目标,第二个参数为被输出的对象。ostream是C++标准库中的输出流类,而我们常用的cout则是这个类中的一个对象。因为有时需要做连续输出,就要将ostream作为函数的返回值进行返回。
输入操作符
在重载输入操作符时,大致方式和输出操作符相似,只不过输入对象ostream改成输出对象istream即可。因为输入操作符一般都会将数据输入到对象中去,所以对象的引用不能使用const修饰。
friend istream &operator >> (istream &in,Point &p) {
in >> p.x >> p.y;
return in;
}
下标运算符
下标运算符常用在从容器类中获取对象。如果需要定义一个容器类,就可以重载下标运算符。下标运算符必须定义为成员函数。而且要保证放在等于号的左右都能够正常使用,所以函数的返回值必须使用引用类型。否则作为左值时无法改变实际的数值。
class String{
private:
char *str;
size_t length;
public:
String(const char* s = NULL);
String(const String &other);
~String();
String &operator = (const String &other);
friend ostream &operator << (ostream &out, const String &s);
//下标操作符
char &operator [] (size_t index);
};
//下标操作符
char &String::operator [] (size_t index){
if (index > length) {
return str[length - 1];
} else {
return str[index];
}
}
因为下标操作有作为左值和右值两种情况。当作为左值时只能使用非const类型的对象,而右值则皆可。所以需要对const类型的下标运算额外做一次定义。函数本身需要定义为const类型,这样才能让const类型的对象来调用。然后返回值也需要定义为const,这样才不能作为左值来使用。
const char &operator [] (size_t index) const;
const char &String::operator [] (size_t index) const{
if (index > length) {
return str[length - 1];
} else {
return str[index];
}
}
总结
- 赋值=,下标[],调用(),成员访问符->,必须被定义为类的成员函数。
- 复合赋值运算符(+=,-=,*=,/=)优先定义为成员函数,因为比较容易书写。
- 自增自减,解引用等单目运算符优先定义为成员函数。
- 输入<<,输出>>操作符必须定义为类的友元函数。
- 其他双目预算符(算术/逻辑/位操作符)优先定义为类的友元函数。
网友评论