一:继承
1.基本语法
class father
{
public:
int sex;
string name;
}
class son : public father
{
}
2.继承方式
这里私有成员不能访问但是依然继承下去
关于private:(实验五第一题第二问有涉及到)
如何访问基类的私有成员?
(1)通过调用基类的成员函数(此函数可以访问/return所需变量)
但是如果需要频繁访问的话,调用函数太不方便
(2)在类中增加保护段
即把所需变量的private改成protected
(3)将派生类声明为基类的friend
或者将需要访问基类的成员函数设置为基类的friend
通过访问声明调整访问域:
1.访问声明仅仅调整名字的访问,不为它说明任何的类型,如果是成员函数则不准带任何的参数2.例子如下:
class A { public:/protected: int private_val; int f(int a,int b) { return a+b; } }; class B : private A { public: A::private_val; A::f; }
注意:调整访问域只适用于private/protected继承使得原本基类的public或protected变量无法访问。
不能解决private变量无法访问
3.继承中的对象模型
问题:从父类继承过来的成员那些属于子类对象
4.子类和父类的同名成员如何处理同名变量
这个可以用构造顺序理解
构造顺序:
父类>子类
如果父类的构造函数有参,那么子类的构造函数必须使用在构造函数中默认初始化
如果在子类中还创建了父类的对象,那么构造函数还需要加上这个对象的构造函数调用
这是因为:B类无法直接访问A类从而给aa赋值,所以需要调用A类的构造函数从而给它赋值
析构顺序:
子类>父类
所以默认访问的是子类
如果需要访问父类,只需使用合适作用域
cout<<father::name<<endl;
5.访问静态成员
和4道理相同
6.多继承语法
class son:public f1,public f2,protected f3 ...
(实际开发不建议使用多继承,可能会出现同名变量)
7.菱形继承
可能会出现同名变量,这个时候必须再访问变量之前确定好定义域
要不然会报错
另一种方法就是 使用 虚基类
拿菱形继承来说
比如a中有个变量m
如果在c中想要访问m
cout<<c.m; //❌
因为无法明确是哪个中的m
如果将b1 b2 变为虚继承,那么b1 b2 中的m都是a中的m
访问 b1.m b2.m的地址会发现地址一样都是 a.m的地址
二:多态
1.静态多态
2.动态多态
class Animal()
{
void speak(){
cout<<"Animal speak"<<endl;
}
};
class Dog() : public Animal
{
void speak(){
cout<<"Dog speak"<<endl;
}
};
class Cat() : public Animal
{
void speak(){
cout<<"Cat speak"<<endl;
}
}
void DoSpeak(Animal &animal)
{
animal.speak();
}
int main()
{
Cat cat;
DoSpeak(cat);
}
结果是会发现:
运行结果为
“Animal speak”
这与我们的期待不同,这是因为在运行程序之前,它已经完成函数地址的早绑定
因为:Dospeak函数传的是Animal类调用的也是该类的函数
所以如果想要实现预期功能就应该实现函数地址晚绑定
即在animal类的speak函数之前加 virtual 使其成为虚函数
首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
类似反向继承
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实3.现这个函数。
3.具体作用(案例)
使用多态实现一个计算器的功能
#include <iostream>
using namespace std;
class AbstractCalculator
{
public:
int num1;
int num2;
virtual int getresult()// 虚函数
{
}
};
class addCalculator : public AbstractCalculator
{
public:
int getresult()
{
return num1+num2;
}
};
int main()
{
AbstractCalculator *abc = new(addCalculator);
//使用多态将abstractcalculator类的指针指向addcalculator类
abc->num1 = 10;
abc->num2 = 1;
cout<<abc->getresult()<<endl;
}
3.纯虚函数和抽象类
多态中父类的虚函数通常毫无意义,主要是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类就是抽象类
抽象类的特点:
1.无法实例化对象
2.子类必须重写抽象类的纯虚函数,不然默认也是抽象类
4.虚析构和纯虚析构
引入:
多态使用时,如果子类中有属性开辟到堆区,那父类指针在释放时无法调用到子类的析构代码
堆区内存无法释放可能导致内存泄漏
解决办法:
使用virtual
注意虚函数是可以被直接调用的
纯虚函数只能通过子类的函数重写
三:练习
1.编写一个程序实现图书和杂志销售管理输入一系列图书和杂志的销售记录后将销售良好(图书:500以上,杂志:2500以上)的图书和杂志名称显示出来,要求使用到抽象类
错误思想:设法在类中创建一个数组来储存数据,但是会遇到诸多问题
#include <iostream> #include <stack> #include <vector> using namespace std; //输入一系列图书和杂志销售记录,并输出其中的销售良好的书名和杂志名 class SellWell { public: SellWell(); virtual ~SellWell(); virtual void check(vector<int> v) = 0; }; class Book:public SellWell { private: vector<int> v; vector<int> ans; public: void check() { while(!v.empty()) { //3.使用迭代器访问元素. vector<int>::iterator it; for (it = v.begin(); it != v.end(); it++) { if(*it >= 500) ans.push_back(*it); } } } void print() { cout<<"the SellWell book:\n"; vector<int>:: iterator it; for(it = v.begin;it!=v.end();i++) { cout<<*it<<" \n"; } cout<<endl; } }; class Magazine:public SellWell { private: vector<int> v; vector<int> ans; public: void check() { while(!v.empty()) { //3.使用迭代器访问元素. vector<int>::iterator it; for (it = v.begin(); it != v.end(); it++) { if(*it >= 2500) ans.push_back(*it); } } } void print() { cout<<"the SellWell Magazine:\n"; vector<int>:: iterator it; for(it = v.begin;it!=v.end();i++) { cout<<*it<<" \n"; } cout<<endl; } }; void putin(class& a ,class& b) { cout<<"输入1为书,2为杂志\n"; cout<<"输入3停止输入"<<endl; int m,n; cin>>m,n; while(m!=3) { if(m==1) { a.v.push_back(c); } if(m==2) { b.v.push_back(c); } else cout<<"cuowu"<<endl; } } int main() { SellWell *book = new Book(); SellWell *magazine = new Magazine(); input(book,magazine); book->check(); book->print(); }
尤其是main函数中
正确方法应该创建一个基类类型的vector数组,存储整个类的对象而不是仅仅存数字
#include <iostream>
#include <vector>
#include <string>
// 抽象类 Product
class Product {
public:
virtual ~Product() {}
virtual double getPrice() const = 0;
virtual std::string getName() const = 0;
};
// Book 类,继承自 Product
class Book : public Product {
public:
Book(const std::string& name, double price) : name_(name), price_(price) {}
double getPrice() const override { return price_; }
std::string getName() const override { return name_; }
private:
std::string name_;
double price_;
};
// Magazine 类,继承自 Product
class Magazine : public Product {
public:
Magazine(const std::string& name, double price) : name_(name), price_(price) {}
double getPrice() const override { return price_; }
std::string getName() const override { return name_; }
private:
std::string name_;
double price_;
};
int main() {
std::vector<Product*> products;
std::string name;
double price;
char type;
// 输入销售记录
while (std::cin >> name >> type >> price) {
if (type == 'B') {
products.push_back(new Book(name, price));
} else if (type == 'M') {
products.push_back(new Magazine(name, price));
} else {
std::cout << "Invalid type!" << std::endl;
continue;
}
}
// 判断销售良好的图书和杂志并输出结果
for (const auto& product : products) {
if (product->getPrice() > 500 && product->getType() == 'B') {
std::cout << "Good selling book: " << product->getName() << std::endl;
} else if (product->getPrice() > 2500 && product->getType() == 'M') {
std::cout << "Good selling magazine: " << product->getName() << std::endl;
}
}
// 释放内存
for (const auto& product : products) {
delete product;
}
return 0;
}
注意:
1.使用 vector<Product*> products;
而不是 vector<Product> products;
是因为 "Product" 是一个抽象类,不能直接被实例化。通过使用指针,我们可以在向量中存储指向实际 "Product" 对象的指针,并实现动态内存分配。
2.vector的遍历
1. 使用迭代器进行遍历:适用于需要对容器中的元素进行修改的情况,例如删除、插入等操作。
vector<int>::iterator it;
for (it = v.begin(); it != v.end(); it++)
{
if(*it >= 2500) ans.push_back(*it);
}
2. 使用范围for循环进行遍历:适用于只需要遍历容器中的元素,不需要对元素进行修改的情况。for (const auto& product : products) {
delete product;
}
3. 使用下标进行遍历:适用于需要访问容器中特定位置的元素的情况。