C++基础
函数的提高
函数默认参数
-
语法
返回值类型 函数名(参数 = 默认值)
-
特性
若传入参数,则优先使用传入的参数,否则使用默认参数
-
注意
如果形参列表中某个参数有默认值,则其后面的所有参数都必须要有默认值
函数占位参数
-
语法
返回值类型 函数名(数据类型)
-
特性
占位参数可以有默认值
函数重载
-
作用
函数名称相同,提高函数名的复用性
-
条件
- 函数名相同
- 函数在同一作用域下
- 函数参数的 类型不同 或 个数不同 或 顺序不同
-
注意
-
函数返回值不可以作为函数重载的条件
-
引用作为重载条件时,要注意加const和不加const的区别
-
当函数重载遇到默认参数时,要避免二义性的出现
-
类和对象
面向对象的特性
-
万事万物皆为对象,对象有其属性和行为
-
封装
-
继承
-
多态
-
封装
-
语法
- class 类名{访问权限:属性/行为};
-
意义
-
将属性和行为作为一个整体,表现生活中的事物
-
将属性和行为加以权限控制
-
-
示例1----设计一个圆类求出圆的周长和面积
#include <iostream> using namespace std; #define PI 3.14 //需求:设计一个圆类,求圆的周长和面积 //class代表设计一个类,后面紧跟着的就是类的名称 class Circle { //公共访问权限 public: //行为 //设置半径 void setRadius(int radius); //求周长 double getCircumference(void); //求面积 double getArea(void); //私有访问权限 private: //属性 //半径 int radius; }; int main() { //通过圆类实例化一个圆对象 Circle c1; //设置圆的半径 c1.setRadius(10); //求圆的周长和面积并打印 cout << "圆的周长:" << c1.getCircumference() << endl << "圆的面积:" << c1.getArea() << endl; return 0; } //设置圆的半径 void Circle::setRadius(int radius) { this->radius = radius; } //计算圆的周长 double Circle::getCircumference(void) { return 2 * PI * this->radius; } //计算圆的面积 double Circle::getArea(void) { return PI * this->radius * this->radius; }
-
成员访问权限
- public(公有权限)
- 类内可以访问,类外可以访问
- protected(保护权限)
- 类内可以访问 ,类外不可以访问
- 子类可以访问
- private(私有权限)
- 类内可以访问 ,类外不可以访问
- 子类不可以访问
- 一般都把成员属性设置为私有权限,然后在公有部分提供访问接口(成员函数)
- 优点
- 可以自己控制读写权限
- 对于写,可以检测数据的有效性
- 优点
- public(公有权限)
-
示例2----设计一个立方体类求出立方体的面积和体积
#include <iostream> using namespace std; //需求:设计一个立方体类,求出立方体的面积和体积 //创建一个立方体类 class Cube { public: //公有权限 void setLength(int length); //设置长 int getLength(void); //获取长 void setWidth(int length); //设置宽 int getWidth(void); //获取宽 void setHeight(int length); //设置高 int getHeight(void); //获取高 int getArea(void); //获取面积 int getVolume(void); //获取体积 protected: //保护权限 private: //私有权限 int length; //长 int width; //宽 int height; //高 }; int main() { //实例化一个立方体对象 Cube c1; //设置立方体的长宽高 c1.setLength(20); c1.setWidth(10); c1.setHeight(30); //打印立方体的长宽高 cout << "长:" << c1.getLength() << endl << "宽:" << c1.getWidth() << endl << "高:" << c1.getHeight() << endl; //计算立方体的面积和体积并打印 cout << "面积:" << c1.getArea() << endl << "体积:" << c1.getVolume() << endl; return 0; } //设置长 void Cube::setLength(int length) { this->length = length; } //获取长 int Cube::getLength(void) { return this->length; } //设置宽 void Cube::setWidth(int width) { this->width = width; } //获取宽 int Cube::getWidth(void) { return this->width; } //设置高 void Cube::setHeight(int height) { this->height = height; } //获取高 int Cube::getHeight(void) { return this->height; } //获取面积 int Cube::getArea(void) { return (this->length*this->width + this->length*this->height + this->width*this->height) * 2; } //获取体积 int Cube::getVolume(void) { return this->length * this->width * this->height; }
今天的C++就学到这里叭,又是学习的一天~
----2023/08/30
对象的初始化和清理
-
构造函数和析构函数
- 构造函数
- 作用
- 创建对象时为对象的成员属性赋值
- 语法
- 类名(){}
- 规则
- 函数名与类名相同
- 没有返回值,可以有参数
- 可以发生重载
- 作用
- 析构函数
- 作用
- 对象销毁前执行一些清理工作,如将对象申请的堆区内存释放
- 语法
- ~类名(){}
- 规则
- 函数名与类名相同,但是前面要加一个~
- 没有返回值,不可以有参数
- 不可以发生重载
- 作用
- 特性
- 由编译器自动调用,不需要手动调用,且每个对象只调用一次
- 构造函数和析构函数都是必须要有的实现,如果我们不提供,编译器会自动生成,其函数内部是空实现
- 构造函数
-
构造函数的分类和调用方法
-
分类
- 按参数分类
- 有参构造
- 无参构造(不需要传递参数的构造也称默认构造)
- 按类型分类
- 普通构造
- 拷贝构造
- 按参数分类
-
调用方法
-
括号法
//括号法 Person p1; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p2(10); //有参构造的调用 Person p3(p2); //拷贝构造的调用
-
显式法
//显式法 Person p4; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p5 = Person(10); //有参构造的调用 Person p6 = Person(p5); //拷贝构造的调用 Person(10); //单独写就是匿名对象,语句结束后系统会立即回收匿名对象
- 通过匿名对象显式的告诉编译器要调用哪种构造
- 注意
- 匿名对象的语句结束后系统会立即回收匿名对象
- 不能利用拷贝构造函数初始化一个匿名对象,否则编译器会认为是对象的声明,导致变量重定义报错
-
隐式转换法
//隐式转换法 Person p7; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p8 = 10; //相当于写了 Person p8 = Person(10); //有参构造 Person p9 = p8; //拷贝构造
-
注意
- 调用默认构造函数时不要加(),否则编译器会认为这是一个函数的声明
-
-
完整示例代码
#include <iostream> using namespace std; class Person { public: //无参构造(只要不需要传递参数的构造都称为默认构造) Person() { cout << "Person 的无参构造函数调用" << endl; } //有参构造 Person(int age) { cout << "Person 的有参构造函数调用" << endl; } //拷贝构造 Person(const Person& p) //构造函数不能修改传进来的参数,必须要用引用传递,如果使用值传递会造成无限递归导致程序崩溃 { this->age = p.age; cout << "Person 的拷贝构造函数调用" << endl; } //析构 ~Person() { cout << "Person 的析构函数调用" << endl; } protected: private: int age; }; int main() { //构造函数的调用方法 //括号法(常用) Person p1; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p2(10); //有参构造的调用 Person p3(p2); //拷贝构造的调用 //显式法 Person p4; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p5 = Person(10); //有参构造的调用 Person p6 = Person(p5); //拷贝构造的调用 Person(10); //单独写就是匿名对象,语句结束后系统会立即回收匿名对象 //隐式转换法 Person p7; //默认构造的调用,注意:不能加(),否则编译器会认为这是一个函数的声明 Person p8 = 10; //相当于写了 Person p8 = Person(10); //有参构造 Person p9 = p8; //拷贝构造 return 0; }
-
-
拷贝构造函数的调用时机
- 使用一个已经创建完毕的对象来初始化另一个对象
- 值传递的方式给函数参数传值
- 以值的方式返回局部对象
#include <iostream> using namespace std; class Person { public: Person() { cout << "Person 的默认构造函数调用" << endl; } Person(int age) { this->age = age; cout << "Person 的有参构造函数调用" << endl; } Person(const Person& p) { this->age = p.age; cout << "Person 的拷贝构造函数调用" << endl; } ~Person() { cout << "Person 的析构函数调用" << endl; } protected: private: int age; }; void test01(Person p) { } Person test02(void) { Person p; return p; } int main() { //拷贝构造函数的调用时机 //使用一个已经创建完毕的对象来初始化另一个对象 Person p1(20); Person p2(p1); //值传递的方式给函数参数传值 Person p3; test01(p3); //以值的方式返回局部对象 Person p4 = test02(); return 0; }
-
构造函数的调用规则
- 默认情况下,编译器至少给一个类添加四个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数(对属性进行值拷贝)
- 赋值运算符重载operator=(对属性进行值拷贝,详见运算符重载章节)
- 如果自定义了有参构造,则编译器不再提供默认无参构造,但是会提供默认拷贝构造
- 如果自定义了拷贝构造,则编译器不再提供任何构造
- 默认情况下,编译器至少给一个类添加四个函数
-
深拷贝与浅拷贝
-
浅拷贝
- 简单的赋值操作,拷贝成员属性中的内容
- 编译器提供的拷贝构造函数属于浅拷贝
-
深拷贝
- 如果一个类的成员属性存在堆区的操作,则在拷贝时重新开辟一块内存提供新对象使用,此时一定要自定义析构函数进行堆区内存的释放操作
- 其目的是避免堆区内存的重复释放
-
-
初始化列表
- 语法
- 构造函数(形参列表):属性1(初值1),属性2(初值2)…{}
- 语法
-
类对象作为类成员
- 注意
- 在构造时,先构造成员对象,再构造自身
- 在析构时,先析构自身,再析构成员对象(先构造的后析构)
- 注意
-
静态成员
-
静态成员变量
- 所有对象共享同一个变量
- 在编译阶段分配内存
- 在类内声明,在类外初始化
- 可以通过对象来访问,也可以通过类名访问(也要看访问权限)
- 受访问权限的控制
-
静态成员函数
- 所有对象共享同一个函数
- 只能访问静态成员变量
- 可以通过对象来访问,也可以通过类名访问(也要看访问权限)
- 受访问权限的控制
-
不迈出第一步,你永远不知道你能走到哪里,加油!
2023/08/31
C++对象模型和this指针
- 成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
- C++编译器会给每个空对象分配1个字节空间,是为了区分空对象占用的内存位置(每个空对象都应该有独一无二的地址)
- this指针
- 特性
- 指向调用成员函数的对象
- 隐含于非静态成员函数内的一种指针
- 不需要定义,可以直接在非静态成员函数中使用
- 用途
- 当形参和成员变量同名时,可用this指针区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
- 特性
- 空指针访问成员函数
- C++中允许空指针调用成员函数
- 在调用时如果有用到this指针,要进行判断增加代码的健壮性
- const修饰成员函数
- 常函数
- 成员函数后加const修饰,这个函数就变成了常函数(加const的本质是修饰this指针指向的值)
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
- 常对象
- 声明对象时加const,这个对象就变成了常对象
- 常对象只能调用常函数
- 成员属性声明时加关键字mutable后,在常对象中依然可以修改
- 常函数
友元
-
目的
- 让一个函数或者一个类访问另一个类中的私有成员
-
三种实现
-
全局函数做友元
#include <iostream> #include <string> using namespace std; class Building { //声明全局函数为友元后,该函数可以访问类中的私有成员 friend void goodGay(Building *building); public: Building() { keTing = "客厅"; woShi = "卧室"; } string keTing; private: string woShi; }; void goodGay(Building *building) { cout << "好基友全局函数正在访问 " << building->keTing << endl; //访问公有成员 cout << "好基友全局函数正在访问 " << building->woShi << endl; //访问私有成员 } void test01(void) { Building building; goodGay(&building); } int main() { test01(); return 0; }
-
类做友元
#include <iostream> #include <string> using namespace std; class Building; class GoodGay { public: GoodGay(); void visit(void); //参观函数 访问Building中的属性 Building *building; }; class Building { //声明类为友元后,该类可以访问本类中的私有成员 friend class GoodGay; public: Building(); string keTing; private: string woShi; }; GoodGay::GoodGay() { this->building = new Building; } Building::Building() { keTing = "客厅"; woShi = "卧室"; } void GoodGay::visit(void) { cout << "好基友类正在访问 " << building->keTing << endl; //访问公有成员 cout << "好基友类正在访问 " << building->woShi << endl; //访问私有成员 } void test01(void) { GoodGay gg; gg.visit(); } int main() { test01(); return 0; }
-
成员函数做友元
#include <iostream> #include <string> using namespace std; class Building; class GoodGay { public: GoodGay(); void visit(void); //让visit函数可以访问Building中的私有成员 void visit2(void); //让visit2函数不可以访问Building中的私有成员 Building *building; }; class Building { //声明类下的成员函数为友元后,该类的成员函数可以访问本类中的私有成员 friend void GoodGay::visit(void); public: Building(); string keTing; private: string woShi; }; GoodGay::GoodGay() { this->building = new Building; } Building::Building() { keTing = "客厅"; woShi = "卧室"; } void GoodGay::visit(void) { cout << "visit函数正在访问 " << building->keTing << endl; //访问公有成员 cout << "visit函数正在访问 " << building->woShi << endl; //访问私有成员,通过 } void GoodGay::visit2(void) { cout << "visit2函数正在访问 " << building->keTing << endl; //访问公有成员 //cout << "visit2函数正在访问 " << building->woShi << endl; //访问私有成员,报错 } void test01(void) { GoodGay gg; gg.visit(); gg.visit2(); } int main() { test01(); return 0; }
-
运算符重载
-
概念
- 对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
-
作用
- 实现两个自定义数据类型的运算
- 运算符重载也可以发生函数重载
-
加号运算符重载operator+
-
通过成员函数重载 或 通过全局函数重载
-
对于内置数据类型不可以重载(比如1+1=2,我们想重载为1+1=0,不允许这样操作)
-
不能滥用重载(比如重载+运算符,却在函数体里实现减法运算)
#include <iostream> #include <string> using namespace std; //通过成员函数重载 + class Person { public: //实现自定义类型和整形型相加 Person operator+(int num) { Person temp; temp.a = this->a + num; temp.b = this->b + num; return temp; } int a; int b; }; //通过全局函数重载 + //实现自定义类型和自定义类型相加 Person operator+(Person &p1, Person &p2) { Person temp; temp.a = p1.a + p2.a; temp.b = p1.b + p2.b; return temp; } void test01(void) { Person p1; p1.a = 10; p1.b = 10; Person p2; p2.a = 10; p2.b = 10; Person p3 = p1 + p2; Person p4 = p1 + 100; cout << "p3.a = " << p3.a << endl << "p3.b = " << p3.b << endl; cout << "p4.a = " << p4.a << endl << "p4.b = " << p4.b << endl; } int main() { test01(); return 0; }
-
-
左移运算符重载operator<<
-
只能通过全局函数重载
-
重载左移运算符配合友元可以实现输出自定义数据类型
#include <iostream> #include <string> using namespace std; //通过成员函数重载 class Person { public: //不会利用成员函数来重载<<运算符,因为无法实现cout等标准流在左侧 // void operator<<() // { // } int a; int b; }; //只能通过全局函数重载 << (返回cout可以实现链式编程) ostream &operator<<(ostream &cout, Person &p) { cout << "p.a = " << p.a << endl << "p.b = " << p.b; return cout; } void test01(void) { Person p1; p1.a = 10; p1.b = 10; cout << p1 << endl; } int main() { test01(); return 0; }
-
-
递增运算符重载operator++
-
前置递增返回引用,后置递增返回值
#include <iostream> #include <string> using namespace std; class MyInt { friend ostream &operator<<(ostream &cout, MyInt myint); public: MyInt():a(0){} //重载前置++运算符 MyInt &operator++(void) { ++this->a; return *this; } //重载后置++运算符 //这个int代表的是占位参数 MyInt operator++(int) { MyInt temp = *this; ++this->a; return temp; } private: int a; }; //重载<<运算符 ostream &operator<<(ostream &cout, MyInt myint) { cout << myint.a; return cout; } //前置++ void test01(void) { MyInt myint; cout << ++(++myint) << endl; cout << myint << endl; } //后置++ void test02(void) { MyInt myint; cout << myint++ << endl; cout << myint << endl; } int main() { test01(); test02(); return 0; }
-
-
赋值运算符重载operator=
-
编译器提供的默认赋值运算符重载是浅拷贝的操作,同样面临着深浅拷贝的问题
#include <iostream> #include <string> using namespace std; class Person { public: Person(int age) { this->age = new int(age); } ~Person() { if(this->age != NULL) delete this->age; } Person &operator=(Person &p) { //先判断是否有属性在堆区,如果有,先释放干净,再深拷贝 if(this->age != NULL) { delete this->age; this->age = NULL; } this->age = new int(*p.age); return *this; } int *age; }; void test01(void) { Person p1(18); Person p2(20); Person p3(0); cout <<"p1的年龄为 " << *p1.age << endl; cout <<"p2的年龄为 " << *p2.age << endl; p3 = p2 = p1; cout <<"p1的年龄为 " << *p1.age << endl; cout <<"p2的年龄为 " << *p2.age << endl; cout <<"p3的年龄为 " << *p3.age << endl; } int main() { test01(); return 0; }
-
-
关系运算符重载operator==和operator!=
-
重载==和!=
#include <iostream> #include <string> using namespace std; class Person { public: Person(string name, int age) { this->name = name; this->age = age; } //重载==号 bool operator==(Person &p) { if(this->name == p.name && this->age == p.age) return true; return false; } //重载!=号 bool operator!=(Person &p) { if(this->name == p.name && this->age == p.age) return false; return true; } ~Person() { } string name; int age; }; void test01(void) { Person p1("Tom", 18); Person p2("Tom", 18); if(p1 == p2) { cout << "p1和p2是相等的" << endl; } else { cout << "p1和p2不是相等的" << endl; } if(p1 != p2) { cout << "p1和p2不是相等的" << endl; } else { cout << "p1和p2是相等的" << endl; } } int main() { test01(); return 0; }
-
-
函数调用运算符重载operator()
-
由于重载后的使用方式非常像函数的调用,因此称为仿函数
-
仿函数没有固定写法,非常灵活
#include <iostream> #include <string> using namespace std; //打印输出类 class MyPrint { public: //重载函数调用运算符 void operator()(string test) { cout << test << endl; } }; //仿函数非常灵活,没有固定的写法 //加法类 class MyAdd { public: int operator()(int a, int b) { return a + b; } }; void test01(void) { MyPrint myPrint; myPrint("Hello world"); } void test02(void) { MyAdd myAdd; int result1 = myAdd(100, 200); //匿名函数对象 int result2 = MyAdd()(200, 300); cout << "result1 = " << result1 << endl; cout << "result2 = " << result2 << endl; } int main() { test01(); test02(); return 0; }
-
继承
-
继承的基本语法
- 语法
- class 子类 : 继承方式 父类
- 子类也称为派生类,父类也称为基类
- 总结
- 继承的好处就是可以减少重复的代码
- 语法
-
继承方式
- 公有继承
- 父类中private权限的成员子类不可以访问
- 父类中public权限的成员在子类中也是public权限
- 父类中protected权限的成员在子类中也是protected权限
- 保护继承
- 父类中private权限的成员子类不可以访问
- 父类中public权限和protected权限的成员在子类中都是protected权限
- 私有继承
- 父类中private权限的成员子类不可以访问
- 父类中public权限和protected权限的成员在子类中都是private权限
- 公有继承
-
继承中的对象模型
- 父类中所有的非静态成员属性都会被子类继承
- 父类中的私有成员在子类中是被编译器隐藏了,因此访问不到,但是确实是被继承下去了
-
继承中构造和析构顺序
- 先构造父类,再构造子类
- 先析构子类,再析构父类
-
继承中同名成员的处理方式
- 子类对象可以直接访问到子类中的同名成员
- 子类对象加作用域可以访问到父类中的同名成员
- 如果子类中出现了和父类中同名的成员函数,那么子类中会隐藏所有父类中的同名成员函数,如果想要访问隐藏的成员函数,需要加作用域
-
继承同名静态成员的处理方式
- 与非静态成员出现同名的处理方式一致
-
多继承语法
-
C++允许一个类继承多个类
-
多继承可能会引发父类中同名成员出现,需要加作用域区分
-
在实际开发中,不建议使用多继承
-
语法
- class 子类 : 继承方式 父类1, 继承方式 父类2…
#include <iostream> #include <string> using namespace std; //父类1 class Base1 { public: Base1() { this->a = 100; } int a; }; //父类2 class Base2 { public: Base2() { this->a = 200; } int a; }; //子类 继承父类1和父类2 class Son : public Base1, public Base2 { public: Son() { this->c = 300; this->d = 400; } int c; int d; }; void test01(void) { Son s; cout << "sizeof Son = " << sizeof(Son) << endl; //当父类中出现同名成员,需要加作用域区分 cout << "Base1 a = " << s.Base1::a << endl; cout << "Base2 a = " << s.Base2::a << endl; } int main() { test01(); return 0; }
-
-
菱形继承
-
概念
- 两个派生类继承同一个基类
- 又有某个类同时继承这两个派生类
- 这种继承被称为菱形继承或者钻石继承
-
菱形继承问题
#include <iostream> #include <string> using namespace std; //动物类 class Animal { public: int age; }; //利用虚继承 解决菱形继承的问题 //在继承之前加上关键字virtual 变为虚继承 //Animal类称为虚基类 //羊类 class Sheep : virtual public Animal { }; //驼类 class Tuo : virtual public Animal { }; //羊驼类 class SheepTuo : public Sheep, public Tuo { }; void test01(void) { SheepTuo st; st.Sheep::age = 18; st.Tuo::age = 28; //菱形继承时,两个父类拥有相同数据,需要加以作用域区分 cout << "st.Sheep::age = " << st.Sheep::age << endl; cout << "st.Tuo::age = " << st.Tuo::age << endl; //这个数据我们知道只要有一个就可以了,菱形继承导致数据有两个,导致资源浪费 //解决方法:利用虚继承 cout << "st.age = " << st.age << endl; //虚继承之后数据只有一个,可以直接访问 } int main() { test01(); return 0; }
-
多态
-
多态的基本概念
-
分类
- 静态多态
- 函数重载 和 运算符重载都属于静态多态,复用函数名
- 函数地址早绑定----编译阶段确定函数地址
- 动态多态
- 派生类和虚函数实现运行时多态
- 函数地址晚绑定----运行阶段确定函数地址
- 静态多态
-
动态多态的满足条件
-
有继承关系
-
子类重写父类的虚函数
-
-
动态多态的使用
-
父类的指针或者引用 指向 子类对象
-
-
多态的好处
-
组织结构清晰
-
可读性强
-
方便前期和后期的扩展和维护
#include <iostream> #include <string> using namespace std; //普通写法和多态技术实现计算器 //普通写法 //class Calculator //{ //public: // int getResult(string oper) // { // if(oper == "+") // return this->num1 + this->num2; // else if(oper == "-") // return this->num1 - this->num2; // else if(oper == "*") // return this->num1 * this->num2; // //如果想扩展新的功能,需要修改源码 // //在真实的开发中 提倡 开闭原则----对扩展进行开放,对修改进行关闭 // } // int num1; // int num2; //}; //利用多态实现计算器 //实现计算器的抽象类 class AbstractCalculator { public: virtual int getResult(void) { return 0; } int num1; int num2; }; //加法计算器类 class AddCalculator : public AbstractCalculator { public: int getResult(void) { return this->num1 + this->num2; } }; //减法计算器类 class SubCalculator : public AbstractCalculator { public: int getResult(void) { return this->num1 - this->num2; } }; //乘法计算器类 class MulCalculator : public AbstractCalculator { public: int getResult(void) { return this->num1 * this->num2; } }; //void test01(void) //{ // c.num1 = 10; // c.num2 = 20; // cout << c.num1 << "+" << c.num2 << "=" << c.getResult("+") << endl; // cout << c.num1 << "-" << c.num2 << "=" << c.getResult("-") << endl; // cout << c.num1 << "*" << c.num2 << "=" << c.getResult("*") << endl; //} void test02(void) { //多态的使用条件 //父类的指针或者引用指向子类对象 AbstractCalculator *abc = new AddCalculator; abc->num1 = 10; abc->num2 = 20; cout << abc->num1 << "+" << abc->num2 << "=" << abc->getResult() << endl; //用完后记得销毁 delete abc; abc = new SubCalculator; abc->num1 = 10; abc->num2 = 20; cout << abc->num1 << "-" << abc->num2 << "=" << abc->getResult() << endl; //用完后记得销毁 delete abc; abc = new MulCalculator; abc->num1 = 10; abc->num2 = 20; cout << abc->num1 << "*" << abc->num2 << "=" << abc->getResult() << endl; //用完后记得销毁 delete abc; } int main() { //test01(); test02(); return 0; }
-
-
-
纯虚函数和抽象类
-
语法
- virtual 返回值类型 函数名(参数列表) = 0;
- 当类中有了纯虚函数,这个类就称为抽象类
-
抽象类的特点
- 无法实例化对象
- 抽象类的子类必须重写抽象类中的纯虚函数,否则也属于抽象类
-
案例
#include <iostream> #include <string> using namespace std; //制作饮品 class AbstractDrink { public: //煮水 virtual void boil(void) = 0; //冲泡 virtual void brew(void) = 0; //倒入杯中 virtual void pourIncup(void) = 0; //加入辅料 virtual void putSomething(void) = 0; //制作饮品 void makeDrink(void) { this->boil(); this->brew(); this->pourIncup(); this->putSomething(); } }; //制作咖啡 class Coffee : public AbstractDrink { public: //煮水 virtual void boil(void) { cout << "煮农夫山泉" << endl; } //冲泡 virtual void brew(void) { cout << "冲泡咖啡" << endl; } //倒入杯中 virtual void pourIncup(void) { cout << "倒入咖啡" << endl; } //加入辅料 virtual void putSomething(void) { cout << "加入糖和牛奶" << endl; } }; //制作茶叶 class Tea : public AbstractDrink { public: //煮水 virtual void boil(void) { cout << "煮矿泉水" << endl; } //冲泡 virtual void brew(void) { cout << "冲泡茶叶" << endl; } //倒入杯中 virtual void pourIncup(void) { cout << "倒入茶叶" << endl; } //加入辅料 virtual void putSomething(void) { cout << "加入枸杞" << endl; } }; //制作函数 void doWork(AbstractDrink *abs) { abs->makeDrink(); delete abs; } void test01(void) { doWork(new Coffee); doWork(new Tea); } int main() { test01(); return 0; }
-
-
虚析构和纯虚析构
-
场景:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
- 解决方法
- 将父类中的析构函数改为虚析构或者纯虚析构
- 解决方法
-
虚析构和纯虚析构的共性
- 都可以解决父类指针无法调用子类析构的问题
- 都需要具体的函数实现
-
虚析构和纯虚析构的区别
- 如果是纯虚析构,那么该类属于抽象类,无法实例化对象
-
语法
- 虚析构
- virtual ~类名(){}
- 纯虚析构
- virtual ~类名() = 0;
- 类名::~类名(){}
- 虚析构
-
案例
#include <iostream> #include <string> using namespace std; //虚析构和纯虚析构 class Animal { public: Animal() { cout << "Animal 构造" << endl; } //纯虚函数 virtual void speak(void) = 0; //利用虚析构可以解决父类指针释放子类对象时不干净的问题 // virtual ~Animal() // { // cout << "Animal 析构" << endl; // } //纯虚析构 需要声明也需要实现 //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象 virtual ~Animal() = 0; }; Animal::~Animal() { cout << "Animal 纯虚析构" << endl; } //小猫类 class Cat : public Animal { public: Cat(string name) { this->name = new string(name); cout << "Cat 构造" << endl; } void speak(void) { cout << *this->name << "小猫在说话" << endl; } ~Cat() { if(this->name != NULL) { delete this->name; this->name = NULL; cout << "Cat 析构" << endl; } } string *name; }; void test01(void) { Animal *animal = new Cat("Tom"); animal->speak(); //父类的指针在析构的时候不会调用子类的析构函数,导致子类如果有堆区属性,会存在内存泄漏的情况 delete animal; } int main() { test01(); return 0; }
-
总结
- 虚析构和纯虚析构都是用来解决通过父类指针释放子类对象的问题
- 如果子类中没有堆区数据,可以不写虚析构或者纯虚析构
- 拥有纯虚析构的类也属于抽象类
-
案例 ---- 组装电脑
#include <iostream> #include <string> using namespace std; //抽象不同零件类 //抽象CPU类 class Cpu { public: //抽象的计算函数 virtual void calculate(void) = 0; }; //抽象显卡类 class VideoCard { public: //抽象的显示函数 virtual void display(void) = 0; }; //抽象内存条类 class Memory { public: //抽象的存储函数 virtual void storage(void) = 0; }; //电脑类 class Computer { public: Computer(Cpu *cpu, VideoCard *vc, Memory *mem) { this->cpu = cpu; this->vc = vc; this->mem = mem; } //提供工作的函数 void work(void) { //让零件工作起来 this->cpu->calculate(); this->vc->display(); this->mem->storage(); } //提供析构函数释放电脑零件 ~Computer() { if(this->cpu != NULL) { delete this->cpu; this->cpu = NULL; } if(this->vc != NULL) { delete this->vc; this->vc = NULL; } if(this->mem != NULL) { delete this->mem; this->mem = NULL; } } private: Cpu *cpu; //CPU零件指针 VideoCard *vc; //显卡零件指针 Memory *mem; //内存条零件指针 }; //具体的厂商 //Intel class IntelCpu : public Cpu { public: void calculate(void) { cout << "Intel CPU 开始计算" << endl; } }; class IntelVideoCard : public VideoCard { public: void display(void) { cout << "Intel 显卡 开始显示" << endl; } }; class IntelMemory : public Memory { public: void storage(void) { cout << "Intel 内存条 开始存储" << endl; } }; //Lenovo class LenovoCpu : public Cpu { public: void calculate(void) { cout << "Lenovo CPU 开始计算" << endl; } }; class LenovoVideoCard : public VideoCard { public: void display(void) { cout << "Lenovo 显卡 开始显示" << endl; } }; class LenovoMemory : public Memory { public: void storage(void) { cout << "Lenovo 内存条 开始存储" << endl; } }; void test01(void) { //第一台电脑零件 Cpu *intelCpu1 = new IntelCpu; VideoCard *intelCard1 = new IntelVideoCard; Memory *intelMem1 = new IntelMemory; //创建第一台电脑 Computer *computer1 = new Computer(intelCpu1, intelCard1, intelMem1); computer1->work(); delete computer1; //第二台电脑零件 Cpu *lenovoCpu2 = new LenovoCpu; VideoCard *lenovoCard2 = new LenovoVideoCard; Memory *lenovoMem2 = new LenovoMemory; //创建第二台电脑 Computer *computer2 = new Computer(lenovoCpu2, lenovoCard2, lenovoMem2); computer2->work(); delete computer2; //第三台电脑零件 Cpu *intelCpu3 = new IntelCpu; VideoCard *lenovoCard3 = new LenovoVideoCard; Memory *lenovoMem3 = new LenovoMemory; //创建第三台电脑 Computer *computer3 = new Computer(intelCpu3, lenovoCard3, lenovoMem3); computer3->work(); delete computer3; } int main() { test01(); return 0; }
-