类引入

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

结果例如以下:

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

/** @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:

详细错误例如以下:

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

多态.

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

结果例如以下:

上面的代码中,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 :

几个概念:

声明与定义

自己主动类型

静态变量

静态数据成员

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

  1. Java基础学习笔记七 Java基础语法之继承和抽象类

    继承 继承的概念 在现实生活中,继承一般指的是子女继承父辈的财产.在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系. 例如公司中的研发部员工和维护部员工都属于员工, ...

  2. Scanner,Random,匿名对象-------------------java基础学习第七天

    1.API 2.Scanner 功能:通过键盘输入数据到程序中. 引用类型的一般使用步骤: 导包 Import 包路径.类名称 只有java.lang 包写的类不需要导包,其他都需要 2.创建 类名称 ...

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

    在開始类的编写之前我们依旧须要回想整理一下前面所说的内容,(前面尽管是一个自己定义数据类型的实现过程,可是内容有点繁杂). 先看一段代码: /** @file calssStruct.cpp */ / ...

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

    Java之前在学习过,基础知识还没有忘光,并且这些高级语言实在是太像,所以那些数据类型,或者循环控制流,以及标准设备等等就直接略过不说了. 只是一些重大概念会穿插在文章的介绍中. So,这些文章适合于 ...

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

    转载请注明出处:http://blog.csdn.net/suool/article/details/38300117 引入 在进行下一步的学习之前,我们须要厘清几个概念. RAII 首先介绍一个编程 ...

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

    開始自己的C++复习进阶之路. 声明: 这次写的博文纯当是一个回想复习的教程.一些非常基础的知识将不再出现.或者一掠而过,这次的主要风格就是演示样例代码非常多~~~ 全部代码在Ubuntu 14.04 ...

  7. webpack从0到1超详细超基础学习教程

    概念 自己是一个一听到webpack就头大,看着一堆不知道那是什么玩意的东西总觉得自己做好前端就行了,但是在使用vue-cli的时候总觉得要改其中的一些东西进行项目初始化的时候能够更好使用!所以想要根 ...

  8. salesforce零基础学习(七十六)顺序栈的实现以及应用

    数据结构中,针对线性表包含两种结构,一种是顺序线性表,一种是链表.顺序线性表适用于查询,时间复杂度为O(1),增删的时间复杂度为O(n).链表适用于增删,时间复杂度为O(1),查询的时间复杂度为O(n ...

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

    承接上一讲. 2.7文件I/O 关于读写文件,C++中有一个专门的头文件<fstream>. 首先是读文件演示样例,例如以下: </pre><pre> /***** ...

随机推荐

  1. HDU——T 2594 Simpsons’ Hidden Talents

    http://acm.hdu.edu.cn/showproblem.php?pid=2594 Time Limit: 2000/1000 MS (Java/Others)    Memory Limi ...

  2. UVa 11743 - Credit Check

    题目:推断卡号是否合法,给你4组4位的数字.偶数位的2倍的位和加上奇数位的和,推断尾数是否为0. 分析:简单题,模拟. 直接依照提议推断就可以. 说明:460题,加油! #include <io ...

  3. 玩转Bash脚本:选择结构之case

    总第5篇 之前,我们谈到了if. 这次我们来谈还有一种选择结构--case. case与if if用于选择的条件,不是非常多的情况,假设选择的条件太多.一系列的if.elif,.也是醉了. 没错,ca ...

  4. golang标准包中文手册

    golang标准包中文手册 http://files.cnblogs.com/files/rojas/liudiwu-pkgdoc-master.zip

  5. Node中的JavaScript和浏览器中的JavaScript的区别

    浏览器中的JavaScript: 1.基于ECMAscript规范,这个规范规定了语法 2.添加了dom:用来处理文档 document object model 3.添加了BOM:用于操作浏览器 w ...

  6. 邮件协议与port

          电子邮箱的协议有SMTP.POP2.POP3.IMAP4等.都隶属于TCP/IP协议簇,默认状态下.分别通过TCPport25.110和143建立连接.针对不同的用途和功能,我们在邮件se ...

  7. Moodle 中文 API 之 文件管理API

    File API  文件管理 文件夹 1. 概述 2. 文件域 2.1 命名文件域 3. 提供文件给用户 4. 从用户那获取文件 5. 样例 5.1 浏览文件 5.2 移动文件 5.3 文件列表 5. ...

  8. HDU 5389 Zero Escape (MUT#8 dp优化)

    [题目链接]:pid=5389">click here~~ [题目大意]: 题意: 给出n个人的id,有两个门,每一个门有一个标号,我们记作a和b,如今我们要将n个人分成两组,进入两个 ...

  9. windows系统自带工具

    辅助功能向导:单击"開始→执行",在弹出的对话框中输入:accwiz 计算器:单击"開始→执行",在弹出的对话框中输入:calc 字符影射表:单击"開 ...

  10. elasticsearch 源码概述

    从功能上说,可以分为两部分,分布式功能和数据功能.分布式功能主要是节点集群及集群附属功能如restful借口.集群性能检测功能等,数据功能主要是索引和搜索.代码上这些功能并不是完全独立,而是由相互交叉 ...