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

时间:2022-01-01 09:56:36

类引入

到眼下为止我们所写的自己定义类型都是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('\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_;
}; // 子类 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()\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;
}

结果例如以下:

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

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

/** @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());
}

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

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

tips:

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

详细错误例如以下:

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

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

多态.

要实现多态仅仅须要一个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 << '\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);
}

结果例如以下:

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

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

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

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

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

在上面的图片代码中,说明了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 :

几个概念:

声明与定义

自己主动类型

静态变量

静态数据成员