C/C++ L9

2018/08/03 C/C++

C/C++程序语言设计 Level9。

抽象和类

生活中充满复杂性,处理复杂性的方法之一是简化和抽象。人的身体是由无数个原子组成的,而一些学者认为人的思想是由半自主的主体组成的。但将人自己看作一个实体将简单得多。在计算中,为了根据信息与用户之间的接口来表示它,抽象是至关重要的。也就是说,将问题的本质特征抽象出来,并根据特征来描述解决方案。

类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

#include <string>
using namespace std;

class Stock{
    private:
        string company;
        long shares;
        double share_val;
        double total_val;
        void set_tot(){
            total_val = shares * share_val;
        }
    public:
        void acquire(const string& co, long n, double pr);
        void buy(long num, double price);
        void sell(long num, double price);
        void update(double price);
        void show();
};

下面的声明创建两个Stock对象,它们分别命名为sally和solly。

Stock sally;
Stock solly;

访问控制

关键字private和public描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但是只能通过公有函数来访问对象的私有成员。因此,公有成员函数是程序与对象的私有成员之间的桥梁,提供了对象和程序之间的接口,防止程序直接访问数据被称为数据隐藏。C++还提供第三个关键字protected,这里不做阐述。

类设计尽可能将公有接口与实现细节分开。公有接口表示设计的抽象组件。将实现细节放在一起并将它们与抽象分开称为封装。数据隐藏是一种封装,将实现的细节隐藏在私有部分中。

一般的,数据项通常放在私有部分,组成类接口的成员函数放在公有部分。类声明中默认是private访问控制。

实现类成员函数

上面还缺少了一部分,是公有接口的函数实现。定义成员函数时,使用作用域解析运算符来标识函数所属的类。

void Stock::acquire(const string& co, long n, double pr){
    company = co;
    if(n<0){
        cout << "Number of shares can't be negative;";
        cout << company << " shares set to 0" << endl;
        shares = 0;
    } else {
        shares = n;
    }
    share_val = pr;
    set_tot();
}

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

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

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

void Stock::show(){
    cout << "Company: " << company;
    cout << " Shares: " << shares << endl;
    cout << " Shares Price: $" << share_val;
    cout << " Total Worth: $" << total_val << endl;
}

使用类

#include <iostream>
using namespace std;

int main(){
    Stock f;
    f.acquire("NanoSmart", 20, 12.50);
    f.show();
    f.buy(15, 18.125);
    f.show();
    f.sell(400, 20.00);
    f.show();
    f.buy(300000, 40.125);
    f.show();
    f.sell(300000, 0.125);
    f.show();
    return 0;
}

小结

指定类设计的第一步是提供类声明。类声明类似结构声明,可以包括数据成员和函数成员。声明有私有部分,在其中声明的成员只能通过成员函数进行访问。声明还具有公有部分,在其中声明的成员可被使用类对象的程序直接访问。通常,数据成员被放在私有部分中,成员函数被放在公有部分中,因此典型的类声明的格式如下。

class className{
    private:
        data member declarations
    public:
        member function prototypes
};

指定类设计的第二步是实现类成员函数。可以在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义。在这种情况下,需要使用作用域解析运算符来指出成员函数属于哪个类。例如,假设Bozo有一个名为Retort()的成员函数,该函数返回char指针,则其函数头如下:

char* Bozo::Retort()

换言之,Retort()不仅是一个char*类型的函数,而是一个属于Bozo类的char*函数。

构造函数

上面Stock对象不能像初始化int或者结构那样来初始化,原因是数据部分的访问状态是私有的,程序不能直接访问数据成员。我们需要使用构造函数来进行对象的初始化。

声明和定义构造函数

// constructor prototype with some default arguments
Stock(const string& co, long n = 0, double pr = 0.0);

// constructor definition
Stock::Stock(const string& co, long n, double pr){
    company = co;
    if(n<0){
        cerr << "Number of shares can't be negative;"
        cerr << company << " shares set to 0." << endl;
        shares = 0;
    } else {
        shares = n;
    }
    share_val = pr;
    set_tot();
}

使用构造函数

C++在每次创建类对象时,都会使用类构造函数,下面是两种构造函数的调用方式。

  1. 显式地调用构造函数:
Stock food = Stock("Word Cabbage", 250, 1.25);
  1. 隐式地调用构造函数:
Stock garment("Furry Mason", 50, 2.5);

默认构造函数

默认构造函数是在未提供显式初始化值时,用来创建对象的构造函数。如果没有提供任何构造函数,C++将自动提供默认构造函数。例如:

Stock::Stock(){}

对的,它看起来什么都没做。下面是初始化的默认构造函数:

Stock::Stock(){
    company = "no name";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

析构函数

用构造函数创建对象之后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数,该函数的名字叫做析构函数。析构函数完成清理工作,例如对象中使用new分配内存,则析构函数将使用delete来释放这些内存。析构函数的格式:

Stock::~Stock(){}

因为上述的Stock类没有使用new,所以析构函数没有实际要完成的任务。什么时候应该调用析构函数呢?其实这是由编译器决定的,通常不会在代码中显式地调用析构函数。

this指针

this指针指向用来调用成员函数的对象,这样函数调用stock1.topval(stock2)将this设置为stock1对象的地址,使得这个指针可以用于topval方法。下面是topval方法的定义:

const Stock& Stock::topval(const Stock& s) const{
    if(s.total_val > total_val)
        return s;
    else
        return *this;
}

总结

面向对象编程强调的是程序如何表示数据。用OOP方法解决编程问题的第一步是根据它与程序之间的接口来描述数据,从而指定如何使用数据。然后,设计一个类来实现该接口。一般来说,私有数据成员存储信息,公有成员函数提供访问数据的唯一途径。类将数据和方法组合成一个单元,其私有性实现数据隐藏。

Search

    Table of Contents