c++继承

什么是继承
定义:

继承(inheritance)机制是面向对象程序设计中使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生的新类,称派生类(或子类),被继承的类称基类(或父类)。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。之前接触的复用都是函数复用,继承是类设计层次的复用。

好吧,光看也看不出个啥,还是直接上代码吧
 

class human {//定义了一个父类,名字叫human
public:
	string name = "小明";//父类里面定义了一个string类型的和一个int类型
	int age = 18;
};
class student:public human {//定义了一个以public方式继承父类的子类student
public:
	int schoolnum = 666;//在父类的name和age的基础上增加了一个schoolnum
	void print()
	{
		cout << name << endl << age << endl << schoolnum << endl;//输出
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

继承的格式

class 新类的名字:继承方式 继承类的名字{};

class student:public human{};
//student是新类的名字,public是继承方式,human是要继承的类
//意思就是说我定义了一个名叫 student的类 以public的方式 来继承你human

我们这里对于student和human就有两种叫法。

一种是教科书里面的基类(human)和派生类(student)。

我本人喜欢第二种父类(human)和子类(student)。毕竟感觉就像继承家产一样。

三:继承后的子类成员访问权限

在这里插入图片描述

继承的总结:
1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。
 

子类和父类(基类和派生类)

1.子类和父类的相互赋值:

class human {//父类
public:
	string name = "小明";
	int age = 18;
};
class student:public human {//子类
public:
	int schoolnum = 666;
};
int main()
{
	student st;
	human hm;
	hm = st;//将子类赋值给父类
	st = hm;//将父类赋值给子类
	return 0;
}

就会出现这样的结果:

 在这里我们引入一个叫做切片原则的东西

 

 

因为父类中没有schoolnum,所以父类接收子类传过来的name和age之后,多余的schoolnum就不管了。但是如果父类传给子类,少传一个,所以会报错。

同时我们给出三种赋值方式

三种方式的赋值:

一:=符号

student st;//子类
	human hm;//父类
	hm = st;

二:引用

student st;//子类
	human& hm=st;父类

 

 三:指针

student st;//子类
	human* hm=&st;//父类

考虑同名的成员变量 

在有些时候,父类和子类中出现了同一个成员变量,如下name

class human {
public:
	string name = "小明";
};
class student:public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这个时候编译器是以子类为优先

但是如果我们就是想要访问父类的该成员变量,就需要加上修饰

void print()
	{
		cout << human::name << endl;
	}

 

同名成员函数

如下,同样一个函数print在父类和子类中都存在

class human {
public:
	string name = "小明";
	void print()
	{
		cout << name << endl;
	}
};
class student :public human {
public:
	string name = "小红";
	void print()
	{
		cout << name << endl;
	}
};
int main()
{
	student st;
	st.print();
	return 0;
}

这就构成了隐藏。(函数重载是在同一个作用域,这里父类和子类是两个作用域)

函数的隐藏,编译器会默认调用子类中匹配的函数,如果没有编译器就会报错

上面的结果如下

虽然成员函数的隐藏,只需要函数名相同就构成隐藏,对参数列表没有要求。

 但是我们修改一下子类的函数

 

void print(int x)//我们对子类的print函数加入一个参数
	{
		cout << name << endl;
	}

 这是因为编译器默认调用子类中print函数,但是子类中唯一的print函数有一个默认的参数,所以编译器无法找到匹配的print函数,所以就会报错。

子类中默认的成员函数

1.构造函数

编译器会默认先调用父类的构造函数,再调用子类的构造函数,如下

class human {
public:
	human(string name = "小明")//先调用:父类默认构造调用一个print打印name
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};
 
 
class student :public human {//后调用:子类默认构造调用一个print打印name和age
public:
	student(string name,int age)
		:_age(age)
	{
		cout << name << endl<<age<<endl;
	}
protected:
	int _age;
};
 
 
int main()
{
	student st("小红", 18);
	return 0;
}

可以看到,编译器先调用了父类的,打印出了小明,然后再次调用了子类的打印出了小红和age。

所以说请务必保证父类构造有效,假如父类失效

human(string name)//你这里不传值,那么就不能完成初始化,相当于父类失效
		:_name(name)
	{
		cout << name << endl;
	}

 那么就必须在子类中给父类构造赋值

student(string name,int age)
		:_age(age)
		, human(name)//新增,子类以自己的name给父类的析构中的name赋值,age和name的顺序随意变动

实在不行就把父类的构造删了,反正编译器也默认会生成的

析构函数

析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数。

验证如下:

我们在原有的代码上,加入两个析构函数

class human {
public:
	human(string name = "小明")
		:_name(name)
	{}
	~human()
	{
		cout << "我是父类" << endl;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name,int a = 20)
		:age(a)
	{}
	~student()
		
	{
		cout <<"我是子类"<< endl;
	}
protected:
	int age;
};
int main()
{
	student st("小明", 18);
	return 0;
}

结果如下: 

 

所以说

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

千万不要在子类中调用父类的析构

如果是指针类型,那么同一块区域被析构两次就会造成野指针的问题。

拷贝构造

子类中调用父类的拷贝构造时,直接传入子类对象即可,父类的拷贝构造会通过“切片”拿到父类的那一部分。

class human {
public:
	human(string name="小明")
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};
class student:public human {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	student(student& s)
		:human(s)//直接将st传过来通过切片拿到父类中的值
		,_age(s._age)//拿除了父类之外的值
	{
		cout << s._age << endl<<s._name<<endl;
	}
protected:
	int _age;
};
int main()
{
	student st("小红",18);
	student st2(st);
	return 0;
}

结果如下:

 

赋值运算符重载

子类的operator=必须要显式调用父类的operator=完成父类的赋值。

因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值要直接修饰限定父类

class human {
public:
	human(string name = "小明")
		:_name(name)
	{
	}
	human& operator=(const human& p)
	{
		if (this != &p)
		{
			cout << "调用父类" << endl;
			_name = p._name;
		}
		return *this;
	}
protected:
	string _name;
};
class student :public human {
public:
	student(string name, int age)
		:_age(age)
	{
	}
	student(student& s)
		:human(s)
		, _age(s._age)
	{
	}
	student& operator=(const student& s)
	{
		if (this != &s)
		{
			cout << "调用了子类" << endl;
			human::operator=(s);//必须调用父类运算符
			_age = s._age;
			_name = s._name;
		}
		return *this;
	}
protected:
	int _age;
};
int main()
{
	student st("小红", 18);
	student st2(st);
	student st3("小刚", 16);
	st = st3;
	return 0;
}

单继承和多继承

单继承:

一个子类只有一个直接父类的继承关系。

多继承:

一个子类有两个或以上直接父类的继承关系。

菱形继承

 

class A {
public:
	string name;
};
class B :public A {
public:
	int age;
};
class C :public A {
public:
	string sex;
};
class D :public B, public C {
public:
	int id;
};
int main()
{
	D student;
	student.name = "小明";
	student.age = 18;
	student.sex = "男";
	student.id = 666;
	return 0;
}

 然后就是报错

 

因为这里的name,同时存在B和C中,所以D不知道继承B的name还是C中的name

这也就是引出了代码冗余和二义性的问题。

所以我们有两种解决方法

解决方法一:

加修饰限定

student.B::name = "小明";

这里我们指定继承B中的name,就不会冲突了

解决方法二:

虚继承:在继承方式前加上virtual。

class B :virtual  public A {
public:
	int age;
};
class C :virtual public A {
public:
	string sex;
};

单继承和多继承的总结:

多继承是C++复杂的一个体现。有了多继承,就存在菱形继承,为了解决菱形继承,又出现了菱形虚拟继承,其底层实现又很复杂。所以一般不建议设计出多继承,一定不要设计出菱形继承。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值