【C++ 】继承

继承概念

继承:如果一个类A继承于另一个类B,那么A称做派生类或者子类B称作基类或者父类,继承可以让子类具有父类的各种变量和函数,所以不需要在进行编写父类的代码,即父类的成员(变量、函数)都会变成子类的一部分。成员函数存在代码段中

继承格式 class A :public B
class Person
{
public:
	void Cout()
	{
		cout << "name=" << _name << endl;
		cout << "age=" << age << endl;
	}
private:
	string _name = "abc";
	int age = 20;
};
class Student :public Person
{
private:
	string _num = "100134";
};
void J_C_Test1()
{
	Student s;
	s.Cout();
}
int main()
{
	J_C_Test1();
	return 0;
}
输出
name=abc
age=20

继承格式 class A :public B
public表示继承方式
继承父类成员访问方式的变化
类成员/继承方式public继承protected继承private继承
父类的public成员子类的public成员子类的protected成员子类的private成员
父类的protected成员子类的protected成员子类的protected成员子类的private成员
父类的private成员在子类中是不可见的在子类中是不可见的在子类中是不可见的

protected:类外不可见,但在子类中可见

private:类外不可见,在子类中也不可见(子类中仍然含有父类的private类型的成员,所以这些成员还是会占用子类的一部分空间

class默认继承方式是private,struct默认继承方式是`public

父类的成员变量一般定义为protected,这些成员变量在类外是不可见的,但子类可见,这样就即体现了封装,也体现了复用
class P 
{
public :
	void Out1()
	{
		cout << "public" << endl;
	}
protected:
	void Out2()
	{
		cout << "protected" << endl;
	}
private:
	void Out2()
	{
		cout << "private" << endl;
	}
};
class P1 :public P
{

};
class P2 :protected P
{

};
class P3 :private P
{

};
void J_C_Test2()
{
	P1 p1;
	p1.Out1();
	p1.Out2();
	p1.Out3();
	P2 p2;
	p2.Out1();
	p2.Out2();
	p2.Out3();
	P3 p3;
	p3.Out1();
	p3.Out2();
	p3.Out3();
}

在这里插入图片描述

在这里插入图片描述
由上可见只有p1.Out1();可以正常输出其他的不可以

父类和子类对象的赋值转换
只有在public继承下,子类对象才可以赋值给父类的对象、指针、引用

原因:

切片
  • 子类的变量数量>=父类的变量数量,所以子类对象是可以给父类对象赋值的(多余的子类变量的值直接舍弃),而父类对象一般情况下不可以给子类对象赋值(多余的子类变量的值不知道该赋什么),只有在父子两类的变量数量相等时,才可以进行相互赋值
    在这里插入图片描述
class PA
{
public:
	PA(int a = 1, int b = 2)
	{
		pa = a;
		pb = b;
	}
	void Out()
	{
		cout << "pa=" << pa << endl;
		cout << "pb=" << pb << endl;
	}
protected:
	int pa;
	int pb;
};
class SA :public PA
{
public :
	SA(int a = 10, int b = 20,char ca='a',char cb='b' )
	{
		pa = a;
		pb = b;
		sa = ca;
		sb = cb;
	}
	void Out()
	{
		cout << "pa=" << pa << endl;
		cout << "pb=" << pb << endl;
		cout << "sa=" << sa << endl;
		cout << "sb=" << sb << endl;
	}
	char sa;
	char sb;
	
};
void J_C_Test3()
{
	cout << "子类给父类赋值" << endl;
	PA p;
	cout << "p的默认值" << endl;
	p.Out();
	cout << endl;
	SA s;
	cout << "s的默认值" << endl;
	s.Out();
	cout << endl;
	p = s;
	cout << "p的修改后的值" << endl;
	p.Out();
	cout << endl;
	PA& pp = s;
	cout << "pp的默认值" << endl;
	pp.Out();
	cout << endl;
	PA* ppp = &s;
	cout << "ppp的默认值" << endl;
	ppp->Out();
	cout << endl;
	cout << "父类给子类赋值" << endl;
	PA p1;
	SA s1;
	s1 = p1;//父类对象不能赋值给子类对象
	s1 = (SA)p1;//不支持父类对象带子类对象的强制转换
	SA* ss1 = &p1;//父类指针不能做直接赋给子类指针
	SA* ss2 = (SA*)&p1;//但是父类指针经过强转就可以赋给子类指针
	SA* sp = (SA*)&p1;//虽然可以赋值,但是不安全,因为&p1本身指向的就是一个父类的地址
	SA* spp = (SA*)&ppp;//安全,因为&ppp实际指向的是&s的地址,&s是一个子类的地址
}
输出
子类给父类赋值
p的默认值
pa=1
pb=2

s的默认值
pa=10
pb=20
sa=a
sb=b

p的修改后的值
pa=10
pb=20

pp的默认值
pa=10
pb=20

ppp的默认值
pa=10
pb=20

在这里插入图片描述

继承中的作用域
  • 父类和子类都有自己独立的作用域
  • 如果子类和父类中有同名成员,则系统会默认屏蔽掉父类成员,这
    种情况也就叫隐藏,在子类成员函数中,可以使用基类::基类成员 显示访问
隐藏1:父类成员变量名,与子类成员变量名,相同,系统会默认屏蔽掉父类成员变量名
class J_C_A
{
public:
	char a = 'a';
	int age = 20;
	
};
class J_C_B :public J_C_A
{
public :
	char a = 'b';
};
void J_C_Test4()
{
	J_C_A a;
	J_C_B b;
	cout << "sizeof(a)=" << sizeof(a) << endl;
	cout << "sizeof(b)=" << sizeof(b) << endl;
	cout << "b.a=" << b.a<< endl;
}
输出
sizeof(a)=8
sizeof(b)=12//可见J_C_B中是继承了J_C_A中的所有东西,包括 J_C_A的char a;
b.a=b//系统会默认屏蔽掉  J_C_A  的 char a;

隐藏2:父类的成员函数名,子类的成员变量名,相同,系统会默认屏蔽掉父类成员函数名
class J_C_A
{
public:
	char a = 'a';
	int age = 20;
	void f()
	{
		cout << "J_C_A" << endl;
	}
	
};
class J_C_B :public J_C_A
{
public :
	char a = 'b';
	int f = 20;
};
void J_C_Test4()
{
	J_C_A a;
	J_C_B b;
	cout << b.f << endl;
	//b.f();
}
输出
20
class J_C_A
{
public:
	char a = 'a';
	int age = 20;
	void f()
	{
		cout << "J_C_A" << endl;
	}
	
};
class J_C_B :public J_C_A
{
public :
	char a = 'b';
	//int f = 20;
};
void J_C_Test4()
{
	J_C_A a;
	J_C_B b;
	//cout << b.f << endl;
	b.f();//调试时不注释掉int f = 20;会出现 “明显调用的表达式前的括号必须具有(指针)函数类型”的报错
}
输出
J_C_A
隐藏3:父类函数名,子类函数名,相同,系统会默认屏蔽掉父类成员函数名,
  • 只要函数名相同就构成了隐藏
class J_C_A
{
public:
	char a = 'a';
	int age = 20;
	void f()
	{
		cout << "J_C_A" << endl;
	}
	void fun()
	{
		cout << "A" << endl;
	}
};
class J_C_B :public J_C_A
{
public :
	char a = 'b';
	//int f = 20;
	void fun()
	{
		cout << "B" << endl;
	}
};
void J_C_Test4()
{
	J_C_A a;
	J_C_B b;
	b.fun();
	b.fun1('a', 10);
}
输出
B
a
10
子类的默认成员函数
  • 子类的构造函数
1.初始化列表,自动调用父类的默认构造函数,初始化从父类继承过来的成员,并不是创建对象
2.父类的默认构造调用完之后,再去初始化子类新增的成员
3.初始化顺序:继承的父类成员,子类新增成员
class Human
{
public:
	Human(int age=20)
		: _age(age)
	{
		cout << "Human()" << endl;
	}
	Human(const Human& h)
	{
		cout << "Human(const Human& h)" << endl;
	}
	Human& operator=(const Human& h)
	{
		cout << "Human& operator=(const Human& h)" << endl;
	}
	~Human()
	{
		cout << "~Human()" << endl;
	}

protected:
	int _age;
};
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
		//: _age(age)//变量只能初始化一次
		
	//区别		
		: _num(num)
	//区别	
		
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
protected:
	int _num;
};
void J_C_Test5()
{
	Worker w;//自动调用子类的构造函数,在初始化列表的时候,调用父类的构造函数
}
输出
Human()
Worker(int age=22,int num=2020)
~Human()

如果父类没有默认的构造函数,不能直接在初始化列表中初始化父类的成员,需要显式指定调用父类的哪一个构造函数,从而完成父类成员的初始化
class Human
{
public:
	Human(int age)
		: _age(age)
	{
		cout << "Human()" << endl;
	}

protected:
	int _age;
};
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
	
	//区别	
		: Human(age)//不写,会报出“类 "Human" 不存在默认构造函数”的错误
		, _num(num)
	//区别	
		
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
protected:
	int _num;
};
  • 子类的拷贝构造函数
1.编译器默认生成的子类拷贝构造会自动调用父类的拷贝构造
class Human
{
public:
	Human(int age=20)
		: _age(age)
	{
		cout << "Human()" << endl;
	}
	Human(const Human& h)
	{
		cout << "Human(const Human& h)" << endl;
	}
	~Human()
	{
		cout << "~Human()" << endl;
	}

protected:
	int _age;
};
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
		//: _age(age)//变量只能初始化一次

		//区别
		: Human(age)//不写,会报出“类 "Human" 不存在默认构造函数”的错误
		, _num(num)
		//区别
			
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
	
	
	//并没有定义子类的拷贝构造函数
	
	
protected:
	int _num;
};
void J_C_Test5()
{
	Worker w;//自动调用子类的构造函数,在初始化列表的时候,调用父类的构造函数
	Worker copy(w);
}
输出
Human()
Worker(int age=22,int num=2020)
Human(const Human& h)//调用了父类的拷贝构造函数
~Human()
~Human()

2.显式定义子类的拷贝构造函数,默认情况下,会自动调用父类的默认构造函数
class Human
{
public:
	Human(int age=20)
		: _age(age)
	{
		cout << "Human()" << endl;
	}
	Human(const Human& h)
	{
		cout << "Human(const Human& h)" << endl;
	}
	~Human()
	{
		cout << "~Human()" << endl;
	}

protected:
	int _age;
};
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
		: Human(age)//不写,会报出“类 "Human" 不存在默认构造函数”的错误
		, _num(num)
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
	Worker(const Worker & w)
	
	//区别
		:_num(w._num)//当父类有默认构造函数时,可以这样写
	区别	
		
		/*:Human(w)
		,_num(w._num)*/
	{
		cout << "Worker(const Worker & w)" << endl;
	}
	
protected:
	int _num;
};
输出
Human()
Worker(int age=22,int num=2020)
Human()//调用了父类的默认构造函数
Worker(const Worker & w)
~Human()
~Human()

3.但也可以显式指定调用父类的拷贝构造
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
		: Human(age)//不写,会报出“类 "Human" 不存在默认构造函数”的错误
		, _num(num)
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
	Worker(const Worker & w)
		//:_num(w._num)//当父类有默认构造函数时,可以这样写

	//区别		
		:Human(w)
		,_num(w._num)
	//区别		

	
	{
		cout << "Worker(const Worker & w)" << endl;
	}
	
protected:
	int _num;
};
输出
Human()
Worker(int age=22,int num=2020)
Human(const Human& h)//调用了父类拷贝构造函数
Worker(const Worker & w)
~Human()
~Human()
  • 子类的operator=函数
1.编译器默认生成的子类operator=,会自动调用父类的operator=
//其他地方和上述的一样
void J_C_Test5()
{
	Worker w;//自动调用子类的构造函数,在初始化列表的时候,调用父类的构造函数
	Worker copy(w);
	cout << "operator=" << endl;
	w = copy;
}
输出
Human()
Worker(int age=22,int num=2020)
Human(const Human& h)
Worker(const Worker & w)
operator=
Human& operator=(const Human& h)//调用了父类的`operator=`
~Human()
~Human()

2.显式定义了子类的operator=,调用子类自己的operator=,就会与父类的operator=构成了函数隐藏,所以就不会自动调用父类的operator=
class Worker : public Human
{
public:
	Worker(int age=22,int num=2020)
		//: _age(age)//变量只能初始化一次
		: Human(age)//不写,会报出“类 "Human" 不存在默认构造函数”的错误
		, _num(num)
	{
		cout << "Worker(int age=22,int num=2020)" << endl;
	}
	Worker(const Worker & w)
		//:_num(w._num)//当父类有默认构造函数时,可以这样写
		:Human(w)
		,_num(w._num)
	{
		cout << "Worker(const Worker & w)" << endl;
	}
	Worker& operator=(const Worker& w)
	{
		cout << "Worker& operator=(const Worker& w)" << endl;
		return *this;
	}
protected:
	int _num;
};
void J_C_Test5()
{
	Worker w;//自动调用子类的构造函数,在初始化列表的时候,调用父类的构造函数
	Worker copy(w);
	cout << "operator=" << endl;
	w = copy;
}
输出
Human()
Worker(int age=22,int num=2020)
Human(const Human& h)
Worker(const Worker & w)
operator=
Worker& operator=(const Worker& w)//调用了子类自己的`operator=`
~Human()
~Human()

3.但是可以显式调用,通过:父类::operator=()
class Worker : public Human
{
public:
//...以上省略
	Worker& operator=(const Worker& w)
	{
		Human::operator=(w);
		cout << "Worker& operator=(const Worker& w)" << endl;
		return *this;
	}
protected:
	int _num;
};
输出
Human()
Worker(int age=22,int num=2020)
Human(const Human& h)
Worker(const Worker & w)
operator=
Human& operator=(const Human& h)//调用父类`operator=`
Worker& operator=(const Worker& w)//调用子类`operator=`
~Human()
~Human()
  • 子类的析构函数
1.任何情况下都是最后调用父类析构函数
2.父类析构函数与子类析构函数会构成同名隐藏,底层编译器会修改析构函数的名字,导致父类与子类的析构函数同名
3.一定不要在子类析构函数中,显式调用父类析构函数,否则会导致父类析构函数会执行两次,导致程序错误
继承与友元

友元关系不能继承,也就是说父类友元不能访问子类protectedprivate成员

class BB;
class AA
{
	friend class BB;
protected:
	int _a = 1;
private:
	int _b = 2;
};
class BB
{
public:
	void OutAA(AA& a)
	{
		cout << a._a << endl;
		cout << a._b << endl;
		CC c;
		cout << c._c << endl;//父类友元不能访问子类`protected`、`private`成员
	}
};
class CC :public AA
{
private:
	int _c = 10;
};
void J_C_Test6()
{
	//Worker w;
	AA a;
	BB b;
	CC c;
	b.OutAA(a);
	b.OutAA(c);
}
继承与静态成员

父类定义了static静态成员,则整个继承体系里面只有一个
样的成员,不论派生出,多少个子类,都只有这一个static成员

class Z
{
public:
	int getZ()
	{
		return _z;
	}
	int addZ()
	{
		++_z;
		return _z;
	}
	static int _z;
};
int Z::_z = 1;
//静态变量虽然是成员变量,但是静态变量和全局变量都是在同一存储区存储的,
//程序初始化的时候就需要对该变量做初始化。所以静态变量的表现就跟全局变量一样,需要类内声明、类外定义。
class Y :public Z
{

};
class X :public Y
{

};
void J_C_Test7()
{
	Z z;
	z.addZ();
	cout << "z.getZ()=" << z.getZ() << endl;
	Y y;
	y.addZ();
	cout << "y.getZ()=" << y.getZ() << endl;
	X x;
	x.addZ();
	cout << "x.getZ()=" << x.getZ() << endl;
}
输出
z.getZ()=2
y.getZ()=3
x.getZ()=4

菱形继承与菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承
在这里插入图片描述
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
在这里插入图片描述
菱形继承:菱形继承是多继承的一种特殊情况
在这里插入图片描述
菱形继承的问题:可以看出菱形继承有数据冗余和二义性的问题。在D的对象A的成员_a有两份

class A
{
protected:
	int _a;
};
class B :public  A
{
protected:
	int _b;
};
class C :public A
{
protected:
	int _c;
};
class D :public B,public C
{
public:
	int get_a()
	{
		//return _a;//"D::_a" 不明确,因为D中有两个分别来B和C的_a
		return B::_a;//或者return C::_a;
	}
protected:
	int _d;
};
void J_C_test7()
{
	A a;
	cout << "sizeof(a)=" << sizeof(a) << endl;
	//sizeof(a) = 4
	B b;
	cout << "sizeof(b)=" << sizeof(b) << endl;
	//sizeof(b) = 8
	C c;
	cout << "sizeof(c)=" << sizeof(c) << endl;
	//sizeof(c) = 8
	D d;
	cout << "sizeof(d)=" << sizeof(d) << endl;
	//sizeof(d) = 20,可以看出d中是有两个_a的
}

虚拟继承可以解决菱形继承的数据冗余和二义性的问题。如上面的基础关键,在B和C继承A时,使用虚拟继承,即可解决问题

class A
{
protected:
	int _a;
};
class B :virtual public  A
{
protected:
	int _b;
};
class C :virtual public A
{
protected:
	int _c;
};
class D :public B,public C
{
public:
	int get_a()
	{
		//return _a;//"D::_a" 不明确,因为D中有两个分别来B和C的_a
		//return B::_a;//或者return C::_a;
		return _a;
	}
protected:
	int _d;
};
void J_C_test7()
{
	A a;
	cout << "sizeof(a)=" << sizeof(a) << endl;
	//sizeof(a) = 4
	//因为int _a  所以 4
	B b;
	cout << "sizeof(b)=" << sizeof(b) << endl;
	//sizeof(b) = 12
	//因为int _a int _b 虚基表指针 所以 12
	C c;
	cout << "sizeof(c)=" << sizeof(c) << endl;
	//sizeof(c) = 12
	//因为int _a int _c 虚基表指针 所以 12
	D d;
	cout << "sizeof(d)=" << sizeof(d) << endl;
	//sizeof(d) = 24,可以看出只含有一个_a
	//int _a int _b int _c int _d  B的虚基表 C的虚基表 所以 24
}
sizeof(a)=4
sizeof(b)=12
sizeof(c)=12
sizeof(d)=24

不过这样我们知道了虚基表指针是存储在对象中的,那么虚基表存储在哪里呢?

根据每个编译器的不同都有不同的处理,vs是存储在寄存器中的。

虽然一个继承体系中虚继承自同一个父类的子类一共只存储一份父类,这是为了防止数据冗余,但是在sizeof()计算每个子类大小时编译器也是会将父类大小的计算加入每个子类中的,尽管他们一共只存储一份父类。例如上面的例子如果我们将B/C类属于他们自身的成员去掉我们会发现他们的大小还是有8,这就是因为编译器在每个子类中还加入了父类大小的计算,这点在虚继承上也不例外。

虚拟继承解决数据冗余和二义性的原理
借助内存窗口观察对象成员的模型
class A
{
public :
	int _a;
};

class B :public A
{
public:
	int _b;
};

class C :public A
{
public :
	int _c;
};

class D : public B, public C
{
public :
	int _d;
};

void J_C_Test8()
{
	D d;
	//d._a = 1;//_a不明确
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
}

在这里插入图片描述

菱形虚拟继承
class A
{
public :
	int _a;
};

class B :virtual public A
{
public:
	int _b;
};

class C :virtual public A
{
public :
	int _c;
};

class D : public B, public C
{
public :
	int _d;
};

void J_C_Test8()
{
	D d;
	//d._a = 1;//_a不明确
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

}
int main()
{
	J_C_Test8();
	return 0;
}

在这里插入图片描述

引入虚拟继承以后,编译器会生成一张虚基表,该表存放的是该类与其虚拟继承的父类之间的地址偏移量,从而在该类中会相应的生成一个指向虚基表的虚基表指针。

D类的两个父类B类、C类都是虚拟继承于A类,因此,B类、C类都有属于自己的虚基表,而它们的虚基表都会存储同一个A类之间的地址偏移量,所以就可以做到D类中只有一个公共的A类成员,从而解决了数据冗余和二义性问题

菱形继承:一种特殊的多继承

问题:数据冗余和二义性

解决问题:菱形虚拟继承–>公共的父类成员只保存一份–>通过虚基表和虚基表指针访问公共的父类成员

总结

什么时候使用继承?什么时候使用组合?

组合与继承

继承:is-a的关系,要实现多态,也必须要继承,类之间的关系可以用继承,

组合:has-a的关系,组合的耦合度低,代码维护性好,所以优先使用组合,当然不行的,也不可以强求

多继承指针偏移:
1.类型决定指针能查看的字节长度
2.对应类型的指针只能获取对应类型的成员

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值