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、抽象类实际作用:充当接口规范的作用:凡是遵循此规范的类,就必须实现指定的函数接口,通常是一系列接口。
- 纯虚函数作为接口这里还无法理解,后面再继续补上