您好,欢迎来到化拓教育网。
搜索
您的当前位置:首页【C++第十五章】继承

【C++第十五章】继承

来源:化拓教育网

【C++第十五章】继承

定义🧐

继承方式和访问限定符🧐

  继承方式和访问限定符都有三种,public、protected以及private,其中public和private在之前就介绍过,如果是protected那么只有子类能访问,外界无法访问。子类继承方式表示继承的类是什么属性,子类继承方式不写默认为私有。

  父类private成员在子类中无论以什么方式继承都是不可见的,其含义是私有成员还是被继承到子类中,但是在语法上了派生类对象不管在类里面还是类外面都无法访问。

  继承方式大多以public为主,并且不提倡使用protected和private继承,因为继承下来的成员都只能在子类的类里面使用,实际中维护性不强。

父子类赋值兼容规则🧐

  公有继承被看做相近类型,可以进行隐式类型转换

  隐式类型转换会生成临时变量,但是在公有继承下,父类与子类是is-a(你就是我)的关系,子类对象赋值给父类对象/父类指针/父类引用,我们认为是天然的,中间不产生临时对象,称为父子类赋值兼容规则(也叫切割/切片)

父子成员同名🧐

  当父类和子类都有同一个成员时,会优先使用自己的成员,如果我们要使用父类成员可以加上父类的域。由此得知,继承中同名函数会构成隐藏,不管参数和返回值,所以尽量不要使用同名函数。

子类的默认成员函数🧐

  我们以下面代码为例:

class Person
{
public:
	Person(const char* name, int age)
		:_name(name)
		,_age(age)
	{
		cout << "Person构造函数" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{
		cout << "Person拷贝构造" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person赋值重载" << endl;
		if (&p != this)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
	void Print()
	{
		cout << _age << " " << _name << endl;
	}
	~Person()
	{
		cout << "Person析构函数" << endl;
	}
protected:
	int _age = 18;
	string _name = "Mick";
};

class Teacher : public Person
{
public:
	void Print()
	{
		cout << Person::_age << endl;
	}
protected:
	int _age = 10;
	int _jobid = 0;
};

  子类不写默认构造函数,那么会去调用父类的默认构造函数

  如果要显示初始化子类,且要用父类成员时,需要把父类当成完整对象,复用父类成员完成初始化。

  拷贝构造需要在子类中取到父类成员,我们对子类切片即可。

  同理,赋值重载也是这种方法。

  子类析构要特殊一点,因为多态的存在,析构函数名会被统一处理成destructor,所以父子类析构函数会构成隐藏,当我们调用父类析构时需要加上域。并且,构造时是先构造父类再构造子类,析构则是先子后父,原因在于父类先析构了,但子类依然能够访问父类,那么就会存在风险,所以要先释放子类,父类访问不了子类,则不存在该风险,编译器为了确保安全,所以只需要析构子类,父类会自动帮我们析构

  完整代码如下,可以自己调试学习:

#include<iostream>
using namespace std;

class Person
{
public:
	Person(const char* name, int age)
		:_name(name)
		,_age(age)
	{
		cout << "Person构造函数" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{
		cout << "Person拷贝构造" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person赋值重载" << endl;
		if (&p != this)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
	void Print()
	{
		cout << _age << " " << _name << endl;
	}
	~Person()
	{
		cout << "Person析构函数" << endl;
	}
protected:
	int _age = 18;
	string _name = "Mick";
};

class Teacher : public Person
{
public:
	Teacher(const char* name,int age,int id)
		:Person(name,age)
		,_age(age)
		,_jobid(id)
	{
		cout << name << " " << _age << " " << _jobid << endl;
	}
	Teacher(const Teacher& t)
		:Person(t) //切片
		,_age(t._age)
		,_jobid(t._jobid)
	{
		cout << "Teacher拷贝构造" << endl;
	}
	Teacher& operator=(const Teacher& s)
	{
		if (&s != this)
		{
			Person::operator=(s); //这里要指定,不然会发生隐藏
			_jobid = s._jobid;
			_age = s._age;
		}
		return *this;
	}
	//由于多态原因,析构函数统一会被处理成destructor
	//父子类的析构函数构成隐藏
	//为了保证析构安全,先子后父
	//父类析构函数不需要显示调用,子类析构函数会自动调用父类
	~Teacher()
	{
		// Person::~Person();
		cout << "Teacher析构" << endl;
	}
	void Print()
	{
		cout << _name << " " << _age << " " << _jobid << endl;
	}
private:
protected:
	int _age = 10;
	int _jobid = 0;
};

int main()
{
	Teacher t("张三",18,20023030);
	Teacher t1("李四", 10, 20043030);
	t = t1;
	t.Print();
	return 0;
}

关键字final🧐

  在C++11之前,我们想要一个类不能被继承,可以将构造函数私有化,而在C++11后,我们可以在父类加上final来禁止继承

静态成员继承🧐

  静态成员继承的是使用权,它存在静态区中,属于整个类。

多继承🧐

  多继承格式如下:

class student
{
    //成员
};

class Teacher
{
    //成员
};

//多继承格式
class Assistant : public student, public Teacher
{
    //子类成员
}

  但是在C++中可能会出现菱形继承,assistant会拥有两份person成员。

  菱形继承不仅会造成代码冗余,并且会出现二义性,编译器无法识别我们想调用哪个类的成员,必须要加上类域才能使用。

  解决方法是在冗余的继承类加上virtual(虚继承)

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;
};

int main()
{
 D d;
 d.B::_a = 1;
 d.C::_a = 2;
 d._b = 3;
 d._c = 4;
 d._d = 5;
 return 0;
}

  我们在内存窗口中看一下对象d的存储情况,发现了数据冗余,A出现了两次。

组合与继承🧐

  如下代码,B类和D类大小一样,但一个是继承一个是组合,区别在于继承权限更大,组合只能使用公有的成员,且在类外不能直接调用成员函数,从可维护性来看,组合更好,因为组合依赖关系不强,耦合度低,有助于保持每个类被封装,但从便利角度来看,继承更好用。

#include<iostream>
using namespace std;

class A
{
public:
	void func()
	{

	}
protected:
	int _a;
};
class B : public A
{
public:
	void f()
	{
		func(); //直接调用
		_a = 1; //可以访问_
	}
protected:
	int _b;
};
class C
{
public:
	void func()
	{

	}
protected:
	int _c;
};
class D
{
public:
	void f()
	{
		_cc.func(); //间接调用
		_c = 1; //不可以访问
	}
protected:
	int _d;
	C _cc;
};
int main()
{
	B bb;
	D dd;
	bb.func(); //可以调用
	dd.func(); //无法调用
	return 0;
}

小试牛刀🧐

题目一:

题目二:

  答案:D的构造函数会先走初始化列表,而继承顺序决定声明顺序,并且这里是虚继承(如果没有虚继承A会调用两次,A自己本身不需要走初始化列表),A只调用一次,所以选A

总结🧐

结尾👍

  以上便是继承的全部内容,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo9.cn 版权所有 赣ICP备2023008801号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务