上一节学习C++中的继承和派生:c++:-4,本节学习C++的多态。

运算符重载

思考:用“+”、“-”能够实现复数的加减运算吗?

  • 实现复数加减运算的方法 ——重载“+”、“-”运算符
  • 运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。

    C++ 几乎可以重载全部的运算符,而且只能够重载C++中已经有的
  • 不能重载的运算符:“.”、“.*”、“::”、“?:”
  • 重载之后运算符的优先级和结合性都不会改变。
  • 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。

    例如:
  • 使复数类的对象可以用“+”运算符实现加法;
  • 是时钟类对象可以用“++”运算符实现时间增加1秒。
  • 重载为类的非静态成员函数;
  • 重载为非成员函数。

重载为成员函数

单目运算符重载

重载为类成员的运算符函数定义形式:

函数类型  operator 运算符(形参)
{
......
}
参数个数=原操作数个数-1 (后置++、--除外)

(1)前置单目运算符重载规则:

  • 如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。
  • 经重载后,表达式 U oprd 相当于 oprd.operator U()

(2)后置单目运算符 ++和--重载规则

  • 如果要重载 ++或--为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中 oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数,且具有一个 int 类型形参。
  • 经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)

举例:

重载前置++和后置++为时钟类成员函数

  • 前置单目运算符,重载函数没有形参
  • 后置++运算符,重载函数需要有一个int形参
  • 操作数是时钟类的对象。
  • 实现时间增加1秒钟。
#include <iostream>
using namespace std; class Clock {//时钟类定义
public:
Clock(int hour = 0, int minute = 0, int second = 0);
void showTime() const;
//前置单目运算符重载
Clock& operator ++ ();
//后置单目运算符重载
Clock operator ++ (int);
private:
int hour, minute, second;
}; Clock::Clock(int hour, int minute, int second) {
if (0 <= hour && hour < 24 && 0 <= minute && minute < 60
&& 0 <= second && second < 60) {
this->hour = hour;
this->minute = minute;
this->second = second;
} else
cout << "Time error!" << endl;
}
void Clock::showTime() const { //显示时间
cout << hour << ":" << minute << ":" << second << endl;
} Clock & Clock::operator ++ () {
second++;
if (second >= 60) {
second -= 60; minute++;
if (minute >= 60) {
minute -= 60; hour = (hour + 1) % 24;
}
}
return *this;//返回的是对象引用,先加1再返回
} Clock Clock::operator ++ (int) {
//注意形参表中的整型参数
Clock old = *this;
++(*this); //调用前置“++”运算符
return old;//先返回,再加1
} int main() {
Clock myClock(23, 59, 59);
cout << "First time output: ";
myClock.showTime();
cout << "Show myClock++: ";
(myClock++).showTime();
cout << "Show ++myClock: ";
(++myClock).showTime();
return 0;
}
输出:
First time output: 23:59:59
Show myClock++: 23:59:59
Show ++myClock: 0:0:1

双目运算符重载

  • 如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。
  • 经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)

举例:

复数类加减法运算重载为成员函数

(1)要求:

将+、-运算重载为复数类的成员函数。

(2)规则:

实部和虚部分别相加减。

(3)操作数:

两个操作数都是复数类的对象。

#include <iostream>
using namespace std; class Complex {
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
//运算符+重载成员函数
Complex operator + (const Complex &c2) const;
//运算符-重载成员函数
Complex operator - (const Complex &c2) const;
void display() const; //输出复数
private:
double real; //复数实部
double imag; //复数虚部
}; Complex Complex::operator+(const Complex &c2) const{
//创建一个临时无名对象作为返回值
return Complex(real+c2.real, imag+c2.imag);
} Complex Complex::operator-(const Complex &c2) const{
//创建一个临时无名对象作为返回值
return Complex(real-c2.real, imag-c2.imag);
} void Complex::display() const {
cout<<"("<<real<<", "<<imag<<")"<<endl;
} int main() {
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1 = "; c1.display();
cout << "c2 = "; c2.display();
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 - c2 = "; c3.display();
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = "; c3.display();
return 0;
}
输出:
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)

重载为非成员函数

有些运算符不能重载为成员函数,例如二元运算符的左操作数不是对象,或者是不能由我们重载运算符的对象

  • 函数的形参代表依自左至右次序排列的各操作数。
  • 重载为非成员函数时
  • 参数个数=原操作数个数(后置++、--除外)
  • 至少应该有一个自定义类型的参数。
  • 后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名。
  • 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。

运算符重载为非成员函数的规则

(1)双目运算符 B重载后,

表达式oprd1 B oprd2
等同于operator B(oprd1,oprd2 )

(2)前置单目运算符 B重载后,

表达式 B oprd
等同于operator B(oprd )

(3)后置单目运算符 ++和--重载后,

表达式 oprd B
等同于operator B(oprd,0 )

(4)举例

重载Complex的加减法和“<<”运算符为非成员函数

  • 将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。
  • 将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用,用以支持下面形式的输出:
cout << a << b;

该输出调用的是:

operator << (operator << (cout, a), b);
#include <iostream>
using namespace std; class Complex {
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
friend Complex operator+(const Complex &c1, const Complex &c2);
friend Complex operator-(const Complex &c1, const Complex &c2);
friend ostream & operator<<(ostream &out, const Complex &c);
private:
double real; //复数实部
double imag; //复数虚部
}; Complex operator+(const Complex &c1, const Complex &c2){
return Complex(c1.real+c2.real, c1.imag+c2.imag);//构造一个临时对象,调用构造函数
}
Complex operator-(const Complex &c1, const Complex &c2){
return Complex(c1.real-c2.real, c1.imag-c2.imag);
} ostream & operator<<(ostream &out, const Complex &c){
out << "(" << c.real << ", " << c.imag << ")";
return out;
} int main() {
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1 = " << c1 << endl;//使用重载运算负完成复数的输出
cout << "c2 = " << c2 << endl;//使用重载运算负完成复数的输出
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 - c2 = " << c3 << endl;//使用重载运算负完成复数的输出
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = " << c3 << endl;//使用重载运算负完成复数的输出
return 0;
}
输出:
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)

虚函数

介绍

  • 用virtual关键字说明的函数
  • 虚函数是实现运行时多态性基础
  • C++中的虚函数是动态绑定的函数
  • 虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
  • 一般成员函数可以是虚函数
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数

一般虚函数成员

  • 虚函数的声明:

    virtual 函数类型 函数名(形参表)
  • 虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候
  • 在派生类中可以对基类中的成员函数进行覆盖
  • 虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。

virtual 关键字

(1)派生类可以不显式地用virtual声明虚函数,这时系统就会用以下规则来判断派生类的一个函数成员是不是虚函数:

  • 该函数是否与基类的虚函数有相同的名称、参数个数及对应参数类型;
  • 该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针、引用型的返回值;

(2)如果从名称、参数及返回值三个方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。

(3)派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。

(4)一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。

举例:通过虚函数实现运行时多态

#include <iostream>
using namespace std; class Base1 {
public:
virtual void display() const; //虚函数
};
void Base1::display() const {
cout << "Base1::display()" << endl;
} class Base2: public Base1 {
public:
virtual void display() const;
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 {
public:
virtual void display() const;
};
void Derived::display() const {
cout << "Derived::display()" << endl;
} void fun(Base1 *ptr) {
ptr->display();
} int main() {
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
return 0;
}
输出:
Base1::display()
Base2::display()
Derived::display()

虚析构函数

为什么需要虚析构函数?

  • 可能通过基类指针删除派生类对象;
  • 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。

举例:

//不使用虚析构函数
#include <iostream>
using namespace std; class Base {
public:
~Base(); //不是虚函数
};
Base::~Base() {
cout<< "Base destructor" << endl;
} class Derived: public Base{
public:
Derived();
~Derived(); //不是虚函数
private:
int *p;
}; //使用虚析构函数
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base();
};
Base::~Base() {
cout<< "Base destructor" << endl;
}
class Derived: public Base{
public:
Derived();
virtual ~Derived();
private:
int *p;
};

虚表与动态绑定

虚表

  • 每个多态类有一个虚表(virtual table)
  • 虚表中有当前类的各个虚函数的入口地址
  • 每个对象有一个指向当前类的虚表的指针(虚指针vptr)

动态绑定

  • 构造函数中为对象的虚指针赋值
  • 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
  • 通过该入口地址调用虚函数

抽象类

纯虚函数

纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:

virtual 函数类型 函数名(参数表) = 0;

抽象类

带有纯虚函数的类称为抽象类:

class 类名 {
virtual 类型 函数名(参数表)=0;
//其他成员……
}

抽象类作用

  • 抽象类为抽象和设计的目的而声明
  • 将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
  • 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。

注意

  • 抽象类只能作为基类来使用。
  • 不能定义抽象类的对象。

举例:

#include <iostream>
using namespace std; //Base1为抽象类
class Base1 {
public:
virtual void display() const = 0; //纯虚函数
}; class Base2: public Base1 {
public:
virtual void display() const; //覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::display()" << endl;
} class Derived: public Base2 {
public:
virtual void display() const; //覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) {
ptr->display();
}
int main() {
Base2 base2;
Derived derived;
fun(&base2);
fun(&derived);
return 0;
}
输出:
Base2::display()
Derived::display()

override和final

override

  • 多态行为的基础:基类声明虚函数,继承类声明一个函数覆盖该虚函数
  • 覆盖要求: 函数签名(signatture)完全一致
  • 函数签名包括:函数名 参数列表 const

下列程序就仅仅因为疏忽漏写了const,导致多态行为没有如期进行:

(1)显式函数覆盖

  • C++11 引入显式函数覆盖,在编译期而非运行期捕获此类错误。
  • 在虚函数显式重载中运用,编译器会检查基类是否存在一虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。

final

C++11提供的final,用来避免类被继承,或是基类的函数被改写例:

struct Base1 final { };
struct Derived1 : Base1 { }; // 编译错误:Base1为final,不允许被继承
struct Base2 { virtual void f() final; };
struct Derived2 : Base2 { void f(); // 编译错误:Base2::f 为final,不允许被覆盖 };

程序

(1)

#include <iostream>
using namespace std; class Point
{
int _x, _y;
public:
Point(int x=0, int y=0) : _x(x), _y(y) {}
Point& operator++();
Point operator++(int);
Point& operator--();
Point operator--(int);
friend ostream & operator << (ostream &o, const Point &p);
};
Point& Point::operator++()
{
_x++;
_y++;
return *this;
}
/* ++i在C++里面的定义最后返回的是被++的对象的引用(系统就是这么实现的),所以++i可以作为左值,即可以写:++i=3 */ //后缀式操作符接受一个额外的int型形参(不会使用它,仅做区分用)
Point Point::operator++(int)
{
Point temp = *this;
++*this; //复用了前缀++的重载
return temp; //后缀式版本中,返回值是尚未自增的原值,但对象本身已经做了自增操作了。
}
/* i++在C++里面的定义是,最后返回的是被++的对象的值(系统就是这么实现的),所以i++不可以作为左值,即不可以写:i++=3 */
Point& Point::operator--()
{
_x--;
_y--;
return *this;
}
Point Point::operator--(int)
{
Point temp = *this;
--*this;
return temp;
}
//友元函数,返回值类型为ostream&,可以支持<<级连操作
ostream & operator<<(ostream &o, const Point &p) {
o << '(' << p._x << ", " << p._y << ')';
return o;
} int main()
{
Point p(1, 2);
cout << p << endl;
cout << p++ << endl;
cout << ++p << endl;
cout << p-- << endl;
cout << --p << endl;
return 0;
}
输出:
(1, 2)
(1, 2)
(3, 4)
(3, 4)
(1, 2)

(2)

#include <iostream>
using namespace std; class Vehicle
{
public:
int MaxSpeed;
int Weight;
//void Run() {cout << "vehicle run!" << endl;}
//void Stop() {cout << "vehicle stop!" << endl;}
virtual void Run() {cout << "vehicle run!" << endl;}
virtual void Stop() {cout << "vehicle stop!" << endl;}
}; class Bicycle : virtual public Vehicle
{
public:
int Height;
void Run() {cout << "bicycle run!" << endl;}
void Stop() {cout << "bicycle stop!" << endl;}
}; class Motorcar : virtual public Vehicle
{
public:
int SeatNum;
void Run() {cout << "motocar run!" << endl;}
void Stop() {cout << "motocar stop!" << endl;}
}; class Motorcycle : public Bicycle, public Motorcar
{
public:
void Run() {cout << "motocycle run!" << endl;}
void Stop() {cout << "motocycle stop!" << endl;}
}; int main()
{
Vehicle v;
v.Run();
v.Stop();
Bicycle b;
b.Run();
b.Stop();
Motorcar m;
m.Run();
m.Stop();
Motorcycle mc;
mc.Run();
mc.Stop();
Vehicle* vp = &v;
vp->Run();
vp->Stop();
vp = &b;
vp->Run();
vp->Stop();
vp = &m;
vp->Run();
vp->Stop();
vp = &mc;
vp->Run();
vp->Stop();
return 0;
}
输出:
vehicle run!
vehicle stop!
bicycle run!
bicycle stop!
motocar run!
motocar stop!
motocycle run!
motocycle stop!
vehicle run!
vehicle stop!
bicycle run!
bicycle stop!
motocar run!
motocar stop!
motocycle run!
motocycle stop!

(3)

//date.h
#ifndef __DATE_H__
#define __DATE_H__ class Date { //日期类
private:
int year; //年
int month; //月
int day; //日
int totalDays; //该日期是从公元元年1月1日开始的第几天 public:
Date(int year, int month, int day); //用年、月、日构造日期
int getYear() const { return year; }
int getMonth() const { return month; }
int getDay() const { return day; }
int getMaxDay() const; //获得当月有多少天
bool isLeapYear() const { //判断当年是否为闰年
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
void show() const; //输出当前日期
//计算两个日期之间差多少天
int operator - (const Date& date) const {
return totalDays - date.totalDays;
}
}; #endif //__DATE_H__ //accumulator.h
#ifndef __ACCUMULATOR_H__
#define __ACCUMULATOR_H__
#include "date.h" class Accumulator { //将某个数值按日累加
private:
Date lastDate; //上次变更数值的时期
double value; //数值的当前值
double sum; //数值按日累加之和
public:
//构造函数,date为开始累加的日期,value为初始值
Accumulator(const Date &date, double value)
: lastDate(date), value(value), sum(0) { } //获得到日期date的累加结果
double getSum(const Date &date) const {
return sum + value * (date - lastDate);
} //在date将数值变更为value
void change(const Date &date, double value) {
sum = getSum(date);
lastDate = date;
this->value = value;
} //初始化,将日期变为date,数值变为value,累加器清零
void reset(const Date &date, double value) {
lastDate = date;
this->value = value;
sum = 0;
}
}; #endif //__ACCUMULATOR_H__ //account.h
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
#include "date.h"
#include "accumulator.h"
#include <string> class Account { //账户类
private:
std::string id; //帐号
double balance; //余额
static double total; //所有账户的总金额
protected:
//供派生类调用的构造函数,id为账户
Account(const Date &date, const std::string &id);
//记录一笔帐,date为日期,amount为金额,desc为说明
void record(const Date &date, double amount, const std::string &desc);
//报告错误信息
void error(const std::string &msg) const;
public:
const std::string &getId() const { return id; }
double getBalance() const { return balance; }
static double getTotal() { return total; }
//存入现金,date为日期,amount为金额,desc为款项说明
virtual void deposit(const Date &date, double amount, const std::string &desc) = 0;
//取出现金,date为日期,amount为金额,desc为款项说明
virtual void withdraw(const Date &date, double amount, const std::string &desc) = 0;
//结算(计算利息、年费等),每月结算一次,date为结算日期
virtual void settle(const Date &date) = 0;
//显示账户信息
virtual void show() const;
}; class SavingsAccount : public Account { //储蓄账户类
private:
Accumulator acc; //辅助计算利息的累加器
double rate; //存款的年利率
public:
//构造函数
SavingsAccount(const Date &date, const std::string &id, double rate);
double getRate() const { return rate; }
virtual void deposit(const Date &date, double amount, const std::string &desc);
virtual void withdraw(const Date &date, double amount, const std::string &desc);
virtual void settle(const Date &date);
}; class CreditAccount : public Account { //信用账户类
private:
Accumulator acc; //辅助计算利息的累加器
double credit; //信用额度
double rate; //欠款的日利率
double fee; //信用卡年费 double getDebt() const { //获得欠款额
double balance = getBalance();
return (balance < 0 ? balance : 0);
}
public:
//构造函数
CreditAccount(const Date &date, const std::string &id, double credit, double rate, double fee);
double getCredit() const { return credit; }
double getRate() const { return rate; }
double getFee() const { return fee; }
double getAvailableCredit() const { //获得可用信用
if (getBalance() < 0)
return credit + getBalance();
else
return credit;
}
virtual void deposit(const Date &date, double amount, const std::string &desc);
virtual void withdraw(const Date &date, double amount, const std::string &desc);
virtual void settle(const Date &date);
virtual void show() const;
}; #endif //__ACCOUNT_H__ //date.cpp
#include "date.h"
#include <iostream>
#include <cstdlib>
using namespace std; namespace { //namespace使下面的定义只在当前文件中有效
//存储平年中某个月1日之前有多少天,为便于getMaxDay函数的实现,该数组多出一项
const int DAYS_BEFORE_MONTH[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
} Date::Date(int year, int month, int day) : year(year), month(month), day(day) {
if (day <= 0 || day > getMaxDay()) {
cout << "Invalid date: ";
show();
cout << endl;
exit(1);
}
int years = year - 1;
totalDays = years * 365 + years / 4 - years / 100 + years / 400
+ DAYS_BEFORE_MONTH[month - 1] + day;
if (isLeapYear() && month > 2) totalDays++;
} int Date::getMaxDay() const {
if (isLeapYear() && month == 2)
return 29;
else
return DAYS_BEFORE_MONTH[month]- DAYS_BEFORE_MONTH[month - 1];
} void Date::show() const {
cout << getYear() << "-" << getMonth() << "-" << getDay();
} //account.cpp
#include "account.h"
#include <cmath>
#include <iostream>
using namespace std; double Account::total = 0; //Account类的实现
Account::Account(const Date &date, const string &id)
: id(id), balance(0) {
date.show();
cout << "\t#" << id << " created" << endl;
} void Account::record(const Date &date, double amount, const string &desc) {
amount = floor(amount * 100 + 0.5) / 100; //保留小数点后两位
balance += amount;
total += amount;
date.show();
cout << "\t#" << id << "\t" << amount << "\t" << balance << "\t" << desc << endl;
} void Account::show() const {
cout << id << "\tBalance: " << balance;
} void Account::error(const string &msg) const {
cout << "Error(#" << id << "): " << msg << endl;
} //SavingsAccount类相关成员函数的实现
SavingsAccount::SavingsAccount(const Date &date, const string &id, double rate)
: Account(date, id), rate(rate), acc(date, 0) { } void SavingsAccount::deposit(const Date &date, double amount, const string &desc) {
record(date, amount, desc);
acc.change(date, getBalance());
} void SavingsAccount::withdraw(const Date &date, double amount, const string &desc) {
if (amount > getBalance()) {
error("not enough money");
} else {
record(date, -amount, desc);
acc.change(date, getBalance());
}
} void SavingsAccount::settle(const Date &date) {
if (date.getMonth() == 1) { //每年的一月计算一次利息
double interest = acc.getSum(date) * rate
/ (date - Date(date.getYear() - 1, 1, 1));
if (interest != 0)
record(date, interest, "interest");
acc.reset(date, getBalance());
}
} //CreditAccount类相关成员函数的实现
CreditAccount::CreditAccount(const Date& date, const string& id, double credit, double rate, double fee)
: Account(date, id), credit(credit), rate(rate), fee(fee), acc(date, 0) { } void CreditAccount::deposit(const Date &date, double amount, const string &desc) {
record(date, amount, desc);
acc.change(date, getDebt());
} void CreditAccount::withdraw(const Date &date, double amount, const string &desc) {
if (amount - getBalance() > credit) {
error("not enough credit");
} else {
record(date, -amount, desc);
acc.change(date, getDebt());
}
} void CreditAccount::settle(const Date &date) {
double interest = acc.getSum(date) * rate;
if (interest != 0)
record(date, interest, "interest");
if (date.getMonth() == 1)
record(date, -fee, "annual fee");
acc.reset(date, getDebt());
} void CreditAccount::show() const {
Account::show();
cout << "\tAvailable credit:" << getAvailableCredit();
} //main.cpp
#include "account.h"
#include <iostream>
using namespace std; int main() {
Date date(2008, 11, 1); //起始日期
//建立几个账户
SavingsAccount sa1(date, "S3755217", 0.015);
SavingsAccount sa2(date, "02342342", 0.015);
CreditAccount ca(date, "C5392394", 10000, 0.0005, 50);
Account *accounts[] = { &sa1, &sa2, &ca };
const int n = sizeof(accounts) / sizeof(Account*); //账户总数 cout << "(d)deposit (w)withdraw (s)show (c)change day (n)next month (e)exit" << endl;
char cmd;
do {
//显示日期和总金额
date.show();
cout << "\tTotal: " << Account::getTotal() << "\tcommand> "; int index, day;
double amount;
string desc; cin >> cmd;
switch (cmd) {
case 'd': //存入现金
cin >> index >> amount;
getline(cin, desc);
accounts[index]->deposit(date, amount, desc);
break;
case 'w': //取出现金
cin >> index >> amount;
getline(cin, desc);
accounts[index]->withdraw(date, amount, desc);
break;
case 's': //查询各账户信息
for (int i = 0; i < n; i++) {
cout << "[" << i << "] ";
accounts[i]->show();
cout << endl;
}
break;
case 'c': //改变日期
cin >> day;
if (day < date.getDay())
cout << "You cannot specify a previous day";
else if (day > date.getMaxDay())
cout << "Invalid day";
else
date = Date(date.getYear(), date.getMonth(), day);
break;
case 'n': //进入下个月
if (date.getMonth() == 12)
date = Date(date.getYear() + 1, 1, 1);
else
date = Date(date.getYear(), date.getMonth() + 1, 1);
for (int i = 0; i < n; i++)
accounts[i]->settle(date);
break;
}
} while (cmd != 'e');
return 0;
}

上面的例子值得反复去看!

习题

重载

(1)下列选项中,与实现运行时多态性无关的是

  • 重载函数(对)
  • 虚函数
  • 指针
  • 引用

分析:B运行时多态与虚函数有关。基类的对象调用其虚函数,会调用到最新重载的子类的该函数。

对于CD:派生类的对象可以认为是基类的对象,但基类的对象不是其派生类的对象。因此,C++允许一个基类对象的指针指向其派生类对象,但不允许一个派生类对象指向其基类对象。在调用虚函数的过程中指针和引用会起到一定的作用。

(2)将运算符“+”重载为非成员函数,下列原型声明中,错误的是

  • MyClock operator+(MyClock, long);
  • MyCIock operator+(MyClock, MyCIock);
  • MyClock operator+(long, long);(错)
  • MyCIock operator+(long,MyClock);

分析:C选项是在重载long之间的加法,而基础数据类型之间的操作符无法重载

(3)将运算符重载为类成员函数时,其参数表中没有参数,说明该运算符是:

  • 不合法的运算符
  • 一元运算符(对)
  • 无操作数的运算符
  • 二元运算符

(4)已知表达式++a中的”++”是作为成员函数重载的运算符,则与++a等效的运算 符函数调用形式为:

  • a.operator++(1)
  • operator++(a)
  • operator++ (a,1)
  • a.operator++()(对)

分析:对于重载的++和--运算符,前置无参数,后置有int作参数

(5)若需要为xv类重载乘法运算符,运算结果为xv类型,在将其声明为类的成员函数时,下列原型声明正确的是:

  • xv operator*(xv,xv);
  • xv*(xv);
  • operator*(xv);
  • xv operator*(xv);(对)

分析:A错在声明了非成员函数,BC明显错误

(6)下列关于运算符重载的说法中,错误的是

  • new和delete运算符可以重载
  • 重载运算符不能改变其原有的操作数个数
  • 三元运算符“?:”不能重载
  • 所有运算符既可以作为类的成员函数重载,又可以作为非成员函数重载(错)

分析:有部分运算符不能重载,比如三元运算符,::运算符和.运算符,且部分运算符不能重载为成员函数

虚函数

(1)sizeof是C++中的一个操作符,作用是返回一个对象或者类型所占的内存字节数,则在64位机器上,sizeof(A)为:8



解析:A中含有一个指向虚表的指针,在64位机器上,指针占8个字节。

抽象类

(1)关于上述类定义,下列描述中错误的是:

class Animal {
public:
virtual void Name()=0;
};
class Koala : public Animal {
public:
void Name(){/*函数体略*/}
};
  • 类Koala是类Animal的派生类
  • 类Koala中的Name函数是一个虚函数
  • 类Animal中的Name函数是一个纯虚函数
  • 语句“Animal a;”能够建立类Animal的一个对象a(错)

分析:Animal中只有一个纯虚函数,是抽象类,抽象类不能生成对象。

(2)关于抽象类,下列说法正确的是:

  • 纯虚函数与虚函数的声明语法相同
  • 可用new操作符来生成抽象类的对象
  • 带有纯虚函数的类称为抽象类(对)

c++:-5的更多相关文章

  1. java web 开发三剑客 -------电子书

    Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知In ...

  2. 所有selenium相关的库

    通过爬虫 获取 官方文档库 如果想获取 相应的库 修改对应配置即可 代码如下 from urllib.parse import urljoin import requests from lxml im ...

  3. In-Memory:内存数据库

    在逝去的2016后半年,由于项目需要支持数据的快速更新和多用户的高并发负载,我试水SQL Server 2016的In-Memory OLTP,创建内存数据库实现项目的负载需求,现在项目接近尾声,系统 ...

  4. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  5. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. App开发:模拟服务器数据接口 - MockApi

    为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块.本篇文章就尝试为使用gradle的android项目设计实现Moc ...

  8. TODO:macOS编译PHP7.1

    TODO:macOS编译PHP7.1 本文主要介绍在macOS上编译PHP7.1,有兴趣的朋友可以去尝试一下. 1.下载PHP7.1源码,建议到PHP官网下载纯净到源码包php-7.1.0.tar.g ...

  9. In-Memory:在内存中创建临时表和表变量

    在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...

  10. In-Memory:内存优化表的事务处理

    内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...

随机推荐

  1. Redis 最适合的场景?

    1.会话缓存(Session Cache) 最常用的一种使用 Redis 的情景是会话缓存(session cache).用 Redis 缓存会 话比其他存储(如 Memcached)的优势在于:Re ...

  2. Swing 是线程安全的?

    不是,Swing 不是线程安全的.你不能通过任何线程来更新 Swing 组件,如 JTable.JList 或 JPanel,事实上,它们只能通过 GUI 或 AWT 线程来更新. 这就是为什么 Sw ...

  3. 学习ELK日志平台(二)

      一.ELK介绍 1.1 elasticsearch 1.1.1 elasticsearch介绍 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索 ...

  4. C/C++头文件以及避免头文件包含造成的重定义方法

    C 头文件 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享.有两种类型的头文件:程序员编写的头文件和编译器自带的头文件. 在程序中要使用头文件,需要使用 C 预处 ...

  5. zx-editor 移动端(HTML5)富文本编辑器,可与原生App混合(hybrid)开发

    ZxEditor 移动端HTML文档(富文本)编辑器,支持图文混排.引用.大标题.无序列表,字体颜色.加粗.斜体. 可用于独立web项目开发,也可以用于与原生App混合(hybrid)开发. 源码地址 ...

  6. Android 动态控制OptionMenu的显示与隐藏

    在有些场景下,可能需要动态的显示和隐藏optionmenu,可以这样实现:如果在activity中默认实现了方法: onCreateOptionsMenu(Menu menu) 那么该OptionMe ...

  7. SimpleDateForma求日期,2008-11月第6周星期日是几号?

    题目4: 巧妙利用SimpleDateFormat根据各种信息求日期.2008-11月第6周的星期日是几号? import java.text.ParseException;import java.t ...

  8. 给一个非矩形数组(Nonrectangular Arrays)

    Nonrectangular Arrays(非矩形数组)  public class Test {     public static void main(String[] args) {       ...

  9. mysql基本操作2

    ##DDL控制表结构,不支持事务##DML控制表数据,支持事务       DQL专门做查询  ##TCL 管理事务##DCL 管理数据库权限     ##ORDER BY  子句-根据指定列对结果集 ...

  10. Python入门-运算符

    运算通常可以根据最终获得的值不同,可以分两类,即结果为具体的值,结果为bool值,那么哪些结果为具体的值-->算数运算.赋值运算,哪些结果又为bool值?--->比较运算.逻辑运算和成员运 ...