[C++ Primer Plus] Chapter 10 Objects and Classes

The most important OOP features: abstraction; encapsulation and data hiding; polymorphism; inheritance; code reusability.

10.1 Procedural programming OPP and object-oriented programming OOP

  1. When taking a procedural programming approach, first consider the steps to follow and then how to represent this data.
  2. When adopting an OOP approach, objects are first considered from the perspective of the user—the data required to describe the object and the operations required to describe the user's interaction with the data. After you have described the interface, you need to determine how the interface and data storage will be implemented. Finally, a program is created using the new design.

10.2 Abstractions and classes

Abstraction is a shortcut to user-defined types. In C++, user-defined types refer to class designs that implement abstract interfaces.
Classes are a C++ tool for converting abstractions into user-defined types, combining data representation and methods for manipulating data into a neat package.

Classes representing stocks:
Manipulation methods: Acquire stocks; increase holdings; sell stocks; update stock prices; display information about holdings.
Data representation: company name; number of shares held; price per share; total value of shares.

In general, a class specification consists of two parts:

  1. Class declaration: describe the data part in the form of data members, and describe the public interface in the form of member functions (called methods).
  2. Class method definition: Describes how to implement class member functions (the interface of the class).

Class declaration and definition of member functions:

  1. Typically, C++ programmers place interfaces (class definitions) in header files and implementations (code for class methods) in source code files.
  2. In the header file, a common but not universal convention - capitalize the first letter of the class name.
  3. In the header file, the data items are usually placed in the private part private:(this keyword can be omitted), and the member functions that make up the class interface are placed in the public partpublic:
  4. In the header file, put the const keyword behind the parentheses of the function to ensure that the function will not modify the calling object.
  5. Programs that use class objects can directly access the public part, but can only access the private members of the object through public member functions (or friend functions).
  6. When defining a member function, use the scope resolution operator (::) to identify the class to which the function belongs;void Stock::update(double price)
  7. Class member functions (methods) can be called through the class object. To do this, you need to use the membership operator period (.)
  8. In header files, functions whose definition is in the class declaration are automatically inlined.
  9. In the header file, defining a member function outside the class declaration and adding inline can also make it called an inline function. Note that the function name also needs to add (::) at this time.
  10. An object can be assigned to another object of the same type.
  11. Classes and Structs: C++ programmers typically use classes to implement class descriptions and restrict structs to represent pure data objects

The first step in specifying a class design: providing a class declaration

  1. A class declaration is similar to a structure declaration and can include data members and function members.
  2. The declaration has a private part, in which the declared members can only be accessed through member functions;
  3. Encapsulating data into private parts protects the integrity of the data, which is known as data hiding.
  4. Declarations also have a public section, in which the declared members are directly accessible by programs using class objects.
  5. The content of the public part constitutes the abstract part of the design - the public interface.

stock00.h // class declaration header file

// 类的声明:类的变量和成员函数,数据和操纵数据的方法
#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

The second step in specifying class design: implementing class member functions

  1. The full function definition can be provided in the class declaration instead of the function prototype, but it is common practice to provide the function definition by itself (unless the function is small).
  2. In this case, the scope resolution operator (::) needs to be used to indicate which class the member function belongs to.

stock00.cpp // class definition source file

// 定义类的成员函数
#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是该对象的地址。
}

The third step in specifying class design: creating class objects and using class methods

  1. To create an object (an instance of a class), just treat the class name as a type name;
  2. Class member functions (methods) can be called through the class object. To do this, you need to use the membership operator period (.).

usestock00.cpp // class usage source file

#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 Class constructors and destructors

10.3.1 Constructors

Why do you need a constructor? The access state of the data section is private, which means that the program cannot directly access the data members. Programs can only access data members through member functions, so appropriate member functions need to be designed to successfully initialize objects. C++ provides a special member function - the class constructor, which is specially used to construct new objects and assign values ​​to their data members.

  1. When an object is created with a class, the constructor is called automatically.
  2. The function name of the constructor is the same as the name of the class. Through function overloading, you can create multiple constructors with the same name.
  3. A constructor that takes one parameter allows an object to be initialized to a value using assignment syntax.
  4. The prototype and function header of the constructor has an interesting feature - although it does not return a value, it is not declared as void. In fact, the constructor does not declare a type.
  5. The parameters of the constructor represent not the class members, but the values ​​assigned to the class members. Therefore, parameter names cannot be the same as class members.
  6. You cannot use an object to call a constructor, because the object does not exist until the constructor constructs the object. So constructors are used to create objects and cannot be called through objects.
  7. Default construction parameters can have formal parameters (if any, they must all have initialized default parameters), or there can be no parameters.
// 默认构造函数,在定义时不加形参,但是给每个成员初始化
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 Destructors

  1. After an object is created with a constructor, it is the program's responsibility to keep track of that object until it expires. When the object expires, the program will automatically call a special member function - the destructor.
  2. The destructor does the cleanup: If the constructor uses new to allocate memory, the destructor will use delete to free that memory.
  3. The prototype of the destructor: the name is preceded by the class name with ~. There is no return value and no declared type. There are no parameters. The prototype of the destructor of the Stock class~Stock();
  4. If the programmer does not provide a destructor, the compiler will implicitly declare a default destructor, and after discovering the code that causes the object to be deleted, provide the definition of the default destructor.
  5. The automatic variable storage of the class is in the form of a stack, and when the destructor is called to release, it is first-in, last-out, last-in-first-out.
  6. If the constructor uses new, a destructor that uses delete must be provided.

When the destructor is called:

  1. If a static storage class object is created, its destructor will be called automatically at the end of the program.
  2. If an automatic storage class object is created (as in the previous example), its destructor will be called automatically when the program finishes executing the block of code in which the object was defined.
  3. If the object is created via new, it will reside in stack memory or free storage, and its destructor will be called automatically when delete is used to free the memory.
  4. Finally, a program can create a temporary object to perform a specific operation, in which case the program will automatically call its destructor when it finishes using the object.

10.4 The this pointer

The this pointer points to the object calling the member function, and this is the address of the object.

Compares which of two objects has the greater total_val value and returns the larger object:

  1. How do you provide the two objects to be compared to a member function? If you want the method to compare two objects, you must pass it the second object as an argument. For efficiency reasons, parameters can be passed by reference.
  2. How do you pass the method's answer back to the calling program? The most straightforward way is to have the method return a reference to the object with the higher total value of the stock price.

Function prototype: const Stock & topval(const Stock & s) const;
function call:top = stock1.topval(stock2); // top is also an object
function definition:

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. The function implicitly accesses one object stock1 and another object stock2 explicitly, and returns a reference to one of the objects.
  2. The const in parentheses indicates that this function will not modify the explicitly accessed object stock2;
  3. The const after the parentheses indicates that the function does not modify the implicitly accessed object stock1.
  4. Since the function returns a reference to one of two const objects, the return type should also be a const reference.

10.5 Objects and Arrays

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 Class scope

  1. The scope of the names defined in the class (such as class data member names and class member function names) is the entire class, and the names with the scope of the entire class are known only in the class and are not known outside the class.
  2. Use the same class member name in different classes without causing conflicts.
  3. The members of the class cannot be directly accessed from the outside. To call public member functions, the object must be passed.
  4. When defining member functions, you must use the scope resolution operator (::)
  5. In a class declaration or member function definition, undecorated member names may be used.

class-scoped constants

Declaring a class only describes the form of the object, and does not create the object. Therefore, until the object is created, there will be no space for storing the value. despite being a const variable.
The first way is to declare an enumeration in the class:enum {Month = 12}; // Just to create symbolic constants, Months is just a symbolic name, no enumeration name required. The scope is the entire class.
The second way is to use the keyword static in the class: static const int Month = 12; // Static global variables, scoped to the entire class.

10.7 Abstract data type (abstract data type, ADT)

ADTs describe data types in a generic way without introducing language or implementation details.

For example, by using a stack, data can be stored in such a way that data is always added or removed from the top of the heap.
For example, C++ programs use the stack to manage automatic variables. When new automatic variables are created, they are added to the top of the heap; when they die, they are removed from the stack.

Routine: Use the member functions of the class to realize the operation of the stack:. . . . . . Programming Exercise 5

10.8 Summary

  1. Object-oriented programming emphasizes how programs represent data. The first step in solving programming problems using the OOP method is to describe the data in terms of its interface with the program, thus specifying how to use the data. Then, design a class to implement the interface. In general, private data members store information, and public member functions (also known as methods) provide the only means of accessing the data. Classes combine data and methods into a single unit, and their privacy enables data hiding.
  2. Typically, class declarations are divided into two parts, which are usually kept in separate files. Class declarations (including methods represented by function prototypes) should be placed in header files. The source code that defines the member functions is placed in the method file. This separates the interface description from the implementation details. In theory, you only need to know the public interface to use the class. Of course, one can look at the implementation (unless only the compiled form is provided), but a program should not depend on its implementation details, such as knowing that a value is stored as an int. As long as the program and the class communicate only through the methods that define the interface, the programmer is free to make independent improvements to any part without worrying about unintended side effects from doing so.
  3. A class is a user-defined type and an object is an instance of a class. This means that the object is a variable of this type, such as memory allocated by new as described by the class. C++ tries to make user-defined types as similar as possible to standard types, so objects, pointers to objects, and arrays of objects can be declared. You can pass objects by value, return objects as functions, and assign one object to another object of the same type. If a constructor is provided, when the object is created, the object can be initialized. If a destructor method is provided, the program executes that function after the object dies.
  4. Each object stores its own data while sharing class methods. If mr_object is the object name and try_me( ) is a member function, you can use the member operator period to call the member function: mr_object.try_me( ). In OOP, this function call is called try_me message sent to the mr_object object. When referring to class data members in the try_me( ) method, the corresponding data members of the mr_object object will be used. Likewise, the function call i_object.try_me( ) will access the data members of the i_object object.
  5. If you want a member function to operate on multiple objects, you can pass additional objects to it as parameters. If the method requires an explicit reference to the object on which it is called, the this pointer can be used. Since the this pointer is set to the address of the calling object, *this is an alias for that object.
  6. Classes are well suited for describing ADTs. The public member function interface provides the services described by the ADT, and the private parts of the class and the code for the class methods provide the implementation, which is hidden from clients of the class.

10.9 Review Questions

  1. What are classes?
    A class is a definition of a user-defined type. The class declaration specifies how the data will be stored and the methods (class member functions) used to access and manipulate that data.
  2. How do classes implement abstraction, encapsulation, and data hiding?
    The class represents the operations that people can perform on the class object through the public interface of the class method, which is abstraction.
    Data members of a class can be private (the default), which means that the data can only be accessed through member functions, which is data hiding.
    The specific details of the implementation (such as data representation and method code) are hidden, which is encapsulation.
  3. What is the relationship between objects and classes?
    A class defines a type, including how to use it.
    An object is a variable or other data object (as created by new) that is created and used according to the class definition.
    The relationship between classes and objects is the same as that between standard types and their variables.
  4. Besides being a function, what is the difference between a class function member and a class data member?
    If you create multiple objects of a given class, each object has its own memory space for data;
    but all objects use the same set of member functions (usually, methods are public and data is private, but this is just A matter of strategy, not a requirement for a class).
  5. When is the class constructor called? What about class destructors?
    The constructor of the class is called when an object of the class is created or when the constructor is called explicitly. When the object expires, the class's destructor will be called.
  6. What is a default constructor and what are the benefits of having a default constructor?
    A default constructor is a constructor that has no parameters or all parameters have default values.
    Having a default constructor allows you to declare an object without initializing it, even if an initializing constructor has been defined. It also enables the declaration of arrays.
  7. What are this and *this?
    The this pointer is a pointer that can be used by class methods, and it points to the object used to call the method. Therefore, this is the address of the object, and *this is the object itself.

10.10 Programming Exercises

5. Write a program that adds and removes customer structures from the stack (the stack is represented by the Stack class declaration). Each time the customer structure is deleted, its payment value is added to the total and the total is reported. Note: It should be possible to use the Stack class without modification; just modify the typedef declaration so that Item is of type customer instead of unsigned long.

(The program below contains the meaning of the eighth question)
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;
}

Guess you like

Origin blog.csdn.net/qq_39751352/article/details/126808697