第1关:复数运算
任务描述
相关知识
运算符重载
运算符重载之外部函数
运算符重载之成员函数
编程要求
测试说明
任务描述
本关任务:设计一个复数类并实现复数的三种运算。
相关知识
所谓重载,就是赋予新的含义。函数重载可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载也是一个道理,同一个运算符可以有不同的功能。本关我们就一起来学习运算符重载的使用。
运算符重载
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载。
重载运算符声明方式如普通成员函数一样,只不过他的名字包含关键字 operator,以及紧跟其后的一个 C++ 预定义的操作符。格式如下:
函数类型 operator 运算符名称 (形参表列)
{
// 对运算符的重载处理
}
例如:
class Test{ /* 类声明 */ };
Test operator+(Test t1,Test t2); // 重载 + 运算符,这个运算符要求两个操作数
并不是所有 C++ 中的运算符都可以支持重载,我们也不能创建一个新的运算符出来并且不能改变运算符操作对象的个数。有的运算符只能作为类成员函数被重载,而有的运算符则只能当作普通函数来使用:
不能被重载的运算符有:. 、.* 、:: 、?: 、sizeof
只能作为类成员函数重载的运算符有:() 、[] 、-> 、=
运算符重载有两种方式:
使用外部函数进行运算符重载;
使用成员函数进行运算符重载。
运算符重载之外部函数
要调用运算符重载函数,有两种方法,一种是通过函数名调用,即operator+(t1,t2),另一种是在使用运算符的时候自动调用,这里介绍第二种。
就如同函数重载的最佳匹配规则,使用一个运算符时,会寻找名为operator<运算符>,且与当前操作数最佳匹配的那个重载版本进行调用。
例如:
class Test{ /* 类声明 / };
class D : public Test {}; //创建一个 Test 类的子类
//外部函数
Test operator+(Test t1,Test t2){ / 一些操作 */ }
int main()
{
Test t1,t2;
Test t3 = t1 + t2; // 最佳匹配是 operator+(Test,Test)
D d1,d2;
D d3 = d1 + d2; // 最佳匹配也是 operator+(Test,Test)
}
至于运算符重载函数内部怎么实现(定义),那就可以根据需求来了,例如:
class Test
{
public:
int a;
};
Test operator+(Test &t1,Test &t2)
{
// 重载加法运算符,实际对 Test 类中的 a 成员变量进行加法运算
Test t;
t.a = t1.a + t2.a;
return t;
}
int main()
{
Test t1,t2;
t1.a = 10; t2.a = 20;
cout << (t1 + t2).a <<endl; // 调用 operator(Test&,Test&)的重载版本
}
输出结果为:30
注意:在运算符重载函数中也是要考虑类成员访问性的问题的。
运算符重载之成员函数
运算符重载的函数也可以写到某个类中成为一个成员函数,格式与写在外面没有区别,但在参数列表上有一些差异。
成为成员函数的运算符重载函数的参数需要少写一个最左边的参数,而少的这个参数就由当前的对象代替。
例如:
class Test
{
public:
int a;
Test operator+(Test& t2); // 少了左边的一个参数
};
Test Test::operator+(Test &t2)
{
Test t;
t.a = a + t2.a; // 当前对象代替了原本的第一个参数
return t;
}
int main()
{
Test t1,t2;
t1.a = 10; t2.a = 20;
cout << (t1 + t2).a << endl; // 调用的是 Test 类成员函数的那个重载版本
}
输出结果为:30
注意:作为成员函数的运算符重载函数也会受访问性影响。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计一个复数类( Complex ),该类有两个成员变量和两个函数(成员变量访问性为私有,函数为公有),并重载+,-,*运算符,实现复数的加、减、乘运算,具体要求如下:
成员变量:float real,代表实部。
成员变量:float image,代表虚部。
构造函数:Complex(float r,float i),用两个参数设置 real 和 image 成员变量的值。
输出复数函数:void Print(),输出格式为:实部 +/- 虚部i,比如1+1i,0−5i。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:1 1 2 2(数据分为两组,前两个和后两个数分别表示一个复数 c1 和 c2,即c1=1+1i,c2=2+2i)
预期输出:
c1 = 1+1i
c2 = 2+2i
c1 + c2 = 3+3i
c1 - c2 = -1-1i
c1 * c2 = 0+4i
测试输入:0 0 -1 0.5
预期输出:
c1 = 0+0i
c2 = -1+0.5i
c1 + c2 = -1+0.5i
c1 - c2 = 1-0.5i
c1 * c2 = -0+0i
开始你的任务吧,祝你成功!
#include <iostream>
using namespace std;
/********* Begin *********/
class Complex
{
//复数类的声明
private:
float real;
float image;
public:
Complex(float r,float i);//构造函数
Complex& operator += (const Complex&);
Complex operator + (Complex &c1);
Complex operator - (Complex &c1);//注:在重载的时候。多个是不能直接写出来的
Complex operator * (Complex &c1);//要用指针去定义,也就是不能(Complex &c1,complex &c2)
void Print();
Complex();
};
//复数类的定义
Complex::Complex()
{ }
Complex ::Complex(float r,float i){//构造函数初始化
real=r;
image=i;
}
Complex Complex::operator + (Complex &c1){
return Complex (real+ c1.real,image+c1.image);
}
Complex Complex::operator - (Complex &c1){
return Complex (real- c1.real,image-c1.image);
}
Complex Complex::operator * (Complex &c1){
Complex temp;
temp.real=real*c1.real-image*c1.image;
temp.image=image*c1.real+c1.image*real;
return temp;
}
void Complex::Print(){
if(Complex::image<0){
cout<<Complex::real<<Complex::image<<"i"<<endl;
}else
cout<<Complex::real<<"+"<<Complex::image<<"i"<<endl;
}
/********* End *********/
第2关:病毒复制
任务描述
本关任务:设计一个病毒类,实现病毒检测功能。
相关知识
为了完成本关任务你需要掌握如何重载!=和==运算符以及拷贝构造函数的使用。
重载 != 和 == 运算符
!=和==都属于关系运算符,因此返回值都是布尔类型( 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
重载==运算符时,t1 中成员 A 的值与 t2 中成员 A 的值并不相等,因此返回结果0,因此重载!=运算符时,返回结果为1。
拷贝构造函数
复制构造函数是构造函数的一种,也称拷贝构造函数,它只有一个参数,参数类型是本类的引用。它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数一般用于实现对象语义上的拷贝。当一个对象的内部有动态分配的资源时,就要考虑是不是要提供一个拷贝构造函数以实现对象的深度拷贝了。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
类名::类名(const 类名& 对象名)
{
函数体;
}
例如:
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 不是在初始化的时候使用赋值运算符的。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计病毒类( Virus ),实现病毒检测功能,具体要求如下:
成员变量:int Gen,代表当前病毒对象的年龄,默认值为 0。
拷贝构造函数:Virus(const Virus &v),拷贝到一个新的病毒对象时,并将新的病毒对象的成员变量年龄在原来病毒对象的年龄上加 1。
重载运算符:bool operator(const int& g,const Virus &v),用来比较g==virus[i],以找出年龄为参数 g 的病毒,并统计计数。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:0 0
预期输出:
年龄:0 数量:1
年龄:1 数量:2
年龄:2 数量:0
输出结果说明:平台会先创建一个病毒1对象,年龄为0,然后拷贝两个病毒1对象,因此这两个新的病毒的年龄就为1。
开始你的任务吧,祝你成功!
/********* Begin *********/
class Virus
{
//病毒类的声明
public:
int Gen;
Virus(const Virus &v);
Virus();
};
//病毒类的定义以及其他内容
Virus::Virus(){
}
bool operator == (const int &g,const Virus &v1){
if(v1.Gen == g){
return 1;
}else
return 0;
}
Virus:: Virus(const Virus &v){
this -> Gen=v.Gen+1;
}
/********* End *********/
nav
第3关:学生信息转换
任务描述
本关任务:设计学生和教师两个类,并实现它们之间的转换。
相关知识
为了完成本关任务,你需要掌握友元函数、友元类以及转换构造函数。
友元函数
有时候我们希望某个函数能访问一个类的非公有成员,但又不想把它做成这个类的成员函数,这个时候就可以使用友元。
如果要将一个函数变成一个类的友元,只需要在类中函数前加一个 friend 关键字来声明函数即可,并且访问性不受限制。即表现形式为:
friend <返回类型> <函数名> (<参数列表>);
但这个友元函数他不属于该类的成员函数,他是定义在类外的普通函数,只是在类中声明该函数可以直接访问类中的 private 或者 protected 成员。
例如:
class Test{
private:
int a;
protected:
int b;
public:
friend void fun(Test t); // 友元函数
};
void fun(Test t)
{
t.a = 100;
t.b = 2000;
cout << "a = " << t.a << ", b = " << t.b << endl;
}
int main()
{
Test test;
fun(test);
return 0;
}
输出结果为:a = 100, b = 2000
友元类
C++ 中也允许声明一个类为某个类的友元类,方法和声明友元函数旗鼓相当。但是需要注意的是,作为友元的类的声明必须在友元类的声明之前。
例如:
class TestFriend
{
private:
int a;
}; // TestFriend 类的声明在 Test 类之前
class Test
{
private:
int a;
friend class TestFriend; // 声明友元类
};
有时候作为友元的类的声明不便放在友元声明之前,这个时候就可以使用前置声明。不过前置声明的类只是一个名字(或者说不完全类型),不能访问它内部的内容。
例如:
class TestFriend; // 前置声明 TestFriend 类,只是一个名字
class Test
{
private:
int a;
friend class TestFriend; // 声明友元类
friend void TestFriend::Func(); // 尝试将 TestFriend 类的成员函数 Func 作为友元函数,但由于 TestFriend 类目前只有前置声明,所以会出错。
};
class TestFriend // TestFriend 类的声明在 Test 类之后
{
private:
int a;
public:
void Func();
};
最后,友元声明还有如下限制:
友元关系不能被继承。
友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元。
友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元。
转换构造函数
一个构造函数接收一个不同于其类类型的形参,可以视为将其形参转换成类的一个对象,像这样的构造函数称为转换构造函数。因此转换构造函数的作用就是将一个其他类型的数据转换成一个类的对象。
除了创建类对象之外,转换构造函数还为编译器提供了执行隐式类型转换的方法。只要在需要类的类型值的地方,给定构造函数的形参类型的值,就将由编译器执行这种类型的转换。
转换构造函数是构造函数的一个特例,当一个构造函数的参数只有一个,而且是一个其他类型的 const 引用时,它就是一个转换构造函数。
例如:
class T1{};
class T2
{
public:
T2(const T1 &t); // 从 T1 转换到 T2 的转换构造函数
};
有了转换构造函数,就可以实现不同类型之间的类型转换了,比如:
/* 类定义同上 */
int main()
{
T1 t1;
T2 t2= (T2)t1; // 用类型转换语法,从 T1 转换到 T2
T2 t3(t1); // 或者直接调用转换构造函数
}
注意:转换构造函数只能有一个参数。如果有多个参数,就不是转换构造函数。
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计学生和教师两个类,类中成员变量都为私有,成员函数都为公有,并实现它们之间的转换,具体要求如下:
学生类( Student )
编号:int number
姓名:string name
性别:string sex
带参构造函数:Student(int num,string nam,string se),用三个参数对应初始化内部的三个成员变量。
输出函数:void Print()函数,输出学生信息,格式为:学生:name,编号:number,性别:sex。
教师类( Teacher )
与 Student 类有三个同样的成员变量
输出函数:void Print(),输出教师信息,格式为:教师:name,编号:number,性别:sex的格式打印三个成员变量。
转换构造函数,用于从 Student 类转换到 Teacher 类,它将 Student 对象的成员变量对应复制 Teacher 对象的成员变量中。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
测试输入:1 王大明 男
预期输出:
学生:王大明,编号:1,性别:男
教师:王大明,编号:1,性别:男
测试输入:2 雷珺 女
预期输出:
学生:雷珺,编号:2,性别:女
教师:雷珺,编号:2,性别:女
开始你的任务吧,祝你成功!
#include <iostream>
#include <string>
using namespace std;
/********* Begin *********/
// 前置声明 Teacher 类
class Teacher;
class Student
{
//学生类的声明
private:
int number;
string name;
string sex;
public:
Student(int num,string nam,string se);
int getnum(){
return number;
}
string getnam(){
return name;
}
string getse(){
return sex;
}
void Print();
};
//学生类的定义
Student::Student(int num,string nam,string se){
number=num;
name=nam;
sex=se;
}
void Student::Print(){
cout<<"学生:"<<name<<",编号:"<<number<<",性别:"<<sex<<endl;
}
class Teacher
{
//教师类的声明
private:
int number;
string name;
string sex;
public:
Teacher();
Teacher(Student &);
Teacher(int num,string nam,string se);
void Print();
};
//教师类的定义
Teacher::Teacher(int num,string nam,string se){
number=num;
name=nam;
sex=se;
}
Teacher::Teacher(Student &stu){
number=stu.getnum();//以对象的形式进行函数的调用
name=stu.getnam();
sex=stu.getse();
}
void Teacher::Print(){
cout<<"教师:"<<name<<",编号:"<<number<<",性别:"<<sex<<endl;
}
/********* End *********/
第4关:矩阵运算
任务描述
本关任务:设计一个矩阵类,并实现简单的矩阵运算。
相关知识
完成本关需要具备的知识介绍请参见第一、二关卡。
矩阵运算参考资料
编程要求
在右侧编辑器中的Begin-End之间补充代码,设计一个矩阵类( Matrix ),并实现矩阵的简单运算,具体要求如下:
成员变量:这一部分学员可以自由发挥,但要求都是私有成员。
成员函数:
构造函数:Matrix(int r,int c),参数 r 和 c 分别代表矩阵的行和列。
全部设值函数:void Fill(int value),函数将矩阵内所有的元素都设置为参数 value 的值。
指定位置设值函数:void Set(int r,int c,int value),函数将矩阵第 r 行 c 列的元素设置为 value 的值。
获取元素函数:int Get(int r,int c)函数,函数返回矩阵第 r 行 c 列的元素。
打印函数:void Print(),函数按照矩阵的形状打印出矩阵内容,每一个值后跟着一个空格。比如一个2x4元素全为1的矩阵,打印结果为(更明显表示格式,空格均用下划线_代替):
1_1_1_1_
1_1_1_1_
普通函数:
Matrix operator+(Matrix &m1,Matrix &m2)函数,重载Matrix类的加法运算符,实现矩阵的加法运算。
Matrix operator-(Matrix &m1,Matrix &m2)函数,重载Matrix类的减法运算符,实现矩阵的减法运算。
Matrix operator*(Matrix &m1,Matrix &m2)函数,重载Matrix类的乘法运算符,实现矩阵的乘法运算。
测试说明
平台会对你编写的代码进行测试,比对你输出的数值与实际正确数值,只有所有数据全部计算正确才能通过测试:
注意:对于输入数据的具体测试,请查看右侧 step3/run.cpp 文件
测试输入:1 1
预期输出:
m1 + m2 :
3
m1 - m2 :
-1
m1 * m3 :
1
测试输入:2 2
预期输出:
m1 + m2 :
3 3
3 3
m1 - m2 :
-1 -1
-1 -1
m1 * m3 :
1 2
1 2
开始你的任务吧,祝你成功!
#include <iostream>
#include <string>
using namespace std;
/********* Begin *********/
class Matrix
{
//矩阵类的声明
private:
int hight;//长
int width;//宽
int juzhen[10][10];//定义一个二元数组来存矩阵元素
public:
Matrix();
Matrix(int r,int c);//r:行,c:列
void Fill(int value);//函数将矩阵内所有的元素都设置为参数 value 的值。
void Set(int r,int c,int value);//函数将矩阵第 r 行 c 列的元素设置为 value 的值。
int Get(int r,int c);//函数返回矩阵第 r 行 c 列的元素。
void Print();
friend Matrix operator+(Matrix& m1,Matrix &m2);
friend Matrix operator-(Matrix& m1,Matrix &m2);
friend Matrix operator*(Matrix& m1,Matrix &m2);
};
//矩阵类的定义
Matrix::Matrix(){
}
Matrix::Matrix(int r,int c){
hight=r;
width=c;
}
void Matrix:: Fill(int value){
for(int i =0;i<hight;i++){
for(int j =0;j<width;j++){
this -> juzhen[i][j]=value;//下面还有使用数组的地方,所有用this关键字去赋值
}
}
}
void Matrix:: Set(int r,int c,int value){
this->juzhen[r][c]=value;
}
int Matrix:: Get(int r,int c){
return this->juzhen[r-1][c-1];
}
void Matrix:: Print(){//矩阵的输出
for(int i =0;i<hight;i++){
for(int j =0;j<width;j++){
cout<< this->juzhen[i][j] <<" ";//注意输出格式
}
cout<<endl;
}
}
Matrix operator+(Matrix& m1,Matrix &m2)
{
//实现矩阵加法
Matrix temp(m1.hight,m2.width);//定义一个temp数组去存矩阵加法后的和值
for(int i =0;i<m1.hight;i++){
for(int j =0;j<m1.width;j++){//j<m1的长度是因为,加只加那一个值,而不是整列
temp.juzhen[i][j]=m1.juzhen[i][j]+m2.juzhen[i][j];
}
}
return temp;
}
Matrix operator-(Matrix& m1,Matrix &m2)
{
//实现矩阵减法
Matrix temp(m1.hight,m2.width);//定义一个temp数组去存矩阵加法后的和值
for(int i =0;i<m1.hight;i++){
for(int j =0;j<m1.width;j++){
temp.juzhen[i][j]=m1.juzhen[i][j]-m2.juzhen[i][j];
}
}
return temp;
}
Matrix operator*(Matrix& m1,Matrix &m2)
{
//实现矩阵乘法
Matrix temp(m1.hight,m2.width);//定义一个temp数组去存矩阵加法后的和值
for (int i = 0; i < m1.hight; i++) {
for (int j = 0; j < m2.width; j++) {
long sum=0;
for (int k = 0; k < m1.width; k++) {
sum += m1.juzhen[i][k] * m2.juzhen[k][j]; //累加第i行与第j列的元素乘积
}
temp.juzhen[i][j] = sum; //赋给i,j元素
}
}
return temp;
}
/********* End *********/