c++的类的封装/继承/多态的简单介绍

 本篇文章仅仅从很表层来介绍一个C++语言中的类,包括什么是类,类的封装性/继承性和多态性。高手直接跳过吧,看了浪费时间,新手或者想温习一下的可以浏览看看。

 

1. 什么是类?

到底什么是类(class)??类就是一种类型,是用户自己定义的一个类型,和内置类型如int/float/double类似,  用一个类可以去定义一个变量,即课本中所谓的类的实例化,会得到一个object。类这个类型比较特别,它即包括了数据(数据成员),又包含了若干个操作这些数据的方法(即成员函数)。为什么需要类呢?类提供了一种对事物的抽象,增强了事物的聚合性,类让我们可以把一个事物当作一个整体去看,方便描述,方便建模。 例如:我们可以定义一个学生的类:包括了姓名/性别/年龄/学号ID等信息

  1 class Student
  2 {
  3 public:
  4         int GetID();
  5         string GetName();
  6         string GetSex();
  7         int GetAge();
  8         
  9         void SetID(int ID_); 
 10         void SetName(string Name_); 
 11         void SetSex(string Sex_); 
 12         void SetAge(int Age_); 
 13  
 14 private: 
 15         string m_Name; 
 16         string m_Sex; 
 17         int m_ID; 
 18         int m_Age 
 19 };           

如上所示,我们定义了一个Student的类, 定义它之后,编译器就不光知道了Student是一个类型,而且知道了类型的一些细节,例如当使用该类型去定义一个变量(object)时,需要分配多少内存等,例如:

1     // 输入Student类型在内存中占多少字节                                                     
2     cout << "Student类型的大小为:" << sizeof(Student) << endl;
3 
4     //实例化一个叫小明的学生,并命名为SB(有点矛盾)
5     Student xiaoming;
6     xiaoming.SetName("SB");
7     cout << xiaoming.GetName() << endl;

 

接下来,详细说说如何定义一个类:

定义一个类,需要做的是:1. 声明类拥有的数据成员, 2. 声明类拥有的成员函数:

1 class Dog
2 {
3 public: 
4     int Age;
5     int GetAge();
6 };

成员函数在哪里定义呢?即可以在类的内部直接定义,也可以在类的外部进行定义(此时,需要指明所属的类)。当定义在类的内部时,默认声明为inline函数。 当类外部定义成员函数时,可以有类内声明为inline函数,也可以在定义时候声明了inline函数,但是个人更喜欢在类外定义的时候声明为inline函数,这样可以根据自己定义一个函数的实际情况,决定是否声明为inline函数,而不需要提前考虑好。

 1 // 类的内部定义
 2 class Dog 
 3 {
 4 public: 
 5     int Age;
 6     int GetAge()    //当定义在类的内部时,默认声明为inline函数
 7     {
 8         return Age;
 9     }
10 };
11 
12 // 类外部定义
13 class Dog
14 {                                                                                                                                                                                               
15 public: 
16     int Age;
17     int GetAge();
18 };
19 
20 Dog::GetAge()
21 {
22     return Age;
23 }
24 
25 // 类外部定义,显示声明为inline函数
26 class Dog
27 {
28 public: 
29     int Age;
30     inline int GetAge();
31 };

另外:

1. 类成员函数通过隐式this指针访问类内部的数据成员的; 如果把this指针修饰为const,在成员函数后面加const ,这就是const 成员函数:  GetAge() const 
2.  编译器在解析一个类,总是先把类内部的数据成员解析完毕,再去处理成员函数,因此,定义一个类时,不必关心数据成员与成员函数的先后位置,数据成员圣成员函数问题可见的。
3. 一个类域也是一个作用域(用{ }括起来的部分),正因为如此, 1. 在类的外部定义成员函数时,指定类名就进入了类的作用域中了,就可以找到数据成员了; 2. 对类内的静态数据成员与静态成员函数,可以通过类名作用域访问(对非静态的不可以,因为非静态的数据成员属于具体的一个对象而不属于类,虽然非静态的成员函数实际上在多个对象之间是共享的,但是也只能通过对象名对象的指针访问,因为它们有隐式的this指针)

 

2. 类的封装性

 封装性就是说可以把一部分东西封装起来,不让别人看到。在C++中,类这种类型通过它的访问控制符来体现了它的封装性,包括:

public: 公有的,在类的外部放着,谁都可以看到;
protected: 保护性的,只让它的子类可以看到;
private: 私有的,即把它们封装在了类的内部,在类的外面是看不到的,只有类内部的人可以看到;

例如:定义了一个House的类,House外面的只能看到pulic下的内容,而在House里面,可以看到所有内容;

 1 class House
 2 {
 3 public:                                                                                      
 4     int Windows;
 5     void OpenWindow();
 6     int doors;
 7     void OpenDoors();
 8 
 9 protected:
10     int *** // 这个不好举例子
11 
12 private:
13     int Desk;
14     int light;
15     void OpenLight();
16 }

    使类具有封装性的目的是:我们可以定义一些类,只对类的使用者留一下公有的接口(即pulic下的内容),而类内部的相关操作对类的使用者来说是透明的,用户不操心。我可以随便改类内部的代码,只有公有的接口不变,类的用户的代码是不需要调整的。保证数据安全,方便用户使用,大家都省心啊。

 

3. 类的继承

    如果我们想在一个类的基础上继续创建一个新类,这就用到了类的继承性。继承可以使用三个访问标志符控制:public、protectd和private。  无论哪个继承,对直接子类没有任何影响,只对子类的用户有影响。基类中的private成员无论使用哪个访问标志符,子类的用户是看不到的,基类的public与protected成员是否能让子类的用户看到由三种访问标志符控制 。

public继承: 基类内的数据成员与成员函数封装特性不变在子类中不变
protected继承: 基类内的数据成员与成员函数的public部分在子类中变为protected
private继承: 基类内的数据成员与成员函数的public和protected部分变为private

例如:

 1 class Base
 2 {
 3 public:
 4     *****
 5 protected:
 6     *****
 7 };
 8                                                                                 
 9 // 公有继承
10 class Derived1 : public Base
11 {
12 public:
13     .....
14 private:
15     ....
16 };
17 
18 // 私有继承
19 class Derived2 : private Base
20 {
21 public:
22     ;;;;;;
23 };

a. 基类中虚函数和纯虚函数

    思考这么一个问题: 当基类定义了某个函数,而在子类中又定义了一个同名的函数(返回类型与参数列表可以不同),这时会发生什么?
答: 子类内的该函数会隐藏基类中同名的函数。即使他们的参数列表与返回类型不同,也不会发生重载,因为重载必须在相同的作用域内发生,如果作用域不同,编译器查找时,总会先找到最近作用域内的同名函数。

    很多时候,我们想在基类与子类中定义相同的接口(即同名函数),它们实现各自的功能,这就可以把这样的函数声明为虚函数,即virtual.   当通过类的指针与引用调用虚函数时,会发生动态绑定,即多态。如下面例子所示:

 1 // 程序
 2 #include<iostream>                                                              
 3 using namespace std;
 4 
 5 class Base
 6 {
 7 public:
 8     virtual void Say() {cout << "I am Base!" << endl;}
 9 };
10 
11 class Derived : public Base
12 {
13 public:
14     virtual void Say() {cout << "I am Derived!" << endl;}
15 };
16 
17 int main()
18 {
19     Base* pA = new Base;
20     Derived* pB = new Derived;
21     pA->Say();
22     pB->Say();
23 }
24 
25 // 输出
26 yin@debian-yinheyi:~/c$ ./a.out 
27 I am Base!
28 I am Derived!

    当我们不想实例化一个类时,我们可以定义一个抽象基类,它只负责提供接口。包含纯虚函数的类为抽象类。纯虚函数必须在子类中进行声明与定义。而虚函数可以不在子类中声明与定义,这时候它会像普通成员函数一样继承基类中的虚函数的实现。

1 class Base
2 {
3 public:                                                                         
4     virtual void Say() = 0;     //纯虚函数
5 }; 

总结来说:

1. 当我们要继承一个类的接口与实现时,我们在基类中定义普普通通的成员即可。
2. 当我们想要继承一个类的接口与默认的实现时,我们在基类中定义为虚函数。
3. 当我们只要继承一个类的接口时,我们在基类中定义为纯虚函数。

其它说明:

1. 在子类中,当我们重新声明和定义虚函数时,可以加上virtual关键字(virtual只能用在类内,不可以把virtual 用在类外),也可以不加, 在c++11标准中,引入了override关键字来显示表示覆盖基类中虚函数的定义,override关键字有利于给编译器更多信息,用于查错。例如当我们在子类中定义了一个与基类中虚函数名字相同,但是参数列表不同的函数,我们本意是定义子类特有的虚函数版本,来覆盖基类中的版本。然而这时候,基类与子类中的函数是独立的,只是基类中的版本隐藏了而已。如果使用了override,编译器发现没有覆盖,就会报错。 如果我们不想让基类中的某个虚函数被覆盖掉,可以使用final关键字。(另外覆盖,即override只会发生在虚函数身上

2. 如果我们定义 了一个类,并且不想该类被继承,可以在定义这个类时,在类名后面加上final关键字。

 1 class Base
 2 {
 3 public:
 4     virtual void Say() {cout << "I am Base!" << endl;}
 5     virtual void Dad() final { cout << " I am your dad!" << endl;}   //对该虚函数使用final关键字
 6 };
 7 
 8 class Derived final : public Base       // 对类Derived 使用了final 关键字                                                                                                                       
 9 {
10 public:
11     void Say() override { cout << " I am Derived!" << endl;}        //使用了override关键字
12 };

3. 虽然一个纯虚函数不需要定义,但是其实我们是可以定义一个纯虚函数的,不过调用它的唯一途径是”调用时明确指出它的class的名称“。

 1 // 程序
 2 class Base
 3 {
 4 public:
 5     virtual void Hello() = 0;
 6 };
 7 void Base::Hello() {cout << "hello " << endl;}                                                                                                                                                  
 8 
 9 class Derived final : public Base
10 {
11 public:
12     void Hello() override {cout << "a,很疼的" << endl;}
13 };
14 
15 int main()
16 {
17     Derived* pB = new Derived;
18     pB->Hello();
19     pB->Base::Hello();
20 }
21 
22 // 输出
23 yin@debian-yinheyi:~/c$ ./a.out 
24 a,很疼的
25 hello 

4. 基类与子类中的虚函数的返回类型和参数列表必须完全一致,如果不一致的话,编译器认为他们是完全不相关的函数。他们之间不会发生覆盖(override),子类中的同名函数只会隐藏子类中的同名函数。

b. 继承中的作用域

关于类的作用域,我们要明白以下几点:

1. 类本身是一个作用域,使用{ }括起来的。
2. 在类的继承过程中,子类的作用域是嵌套在基类的作用域之内的(这就明白了为什么有时候子类中的成员函数会隐藏掉基类中函数,就时候如果我们想要使用被隐藏的基类函数,可以通过显示指明类名(这时可以理解为作用域名)来访问。
3. 在一个类的作用域中,编译器在解析类时,它总会先解析类中声明的所以数据成员与成员函数,再去解析成员函数的定义。正因为这样的原因,无论数据成员定义在成员函数的后面还是前面,还是成员函数的顺序前后之类的, 一个成员函数总是可以找到该类的数据成员或调用其它成员函数。

基于作用域的一个例子:

 1 // 程序
 2 class Base
 3 {
 4  public:
 5     virtual void Hello() { cout << "Hello, I am Base!" << endl; }
 6     void Hi() { cout << "Hi, Hi, Base!" << endl;}
 7 };
 8 
 9 class Derived : public Base
10 {
11 public:
12     void Hello() override { cout << "Hello, I am Derived!" << endl;}
13     void Hi() { cout << "Hi, Hi, Derived!" << endl;}
14 };
15 
16 int main()
17 {
18     Derived Derive;
19     Derive.Hello();
20     Derive.Hi();
21     Derive.Base::Hello();        //显示调用基类的虚函数
22     Derive.Base::Hi();            // 显示调用基类普通函数
23 
24     Base *pBase = &Derive;
25     pBase->Hello();        //指针调用,进行动态绑定,即多态
26     pBase->Base::Hello();     //显示调用基类的虚函数
27     pBase->Base::Hi();          // 显示调用基类普通函数
28 
29     return 0;
30 }
31 
32 //程序输出:
33 Hello, I am Derived!
34 Hi, Hi, Derived!
35 Hello, I am Base!
36 Hi, Hi, Base!
37 Hello, I am Derived!
38 Hello, I am Base!
39 Hi, Hi, Base!

 

 

额外小知识点:

1. 待后续遇到补充!

 

原文链接: https://www.cnblogs.com/yinheyi/p/9853164.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    c++的类的封装/继承/多态的简单介绍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/394984

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月4日 上午9:42
下一篇 2023年4月4日 上午9:42

相关推荐