[C++入門Plus] 第10章 オブジェクトとクラス

最も重要な OOP 機能:抽象化、カプセル化とデータ隠蔽、ポリモーフィズム、継承、コードの再利用性。

10.1 手続き型プログラミング OPP とオブジェクト指向プログラミング OOP

  1. 手続き型プログラミングのアプローチを採用する場合は、まず従うべきステップを検討し、次にこのデータをどのように表現するかを検討します。
  2. OOP アプローチを採用する場合、オブジェクトはまずユーザーの観点から考慮されます。つまり、オブジェクトを記述するために必要なデータと、データとユーザーの対話を記述するために必要な操作です。インターフェイスを説明した後、インターフェイスとデータ ストレージをどのように実装するかを決定する必要があります。最後に、新しい設計を使用してプログラムが作成されます。

10.2 抽象化とクラス

抽象化はユーザー定義型へのショートカットです。C++ では、ユーザー定義型は抽象インターフェイスを実装するクラス設計を指します。
クラスは、抽象化をユーザー定義型に変換し、データ表現とデータを操作するメソッドを適切なパッケージに結合するための C++ ツールです。

株式を表すクラス:
操作方法: 株式の取得、保有の増加、株式の売却、株価の更新、保有に関する情報の表示。
データ表現: 会社名、保有株式数、1 株当たりの価格、株式の総額。

一般に、クラス仕様は次の 2 つの部分で構成されます。

  1. クラス宣言:データ部分をデータメンバーの形式で記述し、パブリックインターフェイスをメンバー関数(メソッドと呼ばれます)の形式で記述します。
  2. クラス メソッドの定義: クラス メンバー関数 (クラスのインターフェイス) を実装する方法を説明します。

クラス宣言とメンバー関数の定義:

  1. 通常、C++ プログラマは、ヘッダー ファイルにインターフェイス (クラス定義) を配置し、ソース コード ファイルに実装 (クラス メソッドのコード) を配置します。
  2. ヘッダー ファイルでは、クラス名の最初の文字を大文字にするという共通の規則がありますが、普遍的ではありません。
  3. ヘッダファイルでは、通常、データ項目はプライベート部分private:(このキーワードは省略可能)に配置され、クラスインターフェースを構成するメンバ関数はパブリック部分に配置されます。public:
  4. ヘッダー ファイルで、関数のかっこの後ろに const キーワードを入れて、関数が呼び出し元のオブジェクトを変更しないようにします。
  5. クラス オブジェクトを使用するプログラムは、パブリック部分に直接アクセスできますが、オブジェクトのプライベート メンバーにはパブリック メンバー関数 (またはフレンド関数) を介してのみアクセスできます。
  6. メンバー関数を定義するときは、スコープ解決演算子 (::) を使用して、関数が属するクラスを識別します。void Stock::update(double price)
  7. クラスのメンバー関数 (メソッド) は、クラス オブジェクトを通じて呼び出すことができます。これを行うには、メンバーシップ演算子ピリオド (.) を使用する必要があります。
  8. ヘッダー ファイルでは、クラス宣言に定義がある関数は自動的にインライン化されます。
  9. ヘッダファイルでは、クラス宣言の外でメンバ関数を定義し、inline を追加することで、インライン関数を呼び出すこともできます。このとき、関数名にも (::) を追加する必要があることに注意してください。
  10. オブジェクトは、同じタイプの別のオブジェクトに割り当てることができます。
  11. クラスと構造体: C++ プログラマは通常、クラスを使用してクラスの説明を実装し、構造体が純粋なデータ オブジェクトを表すように制限します。

クラス設計を指定する最初のステップ: クラス宣言を提供する

  1. クラス宣言は構造体宣言に似ており、データ メンバーと関数メンバーを含めることができます。
  2. 宣言にはプライベート部分があり、宣言されたメンバーにはメンバー関数を通じてのみアクセスできます。
  3. データをプライベート部分にカプセル化すると、データの整合性が保護されます。これはデータ隠蔽と呼ばれます。
  4. 宣言にはパブリック セクションもあり、クラス オブジェクトを使用するプログラムから宣言されたメンバーに直接アクセスできます。
  5. パブリック部分のコンテンツは、デザインの抽象部分、つまりパブリック インターフェイスを構成します。

Stock00.h // クラス宣言ヘッダー ファイル

// 类的声明:类的变量和成员函数,数据和操纵数据的方法
#ifndef PRIMERPLUS_STOCK00_H
#define PRIMERPLUS_STOCK00_H
#include <string>
class Stock // 首字母一般大写
{
    
    
private:    // 这个关键字可以省略,防止其他源代码访问里面的数据,凡是在private里面的数据,只有public里面的方法可以调用。
    std::string company;    // 成员
    long shares;
    double share_val;
    double total_val;
    void set_total() {
    
    total_val = shares * share_val;} // 内联函数
public:     // 这个关键字不能省略

    Stock();    // 默认构造函数,在声明和定义时不加形参,但是定义时给每个成员初始化
                // 函数重载,自定义构造函数,没有返回值,部分形参使用默认参数
    Stock(const std::string &co, long n = 1, double pr = 1.0);
    ~Stock();                           // 析构函数的声明
    void buy(long num, double price);   // 成员函数
    void sell(long num, double price);
    void update(double price);
    void show() const;                  // const成员函数,保证函数不会修改调用对象
    const Stock & topval(const Stock &s) const;
	const string &company_name() const {
    
    return company;}	// 返回公司的名字且不希望被修改
};
#endif //PRIMERPLUS_STOCK00_H

クラス設計を指定する 2 番目のステップ: クラス メンバー関数の実装

  1. 完全な関数定義は、関数プロトタイプの代わりにクラス宣言で指定できますが、関数定義を単独で指定するのが一般的です (関数が小さい場合を除く)。
  2. この場合、スコープ解決演算子 (::) を使用して、メンバー関数がどのクラスに属しているかを示す必要があります。

Stock00.cpp // クラス定義ソースファイル

// 定义类的成员函数
#include <iostream>
#include "stock00.h"
using namespace std;
Stock::Stock()  // 默认构造函数
{
    
    
    company = "stock";
    shares = 0;
    share_val = 0.0;
    set_total();
}

Stock::Stock(const std::string &co, long n, double pr)  // 自定义构造函数
{
    
    
    company = co;
    if (n<0)
    {
    
    
        cout << "Number of shares can't be negative; "
             << company << " shares set to be 0." << endl;
        shares = 0;
    }
    else
        shares = n;
    share_val = pr;
    set_total();
}

Stock::~Stock() // 析构函数定义,没有参数,没有返回值,自动调用
{
    
    
    cout << "Bye " << company << endl;  // 类储存是以栈的方式:先进后出,后进先出
}

void Stock::buy(long num, double price)
{
    
    
    if (num < 0)
        cout << "Number of shares can't be negative; " << endl;
    else
    {
    
    
        shares += num;
        share_val = price;
        set_total();
    }
}

void Stock::sell(long num, double price)
{
    
    
    if (num < 0)
        cout << "Number of shares can't be negative; " << endl;
    else if (num > shares)
        cout << "You can't sell more than you have!" << endl;
    else
    {
    
    
        shares -=num;
        share_val = price;
        set_total();
    }
}

void Stock::update(double price)
{
    
    
    share_val = price;
    set_total();
}

void Stock::show() const
{
    
    
    cout << "Company : " << company << endl;
    cout << "Shares : " << shares << endl;
    cout << "Share price : " << share_val << endl;
    cout << "Total worth : " << total_val << endl;
}

const Stock & Stock::topval(const Stock &s) const
{
    
    
    if (s.total_val > total_val)    // total = this->total_val
        return s;
    else
        return *this;   // this指针指向调用成员函数的对象,this是该对象的地址。
}

クラス設計を指定する 3 番目のステップ: クラス オブジェクトの作成とクラス メソッドの使用

  1. オブジェクト (クラスのインスタンス) を作成するには、クラス名を型名として扱うだけです。
  2. クラスのメンバー関数 (メソッド) は、クラス オブジェクトを通じて呼び出すことができます。これを行うには、メンバーシップ演算子ピリオド (.) を使用する必要があります。

usestock00.cpp // クラス使用ソース ファイル

#include <iostream>
#include "stock00.h"
using namespace std;
int main(void)
{
    
    
    {
    
    
        Stock wind1 = Stock{
    
    "wind1"};       // 显示调用构造函数
        Stock wind2{
    
    "wind2", 20, 20.2};     // 隐式调用构造函数
        Stock wind3;                        // 自动调用默认构造函数
        wind3 = wind2;                      // 可以将一个对象赋给同类型的另一个对象
        wind3 = Stock("wind3", 30, 30.3);   // 构造函数可对已存在的对象赋值
        const Stock wind4 = Stock{
    
    "wind4"}; // wind4只能调用const成员函数,show()

        Stock top;
        top = wind1.topval(wind2);
        top.show();

        const int STKS = 4;
        Stock mystuff[STKS];                // 创建对象数组,自动调用默认构造函数
        Stock sand[STKS] = {
    
                    // 创建对象数组,对不同的元素使用不同的构造函数
                Stock("sand1", 11, 11.1),   // 用括号括起,以逗号分割
                Stock(),                    // 使用默认构造函数
                Stock("sand3", 33)          // 只初始化了三个元素,剩下的自动调用默认构造函数
        };
        int i;
        for (i=0; i<STKS; i++)
            sand[i].show();
        const Stock *topsand = &sand[0];    // 定义一个类的const指针
        for (i=1; i<STKS; i++)
            topsand = &(topsand->topval(sand[i]));
        topsand->show();                    // 显示价格最高的一个对象

//        Stock fluffy_the_cat;
//        fluffy_the_cat.acquire("Mooncake", 20, 12.5);
//        fluffy_the_cat.show();    		// 通过类的对象来访问类的公有成员函数
//        fluffy_the_cat.buy(15, 18.125);
//        fluffy_the_cat.show();
//        fluffy_the_cat.sell(400, 20.0);
//        fluffy_the_cat.update(15.2);
//        fluffy_the_cat.show();

    }   // 添加这个大括号后,析构函数调用将在到达返回语句前执行。
    return 0;
}

10.3 クラスのコンストラクターとデストラクター

10.3.1 コンストラクター

なぜコンストラクターが必要なのでしょうか? データ セクションのアクセス状態はプライベートです。これは、プログラムがデータ メンバーに直接アクセスできないことを意味します。プログラムはメンバー関数を通じてのみデータ メンバーにアクセスできるため、オブジェクトを正常に初期化できるように適切なメンバー関数を設計する必要があります。C++ は、特別なメンバー関数であるクラス コンストラクターを提供します。これは、新しいオブジェクトを構築し、そのデータ メンバーに値を割り当てるために特別に使用されます。

  1. クラスを使用してオブジェクトが作成されると、コンストラクターが自動的に呼び出されます。
  2. コンストラクターの関数名はクラス名と同じです。関数のオーバーロードを通じて、同じ名前を持つ複数のコンストラクターを作成できます。
  3. 1 つのパラメーターを取るコンストラクターを使用すると、代入構文を使用してオブジェクトを値に初期化できます。
  4. コンストラクターのプロトタイプと関数ヘッダーには興味深い機能があります。値は返されませんが、void として宣言されていません。実際、コンストラクターは型を宣言しません。
  5. コンストラクターのパラメーターはクラスのメンバーではなく、クラスのメンバーに割り当てられた値を表します。したがって、パラメーター名をクラスのメンバーと同じにすることはできません。
  6. コンストラクターがオブジェクトを構築するまでオブジェクトは存在しないため、オブジェクトを使用してコンストラクターを呼び出すことはできません。したがって、コンストラクターはオブジェクトの作成に使用され、オブジェクトを通じて呼び出すことはできません。
  7. デフォルトの構築パラメータには、仮パラメータを含めることもできます (存在する場合、すべて初期化されたデフォルト パラメータを持たなければなりません)。パラメータを持たないこともできます。
// 默认构造函数,在定义时不加形参,但是给每个成员初始化
Stock();   	
Stock::Stock() {
    
    ...}	// Stock类的默认构造函数的定义,没有形参,但是给每个成员初始化

// 函数重载,自定义构造函数,没有返回值,部分形参使用默认参数
Stock(const std::string &co, long n=1, double pr=1.0); 	
Stock::Stock(const std::string &co, long n, double pr) {
    
    ...} // Stock类的构造函数的定义
    
Stock fluffy_the_cat = Stock{
    
    "Mooncake"};   // 显示调用自定义构造函数
Stock garment{
    
    "apple", 30, 123.45};         // 隐式调用自定义构造函数
Stock first;                                // 自动调用默认构造函数

10.3.3 デストラクター

  1. コンストラクターを使用してオブジェクトが作成された後、有効期限が切れるまでそのオブジェクトを追跡するのはプログラムの責任です。オブジェクトの有効期限が切れると、プログラムは自動的に特別なメンバー関数、つまりデストラクターを呼び出します。
  2. デストラクターはクリーンアップを行います。コンストラクターが new を使用してメモリーを割り当てる場合、デストラクターは delete を使用してそのメモリーを解放します。
  3. デストラクターのプロトタイプ: 名前の前に ~ が付いたクラス名が続きます。戻り値も宣言された型もありません。パラメータはありません。Stock クラスのデストラクターのプロトタイプ~Stock();
  4. プログラマーがデストラクターを提供しない場合、コンパイラーは暗黙的にデフォルトのデストラクターを宣言し、オブジェクトの削除を引き起こすコードを検出した後、デフォルトのデストラクターの定義を提供します。
  5. クラスの自動変数格納はスタック形式であり、デストラクターが呼び出されて解放されると、先入れ先出し、後入れ先出しになります。
  6. コンストラクターで new を使用する場合は、delete を使用するデストラクターを提供する必要があります。

デストラクターが呼び出されるとき:

  1. 静的ストレージ クラス オブジェクトが作成されると、そのデストラクターがプログラムの最後に自動的に呼び出されます。
  2. (前の例のように) 自動ストレージ クラス オブジェクトが作成された場合、プログラムがオブジェクトが定義されているコード ブロックの実行を終了すると、そのデストラクターが自動的に呼び出されます。
  3. オブジェクトが new 経由で作成された場合、そのオブジェクトはスタック メモリまたは空きストレージに常駐し、delete を使用してメモリを解放すると、そのデストラクタが自動的に呼び出されます。
  4. 最後に、プログラムは特定の操作を実行するために一時オブジェクトを作成できます。この場合、プログラムはオブジェクトの使用を終了するときに自動的にデストラクターを呼び出します。

10.4 thisポインタ

this ポインタはメンバー関数を呼び出すオブジェクトを指し、this はオブジェクトのアドレスです。

2 つのオブジェクトのどちらの total_val 値が大きいかを比較し、大きい方のオブジェクトを返します。

  1. メンバー関数と比較する 2 つのオブジェクトを提供するにはどうすればよいでしょうか? メソッドで 2 つのオブジェクトを比較する場合は、2 番目のオブジェクトを引数として渡す必要があります。効率上の理由から、パラメータは参照によって渡すことができます。
  2. メソッドの応答を呼び出し側プログラムに返すにはどうすればよいでしょうか? 最も簡単な方法は、株価の合計値がより高いオブジェクトへの参照をメソッドに返すようにすることです。

関数プロトタイプ: const Stock & topval(const Stock & s) const;
function call:top = stock1.topval(stock2); // top はオブジェクト
関数定義でもあります:

const Stock & Stock::topval(const Stock & s) const
{
    
    
	if (s.total_val > total_val)	// total = this->total_val
        return s;		// argument object
	else
		return *this; 	// this 指针指向调用对象
}
  1. この関数は、1 つのオブジェクト Stock1 と別のオブジェクト Stock2 に明示的に暗黙的にアクセスし、いずれかのオブジェクトへの参照を返します。
  2. 括弧内の const は、この関数が明示的にアクセスされたオブジェクト Stock2 を変更しないことを示します。
  3. 括弧の後の const は、関数が暗黙的にアクセスされたオブジェクト Stock1 を変更しないことを示します。
  4. この関数は 2 つの const オブジェクトのいずれかへの参照を返すため、戻り値の型も const 参照である必要があります。

10.5 オブジェクトと配列

const int STKS = 4;
Stock mystuff[STKS];                // 创建对象数组,自动调用默认构造函数
Stock sand[STKS] = {
    
                    // 创建对象数组,对不同的元素使用不同的构造函数
        Stock("sand1", 11, 11.1),   // 用括号括起,以逗号分割
        Stock(),                    // 使用默认构造函数
        Stock("sand3", 33)          // 只初始化了三个元素,剩下的自动调用默认构造函数
};
int i;
for (i=0; i<STKS; i++)
    sand[i].show();
const Stock *topsand = &sand[0];    // 定义一个类的const指针
for (i=1; i<STKS; i++)
    topsand = &(topsand->topval(sand[i]));
topsand->show();                    // 显示价格最高的一个对象

10.6 クラスのスコープ

  1. クラス内で定義されている名前(クラスデータメンバ名やクラスメンバ関数名など)の有効範囲はクラス全体であり、クラス全体の有効範囲を持つ名前はクラス内でのみ認識され、クラス外には認識されません。 。
  2. 競合を引き起こすことなく、異なるクラスで同じクラス メンバー名を使用できます。
  3. クラスのメンバには外部から直接アクセスできないため、パブリックメンバ関数を呼び出すにはオブジェクトを渡す必要があります。
  4. メンバー関数を定義するときは、スコープ解決演算子 (::) を使用する必要があります。
  5. クラス宣言またはメンバー関数定義では、装飾されていないメンバー名が使用される場合があります。

クラススコープの定数

クラスの宣言はオブジェクトの形式を記述するだけであり、オブジェクトは作成されません。したがって、オブジェクトが作成されるまで、値を保存するためのスペースはありません。const変数であるにもかかわらず。
最初の方法は、クラスで列挙型を宣言することです。enum {Month = 12}; // シンボリック定数を作成するためだけに、Months は単なるシンボリック名であり、列挙名は必要ありません。スコープはクラス全体です。
2 番目の方法は、クラス内でキーワード static を使用することです。 static const int Month = 12; // クラス全体をスコープとする静的グローバル変数。

10.7 抽象データ型(抽象データ型、ADT)

ADT は、言語や実装の詳細を導入することなく、一般的な方法でデータ型を記述します。

たとえば、スタックを使用すると、データが常にヒープの先頭に追加または削除されるような方法でデータを格納できます。
たとえば、C++ プログラムはスタックを使用して自動変数を管理します。新しい自動変数が作成されると、それらはヒープの先頭に追加され、消滅するとスタックから削除されます。

ルーチン: クラスのメンバー関数を使用して stack: の動作を実現します。プログラミング演習 5

10.8 概要

  1. オブジェクト指向プログラミングでは、プログラムがデータをどのように表現するかが重視されます。OOP メソッドを使用してプログラミングの問題を解決する最初のステップは、プログラムとのインターフェイスの観点からデータを記述し、データの使用方法を指定することです。次に、インターフェイスを実装するクラスを設計します。一般に、プライベート データ メンバーには情報が格納され、パブリック メンバー関数 (メソッドとも呼ばれます) がデータにアクセスする唯一の手段を提供します。クラスはデータとメソッドを 1 つのユニットに結合し、そのプライバシーによりデータの隠蔽が可能になります。
  2. 通常、クラス宣言は 2 つの部分に分割され、通常は別のファイルに保存されます。クラス宣言 (関数プロトタイプによって表されるメソッドを含む) はヘッダー ファイルに配置する必要があります。メンバー関数を定義するソース コードはメソッド ファイルに配置されます。これにより、インターフェイスの説明が実装の詳細から分離されます。理論的には、クラスを使用するにはパブリック インターフェイスを知っているだけで済みます。もちろん、(コンパイルされた形式のみが提供されている場合を除き) 実装を確認することはできますが、値が int として格納されるかどうかなど、プログラムはその実装の詳細に依存すべきではありません。プログラムとクラスがインターフェイスを定義するメソッドを通じてのみ通信する限り、プログラマは、それによる予期せぬ副作用を心配することなく、任意の部分に独立して改良を加えることができます。
  3. クラスはユーザー定義型であり、オブジェクトはクラスのインスタンスです。これは、オブジェクトがこのタイプの変数 (クラスで記述された new によって割り当てられたメモリなど) であることを意味します。C++ は、オブジェクト、オブジェクトへのポインター、およびオブジェクトの配列を宣言できるように、ユーザー定義型を標準型とできる限り似たものにしようとします。オブジェクトを値で渡したり、オブジェクトを関数として返したり、あるオブジェクトを同じタイプの別のオブジェクトに割り当てることができます。コンストラクターが提供されている場合、オブジェクトの作成時にオブジェクトを初期化できます。デストラクター メソッドが提供されている場合、プログラムはオブジェクトが終了した後にその関数を実行します。
  4. 各オブジェクトはクラス メソッドを共有しながら独自のデータを保存します。mr_object がオブジェクト名で try_me( ) がメンバー関数である場合、メンバー演算子ピリオドを使用してメンバー関数 mr_object.try_me( ) を呼び出すことができます。OOP では、この関数呼び出しは、mr_object オブジェクトに送信される try_me メッセージと呼ばれます。try_me( ) メソッドでクラスのデータ メンバーを参照する場合、mr_object オブジェクトの対応するデータ メンバーが使用されます。同様に、関数呼び出し i_object.try_me( ) は、i_object オブジェクトのデータ メンバーにアクセスします。
  5. メンバー関数で複数のオブジェクトを操作する場合は、追加のオブジェクトをパラメーターとして渡すことができます。メソッドが呼び出されるオブジェクトへの明示的な参照を必要とする場合は、this ポインタを使用できます。this ポインタは呼び出し元オブジェクトのアドレスに設定されているため、*this はそのオブジェクトのエイリアスです。
  6. クラスは ADT を記述するのに適しています。パブリック メンバー関数インターフェイスは ADT によって記述されたサービスを提供し、クラスのプライベート部分とクラス メソッドのコードはクラスのクライアントから隠蔽される実装を提供します。

10.9 質問の確認

  1. クラスとは何ですか?
    クラスはユーザー定義型の定義です。クラス宣言では、データの保存方法と、そのデータにアクセスして操作するために使用されるメソッド (クラス メンバー関数) を指定します。
  2. クラスは抽象化、カプセル化、データ隠蔽をどのように実装するのでしょうか?
    クラスは、ユーザーがクラス メソッドのパブリック インターフェイス (抽象化) を通じてクラス オブジェクトに対して実行できる操作を表します。
    クラスのデータ メンバーはプライベート (デフォルト) にすることができます。これは、データにはメンバー関数を通じてのみアクセスできることを意味します。これはデータ隠蔽です。
    実装の特定の詳細 (データ表現やメソッド コードなど) は隠されます。これがカプセル化です。
  3. オブジェクトとクラスの関係は何ですか?
    クラスは、その使用方法を含む型を定義します。
    オブジェクトは、クラス定義に従って作成および使用される変数またはその他のデータ オブジェクト (new によって作成されたもの) です。
    クラスとオブジェクトの関係は、標準型とその変数の関係と同じです。
  4. 関数であること以外に、クラス関数メンバーとクラス データ メンバーの違いは何ですか?
    特定のクラスの複数のオブジェクトを作成する場合、各オブジェクトはデータ用に独自のメモリ空間を持ちます
    が、すべてのオブジェクトは同じメンバー関数のセットを使用します (通常、メソッドはパブリックであり、データはプライベートですが、これは単なる戦略の問題です)クラスの必須条件ではありません)。
  5. クラス コンストラクターはいつ呼び出されますか? クラスのデストラクターについてはどうですか?
    クラスのコンストラクターは、クラスのオブジェクトが作成されるとき、またはコンストラクターが明示的に呼び出されるときに呼び出されます。オブジェクトの有効期限が切れると、クラスのデストラクターが呼び出されます。
  6. デフォルトのコンストラクターとは何ですか?デフォルトのコンストラクターを使用する利点は何ですか?
    デフォルト コンストラクターは、パラメーターを持たないか、すべてのパラメーターがデフォルト値を持つコンストラクターです。
    デフォルトのコンストラクターを使用すると、初期化コンストラクターが定義されている場合でも、オブジェクトを初期化せずに宣言できます。また、配列の宣言も可能になります。
  7. これと*これは何ですか?
    this ポインタはクラスのメソッドで使用できるポインタで、メソッドの呼び出しに使用されるオブジェクトを指します。したがって、これはオブジェクトのアドレスであり、*これはオブジェクト自体です。

10.10 プログラミング演習

5. スタックに顧客構造を追加およびスタックから削除するプログラムを作成します (スタックは Stack クラス宣言によって表されます)。顧客構造が削除されるたびに、その支払額が合計に追加され、合計がレポートされます。注: Stack クラスは変更せずに使用できるはずです。typedef 宣言を変更して、Item が unsigned long 型ではなく customer 型になるようにするだけです。

(以下のプログラムには8問目の意味が含まれています)
p5.h

#ifndef PRIMERPLUS_P5_H
#define PRIMERPLUS_P5_H
#include <iostream>
using namespace std;
struct customer
{
    
    
    char fullname[35];
    double payment;
};
typedef customer Item;         // 起别名,为存放不同的数据类型
void visit_item(Item &item);
class Stack
{
    
    
private:                // 私有部分放成员变量
    enum {
    
    MAX = 10};    // 枚举类型的符号常量
    Item items[MAX];    // holds stack items
    int top;            // 顶部堆栈项的索引,栈顶指针
public:
    Stack();                // 默认构造函数
    bool isempty() const;   // 判断是否为空
    bool isfull() const;    // 判断是否满了
    // push() returns false if stack already is full, true otherwise
    bool push(const Item & item);   // 入栈
    // pop() returns false if stack already is empty, true otherwise
    bool pop(Item & item);          // 出栈

    void visit(void (*pf)(Item &)); // 访问数据项以及执行操作
    // pf指向一个将Item引用作为参数的函数(不是成员函数)
    // visit( )函数将该函数用于列表中的每个数据项。
};
#endif //PRIMERPLUS_P5_H

p5.cpp

#include "p5.h"

Stack::Stack() // create an empty stack
{
    
    
    top = 0;            // 初始化栈顶指针
}

bool Stack::isempty() const
{
    
    
    return top == 0;    // 是否等于最底层
}

bool Stack::isfull() const
{
    
    
    return top == MAX;  // 是否等于最高层
}

bool Stack::push(const Item & item)
{
    
    
    if (top < MAX)      // 入栈条件
    {
    
    
        items[top++] = item;
        return true;
    }
    else
        return false;
}

bool Stack::pop(Item & item)
{
    
    
    if (top > 0)
    {
    
    
        item = items[--top];
        return true;
    }
    else
        return false;
}

void Stack::visit(void (*pf)(Item &))
{
    
    
    for (int i=0; i<top; i++)
        pf(items[i]);
}

void visit_item(Item &item)
{
    
    
    cout << "fullname:" << item.fullname << endl;
    cout << "payment:" << item.payment << endl;
}

usep5.cpp

#include <iostream>
#include <cctype>   // or ctype.h
#include "p5.h"
int main()
{
    
    
    using namespace std;
    Stack st;       // create an empty stack
    char ch;
    customer cust;
    double sum = 0.0;
    cout << "Please enter A/a to add a purchase order, "
         << "P/p to process a PO, or Q/q to quit.\n";
    while (cin >> ch && toupper(ch) != 'Q')
    {
    
    
        while (cin.get() != '\n')   // 消耗回车
            continue;
        if (!isalpha(ch))
        {
    
    
            cout << '\a';
            continue;
        }
        switch(ch)
        {
    
    
            case 'A':
            case 'a':   cout << "Enter a customer's fullname you want to push to stack (string):";
                        cin.getline(cust.fullname, 35);
                        cout << "Enter a customer's payment (double):";
                        cin >> cust.payment;
                        if (st.isfull())
                            cout << "stack already full\n";
                        else
                        {
    
    
                            st.push(cust);
                            st.visit(visit_item);   // 显示
                        }
                        break;
            case 'P':
            case 'p':   if (st.isempty())
                            cout << "stack already empty\n";
                        else
                        {
    
    
                            st.pop(cust);
                            sum += cust.payment;
                            cout << cust.fullname << " is popped\n";
                            cout << cust.payment << " is popped\n";
                            cout << "sum panyment :" << sum << endl;
                        }
                        break;
        }
        cout << "Please enter A/a to add a purchase order, "
             << "P/p to process a PO, or Q/q to quit.\n";
    }
    cout << "Bye\n";
    return 0;
}

おすすめ

転載: blog.csdn.net/qq_39751352/article/details/126808697