C++ 类继承总结笔记

在这里插入图片描述
(这图片这么小看个批哦…)
总结的顺序就没多大逻辑了,但是能把基本的关系大概概括起来,仅供参考.

1. 派生类和基类的关系

形成继承关系:class derived: public base{...}

  1. 派生类从基类里继承了数据成员(实现)和方法(接口)

这句话的意思是,基类有的,派生类也会有。比如下面代码,基类有一个string类对象(复合),两个方法。那么派生类也会有一个string类对象与两个方法。

class base
{
    
    
	private:
		string str;
	public:
		void show(){
    
    cout<<str;}
		void modify(){
    
    cin>>str;}
};

class derived:public base
{
    
    	
};

int main()
{
    
    
	derived d;
	d.modify();
	d.show();
}

输入hello 输出hello

  1. 如有需要,派生类可以定义自己的构造函数以及添加额外数据以及方法

对于派生类的构造函数与基类构造函数问题,下面讲.

//base里modify函数修改如下
void modify(){
    
    getline(cin,str);}

class derived:public base
{
    
    
	private:
		int num;
	public:
		derived():base(),num(10){
    
    }
		int r_num(){
    
    return num;} 
};

int main()
{
    
    
	derived d;
	d.modify();
	d.show();cout<<endl;
	cout<<d.r_num()<<endl;
	
	base b;
	b.modify();
	b.show();cout<<endl;
}

在这里插入图片描述

2.访问权限

  1. 派生类不可以直接访问基类的私有部分

(你爹还是你爹,内脏不可能掏出来给你看吧…) 好吧,私有部分是封装最底层的一部分,私有部分对数据能进行有效保护,所以严格规定除了自己,其他人都是不得访问的(还有友元!)
解决方案: 1. 使用protected来代替private写入数据,则派生类的成员函数可以调用; 2.调用基类方法来访问私有数据。
example:
如果你写上cin>>d.str,就是派生类对象使用基类私有成员,编译器将报错[Error] 'std::string base::str' is private
下面是使用protected,调用基类成员函数的方法上述代码已经展示过了。

class base
{
    
    
	protected:
		string str;
		...
		}
class derived:public base
{
    
    
	...
		public:
			void use_str() {
    
    cout<<str<<endl;}
	}
  1. 如果不调用基类构造函数,程序将使用默认基类构造函数

如果将基类默认构造函数也定义好,其实风险也不大(如果动态内存分配,就要定义好new)

  1. 不能将基类对象或地址赋给派生类引用或指针

派生类里可能有你基类里没有的,所以这样子存在风险(但仍可以强制转换,不建议!)

  1. Base b( Derived)是可以的,就是在创建基类时以派生类对象为实参。这个会调用复制构造函数,而复制构造函数的参数是基类的引用,基类引用可以引用派生类对象。
    同样,
Base b;
Derived c;
b = c

也是可以的。

3. 多态公有继承

多态公有继承的含义就是基类派生出派生类,然后派生类继承了数据和方法,但是派生类想要重新定义一些方法的功能,那这就叫多态公有继承。

  1. 在派生类重新定义基类中已有的方法。
  2. 虚函数(函数声明前+virtual)与非虚函数
    • 如果定义了virtual function,程序将根据使用的对象类型来判断使用哪个函数;
    • 如果没有定义virtual,程序将根据调用函数的引用或指针来判断使用哪个;
class Base
{
    
    	....
	public:
	void show();
	virtual void show2();
}
class derived:public Base
{
    
    
	....
	public:
		void show();
		virtual void show2();
}

int main()
{
    
    
	Base* b;
	derived c;
	b=c;
	b.show();   
	//使用的是Base的show(),
	//因为是non-virtual根据指针或引用选择
	b.show2();	
	//使用的是derived的show2(),
	//是virtual,根据使用对象,使用对象就是derived对象
}
  1. virtual析构函数的作用:是为了确保按正确的顺序执行析构函数

如果你没有virtualize 析构函数,则你搞一个基类指针指向派生类,使用析构函数时,因为不是virtual所以是根据指针或引用决定。
如果你virtualize了析构函数,则基类指针指向派生类,然后调用析构函数调用的是派生类的析构函数,然后派生类析构函数执行完会自动调用基类析构函数。

  1. 如果你定义了virtual,但是基类指针指向派生类对象时,又想用基类的virtual方法,则可以使用作用域解析运算符::调用基类方法。

  2. Tip: 可以定义一个基类指针array,然后存储派生类对象地址,这样就可以通过指针管理派生类对象。

4.静态联编和动态联编

定义:

  • 静态联编就是在编译器编译过程中联编
  • 动态联编就是生成能够在程序运行时选择正确的virtual代码
  1. 编译器对非虚函数使用静态联编,对虚函数使用动态联编。
  2. 效率:静态联编的效率更高,因为不需要方法追踪指针或引用,所以编译器以静态联编为默认联编方式。所以不应该将为重新定义的方法设置为virtual
  3. 虚函数工作原理:

编译器给每个对象添加一个隐藏成员,该成员保存一个指向函数地址数组的指针,称该数组为虚函数表。虚函数表存储了在该对象进行声明的虚函数地址。

  • 若派生类没有重新定义虚函数,则保持的是原函数的地址。
  • 若派生类重新定义了虚函数,保存的是新的虚函数地址,与原函数区别开来了
  • 若声明了新函数,则将新函数的地址也添加到virtual function table中

假如derived d("Jeff", 20) ,然后该类里还有一些虚函数和新定义函数,
base* b = &d, b指向派生类对象d, 如果调用虚函数,则b先指向隐藏成员(一个指向函数地址数组的指针),然后获取地址,前往虚函数表,然后在虚函数表里获得虚函数地址,然后再跳到虚函数地址去执行虚函数。

  1. 动态联编成本:
    a. 每个对象增大,增大两位存储地址的控件
    b. 对每一个类,编译器都将创一个虚函数表
    c. 函数调用需要一个额外操作——查找地址
  2. 虚函数注意事项:
  • 构造函数不能是虚函数,在派生类构造函数中初始化列表调用父类构造函数即可
  • 析构函数要虚函数,以确保析构函数按正确顺序执行。否则用基类指针或引用时只调用基类析构函数
  • 重新定义将覆盖原方法,如果原方法有多个重载,那重新定义后也需要重新定义那几个重载,如果不需要改变可以再函数里invoke原函数
  • 如果重新定义继承下来的方法,应该确保和原型相同。但如果原型返回类型是基类指针或引用,则新定义的方法可以返回派生类指针或引用。(只适用于返回值)
5.protected

protected可被一下访问:

  1. 基类成员函数
  2. 基类友元函数
  3. 派生类成员函数(比private多了这点)
    但是最好设置为private然后通过基类方法访问,更安全。
6.抽象基类(abstract base class ABC)

将多个类共有的共性抽象出一个基类,通过纯虚函数来提供未实现的函数(纯虚函数就是虚函数声明时加上 = 0)。 ABC至少需要一个纯虚函数,但仍可以提供其他方法定义。不可以创建ABC对象,但可以通过ABC指针或引用数组来管理派生类。

7.继承与动态内存分配
  1. 基类使用了new而派生类不用new
    这种情况派生类可以使用默认析构函数、赋值重载运算符和复制构造函数,而基类则必须自定义。使用派生类的这几个功能,都会自动调用基类已经定义好的相关成员函数。

  2. 派生类使用了new
    把必须为派生类定义显式析构函数、复制构造函数和赋值运算符。

猜你喜欢

转载自blog.csdn.net/ZmJ6666/article/details/108632903
今日推荐