阿里云快速建站教程,全球跨境电商平台排行榜前十名,个人电脑搭建云服务器,wordpress 锚点插件个人主页#xff1a;平行线也会相交#x1f4aa; 欢迎 点赞#x1f44d; 收藏✨ 留言✉ 加关注#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】#x1f48c; 本专栏旨在记录C的学习路线#xff0c;望对大家有所帮助#x1f647; 希望我们一起努力、成长平行线也会相交 欢迎 点赞 收藏✨ 留言✉ 加关注本文由 平行线也会相交 原创 收录于专栏【C之路】 本专栏旨在记录C的学习路线望对大家有所帮助 希望我们一起努力、成长共同进步。 目录 一、作用域出个小题小总结 二、派生类的默认成员函数构造函数拷贝构造函数赋值运算符重载析构函数小总结 三、继承与友元四、继承和静态成员 一、作用域
接下来对C继承体系中的作用域展开分析。
在C继承体系中子类和父类有各自的作用域所以子类和父类可以定义同名的成员。
请看针对不同作用域的举例 局部域和当前类域 这里有个小概念 隐藏/重定义子类和父类有同名成员时子类的成员隐藏了父类的成员。如上左图所示 指定当前的父域 作用域当然也对成员函数起作用请看 出个小题
类B和类A中的fun()函数有什么关系。
class A
{
public:void fun(){cout fun() endl;}
};
class B : public A
{
public:void fun(int i){cout fun(int i) endl;}
};B中的fun和A中的fun构成隐藏成员函数满足函数名相同就构成隐藏。并不会构成函数重载因为函数重载针对的是不同的作用域
小总结
在继承体系中基类和派生类都有独立的作用域。子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏 也叫重定义。在子类成员函数中可以使用基类::基类成员进行显示访问举个例子就比如说B b; b.A::fun();需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。但是其实在实际中在继承体系里面最好不要定义同名的成员省的给自己添麻烦。
二、派生类的默认成员函数
再来回顾一下C中的6个默认成员函数构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址及const取地址运算符重载。
构造函数
class Person
{
public:Person(const char* name peter): _name(name){cout Person() endl;}~Person(){cout ~Person() endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(const char* name 李四,int id 0):_id(0){}
protected:int _id;
};
int main()
{Student s;return 0;
}运行结果如下 上述代码中我们并没有定义Person类对象但是却调用了Person类中的默认构造函数为什么呢? 因为C规定了派生类必须调用父类的成员函数来初始化父类的成员变量。 这里是在初始化列表来调用父类中的默认成员函数的。 在来看下面的情况请看 解释在创建Student对象时先调用Person类的构造函数来初始化Person类的成员变量_name然后再调用Student类的构造函数来初始化Student类的成员变量_id。 所以这里是Person类中的成员函数先进行初始化然后再对Student中的成员进行初始化。即派生类的构造函数在执行之前基类的构造函数必须首先完成。 重点通过使用初始化列表并在其中调用基类的构造函数来初始化基类的成员变量可以确保在派生类的构造函数中正确初始化基类的数据成员。这是由于派生类的构造函数在执行之前基类的构造函数必须首先完成。
拷贝构造函数
class Person
{
public:Person(const char* name peter): _name(name){cout Person() endl;}Person(const Person p): _name(p._name){cout Person(const Person p) endl;}~Person(){cout ~Person() endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public://构造函数Student(const char* name 李四,int id 0):_id(0),Person(name){}//拷贝构造函数Student(const Student s):Person(s), _id(s._id){}
protected:int _id;
};
int main()
{Student s1;Student s2(s1);return 0;
}运行结果如下
如果我们去掉基类拷贝构造函数中的Person(s)会怎样呢即没有显式调用基类中的拷贝构造函数
解析去掉Person(s)将导致基类Person的成员变量_name不会被复制而是会调用基类中的默认构造函数而倘若此时基类也没有提供默认构造函数的话就会直接报错。 所以我们应该显式调用拷贝构造函数。如下
//拷贝构造函数
Student(const Student s):Person(s)//这里要显式调用拷贝构造函数否则会调用基类中的默认构造函数, _id(s._id)
{}一句话总结派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
赋值运算符重载
//父类赋值运算符重载
Person operator(const Person p)
{cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;
}
//子类赋值运算符重载
Student operator(const Student s)
{cout Student operator (const Student s) endl;if (this ! s){Person::operator(s);_id s._id;}return *this;
}运行结果如下 这里有的小伙伴看到Student s2 s1;可能会产生疑惑为什么这里不调用赋值运算符重载函数。 解答 因为在语句Student s2 s1;中发生的是对象的初始化而不是赋值操作。 当使用Student s2 s1;来初始化一个已存在的对象s2时会调用拷贝构造函数而不是赋值运算符重载函数。拷贝构造函数用来创建一个新对象并将其内容初始化为另一个同类型对象的副本。 如果要调用赋值运算符重载函数需要使用赋值操作符来对已存在的对象进行赋值例如s2 s1;。这样才会调用赋值运算符重载函数将s1的值赋给s2。 析构函数
//父类析构函数
~Person()
{cout ~Person() endl;
}
//子类析构函数
~Student()
{cout ~Student() endl;
}在C中无法显式调用父类的析构函数。当一个派生类对象被销毁时首先会自动调用派生类的析构函数然后再自动调用基类的析构函数即按照先父后子的顺序来完成对对象的析构。 如果要显式调用是没有办法保证先子后父进行析构的。 小总结
派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认 的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。派生类的operator必须要调用基类的operator完成基类的复制。派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。即按照先清理派生类对象再清理基类对象的顺序。派生类对象初始化先调用基类构造再调派生类构造同时派生类对象初始化先调用基类构造再调派生类构造。
三、继承与友元
友元关系不能继承即基类友元不能访问子类私有和保护成员基类的友元只能访问基类的成员而不能访问派生类的成员。
class Student;
class Person
{
public:friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};
class Student : public Person
{friend void Display(const Person p, const Student s);
protected:int _stuNum; // 学号
};
void Display(const Person p, const Student s)
{cout p._name endl;cout s._stuNum endl;
}
int main()
{Person p;Student s;Display(p, s);return 0;
}解释Person类和Student类互相引用对方作为友元函数因此需要先进行一次前向声明即开头的class Student;。这样可以确保在实际定义这两个类的成员函数之前编译器已经知道这两个类的存在。
四、继承和静态成员
class Person
{
public:Person() { _count; }
//protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s;cout p._name endl;cout s._name endl;cout p._count endl;cout s._count endl;cout Person::_count endl;cout Student::_count endl;
}运行结果如下 静态成员变量是一种属于类而不是类的实例的变量。它在所有类的实例之间共享并且在整个程序的生命周期中只存在一个副本。静态成员变量是在类定义外部进行初始化的。 静态成员变量适用于在类的多个实例之间共享数据并且可以通过类名直接访问而无需实例化类对象。它们在数据共享和数据统计方面非常有用。需要注意的是静态成员变量仅属于类而不属于类的任何特定实例。 静态成员变量的访问方式静态成员变量可以使用类名::成员变量名的方式进行访问即类名::成员变量名例如Person::_count。
下面请看下面代码要统计Person类及其Person派生类对象总共创建了多少个
class Person
{
public:Person() { _count; }
//protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s1;Student s2;Student s3;Graduate s4;cout Person::_count endl;
}运行结果Person类及其派生类对象总共创建了4个对象。
解释在代码中将_count定义为静态成员变量是为了在整个类层级中共享同一个计数变量。当创建派生类对象时构造函数会依次调用每个类的构造函数包括父类的构造函数。所以在父类的构造函数中进行_count操作可以确保每个派生类对象的创建都能正确地增加计数。
好了本文到这里就结束了希望对大家学习C继承体系有所帮助。 再见啦友友们