C++一个主要目标就是促进代码重用。共有继承就是实现这种目标机制之一,还有私有继承和保护继承(用于实现has-a关系),类模板也是另一个重用代码的方法。
1 包含对象成员的类:
1.1valarray类:
这个类包含于头文件valarray中,用于处理数值,它支持诸如将数组中所有元素的值相加以及在数组中找出最大和最小的值等操作。 valarray被定义为一个模板类,以便能够处理不同的数据元素。
模板特性意味着声明对象时,必须指定具体的数据类型。 因此,使用valarray类来声明一个·对象时,需要在标识符valarray后面加上一对尖括号,并在其中包含所需的数据类型:
valarry<int>q_values //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(): 返回最小的元素。
1.2 student类:
这里的关系不是is-a,学生不是姓名,分数;而是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.
2 :私有继承&&保护继承&&多重继承
(1)使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。 这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。
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.
(2)
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。私有成员只能通过基类接口访问。
(3)
多重继承描述的是有多个直接基类的类,共有多继承表示的也是is-a的关系。如
class SingerWaiter:public Waiter,public Singer{...}
而私有多重继承和保护多重继承可以表示has-a的关系。
补充知识虚基类:虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。例如,通过在类中声明使用关键字virtual,可以使Worker被用作singer和waiter的虚基类:
class Singer:virtual public Worker{...};
class Waiter:public virtual Worker{...};
然后
class SingingWaiter:public Singer,public Waiter{...};
现在,SingingWaiter对象将只包含Worker对象的一个副本,从本质上说,继承的Singer和Waiter对象共享一个Worker对象,而不是各自引入自己的Worker对象副本。因为SingingWaiter现在只包含一个Worker
子对象,所以可以使用多态。
3 :类模板
可以定义专门用于存储double值或string对象的Stack类,除了保存的对象类型不同外,这两种Stack类的代码是相同的。 然而,与其编写新的类声明,不如编写一个泛型(即独立于类型的)栈,然后将具体的类型作为参数传递给这个类。
定义模板类
和模板函数一样,模板类以下面这样的代码开头:
template <class Type>
关键字template告诉编译器,将要定义一个模板。 尖括号中的内容相当于函数的参数列表。 可以把关键字class看作是变量的类型名,该变量接受类型作为其值,把Type看作是该变量的名称。
//stacktp.h -- 栈模板
#ifndef STACKTP_H_
#define STACKTP_H_
template<class Type>
class Stack
{
private:
enum { MAX = 10 };
Type items[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item);
bool pop(Type & item);
};
template<class Type>
Stack<Type>::Stack()
{
top = 0;
}
template<class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template<class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template<class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] == item;
return true;
}
else
return false;
}
template<class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif // !STACKTP_H_
使用模板类
仅在程序包含模板并不能生成模板类,而必须请求实例化。 为此,需要声明一个类型为模板类的对象,方法是使用所需的具体类型替代泛型名。 例如,下面的代码创建两个栈,一个用于存储int,另一个用于存储string对象:
Stack<int> kernels;
Stack<string> colonels;
看到上述声明后,编译器将按Stack模板来生成两个独立的类声明和两组独立的类方法。
泛型标识符——例如这里的Type——成为类型参数,这意味着它们类似于变量,但赋给它们的不能是数字,而只能说类型。
注意,必须显示地提供所需的类型,这与常规的函数模板是不同的,因为编译器可以根据函数的参数类型来确定要生成哪种函数。
//stacktp.h -- 栈模板
#ifndef STACKTP_H_
#define STACKTP_H_
template<class Type>
class Stack
{
private:
enum { MAX = 10 };
Type items[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item);
bool pop(Type & item);
};
template<class Type>
Stack<Type>::Stack()
{
top = 0;
}
template<class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template<class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template<class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < MAX)
{
items[top++] == item;
return true;
}
else
return false;
}
template<class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif // !STACKTP_H_
//stacktem.cpp -- 测试template stack类
#include<iostream>
#include<string>
#include<cctype>
#include"stacktp.h"
using std::cin;
using std::cout;
int main()
{
Stack<std::string>st; //创建一个空栈
char ch;
std::string po;
cout << "Please enter A to add a purchase order,\n"
<< "P to process a PO, or Q to quit.\n";
while (cin >> ch && std::toupper(ch) != 'Q')
{
while (cin.get() != '\n')
continue;
if (!std::isalpha(ch))
{
cout << '\a';
continue;
}
switch (ch)
{
case'A':
case'a':
cout << "Enter a PO number to add: ";
cin >> po;
if (st.isfull())
cout << "stack already full\n";
else
st.push(po);
break;
case 'P':
case 'p':
if (st.isempty())
cout << "stack alreay empty\n";
else
{
st.pop(po);
cout << "PO #"<<po<< " popped\n";
break;
}
}
cout << "Please enter A to add a purchase order,\n"
<< "p to process a PO, or Q to quit.\n";
}
cout << "Bye.\n";
system("pause");
return 0;
}
深入探讨模板类
下列程序重新定义了Stack<Type>类,使Stack构造函数能接受一个可选大小的参数。 这涉及到在内部使用动态数组。 因此,Stack类需要包含一个析构函数、一个复制构造函数和一个赋值运算符。
//stacktp1.h -- 栈模板
#ifndef STACKTP_H_
#define STACKTP_H_
template<class Type>
class Stack
{
private:
enum { SIZE = 10 }; //默认大小
int stacksize;
Type *items;
int top;
public:
explicit Stack(int ss=SIZE);
Stack(const Stack & st);
~Stack() { delete[]items; }
bool isempty() { return top == 0; }
bool isfull() { return top == stacksize; }
bool push(const Type & item);
bool pop(Type & item);
Stack & operator=(const Stack & st);
};
template<class Type>
Stack<Type>::Stack(int ss):stacksize(ss),top(0)
{
items = new Type[stacksize];
}
template<class Type>
Stack<Type>::Stack(const Stack & st)
{
stacksize = st.stacksize;
top = st.top;
items = new Type[stacksize];
for (int i = 0; i < top; i++)
items[i] = st.items[i];
}
template<class Type>
bool Stack<Type>::push(const Type & item)
{
if (top < stacksize)
{
items[top++] == item;
return true;
}
else
return false;
}
template<class Type>
bool Stack<Type>::pop(Type & item)
{
if (top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
template<class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
if (this == &st)
return *this;
delete[]items;
stacksize = st.stacksize;
top = st.top;
items = new Type[stacksize];
for (int i = 0; i < top; i++)
items[i] = st.item[i];
return *this;
}
#endif // !STACKTP_H_
//stkoptr1.cpp -- 测试指针栈
#include<iostream>
#include<cstdlib>
#include<ctime>
#include"stacktp1.h"
const int Num = 10;
int main()
{
std::srand(std::time(0));
std::cout << "Please enter stack size: ";
int stacksize;
std::cin >> stacksize;
//创建一个大小为stacksize的空栈
Stack<const char *>st(stacksize);
//入栈
const char * in[Num] = {
"1: Hank Gilgamesh", "2: Kiki Ishtar",
"3: Betty Rocker","4: Ian Flagranti",
"5: Wolfgang Kibble", "6: Portia Koop",
"7: Joy Almodo", "8: Xaverie Paprika",
"9: Juan Moore","10: Misha Mache"
};
//出栈
const char *out[Num];
int processed = 0;
int nextin = 0;
while (processed < Num)
{
if (st.isempty())
st.push(in[nextin++]);
else if (st.isfull())
st.pop(out[processed++]);
else if (std::rand() % 2 && nextin < Num)
st.push(in[nextin++]);
else
st.pop(out[processed++]);
}
for (int i = 0; i < Num; i++)
std::cout << out[i] << std::endl;
std::cout << "Bye\n";
system("pause");
return 0;
}