本博客将记录:类的相关知识点的第7节的笔记!
(这个在学习C++基础课程时已经学习过一次了,这里再次简单地回顾一下而已)
今天总结的知识分为以下3个点:
一、派生类的概念
1)派生类对象定义时调用构造函数的顺序 and 2)派生类对象销毁时调用构造函数的顺序
二、public protected private 三种继承方式(访问权限)(详细总结,配案例代码)
三、函数遮蔽
一、派生类derived class(子类)的概念:
类与类之间,有一种层次的关系,也即:父类和孩子类的关系。父亲和孩子类之间的这种关系我们称之为继承。继承,是面向对象的程序设计的核心思想之一!
继承的语句格式:
class 子类名 : 继承方式 父类名{
public:
/.../
};
我们举个例子把,车Car这个类我们称之为父类(也称之为基类or超类),由该类派生出来的卡车电动车等类,就称之为子类(派生类)。说白了,所谓的继承二字,就是我们先定义一个父类,而该父类中定义了一些公用的成员变量以及成员函数。通过(public/private/protected)继承父类来创建新的子类。一般而言,我们在继承之后只需要在子类中重写与父类同名的函数以及virtual虚函数,再编写属于子类独有的成员变量和成员函数即可。
1)派生类(子类)对象定义时调用构造函数的顺序:
当我们创建子类对象时,编译器会为我们先调用其父类的构造函数,用于创建子类对象中的父类成分(也即父类的成员变量),然后再调用子类的构造函数用于创建子类的成员变量。
(也即,我们在创建子类的对象时会先执行父类的构造函数的函数体创建父类的成分,然后再执行子类的构造函数的函数体创建子类的成分。)
2)派生类(子类)对象销毁时调用构造函数的顺序:
当我们销毁子类对象时,编译器会为我们先调用子类的析构函数,用于销毁子类的对象以及子类的成员变量,然后再调用父类的析构函数用于销毁父类的对象以及父类的成分(也即父类的成员变量)
(也即,我们在销毁子类的对象时会先执行子类的析构函数的函数体销毁子类的成分,然后再执行父类的析构函数的函数体销毁父类的成分。)
直接请看以下代码:
Human.h
#ifndef __HUMAN_H__//这是头文件的防卫声明
#define __HUMAN_H__
#include
#include
using namespace std;
//声明基类/父类/超类
class Human {
public:
int m_Age;
string m_Name;
public:
Human() :m_Age(0), m_Name("") {
cout << "默认的Human()无参构造函数!" << endl;
}
Human(int age,string str) :m_Age(age), m_Name(str) {
cout << "有参Human(int age,string str)构造函数!" << endl;
}
~Human() {
cout << "默认的~Human()无参析构函数!" << endl;
}
};
#endif __HUMAN_H__
Man.h
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man :public Human {//表示Man为Human的子类
public:
Man(){ cout << "默认的Man()无参构造函数!" << endl; }
~Man(){ cout << "默认的~Man()析构函数!" << endl; }
};
#endif __MAN_H__
main.cpp
#include
#include"Human.h"
#include"Man.h"
using namespace std;
void test() {
Man m;//创建子类对象
}
int main(){
test();
system("pause");
return 0;
}
运行结果:
继承的注意事项:
当然,你也可以进行多继承,只不过我们在实际的开发中并不推荐你去do多继承这样的事情!
二、public protected private 三种继承方式(访问权限):
这是重要的知识点,我给大家画一张图来总结一下!学习这三个关键字只搞定下面这张图完全足够了!并且,以后但凡是遇到这三个关键字,你如果忘记了其对应的访问特性,可以立马回来查表!
相信大家一开始看到我总结的这个图,都很懵逼,那么现在我就用代码来给大家解释一下:
注意:这里我都是以三种访问权限的成员变量do为写代码的具体例子,对于三种访问权限的成员函数也是一样的效果,一样的访问特点!
公有继承代码:
Human.h
#ifndef __HUMAN_H__//这是头文件的防卫声明
#define __HUMAN_H__
#include
#include
using namespace std;
//声明基类/父类/超类
class Human {
public://公有成员变量
int m_Age;//年龄
string m_Name;//姓名
protected://保护成员变量
string m_Disease;//疾病
private://私有成员变量
int m_FriendNums;//朋友数量
public:
Human() :m_Age(0), m_Name(""), m_Disease("无"), m_FriendNums(0) {
cout << "调用默认的Human无参构造函数!" << endl;
}
Human(int age,string str,string disease,int frinums) :m_Age(age), m_Name(str),
m_Disease(disease), m_FriendNums(frinums){
cout << "调用有参Human构造函数!" << endl;
}
~Human() {
cout << "调用默认的~Human析构函数!" << endl;
}
};
#endif __HUMAN_H__
Man.h
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man :public Human {//Man是public公有继承自Human的
//父类的public公有成员变量 在子类中仍然为public的!
/* public:
int m_Age;
string m_Name; */
//父类的protected保护成员变量 在子类中仍然为protected的!
/* protected:
string m_Disease; */
//父类的private私有成员变量 在子类永远无法访问!
/* int m_FriendNums; */ 无法访问!
public:
Man(){
cout << "姓名:" << this->m_Age;
cout << " 年龄:" << this->m_Name;
cout << " 疾病:" << this->m_Disease;
//cout << " 朋友数量:" << this->m_FriendNums << endl;
//报错!父类的私有成员在任何方式的继承下都不允许被访问!
cout << "\n调用默认的Man无参构造函数!" << endl;
}
~Man() { cout << "调用默认的~Man析构函数!" << endl; }
};
#endif __MAN_H__
main.cpp
#include
#include"Human.h"
#include"Man.h"
using namespace std;
void test() {
Man m;
}
int main(){
test();
system("pause");
return 0;
}
运行结果:
保护继承代码:(只需要在上述代码中把Man.h中的public改为proteced)
Man.h
class Man : protected Human {//Man是protected保护继承自Human的
//父类的public公有成员变量 在子类中为protected的!
/* protected:
int m_Age;
string m_Name; */
//父类的protected保护成员变量 在子类中为protected的!
/* protected:
string m_Disease; */
//父类的private私有成员变量 在子类永远无法访问!
/* int m_FriendNums; */ 无法访问!
public:
Man(){
cout << "姓名:" << this->m_Age;
cout << " 年龄:" << this->m_Name;
cout << " 疾病:" << this->m_Disease;
//cout << " 朋友数量:" << this->m_FriendNums << endl;
//报错!父类的私有成员在任何方式的继承下都不允许被访问!
cout << "\n调用默认的Man无参构造函数!" << endl;
}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
};
运行结果:
私有继承代码:(只需要在上述代码中把Man.h中的proteced改为private)
Man.h
class Man : private Human {//Man是private 私有继承自Human的
//父类的public公有成员变量 在子类中为private的!
/* private:
int m_Age;
string m_Name; */
//父类的protected保护成员变量 在子类中为private的!
/* private:
string m_Disease; */
//父类的private私有成员变量 在子类永远无法访问!
/* int m_FriendNums; */ 无法访问!
public:
Man(){
cout << "姓名:" << this->m_Age;
cout << " 年龄:" << this->m_Name;
cout << " 疾病:" << this->m_Disease;
//cout << " 朋友数量:" << this->m_FriendNums << endl;
//报错!父类的私有成员在任何方式的继承下都不允许被访问!
cout << "\n调用默认的Man无参构造函数!" << endl;
}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
};
运行结果:
从运行结果可见,公有继承、保护继承、私有继承时,父类的成员变量均可用在子类中!!!
下面我们再来看一看,对于一个单独的类,其public、protected、private成员(这里我以成员变量为例子)在类外的访问特点是如何的呢?
请看以下代码:
这里为了方便起见,将成员变量设置为static静态的,以便于我在类外用类名::static静态成员变量名的方式直接来访问类内的静态变量!因为静态成员是跨对象的!
这一小知识点在前几小节我总结static关键字时已经总结地很清楚了,不知道的小伙伴可以翻阅我前面的总结。
C++新经典课程学习笔记之第三章-3.3小节(重要)
#ifndef __HUMAN_H__//这是头文件的防卫声明
#define __HUMAN_H__
#include
#include
using namespace std;
//声明基类/父类/超类
class Human {
public://公有成员
static int m_Age;//年龄
static string m_Name;//姓名
protected://保护成员
static string m_Disease;//疾病
private://私有成员
static int m_FriendNums;//朋友数量
};
void func() {
cout << Human::m_Age<< endl;//类外可以访问类内的public成员!
cout << Human::m_Name << endl;//类外可以访问类内的public成员!
//cout << Human::m_Disease << endl;//报错!类外不可以访问类内的protected成员!
//cout << Human::m_FriendNums << endl;//报错!类外不可以访问类内的private成员!
}
#endif __HUMAN_H__
相信通过这一个点的详述,你已经学习并清楚地知道了一个类中的public、protected、private三种访问权限(属性)在类外的访问特点了。即:
①public访问权限下的成员可以给任意类内外的实体访问!
②protected访问权限下的成员只可以给类内和子类的成员函数来访问!
③private访问权限下的成员只可以给类内的成员来访问!
三、函数遮蔽:
所谓的函数遮蔽,其实就是当父类中重载了一些同名函数时,子类若也重写了该同名函数的话,那么不论父类中重载了几个这样的同名函数,其在子类中都无法访问到了!
(因为此时,子类中与父类同名的函数就会覆盖掉从父类中继承过来的同名函数,包括其重载的版本,因而在子类中父类的同名函数就不可见 )
请看以下代码:
Human.h
#ifndef __HUMAN_H__//这是头文件的防卫声明
#define __HUMAN_H__
#include
#include
using namespace std;
//声明基类/父类/超类
class Human {
public://公有成员
int m_Age;//年龄
string m_Name;//姓名
protected://保护成员
string m_Disease;//疾病
private://私有成员
int m_FriendNums;//朋友数量
public:
Human() :m_Age(0), m_Name(""), m_Disease("无"), m_FriendNums(0) {
cout << "调用默认的Human无参构造函数!" << endl;
}
Human(int age,string str,string disease,int frinums) :m_Age(age), m_Name(str),
m_Disease(disease), m_FriendNums(frinums){
cout << "调用有参Human构造函数!" << endl;
}
~Human() { cout << "调用默认的~Human析构函数!" << endl;}
void func() { cout << "调用父类Human的func()" << endl; }
void func(int) { cout << "调用父类Human的func(int)" << endl; }
};
#endif __HUMAN_H__
Man.h
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man : public Human {//public 公有继承自Human
public:
Man(){ cout << "\n调用默认的Man无参构造函数!" << endl;}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
//void func() { cout << "调用子类Man的func()" << endl; }
};
#endif __MAN_H__
main.cpp
#include
#include"Human.h"
#include"Man.h"
using namespace std;
void test() {
Man m;
m.func();
m.func(1);
}
int main(){
test();
return 0;
}
运行结果:
但是,如果你在子类Man.h中重写了父类中的同名函数的话:
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man : public Human {//public 公有继承自Human
public:
Man(){ cout << "\n调用默认的Man无参构造函数!" << endl;}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
//重写父类中的同名函数
void func() { cout << "调用子类Man的func()" << endl; }
};
#endif __MAN_H__
编译就会不通过!报错!这就是所谓的函数遮蔽!
此时,如果我们确实想调用父类中的同名函数,怎么办呢?
1)你大可以在子类的成员函数中直接用"父类名::对应的同名函数"的方式来调用父类中的同名函数!
请看以下代码:(将上述写的Man.h该为如下形式,Human.h不变)
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man : public Human {//public 公有继承自Human
public:
using Human::func;
Man() { cout << "调用默认的Man无参构造函数!" << endl;}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
};
main.cpp
#include
#include"Human.h"
#include"Man.h"
using namespace std;
void test() {
Man m;
m.func();
m.func(1);
}
int main(){
test();
return 0;
}
运行结果:
2)C++11引入了using关键字的新的特性,用using可以让父类同名函数在子类中变得可见,也即让父类的同名函数在子类中以重载的方式进行使用。
格式:using 父类名::函数名;
注意:且只能指定函数名,而不能指定函数的参数,且但凡是基类中的public,protected访问权限下的函数都可以在子类中用using将其"暴露"出来,使得其在子类中变得可见!
且在父类中,一旦你用using把对应的同名函数暴露给其子类时,父类中的all该同名函数包括其重载函数都会暴露给子类,也即你一旦这么用using了就无法在子类中只暴露父类中的其中一些同名函数。引入using的主要目的就是:实现在子类对象中调用与父类的同名函数不同的重载版本(的函数)。
再将Man.h改为:
#ifndef __MAN_H__//这是头文件的防卫声明
#define __MAN_H__
#include
#include"Human.h"
using namespace std;
//声明子类/派生类
class Man : public Human {//public 公有继承自Human
public:
using Human::func;
Man() { cout << "调用默认的Man无参构造函数!" << endl;}
~Man() { cout << "调用默认的~Man析构函数!" << endl;}
void func(){ }
void func(int a) { }
};
运行结果:
可以看出,如果你在Man.h中仍然把对应的重名函数定义上的话,就不会再显示父类中的同名函数了,此时子类的同名函数还是照样给你覆盖掉父类中的同名函数。
其实,这个函数的遮蔽说了这么多,你可能会混淆,但是当我们后面介绍了虚函数和纯虚函数之后你就会恍然大悟,原来在子类中用virtual关键字去重写父类的同名函数甚至是重载版本是如此地简单和好用!
class Dad {
public:
int m_Age;
string m_Name;
Dad(int age,const string& name):m_Age(age), m_Name(name) {
cout << "this is Dad 的构造函数!" << endl;
}
~Dad() {}
};
class Son : Dad {//if class Son do默认继承的话,编译器默认会给你按照private的方式来do继承
public:
int girlFriendNums;
//在子类的初始化列表中,直接调用父类的构造函数并传参进进去!
Son(int nums, int age, const string& name) :girlFriendNums(nums), Dad(age,name) {
cout << "this is Son 的构造函数!" << endl;
}
~Son() {}
};
struct Son : Dad {//if struct Son do默认继承的话,编译器默认会给你按照public 的方式来do继承
public:
int girlFriendNums;
//在子类的初始化列表中,直接调用父类的构造函数并传参进进去!
Son(int nums, int age, const string& name) :girlFriendNums(nums), Dad(age,name) {
cout << "this is Son 的构造函数!" << endl;
}
~Son() {}
};
这个小细节应该了解以下。
好,那么以上就是这一3.7小节我所回顾的内容的学习笔记,希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~