C++中的代码重用
C++的一个主要目标是促进代码重用。 公有继承是实现这种目标的机制之一,但并不是唯一的机制。 其中之一是使用这样的类成员:本身是另一个类的对象。 这种方法称为包含、组合或层次化。 另一种方法是使用私有或保护继承。 通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象。 多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起。
类模板是另一种重用代码的方法, 类模板使我们能够使用通用术语定义类,然后使用模板来创建针对特定类型定义的特殊类。
- 包含对象成员的类:
- valarray类:
这个类用于处理数值,它支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值等操作。 valarray被定义为一个模板类,以便能够处理不同的数据元素。
模板特性意味着声明对象时,必须指定具体的数据类型。 因此,使用valarray类来声明一个对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型:
valarray<int> q_value; //int类型数组
valarray<double> weights; //double类型数组
类特性意味着要使用valarray对象,需要了解这个类的构造函数和其他类方法:
double gpa[5]={3.1,3.5,3.8,2.9,3.3};
valarray<double> v1; //double类型数组,长度为0
valarray<int>v2(8); //含有8个int类型元素的数组
valarray<int>v3(10,8); //含有8个int类型元素的数组,均设置为10
valarray<double>v4(gpa,4); //含有4个元素的数组,用gpa数组的前4个值进行初始化
下面是这个类的一些方法:
operator[](): 访问各个元素;
size(): 返回包含的元素数;
sum(): 返回所有元素的总和;
max(): 返回最大的元素;
min(): 返回最小的元素。
- Student类设计
这里的关系是has-a,学生有姓名,也有一组考试分数。 通常,用于建立has-a关系的C++技术是组合,即创建一个包含其他类对象的类。
class
{
private:
string name; //为姓名使用一个string对象
valarray<double> scores; //为分数使用一个valarray<double>对象
…
};
//studentc.h -- 利用包含定义Student类
#ifndef STUDENT_H_
#define STUDENT_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(){}
Student(const char *str,const double *pd,int n)
:name(str),scores(pd,n){}
~Student(){}
double Avergae() 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 // !STUDENT_H_
注意关键字explicit的用法:
explicit Student(const std::string & s)
:name(s), scores(){}
explicit Student(int n):name("Nully"),scores(n){}
可以用一个参数调用的构造函数将用作从参数类型到类类型的隐式转换函数。 在上述第二个构造函数中,第一个参数表示数组的元素个数,而不是数组中的值,因此将一个构造函数用作int到Student的转换函数是没有意义的,所以使用explicit关闭隐式转换。
- 使用被包含对象的接口
被包含对象的接口不是公有的,但可以在类方法中使用它。
double Student::Avergae() 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版本的<<运算符的友元函数:
//使用string版本的<<运算符
std::ostream & operator<<(std::ostream & os, const Student & stu)
{
os << "Scores for " << stu.Name << ":\n";
...
}
因为stu.name是一个string对象,所以它将调用函数operator<<(ostream &, const string &),该函数位于string类中。 注意,operator<<(ostream & os, const Student & stu)必须是Student类的友元函数,这样才能访问name成员。
student.cpp包含了能够使用[]运算符来访问Student对象中各项成绩的方法。
//student.cpp -- 使用包含的Student类
#include"studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
//公有方法
double Student::Avergae() 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]; //使用valarray<double>::operator[]()
}
double Student::operator[](int i) const
{
return scores[i];
}
//私有方法
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 (1 % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << "empty array ";
return os;
}
//友元
//使用string版本的>>运算符
istream & operator >>(istream & is, Student & stu)
{
is >> stu.name;
return is;
}
//使用string友元getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
getline(is, stu.name);
return is;
}
//使用string版本的<<运算符
ostream & operator <<(ostream & os, const Student & stu)
{
os << "Scores for " << stu.name << ":\n";
stu.arr_out(os); //使用scores的私有方法
return os;
}
//use_stuc.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 << endl << ada[i];
cout << "average: " << ada[i].Avergae() << endl;
}
cout << "Done.\n";
system("pause");
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;
}
程序运行结果:
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:
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.
- 私有继承
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。 这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
- Student类示例:
class Student:private srd::string, private std::valarray<double>
{
public:
...
};
使用多个基类的继承被称为多重继承(multiple inheritance, MI)。
//studentc.h -- 利用包含定义Student类
#ifndef STUDENT_H_
#define STUDENT_H_
#include<iostream>
#include<string>
#include<valarray>
class Student: private std::string,private std::valarray<double>
{
private:
typedef std::valarray<double>ArrayDb;
//用于scores输出的私有方法
std::ostream & arr_out(std::ostream & os) const;
public:
Student():std::string("Null Student"),ArrayDb(){}
explicit Student(const std::string & s)
:std::string(s), ArrayDb(){}
explicit Student(int n):std::string("Nully"), ArrayDb(n){}
Student(const std::string &s,int n)
:std::string(s), ArrayDb(){}
Student(const char *str,const double *pd,int n)
:std::string(str), ArrayDb(pd,n){}
~Student(){}
double Avergae() 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 // !STUDENT_H_
//studenti.cpp -- 使用包含的Student类
#include"studenti.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;
//公有方法
double Student::Avergae() const
{
if (ArrayDb::size() > 0)
return ArrayDb::sum() / ArrayDb::size();
else
return 0;
}
const string & Student::Name() const
{
return (const string &) *this;
}
double & Student::operator[](int i)
{
return ArrayDb::operator[](i); //使用valarray<double>::operator[]()
}
double Student::operator[](int i) const
{
return ArrayDb::operator[](i);
}
//私有方法
ostream & Student::arr_out(ostream & os) const
{
int i;
int lim = ArrayDb::size();
if (lim > 0)
{
for (i = 0; i < lim; i++)
{
os << ArrayDb::operator[](i)<< " ";
if (1 % 5 == 4)
os << endl;
}
if (i % 5 != 0)
os << endl;
}
else
os << "empty array ";
return os;
}
//友元
//使用string版本的>>运算符
istream & operator >>(istream & is, Student & stu)
{
is >> (string&)stu;
return is;
}
//使用string友元getline(ostream &, const string &)
istream & getline(istream & is, Student & stu)
{
getline(is, (string &)stu);
return is;
}
//使用string版本的<<运算符
ostream & operator <<(ostream & os, const Student & stu)
{
os << "Scores for " << (const string &)stu << ":\n";
stu.arr_out(os); //使用scores的私有方法
return os;
}
//use_stuc.cpp -- 使用组合类
#include<iostream>
#include"studenti.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 << endl << ada[i];
cout << "average: " << ada[i].Avergae() << endl;
}
cout << "Done.\n";
system("pause");
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;
}
程序运行结果:
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 Roonbe
please enter 5 quiz scores:
83 89 72 78 95
Please enter the student's name: Flwur O;Day
please enter 5 quiz scores:
92 89 96 74 64
Student List:
Scores for Gil Bayts:
92 94 96 93 95 average: 94
Scores for Pat Roonbe:
83 89 72 78 95 average: 83.4
Scores for Flwur O;Day:
92 89 96 74 64 average: 83
Done.
- 保护继承
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。
特征 |
公有继承 |
保护继承 |
私有继承 |
公有成员变成 |
派生类的公有成员 |
派生类的保护成员 |
派生类的私有成员 |
保护成员变成 |
派生类的保护成员 |
派生类的保护成员 |
派生类的私有成员 |
私有成员变成 |
只能通过基类接口访问 |
只能通过基类接口访问 |
只能通过基类接口访问 |
能否隐式向上转换 |
是 |
是(但只能在派生类中) |
否 |
上一篇: 继承
下一篇: JAVA中的父类私有成员变量的继承问题