C++入门基础教程:类和对象(下)

Posted Zhi Zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门基础教程:类和对象(下)相关的知识,希望对你有一定的参考价值。

前言

博主通过对C++基础知识的总结,有望写出深入浅出的C++基础教程专栏,并分享给大家阅读,今后的一段时间我将持续更新C++入门系列博文,想学习C++的朋友可以关注我,希望大家有所收获。

6 继承

6.1 继承的基本概念

一个类中包含了若干成员属性和成员函数,不同的类中的成员属性和成员函数各不相同,但有些类与类之间存在特殊的关系,它们所包含的内容有相同之处。

如图1所示为类与类之间的关系。

图1 类与类之间的关系

继承就是利用已存在的类来建立一个新类。已存在的类称为父类或基类,新建立的类称为子类或派生类。子类通过继承的方式可以获得父类中的成员属性和成员函数,同时可以对自己的成员做必要的调整。

总结:

1)一个新的子类从已有的父类获得已有的特性,称为类的继承;从已有的父类产生一个新的子类的过程,称为类的派生。

2)一个基类可以派生出多个派生类,每一个派生类又可以作为基类再派生出新的派生类,因此,基类和派生类是相对而言的。

3)派生类是基类的具体化,基类是派生类的抽象。

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

例如:class son:public Base

6.2 继承的方式

继承有三种方式:公共继承、保护继承、私有继承。

子类以不同的方式继承父类中的成员属性,则子类中成员属性的访问权限也有差异。

公共继承

父类中的公共权限成员到子类中依然是公共权限;父类中的保护权限成员到子类中依然是保护权限;父类中的私有权限成员,子类访问不到。

保护继承

父类中的公共权限成员到子类中变为保护权限;父类中的保护权限成员到子类中变为保护权限;父类中的私有权限成员,子类访问不到。

私有继承

父类中的公共权限成员到子类中变为私有权限;父类中的保护权限成员到子类中变为私有权限;父类中的私有权限成员,子类访问不到。

图2 继承方式

6.3 继承中的对象模型

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

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class son :public Base
{
public:
	int m_D;
};

void test()
{
	// 父类中所有非静态成员属性都会被子类继承下去
	// 父类中私有成员属性访问不到的原因是被编译器隐藏了,但它确实会被子类继承下去
	cout << sizeof(son) << endl;  // 输出的son的大小为16个字节
}

利用开发人员命令提示工具查看对象模型:

跳转盘符 F:   跳转文件路径  cd 具体路径下   查看命名  cl /d1 reportSingleClassLayout类名 文件名

如图3所示为演示结果。

图3 对象模型

6.4 继承中的构造函数和析构函数的调用顺序

继承中先调用父类构造函数,再调用子类构造函数,析构函数的调用顺序与构造函数相反。

class Base
{
public:
	Base()
	{
		cout << "Base构造函数的调用" << endl;
	}
	~Base()
	{
		cout << "Base析构函数的调用" << endl;
	}
};

class Son :public Base
{
public:
	Son()
	{
		cout << "Son构造函数的调用" << endl;
	}
	~Son()
	{
		cout << "Son析构函数的调用" << endl;
	}
};

void test()
{
	Son s;
}

6.5 同名成员处理方式

1)子类对象可以直接访问到子类的同名成员;

2)子类对象需要加作用域才可以访问到父类的同名成员;

3)当子类与父类拥有同名的成员函数时,子类会隐藏父类中的同名成员函数,加作用域才可以访问到父类中的同名函数。

4)同名静态成员处理方式与同名非静态成员处理方式一样,只是在访问方式上有所不同,不仅可以利用对象访问成员,还可以利用类名来访问成员。

7 多态

7.1 多态的基本概念

多态分为两类:

静态多态:函数重载和运算符重载。

动态多态:派生类和虚函数实现运行时多态。

区别:
静态多态的函数地址早绑定——编译阶段确定函数地址;动态多态的函数地址晚绑定——运行阶段确定函数地址。

总结:

多态满足条件:1)有继承关系;2)子类重写父类中的虚函数。

多态使用条件:父类指针或引用指向子类对象。

例7.1
class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};

class Dog :public Animal
{
public:
	virtual void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

void Dospeak(Animal & animal)   // 父类引用指向子类对象
{
	animal.speak();
}

void test()
{
	Cat cat;
	Dospeak(cat);
	Dog dog;
	Dospeak(dog);
}

加上virtual关键字后,speak就变成了虚函数,此时编译器就无法在编译阶段确定函数地址,只能在运行阶段确定函数地址,实现函数的调用。

7.2 多态的原理剖析

以例7.1中的程序对多态的原理进行分析,Animal类的内部结构如图4所示。

图4 Animal类的内部结构

当Cat类没有重写父类中的虚函数时,会继承Animal类中的成员函数。其内部结构如图5所示。

图5 Cat类的内部结构

当子类重写父类中的虚函数, 子类中的虚函数表内部会把继承的虚函数地址替换成子类的虚函数地址,其内部结构如图6所示,当父类的指针或引用指向子类对象时,就可以发生多态。

图6 Cat类的内部结构

7.3 纯虚函数和抽象类

纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;

当类中有了纯虚函数,则称该类为抽象类。

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

class Base
{
public:
	virtual void func() = 0;
};
class Son :public Base
{
public:
	virtual void func()
	{
		cout << "func的调用!" << endl;
	}
};

void test()
{
	// 抽象类无法实例化对象
	Base b;   // 错误
	new Base; // 错误

	Base *b = new Son;
	b->func();
}

7.4 虚析构和纯虚析构

问题:父类指针在析构时,不会调用子类的析构函数,如果子类有堆区属性,将导致内存泄露。利用虚析构和纯虚析构均可以解决这个问题。

虚析构和纯虚析构的共性:1)可以解决父类指针释放子类对象;2)都需要有具体的函数实现。

虚析构和纯虚析构的区别:如果是纯虚析构,该类属于抽象类,无法实例化对象。

    虚析构语法:
                virtual ~类名(){}
    纯虚析构语法:    
         声明       virtual ~类名()=0;
         实现       类名::~类名(){}

总结:

1)虚析构和纯虚析构就是解决父类指针释放子类对象的问题;

2)如果子类中没有堆区数据,可以不写虚析构或纯虚析构;

3)拥有纯虚析构函数的类属于抽象类,纯虚析构需要声明也需要实现。

结束语

大家的点赞和关注是博主最大的动力,博主所有博文中的代码文件都可分享给您(除了少量付费资源),如果您想要获取博文中的完整代码文件,可通过C币或积分下载,没有C币或积分的朋友可在关注、点赞和评论博文后,私信发送您的邮箱,我会在第一时间发送给您。博主后面会有更多的分享,敬请关注哦!

以上是关于C++入门基础教程:类和对象(下)的主要内容,如果未能解决你的问题,请参考以下文章

C++入门基础教程:类和对象(上)

JAVA入门零基础小白教程day06-类和对象

JAVA入门零基础小白教程day06-类和对象

C++从入门到入土第二篇:类和对象基础

C++入门基础知识:类和引用

黑马程序员 C++教程从0到1入门编程笔记5C++核心编程(类和对象——继承多态)