C++核心编程(三)

本文详细介绍了面向对象编程中的继承机制,包括继承的语法、对象模型、构造和析构函数的顺序。讨论了继承中的同名成员处理,包括静态和非静态成员。此外,还探讨了多继承、菱形继承及其解决方案——虚继承。接着,文章深入阐述了多态的概念,包括静态多态和动态多态,并通过实例展示了动态多态的实现。最后,通过纯虚函数和抽象类的概念,强调了它们在多态中的作用,并展示了如何利用多态提高代码的可扩展性和可维护性。
摘要由CSDN通过智能技术生成

4.6 继承

继承是面向对象三大特性之一

子类拥有父类的一些共性
利用继承的技术可以减少重复的代码

4.6.1 继承方式

语法: class 子类 : 继承方式 父类{}

子类 也被称为派生类
父类 也被称为基类

示例:

// 继承
class Animal
{
public:
	string name;	// 动物名称
	int age;		// 动物年龄
};

class Dog
{
public:
	string leg;		// 狗腿
};

**总结:**
继承的好处:`可以减少重复代码`
派生类中的成员,包含两大部分

- 一类是从基类继承过来的,一类是自己的成员
- 从基类继承继承过来表现其共性,而新增的成员体现了个性。
4.6.2 继承的方式

继承的方式一共有三种

  • 公共继承
  • 保护继承
  • 私有继承

在这里插入图片描述

4.6.3 继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

示例:

#include<iostream>
using namespace std;
#include <string>

// 继承中的对象模型
class Base
{
public:
	int m_A;

protected:
	int m_B;

private:
	int m_C;
};

class Son : public Base
{
public:
	int m_D;

};

// 利用VS提供的开发人员命令提示工具查看对象模型
// 在文件路径下操作:cl /d1 reportSingleClassLayout class_name

void test01()
{
	// 最终结果是 16
	cout << "sizeof of Son = " << sizeof(Son) << endl;

	// 结论:父类中所有的非静态的成员属性都会被子类继承下去
	// 父类中私有成员属性 是被编译器给隐藏了,因此访问不到,但是确实被继承了
}

int main()
{
	test01();
	return 0;
}

结论: 父类中所有的非静态的成员属性都会被子类继承下去,父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承了

4.6.4 继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题: 父类和子类中的构造函数和析构函数顺序呢?

示例:

#include<iostream>
using namespace std;
#include <string>

// 继承中的对象模型
class Base
{

public:
	Base()
	{
		cout << "Base 构造函数执行了" << endl;
	}
	~Base()
	{
		cout << "Base 析构函数执行了" << endl;
	}
public:
	int m_A;

};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son 构造函数执行了" << endl;
	}
	~Son()
	{
		cout << "Son 析构函数执行了" << endl;
	}


};


void test01()
{

	Son son;
}

int main()
{
	test01();
	return 0;
}

运行结果:
在这里插入图片描述

结论: 先构造父类,然后构造儿子,析构则是相反。

4.6.5 继承中同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中的同名数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

#include<iostream>
using namespace std;
#include <string>

// 继承中同名成员处理
class Base
{

public:
	Base()
	{
		m_A = 100;
	}
	void func()
	{
		cout << "父类中的函数调用" << endl;
	}
	void pr()
	{
		cout << "父类中的pr()函数调用" << endl;
	}
public:
	int m_A;

};

class Son : public Base
{
public:
	Son()
	{
		m_A = 200;
	}
	void func()
	{
		cout << "子类中的函数调用" << endl;
	}
	
public:
	int m_A;
};


// 同名的成员属性
void test01()
{
	Son son;
	cout << "子类中 m_A = " << son.m_A << endl;
	cout << "父类中 m_A = " << son.Base::m_A << endl;
}

// 同名的成员函数处理
void test02()
{
	Son son;
	son.func();
	son.pr();
	son.Base::func();

	// 如果子类中出现和父类同名的成员函数,子类的同名成员会影藏掉父类中所有同名的成员函数:重载的也被影藏,仍然要添加作用域
}

int main()
{
	test01();
	test02();
	return 0;
}

运行结果:
在这里插入图片描述
总结:

  1. 子类对象可以直接访问到子类中的同名成员
  2. 子类对象加作用域可以访问到父类中的同名成员,示例:son.Base::func();
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中的同名成员函数,加作用域可以访问到父类中同名函数
4.6.6 继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

#include<iostream>
using namespace std;
#include <string>

// 继承中同名静态成员处理
class Base
{

public:
	static void func()
	{
		cout << "父类中 func() 的调用" << endl;
	}
	
public:
	static int m_A;

};
// 静态成员初始化
int Base::m_A = 100;

class Son : public Base
{
public:
	static void func()
	{
		cout << "子类中 func() 的调用" << endl;
	}


public:
	static int m_A;
};

int Son::m_A = 200;


// 同名的静态成员属性
void test01()
{
	Son son;
	cout << "通过对象访问:" << endl;
	cout << "子类中 m_A = " << son.m_A << endl;
	cout << "父类中 m_A = " << son.Base::m_A<< endl;

	cout << "通过类名访问:" << endl;
	cout << "子类中 m_A = " << Son::m_A << endl;	// 此处的双冒号代表通过类名访问
	cout << "父类中 m_A = " << Son::Base::m_A << endl;	// 此处双冒号代表父类作用域下
}


// 同名的静态成员函数
void test02()
{
	Son son;
	cout << "通过对象访问:" << endl;
	son.func();
	son.Base::func();

	cout << "通过类名访问:" << endl;
	Son::func();
	Son::Base::func();
}



int main()
{
	test01();
	test02();
	return 0;
}

总结: 同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类),此外,如果函数重载,会隐藏父类中的同名函数,需要添加作用域才可以访问。

4.6.7 多继承语法

C++允许一个类继承多个类
语法:class 子类: 继承方式 父类1, 继承方式 父类二 ...{}
多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议使用多继承

示例:

#include<iostream>
using namespace std;
#include <string>

class Base1
{
public:
	int m_A = 100;
};


class Base2
{
public:
	int m_A = 200;
};

class Son : public Base1, public Base2
{

public:
	 int m_B =  300;
};

void test01()
{
	Son son;
	cout << "子类中 m_B = " << son.m_B << endl;
	cout << "父类Base1中 m_A = " << son.Base1::m_A<< endl;
	cout << "父类Base2中 m_A = " << son.Base2::m_A << endl;
}

int main()
{
	test01();

	return 0;
}

运行结果:
在这里插入图片描述
总结: 多继承中如果父类中出现了同名的情况,访问需要使用作用域区别

4.6.8 菱形继承

概念:
  两个派生类继承同一个基类
  又有某个类同时继承两个派生类
  这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例:
在这里插入图片描述
菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据就会产生二义性
  2. 草泥马继承自动物的数据继承了两份,但是这个数据只需要一份就可以。

问题解决:使用虚继承
示例:

#include<iostream>
using namespace std;
#include <string>


// 动物类
class Animal
{

public:

	
public:
	int age = 100;

};

// 利用虚继承 解决菱形继承的问题
// 继承之前 加上关键字 virtual 变为虚继承
// Animal 被称为 虚基类
// 羊类
class Sheep: virtual public Animal
{

};

// 驼类
class Camel : virtual public Animal
{


};

// 羊驼类 alpaca
class Alpaca: public Sheep, public Camel
{

};




void test01()
{
	Alpaca alpaca;
	alpaca.Sheep::age = 18;
	alpaca.Camel::age = 20;

	// 当出现菱形继承的时候,两个父类拥有相同的数据,需要加以作用域区分
	cout << "alpaca.Sheep::age = " << alpaca.Sheep::age << endl;
	cout << "alpaca.Camel::age = " << alpaca.Camel::age << endl;

	cout << "alpaca.age = " << alpaca.age << endl;

	cout << "sizeof Sheep = " << sizeof(Sheep) << endl;
	cout << "sizeof Camel = " << sizeof(Camel) << endl;
	cout << "sizeof Alpaca = " << sizeof(Alpaca) << endl;

}


int main()
{
	test01();

	return 0;
}

运行结果:
在这里插入图片描述

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一
多态分为两类

  • 静态多态:函数重载,和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

下面通过案例进行讲解多态:

#include<iostream>
using namespace std;
#include <string>


// 动物类
class Animal
{

public:
	// 关键字 virtual 使函数地址在运行时绑定
	virtual void speak()
	{
		cout << "父类对应为虚函数:动物在说话" << endl;
	}
	
	void bark()
	{
		cout << "父类对应为普通:动物在咬" << endl;
	}

};

class Cat:  public Animal
{
public:
	void speak()
	{
		cout << "父类对应为虚函数:小猫在说话" << endl;
	}
	void bark()
	{
		cout << "父类对应为普通:函数小猫在咬" << endl;
	}
};

// 执行说话的函数:父类对应函数为虚函数,
// 函数地址晚绑定, 在运行阶段确定函数地址
void doSpeak(Animal &animal)	// Animal& animal = cat
{
	animal.speak();
}

// 执行说话的函数:父类对应函数为普通函数
// 地址早帮定,在编译阶段确定函数地址
void doBark(Animal &animal)
{
	animal.bark();
}


void test01()
{
	Cat cat;
	doSpeak(cat);
	doBark(cat);
}

int main()
{
	test01();

	return 0;
}

执行结果:
在这里插入图片描述

动态多态满足条件

  1. 有继承关系
  2. 子类重写父类的虚函数

动态多态的使用:
父类的指针或者引用 执行子类对象。

Animal& animal = cat; 
animal.speak()

重写: 函数返回值类型、函数名、参数列表完全一致称为重写

虚函数底层:
在这里插入图片描述

4.7.2 多态案例–计算器

案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

传统方法示例:


// 普通的写法
class Calculator
{
public:
	int a;
	int b;
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return a + b;
		}
		else if (oper == "-")
		{
			return a - b;
		}
		else if (oper == "*")
		{
			return a * b;
		}
		else if (oper == "/")
		{
			return a / b;
		}
		// 如果想扩展新的功能,需要修改源码
		// 在真实的开发中,提供开闭原则
		// 开闭原则:对扩展进行开放,对修改进行关闭
	}
};

void test01()
{
	// 创建一个Calculate对象
	Calculator c;
	c.a = 10;
	c.b = 20;
	cout << c.a << "+" << c.b << " = " << c.getResult("+") << endl;
	cout << c.a << "-" << c.b << " = " << c.getResult("-") << endl;
	cout << c.a << "*" << c.b << " = " << c.getResult("*") << endl;
	cout << c.a << "/" << c.b << " = " << c.getResult("/") << endl;
}


int main()
{
	test01();
	return 0;
}

运行结果:
在这里插入图片描述
利用多态的示例:

// 利用多态实现计算器
class AbstractCaluclator
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int a;
	int b;
};

// 设计一个加法计算器类
class AddCalculator : public AbstractCaluclator
{
public:
	int getResult()
	{
		return a + b;
	}
};

// 设计一个减法计算器类
class SubCalculator : public AbstractCaluclator
{
public:
	int getResult()
	{
		return a - b;
	}
};

// 设计一个乘法计算器类
class MulCalculator : public AbstractCaluclator
{
public:
	int getResult()
	{
		return a * b;
	}
};

// 设计一个除法计算器类
class DivideCalculator : public AbstractCaluclator
{
public:
	int getResult()
	{
		return a / b;
	}
};

void test02()
{
	// 加法
	AbstractCaluclator* c = new AddCalculator();
	c->a = 10;
	c->b = 20;
	cout << c->a << "+" <<c->b << " = " << c->getResult() << endl;
	// 使用完毕,记得销毁
	delete c;	// 销毁的只是指正指向的堆区的数据

	// 减法
	c = new SubCalculator;
	c->a = 10;
	c->b = 20;
	cout << c->a << "-" << c->b << " = " << c->getResult() << endl;
	// 使用完毕,记得销毁
	delete c;	// 销毁的只是指正指向的堆区的数据

	// 乘法
	c = new MulCalculator;
	c->a = 10;
	c->b = 20;
	cout << c->a << "*" << c->b << " = " << c->getResult() << endl;
	// 使用完毕,记得销毁
	delete c;	// 销毁的只是指正指向的堆区的数据
}


int main()
{
	test02();
	return 0;
}

运行结果:
在这里插入图片描述
利用多态的好处总结:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期的可扩展性以及可维护性高
4.7.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表) = 0;
当类中有了纯虚函数,这个类也被称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

// 纯虚函数和抽象类
class Base
{
public:
	// 纯虚函数:本类只要有一个纯虚函数,此类就是抽象类,无法实例化
	virtual void func() = 0;
};

class Son :public Base
{
public:
	// 对父类中纯虚函数进行重写:否则无法实例化对象 virtual 关键字可以不写
	void func()
	{
		cout << "哈哈哈哈" << endl;
	}
};

void test01()
{
	Son s;
	s.func();
}

int main()
{
	test01();
	return 0;
}
4.7.4 多态案例二

案例:煮茶和煮咖啡

示例:

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
{
	// 煮水
	virtual void boil()
	{
		cout << "煮水" << endl;
	}

	// 冲泡
	virtual void brew()
	{
		cout << "冲泡咖啡" << endl;
	}

	// 倒入杯中
	virtual void pourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	// 加入辅料
	virtual void putSomething()
	{
		cout << "倒入糖" << endl;
	}
};


// 泡茶
class Tea : public AbstractDrinking
{
	// 煮水
	virtual void boil()
	{
		cout << "煮水" << endl;
	}

	// 冲泡
	virtual void brew()
	{
		cout << "冲泡咖啡" << endl;
	}

	// 倒入杯中
	virtual void pourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	// 加入辅料
	virtual void putSomething()
	{
		cout << "倒入枸杞" << endl;
	}
};

// 制作函数
void doWork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;		// 释放
}


void test01()
{
	cout << "泡咖啡****************" << endl;
	// 制作咖啡
	doWork(new Coffee);

	cout << endl << "泡茶****************" << endl;
	// 泡茶
	doWork(new Tea);
}

int main()
{
	test01();
	return 0;
}

运行结果
在这里插入图片描述

4.7.5 虚析构和纯虚构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯析构区别

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0

示例:

#include<iostream>
using namespace std;
#include <string>


// 虚析构和纯虚析构
class Animal
{
public:
	Animal()
	{
		cout << "Animal 构造函数调用" << endl;
	}

	// 利用虚析构解决子类对象释放不干净的问题
	/*virtual ~Animal()
	{
		cout << "Animal 析构函数调用" << endl;
	}*/

	// 纯虚析构 也需要代码实现,但并不是在子类中实现
	// 有纯虚析构,则是抽象类,不可实例化
	virtual ~Animal() = 0;

	// 纯虚函数
	virtual void speak() = 0;
};

// Animal 中纯虚析构实现
Animal::~Animal()
{
	cout << "Animal中纯虚析构函数调用" << endl;
}

class Cat : public Animal
{
public:
	string *name;

public:

	Cat(string name)
	{
		cout << "Cat的构造函数调用" << endl;
		this->name = new string(name);
	}

	~Cat()
	{
		if (name != NULL)
		{
			cout << "Cat 析构函数调用" << endl;
			delete name;
			name = NULL;
		}
	}

	virtual void speak()
	{
		cout << *this->name << "小猫在说话" << endl;
	}
};

void test01()
{
	Animal* animal = new Cat("Tom");
	animal->speak();
	// 父类指针在析构的时候,不会调用子类中析构函数,导致子类堆区数据有内存泄露情况
	delete animal;
}

int main()
{
	test01();
	return 0;
}

运行结果:
在这里插入图片描述
总结:

  1. 虚析构活纯虚析构就是用来解决通过父类指针释放子类对象的问题
  2. 如果子类中没有堆区数据,就可以不写虚析构或纯虚析构
  3. 拥有纯虚析构,此类为抽象类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值