博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++基础学习教程(七)----类编写及类的两个特性解析--->多态&继承
阅读量:6843 次
发布时间:2019-06-26

本文共 13066 字,大约阅读时间需要 43 分钟。

类引入

到眼下为止我们所写的自己定义类型都是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 
#include
#include
#include
#include
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
(r * 10000)), denominator_(10000) { reduce(); } int numerator() const { return numerator_; } int denominator() const { return denominator_; } float as_float() const { return static_cast
(numerator()) / denominator(); } double as_double() const { return static_cast
(numerator()) / denominator(); } long double as_long_double() const { return static_cast
(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-zerorational 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;}

类特性

在上面的代码中,有两个之前没有见过的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_;};// 子类 bookclass 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};// 子类periodicalclass 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 
#include
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 
#include
#include
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:

详细错误例如以下:

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

多态.

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

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

/** @file virtual_fun.cpp *//** Calling the print Function */#include 
#include
#include
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来调用对应的函数.

由于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 :

几个概念:

声明与定义

自己主动类型

静态变量

静态数据成员

转载地址:http://lmbul.baihongyu.com/

你可能感兴趣的文章
RocketMQ架构模块解析
查看>>
物联网时代需要开放、好用及可信的平台
查看>>
Android Monkey测试
查看>>
Intel芯将整合雷电技术 未来MBP因此便宜点
查看>>
常见物联网近距离无线通信技术解析
查看>>
《嵌入式 Linux C 语言应用程序设计(修订版)》——2.2 嵌入式Linux编辑器vi的使用...
查看>>
黑客发现 Adobe Flash 播放器第二个零日漏洞
查看>>
Docker —— 用于统一开发和部署的轻量级 Linux 容器 【已翻译100%】
查看>>
《初级会计电算化应用教程(金蝶KIS专业版)》——1.3 电算化会计信息系统
查看>>
Android 开发者应该使用 FlatBuffers 替代 JSON ?
查看>>
《拥抱变化——社交网络时代的企业转型之道》一找准组织目标和企业文化
查看>>
《Arduino奇妙之旅:智能车趣味制作天龙八步》一3.2 构建小发明
查看>>
《Cisco安全防火墙服务模块(FWSM)解决方案》——第2章防火墙服务模块概述
查看>>
Go语言项目(kingshard)性能优化实例剖析
查看>>
安全预警:ImageMagick 图象处理软件存在远程代码执行(CVE-2016-3714)
查看>>
《ANSYS Workbench 14有限元分析自学手册》——2.5 体操作
查看>>
《交互式程序设计 第2版》一第1章 交互设计导论
查看>>
ARM拟将ARM核心置入未来的Fusion APU中
查看>>
3分钟参与阿里云《金融行业云上信任报告》调查问卷,98%的几率拿到代金券
查看>>
《C语言编程魔法书:基于C11标准》——2.9 本章小结
查看>>