一、运算符重载
运算符重载:对已有的运算符重载重新定义,赋予其另外一种功能,以适应不同的数据类型。
编译器定义的通用名称是operator需要重载的运算符。
重载时有两种方法:一是通过自己写成员函数实现,二是通过自己写全局函数实现
eg:
加号运算符重载
实现两个自定义数据类型相加的运算。
编译器定义了通用名称operator+。
方式1:通过自己写成员函数来实现两个对象相加属性后的新对象
class score {
public:
int m_score;
score operator+(score& two) {
score three;
three.m_score = this->m_score + two.m_score;
return three;
}
};
int main() {
score one;
one.m_score = 98;
score two;
two.m_score = 88;
//score three= one.operator+(two);
score three = one + two;
cout<<"three的score是"<<three.m_score<<endl;
system("pause");
return 0;
}
该方法的本质调用是score three= one.operator+(two);
但是可以简化成score three = one + two;
方式2:通过自己写全局函数来实现两个对象相加属性后的新对象
class score {
public:
int m_score;
};
score operator+(score& one, score& two) {
score three;
three.m_score = one.m_score + two.m_score;
return three;
}
int main() {
score one;
one.m_score = 98;
score two;
two.m_score = 88;
//score three= operator+(one,two);
score three = one + two;
cout << "three的score是" << three.m_score << endl;
system("pause");
return 0;
}
该方法的本质调用是score three= operator+(one,two);
也可以简化成score three = one + two;
2.左移运算符重载
实现输出自定义类型
输出时只使用一个一个自定义类型,无法使用自定义成员函数实现。
使用自定义全局函数实现输出自定义类型
class score {
public:
int m_score;
};
ostream &operator<<(ostream& cout, score& one) {
cout << "one的score是" << one.m_score;
return cout;
}
int main() {
score one;
one.m_score = 98;
//operator<<(cout, one);
cout << one << endl;
system("pause");
return 0;
}
cout为输出流对象(ostream),全局只能有一个,所以传形参和全局函数的返回值类型设置时都使用引用,并且设置函数返回值为输出流,是为了能够使用链式输出在输出自定义函数后追加输出其他内容。
该方法的本质调用是operator<<(cout, one);
简化为cout << one << endl;
可以通过设置全局函数重载作友元的方式访问私有属性。
3.递增运算符重载
通过重载递增运算符,实现自定义整型数据的递增
class myint {
public:
myint() {
num = 0;
}
myint& operator++(){//前置递增
//先递增
num++;
//再返回
return *this;
}
myint operator++(int) {//后置递增,int用来区分前后置
//先记录
myint temp = *this;
//再递增
num++;
//最后返回记录的值
return temp;
}
int num;
};
ostream &operator<<(ostream& cout, myint m_int) {
cout << m_int.num;
return cout;
}
int main() {
myint one;
myint two;
one.num = 1;
two.num = 1;
cout << ++one << endl;
cout << two++ << endl;
system("pause");
return 0;
}
递增运算符是需要区分前后置,所以使用int作占位符来区分。
前置返回一个引用是为了能够实现在进行++(++one)时,是一直对一个数据进行操作。而在后置时不返回引用是因为返回的是一个局部参数,重载函数结束后就会结束,会出现非法操作。
4.函数调用运算符重载
函数调用运算符是(),没有固定写法,操作灵活。重载后调用像函数调用,所以又叫仿函数。
列举个仿printf
class myprint {
public:
void operator()(string test) {
cout << test << endl;
}
};
int main() {
myprint Myprint;
Myprint("hello world");
system("pause");
return 0;
}
二、继承
继承是面对对象的三大特征之一。
有些类与类之间有存在特殊关系,

上图中下一级类之间除了有上一级的共性之外也有自己的特征,所以利用这种关系,使用继承来实现减少重复代码的效果。
继承的基础语法
继承的好处就是减少代码重复
单继承:class 子类 : 继承方式 父类
多继承:class 子类 : 继承方式 父类1 , 继承方式 父类2
简单案列:
//公共信息
class base {
public:
void school() {
cout << "xx学校" << endl;
}
};
//个人信息
class member01:public base {
public:
void name(){
cout << "xx" << endl;
}
};
class member02 :public base {
public:
void name() {
cout << "xxx" << endl;
}
};
int main() {
member01 one;
member02 two;
one.school();
one.name();
two.school();
two.name();
system("pause");
return 0;
}
2.继承方式
继承的方式一共有3种:

·公共继承:父类中除了私有属性其他属性按原来权限继承。
·保护继承:父类中除了私有属性其他属性全部变为保护权限继承。
·私有继承:父类中除了私有属性其他属性全部变为私有权限继承。
父类中私有权限的属性不管哪种继承方式都不能继承(但是友元可以访问)
3.继承中的对象模型
父类中所有非静态成员属性都会被子类继承,其中私有属性虽然被编译器隐藏,不可访问,但是仍然会被继承。
4.继承中的构造和析构
先构造父类,再构造子类,析构时顺序相反
5.继承中同名成员处理
访问子类同名成员:直接访问
访问父类同名成员:需要加上作用域
(非静态与静态相同)
多继承中容易出现同名情况,要加上作用域来区分
6.菱形继承
有两个子类1和子类2同时继承某个父类,又有一个子类3同时继承这两个子类,就形成菱形继承。
菱形继承存在一个问题子类12从父类那继承来的共同属性会同时继承给子类3,会使该类中出现两份相同的属性。
解决方法:
虚继承:继承前加上关键字virtual,即class 子类:virtual 继承方式 父类
就可以使共同的属性变成一份。
三、多态
多态的基本概念
多态分为两类:
·静态多态:函数重载和运算符重载都属于静态多态。复用函数名
·动态多态:派生类和 虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定(编译阶段确定函数地址)
动态多态的函数地址晚绑定(运行阶段确定函数地址)
动态多态的满足条件:
有继承关系
子类重写父类的虚函数(不是重载)
动态多态的使用:
父类的指针或引用执行子类对象
虚函数:
在函数名前加上关键字virtual,创建虚函数,会产生一个虚函数(表)指针(vfptr),该指针指向虚函数表内的地址,每一个类有自己的虚函数表,创建虚函数时,该函数的地址就存在虚函数表内。
动态多态解析:
父类创建虚函数后,在父类的虚函数表内存入该虚函数地址,在子类继承父类时,会复制虚函数指针和虚函数表内的地址,在子类重写父类虚函数时,产生的新地址会覆盖子类虚函数表中复制来的父类虚函数地址,所以在父类指针或引用调用子类虚函数时,会到子类虚函数表找函数地址,所以调用的会是子类的虚函数,这就实现了多态。
多态的优点:
组织结构清晰
可读性强
前期和后期拓展和维护性高
2.纯虚函数和抽象类
在多态中父类的虚函数一般实现是没有意义的,都是调用的子类重写的虚函数,所以可以把父类中虚函数改为纯虚函数。
纯虚函数:virtual 返回类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类就叫抽象类
抽象类特点:
·无法实例化对象
·子类必须重写父类中纯虚函数,否则子类也叫抽象类
3.虚析构和纯虚析构
如果子类中有属性开辟到堆区,那父类指针在释放时就不能调用子类的析构代码。
解决方式就是在父类中把析构函数改为虚析构或纯虚析构。
虚析构和纯虚析构的共性:
可以解决父类指针释放子类对象
都需要具体函数实现
虚析构和纯虚析构的区别:
如果是纯虚析构,那么这个类就是抽象类。不能实例化对象
虚析构语法:virtual ~类名(){};
虚析构语法:virtual ~类名()=0;