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

类引入

到目前为止我们所写的自定义类型都是关键字struct,从现在起我们将采用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('\0');
  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;
}

类特性

在上面的代码中,有两个之前没有见过的关键字:public和private.其实学习过其他高级语言的话很容易看出这个是访问级别的限制声名符.顾名思义,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.这些关键字也会影响到类.

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

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

/** @file Destructors.cpp */
/** Order of Calling Destructors */
#include <iostream>
#include <ostream>

class base
{
public:
  base()  { std::cout << "base::base()\n"; }
  ~base() { std::cout << "base::~base()\n"; }
};

class middle : public base
{
public:
  middle()  { std::cout << "middle::middle()\n"; }
  ~middle() { std::cout << "middle::~middle()\n"; }
};

class derived : public middle
{
public:
  derived()  { std::cout << "derived::derived()\n"; }
  ~derived() { std::cout << "derived::~derived()\n"; }
};

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 << ")\n";
    }
    // 构造函数
    base()
    : value_(0)
    {
      cout << "base()\n";
    }
    // 构造函数
    base(base const& copy)
    : value_(copy.value_)
    {
      cout << "copy base(" << value_ << ")\n";
    }
    // 析构函数
    ~base() { cout << "~base(" << value_ << ")\n"; }
    //
    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 << ")\n";
    }
    // 构造函数
    derived()
    : base()
    {
      cout << "derived()\n";
    }
    // 构造函数
    derived(derived const& copy)
    : base(copy)
    {
        cout << "copy derived(" << value() << "\n";
    }
    // 析构函数
    ~derived()
    {
      cout << "~derived(" << value() << ")\n";
    }
};

// 方法
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:

具体错误如下:

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

多态.

要实现多态只需要一个关键字,这个关键字会告诉编译器你需要多态,则编译器会神奇的实现多态.仅用一个派生类型的对象去初始化一个基类引用类型的变量,则编译后的代码会检查该对象的真实类型,并调用相应的函数,这个关键字是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 << '\n';
}

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来调用相应的函数.

因为关键字virtual的原因,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 :

几个概念:

声明与定义

自动类型

静态变量

静态数据成员

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

时间: 2024-10-12 17:30:31

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

C++基础学习教程(六)----类编写的前情回顾以及项目实战(1)

在开始类的编写之前我们依然需要回顾整理一下前面所说的内容,(前面虽然是一个自定义数据类型的实现过程,但是内容有点繁杂). 先看一段代码: /** @file calssStruct.cpp */ /** Member Functions for Class point */ #include <cmath> // for sqrt and atan using namespace std; struct point { point() : x_(0.0), y_(0.0) {} point(d

C++基础学习教程(一)

开始自己的C++复习进阶之路. 声明: 这次写的博文纯当是一个回顾复习的教程,一些非常基础的知识将不再出现,或者一掠而过,这次的主要风格就是示例代码很多~~~ 所有代码在Ubuntu 14.04 LTS 版,GCC4.8.1(g++)编译通过.其他的平台没试过,估计有些代码在VC6.0下面通过不了,因为有些语言特性是C++11标准的. 下面就是正文的开始吧. 一.C++必须说和必须略过的一些东西 1.工具 工具的话,简答的编程貌似现在已经习惯了在GCC(g++)下了.Linux平台下面,一般不需

C++基础学习教程(二)

接上一节内容 2.5条件和逻辑 自增和自减操作符 这个主要区别就是在前和后,大多数学习过其他语言的应该都知道.所以,一个程序带过. 示例如下: /************************************************************************* > File Name: list1001_++.cpp > Author: suool_hu > Mail: [email protected] > Created Time: 2014年0

C++基础学习教程(三)

承接上一讲. 2.7文件I/O 关于读写文件,C++中有一个专门的头文件<fstream>. 首先是读文件示例,如下: </pre><pre> /************************************************************************* > File Name: list1301_file.cpp > Author: suool > Mail: [email protected] > Cre

C++基础学习教程(八)

转载请注明出处:http://blog.csdn.net/suool/article/details/38300117 引入 在进行下一步的学习之前,我们需要厘清几个概念. RAII 首先介绍一个编程习语,"RAII"(ResourceAcquisition Is Initialization,资源获取即为初始化),他描述了利用构造函数\析构函数,并在函数返回时自动析构的机制.简言之,RAII意为构造函数获取一种资源;打开一个文件,一个网络连接,或仅仅是从某I/O流中复制一些标志.这种

C++基础学习教程(四)

2.9字符专题 2.9.1类型同义词 也就是typedef声明,这个东西就是相当于起绰号,为了方便记忆和简化而生.相信在学习其他语言的时候一定有所了解,在此不再赘述. 再次示例一个之前写过的用typedef改写的程序: /************************************************************************* > File Name: char_count.cpp > Author: suool > Mail: [email pr

Android基础入门教程——8.3.2 绘图类实战示例

Android基础入门教程--8.3.2 绘图类实战示例 标签(空格分隔): Android基础入门教程 本节引言: 前两节我们学了Bitmap和一些基本的绘图API的属性以及常用的方法,但心里总觉得有点 不踏实,总得写点什么加深下映像是吧,嗯,本节我们就来写两个简单的例子: 1.简单画图板的实现 2.帮美女擦衣服的简单实现 嘿嘿,第二个例子是小猪刚学安卓写的一个小Demo~嘿嘿~ 开始本节内容~ 1.实战示例1:简单画图板的实现: 这个相信大家都不陌生,很多手机都会自带一个给用户涂鸦的画图板,

Java 从基础到进阶学习之路---类编写以及文档凝视.

Java之前在学习过,基础知识还没有忘光,并且这些高级语言实在是太像,所以那些数据类型,或者循环控制流,以及标准设备等等就直接略过不说了. 只是一些重大概念会穿插在文章的介绍中. So,这些文章适合于那些有一定高级面向对象语言基础的人阅读. 我们首先编写一个学生类.其主要要求要熟悉的内容是: 关于类构造器(构造方法)的认识和理解. 关于方法的编写. 关于成员变量 & this 的使用, 熟悉了这些内容后,我们就能够依照以下的类图编写这个类了. 构造器的參数有姓名,性别,学号. 类图例如以下: w

Java 从基础到进阶学习之路---类编写以及文档注释.

Java之前在学习过,基础知识还没有忘光,而且这些高级语言实在是太像,所以那些数据类型,或者循环控制流,以及标准设备等等就直接略过不说了. 不过一些重大概念会穿插在文章的介绍中. So,这些文章适合于那些有一定高级面向对象语言基础的人阅读. 我们首先编写一个学生类.其主要要求要熟悉的内容是: 关于类构造器(构造方法)的认识和理解. 关于方法的编写. 关于成员变量 & this 的使用, 熟悉了这些内容后,我们就可以按照下面的类图编写这个类了. 构造器的参数有姓名,性别,学号. 类图如下: 我写的