zoukankan      html  css  js  c++  java
  • c++入门之类继承初步

    继承是面向对象的一种很重要的特性,先来复习基类的基本知识:

    先上一段代码:

     1 # ifndef  TABLE00_H
     2 # define TABLE00_H
     3 # include "string";
     4 using std::string;
     5 class  Player
     6 {
     7 private:
     8     string first_name;
     9     string last_name;
    10     bool SEAT;
    11 public:  //注意,这里只是头文件,进行函数声明的地方
    12     //Player(const string & fn = "none", const string & ln = "none", bool symbol = false);
    13     Player(const string & fn , const string & ln , bool symbol);
    14     //注意,在最开始设计类的时候,可能我们并没有注意到 要使用 &引用 和const,使用& 和const的契机是:
    15       //1 使用& 是因为,初始化类型为 string类型或者c类型字符串,进行这种非基类数据类型复制的时候,都会耗费大量内存和时间,所以采用引用&
    16       // 2 使用const 的契机: 因为这里采用了引用,这种做法是为了不改变被引用引用的对象。思考将引用符号去掉,是否还有加const的必要性?
    17     void NameShow()const;
    18     bool SeatVerify()const;
    19     //思考:在函数名后面加const,是因为在写函数体之前就想好了函数的功能是否改变成员变量,如果函数的功能不改变成员变量,就添加const,
    20     //说白了这是一种从顶层到底层的设计,我们明白了函数的功能不改变成员变量,所以为了防止写函数体的过程改变成员变量,我们加了一个const。
    21     // 一般的 const加在谁的前面。就是用来修饰谁的,加在返回类型前面,就是修饰返回值,加在形参前面,即修饰形参,则加在函数名后面,是修饰函数体的,具体的也就是
    22     //不改变类对象的成员值。即这种函数称之为常成员函数。
    23     //思考:当函数代码比较短的时候,可否在头文件直接使用内联函数将函数体键入?
    24     void SetSeat(bool);
    25 
    26 };
    27 //以下为共有继承类声明部分
    28 class RePlayer :public Player
    29 {
    30 private:
    31     unsigned int ratio;
    32 public:
    33     RePlayer(unsigned int, const string & fn, const string & ln, bool symbol);
    34     RePlayer(unsigned int, const Player & np);
    35     int Ratio() const;
    36     void InitialRatio(unsigned int);
    37 };
    38 
    39 # endif

    先复习基本知识:

        1  # ifndef TABLE00_H...# endif 表明:如果之前没有定义TABLE00_H段,则编译# ifndef TABLE00_H...# endif之间程序段,否则不编译,这 能够避免一个文件被多个重叠文件连续包含时报错,比如B头文件包含了A文件,C头文件包含了B文件和A文件,那么如果没加# ifndef TABLE00_H...# endif ,则会因为重复定义报错,因此在写头文件时,一律写上 # ifndef TABLE00_H...# endif可以避免程序的报错问题。

        2  对一个类而言,构造函数是十分重要的一个环节,构造函数存在的意义是:让私有成员变量被初始化,我们应当始终注意这一初衷,只有这样,我们才能设计正确的形参。

        3  我们应该注意引用&变量的使用契机,当传递的参数是复杂数据类型(比如类和c类型的字符串),由于巨大的内存开销和调度,采用引用的方式无疑是一种高效的方式

        4  上述代码段17,18,35行的函数成为:常成员函数,在此,先声明函数声明结尾const的作用,使得程序体不能改变私有成员变量的值(否则报错),比如成员显示函数,可以使用常成员函数

    上述代码28-37行为继承类的生命,从这个声明我们可以得到这样一些基本信息与结论:

        1  继承类首先也是类,具有类的一般特性:包括私有成员、公有成员,以及构造函数。

        2  观察继承类的构造函数。发现其构造函数同样服从:让私有成员变量被初始化.但继承类继承了基类,因此也要对基类的成员进行初始化,说白了,要对所有的成员进行初始化。

    易错:

        也许有人看了13,33,34行的代码,会发出这样的疑问:为何这里使用了引用变量却没有初始化,引用变量在定义变量时不是要进行初始化吗?

        回答我们在声明类,甚至在定义类的时候,本质工作是什么???本质工工作是:构造,构造一个数据类型,并不是在定义变量,只有我们在使用类(构造的数据类型)去定义对象的时候,我们才是真正的定义了一个变量,所以 定义类的过程,并不是定义变量的过程,所以并不必要对&进行初始化,说白了,此时的引用&只是一个空壳子,并不实际的分配内存,进行初始化这些功能。

    进行了类声明之后,但成员函数还未得到定义,为此,给出类定义:

     1 # include "table00.h"
     2 # include "iostream"
     3 using std::string;
     4 using std::cout;
     5 using std::endl;
     6  /*class  Player  //如果在函数体文件再声明class Player则会出现重定义的情况!!!,所以采用这种做法是错误的。
     7 {
     8 private:
     9     string first_name;
    10     string last_name;
    11     bool SEAT;
    12 public:
    13     Player(const string & fn = "none", const string & ln = "none", bool symbol = false)
    14     {
    15         first_name = fn;
    16         last_name = ln;
    17         SEAT = symbol;
    18     }
    19     void  NameShow()const  //注意在函数体中,这个const也不能丢舎.
    20     {
    21         cout << first_name << "," << last_name << endl;
    22     }
    23     bool SeatVerify()const
    24     {
    25         return SEAT;
    26     }
    27     void SetSeat(bool change_seat)
    28     {
    29         SEAT = change_seat;
    30     }
    31 };*/
    32 //验证上述写法和下述写法哪个更好。以及对于作用域有没有更好的表示方法。
    33 //Player::Player(const string & fn = "none", const string & ln = "none", bool symbol = false)
    34 Player::Player(const string & fn , const string & ln, bool symbol )
    35 
    36 {              
    37     first_name = fn;
    38     last_name = ln;
    39     SEAT = symbol;
    40 }
    41 void Player:: NameShow()const  //注意在函数体中,这个const也不能丢舎.
    42 {
    43     cout << first_name << "," << last_name << endl;
    44 }
    45  bool Player:: SeatVerify()const
    46 {
    47     return SEAT;
    48 }
    49  void Player:: SetSeat(bool change_seat)
    50 {
    51     SEAT = change_seat;
    52 }
    53 //要认识到面向对象这个词的含义:函数的作用尽管也是为了完成一个功能,但更多的是完成对数据的操作,即我们更关注数据本身
    54 // 成员函数的本质在于:服务于成员变量(通常情况是这样),所以在进行成员函数设计的时候,我们所关注的重点是:对成员变量进行何种操作,完成何种功能
    55  //一定要注意主体对象是成员变量。
    56 
    57  RePlayer::RePlayer(unsigned int v, const string & fn, const string & ln, bool symbol) : Player(fn, ln, symbol)
    58  {  
    59      ratio = v;
    60     // first_name = fn;    注意,如果我们试图直接访问基类私有变量,是有问题的
    61     // last_name = ln;     但我们需要在调用继承类构造函数之前,调用基类构造函数。
    62     // SEAT = symbol;
    63  }
    64    //这两条都是继承类构造函数,需要在调用之前调用基类构造函数,因此需要先初始化基类构造函数。
    65  RePlayer::RePlayer(unsigned int v, const Player & np) : Player(np)
    66  {                                              //需要注意的是:如果 前面定义 unsigned int v =0;则后面的np也要赋初值
    67      //注意,这里的写法发生了重定义。
    68      ratio = v;
    69      // first_name = fn;    注意,如果我们试图直接访问基类私有变量,是有问题的
    70      // last_name = ln;     但我们需要在调用继承类构造函数之前,调用基类构造函数。
    71      // SEAT = symbol;
    72  }
    73  int RePlayer:: Ratio()const
    74  {
    75      return ratio;
    76  }
    77  
    78  void RePlayer::InitialRatio(unsigned int initial)
    79  {
    80      ratio = initial;
    81  }

    关于成员函数(也被称为接口,其实很形象!!!)有以下内容需要说明:

        1  无论是基类的成员函数,还是继承类的成员函数,发现:成员函数都更侧重于:对成员变量(也称为实现,也很形象)进行了何种操作。虽然成员函数也描述了:完成了一个怎样的功能,但我们更侧重于:对成员变量完成了一种怎样的功能,也就是最终落脚点在于:成员变量发生了什么?因此,我们在写成员函数的时候,一定不能漫无目的,思考要完成一个什么功能但脱离了成员变量,一定要认识到我们的成员函数是紧紧的围绕成员变量展开的

        2 关注继承类的构造函数的实现:也就是上述,57和65行的代码。在初始化一个继承类成员(实际上包含了基类成员在内的所有成员)的时候,必然先初始化基类的成员变量,要调用继承类的构造函数,一定要首先调用其基类的构造函数,完成对基类成员变量先进行初始化。因此在进入继承类构造函数函数体之前,必然先要调用基类构造函数完成基类成员变量的初始化。

    这也是为什么57行Player(fn, ln, symbol)与65行的 Player(np)会写在函数体{}的前面

        3  我们注意:60行和69行的代码,当我们试图去直接访问基类私有成员变量时,程序是禁止的,也就是说,我们只能通过基类的公有函数才能访问基类的私有成员。这一点保证了父类和子类的独立性关系。

       最终,我们给出函数的调用:

     1 # include "table00.h"
     2 # include "iostream"
     3 using namespace std;
     4 int main()
     5 {
     6     Player player1("jack", "cracy", true);
     7     player1.NameShow();
     8     Player player2(player1);
     9     player2.NameShow();
    10     RePlayer player3(0, "robert", "lin", true);
    11     player3.NameShow();
    12     RePlayer player4(12,player2);
    13     player4.NameShow();
    14     system("pause");
    15     return 0;
    16 
    17 }

    从代码中,可以看到:继承类可以调用基类的公有函数。

    上说代码体现了类的基本思想和类继承的基本思想,下面我们给出一个更深入的探讨,来探讨基类和继承类的一些关系:

     1 # include "table00.h"
     2 # include "iostream"
     3 using namespace std;
     4 void Show(const Player & );
     5 int main()
     6 {
     7     Player player1("jack", "cracy", true);
     8     player1.NameShow();
     9     Player player2(player1);
    10     player2.NameShow();
    11     RePlayer player3(0, "robert", "lin", true);
    12     player3.NameShow();
    13     RePlayer player4(12,player2);
    14     player4.NameShow();
    15     Player & p = player3;   //我们可以用子类去初始化父类,这是没有问题的,因为子类继承了父类的特性(成员)
    16     p.NameShow();
    17     Player* q= & player4;
    18     q->NameShow(); //注意指针的访问方式和引用访问方式的区别,
    19                    //引用就相当于是别名,所以引用名就和对象名是等价的,因此可以用.访问,而指针并不等价于别名
    20                    //指针的访问要采用->。
    21     Player player("ma", "jack", false);
    22     //RePlayer & rt = player;//我们不可以用父类来初始化(或者赋值)子类,因为子类具有父类不具备的一些特性(子类新3定义成员),
    23     //RePlayer * pt = &player;
    24     Player player5("li", "zhou", false);
    25     Show(player5);//将基类对象作为实参 传递给 基类引用,可行不报错
    26     RePlayer player6(11, "lin", "wu", true);
    27     Show(player6);//将继承类对象作为实参 传递给 基类引用,,可行不报错
    28     player6=player5;//试图用基类对象 赋值 继承类 对象,报错!!!
    29     player5 = player6;//用继承类对象 赋值 基类 对象,不报错,实际上,这里使用了运算符重载!!!player& operator=(const player & )const;
    30     Player player7(player5); //用基类对象初始化另一个基类对象,可行
    31     Player player8(player6);//用 继承类 对象初始化 基类对象,可行。
    32     RePlayer player9(player6);// 用 继承类 对象 初始化 继承类对象 ,可行!
    33     RePlayer player9(player5);//用 基类对象 初始化 继承类对象,不可行!!!
    34     system("pause");
    35     return 0;
    36 
    37 }
    38 
    39 void Show(const Player& rt)
    40 {
    41     rt.NameShow();
    42 }

    上述代码体现了子类和父类这样的一些特性:

           当进行类似于赋值操作的时候,子类可以对父类进行赋值,因为子类继承了父类的全部特性。但不能用父类对子类赋值,因为子类有的特性,父类不一定有。

           同时,注意15,17行代码的给出了引用和指针的一些区别:引用可以看做是别名,但指针并不能看成别名。因此在访问的时候是有一定区别的。

  • 相关阅读:
    Leetcode 238. Product of Array Except Self
    Leetcode 103. Binary Tree Zigzag Level Order Traversal
    Leetcode 290. Word Pattern
    Leetcode 205. Isomorphic Strings
    Leetcode 107. Binary Tree Level Order Traversal II
    Leetcode 102. Binary Tree Level Order Traversal
    三目运算符
    简单判断案例— 分支结构的应用
    用switch判断月份的练习
    java基本打印练习《我行我素购物系统》
  • 原文地址:https://www.cnblogs.com/shaonianpi/p/10261067.html
Copyright © 2011-2022 走看看