曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 2260|回复: 0
打印 上一主题 下一主题

包含(组合)关系

[复制链接]

716

主题

734

帖子

2946

积分

超级版主

Rank: 9Rank: 9Rank: 9

积分
2946
跳转到指定楼层
楼主
发表于 2013-12-9 18:41:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
比如有一个学生类,它包含了学生的姓名与一组考试成绩,“姓名” 和 “成绩” 与学生不是 is-a 关系,因为“学生”既不是姓名,也不是成绩;而是 has-a 关系,因为“学生”有姓名,也有考试分数。用来建立 has-a 关系的 C++ 技术是“组合”或称为“包含”,即创建一个包含其他类对象的类。比如可以将学生类 Student 类声明为:
class Student
{   
private:
    std::string name;   // name 是 string 对象
    std::valarray<double> scores;   // scores 是一个 valarray<double> 对象
    ... ...

name 和 scores 被声明为私有的, string 和 valarray<double> 类的公有接口可以访问与修改 name 和 scores 对象(比如 name.size() ,scores.sum()),但在类的外面不能这样做,而只能通过 Student 类的公有接口访问 name 和 scores 。

使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口是 is-a 关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是 has-a 关系的组成部分。


对于 has-a 关系来说,类对象不能自动获得它所包含的对象的接口是一件好事。比如,string 类中重载的 + 运算符可以将两个字符串连接起来,然而这种方法对于 Student 来说没有什么意义,因为将两个“学生”串接起来没意义。尽管如此,但并不是说所有的接口对新类都没有意义。比如,可能希望使用 string 接口中的 operator<() 方法,以用来将对象按姓名进行排序,为此可以定义 Student::operator<() 成员函数,并在其内部使用 string::operator<() 来处理。

下面在 studentc.h 里声明了 Student 类:
[C++] 纯文本查看 复制代码
// 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;       // 包含对象
    ArrayDb scores;         // 包含对象

    // 输出 scores 的私有方法
    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;

// 友元
    // 输入
    friend std::istream & operator>>(std::istream & is, Student & stu);  // 一单词

    friend std::istream & getline(std::istream & is, Student & stu);     // 一行

    // 输出
    friend std::ostream & operator<<(std::ostream & os,
                                     const Student & stu);
};

#endif

对于 explicit Student(int n) : name("Nully"), scores(n) {} 这个构造函数,它只有一个参数,对于这种情况,如果前面没有 explicit 作为限定的话,会存在从参数类型到类类型的隐式转换,这会带来问题,如对于下面的代码:
Student doh("Homer", 10);   // 设置 Homer 这个学生,并创建了可以存储 10 门课程成绩的数组
doh = 5;   //导致 Homer 这个学生被重置为 "Nully",并且原来可以存储 10 门功课的数组现在只能存储 5 门功课

实际上,doh = 5; 原本是打算将某一门成绩的分数设置为 5 分,但是如上这么一搞,不但没达到设置的目的,反而破坏了原来的数据,正确的写法应为 doh[0] = 5; 。

因此,要用 explicit 来屏蔽隐式转换,在使用了 explicit 之后,编译器就会认为上述赋值运算符是错误的。

初始化被包含的对象成员
在构造函数 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++ 将使用成员对象所属类的默认构造函数。

对于初始化顺序需要注意:如果初始化列表中含有多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序

如何使用包含对象的接口
被包含对象的接口不是共有的,但可以在类方法中使用它,比如:
  1. double Student::Average() const
  2. {
  3.     if (scores.size() > 0)
  4.         return scores.sum()/scores.size();  
  5.     else
  6.         return 0;
  7. }
复制代码
总之,Student 对象调用 Student 的方法(Average()),然后在其中使用被包含对象来调用它自己的方法。

studentc.h 对应的 cpp 文件:
[C++] 纯文本查看 复制代码
// studentc.cpp -- Student class using containment
#include "studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;

//public 方法
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 方法
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>>()
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;
}


测试代码:
[C++] 纯文本查看 复制代码
// 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";
    // cin.get();

    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; 
}

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-21 16:59 , Processed in 0.067528 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表