C++基础——继承和派生

1. 继承概念

面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,已经能够设计出基于对象的程序,这是面向对象程序设计的基础。

要较好地进行面向对象程序设计,还必须了解面向对象程序设计另外两个重要特 征——继承性和多态性。本章主要介绍有关继承的知识,多态性将在后续章节中讲解。
继承性是面向对象程序设计最重要的特征,可以说,如果没有掌握继承性,就等于没有掌握类和对象的精华,就是没有掌握面向对象程序设计的真谛。

1.1 类之间的关系

has-A,uses-A 和 is-A

  • has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
  • uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
  • is-A 机制称为“继承”。关系具有传递性,不具有对称性。

1.2 继承关系举例

植物继承图
在这里插入图片描述

1.3 继承相关概念

在这里插入图片描述

1.4 派生类的定义

在这里插入图片描述

1.5 继承重要说明

1、子类拥有父类的所有成员变量和成员函数
4、子类可以拥有父类没有的方法和属性
2、子类就是一种特殊的父类
3、子类对象可以当作父类对象使用

2. 派生类的访问控制

派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。

2.1 单个类的访问控制

1、类成员访问级别(public、private、protected)

2.2 不同的继承方式会改变继承成员的访问属性

1)C++中的继承方式会影响子类的对外访问属性
  • public继承:父类成员在子类中保持原有访问级别
  • private继承:父类成员在子类中变为private成员
  • protected继承
    父类中public成员会变成protected
    父类中protected成员仍然为protected
    父类中private成员仍然为private
2)private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员 。
3)C++中子类对外访问属性表

在这里插入图片描述

4)继承中的访问控制

在这里插入图片描述

#include "iostream"
using namespace std;

//public修饰的成员 变量和方法在类的内外皆可使用
//private修饰的成员 变量和方法   只能在类的内部使用  不能在外部使用
//protect修饰的成员 变量和方法   在类的内部使用(),在继承的子类中可用

//1  protect关键字   修饰的成员变量 和成员函数是为了在家族中使用
//2  项目开发中,一般情况下是public
class Parent
{
public:
	int a;
protected:
	int b;
private:
	int c;

public:
	void printT()
	{
		cout<<"printT"<<endl;
	} 
};

class Child3 : protected Parent   //保护继承
{
public:
	void useVar()
	{
		a=0;
		b=0; 
		//c=0;   //err
	}
protected:
private:
};

void main23()
{
	Child3 c3;
	//c3.a=10;//err
	//c3.b=20;//err
	//c3.c=30;//err

	system("pause");
	return ;
}


class Child : private Parent   //私有继承
{
public:
	void useVar()
	{
		a=0;
		b=0;
		//c=0;   //err
	}
protected:
private:
};

void main22()
{
	Child c1,c2,c3;
	//c1.a=10;//err
	//c2.b=20;//err
	//c3.b=30;//err

	system("pause");
	return ;
}

//公有继承

//class Child : public Parent
//{
//public:
//	void useVar()
//	{
//		a=0;
//		b=0;
//		//c=0;   //err
//	}
//protected:
//
//private:
//
//};

void main21()
{
	Parent t1,t2;
	t1.a=10;
	//t1.b=20;//err
	//t1.c=30;//err

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

2.3“三看”原则

C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问

  • 1)看调用语句,这句话写在子类的内部、外部
  • 2)看子类如何从父类继承(public、private、protected)
  • 3)看父类中的访问级别(public、private、protected)

2.4 派生类类成员访问级别设置的原则

  • 1、需要被外界访问的成员直接设置为public
  • 2、只能在当前类中访问的成员设置为private
  • 3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
#include "iostream"
using namespace std;

class Parent
{
public:
	void printT()
	{ 
		a=0,b=0;
		cout <<"a:"<<a<<endl;
		cout <<"b:"<<b<<endl;
	}
	int a;
	int b;
protected:
private:
	
};
//class Child : private Parent
//class Child : protected Parent
class Child : public Parent
{
public:
protected:
private:
	int c;
};


void main()
{
	Child c1;
	c1.a=2;
	c1.b=3;
	//c1.c=1;
	c1.printT();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

2.5综合训练

//类的继承方式对子类对外访问属性影响

#include <cstdlib>
#include <iostream>

using namespace std;

class A
{
private:
	int a;
protected:
	int b;
public:
	int c;

	A()
	{
		a = 0;		b = 0;		c = 0;
	}

	void set(int a, int b, int c)
	{
		this->a = a;		this->b = b;		this->c = c;
	}
};
class B : public A
{
public:
	void print()
	{
		//cout<<"a = "<<a; //err
		cout<<"b = "<<b; //ok
		cout<<"c = "<<endl; //ok
	}
};

class C : protected A
{
public:
	void print()
	{
		//cout<<"a = "<<a; //err
		cout<<"b = "<<b; // ok
		cout<<"c = "<<endl; //ok
	}
};

class D : private A
{
public:
	void print()
	{
		//cout<<"a = "<<a; //err
		cout<<"b = "<<b<<endl;  //ok
		cout<<"c = "<<c<<endl; // ok
	}
};

int main()
{
	
	A aa;
	B bb;
	C cc;
	D dd;

	aa.c = 100;  //ok
	bb.c = 100; // ok
	//cc.c = 100; // err 保护的
	//dd.c = 100;  // err
	
	aa.set(1, 2, 3); //ok
	bb.set(10, 20, 30); //ok
	//cc.set(40, 50, 60); // err
	//dd.set(70, 80, 90);  //err
	

	bb.print(); //ok
	cc.print(); //ok
	dd.print(); //


	/**/
	system("pause");
	return 0;
}

3继承中的构造和析构

3.1 类型兼容性原则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:

  • 子类对象可以当作父类对象使用
  • 子类对象可以直接赋值给父类对象
  • 子类对象可以直接初始化父类对象
  • 父类指针可以直接指向子类对象
  • 父类引用可以直接引用子类对象

在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
类型兼容规则是多态性的重要基础之一。

总结:子类就是特殊的父类 (base *p = &child;)
#include "iostream"
using namespace std;

class Parent
{
public:
	void printP()
	{
		cout<<"我是爸爸..."<<endl;
	}
	Parent()
	{
		cout<<"Parent 构造函数"<<endl;
	}
	Parent(const Parent &obj)
	{
		cout<<"copy 构造函数"<<endl;
	}
protected:
private:
	int a;
};

class Child : public Parent
{ 
public:
	void printC()
	{
		cout<<"我是儿子..."<<endl;
	}
protected:
private:
	int c;
};

//C++编译器是不会报错的
void howToPtint(Parent *base)
{
	base->printP();//父类的成员函数
}

void howToPtint2(Parent &base)
{
	base.printP();//父类的成员函数
}

void main()
{
	Parent p1;
	p1.printP();

	Child c1;
	c1.printC();
	c1.printP();

	//赋值兼容性原则
	//1  基类指针(引用)指向 子类对象
	Parent *p=NULL;
	p=&c1;
	p->printP();
	 
	//1-2  指针做函数参数
	howToPtint(&p1); 
	howToPtint(&c1);

	//1-3  引用做函数参数
	howToPtint2(p1); 
	howToPtint2(c1);

	//2  p3=c1;
	//可以让父类对象  初始化   子类对象
	//子类是一种特殊的父类
	Parent p3=c1;


	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

3.2继承中的对象模型

类在C++编译器的内部可以理解为结构体
子类是由父类成员叠加子类新成员得到的
在这里插入图片描述
在这里插入图片描述
问题:如何初始化父类成员?父类与子类的构造函数有什么关系?

  • 在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化
  • 在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理
#include "iostream"
using namespace std;

class Parent
{
public:
	Parent(int a,int b)
	{
		this->a=a;
		this->b=b;
		cout<<"父类构造函数..."<<endl;
	}
	~Parent()
	{
		cout<<"父类析构函数..."<<endl;
	}

	void printP(int a,int b)
	{
		this->a=a;
		this->b=b;
		cout<<"我是爸爸..."<<endl;
	}

protected:
private:
	int a;
	int b;
};

class Child : public Parent
{ 
public:
	Child(int a,int b,int c) : Parent(a,b)
	{
		this->c=c;
	}
	~Child()
	{
		cout<<"儿子析构..."<<endl;
	}
	void printC()
	{
		cout<<"我是儿子..."<<endl;
	}
protected:
private:
	int c;
};


void playObj()
{
	 Child c1(1,2,3);
}

void main()
{
	Parent p(1,2);
	playObj();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

3.3继承中的构造析构调用原则

1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4、析构函数调用的先后顺序与构造函数相反

3.4继承与组合混搭情况下,构造和析构调用原则

原则:

  • 先构造父类,再构造成员变量、最后构造自己
  • 先析构自己,在析构成员变量、最后析构父类
  • 先构造的对象,后释放
#include "iostream"
using namespace std;

class Object
{
public:
	Object(int a,int b)
	{
		this->a=a;
		this->b=b;
		cout<<"object构造函数 执行  "<<"a:"<<a<<",b:"<<b<<endl;
	}
	~Object()
	{
		cout<<"析构函数  object "<<endl;
	}
protected:
	int a;
	int b;
};

class Parent : public Object
{
public:
	Parent(char *p) : Object(1,2)
	{
		this->p=p;
		cout<<"父类构造函数..."<<p<<endl;
	}
	~Parent()
	{
		cout<<"父类析构函数..."<<p<<endl;
	}

	void printP(int a,int b)
	{
		cout<<"我是爸爸..."<<endl;
	}

protected:
	char *p;
};

class Child : public Parent
{ 
public:
	Child(char *p) : Parent(p) , obj1(3,4), obj2(5,6)
	{
		
		this->myp=p;
		cout<<"儿子构造..."<<myp<<endl;
	}
	~Child()
	{
		cout<<"儿子析构..."<<myp<<endl;
	}
	void printC()
	{
		cout<<"我是儿子..."<<myp<<endl;
	}
protected:
	char *myp;
	Object obj1;
	Object obj2;
};

void main()
{
	Child c1(" 继承测试child...");

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

3.5继承中的同名成员变量处理方法

  • 1、当子类成员变量与父类成员变量同名时
  • 2、子类依然从父类继承同名成员
  • 3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符
  • 4、同名成员存储在内存中的不同位置
    在这里插入图片描述
    在这里插入图片描述
    总结:同名成员变量和成员函数通过作用域分辨符进行区分
#include "iostream"
using namespace std;

class A
{
public:
	int a;
	int b;
public:
	void get1()
	{
		cout<<" 父类b: "<<b<<endl;
	}
	void printT2()
	{
		cout<<"AAAAAA"<<endl;
	}
protected:
private:
};
class B : public A
{
public:
	int b;
	int c;
public:
	void get2()
	{
		cout<<" 子类b: "<<b<<endl;
	}
	void printT2()
	{
		cout<<"BBBBBB"<<endl;
	}
protected:
private:
};
// 同名成员变量
void main71()
{
	B b1;
	b1.b=1;  //
	b1.get2();
	
	b1.A::b=2;
	//b1.b = 2; 会认为是B类中的
	b1.get1();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

void main()
{
	B b1;
	b1.printT2();

	b1.A::printT2();
	b1.B::printT2();//默认 

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

3.6派生类中的static关键字

理论知识

  • 基类定义的静态成员,将被所有派生类共享
  • 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制)
  • 派生类中访问静态成员,用以下形式显式说明:
    类名 :: 成员
    或通过对象访问 对象名 . 成员
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    总结:
    1> static函数也遵守3个访问原则
    2> static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)
    3> 构造函数默认为private
#include "iostream"
using namespace std;

class B
{
public: 
	static void Add(){i++;};
	static int i;
	void out(){cout<<"static i="<<i<<endl;}
};
int B::i=0;
class D : private B
{
public:
	void f()
	{
		i=5;
		Add();
		B::i++;
		B::Add();
	}
};

void main()
{
	B x;
	D y;

	x.Add();
	x.out();
	y.f();

	cout<<"static i="<<B::i<<endl;
	cout<<"static i="<<x.i<<endl;
	//cout<<"static i="<<y.i<<endl;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

4 多继承

4.1多继承的应用

多继承概念
一个类有多个直接基类的继承关系称为多继承

  • 多继承声明语法
    class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
    {
    数据成员和成员函数声明
    };
  • 类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员
    在这里插入图片描述
    多继承的派生类构造和访问
  • 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
  • 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
  • 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。

多继承简单应用
在这里插入图片描述
在这里插入图片描述

#include "iostream"
using namespace std;

class Base1
{
public:
	Base1(int b1)
	{
		this->b1=b1;
	}
	void printB1()
	{
		cout<<"b1:"<<b1<<endl;
	}
protected:
private:
	int b1;
};

class Base2
{
public:
	Base2(int b2)
	{
		this->b2=b2;
	}
	void printB2()
	{
		cout<<"b2:"<<b2<<endl;
	}
protected:
private:
	int b2;
};

class B :public Base1,public Base2
{
public:
	B(int b1,int b2,int c):Base1(b1),Base2(b2)
	{
		this->c=c; 
	}
	void printC()
	{
		cout<<"c:"<<c<<endl;
	}
protected:
private:
	int c;
};

void main()
{
	B b1(1,2,3);
	b1.printC();
	b1.printB1();
	b1.printB2();

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

4.2虚继承

如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性。
在这里插入图片描述
在这里插入图片描述
总结:

  • 如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
  • 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
  • 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
  • 虚继承声明使用关键字 virtual

在这里插入图片描述
在这里插入图片描述

#include "iostream"
using namespace std;

class B
{
public:
	int b;
protected:
private:
	
};

class B1 : virtual public B
{
public:
	int b1;
protected:
private: 	
};

class B2 : virtual public B
{
public:
	int b2;
protected:
private: 	
};

class C :public B1,public B2
{
public:
	int c;
protected:
private:
	
};

void main()
{
	C c1;
	c1.b1=100;
	c1.b2=200;
	c1.c=300;

	c1.b=400;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

5继承总结

  • 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
  • 单继承的派生类只有一个基类。多继承的派生类有多个基类。
  • 派生类对基类成员的访问由继承方式和成员性质决定。
  • 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
  • C++提供虚继承机制,防止类继承关系中成员访问的二义性。
  • 多继承提供了软件重用的强大功能,也增加了程序的复杂性。
#include "iostream"
using namespace std;

class B
{
public:
	B()
	{
		cout<<"B的构造"<<endl;
	}
	int b;
protected:
private:

};

class B1 : virtual public B
{
public:
	int b1;
protected:
private: 	
};

class B2 : public B
{
public:
	int b2;
protected:
private: 	
};

class C :public B1,public B2
{
public:
	int c;
protected:
private:

};

void main1101()
{
	C c1;
	c1.b1=100;
	c1.b2=200;
	c1.c=300;

	//c1.b=400;

	cout<<"hello..."<<endl;
	system("pause");
	return ;
}

class D1
{
public:
	int k;
protected:
private:
};

class D2
{
public:
	int k;
protected:
private:
};

class E :  public D1, public D2
{
public:
protected:
private:
};

void main1102()
{
	E e1;
	e1.D1::k=100;
	e1.D2::k=200;



	system("pause");
}

void main()
{
	cout<<sizeof(B)<<endl;   //4
	cout<<sizeof(B1)<<endl;//12
	cout<<sizeof(B2)<<endl;//8
	system("pause");
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值