第1关:病毒复制
任务描述
本关任务:设计一个病毒类。
相关知识
本关涉及到的内容如下:
- 拷贝构造函数
- 重载
!=
和==
运算符
拷贝构造函数
当一个构造函数的唯一一个参数就是它所在类的引用时,这个构造函数就是一个拷贝构造函数,比如:
class Test
{
public:
Test();
Test(const Test& t); //拷贝构造函数,参数不带const也行
};
简单来说,拷贝构造函数的调用时机有三个:
- 用一个对象去初始化一个同类对象时,可以是通过构造函数传入给一个同类对象,还可以是使用赋值运算符
=
。 - 将一个对象作为参数传递给一个函数,而且形式参数不是这个类型的指针或引用。
- 将一个对象作为返回值返回,而且返回值类型不是这个类型的指针或引用。
比如:
/* 类声明同上 */
Test::Test(){}
Test::Test(const Test& t)
{
cout << "Copy" << endl;
}
Test Fun(Test t)
{
cout << "返回值" <<endl;
return t;
}
int main()
{
Test t;
cout << "直接调用" << endl;
Test t2(t);
cout << "初始化" << endl;
Test t3 = t;
cout << "函数调用" <<endl;
Fun(t);
cout << "赋值" << endl;
Test t4;
t4 = t;
}
得到的结果是:
直接调用
Copy
初始化
Copy
函数调用
Copy
返回值
Copy
赋值
注意赋值
之后没有Copy
的输出,因为t4
不是在初始化的时候使用赋值运算符的。
拷贝构造函数一般用于实现对象语义上的拷贝。
当一个对象的内部有动态分配的资源时,就要考虑是不是要提供一个拷贝构造函数以实现对象的深度拷贝了。
重载 != 和 == 运算符
运算符重载在之前的实训已经介绍过,这里就简单介绍一下这两个运算符。
!=
和==
都属于关系运算符,因此返回值都是布尔bool
类型,而且它们都是双操作数运算符,因此重载它们的方式就是:
class T
{
public:
int A;
};
bool operator==(const T &t1,const T &t2) //重载==运算符
{
return t1.A == t2.A;
}
bool operator!=(const T &t1,const T &t2) //重载!=运算符
{
return t1.A != t2.A;
}
int main()
{
T t1,t2;
t1.A = 10;
t2.A = 20;
cout << "t1==t2? " << (t1 == t2) << endl;
cout << "t1!=t2? " << (t1 != t2) << endl;
}
得到的结果是:
t1==t2? 0
t1!=t2? 1
编程要求
设计一个病毒Virus
类,它的内部有一个Gen
变量,代表当前病毒对象的年龄,默认值为0
。
当一个病毒对象通过拷贝构造函数拷贝到一个新的病毒对象时,它就将新对象的的Gen
变量设为它的Gen
值加1
的值。
评测代码会在Begin-End
区域创建一些病毒对象,把它们拷贝多份存在数组里,然后会将数组中的元素与一个int
整型数据g
进行g==virus[i]
形式的比较,以找出Gen
为g
的病毒,并进行 计数。
学员还需要添加适当的内容以支持声明对象数组和与一个整型变量进行比较的功能。
最后会将计数结果输出,这些输出将与规定的输出进行比较,详细见测试说明。
class Virus
{
/********* Begin *********/
public:
int Gen;
//病毒类的声明
Virus():Gen(0){}
Virus(const Virus&v);
Virus(int g)
{
Gen=Gen+1;
}
friend bool operator==(const int g,const Virus &t1)
{
return g==t1.Gen;
}
/********* End *********/
};
/********* Begin *********/
Virus::Virus(const Virus&v)
{
Gen=v.Gen+1;
}
//病毒类的定义以及其他内容
/********* End *********/
第2关:动态学生信息管理
任务描述
本关任务:编写一个能动态管理学生信息的程序。
相关知识
本关涉及的内容如下:
vector
容器的使用- 容器的迭代器
sort
函数的使用find
函数的使用
vector容器的使用
vector
位于头文件<vector>
,是C++
提供的一种顺序存储的容器,能通过位置索引高效的访问容器中的任意元素,可以看做是一个长度能动态变化的数组。
vector
类是一个模板类,使用时需要指定模板参数。注意:作为模板参数的类型要有无参数的构造函数(如果不使用Allocator
)。
它常用的构造函数如下:
int main()
{
vector<string> v; //无参数版
vector<string> v2(10); //初始容量为10的vector,每个元素为都为string的默认值
vector<string> v3(10,"abc"); //初始容量为10,且每一个元素都为 abc
}
如果要访问或者修改vector
的内容可以用如下的函数:
vector<int> v;
v.push_back(10); //将一个元素添加到容器的末尾
v.push_back(20);
cout << v[0] << endl; //访问索引为0的元素,即 10
v.erase(v.begin() + 1); //删除索引为1的元素,begin()函数的含义见下一节
v.insert(v.begin() + 1,30); //在索引为1的元素之前插入一个元素
v.clear(); //删除所有元素
同时,vector
还有如下的成员函数用于查询元素数量:
int size = v.size(); //返回元素数量
bool isem = v.empty(); //容器为空则返回true
vector
的成员函数不仅仅只有这些,感兴趣的同学可以查阅其他资料进行学习。
容器的迭代器
STL
中的容器都提供了 迭代器iterator
成员类型,它就像一个指针,使用它能按照一种 特定的顺序 遍历容器中的所有元素。
一般来说,当你对一个迭代器it
进行it++
自增操作时,就能进入到下一个元素,进行it--
自减操作时,就能回到上一个元素。有些迭代器还支持it+=2
,it-=2
这样跨多个元素的操作。而对它使用*it
取值操作,就能获得当前的元素值。
上一节的begin()
函数,返回的其实就是一个指向第一个元素的迭代器,与之对应的还有一个end()
函数,它返回一个指向最后一个元素之后不存在的元素的迭代器,有了这两个函数,我们就能访问vector
中的所有元素了:
vector<int> v(5,10); //容量为5,元素值都是10
for(vector<int>::iterator it = v.begin();it != v.end();it++)
cout << *it << " ";
得到的结果是:
10 10 10 10 10
此时我们也可以知道,vector
的erase
,insert
函数的参数实际上都是迭代器类型。
注意:在使用迭代器迭代一个容器的过程中,使用修改容器内容的函数可能会使得迭代器无效,因此尽量不要在这个过程中修改容器内容。
sort
函数的使用
sort
函数位于头文件<algorithm>
,它接受两个迭代器参数first
和last
,代表要排序的范围,为左闭右开,即[first,last)
,然后将这个范围里的元素按照升序排列。
它要求迭代器的元素类型重载了此类型对象间比较用的<
运算符。
注意:指针也可以看做是一种迭代器,所以对于数组也是可以使用这个函数的。
比如:
vector<int> v;
v.push_back(2);
v.push_back(1);
int vs[]={2,1};
sort(v.begin(),v.end()); //排序vector
sort(vs,vs + 2); //排序数组
两个容器内的元素都是:1 2
find 函数的使用
find
函数位于头文件<algorithm>
,它接受三个参数,前两个迭代器类型的first
,last
参数代表范围,左闭右开,第三个value
参数代表要查找的内容,因此元素类型必须重载了用于元素和value
之间比较的==
运算符。
它返回的也是一个迭代器,如果找到了这个元素,则返回指向这个元素的迭代器,如果没有,则返回last
参数。
比如:
vector<int> v;
v.push_back(2);
v.push_back(1);
int vs[]={2,1};
find(v.begin(),v.end(),3); //不存在3元素,所以会返回v.end()
find(vs,vs + 2,1); //返回指向数组中 1 元素的指针
注意:find
函数在比对元素的时候,==
运算符的第二个参数是要找的内容,即参数value
。
编程要求
学员需要设计一个学员信息表,并在主函数中读取输入,根据输入的内容作出相应的动作。 输入共有4
种,格式如下:
A <姓名> <分数>
,向信息表的末尾添加一条记录,内容为<姓名> <分数>
,分数均为整数。
注意:如果表中已有同一个人的记录,那就只更新分数。
R <姓名>
,删除表中指定姓名的条目,不存在则不做处理。P
,按照<姓名> <分数>
的格式打印整个信息表,每条记录占1
行。如果表为空,则输出一行[空]
。S
,将整个信息表的数据按照分数的降序排序。
注意 :为了保证排序结果稳定,输入的数据保证不会有两条记录有相同的分数。
每种格式的输入占一行,测试有多行输入。详细见测试说明。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
/********* Begin *********/
class Student
{
public:
string name;
int grade;
Student():name(" "),grade(0){}
Student(string n,int g):grade(g){name=n;}
Student(string n){name=n;}
Student(const Student& rhs)
{ name=rhs.name;
grade=rhs.grade;}
void Set()
{
cin>>name;
cin>>grade;
}
void Print();
friend bool operator==(const Student& a,const Student& b);
friend bool operator<(const Student& a,const Student& b);
};
void Student::Print()
{
cout<<name<<" "<<grade<<endl;
}
bool operator==(const Student& a,const Student& b)
{
return a.name==b.name;
}
bool operator<(const Student& a,const Student& b)
{
return a.grade>b.grade;
}
/********* End *********/
int main()
{
/********* Begin *********/
vector<Student>v;
string n[5];
Student st[5];
char c;
int i=0;
vector<Student>::iterator itr;
while(cin>>c)
{
if(c=='A')
{
Student s;
s.Set();
if(find(v.begin(),v.end(),s)==v.end())
v.push_back(s);
else
{
itr=find(v.begin(),v.end(),s);
v.erase(itr);
v.push_back(s);
}
}
else if(c=='R')
{
cin>>n[i];
st[i]=Student(n[i]);
itr=find(v.begin(),v.end(),st[i]);
if(itr!=v.end())
v.erase(itr);
i++;
}
else if(c=='P')
{
if(v.size()==0)
cout<<"[空]"<<endl;
else
{
for(int j=0;j<v.size();j++)
v[j].Print();
}
}
else if(c=='S')
{
sort(v.begin(),v.end());
}
}
/********* End *********/
}
第3关:还原键盘输入
任务描述
本关任务:编写一个能根据键盘操作记录还原实际输入的小程序。
相关知识
本关涉及到的内容如下:
list
容器的使用
list
容器的使用
list
位于头文件<list>
,是 STL
提供的一种顺序存储的容器,它不支持通过位置索引访问,但能高效的进行插入、删除元素操作,通常由双向链表实现。
list
是一个模板类,使用时需要指定模板参数,而且作为参数的类型需要有无参数的构造函数(如果不使用Allocator
)。
它常用的构造函数如下:
list<int> l(); //无参构造函数
list<int> l2(10); //包含10个默认值0元素
list<int> l3(10,123); //包含10个123元素
它的begin
,end
函数用于获取指向第一个元素的迭代器和指向最后一个元素之后的迭代器。
如果要修改容器内容,可以使用以下函数:
list<int> l;
l.push_back(1); //添加到链尾
l.push_front(2); //添加到链头
l.pop_back(); //删除链尾元素
l.pop_front(); //删除链头元素
l.insert(l.begin(),3); //在指定迭代器之前添加一个元素
l.erase(l.begin()); //删除指定迭代器所指的元素
list
的迭代器有一点特别,当对容器进行修改时,只要不是删除了这个迭代器对应的元素,这个迭代器都不会失效。 比如:
list<int> l;
list<int>::iterator it = l.begin();
l.insert(it,10); //执行之后it依然有效,并且现在it不再与l.begin()相等
l.erase(it-1); //执行之后it依然有效
l.erase(it); //现在it无效了
如果要查询list
的元素数量,可以使用以下函数:
int size = l.size(); //返回元素个数
bool isem = l.empty(); //容器为空时返回true
list
的成员函数不仅仅只有这些,感兴趣的同学可以查阅其他资料进行学习。
编程要求
学员需要读取输入的键盘操作记录,并根据操作记录还原最后的输入结果。
每一次的操作记录为一行字符串,字符串的内容分为以下几类:
- 大小写字母,数字:代表用户在输入光标左侧输入了一个具体字符,光标保持在新输入字符的
Begin-End
区域内。 <
,>
字符:代表用户按下了左键或者右键,作用是输入光标向左或者向右移动一个位置。[
,]
字符:代表用户按下了Home
键或者End
键,作用是移动输入光标到最开始或者最末尾的位置。
每一次操作记录开始的时候都是没有输入的。
注意:输入光标只能在已输入内容的两端和内部移动。
输入将会有多行,每一行代表一次操作记录,每次操作记录产生一行结果,具体见测试说明。
#include <iostream>
#include <string>
#include <list>
using namespace std;
int main()
{
/********* Begin *********/
string s;
int i;
list<char> l;
list<char>::iterator it=l.begin();
while(cin>>s)
{
i=0;
while(i<s.size()+1)
{
if(s[i]=='\0')
{
it=l.begin();
while(it!=l.end())
{
cout<<*it;
it++;
}
l.clear();
it=l.begin();
cout<<endl;
}
if(s[i]>='A'&&s[i]<='Z'||s[i]>='a'&&s[i]<='z'||s[i]>='0'&&s[i]<='9')
{
l.insert(it,s[i]);
}
else if(s[i]=='<')
{it--;}
else if(s[i]=='>')
{it++;}
else if(s[i]=='[')
{it=l.begin();}
else if(s[i]==']')
{it=l.end();}
i++;
}
}
//读取输入,解析并输出复原后的输出
/********* End *********/
}