实验2
一、实验任务1
源代码T.h
1 #pragma once 2 #include<string> 3 class T{ 4 public: 5 T(int x=0,int y=0); 6 T(const T &t); 7 T(T &&t); 8 ~T(); 9 void adjust(int ratio); 10 void display() const; 11 private: 12 int m1,m2; 13 public: 14 static int get_cnt(); 15 public: 16 static const std::string doc; 17 static const int max_cnt; 18 private: 19 static int cnt; 20 friend void func(); 21 }; 22 void func();
源代码T.cpp
1 #include"T.h" 2 #include<iostream> 3 #include<string> 4 const std::string T::doc{"a simple class sample"}; 5 const int T::max_cnt=999; 6 int T::cnt=0; 7 int T::get_cnt(){ 8 return cnt; 9 } 10 T::T(int x,int y):m1{x},m2{y}{ 11 ++cnt; 12 std::cout<<"T constructor called.\n"; 13 } 14 T::T(const T &t):m1{t.m1},m2{t.m2}{ 15 ++cnt; 16 std::cout<<"T copy constructor called.\n"; 17 } 18 T::T(T &&t):m1{t.m1},m2{t.m2}{ 19 ++cnt; 20 std::cout<<"T move constructor called.\n"; 21 } 22 T::~T(){ 23 --cnt; 24 std::cout<<"T destructor called.\n"; 25 } 26 void T::adjust(int ratio){ 27 m1*=ratio; 28 m2*=ratio; 29 } 30 void T::display()const{ 31 std::cout<<"("<<m1<<","<<m2<<")"; 32 } 33 void func(){ 34 T t5(42); 35 t5.m2=2049; 36 std::cout<<"t5="; 37 t5.display(); 38 std::cout<<'\n'; 39 std::cout<<"func:T object'current count:"<<T::get_cnt()<<std::endl; 40 }
源代码task1.cpp
1 #include"T.h" 2 #include<iostream> 3 void test_T(); 4 int main(){ 5 std::cout<<"test Class T:\n"; 6 test_T(); 7 std::cout<<"\ntest friend func:\n"; 8 func(); 9 } 10 void test_T(){ 11 using std::cout; 12 using std::endl; 13 cout<<"T info:"<<T::doc<<endl; 14 cout<<"T objects'max count:"<<T::max_cnt<<endl; 15 cout<<"T objects'current count:"<<T::get_cnt()<<endl<<endl; 16 T t1; 17 cout<<"t1="; 18 t1.display(); 19 cout<<endl; 20 21 T t2(3,4); 22 cout<<"t2="; 23 t2.display(); 24 cout<<endl; 25 26 T t3(t2); 27 t3.adjust(2); 28 cout<<"t3="; 29 t3.display(); 30 cout<<endl; 31 32 T t4(std::move(t2)); 33 cout<<"t4="; 34 t4.display(); 35 cout<<endl; 36 cout<<"test:T objects'current count:"<<T::get_cnt()<<endl; 37 }
运行结果截图

ps:按任务一中的代码要想得出与要求一致的运行代码,要在老师给的源代码T.cpp中对func函数的定义中加上std::cout<<"func:T object'current count:"<<T::get_cnt()<<std::endl;,不然在运行结果中就会缺少func:T object'current count:1,无法获得当前T类对象数目。
问题1:T.h中,在类T内部,已声明func是T的友元函数。在类外部,去掉line36,重新编译,程序能否正常运行。如果能,回答YES;如果不能,以截图形式提供编译报错信息,说明原因。
答:①在Dev-C++中,程序能正常运行不报错,因为它默认使用比较宽松的编译模式,在T.cpp中定义过了,在task1.cpp虽然没有声明但可以在链接时找到。②在vs2022中会报错,因为friend func()只是友元函数声明,不是真正的函数声明,所以编译task1.cpp时编译器找不到func()。

问题2:T.h中,line9-12给出了各种构造函数、析构函数。总结它们各自的功能、调用时机。
答:T(int x=0,int y=0)是普通构造函数,在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使x和y的默认值都为0,在创建一个新对象时调用;T(const T &t)是复制构造函数, 通过使用一个已经存在的对象,去初始化一个同类的新对象,在用已有对象初始化新对象时调用;T(T &&t)是移动构造函数,类似复制构造函数,通过右值引用的设定,移动实参来构建新对象,在用右值初始化新对象时调用;~T()是析构函数,完成对象被删除前的一些清理工作,在对象生存结束时被调用。
问题3:T.cpp中,line8-10,剪切到T.h的末尾,重新编译,程序能否正确编译。如不能,以截图形式给出报错信息,分析原因。
答:①与问题一相同,在Dev-C++中,程序能正常运行不报错。②在vs2022中会报错,tak1.cpp和T.cpp两个包含源文件T.h,都会定义这些静态成员,链接时会出现重复定义的情况。

二、实验任务2
源代码Complex.h
1 #pragma once 2 #include<string> 3 #include<iostream> 4 class Complex{ 5 public: 6 Complex(double real=0,double imag=0):real(real),imag(imag){}; 7 Complex(const Complex &c):real(c.real),imag(c.imag){}; 8 ~Complex(){}; 9 double get_real()const{ 10 return real; 11 } 12 double get_imag()const{ 13 return imag; 14 } 15 void add(const Complex &p){ 16 real+=p.real; 17 imag+=p.imag; 18 } 19 private: 20 double real,imag; 21 public: 22 static const std::string doc; 23 private: 24 friend void output(const Complex &c); 25 friend double abs(const Complex &c); 26 friend Complex add(const Complex &a,const Complex &b); 27 friend bool is_equal(const Complex &a,const Complex &b); 28 friend bool is_not_equal(const Complex &a,const Complex &b); 29 }; 30 void output(const Complex &c); 31 double abs(const Complex &c); 32 Complex add(const Complex &a,const Complex &b); 33 bool is_equal(const Complex &a,const Complex &b); 34 bool is_not_equal(const Complex &a,const Complex &b);
源代码Complex.cpp
1 #include"Complex.h" 2 #include<iostream> 3 #include<string> 4 #include<cmath> 5 #include<iomanip> 6 const std::string Complex::doc="a simplified complex class"; 7 void output(const Complex &c){ 8 if(c.imag>=0) 9 std::cout<<c.real<<"+"<<c.imag<<"i"<<std::endl; 10 else 11 std::cout<<c.real<<c.imag<<"i"<<std::endl; 12 } 13 double abs(const Complex &c){ 14 return std::sqrt(c.real*c.real+c.imag*c.imag); 15 } 16 Complex add(const Complex &a,const Complex &b){ 17 return Complex(a.real+b.real,a.imag+b.imag); 18 } 19 bool is_equal(const Complex &a,const Complex &b){ 20 if(a.real==b.real&&a.imag==b.imag) 21 return true; 22 else 23 return false; 24 } 25 bool is_not_equal(const Complex &a,const Complex &b){ 26 if(a.real==b.real&&a.imag==b.imag) 27 return false; 28 else 29 return true; 30 }
源代码task2.cpp
1 #include"Complex.h" 2 #include<iostream> 3 #include<iomanip> 4 #include<complex> 5 void test_complex(); 6 void test_std_complex(); 7 int main(){ 8 std::cout<<"*******测试1:自定义类complex*******\n"; 9 test_complex(); 10 std::cout<<"\n*******测试2:标准库模版类complex*******\n"; 11 test_std_complex(); 12 } 13 void test_complex(){ 14 using std::cout; 15 using std::endl; 16 using std::boolalpha; 17 cout<<"类成员测试:"<<endl; 18 cout<<Complex::doc<<endl<<endl; 19 cout<<"Complex对象测试:"<<endl; 20 Complex c1; 21 Complex c2(3,-4); 22 Complex c3(c2); 23 Complex c4=c2; 24 const Complex c5(3.5); 25 cout<<"c1="; 26 output(c1); 27 cout<<endl; 28 cout<<"c2="; 29 output(c2); 30 cout<<endl; 31 cout<<"c3="; 32 output(c3); 33 cout<<endl; 34 cout<<"c4="; 35 output(c4); 36 cout<<endl; 37 cout<<"c5.real="<<c5.get_real() 38 <<",c5.imag="<<c5.get_imag()<<endl<<endl; 39 cout<<"复数运算测试:"<<endl; 40 cout<<"abs(c2)="<<abs(c2)<<endl; 41 c1.add(c2); 42 cout<<"c1+=c2,c1="; 43 output(c1); 44 cout<<endl; 45 cout<<boolalpha; 46 cout<<"c1==c2:"<<is_equal(c1,c2)<<endl; 47 cout<<"c1!=c2:"<<is_not_equal(c1,c2)<<endl; 48 c4=add(c2,c3); 49 cout<<"c4=c2+c3,c4="; 50 output(c4); 51 cout<<endl; 52 } 53 void test_std_complex(){ 54 using std::cout; 55 using std::endl; 56 using std::boolalpha; 57 cout<<"std::complex<double>对象测试:"<<endl; 58 std::complex<double> c1; 59 std::complex<double> c2(3,-4); 60 std::complex<double> c3(c2); 61 std::complex<double> c4=c2; 62 const std::complex<double> c5(3.5); 63 cout<<"c1="<<c1<<endl; 64 cout<<"c2="<<c2<<endl; 65 cout<<"c3="<<c3<<endl; 66 cout<<"c4="<<c4<<endl; 67 cout<<"c5.real="<<c5.real() 68 <<",c5.imag="<<c5.imag()<<endl<<endl; 69 cout<<"复数运算测试:"<<endl; 70 cout<<"abs(c2)="<<abs(c2)<<endl; 71 c1+=c2; 72 cout<<"c1+=c2,c1="<<c1<<endl; 73 cout<<boolalpha; 74 cout<<"c1==c2:"<<(c1==c2)<<endl; 75 cout<<"c1!=c2:"<<(c1!=c2)<<endl; 76 c4=c2+c3; 77 cout<<"c4=c2+c3,c4="<<c4<<endl; 78 }
运行结果截图


问题1:比较自定义类Complex和标准库模板类complex的用法,在使用形式上,哪一种更简洁?函数和运算内在有关联吗?
答:我觉得使用标准库模板类complex更简洁,因为标准库模板类可以直接通过“+”“-”“=”等运算符进行计算,看起来更直观,类似数学表达式。函数和运算内在在逻辑上相关联,c1+=c2表示c1在自身基础上加上c2,c4=c2+c3表示c4是c2和c3的和,虽然具体表示的是实部实部相加,虚部虚部相加,但还是以加法运算为基础。
问题2:2-1自定义Complex中,output/abs/add/等均设为友元,它们真的需要访问私有数据吗?
答:是。因为这些函数都需要用到实部real和虚部imag的数据进行操作,real和imag是private类型的。
2-2:标准库std::complex是否把abs设为友元?
答::否。标准库std::complex中的abs是通过公共接口real()和imag()来访问的,不是设为友元来直接访问私有数据。

2-3:什么时候才考虑使用friend?总结你的思考。
答:当外部函数需要直接访问类的内部数据,并且需要多次访问时,用友元。其他情况使用公共接口访问可以保护封装性。
问题3:如果构造对象时禁用=形式,即遇到complex c4=c2;编译报错,类Complex的设计应如何调整。
可以在复制构造函数Complex(const Complex &c)后加=delete,让其无法被调用。
三、实验任务3
源代码PlayerControl.h
1 #pragma once 2 #include<string> 3 enum class ControlType{Play,Pause,Next,Prev,Stop,Unknown}; 4 class PlayerControl{ 5 public: 6 PlayerControl(); 7 ControlType parse(const std::string& control_str); 8 void execute(ControlType cmd)const; 9 static int get_cnt(); 10 private: 11 static int total_cnt; 12 };
源代码PlayerControl.cpp
1 #include"PlayerControl.h" 2 #include<iostream> 3 #include<algorithm> 4 int PlayerControl::total_cnt=0; 5 PlayerControl::PlayerControl(){} 6 ControlType PlayerControl::parse(const std::string& control_str){ 7 std::string s1; 8 for(auto c:control_str) 9 s1+=std::tolower(c); 10 if(s1=="play"){ 11 total_cnt++; 12 return ControlType::Play; 13 } 14 else if(s1=="pause"){ 15 total_cnt++; 16 return ControlType::Pause; 17 } 18 else if(s1=="next"){ 19 total_cnt++; 20 return ControlType::Next; 21 } 22 else if(s1=="prev"){ 23 total_cnt++; 24 return ControlType::Prev; 25 } 26 else if(s1=="stop"){ 27 total_cnt++; 28 return ControlType::Stop; 29 } 30 else { 31 total_cnt++; 32 return ControlType::Unknown; 33 } 34 } 35 void PlayerControl::execute(ControlType cmd)const{ 36 switch(cmd){ 37 case ControlType::Play: 38 std::cout<<"[Play] Playing music...\n";break; 39 case ControlType::Pause: 40 std::cout<<"[Pause] Music paused\n";break; 41 case ControlType::Next: 42 std::cout<<"[Next] Skipping to next track\n";break; 43 case ControlType::Prev: 44 std::cout<<"[Prev] Back to previous track\n";break; 45 case ControlType::Stop: 46 std::cout<<"[Stop] Music stopped\n";break; 47 default: 48 std::cout<<"[Error] unknown control\n";break; 49 } 50 } 51 int PlayerControl::get_cnt(){ 52 return total_cnt; 53 }
源代码task3.cpp
1 #include"PlayerControl.h" 2 #include<iostream> 3 void test(){ 4 PlayerControl controller; 5 std::string control_str; 6 std::cout<<"Enter Control:(play/pause/next/prev/stop/quit):\n"; 7 while(std::cin>>control_str){ 8 if(control_str=="quit") 9 break; 10 ControlType cmd=controller.parse(control_str); 11 controller.execute(cmd); 12 std::cout<<"Current Player control:"<<PlayerControl::get_cnt()<<"\n\n"; 13 } 14 } 15 int main(){ 16 test(); 17 }
运行结果截图

思考:如果希望模拟播放控制时,输出控制更现代(使用emoji),如何调整代码实现?
答:可以在emoji官网上查找对应emoji的UTF-8编码,添加在输出的代码中。
其他两个源代码不变
修改后的源代码PlayerControl.cpp
1 #include"PlayerControl.h" 2 #include<iostream> 3 #include<algorithm> 4 #include<windows.h> 5 int PlayerControl::total_cnt=0; 6 PlayerControl::PlayerControl(){ 7 SetConsoleOutputCP(65001); 8 } 9 ControlType PlayerControl::parse(const std::string& control_str){ 10 std::string s1; 11 for(auto c:control_str) 12 s1+=std::tolower(c); 13 if(s1=="play"){ 14 total_cnt++; 15 return ControlType::Play; 16 } 17 else if(s1=="pause"){ 18 total_cnt++; 19 return ControlType::Pause; 20 } 21 else if(s1=="next"){ 22 total_cnt++; 23 return ControlType::Next; 24 } 25 else if(s1=="prev"){ 26 total_cnt++; 27 return ControlType::Prev; 28 } 29 else if(s1=="stop"){ 30 total_cnt++; 31 return ControlType::Stop; 32 } 33 else { 34 total_cnt++; 35 return ControlType::Unknown; 36 } 37 } 38 void PlayerControl::execute(ControlType cmd)const{ 39 switch(cmd){ 40 case ControlType::Play: 41 std::cout<<"\xF0\x9F\x8E\xB5 Playing music...\n";break; 42 case ControlType::Pause: 43 std::cout<<"\xE2\x8F\xA9 Music paused\n";break; 44 case ControlType::Next: 45 std::cout<<"\xE2\x8F\xAD Skipping to next track\n";break; 46 case ControlType::Prev: 47 std::cout<<"\xE2\x8F\xAE Back to previous track\n";break; 48 case ControlType::Stop: 49 std::cout<<"\xE2\x8F\xB9 Music stopped\n";break; 50 default: 51 std::cout<<"\xE2\x9D\x8C unknown control\n";break; 52 } 53 } 54 int PlayerControl::get_cnt(){ 55 return total_cnt; 56 }

四、实验任务4
源代码Fraction.h
1 #pragma once 2 #include<string> 3 class Fraction{ 4 public: 5 Fraction(int up=0,int down=1); 6 Fraction(const Fraction &f):up(f.up),down(f.down){} 7 ~Fraction(){} 8 int get_up()const{ 9 return up; 10 } 11 int get_down()const{ 12 return down; 13 } 14 Fraction negative()const; 15 private: 16 int up,down; 17 public: 18 static const std::string doc; 19 private: 20 void func(); 21 friend void output(const Fraction &f); 22 friend Fraction add(const Fraction &f1,const Fraction &f2); 23 friend Fraction sub(const Fraction &f1,const Fraction &f2); 24 friend Fraction mul(const Fraction &f1,const Fraction &f2); 25 friend Fraction div(const Fraction &f1,const Fraction &f2); 26 }; 27 void output(const Fraction &f); 28 Fraction add(const Fraction &f1,const Fraction &f2); 29 Fraction sub(const Fraction &f1,const Fraction &f2); 30 Fraction mul(const Fraction &f1,const Fraction &f2); 31 Fraction div(const Fraction &f1,const Fraction &f2);
源代码Fraction.cpp
1 #include"Fraction.h" 2 #include<iostream> 3 #include<cmath> 4 const std::string Fraction::doc="Fraction类v 0.01版.\n目前仅支持分数对象的构造、输出、加/减/乘/除运算."; 5 Fraction::Fraction(int up,int down):up(up),down(down){ 6 func(); 7 } 8 Fraction Fraction::negative()const{ 9 return Fraction(-up,down); 10 } 11 void Fraction::func(){ 12 if(up>0&&down<0){ 13 up=-up; 14 down=-down; 15 } 16 else if(up<0&&down<0){ 17 up=-up; 18 down=-down; 19 } 20 int i,a,b; 21 a=up; 22 b=down; 23 a=std::abs(a); 24 b=std::abs(b); 25 while(b!=0){ 26 i=b; 27 b=a%b; 28 a=i; 29 } 30 up/=a; 31 down/=a; 32 } 33 void output(const Fraction &f){ 34 if(f.up==0){ 35 std::cout<<"0"<<std::endl; 36 } 37 else if(f.down==1){ 38 std::cout<<f.up<<std::endl; 39 } 40 else if(f.down>0){ 41 std::cout<<f.up<<"/"<<f.down<<std::endl; 42 } 43 else if(f.up>0&&f.down<0){ 44 std::cout<<"-"<<f.up<<"/"<<f.down<<std::endl; 45 } 46 else if(f.up<0&&f.down<0){ 47 std::cout<<f.up<<"/"<<f.down<<std::endl; 48 } 49 } 50 Fraction add(const Fraction &f1,const Fraction &f2){ 51 return Fraction(f1.up*f2.down+f1.down*f2.up,f1.down*f2.down); 52 } 53 Fraction sub(const Fraction &f1,const Fraction &f2){ 54 return Fraction(f1.up*f2.down-f1.down*f2.up,f1.down*f2.down); 55 } 56 Fraction mul(const Fraction &f1,const Fraction &f2){ 57 return Fraction(f1.up*f2.up,f1.down*f2.down); 58 } 59 Fraction div(const Fraction &f1,const Fraction &f2){ 60 if(f2.up==0){ 61 std::cout<<"分母不能为0"<<std::endl; 62 } 63 return Fraction(f1.up*f2.down,f1.down*f2.up); 64 }
源代码task4.cpp
1 #include"Fraction.h" 2 #include<iostream> 3 void test1(); 4 void test2(); 5 int main(){ 6 std::cout<<"测试1:Fraction类基础功能测试\n"; 7 test1(); 8 std::cout<<"\n测试2:分母为0测试:\n"; 9 test2(); 10 } 11 void test1(){ 12 using std::cout; 13 using std::endl; 14 cout<<"Fraction类测试:"<<endl; 15 cout<<Fraction::doc<<endl<<endl; 16 Fraction f1(5); 17 Fraction f2(3,-4),f3(-18,12); 18 Fraction f4(f3); 19 cout<<"f1=";output(f1);cout<<endl; 20 cout<<"f2=";output(f2);cout<<endl; 21 cout<<"f3=";output(f3);cout<<endl; 22 cout<<"f4=";output(f4);cout<<endl; 23 const Fraction f5(f4.negative()); 24 cout<<"f5=";output(f5);cout<<endl; 25 cout<<"f5.get_up()="<<f5.get_up() 26 <<",f5.get_down()="<<f5.get_down()<<endl; 27 cout<<"f1+f2=";output(add(f1,f2));cout<<endl; 28 cout<<"f1-f2=";output(sub(f1,f2));cout<<endl; 29 cout<<"f1*f2=";output(mul(f1,f2));cout<<endl; 30 cout<<"f1/f2=";output(div(f1,f2));cout<<endl; 31 cout<<"f4+f5=";output(add(f4,f5));cout<<endl; 32 } 33 void test2(){ 34 using std::cout; 35 using std::endl; 36 Fraction f6(42,55),f7(0,3); 37 cout<<"f6=";output(f6);cout<<endl; 38 cout<<"f7=";output(f7);cout<<endl; 39 cout<<"f6/f7=";output(div(f6,f7));cout<<endl; 40 }
运行结果截图


问题:分数的输出和计算,你选择的是哪一种方案?决策理由?
答:我选择的是用友元方案,因为分数的输出和计算函数中每一个都需要用到类的私有数据up和down,使用友元就可以使调用更加简洁,不需要多次使用get_up()和get_down()接口,但是友元会破坏封装性也不利于维护,所以当代码更复杂时再改用公有接口进行调用。
实验总结:1、静态成员变量要在类内声明,类外初始化,不然可能出现难以解决的bug,但其他的函数没有这个要求,可以直接在.h的头文件中进行初始化,简化代码。
2.在编写运算函数时要注意不同的运算规则,比如计算分数加减乘除时要与普通的加减乘除区分开,分别对分子分母做处理。
3.在处理枚举类型时,要注意返回的是枚举里的元素,而不是直接返回其对应的序号,比如要返回 ControlType::Play而不是直接返回整型数0。
4.在多文件中要小心重定义的问题,检查每个函数在所以.cpp文件中只定义了一次,减少报错。


浙公网安备 33010602011771号