C++继承

Posted 蚍蜉撼树谈何易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++继承相关的知识,希望对你有一定的参考价值。

继承概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强
调用私有继承的时机:
若D类以私有继承方式继承B类的话,意味着只有基类的公有部分/保护方法被继承,而类外不可访问,意思是D对象根据B对象实现而得,对外部统一保持私有。

基类和派生类对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
基类对象不能赋值给派生类对象
基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-Time Type Information)的dynamic_cast 来进行识别后进行安全转换。

同名隐藏

同名隐藏—重定义
基类如果和子类具有相同名称的成员?public继承方式

  1. 成员变量同名
  2. 成员方法
    如果通过子类对象调用相同名称的成员时,优先访问子类的,基类同名的成员永远无法通过子类对象直接调用到,相当于子类同名成员将基类的同名成员隐藏了
    如果想要同名子类对象访问基类中同名的成员,只需在成员前加上基类名称::
    如果在子类成员函数中,想要访问基类同名的成员,只需在基类成员前加上基类名称::
// 同名隐藏---重定义
// 基类如果和子类巨头相同名称的成员?
// 1. 成员变量同名
// 2. 成员方法
// 如果通过子类对象调用相同名称的成员时,优先访问子类的,基类同名的成员永远无法通过
// 子类对象直接调用到,相当于子类同名成员将基类的同名成员隐藏了
// 如果想要同名子类对象访问基类中同名的成员,只需在成员前加上基类名称::
// 如果在子类成员函数中,想要访问基类同名的成员,只需在基类成员前加上基类名称::
class Base
{
public:
	void SetBase(int b)
	{
		_b = b;
	}

	void fun()
	{
		cout << "Base::fun()" << endl;
	}

	//protected:
public:
	int _b;
	char _c;
};

class Derived : public Base
{
public:
	void SetDeirved(int b, int d)
	{
		_c = 100;
		Base::_c = 100;
		__super::_c = 100;
		SetBase(b);
		_d = d;
	}

	void fun(int a)
	{
		cout << "Derived::fun(int)" << endl;
	}

	//protected:
public:
	int _d;
	int _c;
};


int main()
{

	Base b;
	b.SetBase(10);

	Derived d;
	d.SetDeirved(100, 200);

	/*
	如果子类和基类中具有相同名称的成员变量时,不管成员变量的类型是否相同,
	都优先访问子类的同名成员变量
	不能通过子类对象直接访问子类和父类中同名的成员变量,就相当与子类同名的成员变量将基类的同名成员变量隐藏了
	*/
	d._c = 'A';   // d对象中有两个_c

	// 有些情况下可能需要通过子类对象访问基类中同名的成员变量
	d.Base::_c = 'B';


	// d.fun();   // 编译报错
	d.fun(10);
	d.Base::fun();
	return 0;
}

子类构造与析构

1.若基类没有显式给出构造函数的话(或者存在,但是是默认构造函数),则子类可以不用显式给出构造函数
2.若基类显式给出构造函数的话(但是不是默认构造函数的话),则子类必须显式给出构造函数来初始化基类的变量,且不能在子类初始化列表中直接初始化基类中成员变量的值,只可以通过基类构造方法调用构造。
3.若声明一个子类对象,则去调用子类构造函数时内部会先去调用基类构造函数。

class Base
{
public:
	Base(int b)
		: _b(b)
	{}


	// 拷贝构造函数
	Base(const Base& b)
		: _b(b._b)
	{}

	Base& operator=(const Base& b)
	{
		if (this != &b)
		{
			_b = b._b;
		}

		return *this;
	}

	~Base()
	{
		cout << "Base::~Base()" << endl;
	}
protected:
	int _b;
};

class Derived : public Base
{
public:
	Derived(int b, int d)
		: Base(b)
		, _d(d)
	{}

	Derived(const Derived& d)
		: Base(d)
		, _d(d._d)
	{}

	Derived& operator=(const Derived& d)
	{
		if (this != &d)
		{
			// 给基类部分成员赋值
			// *this = d;  不是这么调用
			Base::operator=(d);

			// 给子类部分成员赋值
			_d = d._d;
		}

		return *this;
	}

	~Derived()
	{
		cout << "Derived::~Derived()" << endl;

		// 编译器在编译代码时,会在子类析构函数最后一条有效语句之后,
		// 条件调用基类析构函数的语句
		// call Base::~Base();
	}
protected:
	int _d;
};


void TestDerived()
{
	Derived d(10, 20);
}

int main()
{
	//TestDerived();

	Derived d(10, 20);
	Derived d1(d);
	Derived d2(30, 40);

	d1 = d2;
	return 0;
}

基类中友元与static变量继承

1,基类中static变量,子类会继承下来,但是与基类对象共用一份static变量。
2.友元关系是不可以被继承的

class Base
{
public:
	int _b;
	static int n;
	friend void TestFriend();
private:
	int _a;
};



int Base::n = 0;

class Derived : public Base
{
public:
	int _d;
private:
	int _c;
};



void TestFriend()
{
	Base b;
	b._b = 10;
	b._a = 20;

	Derived d;
	//d._c = 10;//error
}

int main()
{
	cout << sizeof(Derived) << endl;
	Derived::n = 10;
	cout << &Base::n << "=" << &Derived::n << endl;
	return 0;
}

派生类的默认成员函数

派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
派生类的operator=必须要调用基类的operator=完成基类的复制。
派生类的析构函数会在被调用**完成后自动调用基类的析构函数清理基类成员。**因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
派生类对象初始化先调用基类构造再调派生类构造。
派生类对象析构清理先调用派生类析构再调基类的析构。

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
	Person(const char* name = "张三")
		:_name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		:_name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;   //姓名
};

class Student :public Person
{
public:
	//s1()
	Student(const char* name, int num)   //派生类的构造函数调用父类的构造函数初始化
		:Person(name)
		,_num(num)
	{
		cout << "Student()" << endl;
	}
	//s2(s1)
	Student(const Student& s)
		:Person(s)
		, _num(s._num)
	{
		cout << "Student(const Student& s)" << endl;
	}
	//s1=s2;
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			//派生类的operator=与基类的operator=构成同名函数会发生隐藏
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}

	~Student()
	{
		//Person::~Person();
		cout << "~Student()" << endl;
	}
protected:
	int _num;  //学号
};
int main()
{
	Student s1("哈士奇", 101);
	Student s2(s1);
	//Student s3("阿拉", 102);
	//s1 = s3;
	return 0;
}

注:构造函数/析构函数私有的类不可被继承

菱形继承


菱形继承问题:1.数据冗余问题,继承同一份资源多次。
2.二义性问题。

面试总结

什么是菱形继承?菱形继承的问题是什么?

什么是菱形虚拟继承?如何解决数据冗余和二义性的?

继承和组合的区别?什么时候用继承?什么时候用组合?

以上是关于C++继承的主要内容,如果未能解决你的问题,请参考以下文章

以下代码片段 C++ 的说明

C++ 代码片段执行

此 Canon SDK C++ 代码片段的等效 C# 代码是啥?

C++ 代码片段(积累)

C++ - 指针和“智能指针”

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情