c++:-5

PamShao發表於2022-05-07

上一節學習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操作符來生成抽象類的物件
  • 帶有純虛擬函式的類稱為抽象類(對)

相關文章