C++ Primer类、类继承、虚函数(十二、三、四)

本文详细介绍了C++中的继承概念,包括私有继承、保护继承和多重继承的使用方式与注意事项。同时,探讨了构造函数与析构函数的调用顺序,以及显示构造与隐式构造的区别。文章还重点讲解了多态性,特别是虚函数的工作原理和虚函数表,以及抽象基类和静态成员的功能和用法。
摘要由CSDN通过智能技术生成

目录

概述:

组合和聚合:

私有继承、保护继承, 多重继承

区分显示构造和隐士构造:

多态(virtual):

静态联编和动态联编:

虚函数的工作原理(虚函数表):

抽象基类:

静态类成员及函数:


c++编译器: 从上往下, 从左往右

概述:

  • 创建派生类对象时, 程序调用基类构造函数, 再调用派生类构造函数;
  • 释放对象的顺序与创建对象的顺序相反, 即首执行派生类的析构函数, 然后自动调用基类的析构函数; 派生类构造函数总是调用一次; 可以使用初始化器列表语法指明要使用的基类构造函数, 否则将使用默认的基类构造函数; 除非是虚基类(不能被实例化,  或者重写所有的纯虚函数);
  • 基类可以 指向|引用 派生类对象, 反过来不行;
  • 继承可以在基类的基础上添加属性, 但不能删除基类的属性;
  • 不能继承基类的: 构造函数、析构函数、赋值运算符、友元函数

组合和聚合:

组合理解: 

电脑和CPU, 生命周期一致;

初始化:  初始化列表中使用成员名, 而不是类名;  构造函数调用顺序根据类中定义顺序有关, 谁先定义, 就先调用谁的构造函数

聚合理解:

电脑和音箱, 可有可无; 

私有继承、保护继承, 多重继承:

参考链接:http://t.csdn.cn/6XSLp

私有继承(private):

1.  访问基类的友元函数, 可以通过显示地转换为基类来调用正确的函数;

2.  未进行显示类型转换的派生类 引用|指针, 无法赋给基类的 引用|指针;

保护继承(protected):

假设要让基类的方法在派生类外面可用:

方法一:  定义一个使用该基类方法的派生类方法;

方法二:  在派生类public部分中使用一个using声明来指出派生类可用使用特定的基类成员

注意, 使用using声明只使用成员名---没有圆括号、函数特征和返回类型; 声明只使用于继承, 而不适用于组合;

方法三: 使用作用域解析运算符来调用;

多重继承:  //太多了, 去前面那个链接看

继承使用注意事项:

1.   重新定义继承的方法不是重载, 如果派生类中重新定义函数, 将不是使用相同的函数特征覆盖基类的声明, 而是隐藏同名的基类声明, 而是隐藏基类的同名方法, 不管参数特征如何

两条规则

(一)、 重新定义继承的方法, 应确保与原来的原型完全相同, 但如果返回类型是 指针|引用, 则可以修改为指向派生类的引用或指针(类型协变), 因为允许返回类型随类类型的变化而变化;

(二)、 如果基类声明被重载了, 则应在派生类中重新定义所有的基类版本, 避免被覆盖;

2.   当派生类存在与基类同名的成员变量时候,派生类的成员会隐藏基类成员(不可见), 使得基类这个成员是一个未初始化的值(尽量和基类的变量名区分开, 避免问题);

3.  如果派生类重写了基类的方法, 如 果想调用基类的方法, 在类方法中通过作用域解析运算符, 在外面通过 派生类.基类类名::方法   

区分显示构造和隐士构造:

隐式构造: string a = "abc"; 

显示构造:string a = string("abc");   或者 string a("abc");

CODE:

#include <iostream>
#include <string>

using namespace std;

class student {
public:
	explicit student(int _age)//默认隐式构造implicit
	{
		age = _age;
		cout << "age=" << age << endl;
	}

	student(int _age, const string _name)
	{
		age = _age;
		name = _name;
		cout << "age=" << age << "; name=" << name << endl;
	}

	~student()
	{

	}

	int getAge()
	{
		return age;
	}

	string getName() {
		return name;
	}


private:
	int age;
	string name;

};

int main(void) {
	student xiaoM(18);					//显示构造
	//student xiaoW = 18;				//隐式构造  
	//student xiaoHua(19, "小花");		//显示构造
	//student xiaoMei = { 18, "小美" };	//隐式构造  初始化参数列表,C++11 前编译不能通过,C++11新增特性

	system("pause");
	return 0;
}

多态(virtual):

程序将根据 引用|指针 指向的对象的类型来选择方法, 如果对象的类型是基类, 则调用基类的方法, 如果对象类型是派生类, 则调用派生类的方法;

虚析构函数: 基类声明一个虚析构函数, 这样为了确保释放派生对象时, 按正确的顺序析构函数

如果用基类的 指针|引用 指向派生派生类对象, 如果基类析构函数不是虚的, 则将只能调用对应于指针类型(基类)的析构函数

在派生类方法中, 标准技术是作用作用域解析运算符来调用基类的方法了。

如果某个成员函数被声明为虚函数,那么它的子类【派生类】,以及子类的子类中,所继承的这个成员函数,也自动是虚函数

如果在子类中重写这个虚函数,可以不用再写virtual, 但是仍建议写virtual, 更可读!

CODE1:

#include<iostream>
using namespace std;

class A
{
public:
	A() { cout << "A::构造函数" << endl; };
	A(int& _x, double& _y) : x(_x), y(_y) { cout << "A::构造函数" << endl; };
	virtual ~A() { cout << "A::析构函数" << endl; };
	void getX() const { cout << "A::getX" << endl;};
	static void printfs(){ cout << "A::printfs" << endl; }
public:
	int x;
	double y;
	static int z;
};

int A::z = 520;

class B : public A
{
public:
	B() { cout << "B::构造函数" << endl; };
	B(int& _x, int& _z, double& _y) :A(x, y), z(_z)
	{
		x = _x;
		y = _z; 
		cout << "B::构造函数" << endl;
	};
	virtual ~B() { cout << "B::析构函数" << endl; };

	 void test() 
	{
		// 在派生类方法中, 标准技术是使用作用域解析运算符来调用基类的方法
		A::getX();
		cout << "A::z = " << A::z << endl;
		cout << "A::x = " << A::x << endl;
	};

	 static void test1() { cout << "B.test" << endl; };
public:
	/*int x;
	double y;*/
	int z;
};

int main()
{
	int x = 1, z = 3;
	double y = 2.1;
	B b(x, z,y);
	b.test();
	//B::x;										// 错误
	//B::test();								// 错误
	//A::x;										// 错误
	//A::test()									// 错误
	B::test1();
	A::printfs();
	cout << A::z << endl;
	cout << "x = " << b.x << endl;
	cout << "y = " << b.y << endl;
	cout << "z = " << b.z << endl;
	system("pause");
	return 0;
}

CODE2:

#include<iostream>
using namespace std;

class A
{
public:
	A() { cout << " A构造" << endl; };
	 ~A(){ cout << " A析造" << endl; }
};

class B : public A
{
public:
	B() { cout << " B构造" << endl; };
	~B() { cout << " B析构" << endl; }
};

int main()
{
	//A a();			// !!!注意: 这样写的话,他把它认成定义函数了
	A* a = new B;
	delete a;
	system("pause");
	return 0;
}

静态联编和动态联编:

程序默认静态联编, 目的: 减少额外开销

静态联编: 编译过程, 编译器对非虚函数使用静态联编

动态联编: 程序运行

向上强制转换: 派生类 引用|指针 转换为基类 引用|指针 可传递

向下强制转换: 基类 引用|指针 转换为派生类引用|指针 可传递

虚函数的工作原理(虚函数表):

对象内,首先存储的是“虚函数表指针”,又称“虚表指针”。然后再存储非静态数据成员;

对象的非虚函数,保存在类的代码中!对象的内存,只存储虚函数表和数据成员,  类的静态数据成员,保存在数据区中,和对象是分开存储的;

添加虚函数后,对象的内存空间不变!仅虚函数表中添加条目,  多个对象,共享同一个虚函数表!

补充:

子类的虚函数表的过程:

  • 第一步: 直接复制父类的虚函数表;
  • 第二步: 如果子类重写了某个虚函数, 那么就在这个虚函数表中进行相应的替换;
  • 第三步: 如果子类增加了新的虚函数, 就把这个虚函数添加到虚函数表中(在尾部添加);

成本:

  • 每个对象都将增大, 增大量为存储地址的空间;
  • 对于每个类, 编译器都创建一个虚函数地址表(是数组, 不是链表);
  • 对于每个函数调用, 都需要执行一项额外的操作, 即到表中查找地址;

注意事项:

  • 友元函数不能是虚函数, 因为友元函数不属于类, 而只有成员才能是虚函数
  • 解决方案: 可以通过友元函数使用虚成员函数来解决
  • 参考:http://t.csdn.cn/2vgyo

抽象基类:

多学学设计模式: http://t.csdn.cn/k2pc9

  • 包含纯虚函数的类,就称为抽象类,  不能被实例化
  • 在原型中使用 =0 指出类是一个抽象基类, 在类中可以不定义该函数(也可以定义)

静态类成员及函数:

静态类成员:

无论创建多少对象, 程序都只创建一个静态类变量副本

为什么不能在类声明中初始化静态成员变量?

因为声明描述了如何分配内存, 但并不分配内存

为什么不能在类声明文件中初始化, 而在方法文件中初始化?

因为程序可能将头文件包括在其他几个文件中, 将出现多个初始化语句副本, 从而引发错误

静态类成员函数:

声明必须包含关键字static, 但函数定义是独立的, 则其中不能包含关键字static

静态成员只能调用静态成员函数, 不能使用this指针

可以使用类名和作用域解析运算符来调用它(静态成员函数和静态成员)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值