C++类和对象2:https://developer.aliyun.com/article/1548157
五、运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型;
1、加号运算符重载
#include <iostream> #include <string> using namespace std; class Person { public: int m_A; int m_B; //成员函数加号重载 Person operator+(const Person &person) { Person tem; tem.m_A = this->m_A + person.m_A; tem.m_B = this->m_B + person.m_B; return tem; }; }; //成员函数加号重载 Person operator+(const Person &p1,const Person &p2) { Person tem; tem.m_A = p1.m_A + p2.m_A; tem.m_B = p1.m_B + p2.m_B; return tem; }; void test01() { Person p1; p1.m_A = 10; p1.m_B = 20; Person p2; p2.m_A = 10; p2.m_B = 20; Person p3 = p1 + p2; cout << p3.m_A << endl; cout << p3.m_B << endl; } int main() { test01(); return 0; }
20 40
2、左移运算符重载
重载左移运算符配合友元可以实现输出自定义数据类型
#include <iostream> #include <string> using namespace std; //左移运算符重载 class Person { friend ostream &operator<<(ostream &cout, Person &p); public: Person(int a, int b) : m_A(a), m_B(b) {} private: int m_A; int m_B; }; //只能利用全局函数重载左移运算符 成员函数无法实现cout在左侧 ostream &operator<<(ostream &cout, Person &p) { cout << "m_A=" << p.m_A << " m_B=" << p.m_B; return cout; } void test01() { Person p(10, 10); cout << p << " test" << endl; } int main() { test01(); return 0; }
m_A=10 m_B=10 test
3、递增运算符重载
前置递增返回的是引用,后置递增返回的值。
#include <iostream> #include <string> using namespace std; //自增运算符重载 class MyInteger { friend ostream &operator<<(ostream &cout, const MyInteger &myInteger); public: MyInteger() { m_Num = 0; } //重载前置++运算符 MyInteger &operator++() { // 先进行++运算 m_Num++; //再将自身做返回 return *this; } //重载后置++运算符 MyInteger operator++(const int) { // 先记录当时的结果 MyInteger tem = *this; // 后递增 m_Num++; //最后将记录结果做返回 return tem; } private: int m_Num; }; ostream &operator<<(ostream &cout, const MyInteger &myInteger) { cout << myInteger.m_Num << " "; return cout; } void test01() { int a = 10; cout << ++a << endl; cout << a << endl; int b = 10; cout << b++ << endl; cout << b << endl; } void test02() { MyInteger myInt; cout << ++(++myInt) << endl; cout << myInt << endl; } void test03() { MyInteger myInt; cout << myInt++ << endl; cout << myInt << endl; } int main() { // test01(); test02(); test03(); return 0; }
2 2 0 1
4、赋值运算符重载
#include <iostream> #include <string> using namespace std; //赋值运算符重载 class Person { public: Person(int age) { m_Age = new int(age); } ~Person() { if (m_Age != NULL) { delete m_Age; m_Age = NULL; } } // 重载赋值运算符 Person &operator=(Person &p) { // 先判断是否有属性在堆区,如果有先释放,然后深拷贝 if (m_Age != NULL) { delete m_Age; m_Age = NULL; } m_Age = new int(*p.m_Age); return *this; } int *m_Age; }; void test01() { Person p1(18); Person p2(20); Person p3(30); p3=p2 = p1; cout << *p1.m_Age << endl; cout << *p2.m_Age << endl; cout << *p3.m_Age << endl; } int main() { test01(); return 0; }
18 18 18
5、关系运算符重载
作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作
#include <iostream> #include <string> using namespace std; //作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作 class Person { public: Person(string name, int age) { name = m_Name; age = m_Age; } bool operator==(const Person &p) { if (this->m_Name == p.m_Name&&this->m_Age==p.m_Age) { return true; } else { return false; } } bool operator!=(const Person &p) { return !operator==(p); } string m_Name; int m_Age; }; void test01() { Person p1("zhang", 15); Person p2("zhang", 16); cout << (p1 == p2) << endl; } int main() { test01(); return 0; }
0
6、函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
#include <iostream> #include <string> using namespace std; //函数调用运算符()也可以重载 //由于重载后使用的方式非常像函数的调用,因此称为仿函数 //仿函数没有固定写法,非常灵活 class MyPrint { public: void operator()(string text) { cout << text << endl; } }; void test01() { // 重载()操作符,也称为仿函数 MyPrint myPrint; myPrint("hello world"); // 匿名函数 MyPrint()("hello world"); } int main() { test01(); return 0; }
hello world hello world
六、继承
1、继承的基本语法
继承好处:减少重复代码
语法:class 子类:继承方式 父类
子类也称为派生类
父类也称为基类
#include <iostream> #include <string> using namespace std; /* * 继承好处:减少重复代码 * 语法:class 子类:继承方式 父类 * 子类也称为派生类 * 父类也称为基类 */ //普通页面实现 class BasePage { public: void header() { cout << "首页、公开课、登录、注册……(公共头部)" << endl; } void footer() { cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl; } void left() { cout << "Java、Python、C++……(公共分类列表)" << endl; } }; //java页面 class Java : public BasePage { public: void content() { cout << "Java学科视频" << endl; } }; //python页面 class Python : public BasePage { public: void content() { cout << "Python学科视频" << endl; } }; void test01() { cout << "Java下载视频" << endl; Java ja; ja.header(); ja.footer(); ja.left(); ja.content(); cout<<"------------------"<<endl; cout << "Python下载视频" << endl; Python py; py.header(); py.footer(); py.left(); py.content(); } int main() { test01(); return 0; }
Java下载视频 首页、公开课、登录、注册……(公共头部) 帮助中心、交流合作、站内地图……(公共底部) Java、Python、C++……(公共分类列表) Java学科视频 ------------------ Python下载视频 首页、公开课、登录、注册……(公共头部) 帮助中心、交流合作、站内地图……(公共底部) Java、Python、C++……(公共分类列表) Python学科视频
2、继承方式
公共继承:父类的私有属性不可访问,其他属性权限和父类保持一致;
保护继承:父类的私有属性不可访问,父类public权限会降级为protected;
私有继承:父类的私有属性不可访问,父类public、protected会降级为private;
#include <iostream> #include <string> using namespace std; //公共继承基类 class Base1 { public: int m_A; protected: int m_B; private: int m_C; }; //公共继承 class Son1:public Base1{ public: void func(){ cout<<m_A<<endl; cout<<m_B<<endl; } }; //保护继承 class Son2:protected Base1{ void func(){ cout<<m_A<<endl; cout<<m_B<<endl; } }; //私有继承 class Son3:private Base1{ void func(){ cout<<m_A<<endl; cout<<m_B<<endl; } }; void test01(){ Son1 s1; s1.m_A=100; // s1.m_B=100; } int main() { return 0; }
3、继承中的对象模型
#include <iostream> #include <string> using namespace std; //作用: 重载关系运算符,可以让两个自定义类型对象进行对比操作 class Base { public: int m_A; protected: int m_B; private: int m_C;//私有成员只是被隐藏,但是还是会被继承下去 }; //公共继承 class Son : public Base { public: int m_D; }; void test01() { //16 //父类中所有非静态成员属性都会被子类继承下去 //父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去了 cout << "size of Son = " << sizeof(Son) << endl; } int main() { test01(); return 0; }
size of Son = 16
使用*Visual Studio 2022 Developer Command Prompt v17.5.5工具,查看对象模型;
#进入代码盘符 D:\ProgramFiles\Microsoft Visual Studio\2022\Community>f: #进入代码目录 F:\>cd F:\myCode\c++\0418_01 #查看代码类的结构 cl /d1 reportSingleClassLayout类名 文件名 F:\myCode\c++\0418_01>cl /d1 reportSingleClassLayoutSon main.cpp
4、继承中构造与析构顺序
先构造父类,再构造子类,析构的顺序与构造的顺序相反
#include <iostream> #include <string> using namespace std; //继承中的构造与析构顺序 class Base { public: Base() { cout << "Base构造执行" << endl; } ~Base() { cout << "Base析构执行" << endl; } }; class Son : public Base { public: Son() { cout << "Son构造执行" << endl; } ~Son() { cout << "Son析构执行" << endl; } }; void test01() { Son s; } //继承中的构造和析构顺序如下: //先构造父类,再构造子类,析构的顺序与构造的顺序相反 int main() { test01(); system("pause"); return 0; }
Base构造执行 Son构造执行 Son析构执行 Base析构执行
5、继承同名成员处理方式
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
#include <iostream> #include <string> using namespace std; //访问子类同名成员 直接访问即可 //访问父类同名成员 需要加作用域 class Base { public: Base() { m_A = 100; } void func() { cout << "Base func()" << endl; } void func(int a) { cout << "Base func(int a)" << endl; } int m_A; }; class Son : public Base { public: Son() { m_A = 200; } void func() { cout << "Son func()" << endl; } int m_A; }; //同名成员属性 void test01() { Son son; cout << son.m_A << endl; // 如果通过子类对象访问父类中的同名成员,需要加作用域 cout << son.Base::m_A << endl; }; //同名成员函数 void test02() { Son son; son.func(); son.Base::func(); // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中所有同名成员函数,如果想访问,需要加作用域 son.Base::func(10); }; int main() { test01(); test02(); return 0; }
200 100 Son func() Base func()
6、继承同名静态成员处理方式
与同名成员处理方式一致。可通过类名访问。
7、多继承语法
#include <iostream> #include <string> using namespace std; //多继承 class Base1 { }; class Base2 { }; class Son : public Base1, protected Base2 { }; int main() { return 0; }
8、菱形继承
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
#include <iostream> #include <string> using namespace std; //动物类 class Animail { public: int m_Age; }; //利用虚继承 解决菱形继承的问题 //继承之前加上 virtual变成虚继承 Animail类称为虚基类 //羊类 class Sheep : virtual public Animail { }; //驼类 class Tuo : virtual public Animail { }; //羊驼类 class SheepTuo : public Sheep, public Tuo { }; void test01() { SheepTuo st; st.Sheep::m_Age=18; st.Tuo::m_Age=28; cout<<st.m_Age<<endl; cout << sizeof(st) << endl; } int main() { test01(); return 0; }
28 24
七、多态
静态多态:函数重载和 运算符重载属于静态多态,复用函数名;
动态多态: 派生类和虚函数实现运行时多态;
静态多态的函数地址早绑定 - 编译阶段确定函数地址;
动态多态的函数地址晚绑定 - 运行阶段确定函数地址;
#include <iostream> #include <string> using namespace std; //多态 //动物类 class Animal { public: //虚函数 virtual void speak() { cout << "Animal speak" << endl; } }; class Cat : public Animal { public: void speak() { cout << "cat apeak" << endl; } }; //执行说话的函数 //地址早绑定,在编译阶段确定函数地址 //如果想让猫说话,那么这个函数地址不能提前绑定,需要在运行阶段绑定,地址晚绑定 //动态多态满足条件:1、有继承关系;2、子类重写父类的虚函数。动态多态的使用:父类的指针或者引用,指向子类对象。 void doSpeak(Animal &animal) { animal.speak(); } void test01() { Cat cat; doSpeak(cat); } int main() { test01(); return 0; }
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
#include <iostream> #include <string> using namespace std; //计算器普通写法 class Calculator { public: int getResult(string oper) { if (oper == "+") { return m_NUm1 + m_Num2; } else if (oper == "-") { return m_NUm1 - m_Num2; } else if (oper == "/") { return m_NUm1 / m_Num2; } else if (oper == "*") { return m_Num2 * m_NUm1; } } int m_NUm1; int m_Num2; }; //实现计算器抽象类 class AbstractCalculator { public: virtual int getResult() { return 0; } int m_Num1; int m_Num2; }; //加法计算器 class AddCalculator : public AbstractCalculator { public: int getResult() { return m_Num1 + m_Num2; } }; //减法计算器 class SubCalculator : public AbstractCalculator { public: int getResult() { return m_Num1 - m_Num2; } }; void test01() { Calculator cal; cal.m_NUm1 = 10; cal.m_Num2 = 20; cout << cal.getResult("+") << endl; cout << cal.getResult("-") << endl; } void test02() { AbstractCalculator *abc = new AddCalculator(); abc->m_Num1 = 10; abc->m_Num2 = 20; cout << abc->getResult() << endl; delete abc; } int main() { // test01(); test02(); return 0; }
类中有纯虚函数,类称为抽象类;
抽象类无法实例化对象,子类必须重写抽象类的纯虚函数,否则也称为抽象类;
#include <iostream> #include <string> using namespace std; //类中有纯虚函数,类称为抽象类 //抽象类无法实例化对象,子类必须重写抽象类的纯虚函数,否则也称为抽象类 class Base { public: virtual void func() = 0; }; class Son : public Base { public: void func() { cout << "son func" << endl; } }; void test01() { Base *base = new Son(); base->func(); delete base; } int main() { test01(); return 0; }
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:都可以解决父类指针释放子类对象,都需要有具体的函数实现;区别:纯虚析构的类输入抽象类,无法实例化对象。
#include <iostream> #include <string> using namespace std; //多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码 //解决方式:将父类中的析构函数改为虚析构或者纯虚析构 //虚析构和纯虚析构共性:都可以解决父类指针释放子类对象,都需要有具体的函数实现;区别:纯虚析构的类输入抽象类,无法实例化对象。 //虚析构和纯虚析构 class Animal { public: Animal() { cout << "Animal 构造函数" << endl; } //利用虚析构可以解决,父类指针释放子类对象时不干净的问题。 // virtual ~Animal() { // cout << "Animal 析构函数" << endl; // } //纯虚析构,需要申明和实现 virtual ~Animal() = 0; // 纯虚函数 virtual void speak() = 0; }; Animal::~Animal() { cout << "Animal 纯虚析构函数" << endl; } class Cat : public Animal { public: Cat(string name) { cout << "Cat 构造函数" << endl; m_Name = new string(name); } virtual void speak() { cout << *m_Name << "小猫在说话" << endl; } ~Cat() { if (m_Name != NULL) { cout << "Cat 析构函数" << endl; delete m_Name; m_Name = NULL; } } string *m_Name; }; void test01() { Animal *animal = new Cat("tom "); animal->speak(); delete animal; } int main() { test01(); return 0; }
Animal 构造函数 Cat 构造函数 tom 小猫在说话 Cat 析构函数 Animal 纯虚析构函数