目录
前言
不出意外应该是我最后一篇blog
本文中多态部分讲述并不清晰,若想学到真东西还需移步他处
5.继承
c++类和对象的三大特性之一
继承的好处:减少重复代码
5.1基本语法
class 子类 : 继承方式 父类
父类 也称为 基类
子类 也称为 派生类
class Basepage //被继承的类
{
public:
}
class Java : public Basepage //继承类 Basepage
{
public:
private:
}
5.2继承方式
三种继承方式:
- 公共继承
- 保护继承
- 私有继承
下图可以很好地展示三种继承方式的效果
无论哪种继承方式,父类中私有权限的成员都是无法被访问(无法继承)的
同时,继承方式限定了继承后成员的访问权限
这一点很好理解,相当于权限有优先级,然后覆盖、收束:私有>保护>公共
也可以理解为是给成员限定了一个下限
继承是可以嵌套的,所以在使用时需要注意成员访问权限的变化
5.3继承中的对象模型
前文提到,私有权限的成员在继承后无法被访问,其在继承中的使用也就止步于此
需要注意的是,实际上所有非静态成员属性都会被子类继承下去,只是被编译器隐藏了
可以通过下面这段程序验证
#include <iostream>
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 test1()
{
cout << "sizeof Son = " << sizeof(Son) << endl;
}
int main()
{
test1();
return 0;
}
一个int类型变量占4个字节,所以输出结果是 sizeof Son = 16
5.4构造和析构顺序
一句话:父构 -> 子构 -> 子析 -> 父析
这很好理解,先有父再有子;如果父类先析构,那子类怎么析构呢?
入栈出栈道理是一样的
5.5同名成员处理
直接惯性思维理解即可,访问谁的就是谁的,通过子类调用父类需要用 ::
- 访问子类同名成员 直接访问
- 访问父类同名成员 需要加作用域
#include <iostream>
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;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//当子类与父亲拥有同名的成员函数,子类会隐藏父亲中所有版本的同名成员函数
//如果想访问父亲中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}
int main()
{
test();
return 0;
}
易懂,看案例就行
当然对于静态成员语法也是一样的,只是多了一条可以通过类名访问
像这样: Son::Base::m_A 通过子类访问父类成员
5.6多继承语法
语法:class 子类 : 继承方式 父类1,继承方式 父类2
同样的继承语法,要留意同名成员的处理
在实际开发中不建议使用多继承
5.7菱形继承 问题及解决
概念
两个派生类继承同一个基类
又有某个类同时继承这两个派生类
这种继承被称为菱形继承,或者钻石继承
经典案例
问题
问题是什么呢?
假设 动物类 有一个成员变量m_age,那么羊继承一个m_age,驼也继承一个,等到羊驼继承时就会继承两个m_age,然而我们很清楚这两份数据是一样的,我们只需要一份即可。
也就是说造成了内存浪费,羊驼现在有两个age,通过父类区分
解决方法
利用 虚继承 解决
即在羊和驼继承时加上 virtual 关键字
(假设有类animal,yang,tuo,yangtuo)
羊类 class yang :: virtual public animal
驼类 class tuo :: virtual public animal
注意两个都要加virtual
原理
使用虚继承后,两个派生类继承的是虚基类指针(vbptr),这个指针指向虚基类表(vbtable)
这样就很好理解了,继承的成员实际上是继承了指针,多个指针指向同一片内存,所有数据也就只有一份,这时候就连 yangtuo.m_age 这样的表达式也不会再出现因二义性而报错
6.多态
6.1基本概念和语法
C++面对对象三大特性之一——多态
多态分为两类
- 静态多态:函数重载 和 运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
下面通过一个案例看看多态的语法是怎样的
#include <iostream>
using namespace std;
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog : public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal& animal)
{
animal.speak();
}
//
//多态满足条件:
//1.有继承关系
//2.子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
void test()
{
Cat Tom;
DoSpeak(Tom);
Dog Buk;
DoSpeak(Buk);
}
int main()
{
test();
return 0;
}
如果animal中speak函数定义不加virtual关键字,那么DoSpeak函数调用时输出是 动物在说话
优点:
- 组织结构清晰
- 可读性强
- 对于前期和后期扩展以及维护性高
在实际开发中提倡 开闭原则:对扩展进行开放,对修改进行关闭
下面是一个具体的案例,可以感受多态的好处
#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;
}
//如果要提供新的运算,需要修改源码
}
public:
int m_Num1;
int m_Num2;
};
void test01()
{
//普通实现测试
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
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;
}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//创建加法计算器
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //用完了记得销毁
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
6.2纯虚函数
在多态中,基类中的函数实现通常没有意义,这时可以将其改为纯虚函数
语法:virtual 返回值类型 函数名 (参数列表) = 0
有纯虚函数的类被称为 抽象类
特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象函数
需要注意,继承的派生类必须重写基类中的纯虚函数,否则也被视为抽象类
下面是一个制作饮品的案例
//抽象制作饮品
class AbstractDrinking {
public:
//烧水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入牛奶!" << endl;
}
};
//制作茶水
class Tea : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入枸杞!" << endl;
}
};
//业务函数
void DoWork(AbstractDrinking* drink) {
drink->MakeDrink();
delete drink;
}
void test01() {
DoWork(new Coffee);
cout << "--------------" << endl;
DoWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
6.3虚析构和纯虚析构
虚函数的使用也可以应用在析构函数上
析构函数中包含delete时就可以动态调用子类中的析构函数,从而实现精准制导
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
(纯)虚析构不要求函数名相同
案例实现
#include<iostream>
using namespace std;
//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate() = 0;
};
//抽象显卡类
class VideoCard
{
public:
//抽象的显示函数
virtual void display() = 0;
};
//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void work()
{
//让零件工作起来,调用接口
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
//释放CPU零件
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
//释放显卡零件
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
//释放内存条零件
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU * m_cpu; //CPU的零件指针
VideoCard * m_vc; //显卡零件指针
Memory * m_mem; //内存条零件指针
};
//具体厂商
//Intel厂商
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的CPU开始计算了!" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的显卡开始显示了!" << endl;
}
};
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的内存条开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的显卡开始显示了!" << endl;
}
};
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的内存条开始存储了!" << endl;
}
};
void test01()
{
//第一台电脑零件
CPU * intelCpu = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMem = new IntelMemory;
cout << "第一台电脑开始工作:" << endl;
//创建第一台电脑
Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "-----------------------" << endl;
cout << "第二台电脑开始工作:" << endl;
//第二台电脑组装
Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);;
computer2->work();
delete computer2;
cout << "-----------------------" << endl;
cout << "第三台电脑开始工作:" << endl;
//第三台电脑组装
Computer * computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);;
computer3->work();
delete computer3;
}
*************************************
本文案例及思路来自 黑马程序员
*************************************
做作业学到的
1.constexpr关键字
在允许C++11的编译器中,部分情况下可以使用constexpr代替const和#define
constexpr可以在编译阶段求出常量表达式
例如,这样写会报错
加上const依然报错
但是换成constexpr就可以了
噢,cool
2.类成员静态变量
在看教学视频的时候并没有注意到静态变量该怎么初始化的问题
然后想起来这次在静态同名成员处理时有实际演示
于是回头看视频,发现老师是在定义成员后紧接着在类外就直接初始化了
啊,这么简单我怎么没想到
class Stu //学生类
{
public:
string m_ID;
virtual void out_info()
{
cout << endl << "姓名:" << this->m_Name << endl;
cout << "学号:" << this->m_ID << endl;
}
Stu()
{
cout << endl << "姓名:";
cin >> m_Name;
cout << "学号:";
cin >> m_ID;
cout << "年龄:";
cin >> m_Age;
Number++;
}
/*~Stu()
{
cout << endl << "姓名:" << m_Name << endl;
cout << "学号:" << m_ID << endl;
}*/
protected:
string m_Name;
private:
int m_Age;
static int Number;
};
int Stu::Number = 0; //这里初始化
int main()
{
return 0;
}
一点总结
做笔记发博客确实是一个不错的习惯
做笔记便于我回头来反复察看,发博客可以规范我笔记的写法,不至于内容不明不白
若是按我自己的学习方法,没有大量的练习,类和对象这些内容难以像现在这样熟练
大家学习编程语言的方法是怎样的呢?
附前两篇博客地址: