• 【C++ Primer Plus】第10章 对象和类


    最重要的OOP特性: 抽象; 封装和数据隐藏; 多态; 继承; 代码的可重用性。

    10.1 过程性编程OPP和面向对象编程OOP

    1. 采用过程性编程方法时,首先考虑要遵循的步骤,然后考虑如何表示这些数据。
    2. 采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的描述后,需要确定如何实现接口和数据存储。最后,使用新的设计方案创建出程序。

    10.2 抽象和类

    抽象 是通往用户定义类型的捷径,在C++中,用户定义类型指的是实现抽象接口的类设计。
    是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

    表示股票的类:
    操纵方法:获得股票;增持;卖出股票;更新股票价格;显示关于所持股票的信息。
    数据表示:公司名称;所持股票的数量;每股的价格;股票总值。

    一般来说,类规范由两个部分组成:

    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 
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    指定类设计的第二步:实现类成员函数

    1. 可以在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义 (除非函数很小)。
    2. 在这种情况下,需要使用作用域解析运算符(::)来指出成员函数属于哪个类。

    stock00.cpp // 类的定义源文件

    // 定义类的成员函数
    #include 
    #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是该对象的地址。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    指定类设计的第三步:创建类对象和类方法的使用

    1. 创建对象(类的实例),只需将类名视为类型名即可;
    2. 类成员函数(方法)可通过类对象来调用。为此,需要使用成员运算符句点(.)。

    usestock00.cpp // 类的使用源文件

    #include 
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    10.3 类的构造函数和析构函数

    10.3.1 构造函数

    为什么需要构造函数?数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员。程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能成功地将对象初始化。C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员。

    1. 用类创建对象时,自动调用构造函数。
    2. 构造函数的函数名和类的名称相同。通过函数重载,可以创建多个同名的构造函数。
    3. 接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值。
    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;                                // 自动调用默认构造函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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是该对象的地址。

    比较两个对象中哪个total_val值大并返回大的对象:

    1. 如何将两个要比较的对象提供给成员函数呢?如果希望该方法对两个对象进行比较,则必须将第二个对象作为参数传递给它。出于效率方面的考虑,可以按引用来传递参数。
    2. 如何将方法的答案传回给调用程序呢?最直接的方法是让方法返回一个引用,该引用指向股价总值较高的对象。

    函数原型:const Stock & topval(const Stock & s) const;
    函数调用: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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 该函数隐式地访问一个对象stock1,而显式地访问另一个对象stock2,并返回其中一个对象的引用。
    2. 括号中的const表明,该函数不会修改被显式地访问的对象stock2;
    3. 括号后的const表明,该函数不会修改被隐式地访问的对象stock1。
    4. 由于该函数返回了两个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();                    // 显示价格最高的一个对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    10.6 类作用域

    1. 在类中定义的名称(如类数据成员名和类成员函数名)的作用域都为整个类,作用域为整个类的名称只在该类中是已知的,在类外是不可知的。
    2. 在不同类中使用相同的类成员名而不会引起冲突。
    3. 不能从外部直接访问类的成员,要调用公有成员函数,必须通过对象。
    4. 在定义成员函数时,必须使用作用域解析运算符(::)
    5. 在类声明或成员函数定义中,可以使用未修饰的成员名称。

    作用域为类的常量

    声明类只是描述了对象的形式,并没有创建对象。因此,在创建对象前,将没有用于存储值的空间。尽管是一个const变量。
    第一种方式是在类中声明一个枚举:enum {Month = 12}; // 只是为了创建符号常量,Months只是一个符号名称,不需要枚举名。作用域为整个类。
    第二种方式是在类中使用关键字static: static const int Month = 12; // 静态全局变量,作用域为整个类。

    10.7 抽象数据类型(abstract data type,ADT)

    ADT以通用的方式描述数据类型,而没有引入语言或实现细节。

    例如,通过使用栈,可以以这样的方式存储数据,即总是从堆顶添加或删除数据。
    例如,C++程序使用栈来管理自动变量。当新的自动变量被生成后,它们被添加到堆顶;消亡时,从栈中删除它们。

    例程:用类的成员函数实现对栈的操作:。。。。。。编程练习5

    10.8 总结

    1. 面向对象编程强调的是程序如何表示数据。使用OOP方法解决编程问题的第一步是根据它与程序之间的接口来描述数据,从而指定如何使用数据。然后,设计一个类来实现该接口。一般来说,私有数据成员存储信息,公有成员函数(又称为方法)提供访问数据的唯一途径。类将数据和方法组合成一个单元,其私有性实现数据隐藏。
    2. 通常,将类声明分成两部分组成,这两部分通常保存在不同的文件中。类声明(包括由函数原型表示的方法)应放到头文件中。定义成员函数的源代码放在方法文件中。这样便将接口描述与实现细节分开了。从理论上说,只需知道公有接口就可以使用类。当然,可以查看实现方法(除非只提供了编译形式),但程序不应依赖于其实现细节,如知道某个值被存储为int。只要程序和类只通过定义接口的方法进行通信,程序员就可以随意地对任何部分做独立的改进,而不必担心这样做会导致意外的不良影响。
    3. 类是用户定义的类型,对象是类的实例。这意味着对象是这种类型的变量,例如由new按类描述分配的内存。C++试图让用户定义的类型尽可能与标准类型类似,因此可以声明对象、指向对象的指针和对象数组。可以按值传递对象、将对象作为函数返回值、将一个对象赋给同类型的另一个对象。如果提供了构造函数,则在创建对象时,可以初始化对象。如果提供了析构函数方法,则在对象消亡后,程序将执行该函数。
    4. 每个对象都存储自己的数据,而共享类方法。如果mr_object是对象名,try_me( )是成员函数,则可以使用成员运算符句点调用成员函数:mr_object.try_me( )。在OOP中,这种函数调用被称为将try_me消息发送给mr_object对象。在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和*this是什么?
      this指针是类方法可以使用的指针,它指向用于调用方法的对象。因此,this是对象的地址,*this是对象本身。

    10.10 编程练习

    5、编写一个程序,它从栈中添加和删除customer结构(栈用Stack类声明表示)。每次customer结构被删除时,其payment的值都被加入到总数中,并报告总数。注意:应该可以直接使用Stack类而不作修改;只需修改typedef声明,使Item的类型为customer,而不是unsigned long即可。

    (下面的程序包含第八题的题意)
    p5.h

    #ifndef PRIMERPLUS_P5_H
    #define PRIMERPLUS_P5_H
    #include 
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    usep5.cpp

    #include 
    #include    // 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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
  • 相关阅读:
    工作应该选择稳定的、工资低的?还是有挑战的工资高的?
    腾讯云4核8G服务器配置价格表,轻量和CVM标准型S5实例
    Azure - 机器学习:创建机器学习所需资源,配置工作区
    mysql面试题9:MySQL中的SQL常见的查询语句有哪些?有哪些对SQL语句优化的方法?
    在 Node.js 中实现基于角色的访问控制
    金仓数据库KingbaseES安全指南--6.7. GSSAPI身份验证
    vue3 封装公共弹窗函数
    How to import dgl-cu113 如何导入 dgl-cu113
    29、CSS进阶——美化表单元素
    线性DP AcWing 898. 数字三角形
  • 原文地址:https://blog.csdn.net/qq_39751352/article/details/126808697