C++继承

一、什么是继承

    继承是面对对象复用的重要手段,继承是类型之间的关系建模。我们通过继承定义一个类,共享共有的东西实现各自不同的功能。我们看一个例子:


在这个例子中,可以看到Person类是父类,而Student类是子类;子类以公有的继承关系继承了父类的成员,也就是说父类的成员变成了子类的一部分。


那么继承关系对子类中继承到的成员有什么影响呢?

答:


  • 基类中的私有成员在派生类中不可见,即它的派生类是无法访问基类中的私有成员的。若想要访问该成员变量,就要把该成员设置为protected。
  • 不论哪种继承方式,子类可以访问基类的公有和保护成员;而不能访问基类的私有成员。
  • 使用struct默认的继承方式是public,而class默认的继承方式是private。
  • public继承是一个接口继承,保持is-a的原则。即每个父类的可用成员对子类也可用;每个子类对象也是一个父类对象。
  • protected、private继承是实现继承,保持has-a的原则。

二、赋值与转换(赋值兼容规则--public继承

    前面我们从继承关系中得到,public继承是一个接口继承,保持is-a的原则,所以就出现了子对象与父对象的赋值。它们遵循下列规则:

  • 子对象可以赋值给父类对象(切片),但父类对象不可以赋值给子类对象。


  • 父类对象的指针/引用可以指向子类对象(切片);但子类对象的指针/引用不可以指向父类对象。
        Person p;
	Student s;
	//子类对象可以赋值给父类对象
	p = s;
	
	//父类对象的指针可以指向子类对象
	Person* p1;
	p1 = &s;

	//父类对象的引用可以指向子类对象
	Person& p2 = s;

三、继承体系中的作用域

  • 在继承体系中,子类与父类都有独立的作用域;
  • 当子类和父类有同名的成员时,子类会屏蔽对父类该成员的直接访问。这种现象叫做隐藏/重定义。
class Person
{
public:
	void show()
	{
		cout << _name << endl;
	}
public:
	string _name;
	int _num;
};
class Student : public Person
{
public:
	int _num;
};
int main()
{
	Person p;
	p._num = 10;//访问父类成员
	Student s;
	s._num = 20;//访问子类成员
	s.Person::_num = 30;  //若想访问父类成员,可以指定作用域
	return 0;
}

练习1:在下面的代码中,会发生什么?

class Person
{
public:
	void show()
	{
		cout << _name << endl;
	}
	void f1()
	{}
protected:
	string _name;
	int _num;
};
class Student : public Person
{
public:
	void f1(int)
	{}
protected:
	int _num;
};
int main()
{
	Student s;
	s.f1();
	return 0;
}

A f1()构成重载      B f1()构成隐藏      C 代码编不过       D 以上都不对

答:BC

首先,子类与父类中有同名函数f1,构成隐藏。其次子类对象调用成员函数f1调的是子类的f1(该f1有参),我们在调用时未能传入参数,所以调不到该成员函数,代码编不过。

练习2:运行下面的代码,会发生什么?

class Student
{
public:
	void f1()
	{
		cout << "f1()" << endl;
	}
protected:
	int _num;
};
int main()
{
	Student* s  = NULL;
	s->f1();
	return 0;
}

A 代码编不过     B 可以编译通过,但程序会崩溃     C 可以编译通过,并且正常输出         D 以上都不对

答:C


四、派生类的默认成员函数

        我们知道,类有6个默认成员函数,在程序员没有定义的时候,系统会自动生成。那么派生类呢?

  •         派生类的默认成员函数如果没有定义,系统会默认合成。
class Person
{
public:
	Person()
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
	{
		cout << "Person(const Person& p)" << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person& operator=(const Person& p)" << endl;
		return *this;
	}

protected:
	string _name;
	int _num;
};
class Student : public Person 
{
protected:
	int _num;
};
void test()
{
	Student s1;
	Student s2(s1);
	s2 = s1;
}
int main()
{
	test();
	system("pause");
	return 0;
}

  • 在定义子类的构造函数和赋值运算符的重载函数时,需要显示的调用父类的构造和赋值函数;
  • 但在定义子类的析构函数是,不需要显示调用父类的析构。子类的析构函数调用完后,出了作用域,系统会自动调用父类的析构函数。
class Student : public Person 
{
public:
	Student(int num = 010)
		:Person()
		, _num(num)
	{}
	Student(const Student& s)
		:Person()
		,_num(s._num)
	{}
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}
	~Student()
	{}
protected:
	int _num;
};

五、菱形继承

单继承与多继承:

        单继承:一个子类只有一个直接父类

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

菱形继承:

class AA
{
protected:
	int _a;
};
class BB : public AA
{
protected:
	int _b;
};
class CC : public AA
{
protected:
	int _c;
};
class DD : public BB, public CC
{
protected:
	int _d;
};

菱形继承的对象模型:


可以看到_a在类DD的对象中保存了2份,使得菱形继承存在二义性和数据冗余的问题。二义性我们可以通过制定作用域来解决,可是数据冗余呢?这时候,可以通过虚继承的方式来解决二义性和数据冗余的问题。

虚继承:

那么虚继承是如何解决这些问题的呢?在vs2013下探索:

定义四个类AA,BB,CC,DD,BB类和CC类继承了AA类,DD类继承了BB类和CC类。

class AA
{
public:
	AA(int a = 1)
		:_a(a)
	{}
protected:
	int _a;
};
class BB : virtual public AA
{
public:
	BB(int b = 2)
		:_b(b)
	{}
protected:
	int _b;
};
class CC : virtual public AA
{
public:
	CC(int c = 3)
		:_c(c)
	{}
protected:
	int _c;
};
class DD : public BB, public CC
{
public:
	DD(int d = 4)
		:_d(d)
	{}
protected:
	int _d;
};

针对上面的代码,在加关键字virtual时和不加关键字virtual时,分别探索内存:


        上图的左边展示的是不加virtual时的内存分配,右边展示的是虚继承的内存。

        可以看出虚继承将AA中的成员放在了最下面,在原本放AA类中成员的地方存放了一个指针,该指针指向的位置存放的是一个偏移地址,这样就可以通过这个偏移地址找到AA类中的成员。同时因为AA类中的成员在DD类中只保存了一份,也就解决了数据冗余和二义性的问题。

六、其它注意事项

  • 友元关系不能继承,也就是说基类的友元不能访问派生类的私有和保护成员。
  • 基类定义了static成员后,整个继承体系中只有一个该成员;无论派生出多少个子类,都只有一个该static成员实例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值