实验二

试验任务一:

问题一:不能

image

C++ 编译遵循 “先声明后使用” 原则,即使func在类内被声明为友元函数,也只能给他授予访问权限,而不是在全局域声明函数原型,因此task1.cpp找不到该函数,出现报错。

问题二:

普通构造函数:功能:初始化对象 调用时机:创建对象。

复制构造函数:功能:用已存在对象的成员值初始化新对象 调用时机:用对象初始化新对象,函数参数按值传递对象

 移动构造函数:功能:转移右值对象的资源 调用时机:. 用右值初始化新对象。

析构函数:功能:释放对象资源 调用时机: 对象生命周期结束,程序结束时全局 / 静态对象被销毁。

问题三:

image

 类的非inline静态成员变量必须在类外初始化。将T::docT::max_cntT::cnt的初始化剪切到T.h末尾,仍属于类外初始化。但T.h可能被多个文件包含,导致静态成员变量被重复定义。

试验任务二:

#pragma once
#include <string>

class Complex {
public:
    static const std::string doc;

    Complex();                              
    Complex(double real);                   
    Complex(double real, double imag);     
    Complex(const Complex& other);         

    double get_real() const;                
    double get_imag() const;               
    void add(const Complex& other);         

    
    friend void 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); 

private:
    double real;
    double imag;
};
#include "Complex.h"
#include <iostream>
#include <cmath>
#include <string>

const std::string Complex::doc = "a simplified complex class";


Complex::Complex() : real(0.0), imag(0.0) {}

Complex::Complex(double real) : real(real), imag(0.0) {}

Complex::Complex(double real, double imag) : real(real), imag(imag) {}

Complex::Complex(const Complex& other) : real(other.real), imag(other.imag) {}


double Complex::get_real() const {
    return real;
}

double Complex::get_imag() const {
    return imag;
}

void Complex::add(const Complex& other) {
    real += other.real;
    imag += other.imag;
}

void output(const Complex& c) {
    if (c.imag > 0) {
        std::cout << c.real << "+" << c.imag << "i";
    }
    else if (c.imag < 0) {
        std::cout << c.real << c.imag << "i";
    }
    else {
        std::cout << c.real;
    }
}

double abs(const Complex& c) {
    return sqrt(c.real * c.real + c.imag * c.imag);
}

Complex add(const Complex& c1, const Complex& c2) {
    Complex a;
    a.real = c1.real + c2.real;
    a.imag = c1.imag + c2.imag;
    return a;
}

bool is_equal(const Complex& c1, const Complex& c2) {
    return (c1.real == c2.real) && (c1.imag == c2.imag);
}

// 友元:判断两复数不等(复用is_equal)
bool is_not_equal(const Complex& c1, const Complex& c2) {
    return !is_equal(c1, c2);
}
Complex.cpp
#include "Complex.h" 
#include <iostream>
#include <iomanip>
#include <complex>

void test_Complex();
void test_std_complex();

int main() {
    std::cout << "*******测试1: 自定义类Complex*******\n";
    test_Complex();

    std::cout << "\n*******测试2: 标准库模板类complex*******\n";
    test_std_complex();
}

void test_Complex() {
    using std::cout;
    using std::endl;
    using std::boolalpha;

    cout << "类成员测试: " << endl;
    cout << Complex::doc << endl << endl;

    cout << "Complex对象测试: " << endl;
    Complex c1;
    Complex c2(3, -4);
    Complex c3(c2);
    Complex c4 = c2;
    const Complex c5(3.5);

    cout << "c1 = "; output(c1); cout << endl;
    cout << "c2 = "; output(c2); cout << endl;
    cout << "c3 = "; output(c3); cout << endl;
    cout << "c4 = "; output(c4); cout << endl;
    cout << "c5.real = " << c5.get_real()
        << ", c5.imag = " << c5.get_imag() << endl << endl;

    cout << "复数运算测试: " << endl;
    cout << "abs(c2) = " << abs(c2) << endl;
    c1.add(c2);
    cout << "c1 += c2, c1 = "; output(c1); cout << endl;
    cout << boolalpha;
    cout << "c1 == c2 : " << is_equal(c1, c2) << endl;
    cout << "c1 != c2 : " << is_not_equal(c1, c2) << endl;
    c4 = add(c2, c3);
    cout << "c4 = c2 + c3, c4 = "; output(c4); cout << endl;
}

void test_std_complex() {
    using std::cout;
    using std::endl;
    using std::boolalpha;
    using std::complex;

    cout << "std::complex<double>对象测试: " << endl;
    complex<double> c1;
    complex<double> c2(3, -4);
    complex<double> c3(c2);
    complex<double> c4 = c2;
    const complex<double> c5(3.5);

    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;
    cout << "c3 = " << c3 << endl;
    cout << "c4 = " << c4 << endl;
    cout << "c5.real = " << c5.real()
        << ", c5.imag = " << c5.imag() << endl << endl;

    cout << "复数运算测试: " << endl;
    cout << "abs(c2) = " << abs(c2) << endl;
    c1 += c2;
    cout << "c1 += c2, c1 = " << c1 << endl;
    cout << boolalpha;
    cout << "c1 == c2 : " << (c1 == c2) << endl;
    cout << "c1 != c2 : " << (c1 != c2) << endl;
    c4 = c2 + c3;
    cout << "c4 = c2 + c3, c4 = " << c4 << endl;
}
task2.cpp

运行结果:

image

问题一:

标准库模板类更简洁;有关系,如c1.add,c1 += c2。

问题二:

2-1:需要;output需访问realimag以格式化输出;abs需计算real²+imag²,大家都需要访问real和imag。

2-2:否;std::abs(std::complex<T>)是全局函数,而非友元。std::complex通过公有成员函数real()imag()对外提供数据访问,避免使用友元,符合 “最小权限原则”。

2-3:需维持类的封装性;无法通过公有成员函数间接获取所需数据;外部函数或类需访问当前类的私有成员。

问题三:

删除复制构造函数即可

Complex(const Complex& other) = delete;

实验任务三:

#pragma once
#include <string>

enum class ControlType { Play, Pause, Next, Prev, Stop, Unknown };

class PlayerControl {
public:
    PlayerControl();  

    ControlType parse(const std::string& control_str);  
    void execute(ControlType cmd) const;                

    static int get_cnt();

private:
    static int total_cnt;
};
PlayerControl.h
#include "PlayerControl.h"
#include <iostream>
#include <algorithm>  
#include <string>

int PlayerControl::total_cnt = 0;

PlayerControl::PlayerControl() {}


ControlType PlayerControl::parse(const std::string& control_str) {
    
    std::string lower_str = control_str;
    std::transform(
        lower_str.begin(),
        lower_str.end(),
        lower_str.begin(),
        [](unsigned char c) { return std::tolower(c); }); 
    if (lower_str == "play") {
        ++total_cnt;  
        return ControlType::Play;
    }
    else if (lower_str == "pause") {
        ++total_cnt;
        return ControlType::Pause;
    }
    else if (lower_str == "next") {
        ++total_cnt;
        return ControlType::Next;
    }
    else if (lower_str == "prev") {
        ++total_cnt;
        return ControlType::Prev;
    }
    else if (lower_str == "stop") {
        ++total_cnt;
        return ControlType::Stop;
    }
    else {
        return ControlType::Unknown;
    }
}

void PlayerControl::execute(ControlType cmd) const {
    switch (cmd) {
    case ControlType::Play:
        std::cout << "[play] Playing music...\n";
        break;
    case ControlType::Pause:
        std::cout << "[Pause] Music paused\n";
        break;
    case ControlType::Next:
        std::cout << "[Next] Skipping to next track\n";
        break;
    case ControlType::Prev:
        std::cout << "[Prev] Back to previous track\n";
        break;
    case ControlType::Stop:
        std::cout << "[Stop] Music stopped\n";
        break;
    default:
        std::cout << "[Error] unknown control\n";
        break;
    }
}

int PlayerControl::get_cnt() {
    return total_cnt;
}
PlayerControl.cpp
#include "PlayerControl.h"
#include <iostream>

void test() {
    PlayerControl controller;
    std::string control_str;
    std::cout << "Enter Control: (play/pause/next/prev/stop/quit):\n";

    while (std::cin >> control_str) {
        if (control_str == "quit")
            break;

        ControlType cmd = controller.parse(control_str);
        controller.execute(cmd);
        std::cout << "Current Player control: " << PlayerControl::get_cnt() << "\n\n";
    }
}

int main() {
    test();
}
task3.cpp

 

运行结果:

屏幕截图 2025-10-26 141126

 

实验任务四:

#pragma once
#include <string>

namespace FractionUtil {
    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;

    private:
        int up;
        int down;
    };
    void output(const Fraction& f);
    Fraction add(const Fraction& f1, const Fraction& f2);
    Fraction sub(const Fraction& f1, const Fraction& f2);
    Fraction mul(const Fraction& f1, const Fraction& f2);
    Fraction div(const Fraction& f1, const Fraction& f2);
}
Fraction.h
#include "Fraction.h"
#include <iostream>
#include <cmath>
#include <string>

const std::string FractionUtil::Fraction::doc =
"Fraction类 v 0.01版.\n目前仅支持分数对象的构造、输出、加 / 减 / 乘 / 除运算.";

FractionUtil::Fraction::Fraction(int up) : up(up), down(1) {
    int a = std::abs(up);
    int b = std::abs(down);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    int temp = a;
    up /= temp;
    down /= temp;
}
FractionUtil::Fraction::Fraction(int up, int down) : up(up), down(down) {
    if (down == 0) {
        std::cout << "错误,分母不能为零\n";
        exit(1);
    }
    if(down < 0) {  
        up *= -1;
        down *= -1;
    }
    if (up == 0) { 
        down = 1;
        return;
    }
    int a = std::abs(up);
    int b = std::abs(down);
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    int temp = a;
    up /= temp;
    down /= temp;
}
FractionUtil::Fraction::Fraction(const Fraction& other) : up(other.up), down(other.down) {}

int FractionUtil::Fraction::get_up() const { return up; }
int FractionUtil::Fraction::get_down() const { return down; }

FractionUtil::Fraction FractionUtil::Fraction::negative() const {
    return Fraction(-up, down); 
}

void FractionUtil::output(const Fraction& f) {
    if (f.get_down() == 1) {
        std::cout << f.get_up();
    }
    else {
        std::cout << f.get_up() << "/" << f.get_down();
    }
}

FractionUtil::Fraction FractionUtil::add(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.get_up() * f2.get_down() + f2.get_up() * f1.get_down();
    int new_down = f1.get_down() * f2.get_down();
    return Fraction(new_up, new_down);
}

FractionUtil::Fraction FractionUtil::sub(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.get_up() * f2.get_down() - f2.get_up() * f1.get_down();
    int new_down = f1.get_down() * f2.get_down();
    return Fraction(new_up, new_down);
}
FractionUtil::Fraction FractionUtil::mul(const Fraction& f1, const Fraction& f2) {
    int new_up = f1.get_up() * f2.get_up();
    int new_down = f1.get_down() * f2.get_down();
    return Fraction(new_up, new_down);
}
FractionUtil::Fraction FractionUtil::div(const Fraction& f1, const Fraction& f2) {
    if (f2.get_up() == 0) {
        std::cout << "分母不能为";
        return NULL;
    }
    int new_up = f1.get_up() * f2.get_down();
    int new_down = f1.get_down() * f2.get_up();
    return Fraction(new_up, new_down);
}
Fraction.cpp
#include "Fraction.h"
#include <iostream>
using namespace FractionUtil;

void test1();
void test2();

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::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 << "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;  
}
task4.cpp

运行结果:

屏幕截图 2025-10-26 153400

选择方案:命名空间+自由函数;

友元的缺点:破坏封装性,如果后续有更改的话修改起来很麻烦;

命名空间 + 自由函数的优势:方便添加新东西;感觉更简单;并且自由函数不依赖类的私有成员。

实验总结:

实验一就是在告诉我们关于友元函数的声明和调用的一些规则,注意即可。

实验二就是强调了什么情况是使用友元函数,本身实验难度不高。

实验三难度主要在将那个。字符串转化成统一大写或小写的字符串,我在这里用了 Transform函数,主要它可以便利的将字符串里的每个字符操作一遍,再通过tolower可以很快的将字符串统一小写,相较于去写循环操作要方便很多。至于其他的内容就是正常的字符匹配。

实验四需要考虑的内容就比较多,主要因为他是分数,在初始化的时候,单参数初始化还好,双参数初始化需要去考虑分母是不是为0,就需要多列一个情况。至于那个 negative,还有加减乘除这四个算法,为了减少代码行数,我就把计算得到的数字全部进行初始化,这样也可以不用考虑约分之类的,因为这些操作都已经在初始化函数里面。但写完我才发现可以把初始化里面的约分提出来,用一个单独的函数,这样还能再方便一点,不然要写两次约分。最后就是在除法中检验分母不能为0的时候,这边已经判断了分母不能为0的时候,我就让他输出这个提示,但是它会多输出来一个0,即使我用 return NuLL了,它依然多输出来一个0。不知道为什么,所以我只能把提示变成“分母不能为”这样结果就正常了。

posted @ 2025-10-26 16:03  yahuao  阅读(8)  评论(1)    收藏  举报