定义类


有些数据会有相关性,相关联的数据组织在一起,对于数据本身的可用性或者是代码的可读性,都会有所帮助,例如,在程序中你可能发现,在进行帐户之类的处理时,帐号、名称、余额这三个数据总是一并出现的,这时可以将它们组织在一起,定义为类:

account.h

#include <string>
using namespace std; 

class Account { 
public: 
    string id;  
    string name; 
    double balance;
};

在文件头文件中定义类,表头文件的名称建议与类名称同名,class是定义类的关键字,Account是类名称,public表示定义的idnamebalance值域(field),都是可以公开访问的。例如:

main.cpp

#include <iostream> 
#include "account.h"

void printAcct(Account *acct) {
    cout << "Account(" 
         << acct->id << ", "
         << acct->name << ", "
         << acct->balance << ")"
         << endl;
}

void printAcct(Account &acct) {
    printAcct(&acct);
}

int main() { 
    Account acct1;
    acct1.id = "123-456-789";
    acct1.name = "Justin Lin";
    acct1.balance = 1000;
    printAcct(acct1);

    Account *acct2 = new Account();
    acct2->id = "789-654-321";
    acct2->name = "Monica Huang";
    acct2->balance = 1000;   
    printAcct(acct2);
    delete acct2;

    return 0; 
}

Account acct1创建了Account的实例,这时acct1在函数执行完毕后就会自动清除,访问实例的值域时可以使用 dot 运算符「.」。

若是Account acct = acct1这类指定,会将acct1的值域复制给acct,若Account的值域占用了许多资源,复制会造成负担的话,可以透过参考或指针来避免复制的动作,例如printAcct(acct1)运用的就是参考。

可以使用new来动态构造Account的实例,动态创建的实例不需要时要使用delete清除,透过指针访问实例成员时,要使用箭头运算符「->」。

从 C 背景来的开发者可能会想,这种风格像是 C 的结构(struct),在 C++ 中,struct也被视为定义类,将以上的class关键字换为struct,程序也可以运作,structclass的差别在于,前者在第一个权限可见的修修饰符出现前(例如publicprivate),定义的成员默认会是公开可访问,而后者默认会是私有(也就是private)。

执行结果如下:

Account(123-456-789, Justin Lin, 1000)
Account(789-654-321, Monica Huang, 1000)

在方才的范例中,初始化Account值域的流程,其实是重复了,若要消弥这类重复,可以定义构造函数(constructor),例如:

account.h

#include <string>
using namespace std; 

class Account { 
public: 
    Account(string id, string name, double balance);
    string id;  
    string name; 
    double balance;
};

在标头文件的构造函数定义中,定义了构造实例时,需要帐号、名称、余额这三个数据,接下来将方才的初始化流程重构至构造函数的实现:

account.cpp

#include <string>
#include "account.h"
using namespace std;

Account::Account(string id, string name, double balance) {
    this->id = id;
    this->name = name;
    this->balance = balance;
}

::是类范围解析(class scope resolution)运算符,在实现类构造函数或方法(method)时,在::前指明实现哪类之定义。

如果没有定义任何构造函数,编译器会自动产生没有参数的默认构造函数,如果自定义了构造函数,就会使用你定义的构造函数,在构造函数或方法的实现中,若要访问实例本身,可以透过this,这是个指针,因此要透过箭头运算符来访问值域。

现在可以如下写个程序来使用Account类:

main.cpp

#include <iostream> 
#include <string>
#include "account.h"

string to_string(Account &acct) {
    return string("Account(") + 
           acct.id + ", " +
           acct.name + ", " +
           std::to_string(acct.balance) + ")";
}

void deposit(Account &acct, double amount) {
    if(amount <= 0) {
        cout << "必须存入正数" << endl;
        return;
    }
    acct.balance += amount;
}

void withdraw(Account &acct, double amount) {
    if(amount > acct.balance) {
        cout << "余额不足" << endl;
        return;
    }
    acct.balance -= amount;
}

int main() { 
    Account acct("123-456-789", "Justin Lin", 1000);
    cout << to_string(acct) << endl;

    deposit(acct, 500);
    cout << to_string(acct) << endl;

    withdraw(acct, 700);
    cout << to_string(acct) << endl;

    return 0; 
}

std::to_string是 C++ 11 定义在string中的函数,执行结果如下:

Account(123-456-789, Justin Lin, 1000.000000)
Account(123-456-789, Justin Lin, 1500.000000)
Account(123-456-789, Justin Lin, 800.000000)

范例中的to_stringdepositwithdraw都是为了Account而设计的,既然这样,为什么不将它们放到Account的定义中呢?

account.h

#include <string>
using namespace std; 

class Account { 
private:
    string id;  
    string name; 
    double balance;

public: 
    Account(string id, string name, double balance);
    void deposit(double amount);
    void withdraw(double amount);
    string to_string();
};

以上只定义了方法的签署,也可以选择在类中同时实现方法,这类方法默认是inline的,选择在类之外实现方法时,则可以明确地指定inline

现在to_stringdepositwithdraw被定义为Account的方法了,也称为成员函数(member function),因为实现时,可以透过this来访问实例,就不用在方法上定义接受Account的参数了,而原本的idnamebalance被放到了private区域,这是因为不想被公开访问,也就只能被构造函数或方法访问,这么一来,就可以定义更动这些值域的流程。

实际上,private在这边是不需要的,如前头谈过的,以class定义类时,在第一个权限可见的修修饰符出现前,定义的成员默认会是私有。

account.cpp

#include <iostream> 
#include <string>
#include "account.h"
using namespace std;

Account::Account(string id, string name, double balance) {
    this->id = id;
    this->name = name;
    this->balance = balance;
}

string Account::to_string() {
    return string("Account(") + 
           this->id + ", " +
           this->name + ", " +
           std::to_string(this->balance) + ")";
}

void Account::deposit(double amount) {
    if(amount <= 0) {
        cout << "必须存入正数" << endl;
        return;
    }
    this->balance += amount;
}

void Account::withdraw(double amount) {
    if(amount > this->balance) {
        cout << "余额不足" << endl;
        return;
    }
    this->balance -= amount;
}

接下来要使用Account就简单多了:

#include <iostream> 
#include <string>
#include "account.h"

int main() { 
    Account acct = {"123-456-789", "Justin Lin", 1000};
    cout << acct.to_string() << endl;

    acct.deposit(500);
    cout << acct.to_string() << endl;

    acct.withdraw(700);
    cout << acct.to_string() << endl;

    return 0; 
}

这就是为什么要定义类,将相关的数据与方法组织在一起的原因:易于使用。面向对象目的之一就是易于使用,当然,可以重用也是面向对象的其中一个目的,不过易用性的考量,往往会比重用来得重要,过于强调重用,反而会设计出不易使用的类。


展开阅读全文