1.继承
将父类的共有内容ji继承到子类。
1.1.继承的基本语法
class 子类:继承方式 + 父类
class Student{};//父类
class Studentson:public Student{};//子类继承父类,public是一个继承方式
1.2.继承方式
继承方式有三种:公有继承( public )、保护继承( protected )、私有继承( private )
公有继承:(1)父类的公有成员继承到子类还是公有成员
(2)父类的保护成员继承到子类还是保护成员
保护成员:父类的公有成员和保护成员继承到子类,在子类中都是保护成员
私有继承:父类的公有成员和保护成员继承到子类,在子类中都是私有成员
注意:父类的私有成员虽然也会被继承,但是会被编译器隐藏,无法从子类访问父类的私有成员
1.3.继承中的对象模型
class Base
{
public:
int m_A;
int m_B;
private:
int m_C;
}
class Son:public Base//将父类Base的成员继承到子类Son中
{
int m_D;
}
void test()
{
cout << sizeof(Son) << endl;//计算子类Son的字节长度
}
int main()
{
test();
system("pause");
return 0;
}
我们已经知道父类中的私有成员被继承到子类中,只是被隐藏了,所以子类Son中有4个int变量,所以运行结果为16
1.4. 继承中的结构与析构顺序
构造函数的顺序:先运行父类的构造函数,再运行子类的构造函数
析构函数的顺序:先运行子类的析构函数,再运行子类的析构函数
1.5.同名成员处理
父类和子类的成员可能出现同名情况,可以使用作用域(类名::)解决
(1)直接访问调用时,编译器默认调用子类的成员
(2)使用作用域强调时,则调用作用域内的成员
class Person
{
public:
int m_Age;
}
class Son:public Person{}
int main()
{
m_Age=18;//直接调用,默认子类Son的成员
Person::m_Age=20;//作用域强调Person的成员
cout<<m_Age<<endl;//18
cout<<Person::m_Age<<endl;//20
system("pause");
return 0;
}
1.6.多继承
一个儿子可以认多个爹
因为继承时容易出现同名情况,所以在开发过程中一般不建议多接触
语法: class 子类:继承方式 父类1,继承方式 父类2 ......
class Person1{};//父类1
class Person2{};//父类2
class Person3{};//父类3
class Son:public Person1,protected Person2,private Person3;//多继承
1.7.菱形继承(钻石继承)
两个派生类继承同一个基类
又有某个类同时继承两个派生类
图中B和C都继承D的成员, A同时继承B和C的成员(多继承)
因为继承过程中容易出现成员同名情况,因此谨慎使用
2.多态
对事物或者人做大致的描述作为父类,在子类中细化不同属性和行为,以达到多种形态的目的。
例如买火车票,同样是人买票,这样大致的描述作为父类,但是成人、学生和儿童买票的价格不一样,因此对人做不同的描述导致票价也不一样,出现多种形态。
多态分为两大类:
(1)静态多态:函数重载和运算符重载,复用函数名
(2)动态多态:派生类和虚函数运行时多态
静态多态和动态多态的区别:
(1)静态多态的函数在编译阶段就已经绑定函数地址
(2)动态多态的函数在运行阶段才确定函数地址
多态的要求:
(1)被调用的函数必须是虚函数,子类对父类的函数进行重写
(2)父类指针或者引用调用虚函数
多态的原理:
父类的虚函数内部结构是一个虚函数指针指向虚函数表,虚函数表内部记录父类定义域下的函数地址,子类继承父类以后内部结构也与父类结构相同,当子类重写父类虚函数时,其虚函数表内部地址会被替换成子类虚函数地址。使用父类指针或者引用指向子类即发生多态,虚函数的地址在运行阶段确定。
虚函数重写之后,可以实现不同的对象,有不同的实现方法,展现不同的效果。
2.1.多态的语法
例如:
class Person
{
public:
virtual void func()//虚函数
{
cout<<"要工作"<<endl;
}
}
class Student:Public Person
{
public;
void func()//子类重写父类的虚函数
{
cout<<"要学习"<<endl;
}
}
void activity(Person &person)//父类引用调用虚函数
{
person.func();
}
int main()
{
Person p;
activity(p);
Student s;
activity(s);
system("pause");
return 0;
}
2.2.多态的优点
(1)组织结构清晰
(2)可读性强
(3)利于前期和后期的扩展和维护
例如:在设置计算器用于计算两个数的运算时,我们可以先设置一个计算器类作为父类,写一个空的虚函数声明是用于获取结果。之后设置不同算法(加减乘除)作为不同子类,重写虚函数,在虚函数中设置各算法的计算过程。之后不管哪种算法出错或者要编写新的算法,只要找到相关子类更改或者编写新的算法子类即可,不需要更改之前的编码。这就是多态的好处。
2.3.纯虚函数、虚函数与抽象类
在多态中,父类中虚函数的实现通常是毫无意义的,因此可以改为纯虚函数
纯虚函数与虚函数的区别:
(1)纯虚函数无法实例化对象,虚函数可以
(2)纯虚函数:virtual 函数名(参数列表)=0;
(3)虚函数:virtual 函数名(参数列表){};
(4)如果有纯虚函数,那么该父类就为抽象类
纯虚函数与虚函数的共同点:
(1)子类都需要重写父类的虚函数或者纯虚函数,否则为抽象类
class Animal
{
public:
virtual dospeak()=0;//纯虚函数
virtual dospeak(){};//虚函数
}//以上函数都是dospeak,因此要么是纯虚函数,要么是虚函数
2.4.虚析构和纯虚析构
在多态使用时,如果子类有属性开辟到堆区,那么父类的指针在释放时无法调用到子类的析构函数
这时可以将父类的析构函数改为虚析构函数或者纯虚析构函数
虚析构与纯虚析构的共同点:
(1)都解决父类指针释放子类对象
(2)都有具体的函数实现
虚析构与纯虚析构的区别:
(1)只要是纯虚析构,那么该类属于抽象类,无法实例化对象
class Person
{
public:
virtual ~Person()=0;//纯虚析构
virtual ~Person(){};//虚析构
}
Person::~Person(){};//虚析构类外进行具体函数实现
以下是在做题过程中遇到的问题和想法:
1.在创建类时,当成员中有指针时,在后续调用过程中经常遇到 “不能将char* 类型的对象的值分配到char的实体” 这种问题。那么在遇到这种情况时,我们可以使用const来声明指针成员
#include<iostream>
using namespace std;
int main()
{
const char *p = new char[7];
p = "laylay";
cout << "the thing we stored in point p is \"" << p << "\"."<<endl;
cout << "the position is " << (int *)p << "." << endl;
return 0;
}
在声明p指针指向char数组的时候,前面要加const 不然就会报错,字符串字面值是常量,以这种方式使用const意味着可以用p来访问字符串,但不能修改它
2.在一次做题中发现并学习到了&的作用
class Person
{
public:
string& Name;//在定义属性时已经使用了&,这是错误的
Student(string& name):Name(name){};
}
在这里&是作为引用符号,当&在类型声明中使用时,它表示引用
int a = 10;
int& ref = a; // ref 是 a 的引用
我个人看法是,当&作为要用符号时,前提是已经有一个可以引用的对象,在定义Name属性时,因为没有可以引用的对象,在定义时就引用会导致引用到空对象,导致后续输出的结果为空。
以上是作者在学习中写下的一些个人疑惑和见解以及对自己所学的简单概括,可能有不足或者需要补充的地方,如果有发现问题,可以在评论区指出更正,谢谢