四川省省建设厅网站,重庆百度seo整站优化,中小企业网站制作推广方法,做视频网站流量费高吗目录 派生类的默认成员函数①派生类的构造函数②派生类的拷贝构造函数③派生类的赋值构造④派生类的析构函数 继承与友元继承与静态成员 前言 我们在上一章讲解了: 继承三部曲#xff0c;本篇基于上次的基础继续深入了解继承的相关知识#xff0c;欢迎大家和我一起学习继承 派… 目录 派生类的默认成员函数①派生类的构造函数②派生类的拷贝构造函数③派生类的赋值构造④派生类的析构函数 继承与友元继承与静态成员 前言 我们在上一章讲解了: 继承三部曲本篇基于上次的基础继续深入了解继承的相关知识欢迎大家和我一起学习继承 派生类的默认成员函数 6个默认成员函数“默认”的意思就是指我们不写编译器会变我们自动生成一个那么在派生类中这几个成员函数是如何生成的呢
①派生类的构造函数 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。 1.1、有默认构造的情况
class Person
{
public:Person(const char* namehhh):_name(name){cout Person() endl;}
protected:string _name;
};class Student :public Person
{
protected:int _stuid;
};int main()
{Student s;return 0;
}在有默认构造的情况下Student s创建的派生类s对象会自动调用自己的默认构造它里面的内置类型_stuid不做处理但是继承父类里面的_name会被当做一个Person类的对象也就是自定义类型成员_name会调用Person的默认构造来初始化自己
1.2、没有默认构造的情况
这个基类我们没有写无参默认构造但是我们写了带参的默认构造所以编译器不会为我们生成默认无参的构造函数
class Person
{
public:Person(const char* name):_name(name){cout Person() endl;}
protected:string _name;
};在派生类中如何初始化成员 下面是❌示范
class Student:public Person
{
public:Student(int stuid1001,const char* namepeter):_name(name)//这里这样写会直接报错有红色波浪线的那种只是这里看不出来,_stuid(stuid){cout Student() endl;}
protected:int _stuid;
};我们不能够直接拿父类的成员出来单独进行初始化父类的初始化要看作是一个整体可以理解为父类的成员是隐藏在子类中的自定义类型成员然而在初始化时自定义类型需要走它的默认构造即使我们不在初始化列表显示写它也会走初始化列表然而这里我们没有默认的Person构造函数所以我们需要显示调用这个构造我们看下面的正确写法 ✔写法 我们显示调用Person类的构造来初始化从Person那边继承过来的成员变量就行了这里就充分体现了父类的初始化要看成是一个整体
class Student:public Person
{
public:Student(int stuid1001,const char* namepeter):Person(name)//如果这个构造函数有多个参数那我们就传多个参数看具体构造函数来传参,_stuid(stuid){cout Student() endl;}
protected://Person _p;//父类的成员就好似这样需要我们走Person的构造不能单独初始化里面的成员int _stuid;
};当然除了上面这种写法我们还可以去父类自己写一个无参默认构造这里我就不做演示了如果不会可以评论我再进行补充✍
总结
派生类的初始化父类自己内置类型和自定义类型父类调用父类的构造函数初始化自己这里体现了复用在派生类中要把父类成员当成一个整体的自定义类型成员子类的其他成员和以前一样对内置类型不做处理对自定义类型去调它的默认构造
形象的理解一下父类是一个整体的概念
class BB
{
public:BB(int num,const char* name):_p(name)//会在初始化列表调用Person的构造函数来初始name,_num(num){}
private:Person _p;//这里显示有Person的对象int _num;
};②派生类的拷贝构造函数 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。 以下面这个父类来举例
class Person
{
public:Person(const char* name hhh):_name(name){cout Person() endl;}Person(const Person p)//拷贝构造:_name(p._name){}
protected:string _name;
};
2.1、子类中不显示写拷贝构造就使用编译器默认生成的拷贝构造
class Student :public Person
{
public:Student(int num1001,const char* namepeter):_num(num),Person(name){}
protected:int _num;
};int main()
{Student s(1002, okk);Student s1(s);return 0;
}在实现用s拷贝s1时派生类的拷贝构造和上面我们所说的默认构造有异曲同工之妙他们都把父类成员当成一个整体的自定义类型成员在走拷贝构造时会去调用自定义类的拷贝构造
2.2、假如派生类需要写拷贝构造完成一些深拷贝那我们要显示的写出拷贝构造要怎么写父类的那一块呢
class Student :public Person
{
public:Student(const Student s):_num(s._num),Person(s)//显示调用基类的拷贝构造函数用s来初始化Person部分 {}Student(int num1001,const char* namepeter):_num(num),Person(name){}
protected:int _num;
};另外这里我们要知道当一个派生类如Student的对象被创建时其基类如Person的部分会首先被初始化。这是对象构造过程的一部分它确保基类部分在派生类部分之前处于有效状态
③派生类的赋值构造 派生类的operator必须要调用基类的operator完成基类的复制 基类
class Person
{
public:Person(const char* name hhh):_name(name){cout Person() endl;}Person(const Person p):_name(p._name){}Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;}protected:string _name;
};子类
class Student :public Person
{
public:Student(const Student s):_num(s._num),Person(s){}Student(int num1001,const char* namepeter):_num(num),Person(name){}Student operator(const Student s){if (this ! s){//operator(s);//在子类中这样调用父类中的赋值构造是不对的他们函数名相同会隐藏掉父类的operator函数//这里如果这样写会一直反复调用子类中的operator这样会栈溢出//如果想调到父类的operator函数可以显示调用Person::operator(s);Person::operator(s);_num s._num;}return *this;}
protected:int _num;
};④派生类的析构函数 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序。 class Person
{
public:Person(const char* name hhh):_name(name){cout Person() endl;}Person(const Person p):_name(p._name){}Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;}~Person(){cout ~Person() endl;}protected:string _name;
};class Student :public Person
{
public:Student(const Student s):_num(s._num),Person(s){}Student(int num1001,const char* namepeter):_num(num),Person(name){}Student operator(const Student s){if (this ! s){Person::operator(s);_num s._num;}return *this;}~Student(){//~Person();//这里不能直接访问父类的析构函数因为后续多态的需要析构函数名字会被统一处理成destructor//所以这里析构也是被隐藏了根本找不到这个析构名所以直接报红色波浪线了//如果你想访问也可以就显示调用他就好了Person::~Person();cout ~Student() endl;}Student s(1002, okk);如果我在子类析构函数中显示调用父类的析构函数就会出问题1 这里我们父类的构造只构造了一次却析构了两次这样会造成不可预料的问题所以我们就不该显示的写父类的析构函数
问题2
~Student()
{Person::~Person();cout_nameendl;//这里是父类的成员cout~Student()endl;
}还有就是如果我们先析构了父类但是我们还需要用到父类的成员就会出现访问不到的情况或者是其他不可预料的问题在继承机制中子类的析构函数通常会自动调用其父类的析构函数所以父类的析构不需要我们显示写不要画蛇添足
派生类对象析构清理先调用派生类析构再调基类的析构要保证这个原则所以我们不能显示调用父类的析构将上面的子类析构函数改成
~Student()
{cout~Student()endl;
}总结 派生类对象在初始化时先父后子如果你不信可以调试看一下 派生类对象在析构时先子后父这个就是继承机制的原因了
继承与友元
友元关系不能继承也就是说基类友元不能访问子类私有和保护成员
class Person
{
public:friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person p, const Student s)
{cout p._name endl;//cout s._stuNum endl;Display()函数是父类的友元并不是子类的友元不能访问子类的私有或者保护
}如果你需要访问子类和父类的私有成员和保护成员那你可以让这个函数即是父类的友元也是子类的友元一个函数可以同时是多个类的友元
继承与静态成员
基类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类都只有一个static成员实例
注意静态成员是属于类本身的而不是类的实例对象的
class Person
{
public:Person() { _count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。---统计Person及其Person对象一共产生了多少个
};
int Person::_count 0;
//注意静态成员要在外面定义,定义的时候才会为他开空间
//由于静态成员是属于类本身的,而不是类的任何实例,所以它们需要有一个唯一的存储空间
//类外的定义确保了这一点class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};int main()
{//_count 静态成员只有一份当前类和它的派生类共用一个//父类静态成员属于当前类也属于当前类的所有派生类cout (Person::_count) endl;cout (Student::_count) endl;cout (Graduate::_count) endl;return 0;
}由于Student类继承了Person类所以他可以使用这个静态_count成员至于Graduate 类他继承的是Student类但是Student类继承了Person类所以Graduate也可以使用_count成员上面分别是从这三个类中找到_count对象并取出它的地址打印出来我们会发现这是同一个地址这就更验证了 父类静态成员属于当前类也属于当前类的所有派生类
有了这个特性之后我们可以用他来求父类在一个程序中总共创建了多少个对象在构造函数里面加上_count就可以统计出该程序从运行到结束一共创建了多少个对象如果只想知道现在还存在的对象一共有多少个就可以在析构函数里面写上_count-- 本篇暂且先到这里我们下篇见✋