zoukankan      html  css  js  c++  java
  • 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. 待后续遇到补充!

  • 相关阅读:
    51串口通信
    juicer使用备忘
    51单片机音乐盒程序
    最精简24L01程序--接收
    sqlserver 数据库迁移
    sqlserver自增主键
    js keycode
    tabindex 去掉虚线
    div 绑定keyup
    sqlserver 当前时间减去30天
  • 原文地址:https://www.cnblogs.com/yinheyi/p/9853164.html
Copyright © 2011-2022 走看看