C++ 实验笔记 Lab1–Lab10
0 总览
- Lab1:随机数、格式化输出、文件读写、
getline
- Lab2:三种参数传递(值 / 指针 / 引用),以及格式化输出
- Lab3 & Lab10:模板类实现细节(
template<class T>、友元、显式实例化)
- Lab4:
assign(const String& other) 的 const 与引用
- Lab5:构造/拷贝/赋值、
static 成员初始化、const 成员初始化列表
- Lab6:继承:基类构造、初始化列表、调用基类成员函数
- Lab7:带默认参数的构造函数、
NULL/空串处理、友元运算符
- Lab8:
operator=、资源管理、(简化版)引用计数智能指针思路
- Lab9:虚函数 vs 纯虚函数(含
const 签名一致性)
1 Lab1:随机数、格式化输出与文件读写
1.1 随机数:srand 与 rand
常见用法:用当前时间作为随机种子(seed),避免每次运行得到相同序列。
1 2 3 4 5
| #include <cstdlib> #include <ctime>
srand(time(0)); int a = rand();
|
1.2 输出格式控制:<iomanip>
setprecision(2):设置浮点输出精度(常配合 fixed)
setw(2):设置最小输出宽度
setfill('0'):宽度不足时用指定字符补齐
1 2 3 4 5 6
| #include <iostream> #include <iomanip> using namespace std;
cout << fixed << setprecision(2) << 3.14159 << endl; cout << setw(2) << setfill('0') << 7 << endl;
|
1.3 文件输入输出:ifstream / ofstream
输入文件流(读):ifstream
输出文件流(写):ofstream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <fstream> #include <iostream> #include <string> using namespace std;
int main() { string filename = "../Pro2_rand/rand.cpp"; ifstream inFile(filename); if (!inFile.is_open()) { cerr << "无法打开文件: " << filename << endl; return 1; }
ofstream outFile("copy.cpp"); if (!outFile.is_open()) { cerr << "无法创建文件: copy.cpp" << endl; return 1; }
}
|
注意:ofstream("copy.cpp") 默认覆盖写,若文件已存在会清空后重写。
1.4 getline 逐行读取
getline(inFile, line):从 inFile 读一行(不包含换行符)到 line。
while(getline(...)):读成功继续;读到文件末尾或出错停止。
1 2 3 4
| string line; while (getline(inFile, line)) { cout << line << endl; }
|
2 Lab2:三种参数传递与 swap
2.1 值传递 / 指针传递 / 引用传递
对比三种交换函数(核心是:能否修改实参):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void swap1(int a, int b) { int t = a; a = b; b = t; }
void swap2(int* a, int* b) { int t = *a; *a = *b; *b = t; }
void swap3(int& a, int& b) { int t = a; a = b; b = t; }
|
2.2 时间/数字格式化输出(补 0)
1 2 3
| #include <iomanip>
cout << setw(2) << setfill('0') << hour << endl;
|
3 Lab3 & Lab10:模板类实现要点(Array)
重点:模板类/模板函数的定义通常要让编译器“看得到”,因此常放在头文件;如果拆到 .cpp,就要做显式实例化。
3.1 在 .cpp 写模板成员:每个定义都要带 template<class T>
1 2 3 4 5 6 7 8
| template<class T> Array<T>::Array(int size) : size(size) { if (size > 0) arr = new T[size]; else { this->size = 0; arr = nullptr; } }
|
3.2 友元运算符声明也要是模板
关键点:友元声明前也要加模板参数。
1 2 3 4 5 6
| template<class T> class Array { template<class U> friend std::ostream& operator<<(std::ostream& out, const Array<U>& rhs); };
|
3.3 类型转换运算符:operator T*()
当你想把“动态数组对象”转成原生指针时,可写转换运算符(但要谨慎使用,避免隐式转换带来歧义)。
1 2 3 4 5 6 7 8
| template<class T> class Array { public: operator T*() { return arr; } operator const T*() const { return arr; } private: T* arr = nullptr; };
|
3.4 operator+:先算新大小,再构造结果数组
1 2 3 4 5 6 7
| template<class T> Array<T> Array<T>::operator+(const Array& rhs) const { int new_size = this->size + rhs.size; Array<T> result(new_size); return result; }
|
3.5 显式实例化(写在 .cpp 末尾)
当模板定义放在 .cpp,你必须告诉编译器“我要生成哪些类型”。
1 2 3 4 5
| template class Array<int>; template class Array<int*>;
template std::ostream& operator<<(std::ostream& out, const Array<int>& rhs);
|
4 Lab4:assign(const String& other) 的 const 与引用
核心就是:用引用避免拷贝,用 const 保证不修改参数。
1
| void assign(const String& other);
|
5 Lab5:构造/拷贝/赋值、static 与 const 初始化
5.1 三个常见构造函数
1 2 3 4 5 6
| class String { public: String(); String(const char* s); String(const String& other); };
|
5.2 static 成员初始化要在类外
1 2 3 4 5 6
| class MyClass { public: static int number; };
int MyClass::number = 0;
|
5.3 const 成员必须用初始化列表
1 2 3 4 5 6
| class MyClass { public: MyClass() : pi(3.14) {} private: const double pi; };
|
operator= 是对已有对象赋新值,不会走构造;如果你用 static 计数对象数量,要分清 构造/析构 才是对象生命周期变化点。
6 Lab6:继承与初始化列表(先构造基类,再构造派生类)
6.1 派生类构造函数:用 : 调基类构造
1 2 3 4 5 6 7 8 9 10 11 12
| class cemployee : public cperson { public: cemployee(const string& name, const string& id, int sex, int age, const string& department, float salary); private: string department; float salary; };
cemployee::cemployee(const string& name, const string& id, int sex, int age, const string& department, float salary) : cperson(name, id, sex, age), department(department), salary(salary) {}
|
6.2 调用基类成员函数:Base::func()
1 2 3 4
| void cemployee::print() const { cperson::print(); cout << department << " " << salary << endl; }
|
7 Lab7:默认参数构造与友元运算符
7.1 MyString(const char* str = NULL):NULL 代表“空指针”,不是“空串”
典型写法:如果传入 NULL,就构造一个空字符串 ""。
1 2 3 4 5 6 7 8 9
| MyString::MyString(const char* str) { if (str == NULL) { m_data = new char[1]; m_data[0] = '\0'; } else { m_data = new char[strlen(str) + 1]; strcpy(m_data, str); } }
|
7.2 “基本类型 + 类” 运算:用友元更方便
当出现 double + complexd 这种左操作数不是类对象的情况,成员函数做不到(因为左边不是 this),通常用友元/非成员函数:
1
| friend complexd operator+(double h, const complexd& rhs);
|
8 Lab8:operator= 与资源管理(含引用计数思路)
8.1 先说清楚三种语义
关键点:把指针直接转移走并把右边置空,更像移动赋值。但如果签名写成 share& operator=(share&),语义会混乱。更稳妥的方向是:
- 深拷贝:两个对象各自拥有独立资源
- 共享资源:引用计数(见下一节)
- 移动语义:应使用
share& operator=(share&&)(C++11 以后)
8.2 引用计数(简化版 shared_ptr 思路)
下面是一个能覆盖你笔记核心点的“引用计数版本”框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class share { public: share() : point(nullptr), count(new int(1)) {} explicit share(int* p) : point(p), count(new int(1)) {}
share(const share& other) : point(other.point), count(other.count) { ++(*count); }
share& operator=(const share& other) { if (this == &other) return *this;
release();
point = other.point; count = other.count; ++(*count); return *this; }
~share() { release(); }
private: void release() { if (count && --(*count) == 0) { delete point; delete count; point = nullptr; count = nullptr; } }
int* point; int* count; };
|
9 Lab9:虚函数与纯虚函数
9.1 纯虚函数:派生类必须实现
1 2 3 4
| class Base { public: virtual void print() const = 0; };
|
- 如果基类里写了
virtual ... const = 0
那么派生类实现时签名必须一致(包括 const)。
9.2 普通虚函数:可以选择重写
普通 virtual 不强制派生类实现;不重写就用基类版本。
10 小结
高频点主要就三类:
- 资源管理:构造/拷贝/赋值/析构要配套(避免泄漏、重复释放)
- 继承多态:虚函数签名一致、初始化列表构造基类、需要时调用
Base::func()
- 模板编译模型:模板定义可见性(头文件 or 显式实例化)