C++类的继承总结

、继承概念:是面向对象程序设计使代码可以复用的重要手段,保持原有类特性基础进行扩展,增加功能,产生新的类,称为派生类。

定义格式:


三种继承关系:public,protected,private

三种类成员访问限定符:public,protected,private(需要与三种继承关系区别,不能混淆

举一个简单类:B公有继承(public)与A,B为派生类,A为基类

class A
{
public:
	void FunTest1()
	{
		cout<<"FunTest1"<<endl;
	}
	int _a1;
protected:
	int _a2;
private:
	int _a3;
};
class B:public A
{
public:
	void FunTest2()
	{
		cout<<"FunTest2"<<endl;
	}
private:
	int _b;
};

void FunTest()
{
	B b;
	b.FunTest1();
	b._a1=1;
}

继承方式与基类成员访问限定符关系变化:


总结:1.派生类继承了(无论哪一种继承方式)基类内部所有数据成员和成员函数,在派生类内部中可以访问基类的公有(public)成员和保护(protected)成员,基类的私有(private)成员存在但不可见,派生类不可以直接访问;

2.若基类成员不想在类外直接被访问,但需要在派生类中被访问,就定义为protected(即保护成员限定符是因继承才出现的);若在类外定义派生类对象,其也只能访问派生类中的public成员;

3.public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象;protetced/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分, has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,一般使用的都是公有继承。

4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

二、派生类的构造函数和析构函数(区别调用构造函数和执行函数体两种顺序)

类继承关系中派生类调用构造函数顺序:

派生类构造函数————>>基类构造函数(在派生类初始化列表调用)

函数体执行顺序:

基类构造函数————->>派生类构造函数(因为在派生类初始化列表又调用了基类构造函数,所以先执行基类函数体)

调用析构函数顺序:

派生类析构函数————>>基类析构函数

函数体执行顺序:

派生类析构函数————>>基类析构函数(因为:1派生类对象中自己独有成员创建晚,生命周期短,2派生类析构自己独有成员,基类析构自己独有成员,二者没有关系)

举例如:

class A
{
public:
	A()
	{
		cout<<"A()"<<endl;
	}
	~A()
	{
		cout<<"~A()"<<endl;
	}
	int _a1;
protected:
	int _a2;
private:
	int _a3;
};

class B:public A
{
public:
	B()
	{
		cout<<"B()"<<endl;
	}
	~B()
	{
		cout<<"~B()"<<endl;
	}
private:
	int _b;
};

void FunTest()
{
	B b;
}
在VS2010中其运行结果显示为派生类对象b调用构造函数和析构函数的函数体执行顺序:

注意:

1.基类有缺省构造函数,派生类没有显示定义构造函数,编译器会给派生类合成默认构造函数;

2.基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表;如:

class A
{
public:
	A(int a)
	{
		cout<<"A()"<<endl;
	}
};

class B:public A
{
public:
	B(int a)
		:A(a)
	{
		cout<<"B()"<<endl;
	}
};

void FunTest()
{
	B b(2);
}
3.基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数;

4.基类定义了带有形参表构造函数,派生类就一定定义构造函数。

 三.继承与转换

同名隐藏:若基类与派生类中有相同名字函数或数据成员,在类外派生类对象调用基类同名成员时,加基类作用域,如:

class A
{
public:
	void FunTest()
	{
		cout<<"A::FunTest()"<<endl;
	}
	int _a;
};
class B:public A
{
public:
	void FunTest()
	{
		cout<<"B::FunTest()"<<endl;
	}
	int _a;
};
void FunTest()
{
	B b;
	b.FunTest();  //访问的是派生类中的FunTest();
	b.A::FunTest();//访问基类A中FunTest();
	b._a;             //访问派生类成员
	b.A::_a;         //访问基类成员
}

赋值兼容规则 --public 继承

 1. 子类对象可以赋值给父类对象;

 2. 父类对象不能赋值给子类对象;

 3. 父类的指针/引用可以指向子类对象;

 4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

在VS2010中举例如:

class A
{
public:
	int _a;
};
class B:public A
{
public:
	int _b;
};
void FunTest()
{
	A a;     //父类对象
	B b;     //子类对象
	a=b;
	//b=a;    编译错误
	b=*(B*)&a; //可强制类型转换(使用时需要谨慎)

	A* pa=&b;
	A& ra=b;

	//B* pb=&a;  编译错误
	//B& rb=a;   编译错误
	B* pb=(B*)&a;  //强转
	B& rb=*((B*)&a);
}

四.继承方式

单继承:一个子类只有一个直接父类时称这个继承关系为单继承(简单的基本继承)


多继承:一个子类有两个或以上直接父类称为多继承


菱形继承:如

在VS2010中举简单例子:

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 FunTest()
{
	D d;
}
运行监视d对象组成:

它的内存组成方式为:(内存分布按照继承顺序)

由上可知,d中_a占了两份内存空间,因为类C与类D都继承了类A的成员,所以菱形继承存在二义性与数据冗余问题,浪费空间,为解决这一问题,出现

虚继承:在继承方式前加关键字:virtual(使_a只占一份空间,能够共用)

如:

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 FunTest()
{
	D d;
	d._a=1;
	d._b=2;
	d._c=3;
	d._d=4;
}

运行完成查看d内存:


即内存分布方式为:(即内存第一份和第三份空间是个指针,存储地址,监视查看此地址内容,存储的为分别相对于B和C自身偏移地址与相对于_a偏移地址(可把它们看成一个表))


一般派生类是虚继承时,编译器会为其合成默认构造函数,作用:将存储偏移地址的表首地址赋给其对象内存存储顺序第一个空间。

虽然虚继承解决了菱形继承的二义性和数据冗余的问题,但虚继承效率太低,每次都要访问表的偏移地址后才能访问其成员。

注意:

1.友元与继承友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员;

2.如果类中包含静态成员,无论继承多少派生类,静态成员只保存一份;

3.构造函数和析构函数不能被继承。




  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值