C++学习笔记(类和对象终篇)

目录

前言

5.继承

5.1基本语法

5.2继承方式 

5.3继承中的对象模型

5.4构造和析构顺序

5.5同名成员处理

5.6多继承语法

5.7菱形继承  问题及解决

概念

经典案例

问题

解决方法

原理

6.多态

6.1基本概念和语法

6.2纯虚函数

6.3虚析构和纯虚析构 

做作业学到的

1.constexpr关键字

2.类成员静态变量

一点总结


前言

不出意外应该是我最后一篇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函数调用时输出是 动物在说话

优点:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期扩展以及维护性高

在实际开发中提倡 开闭原则:对扩展进行开放,对修改进行关闭

下面是一个具体的案例,可以感受多态的好处

#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;
}

一点总结

做笔记发博客确实是一个不错的习惯

做笔记便于我回头来反复察看,发博客可以规范我笔记的写法,不至于内容不明不白

若是按我自己的学习方法,没有大量的练习,类和对象这些内容难以像现在这样熟练

大家学习编程语言的方法是怎样的呢?

附前两篇博客地址:

  1. C++学习周报(2023.12.9 - 2023.12.16)
  2. C++学习周报(2023.12.17~2023.12.23)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值