C++笔记五(继承)

1.继承

1.1 减少重复代码。语法 class 子类 : 继承方式 父类 。
子类也称派生类,父类也称基类。

1.2继承方式一共有三种 公有继承、保护继承、私有继承
公有继承:父类公有在子类是公有;父类保护在子类是保护;父类私有不可访问
保护继承:父类公有在子类是保护;父类保护在子类是保护;父类私有不可访问
私有继承:父类公有在子类是私有;父类保护在子类是私有;父类私有不可访问

1.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 << "类Son大小:" << sizeof(Son) << endl;	//输出16
}
int main()
{
    
    
	test();
	return 0;
}

还可以使用VS的开发人员命令提示符查看类继承情况
首先打开vs 2017的开发人员命令提示符
接着更换到文件所在盘         命令为C:
然后跳转到cpp文件所在的文件夹    命令为cd 路径
最后输入 cl /d1 reportSingleClassLayout类名 “cpp文件名" 当然在此之前也可以先使用dir查看文件夹下文件
最终结果
1.4 继承中构造和析构的顺序
父类先构造,子类再构造,子类析构,最后父类析构。
语法:派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数)
{本类成员初始化赋值语句;}
;见C++笔记三中的初始化列表

class Base
{
    
    
public:
	Base() {
    
     cout << "Base:无参构造!" << endl; }
	Base(int v)
	{
    
    
		m_B = v;
		cout << "有参构造!" << endl;
	}
	Base(const Base &b)
	{
    
    
		m_B = b.m_B;
		cout << "拷贝构造!" << endl;
	}
	void func()
	{
    
    
		cout << "Base中的func()!" << endl;
	}
	void func(int a)
	{
    
    
		cout << "Base中的func(int a)!" << endl;
	}
	int m_B;
};
class Son :public Base
{
    
    
public:
	//Son(int vb,int vs):Base(vb)
	//{
    
    
	//	m_S = vs;
	//}
	Son(int vb, int vs) 
	{
    
    
		m_B = vb;
		m_S = vs;
	}
	//Son(const Son &s) :Base(s.m_B)
	//{
    
    
	//	m_S = s.m_S;
	//}
	Son(const Son &s) 
	{
    
    
		m_B = s.m_B;
		m_S = s.m_S;
	}
	void func()
	{
    
    
		cout << "Son中的func()!" << endl;
	}
	int m_S;
};
void test()
{
    
    
	Son s(1,2);
	cout << "子类m_A:" << s.m_S << endl;		//输出2
	cout << "父类m_A:" << s.Base::m_B << endl;	//输出1
	s.func();
	s.Base::func();
	//s.func(10);	//即使父类成员函数发生重载也不行,因为只要同名就会被隐藏
	s.Base::func(10);
	//Son中的func()!
	//Base中的func()!
	//Base中的func(int a)!
	cout << "构造效果与成员对象一样!" << endl;
	Son s2(s);
}

1.5对于继承中同名成员处理方式
对于子类的同名成员,直接访问即可。
对于父类的同名成员,需要通过作用域访问。不管是成员变量也好还是成员函数也好都是这样使用。
而且就算父类成员函数发生了重载也不能,因为只要出现了同名情况,编译器就会把父类所有同名的都隐藏。

class Base
{
    
    
public:
	Base()
	{
    
    
		m_A = 10;
	}
	void func()
	{
    
    
		cout << "Base中的func()!" << endl;
	}
	void func(int a)
	{
    
    
		cout << "Base中的func(int a)!" << endl;
	}
	int m_A;
};
class Son :public Base
{
    
    
public:
	Son()
	{
    
    
		m_A = 100;
	}
	void func()
	{
    
    
		cout << "Son中的func()!" << endl;
	}
	int m_A;
};
void test()
{
    
    
	Son s;
	cout << "子类m_A:" << s.m_A << endl;		//输出100
	cout << "父类m_A:" << s.Base::m_A << endl;	//输出10
	s.func();
	s.Base::func();
	//s.func(10);	//即使父类成员函数发生重载也不行,因为只要同名就会被隐藏
	s.Base::func(10);
}
int main()
{
    
    
	test();
	return 0;
}

1.6 继承同名静态成员处理方式
继承中静态成员同名处理方式与非静态处理方式一致
唯一有些特殊的就是静态成员可以直接通过类名去调用,所以加入class Base与class Son中都有一个static int m_A的话,Base::m_A与Son::Base::m_A结果一致但前者是直接使用静态成员的性质通过类名调用,而后者是通过子类调用的,调用的是Base::m_A,因为父类静态成员变量已经被继承过来。注意静态成员变量如此,静态成员函数也是如此!!!

1.7多继承(实际开发中不建议多继承!!!
语法为 class 子类 : 继承方式 父类1,继承方式 父类2……
缺点:多继承可能有父类中有同名成员出现的情况,从而需要通过作用域区分。(就是1.5、1.6内容)

1.8菱形继承(两个子类继承同一个父类,而这两个子类又都被另一个类继承)
比如(动物);(马、驴);(骡)
问题
其一,继承中可能有二义性!马继承动物的数据、驴也同样继承了,那么骡就会保存两份动物的数据,一份通过马,一份通过驴,那么使用数据时就会出现调用不明确的问题

class A
{
    
    
public:
	void func()
	{
    
    
		cout << "A中func()" << endl;
	}
};
class B:public A
{
    
    
};
class C :public A
{
    
    
};
class D :public B, public C
{
    
    
};
int main()
{
    
    
	D d;
	d.func();	//报错调用不明确
	return 0;
}

其二,在继承过程中有很多数据其实只要一份就够了,比如age,会造成数据冗余的问题,明明可以只要一份就好,而我们却保存了两份。

解决方法:

class A
{
    
    
public:
	int m_Num;
};
class B:public A
{
    
    
};
class C :public A
{
    
    
};
class D :public B, public C
{
    
    
};

class A1
{
    
    
public:
	int m_Num;
};
class B1 :virtual public A1	//在继承前加入关键字virtual 变为虚继承
{
    
    
};
class C1 :virtual public A1	//此时A1为虚基类
{
    
    
};
class D1 :public B1, public C1
{
    
    
};
void test()
{
    
    
	D d;
	//d.m_Num = 10;	//报错,错误为第一种
	d.B::m_Num = 10;
	d.C::m_Num = 20;	//可以通过作用域区分
	cout << "通过B访问m_Num:" << d.B::m_Num << endl;	//输出为10
	cout << "通过C访问m_Num:" << d.C::m_Num << endl;	//输出为10。见,即使这样,也还是有两份数据
	cout << "------------------------------------------------" << endl;
	
	D1 d1;
	//d.m_Num = 10;	//报错,错误为第一种
	d1.B1::m_Num = 10;
	d1.C1::m_Num = 20;
	cout << "通过B访问m_Num:" << d1.B1::m_Num << endl;	//输出为20
	cout << "通过C访问m_Num:" << d1.C1::m_Num << endl;	//输出为20
}
int main()
{
    
    
	test();
	return 0;
}

1>针对调用不明确可以通过作用域解决,但缺点是依然产生了两份数据,如下图
class D
2>针对数据冗余,可以使用虚继承。需要在继承前加关键字virtual,变成虚继承,如上例,其中基类A1为虚基类。我们可以通过VS的开发人员命令符看D1这个类,如下图:
classD1
由上图可以看出使用虚继承后m_Num只出现了一个,但B1与C1继承了一个vbptr(virtual base pointer,虚基类指针),虚基类指针指向vbtable(虚基类表),上图可以看到虚基类表,例如类D1中B1的虚基类指针指向对应B1的虚基类表,虚基类表中记录了一个偏移量8,在class D1中0+8便可找到m_Num的位置。同理,C1的虚基类表偏移量为4,在class C1中对应的虚基类指针通过偏移4字节后4+4为8也找到了m_Num。所以,在对象d1中无论怎么使用m_Num它都只有一个(上面代码中输出都为20)。

少用多继承!!!

猜你喜欢

转载自blog.csdn.net/weixin_45884870/article/details/109973922
今日推荐