实验二
task1
T.h
#pragma once
#include <string>
// 类T: 声明
class T {
	// 对象属性、方法
public:
	T(int x = 0, int y = 0);   // 普通构造函数
	T(const T& t);  // 复制构造函数
	T(T&& t);       // 移动构造函数
	~T();           // 析构函数
	void adjust(int ratio);      // 按系数成倍调整数据
	void display() const;           // 以(m1, m2)形式显示T类对象信息
private:
	int m1, m2;
	// 类属性、方法
public:
	static int get_cnt();          // 显示当前T类对象总数
public:
	static const std::string doc;       // 类T的描述信息
	static const int max_cnt;           // 类T对象上限
private:
	static int cnt;         // 当前T类对象数目
	// 类T友元函数声明
	friend void func();
};
// 普通函数声明
void func();
T.cpp
#include "T.h"
#include <iostream>
#include <string>
// 类T实现
// static成员数据类外初始化
const std::string T::doc{ "a simple class sample" };
const int T::max_cnt = 999;
int T::cnt = 0;
// 类方法
int T::get_cnt() {
    return cnt;
}
T::T(int x, int y) : m1{ x }, m2{ y } {
    ++cnt;
    std::cout << "T constructor called.\n";
}
T::T(const T& t) : m1{ t.m1 }, m2{ t.m2 } {
    ++cnt;
    std::cout << "T copy constructor called.\n";
}
T::T(T&& t) : m1{ t.m1 }, m2{ t.m2 } {
    ++cnt;
    std::cout << "T move constructor called.\n";
}
T::~T() {
    --cnt;
    std::cout << "T destructor called.\n";
}
void T::adjust(int ratio) {
    m1 *= ratio;
    m2 *= ratio;
}
void T::display() const {
    std::cout << "(" << m1 << ", " << m2 << ")";
}
// 普通函数实现
void func() {
    T t5(42);
    t5.m2 = 2049;
    std::cout << "t5 = "; t5.display(); std::cout << '\n';
}
task1.cpp
#include "T.h"
#include <iostream>
void test_T();
int main() {
    std::cout << "test Class T: \n";
    test_T();
    std::cout << "\ntest friend func: \n";
    func();
}
void test_T() {
    using std::cout;
    using std::endl;
    cout << "T info: " << T::doc << endl;
    cout << "T objects'max count: " << T::max_cnt << endl;
    cout << "T objects'current count: " << T::get_cnt() << endl << endl;
    T t1;
    cout << "t1 = "; t1.display(); cout << endl;
    T t2(3, 4);
    cout << "t2 = "; t2.display(); cout << endl;
    T t3(t2);
    t3.adjust(2);
    cout << "t3 = "; t3.display(); cout << endl;
    T t4(std::move(t2));
    cout << "t4 = "; t4.display(); cout << endl;
    cout << "test: T objects'current count: " << T::get_cnt() << endl;
}

问题1:T.h中,在类T内部,已声明 func 是T的友元函数。在类外部,去掉line36,重新编译,程序能否正常运行?
A:不能。原因:在类 T 内部声明 func 为友元函数,仅表明 func 有权访问 T 类的私有成员,但 func 本身仍是一个全局函数。在 task1.cpp 中调用 func 时,编译器需要知道 func 的声明。若去掉 T.h 中 line36 的void func();声明,task1.cpp 包含 T.h 后,无法获取 func 的声明,导致编译报错。

问题2:T.h中,line9-12给出了各种构造函数、析构函数。总结它们各自的功能、调用时机。
A:普通构造函数功能:初始化 T 类对象的成员变量 m1 和 m2,同时将对象计数 cnt 加 1,并输出构造调用信息。
调用时机:当使用T t1;、T t2(3,4);等方式创建对象,且不是通过复制或移动已有对象创建时调用。
复制构造函数功能:用已有 T 类对象 t 的成员变量值初始化新对象的 m1 和 m2,将对象计数 cnt 加 1,并输出复制构造调用信息。
调用时机:当用一个已存在的对象初始化新对象时,如T t3(t2);、函数参数按值传递 T 类对象、函数返回 T 类对象。
移动构造函数功能:窃取已有临时 T 类对象 t 的成员变量值初始化新对象,将对象计数 cnt 加 1,并输出移动构造调用信息。
调用时机:当用一个右值初始化新对象的时候。
析构函数功能:在对象生命周期结束时,将对象计数 cnt 减 1,并输出析构调用信息。
调用时机:对象所在作用域结束、动态分配的对象被 delete 时。
问题3:T.cpp中,line13-15,剪切到T.h的末尾,重新编译,程序能否正确编译。
A:不能。原因:C++ 中,静态成员变量的定义通常应放在.cpp 文件中,而不是.h 文件。若将 T.cpp 中 line7-9 的静态成员初始化代码剪切到 T.h 末尾,当多个.cpp 文件包含该 T.h 时,会导致静态成员变量被多次定义,违反 “单一定义规则”,从而引发编译错误。

task2
complex.h
#pragma once
 
#include <iostream>
#include <string>
 
class Complex
{
private:
	double x1;
	double x2;
public:
	static const std::string doc;
	Complex(double real = 0.0, double imag = 0.0);
	Complex(const Complex &c);
 
	double get_real() const;
	double get_imag() const;
	void add(const Complex& c2);
 
	friend double output(const Complex& c);
	friend double abs(const Complex& c); 
	friend Complex add(const Complex& c1, const Complex& c2);
	friend bool is_equal(const Complex& c1, const Complex& c2);
	friend bool is_not_equal(const Complex& c1, const Complex& c2);
 
	~Complex();
};
complex.cpp
#include "Complex.h"
#include <cmath>
 
const std::string Complex::doc = "a simplified complex class";
 
// 构造函数
Complex::Complex(double real, double imag) : x1(real), x2(imag) {}
 
// 拷贝构造函数
Complex::Complex(const Complex& c) : x1(c.x1), x2(c.x2) {}
 
double Complex::get_real() const {
    return x1;
}
 
double Complex::get_imag() const {
    return x2;
}
 
void Complex::add(const Complex& c2) {
    x1 += c2.x1;
    x2 += c2.x2;
}
 
double output(const Complex& c) {
    if (c.x2 >= 0) {
        std::cout << c.x1 << " + " << c.x2 << "i";
    }
    else {
        std::cout << c.x1 << " - " << -c.x2 << "i";
    }
    return 0;
}
 
double abs(const Complex& c) {
    return std::sqrt(c.x1 * c.x1 + c.x2 * c.x2);
}
 
Complex add(const Complex& c1, const Complex& c2) {
    return Complex(c1.x1 + c2.x1, c1.x2 + c2.x2);
}
bool is_equal(const Complex& c1, const Complex& c2) {
    return (c1.x1 == c2.x1) && (c1.x2 == c2.x2);
}
 
bool is_not_equal(const Complex& c1, const Complex& c2) {
    return !(is_equal(c1, c2));
}
Complex::~Complex() {}
task2.cpp
1 #include <iostream>
 2 #include <iomanip>
 3 #include <complex>
 4 #include"Complex.h"
 5 void test_Complex();
 6 void test_std_complex();
 7 
 8 int main() {
 9     std::cout << "*******测试1: 自定义类Complex*******\n";
10     test_Complex();
11 
12     std::cout << "\n*******测试2: 标准库模板类complex*******\n";
13     test_std_complex();
14 }
15 
16 void test_Complex() {
17     using std::cout;
18     using std::endl;
19     using std::boolalpha;
20 
21     cout << "类成员测试: " << endl;
22     cout << Complex::doc << endl << endl;
23 
24     cout << "Complex对象测试: " << endl;
25     Complex c1;
26     Complex c2(3, -4);
27     Complex c3(c2);
28     Complex c4 = c2;
29     const Complex c5(3.5);
30 
31     cout << "c1 = "; output(c1); cout << endl;
32     cout << "c2 = "; output(c2); cout << endl;
33     cout << "c3 = "; output(c3); cout << endl;
34     cout << "c4 = "; output(c4); cout << endl;
35     cout << "c5.real = " << c5.get_real() 
36          << ", c5.imag = " << c5.get_imag() << endl << endl;
37 
38     cout << "复数运算测试: " << endl;
39     cout << "abs(c2) = " << abs(c2) << endl;
40     c1.add(c2);
41     cout << "c1 += c2, c1 = "; output(c1); cout << endl;
42     cout << boolalpha;
43     cout << "c1 == c2 : " << is_equal(c1, c2) << endl;
44     cout << "c1 != c2 : " << is_not_equal(c1, c2) << endl;
45     c4 = add(c2, c3);
46     cout << "c4 = c2 + c3, c4 = "; output(c4); cout << endl;
47 }
48 
49 void test_std_complex() {
50     using std::cout;
51     using std::endl;
52     using std::boolalpha;
53 
54     cout << "std::complex<double>对象测试: " << endl;
55     std::complex<double> c1;
56     std::complex<double> c2(3, -4);
57     std::complex<double> c3(c2);
58     std::complex<double> c4 = c2;
59     const std::complex<double> c5(3.5);
60 
61     cout << "c1 = " << c1 << endl;
62     cout << "c2 = " << c2 << endl;
63     cout << "c3 = " << c3 << endl;
64     cout << "c4 = " << c4 << endl;
65 
66     cout << "c5.real = " << c5.real() 
67          << ", c5.imag = " << c5.imag() << endl << endl;
68 
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


问题1:比较自定义类 Complex 和标准库模板类 complex 的用法,在使用形式上,哪一种更简洁?函数和运算内在有关联吗?
A:标准库模板类complex 在使用形式上更简洁。
有关联,两者的内在关联都可与复数数学运算紧密绑定,但标准库的 std::complex 因设计更通用、接口更贴合数学习惯,在 “直觉一致性” 上表现更优。
问题2:
2-1:自定义 Complex 中, output/abs/add/ 等均设为友元,它们真的需要访问 私有数据 吗?(回答“是/否”并给出理由)
A:是。因为 output 函数需要输出复数的实部和虚部,abs 函数需要根据实部和虚部计算模值,add 函数需要获取两个复数的实部和虚部进行相加运算,而实部 real 和虚部 imag 是 Complex 类的私有成员,只有通过友元函数才能直接访问。
2-2:标准库 std::complex 是否把 abs 设为友元?(查阅 cppreference后回答)
A:标准库std::complex没有把abs设为友元。std::complex的abs函数是通过调用std::complex的公有成员函数(如real()和imag())来获取复数的实部和虚部,进而计算模值,无需作为友元。
2-3:什么时候才考虑使用 friend?总结你的思考。
当一个函数或类需要访问另一个类的私有或保护成员,且通过公有接口无法高效或合理实现该功能时,考虑使用 friend。
问题3:如果构造对象时禁用=形式,即遇到 Complex c4 = c2; 编译报错,类Complex的设计应如何调整?
将复制构造函数声明为私有,或者在 C++11 及以后标准中,使用= delete删除复制构造函数。
task3
PlayerControl.h
1 #pragma once
 2 #include <string>
 3 
 4 enum class ControlType {Play, Pause, Next, Prev, Stop, Unknown};
 5 
 6 class PlayerControl {
 7 public:
 8     PlayerControl();
 9 
10     ControlType parse(const std::string& control_str);   
11     void execute(ControlType cmd) const;      
12 
13     static int get_cnt();
14 
15 private:
16     static int total_cnt;   
17 };
PlayerControl.h
PlayerControl.cpp
1 #include "PlayerControl.h"
 2 #include <iostream>
 3 #include <algorithm>   
 4 
 5 int PlayerControl::total_cnt = 0;
 6 
 7 PlayerControl::PlayerControl() {}
 8 
 9 ControlType PlayerControl::parse(const std::string& control_str) {
10    
11    std::string lower_str = control_str;
12 for (char& c : lower_str) {  
13     c = tolower(static_cast<unsigned char>(c));  
14 }
15     
16     if (lower_str == "play") {
17         total_cnt++;
18         return ControlType::Play;
19     } else if (lower_str == "pause") {
20         total_cnt++;
21         return ControlType::Pause;
22     } else if (lower_str == "next") {
23         total_cnt++;
24         return ControlType::Next;
25     } else if (lower_str == "prev") {
26         total_cnt++;
27         return ControlType::Prev;
28     } else if (lower_str == "stop") {
29         total_cnt++;
30         return ControlType::Stop;
31     } else {
32         
33         return ControlType::Unknown;
34     }
35 }
36 
37 void PlayerControl::execute(ControlType cmd) const {
38     switch (cmd) {
39     case ControlType::Play:  std::cout << "[play] Playing music...\n"; break;
40     case ControlType::Pause: std::cout << "[Pause] Music paused\n";    break;
41     case ControlType::Next:  std::cout << "[Next] Skipping to next track\n"; break;
42     case ControlType::Prev:  std::cout << "[Prev] Back to previous track\n"; break;
43     case ControlType::Stop:  std::cout << "[Stop] Music stopped\n"; break;
44     default:                 std::cout << "[Error] unknown control\n"; break;
45     }
46 }
47 
48 int PlayerControl::get_cnt() {
49     return total_cnt;
50 }
PlayerControl.cpp
task3.cpp
1 #include "PlayerControl.h"
 2 #include <iostream>
 3 
 4 void test() {
 5     PlayerControl controller;
 6     std::string control_str;
 7     std::cout << "Enter Control: (play/pause/next/prev/stop/quit):\n";
 8 
 9     while(std::cin >> control_str) {
10         if(control_str == "quit")
11             break;
12         
13         ControlType cmd = controller.parse(control_str);
14         controller.execute(cmd);
15         std::cout << "Current Player control: " << PlayerControl::get_cnt() << "\n\n";
16     }
17 }
18 
19 int main() {
20     test();
21 }
task3.cpp

task4
task4.cpp
#include "Fraction.h"
#include <iostream>
 
void test1();
void test2();
using namespace FracCal;	
 
 
int main() {
    std::cout << "测试1: Fraction类基础功能测试\n";
    test1();
 
    std::cout << "\n测试2: 分母为0测试: \n";
    test2();
 
}
 
void test1() {
    using std::cout;
    using std::endl;   
 
    cout << "Fraction类测试: " << endl;
    cout << Fraction::doc << endl << endl;
    
    Fraction f1(5);
    Fraction f2(3, -4), f3(-18, 12);
    Fraction f4(f3);
    cout << "f1 = "; output(f1); cout << endl;
    cout << "f2 = "; output(f2); cout << endl;
    cout << "f3 = "; output(f3); cout << endl;
    cout << "f4 = "; output(f4); cout << endl;
    
    const Fraction f5(f4.negative());
    cout << "f5 = "; output(f5); cout << endl;
    cout << "f5.get_up() = " << f5.get_up() 
        << ", f5.get_down() = " << f5.get_down() << endl;
    
    cout << "f1 + f2 = "; output(add(f1, f2)); cout << endl;
    cout << "f1 - f2 = "; output(sub(f1, f2)); cout << endl;
    cout << "f1 * f2 = "; output(mul(f1, f2)); cout << endl;
    cout << "f1 / f2 = "; output(div(f1, f2)); cout << endl;
    cout << "f4 + f5 = "; output(add(f4, f5)); cout << endl;
 
}
 
void test2() {
    using std::cout;
    using std::endl;
 
    Fraction f6(42, 55), f7(0, 3);
    cout << "f6 = "; output(f6); cout << endl;
    cout << "f7 = "; output(f7); cout << endl;
    cout << "f6 / f7 = "; output(div(f6, f7)); cout << endl;
 
}
Fraction.h
#pragma once
#include <string>
class Fraction {
public:
    static const std::string doc;
    Fraction(int up);
    Fraction(int up, int down);
    Fraction(const Fraction& other);
    int get_up() const;
    int get_down() const;
    Fraction negative() const;
    friend void output(const Fraction& f);
    friend Fraction add(const Fraction& f1, const Fraction& f2);
    friend Fraction sub(const Fraction& f1, const Fraction& f2);
    friend Fraction mul(const Fraction& f1, const Fraction& f2);
    friend Fraction div(const Fraction& f1, const Fraction& f2);
private:
    int up; // 分子
    int down; // 分母
    int gcd(int a, int b) const;
    void reduce();
};
Fraction.cpp
#include "Fraction.h"
#include <iostream>
#include <cmath>
const std::string Fraction::doc = "Fraction类 v 0.01版.\n目前仅支持分数对象的构造、输出、加/减/乘/除运算.";
Fraction::Fraction(int up) : up(up), down(1) {
    reduce();
}
Fraction::Fraction(int up, int down) : up(up), down(down) {
    if (down == 0) {
        std::cerr << "错误:分母不能为0,已默认设为1" << std::endl;
        this->down = 1;
    }
    reduce();
}
Fraction::Fraction(const Fraction& other) : up(other.up), down(other.down) {}
int Fraction::gcd(int a, int b) const {
    a = abs(a);
    b = abs(b);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}
void Fraction::reduce() {
    int common_divisor = gcd(up, down);
    up /= common_divisor;
    down /= common_divisor;
    if (down < 0) {
        up *= -1;
        down *= -1;
    }
}
int Fraction::get_up() const {
    return up;
}
int Fraction::get_down() const {
    return down;
}
Fraction Fraction::negative() const {
    Fraction temp(-up, down);
    temp.reduce();
    return temp;
}
void output(const Fraction& f) {
    if (f.down == 1) {
        std::cout << f.up;
    } else {
        std::cout << f.up << "/" << f.down;
    }
}
Fraction add(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.up * f2.down + f2.up * f1.down;
    int new_down = f1.down * f2.down;
    Fraction temp(new_up, new_down);
    temp.reduce();
    return temp;
}
Fraction sub(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.up * f2.down - f2.up * f1.down;
    int new_down = f1.down * f2.down;
    Fraction temp(new_up, new_down);
    temp.reduce();
    return temp;
}
Fraction mul(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.up * f2.up;
    int new_down = f1.down * f2.down;
    Fraction temp(new_up, new_down);
    temp.reduce();
    return temp;
}
Fraction div(const Fraction& f1, const Fraction& f2) {
    if (f2.up == 0) {
        std::cerr << "分母不能为0" << std::endl;
        return Fraction(0, 1);
    }
    int new_up = f1.up * f2.down;
    int new_down = f1.down * f2.up;
    Fraction temp(new_up, new_down);
    temp.reduce();
    return temp;
}

设计方案:选择友元函数方案实现output/add/sub/mul/div 工具函数。
决策理由:
友元方案优点:友元函数可以直接访问 Fraction 类的私有成员,无需通过公有接口间接获取,减少了函数调用开销,提高了代码效率;对于分数的运算(如加、减、乘、除)和输出操作,直接访问私有成员能让代码逻辑更简洁直观,避免了多次调用 get_up () 和 get_down () 的冗余代码。
友元方案缺点:破坏了类的封装性,友元函数与类紧密耦合,若类的私有成员结构发生变化,友元函数也需要相应修改;友元关系不可传递,若后续需要新增相关功能函数,可能需要重复声明友元。
静态成员函数方案不适用原因:静态成员函数属于类,虽然也可以实现工具函数功能,但对于分数的二元运算静态成员函数的调用形式不如友元函数的全局调用形式简洁自然,不符合通常的函数使用习惯。
命名空间 + 自由函数方案考虑因素:若使用命名空间包裹自由函数,虽然能避免全局命名污染,但自由函数无法直接访问类的私有成员,必须通过公有接口获取分子和分母,会增加函数调用次数,且代码逻辑相对繁琐,不如友元函数高效直观。
实验结论
task1:成功实现了简单类 T 的定义、多文件组织及测试,掌握了类的封装、静态成员、友元函数、构造函数与析构函数的使用,明确了各类函数的调用时机及静态成员的初始化规则。
task2:完成了简化版复数类 Complex 的设计与实现,对比标准库 complex 类,理解了运算符重载对代码简洁性的提升,掌握了友元函数的适用场景及复制构造函数的控制方法。
task3:实现了播放器控制类,模拟了播放控制功能,掌握了枚举类型、静态成员变量、字符串大小写转换的使用。
task4:设计并实现了分数类 Fraction,支持分数的构造、化简、运算及输出,掌握了内部工具函数的设计、友元工具函数的使用,解决了分母为 0 等异常情况的处理。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号