• C++基础学习教程(七)----类编写及类的两个特性解析--->多态&继承


    类引入

    到眼下为止我们所写的自己定义类型都是keywordstruct,从如今起我们将採用class方式定义类,这样的方式对于学习过其它高级语言包含脚本(Such as Python)的人来说再熟悉只是了.

    可是在写之前我们还是须要比較一下用struct和class之间有什么差别.

    首先对于struct,在C兼容性方面非常重要,虽然C++是有别于C的还有一门语言,但很多程序还是必须与C交互,C++有两个重要功能,能够方便的与C交互.当中之中的一个的就是POD,即是Plain Old Data(简单旧式数据)的缩写.

    POD类型就是没有其它功能仅用于存储数据的类型.如内置类型就是POD类型,该类型没有其它功能.详细的int类型.一个既无构造函数也没有重载的赋值操作函数而仅有共同拥有的POD类型作为数据成员的类也是一个POD类型.

    POD类型的重要性在于,那些在C++库/第三方库的/操作系统接口中遗留的C函数须要POD类型.

    在编写类之前,我们能够直接看一下class的样例,即是rational的最新版本号,部分代码例如以下:

    /** @file rational_class.cpp */
    /** The Latest Rewrite of the rational Class */
    #include <cassert>
    #include <cstdlib>
    #include <istream>
    #include <ostream>
    #include <sstream>
    
    using namespace std;
    
    /// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
    int gcd(int n, int m)
    {
      n = abs(n);
      while (m != 0) {
        int tmp(n % m);
        n = m;
        m = tmp;
      }
      return n;
    }
    
    /// Represent a rational number (fraction) as a numerator and denominator.
    class rational
    {
    public:
      rational(): numerator_(0), denominator_(1)  {}
      rational(int num): numerator_(num), denominator_(1) {}
    
      rational(int num, int den)
      : numerator_(num), denominator_(den)
      {
        reduce();
      }
    
      rational(double r)
      : numerator_(static_cast<int>(r * 10000)), denominator_(10000)
      {
        reduce();
      }
    
      int numerator()   const { return numerator_; }
      int denominator() const { return denominator_; }
      float as_float()
      const
      {
        return static_cast<float>(numerator()) / denominator();
      }
    
      double as_double()
      const
      {
        return static_cast<double>(numerator()) / denominator();
      }
    
      long double as_long_double()
      const
      {
        return static_cast<long double>(numerator()) / 
               denominator();
      }
    
      /// Assign a numerator and a denominator, then reduce to normal form.
      void assign(int num, int den)
      {
        numerator_ = num;
        denominator_ = den;
        reduce();
      }
    private:
      /// Reduce the numerator and denominator by their GCD.
      void reduce()
      {
        assert(denominator() != 0);
        if (denominator() < 0)
        {
          denominator_ = -denominator();
          numerator_ = -numerator();
        }
        int div(gcd(numerator(), denominator()));
        numerator_ = numerator() / div;
        denominator_ = denominator() / div;
      }
    
      int numerator_;
      int denominator_;
    };
    
    /// Absolute value of a rational number.
    rational abs(rational const& r)
    {
      return rational(abs(r.numerator()), r.denominator());
    }
    
    /// Unary negation of a rational number.
    rational operator-(rational const& r)
    {
      return rational(-r.numerator(), r.denominator());
    }
    
    /// Add rational numbers.
    rational operator+(rational const& lhs, rational const& rhs)
    {
      return rational(
              lhs.numerator() * rhs.denominator() + rhs.numerator() * lhs.denominator(),
              lhs.denominator() * rhs.denominator());
    }
    
    /// Subtraction of rational numbers.
    rational operator-(rational const& lhs, rational const& rhs)
    {
      return rational(
              lhs.numerator() * rhs.denominator() - rhs.numerator() * lhs.denominator(),
              lhs.denominator() * rhs.denominator());
    }
    
    /// Multiplication of rational numbers.
    rational operator*(rational const& lhs, rational const& rhs)
    {
      return rational(lhs.numerator() * rhs.numerator(),
                      lhs.denominator() * rhs.denominator());
    }
    
    /// Division of rational numbers.
    /// TODO: check for division-by-zero
    rational operator/(rational const& lhs, rational const& rhs)
    {
      return rational(lhs.numerator() * rhs.denominator(),
                      lhs.denominator() * rhs.numerator());
    }
    
    /// Compare two rational numbers for equality.
    bool operator==(rational const& a, rational const& b)
    {
      return a.numerator() == b.numerator() and a.denominator() == b.denominator();
    }
    
    /// Compare two rational numbers for inequality.
    inline bool operator!=(rational const& a, rational const& b)
    {
      return not (a == b);
    }
    /// Compare two rational numbers for less-than.
    bool operator<(rational const& a, rational const& b)
    {
      return a.numerator() * b.denominator() < b.numerator() * a.denominator();
    }
    
    /// Compare two rational numbers for less-than-or-equal.
    inline bool operator<=(rational const& a, rational const& b)
    {
      return not (b < a);
    }
    /// Compare two rational numbers for greater-than.
    inline bool operator>(rational const& a, rational const& b)
    {
      return b < a;
    }
    
    /// Compare two rational numbers for greater-than-or-equal.
    inline bool operator>=(rational const& a, rational const& b)
    {
      return not (b > a);
    }
    
    /// Read a rational number.
    /// Format is @em integer @c / @em integer.
    istream& operator>>(istream& in, rational& rat)
    {
      int n(0), d(0);
      char sep('');
      if (not (in >> n >> sep))
        // Error reading the numerator or the separator character.
        in.setstate(in.failbit);
      else if (sep != '/')
      {
        // Push sep back into the input stream, so the next input operation
        // will read it.
        in.unget();
        rat.assign(n, 1);
      }
      else if (in >> d)
        // Successfully read numerator, separator, and denominator.
        rat.assign(n, d);
      else
        // Error reading denominator.
        in.setstate(in.failbit);
    
      return in;
    }
    
    /// Write a rational numbers.
    /// Format is @em numerator @c / @em denominator.
    ostream& operator<<(ostream& out, rational const& rat)
    {
      ostringstream tmp;
      tmp << rat.numerator() << '/' << rat.denominator();
      out << tmp.str();
    
      return out;
    }
    

    类特性

    在上面的代码中,有两个之前没有见过的keyword:public和private.事实上学习过其它高级语言的话非常easy看出这个是訪问级别的限制声名符.顾名思义,public就是公共的訪问级别,对外开放的,而private则不是,他不正确用户开放,用户只能通过public的方法或接口来訪问或改动

    既然介绍了class,就应该说一以下向对象的有关特征.

    类主要包括动作和属性这两样东西.他们的差别在于,属性对于单个对象是独特的,而动作是属于同一类的全部对象多共享的.动作有时也被称为行为.在C++中,类描写叙述了全部对象的行为或动作,以及属性类型.每一个对象都有其自己的属性,并在类中枚举出来.在C++中,成员函数实现动作,并提供堆属性的訪问机制,而数据成员存储属性.

    面向对象编程其它的特点就是继承和多态.

    关于继承

    即是存在父类和子类(派生类),派生类继承了他的父类的非私有属性和行为.

    Liskov置换原则.当派生类特化了一个基类的行为或者属性的时候,则代码中不论什么使用基类的地方代之以继承类的对象是等效的.简单的描写叙述是:假设基类B和派生类D,则在不论什么调用B类型的一个对象环境里,能够无副作用的使用一个D类型对象置换之.

    关于多态,即是变量的类型决定了它所包括的对象的类型.多态的变量能够包括众多的不同类型的对象的一个.特别的,一个基类的变量既能够指代一个该基类的对象,也能够指代由该基类派生的随意类型的一个对象.依据置换原则,能够使用基类变量编写代码,调用基类的随意成员函数,而改代码均会正常工作,不管该对象真正的/派生的类型.

    如今就实际说明一下关于继承的编程方法.首先还是看代码,以下我们构造了一个work类,以及两个派生类book和periodical.

    /** @file calss_inh.cpp */
    /** Defining a Derived Class */
    
    using namespace std;
    class work
    {
    public:
      // 构造函数
      work() 
      : id_(), title_() 
      {}
      // 构造函数
      work(string const& id, string const& title) 
      : id_(id), title_(title) 
      {}
      // 内置方法
      string const& id()    
      const 
      { 
        return id_; 
      }
    
      string const& title()
      const 
      { 
        return title_; 
      }
    // 数据成员
    private:
      string id_;
      string title_;
    };
    
    // 子类 book
    class book : public work
    {
    public:
      // 构造函数
      book() 
      : author_(), pubyear_(0) 
      {}
      // 构造函数
      book(string const& id, string const& title, string const& author,int pubyear)
      : work(id, title), author_(author), pubyear_(pubyear)
      {}
    
      // 内置方法 
      string const& author() 
      const 
      { 
        return author_;
      }
    
      int pubyear()
      const 
      { 
        return pubyear_; 
      }
    // 数据成员
    private:
      string author_;
      int pubyear_; ///< year of publication
    };
    
    // 子类periodical
    class periodical : public work
    {
    public:
      periodical() 
      : volume_(0), number_(0), date_() 
      {}
      periodical(string const& id, string const& title, int volume,
                 int number,string const& date)
      : work(id, title), volume_(volume), number_(number), date_(date)
      {}
    
      // 内置方法
      int volume()              
      const 
      { 
        return volume_; 
      }
    
      int number()              
      const 
      { 
        return number_; 
      }
    
      string const& date() 
      const 
      { 
        return date_; 
      }
    
      // 数据成员
    private:
      int volume_;       ///< volume number
      int number_;       ///< issue number
      string date_;      ///< publication date
    };
    

    义类时候假设使用struct,则默认訪问级别是public,若使用class,则默认訪问级别是private.这些keyword也会影响到类.

    上面的代码中每一个类都有自己的构造函数,每一个类也能够有自己的析构函数,所谓析构函数是指运行类的清理工作的函数.这个函数一样没有返回值,名字是~加上类名字.

    例如以下的代码中,增加了析构函数,执行之后我们能够看出构造函数和析构函数在继承中的执行顺序.代码例如以下:

    /** @file Destructors.cpp */
    /** Order of Calling Destructors */
    #include <iostream>
    #include <ostream>
    
    class base
    {
    public:
      base()  { std::cout << "base::base()
    "; }
      ~base() { std::cout << "base::~base()
    "; }
    };
    
    class middle : public base
    {
    public:
      middle()  { std::cout << "middle::middle()
    "; }
      ~middle() { std::cout << "middle::~middle()
    "; }
    };
    
    class derived : public middle
    {
    public:
      derived()  { std::cout << "derived::derived()
    "; }
      ~derived() { std::cout << "derived::~derived()
    "; }
    };
    
    int main()
    {
      derived d;
    }
    


    结果例如以下:



    假设没有手动编写析构函数,则编译器一样会自己主动生成一个短小的默认析构函数.在运行完析构函数体后,编译器会调用每一个成员函数的析构函数,然后从最后派生的类開始调用全部基类的析构函数.以下一个演示样例代码,你能猜到输出是什么么?

    /** @file Constructors_Destructors.cpp */
    /** Constructors and Destructors */
    #include <iostream>
    #include <ostream>
    #include <vector>
    
    using namespace std;
    
    class base
    {
    public:
        // 构造函数
        base(int value) 
        : value_(value) 
        { 
            cout << "base(" << value << ")
    "; 
        }
        // 构造函数
        base() 
        : value_(0) 
        { 
          cout << "base()
    "; 
        }
        // 构造函数 
        base(base const& copy)
        : value_(copy.value_)
        { 
          cout << "copy base(" << value_ << ")
    "; 
        }
        // 析构函数
        ~base() { cout << "~base(" << value_ << ")
    "; }
        // 
        int value() 
        const 
        { 
          return value_; 
        }
    
        base& operator++()
        {
          ++value_;
          return *this;
        }
        // 私有数据成员
    private:
        int value_;
    };
    
    // 子类
    class derived : public base
    {
    public:
        // 构造函数
        derived(int value)
        : base(value) 
        { 
          cout << "derived(" << value << ")
    "; 
        }
        // 构造函数
        derived() 
        : base() 
        { 
          cout << "derived()
    "; 
        }
        // 构造函数
        derived(derived const& copy)
        : base(copy)
        { 
            cout << "copy derived(" << value() << "
    "; 
        }
        // 析构函数
        ~derived() 
        { 
          cout << "~derived(" << value() << ")
    "; 
        }
    };
    
    // 方法
    derived make_derived()
    {
      return derived(42);
    }
    
    base increment(base b)
    {
      ++b;
      return b;
    }
    
    void increment_reference(base& b)
    {
      ++b;
    }
    
    int main()
    {
      derived d(make_derived());
      base b(increment(d));
      increment_reference(d);
      increment_reference(b);
      derived a(d.value() + b.value());
    }
    

    结果例如以下(图片为反,希望能够自己调试自己思考一下结果).


    tips:


    详细错误例如以下:


    说完继承,我们继续说类的还有一个特性:

    多态.

    要实现多态仅仅须要一个keyword,这个keyword会告诉编译器你须要多态,则编译器会奇妙的实现多态.仅用一个派生类型的对象去初始化一个基类引用类型的变量,则编译后的代码会检查该对象的真实类型,并调用对应的函数,这个keyword是virtual.

    以下一个样例演示样例了一个virtual的print函数,代码:

    /** @file virtual_fun.cpp */
    /** Calling the print Function */
    #include <iostream>
    #include <ostream>
    #include <string>
    
    using namespace std;
    
    /** Adding a Polymorphic print Function to Every Class Derived from work */
    class work
    {
    public:
      work() : id_(), title_() {}
      work(string const& id, string const& title) : id_(id), title_(title) {}
      virtual ~work() {}
      string const& id()    const { return id_; }
      string const& title() const { return title_; }
      virtual void print(ostream& out) const {}
    private:
      string id_;
      string title_;
    };
    
    class book : public work
    {
    public:
      book() : author_(), pubyear_(0) {}
      book(string const& id, string const& title, string const& author,
           int pubyear)
      : work(id, title), author_(author), pubyear_(pubyear)
      {}
      string const& author() const { return author_; }
      int pubyear()               const { return pubyear_; }
      virtual void print(ostream& out) const
      {
        out << author() << ", " << title() << ", " << pubyear() << ".";
      }
    private:
      string author_;
      int pubyear_; ///< year of publication
    };
    
    class periodical : public work
    {
    public:
      periodical() : volume_(0), number_(0), date_() {}
      periodical(string const& id, string const& title, int volume,
                 int number,
     string const& date)
      : work(id, title), volume_(volume), number_(number), date_(date)
      {}
      int volume()              const { return volume_; }
      int number()              const { return number_; }
      string const& date() const { return date_; }
      virtual void print(ostream& out) const
      {
        out << title() << ", " << volume() << '(' << number() << "), " << date() << ".";
      }
    private:
      int volume_;       ///< volume number
      int number_;       ///< issue number
      string date_; ///< publication date
    };
    
    void showoff(work const& w)
    {
      w.print(cout);
      cout << '
    ';
    }
    
    int main()
    {
      book sc("1", "The Sun Also Crashes", "Ernest Lemmingway", 2000);
      book ecpp("2", "Exploring C++", "Ray Lischner", 2008);
      periodical pop("3", "Popular C++", 13, 42, "January 1, 2000");
      periodical today("4", "C++ Today", 1, 1, "January 13, 1984");
    
      showoff(sc);
      showoff(ecpp);
      showoff(pop);
      showoff(today);
    }
    

    结果例如以下:


    上面的代码中,showoff函数无需知道book和periodical,仅仅需关心w是work的一个引用.此处可以调用的函数必须在work类声明过.虽然如此,当showoff调用print时,他会依据该对象的真实类型是book还是periodical来调用对应的函数.

    由于keywordvirtual的原因,C++也把多态函数称作虚函数(virtual function).当某个函数定义为虚函数时候,它在其派生类中也保持虚函数的特性,因此不须要再派生类中使用virtual,可是依旧推荐使用,这样能够方便阅读和识别.在每一个派生类中,对应的虚函数必须是有同样的名字同样的返回类型,而且參数的个数及类型也要相等.

    派生类也可不实现某个虚函数,此时他会把基类的函数像非虚函数一样继承下来.而假设派生类实现了虚函数,则称为覆盖(override)函该数,由于派生类的行为覆盖了本应该继承于基类的行为.



    在上面的图片代码中,说明了showoff函数的參数是引用传递类型的,而不是按值传递,由于假设是按值传递參数,或将一个派生类的对象赋值给基类变量,则会失去多态特性.

    上面的基类work尽管定义了print函数,可是该函数没实用处,为了让他实用,每一个派生类必须覆盖print.而诸如work类的编写者为了确保每一个派生类都会正确的覆盖虚函数,能够省略函数体,而用=0取代之.该符号将函数标记为纯虚函数,表明此函数没有可继承的实现,派生类必须实现该函数(有点类似Java的抽象(类)函数).而对于纯虚函数,编译器增加了一些规则,至少有一个纯虚函数的类称为抽象类.不同意定义抽象类的对象.

    比方讲work类改动成纯虚函数:

     

    /** Defining work as an Abstract Class. */
    class work
    {
    public:
      work() : id_(), title_() {}
      work(std::string const& id, std::string const& title) : id_(id), title_(title) {}
      virtual ~work() {}
      std::string const& id()    const { return id_; }
      std::string const& title() const { return title_; }
      virtual void print(std::ostream& out) const = 0;
    private:
      std::string id_;
      std::string title_;
    };
    

    虽然大部分类不须要手动编写析构函数,可是有一个规则,假设一个类有虚函数,那么该类一定要将析构函数也声明为虚函数.这个不过编程建议,不是语法要求,编译器也不会提示你应该写,可是你要取代编译器,推荐你写.


    That is it.

    Next :

    几个概念:

    声明与定义

    自己主动类型

    静态变量

    静态数据成员

  • 相关阅读:
    【Android】Handler的应用(二):从服务器端加载JSON数据的优化
    [置顶] IOS 开发之 CocoaPods讲解
    POJ 1068 (13.10.11)
    android使用百度地图、定位SDK实现地图和定位功能!(最新、可用+吐槽)
    C++笔记(1)
    WCF讲解
    php5 图片验证码一例
    PHP5 GD库生成图形验证码(汉字)
    mysql中limit的用法实例解析
    Limit参数优化MySQL查询的方法
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/5101743.html
Copyright © 2020-2023  润新知