【面向对象】类和对象的基本概念

来源:互联网 发布:java enum 指定值 编辑:程序博客网 时间:2024/05/22 01:55

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

首先,必须考虑如何表示股票。可以将一股作为基本单元,定义一个表示一股股票的类。然而,这意味着需要100个对象才能表示100股,这不现实。相反,可以将某人当前持有的某种股票作为一个基本单元,数据表示中包含他持有股票数量。一种比较现实的方法是,必须记录最初购买价格和购买日期(用于计算纳税)等内容。另外,还必须管理诸如拆股等事件。首次定义类就考虑这么多因素有些困难,因此我们对其进行简化。具体地说,应该将可执行的操作限制为:

获得股票;

增持;

卖出股票;

更新股票价格;

显示关于所有股票的信息。

可以根据上述清单定义stock类的公有接口(如果您有兴趣,还可以添加其他特性)。为支持该接口,需要存储一些信息。我们再次进行简化。例如,不考虑标准的美式股票计价方式。我们将存储下面的信息:

公司名称;

所持股票的数量;

每股的价格;

股票总值。

为帮助识别类,我们遵循一种常见但不通用的约定——将类名的首字母大写。类的声明如下程序清单所示:

#ifndef STOCK_H_#define STOCK_H_#include <iostream>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:Stock(); // default constructorStock(const string & co, long n = 0, double pr = 0.0); ~Stock(); // do-nothing destructorvoid buy(long num, double price); void sell(long num, double price); void update(double price); void show() const; const Stock & topval(const Stock & s) const; }; #endif 
此外,我们还需要创建类描述的第二部分:为那些由类声明中的原型表示的成员函数提供代码。成员函数定义与常规函数定义非常相似,它们有函数头和函数体,也可以有返回类型和参数。但是它们还有两个特殊的特征:

定义成员函数时,使用作用域解析运算符(::)来标识函数所属的类;

类方法可以访问类的private组件。

如下程序清单实现了类的方法:

#include "stock.h"Stock::Stock(){company = "no name"; shares = 0; share_val = 0.0; total_val = 0.0; }Stock::Stock(const string & co, long n, double pr) {company = co; if (n < 0) {cout << "Number of shares can't be negative; " << company << " shares set to 0.\n"; shares = 0; }else shares = n; share_val = pr; set_tot(); }Stock::~Stock(){}void Stock::buy(long num, double price){if (num < 0)cout << "Number of shares purchased can't be negative. Transaction is aborted.\n";  else {shares += num; share_val = price; set_tot(); }}void Stock::sell(long num, double price){if (num < 0)cout << "Number of shares sold can't be negative. Transaction is aborted. \n"; else if (num > shares) cout << "You can't sell more than you have! Transaction is aborted. \n"; else {shares -= num; share_val = price; set_tot(); }}void Stock::update(double price){share_val = price; set_tot(); }void Stock::show() const {ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield); streamsize prec = cout.precision(3); cout << "Company: " << company << " Shares: " << shares << endl; cout << " Share Price: $" << share_val; cout.precision(2); cout << " Total Worth: $" << total_val << endl; cout.setf(orig, ios_base::floatfield); cout.precision(prec); }const Stock & Stock::topval(const Stock & s) const {if (s.total_val > total_val) return s; else return *this; }
我们对以上程序作如下说明:

1. 类和结构

类描述看上去很像是包含成员函数以及public和private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。它们之间的唯一区别就是,结构的默认访问类型是public,而类为private。

2. 内联方法

其定义位于类声明中的函数都将自动成为内联函数,因此Stock::set_tot()是一个内联函数。类声明常将短小的成员函数作为内联函数,set_tot()符合这样的要求。

如果愿意,也可以在类声明之外定义成员函数,并使其成为内联函数。为此,只需要在类实现部分中定义函数时使用inline限定符即可:

inline void Stock::set_tot(){total_val = shares * share_val; }
内联函数的特殊规则要求在每个使用它们的文件中都要对其进行定义。确保内联定义对多文件程序中的所有文件都可用的、最简便的方法是:将内联定义放在定义类的头文件中(有些开发系统包含智能链接程序,允许将内联定义放在一个独立的实现文件)。

3. 类的构造函数和析构函数

(1) 构造函数

C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员。更准确地说,C++为这些成员函数提供了名称和使用语法,而程序员需要提供方法定义。名称与类名相同。构造函数的原型和函数头有一个有趣的特征——虽然没有返回值,但没有被声明为void类型。实际上,构造函数没有声明类型。

(2) 默认构造函数

默认构造函数是在未提供显示初始值时,用来创建对象的构造函数。如果没有提供任何构造函数,则C++将自动提供默认构造函数。它是默认构造函数的隐式版本,不做任何工作。对于Stock类来说,默认构造函数可能如下:

Stock() {}
当且仅当没有构造函数时,编译器才会提供默认构造函数。

定义默认构造函数的方式有两种,一种是给已有的构造函数的所有参数提供默认值,另一种方式是通过函数重载来定义另一个构造函数。

由于只能有一个默认构造函数,因此不要同时采用这两种方式。

注意:在设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数。

(3) 析构函数

用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数,该函数的名称令人生畏——析构函数。

和构造函数一样,析构函数的名称也很特殊:在类名类加上~。另外,和构造函数一样,析构函数也可以没有返回值和声明类型。与构造函数不同的是,析构函数没有参数,因此,Stock析构函数的原型必须是这样的:

~Stock(); 
什么时候应调用析构函数呢?这由编译器决定,通常不应该在代码中显式地调用析构函数。如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用。如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时(该对象在其中定义的)自动被调用。如果对象是通过new创建的,则它将驻留在栈内存或自由存储区中,当使用delete来释放内存时,其析构函数将自动被调用。最后,程序可以创建临时对象来完成特定的操作,在这种情况下,程序将在结束对该对象的使用时自动调用其析构函数。

由于在类对象过期时析构函数将自动被调用,因此必须有一个析构函数。如果程序员没有提供析构函数,编译器将隐式地声明一个默认析构函数,并在发现导致对象被删除的代码后,提供默认析构函数的定义。

4. const成员函数

如果成员函数show()的定义不加上const,请看下面的代码:

const Stock land = ("Kludgehorn Properties"); land.show(); 
编译器将拒绝第二行。这是什么原因呢?因为show()的代码无法确保调用对象不被修改——调用对象和const一样,不应该被修改。可以通过将函数参数声明为const引用或指向const的指针来解决这种问题。但这里存在语法问题:show()方法没有参数。相反,它所使用的对象是由方法调用隐式地提供的。需要一种新的语法——保证函数不会修改调用对象。C++的解决方法是将const关键字放在函数的括号后面。也就是说,show()声明应该像这样:

void show() const;

以这种方式声明和定义的类函数被称为const成员函数。就像应尽可能将const引用和指针引用作函数形参一样,只要类方法不修改调用对象,就应将其声明为const。

5. this指针
this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)。

在本代码示例中我们要注意:

每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式 *this。在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值。

然而,要返回的并不是this,因为this是对象的地址。要返回的是对象本身,即*this(将解除引用运算符*用于指针,将得到指针指向的值)。

6. 在类中声明常量

有时候,使符号常量的作用域为类很有用。例如,类声明可能使用字面值30来指定数组的长度,由于该常量对于所有对象来说都是相同的,因此创建一个由所有对象共享的常量是个不错的主意。您可能以为这样做可行:

class Bakery {    private:        const int Months = 12;         double costs[Months];         ......

但这是行不通的,因为声明类只是描述了对象的形式,并没有创建对象。因此,在创建对象前,将没有用于存储值的空间。常量初始化应放在构造函数中。另外,有两种方法可以实现这个目标,并且效果相同。

第一种方式是在类中声明一个枚举。在类声明中声明的枚举的作用域为整个类,因此可以用枚举为整型常量提供作用域为整个类的符号名称。也就是说,可以这样开始Bakery声明:

class Bakery {    private:        enum { Months = 12 };         double costs[Months];         ......
C++提供了另一种在类中定义常量的方式——使用关键字static:

class Bakery {    private:        static const int Months = 12;         double costs[Months];         ......
0 0
原创粉丝点击