5-类的相关使用
1、简介
操作符重载:为了让我们自己设计的类类型也和基本数据类型一样可以参与各种运算
- 操作符标记
- 单目操作符:-、++、–、*、->等
- 双目操作符:+、-、>、<、+=等
- 三目操作符:? :
- 操作符函数
- 在特定条件下,编译器有能力把一个由操作数和操作符组成的表达式,
- 解释为一个全局函数的调用
- 解释为一个成员函数的调用
- 通过定义操作符函数,可以实现针对自定义类型的运算法则,使之与内置类型一样参与各种操作符表达式
- 在特定条件下,编译器有能力把一个由操作数和操作符组成的表达式,
- 双目操作符表达式:L#R
- 成员形式的操作符函数调用:L.operator# ® 左操作数是调用对象,右操作数是参数对象
- 全局形式的操作符函数调用:operator# (L, R) 左操作数是第一参数,右操作数是第二参数
- 单目操作符表达式:#O/O#
- 成员形式的操作符函数调用:O.operator# ()
- 全局形式的操作符函数调用:operator# (O)
- 三目操作符表达式:没有重载
2、双目操作符
2.1 运算类双目操作符
- 运算类操作符:+、-、*、\等
- 左操作数应可以为非常左值、常左值或右值
- 右操作数应可以为非常左值、常左值或右值
- 表达式的结果必须为右值
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
void getInfo(){
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
Human sum(Human& that){
return Human(this->m_age + that.m_age, (this->m_name +"+"+ that.m_name).c_str());
}
Human sub(const Human& that) const {
return Human(this->m_age - that.m_age, (this->m_name + "-" + that.m_name).c_str());
}
// 成员函数形式的操作符函数
Human operator + (const Human& that)const{
return Human(this->m_age + that.m_age, (this->m_name + "+" + that.m_name).c_str());
}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
friend Human operator-(const Human& L, const Human& R);
};
// 全局函数型的操作符函数
Human operator-(const Human& L, const Human& R){
return Human(L.m_age - R.m_age, (L.m_name + "-" + R.m_name).c_str());
}
int main(){
Human a(22, "A"), b(18, "B");
const Human c(47, "C"), d(56, "D");
Human res = a.sum(b);
res.getInfo();
res = a + d;// a+d ==> a.operator+(d) 或者 operator+(a,d)
res.getInfo();
res = c.sub(d);
res.getInfo();
res = c - a;//c-a ==>c.operator-(a) 或者 operator-(c,a)
res.getInfo();
return 0;
}
2.2 赋值类双目操作符
- 赋值类操作符:=、+=、-=、*=、/=等
- 右操作数应可以为非常左值、常左值或右值
- 左操作数应可以为非常左值
- 表达式的结果必须为左操作数本身(不是副本)
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
void getInfo(){
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
// 成员函数形式的操作符函数
Human& operator += (const Human& that){
this->m_age = this->m_age + that.m_age;
this->m_name = this->m_name + "+" + that.m_name;
// 表达式的结果必须为左操作数本身
return *this;
}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
};
int main(){
Human a(22, "A"), b(18, "B");// 非常左值
Human c(47, "C"), d(56, "D");// 常左值
a += b;// a+=c ==>a.operator+=(b)
a.getInfo();
a = c;// a=c ==>a.operator=(c)---> 拷贝赋值函数 或者 operator=(a,c)
a.getInfo();
a = C5_Human(78, "E");
a.getInfo();
return 0;
}
2.3 比较类双目操作符
- 比较类操作符:>、<、==、<=、>=等
- 左操作数可以为非常左值、常左值或右值
- 右操作数可以为非常左值、常左值或右值
- 表达式结果必须为bool
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
void getInfo(){
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
// 成员函数形式的操作符函数
bool operator == (const C5_Human& that)const{
return this->m_age == that.m_age && this->m_name == that.m_name;
}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
};
int main(){
Human a(22, "A"), b(18, "B");// 非常左值
Human c(47, "C"), d(56, "D");// 常左值
cout << boolalpha << "a == b: " << (a == b) << endl;;
cout << boolalpha << "a == c: " << (a == c) << endl;
cout <<boolalpha<< "C5_Human(3, 'AA') == C5_Human(3, 'AA') " << (C5_Human(3, "AA") == C5_Human(3, "AA")) << endl;
return 0;
}
3、单目操作符
3.1 运算类单目运算符
- 运算类单目操作符:-、~、!等
- 唯一操作数为非常左值、常左值或右值
- 表达式的结果必须为右值
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
void getInfo(){
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
// 成员函数形式的操作符函数
Human operator-()const{
return Human(-this->m_age, (this->m_name).c_str());
}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
};
int main(){
Human a(22, "A"), b(18, "B");// 非常左值
Human c(47, "C"), d(56, "D");// 常左值
Human res = - c;
res.getInfo();
return 0;
}
3.2 自增自减
- 前自增减类单目操作符:前++、前–
- 操作数为非常左值
- 表达式的结果为操作数本身(而非副本)
- 后自增减类单目操作符:后++、后–
- 操作数为非常左值
- 表达式的结果为右值,且为自增减以前的值
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
void getInfo(){
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
// 成员函数形式的操作符函数
// 前自增自减
Human& operator++(){
this->m_age = this->m_age +=1;
return *this;
}
// 后自增自减 这里需要使用哑元
Human operator++(int){
Human old = *this;// 先克隆一份b原来的值
this->m_age = this->m_age += 1;
return old;// 返回的是原来的值
}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
};
int main(){
Human a(22, "A"), b(18, "B");// 非常左值
(++a).getInfo();
(b++).getInfo();// 后加加,编译器会在调用时添加一个参数0,用于区分 b.operator++(0)
b.getInfo();
return 0;
}
4、输入输出操作符
4.1 输出操作
- 输出操作符:<<
- 左操作数为非常左值形式的输出流(ostream)对象,右操作数为左值或右值
- 表达式的结果为左操作数本身(而非副本)
- 左操作数的类型为ostream,若以成员函数形式重载该操作符,就应将其定义为ostream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
friend ostream & operator<<(ostream & os, const Human & that);
};
// 输出操作符重载
ostream & operator<<(ostream & os, const Human & that){
os << "姓名:" << that.m_name << "年龄:" << that.m_age;
return os;
}
int main(){
Human a(22, "A");// 非常左值
const Human b(18, "B");// 常左值
cout << a << endl;
cout << b << endl;
return 0;
}
4.2 输入操作
- 输入操作符:>>
- 左操作数为非常左值形式的输入流(istream)对象,右操作数为非常左值
- 表达式的结果为左操作数本身(而非副本)
- 左操作数的类型为istream,若以成员函数形式重载该操作符,就应将其定义为istream类的成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数形式重载该操作符
class Human{
public:
Human(int age, const char* name = "无名") :m_age(age), m_name(name){}
private:
int m_age; // 基本类型成员变量
string m_name;// 类类型成员变量
friend istream& operator>> (istream &is, Human & r);
};
// 输出操作符重载
istream& operator>> (istream &is, Human & r){
is >> r.m_name >> r.m_age;
return is;
}
int main(){
Human a(22, "A");// 非常左值
cin>>a ;
cout << a << endl;
return 0;
}
5、下标操作符
- 下标操作符:[]
- 常用于在容器类型中以下标方式获取数据元素
- 非常容器的元素为非常左值,常容器的元素为常左值
class Stack{
public:
Stack() :len(0){}
void push(int data){
if (len == 20){
return;
}
arr[len++] = data;
}
int pop(){
if (len == 0){
return -1;
}
return arr[--len];
}
int size(){ return len; }
int& operator[](const size_t& index){ // 非常函数
return this->arr[index];
}
const int& operator[](const size_t& index)const{ // 常函数
return this->arr[index];
}
private:
int arr[20];
int len;
};
// 下标操作符
int main(){
Stack s;// 非常容器
for (int i = 0; i < 10; i++){
s.push(100 + i);
}
s[5] = 888;//==>s.operator[](5)
for (int i = 0; i < 10; i++){
cout << s[i] << " ";
}
cout << endl;
const Stack cs=s;// 常容器
//cs[5] = 666;
for (int i = 0; i < 10; i++){
cout << cs[i] << " ";
}
return 0;
}
6、解引用和间接成员访问
6.1 简介
- 解引用和间接成员访问操作符:*、->
- 如果一个类重载了“解引用”和“间接成员访问操作符”,那么该类的对象就可以被当做指针来使用.
- 应用的体现(智能指针)
- 智能指针的本质就是一个类对象,并且其维护一个指针型成员变量
6.2 智能指针
- 常规指针的缺点
- 当一个常规指针离开它的作用域时,只有该指针变量本身所占据的内存空间(通常是4个字节)会被释放,而它所指向的动态内存并未得到释放必须自己手动释放
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
class C05_A{
public:
C05_A() : m_f(open("./cfg", O_CREAT | O_RDWR, 0644)){
// 定义了m_f,初值为文件描述符--》文件表等内核结构(动态资源)
cout<<"C05()构造函数调用了"<<endl;
}
~C05_A(){
close(m_f);
cout<<"~C05()执行了"<<endl;
// 释放m_p本身所占的内存空间
}
void foo(){
write(m_f, "hello cfg",9);
cout<<"foo写了数据"<<endl;
}
private:
int m_f;
};
int main(){
C05_A* pa = new C05_A;
(*pa).foo();
delete pa;// 这里一定要自己手动调用,不然不会调用析构函数,会导致内存泄漏
return 0;
}
- 智能指针的优点
- 智能指针是一个类对象(封装了常规指针),当它离开作用域时,其析构函数负责释放该常规指针所指向的动态内存
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
class C05_A{
public:
C05_A() : m_f(open("./cfg", O_CREAT | O_RDWR, 0644)){
// 定义了m_f,初值为文件描述符--》文件表等内核结构(动态资源)
cout<<"C05()构造函数调用了"<<endl;
}
~C05_A(){
close(m_f);
cout<<"~C05()执行了"<<endl;
// 释放m_p本身所占的内存空间
}
void foo(){
write(m_f, "hello cfg",9);
cout<<"foo写了数据"<<endl;
}
private:
int m_f;
};
class Auto_ptr{
public:
Auto_ptr(C05_A* a):m_a(a){
}
~Auto_ptr(){
delete m_a;
m_a=NULL;
}
C05_A& operator*(){
return *m_a;
}
C05_A* operator->(){
return m_a;
}
private:
C05_A* m_a;//常规指针
};
int main(){
/*
C05_A* pa = new C05_A;
(*pa).foo();
*/
Auto_ptr pua = new C05_A;
(*pua).foo();// pua.operator*().foo
pua->foo();//pua.operator->().foo
return 0;
}
7、类型转换操作符
- 若源类型是基本类型,目标类型是类类型,则只能通过类型转换构造函数实现自定义类型转换
class 目标类型 {
目标类型 (const 源类型& src) { … }
};
- 若目标类型是基本类型,源类型是类类型则只能通过类型转换操作符函数 实现自定义类型转换
class 源类型 {
operator 目标类型 (void) const { … }
};
//类型转换操作符
class IntegerInt{
public:
IntegerInt(int i) :m_i(i){
cout << "类型转换构造函数" << endl;
}
operator int(void) const {
cout << "类型转换操作符函数" << endl;
return this->m_i;
}
private:
int m_i;
};
int main(){
int n = 100;
//int --->IntegerInt(基本类型-> 类类型)
IntegerInt a = n;// 定义了匿名的IntegetInt对象 ,利用匿名IntegerInt类对象.IntegerInt(n) -->触发类型转化构造函数
// IntegerInt --->int (类类型 -->基本类型)
int m = a;// 定义了匿名int对象,利用匿名.int(a)-->int类中没有这个函数,并且也不让修改
// int m = a.operator int() --> 触发类型转换操作符函数
return 0;
}
- 若源类型和目标类型都是类类型(而非基本类型),则既可以通过类型转换构造函数也可以通过类型转换操作符函数实现自定义类型转换,但不要两者同时使用
- 若源类型和目标类型都是基本类型,则无法实现自定义类型转换,基本类型间的类型转换规则完全由编译器内置
class Dog; // 短式声明
class Cat{
public:
Cat(const char* name) :m_name(name){
}
void talk(){
cout << m_name << ":喵喵喵" << endl;
}
operator Dog(void) const; // 类型转换操作符
private:
string m_name;
friend class DogB;// 友元声明
};
class Dog{
public:
Dog(const char* name) :m_name(name){}
/* Dog(const Cat& c) :m_name(c.m_name){// 类型转换构造函数
cout << "Dog类的类型转换构造函数被调用" << endl;
} */
void talk(){
cout << m_name << ":汪汪汪" << endl;
}
private:
string m_name;
};
Cat :: operator Dog(void) const{ return Dog(this->m_name.c_str()); }
int main(){
Cat smallwhite("小白");
smallwhite.talk();
DogB bigDog = smallwhite;// 使用的是类型转换操作符进行的转换
smallback.talk();
bigDog.talk();
}
8、操作符重载的限制
- 不是所有的操作符都能重载,以下操作符不能重载
- 作用域限定操作符( :: )
- 直接成员访问操作符( . )
- 条件操作符( ? : )
- 字节长度操作符( sizeof )
- 类型信息操作符( typeid )