普通函数的定义通常应该放在 .cpp 文件中,除非使用 inline 或 static 修饰符,否则在头文件中定义会导致多重定义错误。
实验任务二:
Complex.h:
#ifndef COMPLEX_H
#define COMPLEX_H
#include <string>
class Complex {
private:
double real; // 实部
double imag; // 虚部
public:
// 静态常量类属性
static const std::string doc;
// 构造函数
Complex(double r = 0.0, double i = 0.0); // 默认构造函数
Complex(const Complex& other); // 拷贝构造函数
// 成员函数
double get_real() const;
double get_imag() const;
void add(const Complex& other);
void output() const;
// 友元函数声明
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);
};
// 友元函数声明
double abs(const Complex& c);
Complex add(const Complex& c1, const Complex& c2);
bool is_equal(const Complex& c1, const Complex& c2);
bool is_not_equal(const Complex& c1, const Complex& c2);
#endif
Complex.cpp:
#include "Complex.h"
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std;
// 静态成员初始化
const string Complex::doc = "a simplified Complex class";
// 构造函数实现
Complex::Complex(double r, double i) : real(r), imag(i) {}
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 Complex::output() const {
cout << real;
if (imag >= 0) {
cout << " + " << imag << "i";
} else {
cout << " - " << -imag << "i";
}
}
// 友元函数实现
double abs(const Complex& c) {
return sqrt(c.real * c.real + c.imag * c.imag);
}
Complex add(const Complex& c1, const Complex& c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
bool is_equal(const Complex& c1, const Complex& c2) {
// 使用容差比较浮点数,避免精度问题
const double epsilon = 1e-10;
return (fabs(c1.real - c2.real) < epsilon) &&
(fabs(c1.imag - c2.imag) < epsilon);
}
bool is_not_equal(const Complex& c1, const Complex& c2) {
return !is_equal(c1, c2);
}
task2:
#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;
cout << "std::complex<double>对象测试: " << endl;
std::complex<double> c1;
std::complex<double> c2(3, -4);
std::complex<double> c3(c2);
std::complex<double> c4 = c2;
const std::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;
}
正确录入后,运行结果为:
![屏幕截图 2025-10-28 194328]()
![屏幕截图 2025-10-28 194351]()
问题一:
标准库模板类complex在使用形式上显著更简洁,它通过运算符重载实现了自然的数学表达式语法,如c1 + c2、c1 == c2等,完全符合数学直觉和编程习惯。而自定义类Complex需要使用函数调用形式add(c1,c2)is_equal(c1,c2),代码相对冗长且不够直观。两者在功能和运算逻辑上存在紧密的内在关联,所有函数都对应着相同的数学概念和操作语义,只是表达方式不同——标准库采用了更优雅的运算符形式,将数学运算直接映射为编程语法,大大提升了代码的可读性和易用性。
问题二:
2.1:在自定义Complex类中,将output、abs、add等函数设为友元并非必要。这些功能完全可以通过类的公有接口实现:output函数可以调用get_real()和get_imag()方法来获取数据并进行格式化输出;abs函数只需要实部和虚部的数值来计算模长,完全可以通过公有访问器获得所需数据;add函数作为非成员函数时,可以利用已有的构造函数和公有方法来创建新的复数对象。将这些函数设为友元虽然提供了直接访问私有数据的便利,但实质上破坏了类的封装性,是一种过度设计。
2.2:根据cppreference文档,标准库std::complex并没有将abs函数设为类的友元。std::abs是一个独立的模板函数,它通过std::complex提供的公有接口real()和imag()来获取复数的实部和虚部,然后进行计算。这种设计严格遵守了封装原则,保持了类的边界清晰,同时也证明了对于复数取模这样的操作,完全不需要友元机制就能优雅地实现。
2.3:考虑使用友元的情况应该严格限制在以下几种场景:当需要实现对称的运算符重载如operator==时,友元可以提供更好的语法对称性;当某个函数需要同时访问多个不同对象的私有数据且这种访问模式无法通过公有接口高效完成时;在性能极其关键的场景下,直接访问可能带来显著优化;或者当某些功能无法通过公有接口合理实现时。总体而言,友元是对封装性的有意识破坏,应当作为最后的选择而非首选方案,始终坚持最小权限原则,优先考虑通过设计良好的公有接口来满足功能需求。
问题三:将拷贝构造函数声明为 explicit 即可禁用 Complex c4 = c2 形式的拷贝初始化,只允许 Complex c4(c2) 形式的直接初始化。
实验任务三:
PlayerControl.h:
#pragma once
#include <string>
enum class ControlType {Play, Pause, Next, Prev, Stop, Unknown};
class PlayerControl {
public:
PlayerControl();
ControlType parse(const std::string& control_str); // 实现std::string --> ControlType转换
void execute(ControlType cmd) const; // 执行控制操作(以打印输出模拟)
static int get_cnt();
private:
static int total_cnt;
};
playerControl.cpp:
#include "PlayerControl.h"
#include <iostream>
#include <algorithm>
#include <cctype>
using namespace std;
int PlayerControl::total_cnt = 0;
PlayerControl::PlayerControl() {}
ControlType PlayerControl::parse(const std::string& control_str) {
// 1. 将输入字符串转为小写,实现大小写不敏感
string lower_str = control_str;
transform(lower_str.begin(), lower_str.end(), lower_str.begin(),
[](unsigned char c) { return tolower(c); });
ControlType result;
// 2. 匹配"play"/"pause"/"next"/"prev"/"stop"并返回对应枚举
if (lower_str == "play") {
result = ControlType::Play;
} else if (lower_str == "pause") {
result = ControlType::Pause;
} else if (lower_str == "next") {
result = ControlType::Next;
} else if (lower_str == "prev") {
result = ControlType::Prev;
} else if (lower_str == "stop") {
result = ControlType::Stop;
} else {
// 3. 未匹配的字符串返回ControlType::Unknown
result = ControlType::Unknown;
}
// 4. 每次成功调用parse时递增total_cnt(仅对有效命令计数)
if (result != ControlType::Unknown) {
total_cnt++;
}
return result;
}
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;
}
task3:
#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();
}
正确录入后,运行结果为:
![屏幕截图 2025-10-28 200258]()
实验任务4:
Fraction.h:
#ifndef FRACTION_H
#define FRACTION_H
#include <string>
class Fraction {
private:
int up; // 分子
int down; // 分母
// 内部工具函数:化简分数
void simplify();
public:
// 静态常量类属性
static const std::string doc;
// 构造函数
Fraction(int numerator = 0, int denominator = 1);
Fraction(const Fraction& other);
// 成员函数
int get_up() const;
int get_down() const;
Fraction negative() const;
// 输出函数(声明为友元以便直接访问私有成员)
friend 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);
// 输出函数声明
void output(const Fraction& f);
#endif
Fraction.cpp:
#include "Fraction.h"
#include <iostream>
#include <numeric>
#include <algorithm>
using namespace std;
// 静态成员初始化
const string Fraction::doc = "Fraction类 v 0.01版.\n\n目前仅支持分数对象的构造、输出、加/减/乘/除运算.";
// 内部工具函数:求最大公约数
int gcd(int a, int b) {
a = abs(a);
b = abs(b);
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
// 构造函数
Fraction::Fraction(int numerator, int denominator) : up(numerator), down(denominator) {
if (down == 0) {
down = 1; // 防止除零错误
}
simplify();
}
Fraction::Fraction(const Fraction& other) : up(other.up), down(other.down) {}
// 内部化简函数
void Fraction::simplify() {
if (down < 0) {
up = -up;
down = -down;
}
int common = gcd(up, down);
if (common != 0) {
up /= common;
down /= common;
}
// 处理分子为0的情况
if (up == 0) {
down = 1;
}
}
// 成员函数实现
int Fraction::get_up() const {
return up;
}
int Fraction::get_down() const {
return down;
}
Fraction Fraction::negative() const {
return Fraction(-up, down);
}
// 输出函数实现
void output(const Fraction& f) {
if (f.down == 1) {
cout << f.up;
} else {
cout << f.up << "/" << f.down;
}
}
// 工具函数实现
Fraction 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);
}
Fraction 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);
}
Fraction 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);
}
Fraction div(const Fraction& f1, const Fraction& f2) {
// 检查除数是否为0
if (f2.get_up() == 0) {
cout << "分母不能为0";
return Fraction(0, 1); // 返回一个默认分数
}
int new_up = f1.get_up() * f2.get_down();
int new_down = f1.get_down() * f2.get_up();
return Fraction(new_up, new_down);
}
task4:
#include "Fraction.h"
#include <iostream>
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类测试:" << 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;
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;
try {
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;
} catch (const std::exception& e) {
cout << e.what() << endl;
}
}
正确录入后,运行结果为:
![屏幕截图 2025-10-28 200933]()
问题:我选择的是混合方案:output函数采用友元实现,因为它需要直接访问私有数据来优化输出格式;而add/sub/mul/div等运算函数采用自由函数实现,因为它们完全可以通过公有接口get_up()和get_down()完成计算,这样既保持了良好的封装性又提供了清晰的自然语法。这种设计在封装性和实用性之间取得了平衡,既避免了过度使用友元破坏封装,又确保了关键的性能需求,同时运算函数作为自由函数更符合数学运算的直觉表达,比静态成员函数或命名空间方案更加简洁直观。