当前位置:嗨网首页>书籍在线阅读

05-Student类示例

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

14.1.3 Student类示例

现在需要提供Student类的定义,当然它应包含构造函数以及一些用作Student类接口的方法。程序清单14.1是Student类的定义,其中所有构造函数都被定义为内联的;它还提供了一些用于输入和输出的友元函数。

程序清单14.1 studentc.h

// studentc.h -- defining a Student class using containment
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include <iostream>
#include <string>
#include <valarray>
class Student
{
private:
    typedef std::valarray<double> ArrayDb;
    std::string name; // contained object
    ArrayDb scores;   // contained object
    // private method for scores output
    std::ostream & arr_out(std::ostream & os) const;
public:
    Student() : name("Null Student"), scores() {}
    explicit Student(const std::string & s)
        : name(s), scores() {}
    explicit Student(int n) : name("Nully"), scores(n) {}
    Student(const std::string & s, int n)
        : name(s), scores(n) {}
    Student(const std::string & s, const ArrayDb & a)
        : name(s), scores(a) {}
    Student(const char * str, const double * pd, int n)
        : name(str), scores(pd, n) {}
    ~Student() {}
    double Average() const;
    const std::string & Name() const;
    double & operator[](int i);
    double operator[](int i) const;
// friends
    // input
    friend std::istream & operator>>(std::istream & is,
                                    Student & stu); // 1 word
    friend std::istream & getline(std::istream & is,
                                  Student & stu); // 1 line
    // output
    friend std::ostream & operator<<(std::ostream & os,
                                     const Student & stu);
};
#endif

为简化表示,Student类的定义中包含下述typedef:

typedef std::valarray<double> ArrayDb;

这样,在以后的代码中便可以使用表示ArrayDb,而不是std::valarray,因此类方法和友元函数可以使用ArrayDb类型。将该typedef放在类定义的私有部分意味着可以在Student类的实现中使用它,但在Student类外面不能使用。

请注意关键字explicit的用法:

explicit Student(const std::string & s)
    : name(s), scores() {}
explicit Student(int n) : name("Nully"), scores(n) {}

本书前面说过,可以用一个参数调用的构造函数将用作从参数类型到类类型的隐式转换函数;但这通常不是好主意。在上述第二个构造函数中,第一个参数表示数组的元素个数,而不是数组中的值,因此将一个构造函数用作int到Student的转换函数是没有意义的,所以使用explicit关闭隐式转换。如果省略该关键字,则可以编写如下所示的代码:

Student doh("Homer", 10); // store "Homer", create array of 10 elements
doh = 5; // reset name to "Nully", reset to empty array of 5 elements

在这里,马虎的程序员键入了doh而不是doh[0]。如果构造函数省略了explicit,则将使用构造函数调用Student(5)将5转换为一个临时Student对象,并使用“Nully”来设置成员name的值。因此赋值操作将使用临时对象替换原来的doh值。使用了explicit后,编译器将认为上述赋值运算符是错误的。

C++和约束

C++包含让程序员能够限制程序结构的特性——使用explicit防止单参数构造函数的隐式转换,使用const限制方法修改数据,等等。这样做的根本原因是:在编译阶段出现错误优于在运行阶段出现错误。

1.初始化被包含的对象

构造函数全都使用您熟悉的成员初始化列表语法来初始化name和score成员对象。在前面的一些例子中,构造函数用这种语法来初始化内置类型的成员:

Queue::Queue(int qs) : qsize(qs) {...} // initialize qsize to qs

上述代码在成员初始化列表中使用的是数据成员的名称(qsize)。另外,前面介绍的示例中的构造函数还使用成员初始化列表初始化派生对象的基类部分:

hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {...}

对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。对于成员对象,构造函数则使用成员名。例如,请看程序清单14.1的最后一个构造函数:

Student(const char * str, const double * pd, int n)
       : name(str), scores(pd, n) {}

因为该构造函数初始化的是成员对象,而不是继承的对象,所以在初始化列表中使用的是成员名,而不是类名。初始化列表中的每一项都调用与之匹配的构造函数,即name(str)调用构造函数string(const char ),scores(pd, n)调用构造函数ArrayDb(const double , int)。

如果不使用初始化列表语法,情况将如何呢?C++要求在构建对象的其他部分之前,先构建继承对象的所有成员对象。因此,如果省略初始化列表,C++将使用成员对象所属类的默认构造函数。

初始化顺序

当初始化列表包含多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。例如,假设Student构造函数如下:

Student(const char * str, const double * pd, int n)
       : scores(pd, n), name(str) {}

则name成员仍将首先被初始化,因为在类定义中它首先被声明。对于这个例子来说,初始化顺序并不重要,但如果代码使用一个成员的值作为另一个成员的初始化表达式的一部分时,初始化顺序就非常重要了。

2.使用被包含对象的接口

被包含对象的接口不是公有的,但可以在类方法中使用它。例如,下面的代码说明了如何定义一个返回学生平均分数的函数:

double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum()/scores.size();
    else
        return 0;
}

上述代码定义了可由Student对象调用的方法,该方法内部使用了valarray的方法size()和sum()。这是因为scores是一个valarray对象,所以它可以调用valarray类的成员函数。总之,Student对象调用Student的方法,而后者使用被包含的valarray对象来调用valarray类的方法。

同样,可以定义一个使用string版本的<<运算符的友元函数:

// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << stu.name << ":\n";
    ...
}

因为stu.name是一个string对象,所以它将调用函数operatot<<(ostream &, const string &),该函数位于string类中。注意,operator<<(ostream & os, const Student & stu)必须是Student类的友元函数,这样才能访问name成员。另一种方法是,在该函数中使用公有方法Name(),而不是私有数据成员name。

同样,该函数也可以使用valarray的<<实现来进行输出,不幸的是没有这样的实现;因此,Student类定义了一个私有辅助方法来处理这种任务:

// private method
ostream & Student::arr_out(ostream & os) const
{
    int i;
    int lim = scores.size();
    if (lim > 0)
    {
        for (i = 0; i < lim; i++)
        {
            os << scores[i] << " ";
            if (i % 5 == 4)
                os << endl;
        }
        if (i % 5 != 0)
            os << endl;
    }
    else
        os << " empty array ";
    return os;
}

通过使用这样的辅助方法,可以将零乱的细节放在一个地方,使得友元函数的编码更为整洁:

// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << stu.name << ":\n";
    stu.arr_out(os); // use private method for scores
    return os;
}

辅助函数也可用作其他用户级输出函数的构建块——如果您选择提供这样的函数的话。

程序清单14.2是Student类的类方法文件,其中包含了让您能够使用[ ]运算符来访问Student对象中各项成绩的方法。

程序清单14.2 student.cpp

// studentc.cpp -- Student class using containment
#include "studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
//public methods
double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum()/scores.size();
    else
        return 0;
}
const string & Student::Name() const
{
    return name;
}
double & Student::operator[](int i)
{
    return scores[i];   // use valarray<double>::operator[]()
}
double Student::operator[](int i) const
{
    return scores[i];
}
// private method
ostream & Student::arr_out(ostream & os) const
{
    int i;
    int lim = scores.size();
    if (lim > 0)
    {
        for (i = 0; i < lim; i++)
        {
            os << scores[i] << " ";
            if (i % 5 == 4)
                os << endl;
        }
        if (i % 5 != 0)
            os << endl;
    }
    else
        os << " empty array ";
    return os;
}
// friends
// use string version of operator>>()
istream & operator>>(istream & is, Student & stu)
{
    is >> stu.name;
    return is;
}
// use string friend getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
    getline(is, stu.name);
    return is;
}
// use string version of operator<<()
ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << stu.name << ":\n";
    stu.arr_out(os); // use private method for scores
    return os;
}

除私有辅助方法外,程序清单14.2并没有新增多少代码。使用包含让您能够充分利用已有的代码。

3.使用新的Student类

下面编写一个小程序来测试这个新的Student类。出于简化的目的,该程序将使用一个只包含3个Student对象的数组,其中每个对象保存5个考试成绩。另外还将使用一个不复杂的输入循环,该循环不验证输入,也不让用户中途退出。程序清单14.3列出了该测试程序,请务必将该程序与Student.cpp一起进行编译。

程序清单14.3 use_stuc.cpp

// use_stuc.cpp -- using a composite class
// compile with studentc.cpp
#include <iostream>
#include "studentc.h"
using std::cin;
using std::cout;
using std::endl;
void set(Student & sa, int n);
const int pupils = 3;
const int quizzes = 5;
int main()
{
    Student ada[pupils] =
        {Student(quizzes), Student(quizzes), Student(quizzes)};
    int i;
    for (i = 0; i < pupils; ++i)
        set(ada[i], quizzes);
    cout << "\nStudent List:\n";
    for (i = 0; i < pupils; ++i)
        cout << ada[i].Name() << endl;
    cout << "\nResults:";
    for (i = 0; i < pupils; ++i)
    {
        cout << endl << ada[i];
        cout << "average: " << ada[i].Average() << endl;
    }
    cout << "Done.\n";
    return 0;
}
void set(Student & sa, int n)
{
    cout << "Please enter the student's name: ";
    getline(cin, sa);
    cout << "Please enter " << n << " quiz scores:\n";
    for (int i = 0; i < n; i++)
        cin >> sa[i];
    while (cin.get() != '\n')
        continue;
}

下面是程序清单14.1~程序清单14.3组成的程序的运行情况:

Please enter the student's name: Gil Bayts
Please enter 5 quiz scores:
92 94 96 93 95
Please enter the student's name: Pat Roone
Please enter 5 quiz scores:
83 89 72 78 95
Please enter the student's name: Fleur O’Day
Please enter 5 quiz scores:
92 89 96 74 64
Student List:
Gil Bayts
Pat Roone
Fleur O'Day
Results:
Scores for Gil Bayts:
92 94 96 93 95
average: 94
Scores for Pat Roone:
83 89 72 78 95
average: 83.4
Scores for Fleur O'Day:
92 89 96 74 64
average: 83
Done.