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

回答问题:
1.
.
不能,原因:在类 T 内部声明func()为友元函数时,仅仅表示func可以访问 T 的私有成员,但func本身的函数声明仍需在类外部进行。当去掉类外部的func()的声明后,在程序运行时,编译器无法找到func的函数声明,导致编译错误。
2.(1)普通构造函数 T(int x=0,int y=0);
功能:初始化对象的私有成员变量 m1 和 m2,若无声明,则默认m1,m2=0;
调用时机:T.cpp line49 :T t5(42) ;默认m2=0。
task1.cpp line22 :T ti; line 25:T t2(3,4);
(2)复制构造函数 T(const T &t);
功能:通过已定义的对象 t 复制其成员变量 m1 和 m2 到新对象中;
调用时机:task1.cpp line28: T t3(t2); 将t2中的对象值m1,m2复制到对象t3中。
(3)移动构造函数 T(T &&t);
功能:通过 “右值引用” 获取临时对象 t 的成员变量 m1 和 m2,直接复用资源,减少内存开销。
调用时机:task1.cpp line32:T t4(std::move(t2)); 用std::move()右值引用,初始化t4.
(4)析构函数 ~T();
功能:清理被销毁的对象。
调用时机:函数(main函数等)执行结束时,局部对象销毁时等。
3.

不能。原因:在原代码中,T.cpp的 line8-10 是 T类静态成员 doc,max_cnt,cnt的定义。这些定义是关于 T 类的完整声明(在 T.h 中),且必须在类外部(即全局作用域,且 T 类已声明的前提下)进行定义。当将这些代码剪切到 T.h 的末尾时,虽然 T 类已声明,但静态成员的定义语法要求必须在类外部。在实际编译时,编译器会严格检查静态成员定义与类的作用域关联,由于头文件可能被多次包含,且静态成员定义不允许重复,这种将定义放入头文件的做法会导致链接错误,即重复定义。
实验任务二:
源代码:
Complex.h
1 #pragma once 2 #include<string> 3 using namespace std; 4 class Complex 5 { 6 7 private: 8 double real, imag; 9 public: 10 Complex(double x = 0, double y = 0); 11 Complex(const Complex& t); 12 13 14 static const string doc; 15 16 17 double get_real() const { return real; }; 18 double get_imag() const { return imag; }; 19 20 void add(const Complex& t) { 21 real += t.real; 22 imag += t.imag; 23 } 24 25 friend void output(const Complex &t); 26 friend double abs(const Complex &t); 27 friend Complex add(const Complex &c1,const Complex &c2); 28 friend bool is_equal(Complex c1, Complex c2); 29 friend bool is_not_equal(Complex c1, Complex c2); 30 31 }; 32 void output(const Complex &t); 33 double abs(const Complex &t); 34 Complex add(const Complex &c1,const Complex &c2); 35 bool is_equal(Complex c1, Complex c2); 36 bool is_not_equal(Complex c1, Complex c2);
Complex.cpp
1 #include "Complex.h" 2 #include<string> 3 #include<iostream> 4 #include<cmath> 5 using namespace std; 6 const string Complex::doc{ "a simplified complex class" }; 7 8 Complex::Complex(double x, double y) { 9 real = x; 10 imag = y; 11 } 12 Complex::Complex(const Complex& t) { 13 real = t.real; 14 imag = t.imag; 15 } 16 17 18 void output(const Complex &t) { 19 if (t.imag >= 0) 20 cout << t.real << '+' << t.imag << 'i' << '\n'; 21 22 else 23 cout << t.real << '-' << -t.imag << 'i' << '\n'; 24 25 } 26 double abs(const Complex& t) { 27 double ans ; 28 ans = sqrt(t.real * t.real + t.imag * t.imag); 29 return ans; 30 } 31 Complex add(const Complex &c1, const Complex &c2) { 32 Complex c; 33 c.real = c1.real + c2.real; 34 c.imag = c1.imag + c2.imag; 35 return c; 36 } 37 bool is_equal(Complex c1, Complex c2) { 38 return ((c1.real == c2.real) && (c1.imag == c2.imag)); 39 } 40 bool is_not_equal(Complex c1, Complex c2) { 41 return ((c1.real != c2.real) || (c1.imag != c2.imag)); 42 }
task2.cpp
1 // 待补足头文件 2 // xxx 3 #include"Complex.h" 4 #include <iostream> 5 #include <iomanip> 6 #include <complex> 7 void test_Complex(); 8 void test_std_complex(); 9 int main() { 10 std::cout << "*******测试1: 自定义类Complex*******\n"; 11 test_Complex(); 12 std::cout << "\n*******测试2: 标准库模板类complex*******\n"; 13 test_std_complex(); 14 } 15 void test_Complex() { 16 using std::cout; 17 using std::endl; 18 using std::boolalpha; 19 cout << "类成员测试: " << endl; 20 cout << Complex::doc << endl << endl; 21 cout << "Complex对象测试: " << endl; 22 Complex c1; 23 Complex c2(3, -4); 24 Complex c3(c2); 25 Complex c4 = c2; 26 const Complex c5(3.5); 27 cout << "c1 = "; output(c1); cout << endl; 28 cout << "c2 = "; output(c2); cout << endl; 29 cout << "c3 = "; output(c3); cout << endl; 30 cout << "c4 = "; output(c4); cout << endl; 31 cout << "c5.real = " << c5.get_real() 32 << ", c5.imag = " << c5.get_imag() << endl << endl; 33 cout << "复数运算测试: " << endl; 34 cout << "abs(c2) = " << abs(c2) << endl; 35 c1.add(c2); 36 cout << "c1 += c2, c1 = "; output(c1); cout << endl; 37 cout << boolalpha; 38 cout << "c1 == c2 : " << is_equal(c1, c2) << endl; 39 cout << "c1 != c2 : " << is_not_equal(c1, c2) << endl; 40 c4 = add(c2, c3); 41 cout << "c4 = c2 + c3, c4 = "; output(c4); cout << endl; 42 } 43 void test_std_complex() { 44 using std::cout; 45 using std::endl; 46 using std::boolalpha; 47 cout << "std::complex<double>对象测试: " << endl; 48 std::complex<double> c1; 49 std::complex<double> c2(3, -4); 50 std::complex<double> c3(c2); 51 std::complex<double> c4 = c2; 52 const std::complex<double> c5(3.5); 53 cout << "c1 = " << c1 << endl; 54 cout << "c2 = " << c2 << endl; 55 cout << "c3 = " << c3 << endl; 56 cout << "c4 = " << c4 << endl; 57 cout << "c5.real = " << c5.real() 58 << ", c5.imag = " << c5.imag() << endl << endl; 59 cout << "复数运算测试: " << endl; 60 cout << "abs(c2) = " << abs(c2) << endl; 61 c1 += c2; 62 cout << "c1 += c2, c1 = " << c1 << endl; 63 cout << boolalpha; 64 cout << "c1 == c2 : " << (c1 == c2) << endl; 65 cout << "c1 != c2 : " << (c1 != c2) << endl; 66 c4 = c2 + c3; 67 cout << "c4 = c2 + c3, c4 = " << c4 << endl; 68 }
运行测试截图:


比较自定义类Complex和标准库模板类complex的使用,回答问题:
问题1: 比较自定义类Complex和标准库模板类complex的用法,在使用形式上,哪一种更简洁?函数和运算内在有关联吗?
标准库模板更简洁。有关联,数学逻辑基本一致,标准库更为简洁。
问题2:
2-1:自定义Complex中, output/abs/add/ 等均设为友元,它们真的需要访问 私有数据 吗?(回答“是/否”并 给出理由)
是;理由:output 需要读取 real 和 imag 才能格式化输出;abs 需要用 real 和 imag 计算模长;add 需要将两个复数的 real 和 imag 分别相加。若不声明为友元,这些函数无法直接访问私有成员,只能通过 get_real() 和 get_imag() 间接获取,而设为友元后,它们可以直接访问,简化了代码。
2-2:标准库 std::complex 是否把 abs 设为友元?(查阅 cppreference后回答)
根据 cppreference ,abs 是一个非成员函数(全局函数),而非 std::complex 的友元。std::complex 提供了 real() 和 imag() 成员函数(公有的访问接口),abs 函数通过这两个接口获取实部和虚部,无需作为友元直接访问私有成员。
2-3:什么时候才考虑使用 friend?总结你的思考
需要直接访问私有成员的函数(如output函数)或者两个关系紧密的类,可以通过设立友元来减少代码量。但应谨慎使用友元,避免其破坏类的封闭性。实践中优先使用公有成员函数实现。
问题3: 如果构造对象时禁用=形式,即遇到Complex c4 = c2;编译报错,类Complex的设计应如何调整?
删除拷贝构造函数,可以使用 = delete 显式删除拷贝构造函数,使其无法被调用。Complex(const Complex&t) = delete;
实验任务三
源代码:
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 8 ControlType parse(const std::string& control_str); // 实现std::string --> ControlType转换 9 void execute(ControlType cmd) const; // 执行控制操作(以打印输出模拟) 10 static int get_cnt(); 11 12 private: 13 static int total_cnt; 14 };
PlayerControl.cpp
1 #include "PlayerControl.h" 2 #include <iostream> 3 #include <algorithm> 4 int PlayerControl::total_cnt = 0; 5 PlayerControl::PlayerControl() {} 6 // 待补足 7 // 1. 将输入字符串转为小写,实现大小写不敏感 8 // 2. 匹配"play"/"pause"/"next"/"prev"/"stop"并返回对应枚举 9 // 3. 未匹配的字符串返回ControlType::Unknown 10 // 4. 每次成功调用parse时递增total_cnt 11 ControlType PlayerControl::parse(const std::string& control_str) { 12 std::string s; 13 for (auto c : control_str) { 14 s += tolower(c); 15 } 16 ControlType ans; 17 if (s == "play") { 18 ans = ControlType::Play; 19 } 20 else if (s == "pause") { 21 ans = ControlType::Pause; 22 } 23 else if (s == "next") { 24 ans = ControlType::Next; 25 } 26 else if (s == "prev") { 27 ans = ControlType::Prev; 28 } 29 else if (s == "stop") { 30 ans = ControlType::Stop; 31 } 32 else { 33 ans = ControlType::Unknown; // 未匹配的命令 34 } 35 total_cnt++; 36 return ans; 37 } 38 void PlayerControl::execute(ControlType cmd) const { 39 switch (cmd) { 40 case ControlType::Play: std::cout << "[play] Playing music...\n"; break; 41 case ControlType::Pause: std::cout << "[Pause] Music paused\n"; break; 42 case ControlType::Next: std::cout << "[Next] Skipping to next track\n"; break; 43 case ControlType::Prev: std::cout << "[Prev] Back to previous track\n"; break; 44 case ControlType::Stop: std::cout << "[Stop] Music stopped\n"; break; 45 default: std::cout << "[Error] unknown control\n"; break; 46 } 47 } 48 int PlayerControl::get_cnt() { 49 return total_cnt; 50 }
tesk3
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 11 ControlType cmd = controller.parse(control_str); 12 controller.execute(cmd); 13 std::cout << "Current Player control: " << PlayerControl::get_cnt() << "\n\n"; 14 } 15 } 16 int main() { 17 test(); 18 }
运行测试截图

实验任务四:
源代码:
Fraction.h
1 #pragma once 2 #include<string> 3 #include<cstdlib> 4 #include<numeric> 5 6 using namespace std; 7 8 class Fraction 9 { 10 11 private: 12 int up, down; 13 public: 14 static const string doc; 15 16 Fraction(int x, int y = 1) { 17 bool sign; 18 if (x > 0 && y < 0 || x < 0 && y>0) sign = 0; 19 else sign = 1; 20 up = abs(x);down = abs(y); 21 int r = gcd(up, down); 22 up /= r;down /= r; 23 if (!sign) up = -up; 24 } 25 Fraction(const Fraction& t) :up{ t.up }, down{t.down}{} 26 27 int get_up() const{ return up; } 28 int get_down() const{ return down; } 29 Fraction negative()const { 30 return Fraction(-up,down); 31 32 } 33 34 friend void output(const Fraction &t); 35 friend Fraction add(const Fraction &f1, const Fraction &f2); 36 friend Fraction sub(const Fraction &f1, const Fraction &f2); 37 friend Fraction mul(const Fraction &f1, const Fraction &f2); 38 friend Fraction div(const Fraction &f1, const Fraction &f2); 39 }; 40 void output(const Fraction& t); 41 Fraction add(const Fraction& f1, const Fraction& f2); 42 Fraction sub(const Fraction& f1, const Fraction& f2); 43 Fraction mul(const Fraction& f1, const Fraction& f2); 44 Fraction div(const Fraction& f1, const Fraction& f2);
Fraction.cpp
1 #include "Fraction.h" 2 #include<iostream> 3 #include<string> 4 #include<cstdlib> 5 #include<numeric> 6 using namespace std; 7 8 const string Fraction::doc{ "Fraction类 v 0.01版." }; 9 10 void output(const Fraction& t) { 11 if (t.get_down() == 0) { 12 cout << "分母不能为0" ; 13 return; 14 } 15 if (t.get_down() != 1) 16 cout << t.get_up() << '/' << t.get_down() ; 17 else 18 cout << t.get_up() ; 19 } 20 Fraction add(const Fraction& f1, const Fraction& f2) { 21 int up1 = f1.get_up(), down1 = f1.get_down(), 22 up2 = f2.get_up(), down2 = f2.get_down(); 23 if (up1 > 0 && down1 < 0 ) { 24 up1 = -up1; 25 down1 = -down1; 26 } 27 else { 28 down1 = abs(down1); 29 } 30 if (up2 > 0 && down2 < 0) { 31 up2 = -up2; 32 down2 = -down2; 33 } 34 else { 35 down2 = abs(down2); 36 } 37 int r = gcd(down1, down2); 38 int up, down; 39 up = up1 * (down2 / r) + up2 * (down1 / r); 40 down = down1 * down2 / r; 41 42 return Fraction(up, down); 43 } 44 Fraction sub(const Fraction& f1, const Fraction& f2) { 45 return add(f1,f2.negative()); 46 } 47 Fraction mul(const Fraction& f1, const Fraction& f2) { 48 return Fraction(f1.get_up() * f2.get_up(), f1.get_down() * f2.get_down()); 49 } 50 Fraction div(const Fraction& f1, const Fraction& f2) { 51 return Fraction(f1.get_up() * f2.get_down(), f1.get_down() * f2.get_up()); 52 }
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 24 const Fraction f5(f4.negative()); 25 cout << "f5 = "; output(f5); cout << endl; 26 cout << "f5.get_up() = " << f5.get_up() 27 << ", f5.get_down() = " << f5.get_down() << endl; 28 cout << "f1 + f2 = "; output(add(f1, f2)); cout << endl; 29 cout << "f1 - f2 = "; output(sub(f1, f2)); cout << endl; 30 cout << "f1 * f2 = "; output(mul(f1, f2)); cout << endl; 31 cout << "f1 / f2 = "; output(div(f1, f2)); cout << endl; 32 cout << "f4 + f5 = "; output(add(f4, f5)); cout << endl; 33 } 34 void test2() { 35 using std::cout; 36 using std::endl; 37 Fraction f6(42, 55), f7(0, 3); 38 cout << "f6 = "; output(f6); cout << endl; 39 cout << "f7 = "; output(f7); cout << endl; 40 cout << "f6 / f7 = "; output(div(f6, f7)); cout << endl; 41 }
运行结果截图:

分数的输出和计算, output/add/sub/mul/div ,你选择的是哪一种设计方案?(友元/自由函数/命名空间+自由函数/类+static) 你的决策理由?如友元方案的优缺点、静态成员函数方案的适用场景、命名空间方案的考虑因素等
友元;理由:代码简洁,无需通过get_up()/get_down()接口间接访问私有成员,直接操作分子和分母,代码编写更为灵活。但友元也存在明显缺点:破坏封装性,权限失控,扩展性差等。静态成员函数方案的适用场景:提供 “与类相关但独立于对象实例” 的功能。命名空间通过隔离作用域、强化模块化、支持扩展、兼容灵活的运算符重载等特性,为分数类的运算和输出功能提供了清晰、可维护的设计框架。其核心是在 “不破坏类封装” 的前提下,将相关功能组织成独立模块,同时避免命名冲突和代码冗余,尤其适合需要长期维护、功能可能扩展的场景。

浙公网安备 33010602011771号