C++继承、virtul继承、构造与析构、类的大小、多重继承、纯虚函数

1、继承:

当B继承A时,则自动的将父类中的所有public成员继承
继承格式class Child :public Father

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:
	int age;
	char name[100];
private:
	int weight;
protected:
	int length;
};

class Child :public Father
{

};

int main()
{
	Child lily;
	lily.age = 18;
	strcpy_s(lily.name, "lily");
	
	
	return 0;
}
//运行成功,并从下图内存中可以看出来

在这里插入图片描述

1、private 不能被访问,同时不能被继承。
2、protected(受保护的)介于private之间,不能被访问但是可以被继承,并有以下两个规则
(1)该成员不能被外部访问,同private
(2)该成员可以被继承,同public
这里值得说的一点是,虽然private成不能被子类继承并访问,但是会体现在子类的内存中,只是编译器限制了访问。上图的weight 和length两个变量是存在于Child类中的lily实例中,在内存中还是体现出来了。

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:
	int age;
	char name[100];
private:
	int weight;
protected:
	int length;
};

class Child :public Father
{
public:
	int score;
};

int main()
{
	Child lily;
	lily.age = 18;
	strcpy_s(lily.name, "lily");
	lily.score = 100;
	
	
	return 0;
}

从内存上看:在子类和父类同时调用时,会把父类的放前面,子类的放后面
在这里插入图片描述

  • 继承小结

1、用class B : public class A {} 表示B继承A
2、当B继承A以后,父类的所有protected/public成员都会被继承
3、什么叫被继承?答:就是这些父类的成员就如同直接写在子类里一般
4、使用继承的好处:代码上可以简化

2、virtul继承

重写:就是子类觉得父类中的函数写的不够好,不完善有漏洞,想自己写一个这样的函数,这种情况就叫重写overwriting,实际上在子类调用这个重写的函数时,调用的就是自己重新定义的函数

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:
	int age;
	char name[100];
	void print()//父类定义的print函数
	{
		cout << "father said: you are very nice!" << endl;
	}
private:
	int weight;
protected:
	int length;
};

class Child :public Father
{
public:
	int score;
	void print()//子类重写print函数
	{
		cout << "child say: you are so strong!" << endl;
	}
};

int main()
{
	Child famale;
	//调用函数要在函数名后面加上括号
	famale.print();//调用print函数,实际上用的是重写以后的函数
	
	return 0;
}

//程序输出

child say: you are so strong!

C:\Users\source\repos\democlass\Debug\democlass.exe (进程 312880)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

如果重写的时候,还想嵌入调用一下父类的函数,就是觉得父类的还缺点东西,想先调用父类的函数,然后加点自己的东西
1、按照顺序执行。显示调用放在上面面,则先执行父类的函数,然后执行添加的函数,反之,则后执行父类的函数
显示的调用父类的函数格式:Father::print();

//将上面代码中的子类中的print函数修改一下
class Child :public Father
{
public:
	int score;
	void print()//子类重写print函数
	{
		Father::print();//显示的调用父类的函数
		cout << "child say: you are so strong!" << endl;//加上自己的东西
	}
};

//程序输出

father said: you are very nice!
child say: you are so strong!

C:\Users\source\repos\democlass\Debug\democlass.exe (进程 314980)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

问题:对于Father* ptr = new Child; p->print();即指针ptr类型是父类的,但是指针ptr指向的对象是子类的
对于父类的指针指向子类,这样是合乎情理的,并且从子类的内存也可以看出,前半段内存存储的是父类的成员,内存后半段是子类的成员。
从下面的程序可以看出,实际上调用的是父类的函数

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:
	void print()
	{
		cout << "father said: you are very nice!" << endl;
	}
};

class Child :public Father
{
public:
	void print()//子类重写print函数
	{
		cout << "child say: you are so strong!" << endl;
	}
};
int main()
{
	Child famale;
	Father* ptr = &famale;//父类的指针指向子类,
	ptr->print();//指针调用print函数(一个是父类原始函数,一个是子类重写函数),结果是调用的是父类的函数
	return 0;
}

//程序输出

father said: you are very nice!

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 316580)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

虚拟继承 virtual的出现就是为了解决上述的问题
1、当一个成员函数需要子类重写,那么在父类那里应该将其声明为virtual(有时称virtual函数位”虚函数“),virtual本身表明函数即将被子类重写
2、如果print()父类中被声明了Virtual,是调用子类的print()。这解释了virtual的作用:根据对象的实际类型,调用相应类型的函数
3、一般在父类中将函数声明为virtual, 子类就自动的继承了这个属性
4、即将被重写的函数添加virtual,是一条应该遵守的编码习惯。

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:

	virtual void print()//添加了virtual
	{
		cout << "father said: you are very nice!" << endl;
	}
};

class Child :public Father
{
public:
	void print()//子类重写print函数
	{
		cout << "child say: you are so strong!" << endl;
	}
};
int main()
{
	Child famale;
	Father* ptr = &famale;
	ptr->print();	
	return 0;
}

//程序输出

child say: you are so strong!

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 317052)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...
3、构造与析构

子类继承父类,在调用子类对象时,还是会首先调用到父类的构造和析构函数、再调用子类的构造与析构函数。那么当创建一个子类对象时(编译器动作)
1、子类对象构造时,先调用谷类的构造函数,再调用子类的构造函数
2、子类对象析构时,先调用子类的析构函数,再调用父类的析构函数。

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:
	Father()
	{
		cout << "父类被构造函数创建" << endl;
	}
	~Father()
	{
		cout << "父类被析构函数回收" << endl;
	}
};

class Child :public Father
{
public:
	Child()
	{
		cout << "子类被构造函数创建" << endl;
	}
	~Child()
	{
		cout << "子类被析构函数回收" << endl;
	}
};

int main()
{
	Child famale;
	cout << "--------------" << endl;
	return 0;
}

//程序输出

父类被构造函数创建
子类被构造函数创建
--------------
子类被析构函数回收
父类被析构函数回收

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 318208)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

3、当父类有多个构造函数时,可以显示的调用其中一个构造函数,如果没有显示调用,则调用父类的默认构造函数。

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
private:
	int length;
public:
	int age;
	Father()
	{
		cout << "父类被构造函数创建" << endl;
	}
	Father(int age_input, int length_input)
	{
		this->age = age_input;//将输入的值赋值给类中的length、age变量
		this->length = length_input;
		cout << "父类被构造函数创建,且有两个参数age、length" << endl;
	}
	~Father()
	{
		cout << "父类被析构函数回收" << endl;
	}
};

class Child :public Father
{
public:
	Child() : Father(18, 18)//显示的调用父类	的函数
	{
		cout << "子类被构造函数创建" << endl;
	}
	~Child()
	{
		cout << "子类被析构函数回收" << endl;
	}
};

int main()
{
	Child famale;
	cout << "--------------" << endl;
	cout << famale.age << endl;
	//cout << famale.length << endl;//私有成员不能被继承,不能被访问
	return 0;
}

//程序输出

父类被构造函数创建,且有两个参数age、length
子类被构造函数创建
--------------
18
子类被析构函数回收
父类被析构函数回收

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 319568)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

5、virtual析构函数,当一个类被继承时,应该将父类的析构函数声明为virtual,否则会存在一个严重的问题:当子类new出一块堆内存时,应该释放的是子类的内存而非父类的内存

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
private:
	int length;
public:
	int age;
	Father()
	{
		cout << "父类被构造函数创建" << endl;
	}
	Father(int age_input, int length_input)
	{
		this->age = age_input;//将输入的值赋值给类中的length、age变量
		this->length = length_input;
		cout << "父类被构造函数创建,且有两个参数age、length" << endl;
	}
	~Father()
	{
		cout << "父类被析构函数回收" << endl;
	}
};

class Child :public Father
{
public:
	Child()
	{
		cout << "子类被构造函数创建" << endl;
	}
	~Child()
	{
		cout << "子类被析构函数回收" << endl;
	}
};

int main()
{
	Father* ptr =new Child();
	delete ptr;//在父类析构函数没有加virtual时,释放的ptr指针属于父类的
	cout << "--------------" << endl;
	return 0;
}

//程序输出

父类被构造函数创建
子类被构造函数创建
父类被析构函数回收
--------------

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 316816)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

上面的输出可以看出,根本没掉用子类的析构函数,就是没释放子类的内存,现在在父类的析构函数前加上virtual,可以看到最终的输出时正常的

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
private:
	int length;
public:
	int age;
	Father()
	{
		cout << "父类被构造函数创建" << endl;
	}
	Father(int age_input, int length_input)
	{
		this->age = age_input;//将输入的值赋值给类中的length、age变量
		this->length = length_input;
		cout << "父类被构造函数创建,且有两个参数age、length" << endl;
	}
	virtual ~Father()
	{
		cout << "父类被析构函数回收" << endl;
	}
};

class Child :public Father
{
public:
	Child()
	{
		cout << "子类被构造函数创建" << endl;
	}
	~Child()
	{
		cout << "子类被析构函数回收" << endl;
	}
};

int main()
{
	Father* ptr =new Child();
	cout << "--------------" << endl;
	delete ptr;

	return 0;
}

//程序输出

父类被构造函数创建
子类被构造函数创建
--------------
子类被析构函数回收
父类被析构函数回收

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 320188)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

6、构造函数是不能加virtual 的,否则编译器直接报错

4、类的大小

1、类的大小由成员变量(int,string…)决定的(这与struct院的原理相同)。与函数的个数无关,即使1000个函数对它占用的内存没有影响
2、但是!如果有一个成员函数被声明成virtual,那么类的大小会增加4字节。虚拟函数格式:virtual void test1() {}

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
class Object
{
private:
	int length;//两个int型,占用8字节,两个函数不占用内存
public:
	int age;
	void test1() {}
	void test2() {}
};
int main()
{
	int len = sizeof(Object);
	cout << len << endl;
	return 0;
}

//程序输出

8

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 321492)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...

//改一下,加上virtual

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
class Object
{
public:

	virtual void test1() {}//可以看输出就只有4字节
	void test2() {}
};

int main()
{
	int len = sizeof(Object);
	cout << len << endl;
	return 0;
}

//程序输出

4

C:\Users\daicong\source\repos\democlass\Debug\democlass.exe (进程 318116)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...
5、多重继承

定义这个语法的本意:一个孩子由父有母,可以从父母那里各自继承一些特点
多重继承的结果:从所有父类(父母)中,继承他们所有可以被继承的成员(protected/public)
多重继承的问题:当父类有相同的成员时,会影响冲突。例如父母中有相同的变量名或者函数名,此时继承父母的子类调用这类变量或者函数时,就会出现不知道调用谁的问题,直接就报错了
多重继承的格式:class Child :public Father, public Mother

#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;

class Father
{
public:

	void print1()//
	{
		cout << "father said: you are very nice!" << endl;
	}
};
class Mother
{
public:

	void print2()//
	{
		cout << "mother always tells me to smile put on a happyface. u are put here to spread joy and laughter!" << endl;
	}
};
class Child :public Father, public Mother//多重继承的格式
{
public:
	void print3()//
	{
		cout << "child say: It’s funny. When I was a little boy, I toldpeople that I’m doing to be a comedian. Everyone laughed at me!" << endl;
	}
};
int main()
{
	Child famale;
	Child* ptr = &famale;
	ptr->print1();
	ptr->print2();
	return 0;
}

//程序输出

father said: you are very nice!
mother always tells me to smile put on a happyface. u are put here to spread joy and laughter!

C:\Users\daicong\source\repos\democlass\x64\Debug\democlass.exe (进程 360712)已退出,返回代码为: 0。
若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口...
6、纯虚函数

语法:
1、将成员函数声明为virtual
2、在后面加上=0
3、该函数没有函数体
例如

class Student
{
public:
	virtual void add_stu(char* name) = 0;
};

4、含有虚构函数的类称为抽象类(Abstract Class)(或称为纯虚类)上面的Student就是纯虚类
5、抽象类不能被实例化,即无法创建该对象
6、抽象类实际作用:充当接口规范的作用:凡是遵循此规范的类,就必须实现指定的函数接口,通常是一系列接口。

  • 纯虚函数作为接口这里还无法理解,后面再继续补上
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值