工程9-1 创建集合类
运算符重载使得我们可以创建与C++编程环境完全兼容的类。例如, 通过定义必要的运算符函数,我们可以像使用内置数据类型那样使用类。我们可以对该类的对象使用运算符,还可以在表达式中使用该类的对象。下面我们通过创建一个名为Set的类,定义Set类型来演示如何创建与C++编程环境想兼容的类。
在开始之前,我们必须先明确这里说的Set的具体含义。在这个工程中,Set(集合)被定义是一系列互不相同的元素的集合。也就是说,在任何一个给定的Set(集合)中没有两个相同的元素。集合中元素的顺序是无关紧要的。因此集合{A,B,C}和{A,C,B}是同一个集合。集合还可以为空。
集合支持很多的操作。本工程中实现了集合的下列操作:
l 为集合增加元素
l 从集合中删除一个元素
l 集合的并集
l 集合的差集
其中,增加元素到集合中和从集合中删除元素操作的含义是很明显的。这里需要对并集和差集进行解释。两个集合的并集就是含有这两个集合全部元素的集合。(当然,其中不含有重复的元素)。我们将使用+运算来完成并集的运算。
集合的差集是仅含有第一个集合中那些不属于第二个集合的元素构成的集合。我们将使用-运算来实现集合的差集。例如,有两个集合S1和S2, 把S2从S1中删除后的集合构成S3的语句如下:
S3 = S1 - S2;
如果S1和S2中的元素是完全相同的,则S3集合为空的集合。类Set中还有一个成员函数为isMember(),用来判断一个元素是否属于该集合。当然,还有一些别的和集合相关的运算。其中一些会在练习题中找到,其余的我们可以尝试自己编写。
为了简单起见,Set类存储的是字符。但是用于存储其它类型元素的类在实现原理上都是相同的。
步骤:
1. 创建一个新文件,名称为Set.cpp.
2. 通过声明Set类,如下:
const int MaxSize = 100;
class Set
{
int len; //元素的数量
char members[MaxSize]; //采用数组来存储集合中的元素
/* find()函数是私有的,因为在Set类之外是不使用的*/
int find(char ch); //查找一个元素
public:
//构造一个空的集合
Set()
{
len = 0;
};
//返回集合中元素的数量
int getLength()
{
return len;
}
void showset(); // 显示出集合中的元素
bool isMember( char ch ); // 检查是否是集合中的元素
Set operator +(char ch); // 为集合增加元素
Set operator -(char ch); // 删除一个元素
Set operator +(Set ob2); //求集合的并集
Set operator -(Set ob2); //求集合的差集
};
集合中采用数组members来存储元素。集合中元素的数量保存在len变量中。集合中最多可以含有MaxSize个元素,也就是100个元素。(如果需要处理更大的集合,可以自行修改这个值。)
Set类的构造函数生成一个空的集合,即不含有任何元素。我们没有必要再创建别的构造函数或者显示地定义其拷贝构造函数了,因为缺省的逐位拷贝的操作是满足我们要求的。函数getLength()返回len的值,也就是集合中实际的元素的数量。
3. 从私有函数find()开始定义其成员函数,如下:
/* 返回指定元素ch的索引;
如果没有找到,则返回-1 */
int Set::find(char ch)
{
int i;
for ( i = 0; i < len ; i++)
{
if ( ch == members[i] )
{
return i;
}
}
return -1;
}
上面的函数用来判断传入的字符ch是否是集合中的元素。如果在集合中找到了该元素,函数返回其索引;否则返回-1。我们在类Set范围之外不使用这个函数,因此它是私有的。正如我们在前面讲过的那样,成员函数可以是仅供该类使用的私有函数。私有函数只能由该类中的其他成员函数来调用。
4. 为类增加showset()函数,如下:
//显示集合中的元素
void Set::showset()
{
cout << " ( ";
for( int i = 0; i < len ; i++)
{
cout << members[i] << " ";
}
cout << ")";
}
这个函数显示出集合的内容。
5. 为类增加isMember()函数,用来判断一个字符是否在集合中。如下:
/* 如果指定的字符时集合中的元素则返回true;
否则返回false */
bool Set::isMember(char ch)
{
if ( find(ch) != -1 )
{
return true;
}
else
{
return false;
}
}
这个函数中调用了find()来判断ch是否是集合中的元素。如果是,isMember()函数返回true;否则返回false。
6. 下面开始为Set类增加运算符。从加法运算符开始。对Set类的+运算符进行重载,实现往集合中增加一个元素,如下。
//为集合中增加一个元素
Set Set::operator +(char ch)
{
Set newset;
if ( len == MaxSize )
{
cout << "Set is full . \n";
return *this;
}
newset = *this;//复制当前的集合
//检查ch是否已经在集合中了
if(find(ch) == -1 )
{
//如果不在集合中,则增加该元素到集合中
newset.members[newset.len] = ch;
newset.len++;
}
return newset; //返回数据更新后的集合
}
这个函数我们需要仔细研究一下。首先,生成了一个新的集合。这个集合将用来存储原集合中的元素再加上元素ch。在把ch增加到集合中之前,先检查是否有足够的空间来存储增加的字符。如果有足够的空间可用于存储新增的字符,则把原始的集合赋值给新的集合。接着调用函数find()来判断ch是否已经在集合中了。如果不在,则把ch增加到新的集合中,并让len自增。在各种情况下,函数都是返回newset这个新的集合。因此,原始的集合在该函数中没有被修改,保持不变。
7. 重载-运算,以实现从集合中删除一个元素,如下:
// 从集合中删除一个元素
Set Set::operator -(char ch)
{
Set newset;
int i = find(ch); //如果ch不在集合中,则i为-1
//把其它的元素复制到新的集合中
for ( int j = 0; j < len ; j ++)
{
if ( j != i )
{
newset = newset + members[j];
}
}
return newset;
}
函数一开始创建了一个空的集合。然后调用find()函数来取得ch在原始集合中的索引。find()函数在ch不是集合中元素的时候返回-1。接着,通过循环把原始集合中除了索引等于find()返回值之外的所有元素都加入到新的集合中。这样,新的集合中就含有原始集合中除了ch以外的全部元素了。如果ch不是原始集合中的元素,新的集合和原始集合是相等的。
8. 重载+和-,来求集合的并集和差集,如下:
//求并集
Set Set::operator +(Set ob2)
{
Set newset = * this; //拷贝第一个集合
//把第二个集合中不属于第一个集合的元素拷贝到新的集合中
for ( int i = 0; i < ob2.len; i++)
{
newset = newset + ob2.members[i];
}
return newset; //返回并集
}
//求差集
Set Set::operator -(Set ob2)
{
Set newset = * this; //拷贝第一个集合
//从中减去第二个集合中的元素
for ( int i = 0; i < ob2.len; i++)
{
newset = newset - ob2.members[i];
}
return newset; //返回差集
}
我们可以看到,在这两个函数中我们用到了前面定义的+和-运算来辅助完成求并集和差集的操作。就求并集的操作来说,先生成了一个含有第一个集合所有元素的新集合。然后把第二个集合中的元素增加到了新的集合中。由于+运算符只会把不在集合中的元素加入到集合中,因此得到的新的集合就是不含有重复元素的两个集合的并集。Set类的求差集运算符则是把第二个集合中的元素从第一个中删除掉。
9. 下面是Set了以及main()函数构成的演示Set类使用方法的完整程序:
/* 构成-1
元素为字符的集合类
*/
#include <iostream>
using namespace std;
const int MaxSize = 100;
class Set
{
int len; //元素的数量
char members[MaxSize]; //采用数组来存储集合中的元素
/* find()函数是私有的,因为在Set类之外是不使用的*/
int find(char ch); //查找一个元素
public:
//构造一个空的集合
Set()
{
len = 0;
};
//返回集合中元素的数量
int getLength()
{
return len;
}
void showset(); // 显示出集合中的元素
bool isMember( char ch ); // 检查是否是集合中的元素
Set operator +(char ch); // 为集合增加元素
Set operator -(char ch); // 删除一个元素
Set operator +(Set ob2); //求集合的并集
Set operator -(Set ob2); //求集合的差集
};
/* 返回指定元素ch的索引;
如果没有找到,则返回-1 */
int Set::find(char ch)
{
int i;
for ( i = 0; i < len ; i++)
{
if ( ch == members[i] )
{
return i;
}
}
return -1;
}
//显示集合中的元素
void Set::showset()
{
cout << " ( ";
for( int i = 0; i < len ; i++)
{
cout << members[i] << " ";
}
cout << ")";
}
/* 如果指定的字符时集合中的元素则返回true;
否则返回false */
bool Set::isMember(char ch)
{
if ( find(ch) != -1 )
{
return true;
}
else
{
return false;
}
}
//为集合中增加一个元素
Set Set::operator +(char ch)
{
Set newset;
if ( len == MaxSize )
{
cout << "Set is full . \n";
return *this;
}
newset = *this;//复制当前的集合
//检查ch是否已经在集合中了
if(find(ch) == -1 )
{
//如果不在集合中,则增加该元素到集合中
newset.members[newset.len] = ch;
newset.len++;
}
return newset; //返回数据更新后的集合
}
// 从集合中删除一个元素
Set Set::operator -(char ch)
{
Set newset;
int i = find(ch); //如果ch不在集合中,则i为-1
//把其它的元素复制到新的集合中
for ( int j = 0; j < len ; j ++)
{
if ( j != i )
{
newset = newset + members[j];
}
}
return newset;
}
//求并集
Set Set::operator +(Set ob2)
{
Set newset = * this; //拷贝第一个集合
//把第二个集合中不属于第一个集合的元素拷贝到新的集合中
for ( int i = 0; i < ob2.len; i++)
{
newset = newset + ob2.members[i];
}
return newset; //返回并集
}
//求差集
Set Set::operator -(Set ob2)
{
Set newset = * this; //拷贝第一个集合
//从中减去第二个集合中的元素
for ( int i = 0; i < ob2.len; i++)
{
newset = newset - ob2.members[i];
}
return newset; //返回差集
}
//展示集合类Set的使用
int main()
{
//构建空的集合
Set s1;
Set s2;
Set s3;
s1 = s1 + 'A';
s1 = s1 + 'B';
s1 = s1 + 'C';
cout << "s1 after adding A B C: ";
s1.showset();
cout << "/n/n";
cout << "Testing for membership using isMember().\n";
if ( s1.isMember('B'))
{
cout << "B is a member of s1.\n";
}
else
{
cout << "B is not a member of s1.\n";
}
if ( s1.isMember('T'))
{
cout << "T is a member of s1.\n";
}
else
{
cout << "T is not a member of s1.\n";
}
cout << "\n";
s1 = s1 - 'B';
cout << "s1 after s1 = s1 - 'B' ";
s1.showset();
cout << "\n";
s1 = s1 - 'A';
cout << "s1 after s1 = s1 - 'A' ";
s1.showset();
cout << "\n";
s1 = s1 - 'C';
cout << "s1 after s1 = s1 - 'C' ";
s1.showset();
cout << "\n\n";
s1 = s1 + 'A';
s1 = s1 + 'B';
s1 = s1 + 'C';
cout << "s1 after adding A B C: ";
s1.showset();
cout << "\n\n";
s2 = s2 + 'A';
s2 = s2 + 'X';
s2 = s2 + 'W';
cout << "s2 after adding A X W: ";
s2.showset();
cout << "\n\n";
s3 = s1 + s2;
cout << "s3 after s3 = s1 + s2: ";
s3.showset();
cout << "\n\n";
s3 = s3 - s1;
cout << "s3 after s3 - s1 : ";
s3.showset();
cout << "\n\n";
s2 = s2 - s2; /*清空s2 */
cout << "s2 after s2 = s2 - s2: ";
s2.showset();
cout << "\n\n";
s2 = s2 + 'C';
s2 = s2 + 'B';
s2 = s2 + 'A';
cout << "s2 after adding C B A: ";
s2.showset();
return 0;
}
上面程序的输出结果如下:
s1 after adding A B C: ( A B C )
Testing for membership using isMember().
B is a member of s1.
T is not a member of s1.
s1 after s1 = s1 - 'B' ( A C )
s1 after s1 = s1 - 'A' ( C )
s1 after s1 = s1 - 'C' ( )
s1 after adding A B C: ( A B C )
s2 after adding A X W: ( A X W )
s3 after s3 = s1 + s2: ( A B C X W )
s3 after s3 - s1 : ( X W )
s2 after s2 = s2 - s2: ( )
s2 after adding C B A: ( C B A )
练习
1. 什么是拷贝构造函数?它在什么时候被调用?写出拷贝构造函数的通用形式。
2. 请解释一下当函数返回一个对象的时候发生了什么?特别解释析构函数是在什么时候被调用的。
3. 有如下代码段:
class T
{
int i,j;
public:
int sum()
{
return i + j;
}
}
请使用this指针重写上面的代码。
4. 什么是结构体? 什么是联合体?
5. 在类的成员函数中, *this指的什么?
6. 什么是友元函数?
7. 写出重载二目运算符函数的通用形式。
8. 怎样做才能实现涉及类类型和内置类型的运算?
9. ?运算符可否被重载?运算符的优先级别是否可以被改变?
10. 就工程9-1中的Set类,定义< 和 >运算用来判断一个集合是其子集或者是超集。其中,< 运算符在其左侧的集合是其右侧集合的子集的时候返回true;否则返回false。> 运算在其左侧集合是其右侧集合的超级的时候返回true;否则返回false。
11. 为Set类定义&运算,用于计算两个集合的交集。
12. 就工程9-1中的Set类,为其增加其它的运算。比如,增加|运算,用于计算两个集合的对称差分。对称差分是由两个集合中不属于他们交集的那些元素构成的集合。