类引入
到眼下为止我们所写的自己定义类型都是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 :
几个概念:
声明与定义
自己主动类型
静态变量
静态数据成员