C++ primer plus 第10章对象和类

1 过程性编程和面向对象编程

2 抽象和类

2.1 ifndef

文件中的#ifndef

头件的中的#ifndef,这是一个很关键的东西。

需要注意的是,#ifndef起到的效果是防止一个源文件两次包含同一个头文件,而不是防止两个源文件包含同一个头文件。

而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。

还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:

#ifndef <标识>
#define <标识>

......
......

#endif

2.2 访问控制

公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。防止程序直接访问数据被称为数据隐藏

类表示人们可以类方法的公有接口对类对象执行的操作,这是抽象

类的数据成员可以是私有的(默认值),这意味着只能通过成员函数来访问这些数据,这是数据隐藏

实现的具体细节,(如数据表示和方法的代码)都是隐藏的,这是封装

2.3 类和结构

类对象的默认访问控制是private,而结构的默认访问类型是public

class World
{
    float mass;
    char name[20];  //默认是private
public:
    void tellall(void);
}

2.4 实现类成员函数

类成员函数有两个特征:

  • 定义成员函数时,使用作用域解析运算符::来标识函数所属的类
  • 类方法可以访问类的private组件
void Stock::update(double price)

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

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

3.1 声明和定义构造函数

构造函数原型(声明):

Stock(const & co, long n = 0, double pr = 0.0)

构造函数定义(没有返回类型):

Stock::Stock(const & co, long n = 0, double pr = 0.0)
{
    company = co;
    shares = n;
    share_val = pr;
    set_tot();
}

成员名和参数名不能相同!!!

3.2 使用构造函数

Stock food = Stock("World Cabbage", 50, 2.5);
或
Stock food("World Cabbage", 50, 2.5);

3.3 默认构造函数

定义默认构造函数有两种方法:

  • 一种是给已有构造函数的所有参数提供默认值:
Stock(const & co, long n = 0, double pr = 0.0)
  • 另一种是通过函数重载来定义另一个构造函数——一个没有参数的构造函数
Stock();

用户定义的默认构造函数通常给所有成员提供隐式初始值。例如,下面是Stock类定义的一个默认构造函数:

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

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

声明对象变量时,初始化方式可以为:

Stock first("Concrete Conlomerate"); //调用构造函数
Stock second();  //声明一个返回Stock对象的函数
Stock third;  //隐式的调用默认构造函数
Stock *fourth = new Stock("popo"); //动态对象

3.4 析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

如果构造函数使用了new,则必须提供使用delete的析构函数,如:

class Student{
public:
  Student(){m_name=new char[20];}     //即在堆上定义----故在释放对象时必须要释放掉内存

private:
  char *m_name;
};

故需要添加析构函数:      //销毁时自动调用,没有则由系统默认生成
            //没有返回值,也没有参数,从而不可重载

Student::~Student()
{
    delete m_name;
    m_name=NULL;
}

3.5 改进Stock类

4 this指针

当需要比较两个类中数据成员的大小,并返回变量值较大的类时,函数原型为:

const Stock & topval(const Stock & s) const

函数调用为:

top = stock1.topval(stock2);
或
top = stock2.topval(stock1);

但是在函数实现中会有一个问题:

const Stock & Stock::topval(const Stock & s) const
{
    if (s.total_val > total_val)
        return s;
    else
        return ???????????; //函数无法返回调用该方法的对象
}

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

于是,可以使用this指针返回调用该方法的对象:

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

5 对象数组

声明对象数组的方法和标准类型数组方法相同:

Stock mystuff[4]; //创建一个含有四个Stock对象的数组

使用构造函数来初始化数组元素时,必须为每一个元素调用构造函数。

const int STKS = 4;
Stock stocks[STKS] = {
    Stock("Nono", 12.5, 20),
    Stock("dsa", 200, 2.0),
    Stock("Mono", 130, 20.25),
    Stock("Fleep", 60, 6.5),
}

6 类作用域

当我们要创建一个由所有对象共享的常量是个不错的主意,我们可能认为下面做法是可行的:

class Bakery()
{
private:
    const int Months = 12; //声明一个常量?这是错误的。
    double costs[Months];
    ...
}

声明类只是描述了对象的形式,并没有创建对象,在创建对象之前,将没有用于存储值的空间。

此时有两种方法:

  • 第一种为在类中声明一个枚举,在类声明中声明的枚举的作用域为整个类,因此可以用枚举为整型常量提供作用域为整个类的符号名称。
class Bakery()
{
private:
    enum {Months = 12};
    double costs[Months];
    ...
}
  • 第二种方法为在类中定义常量的方式——使用关键字static
class Bakery()
{
private:
    static const int Months = 12;
    double costs[Months];
    ...
}

将常量与其他静态变量存储在一起,而不是存储在对象中。

7 抽象数据类型

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

下面简要介绍一下栈的特征,首先,栈存储了多个数据项(该特征使得栈成为了一个容器——一种更通用的抽象);其次栈由可对他执行的操作来描述。

  • 可创建空栈
  • 可将数据项添加到栈顶(压入)
  • 可从栈顶删除数据项(弹出)
  • 可查看栈是否填满
  • 可查看栈是否为空

可以将上述描述转换为一个类声明,其中共有函数提供了表示栈操作的接口,而私有数据成员负责存储栈数据。类概念特别适合于ADT方法。