实验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.h

源代码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 }
T.cpp

源代码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 }
task1.cpp

运行结果截图

image

 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()。

image

问题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,都会定义这些静态成员,链接时会出现重复定义的情况。

屏幕截图 2025-10-26 163255

 

二、实验任务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.h

源代码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 }
Complex.cpp

源代码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 }
task2.cpp

运行结果截图

image

image

问题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()来访问的,不是设为友元来直接访问私有数据。

image

 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.h

源代码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 }
PlayerControl.cpp

源代码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 }
task3.cpp

运行结果截图

image

思考:如果希望模拟播放控制时,输出控制更现代(使用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 }
PlayerControl.cpp

image

 

四、实验任务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.h

源代码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 }
Fraction.cpp

源代码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 }
task4.cpp

运行结果截图

image

image

问题:分数的输出和计算,你选择的是哪一种方案?决策理由?

答:我选择的是用友元方案,因为分数的输出和计算函数中每一个都需要用到类的私有数据up和down,使用友元就可以使调用更加简洁,不需要多次使用get_up()和get_down()接口,但是友元会破坏封装性也不利于维护,所以当代码更复杂时再改用公有接口进行调用。

 

实验总结:1、静态成员变量要在类内声明,类外初始化,不然可能出现难以解决的bug,但其他的函数没有这个要求,可以直接在.h的头文件中进行初始化,简化代码。

2.在编写运算函数时要注意不同的运算规则,比如计算分数加减乘除时要与普通的加减乘除区分开,分别对分子分母做处理。

3.在处理枚举类型时,要注意返回的是枚举里的元素,而不是直接返回其对应的序号,比如要返回 ControlType::Play而不是直接返回整型数0。

4.在多文件中要小心重定义的问题,检查每个函数在所以.cpp文件中只定义了一次,减少报错。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

posted @ 2025-10-27 20:24  鱼籽不煮粥  阅读(4)  评论(0)    收藏  举报