比如有一个学生类,它包含了学生的姓名与一组考试成绩,“姓名” 和 “成绩” 与学生不是 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++ 将使用成员对象所属类的默认构造函数。
对于初始化顺序需要注意:如果初始化列表中含有多个项目时,这些项目被初始化的顺序为它们被声明的顺序,而不是它们在初始化列表中的顺序。
如何使用包含对象的接口
被包含对象的接口不是共有的,但可以在类方法中使用它,比如:- double Student::Average() const
- {
- if (scores.size() > 0)
- return scores.sum()/scores.size();
- else
- return 0;
- }
复制代码 总之,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;
}
|