图源:文心一言
听课笔记简单整理,供小伙伴们参考~🥝🥝
- 第1版:听课的记录代码~🧩🧩
编辑:梅头脑🌸
审核:文心一言
目录
🐳类与对象
🐳函数
🐋4.2 默认构造函数
🐋4.5 类的组合程序
🐋4.6 前向引用
🐋4.7 结构体
🐋4.8 联合体
🐋4.9 枚举类
🐋4.10 组合类例题
🔚结语
🐳类与对象
- 郑莉老师的公开课:🌸C++语言程序设计基础 - 清华大学 - 学堂在线 (xuetangx.com)
🐳函数
🐋4.2 默认构造函数
🧩题目
构造钟表类。
📇算法思路
——
⌨️算法代码
#include <iostream>
using namespace std;
class Clock { // Clock类的定义
public: // 公有成员,外部接口
Clock(int newH, int newM, int newS); // 构造函数:接受3个参数newH、newM、newS,并分别赋给hour、minute、second
Clock(); // 默认构造函数:如果没有特别指定hour、minute和second的值,那么它们将默认被设置为0
void setTime(int newH = 0, int newM = 0, int newS = 0); // 设置时间:接受3个可选参数newH、newM、newS,并分别赋给hour、minute、second。如果没有提供参数,则使用默认值0。
void showTime(); // 显示时间:打印当前时钟的时间到控制台
private: // 私有成员,类内可见
int hour, minute, second;
};
// 构造函数
Clock::Clock(int newH, int newM, int newS) :
hour(newH), minute(newM), second(newS) {
}
// 默认构造函数
Clock::Clock():hour(0),minute(0),second(0){
}
// showTime函数
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
// showTime函数
void Clock::showTime() {
cout << hour << ":" << minute << ":" << second << endl;
}
int main()
{
Clock c1(8, 10, 0); // 调用构造函数,创建一个Clock对象c1,并设置时间为8:10:00
Clock c2; // 调用默认构造函数,创建一个Clock对象c2,使用默认时间0:0:0
c1.showTime(); // 显示 c1 的时间
c2.showTime(); // 显示 c2 的时间
c2.setTime(1, 1, 1); // 使用settime,重新设置c2的时间为1:1:1
c2.showTime(); // 显示 c2 的时间
return 0;
}
📇执行结果
📇相关概念
类(Class)
在面向对象编程中,类可以被看作是创建对象的模板或蓝图。它定义了对象应该具有的属性和可以执行的方法。
以上述代码为例,
Clock
就是一个类,它代表了时钟的概念。这个类定义了时钟应该具有的属性,如小时、分钟和秒。同时,它也定义了时钟可以进行的行为,比如设置时间和显示时间。当我们根据
Clock
类创建对象时,这些对象就会具有类所定义的属性和方法。在上述代码中,c1
和c2
就是根据Clock
类创建的具体对象。我们可以为这些对象设置具体的时间值,比如将c1
的时间设置为8小时10分0秒,或者不设置其值,让它默认为0小时0分0秒。通过这些对象,我们可以方便地操作和管理与时钟相关的数据和行为。
类的成员(Class Members)
类的成员包括数据成员(通常称为属性或字段)和函数成员(通常称为方法)。
- 数据成员(Data Members):
- 数据成员是类中存储数据的变量。在上述代码中,
Clock
类有三个私有数据成员:hour
、minute
和second
。这些数据成员用于存储时钟的小时、分钟和秒。- 由于它们是私有的(
private
),所以只能在Clock
类的内部被访问和修改。这有助于封装数据并确保数据的完整性。- 函数成员(Function Members):
- 函数成员是类中可以执行的操作或行为。在上述代码中,
Clock
类有四个公有函数成员:一个构造函数、一个默认构造函数、一个设置时间的方法和一个显示时间的方法。
public Clock(int newH, int newM, int newS);
// 构造函数,用于初始化时钟的时间public Clock();
// 默认构造函数,如果没有提供参数,则使用默认时间(0:0:0)初始化时钟public void setTime(int newH = 0, int newM = 0, int newS = 0);
// 设置时间的方法,允许用户更改时钟的时间。参数有默认值,因此可以部分或完全不提供参数。public void showTime();
// 显示时间的方法,将时钟的当前时间打印到控制台- 由于这些函数成员是公有的(
public
),所以它们可以在Clock
类的外部被访问和使用。这允许用户与类进行交互并操作类的数据成员。
🐋4.5 类的组合程序
🧩题目
构造线段类。
📇算法思路
根据小学课本定义,线段:直线上两个点和它们之间的部分叫做线段,这两个点叫做线段的端点。因此类中包含端点(point)与连接它们的直线(line)。
⌨️算法代码
#include <iostream>
#include <cmath>
using namespace std;
class Point { // 类Point的定义
public: // 公有成员,外部接口
Point(int newX, int newY) : x(newX), y(newY) {} // 构造函数:接受两个参数 newX、newY,分别赋值给 x、y
Point() : x(0), y(0) {} // 默认构造函数:如果没有特别指定x、y的值,那么它们将默认被设置为0
Point(const Point& p); // 复制构造函数:复制类 接受 原有类 的参数p.x、p.y,并分别赋值给 x、y
int getX() const { return x; } // 类成员函数:获得 x 的值
int getY() const { return y; } // 类成员函数:获得 y 的值
private: // 私有成员,类内可见
int x, y; // 点的 x、y 坐标
};
// 类Point 的复制构造函数
Point::Point(const Point& p) : x(p.x), y(p.y) {
cout << "Calling the copy constructor of Point" << endl;
}
class Line { // 类Line的定义
public:
Line(const Point& xp1, const Point& xp2); // 构造函数:接受两个类Point(点)xp1、xp2 对象作为常量引用
Line(const Line& l); // 复制构造函数:传递Line(线段)对象作为引用
double getLen() const { return len; } // 类成员函数:获得线段长度 len 的值
private: // 私有成员,类内可见
Point p1, p2; // 线段的端点 p1, p2
double len; // 线段的长度 len
};
// 类Line 的构造函数
Line::Line(const Point& xp1, const Point& xp2) : p1(xp1), p2(xp2) {
cout << "Calling constructor of Line" << endl;
double dx = static_cast<double>(p1.getX() - p2.getX());
double dy = static_cast<double>(p1.getY() - p2.getY());
len = sqrt(dx * dx + dy * dy);
}
// 类Line 的复制构造函数
Line::Line(const Line& l) : p1(l.p1), p2(l.p2), len(l.len) {
cout << "Calling the copy constructor of Line" << endl;
}
int main() {
Point myp1(1, 1), myp2(4, 5); // 调用Point构造函数,创造Point 对象myp1, myp2
Line line(myp1, myp2); // 调用Line 构造函数,创造Line 对象 line
Line line2(line); // 调用Line 构造函数,复制Line 对象 line1 至 line2
cout << "The length of the line is: "; // 调用Line 成员函数getLen,输出line线段长度
cout << line.getLen() << endl;
cout << "The length of the line2 is: "; // 调用Line 成员函数getLen,输出line2线段长度
cout << line2.getLen() << endl;
return 0;
}
📇执行结果
📇代码解释
在创建line1时,不仅调用了Line的构造函数,而且调用了2次point 的复制构造函数,也就是说,2个点对象mypoint1、mypoint2先复制到Line中,再计算长度。
- 备注:这一点,AI表示不理解,它认为:“
Line
的构造函数是通过传值方式接受这两个Point
对象的,但由于您使用的是常量引用(const Point& xp1, const Point& xp2
),所以实际上并没有复制这两个Point
对象,而是直接使用了它们的引用。因此,在这种情况下,Point
的复制构造函数不会被调用。”;- 但考虑到了 实际运行结果,本次确实是复制了2个点无疑,AI这次的推理是有误的。
在创建line2时,同样调用了2次point 的复制构造函数与1次line的复制构造函数。也就是在组合类复试或构造时,通常都会调用子对象的复制构造函数~
📇相关知识
类的组合
类的组合是面向对象编程中的一个重要概念,它指的是一个类中包含另一个类的对象作为其数据成员的情况。组合允许您将多个对象组合成一个更大的对象,以表示现实世界中的复杂实体。例如,如果您有一个表示汽车的类,您可能会将引擎、轮胎、座椅等其他类组合到汽车类中,以构建一个完整的汽车对象。
在类的组合中,一个类(称为组合类)通常包含对其他类(称为组件类)的对象的引用。这些组件类的对象可以是已经存在的对象,也可以在创建组合类对象时创建。
组合类的构造函数通常会接收组件类对象的引用或指针,并将其存储在组合类对象的成员变量中。这样,组合类对象就可以访问和使用这些组件类对象的功能和数据。
组合的好处是可以提高代码的模块化和可重用性。通过将功能分散到不同的类中,并将它们组合在一起,可以更容易地管理和维护代码。此外,通过使用不同的组合方式,可以创建出灵活多变的数据结构和行为。
需要注意的是,在组合中,组合类与组件类之间应该保持较低的耦合度,即它们之间的依赖关系应该尽可能简单和清晰。这样可以使得代码更加灵活、可扩展和可维护。同时,也需要注意避免循环依赖的问题,即两个类之间相互依赖导致无法正确工作的情况。
🐋4.6 前向引用
📇相关概念
前向引用(Forward Declaration)在C++中指的是在类的完整定义之前对其进行的声明。它告诉编译器这个类的名字,表明这个类将在程序的其他地方定义。前向引用的语法很简单,就是在类名前面加上“class”关键字(对于类来说)或者“struct”关键字(对于结构体来说)。
然而,前向引用只能用于指针或引用类型的成员,因为这两种类型不需要知道类的完整大小。如果你试图使用一个仅仅是前向声明的类作为值类型(比如直接作为类的成员变量),编译器将会报错,因为它不知道这个类的大小和布局。
// demo1:正确 class B; class A { public: void f(B, b); }; class B { public: void f(A, a); };
// demo2:错误 class Fred; // 前向引用声明 class Barney { Fred x; // 错误,类Fred的声明尚不完善 }; class Fred { Barney y; };
🐋4.7 结构体
🧩题目
创建结构体student,具有属性 num、name、sex、age。
📇算法思路
——
⌨️算法代码
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
// 结构体 student
struct Student {
int num;
string name;
char sex;
int age;
};
int main()
{
Student stu = { 97001, "Lin Lin", 'F', 19 };
cout << "Num:" << stu.num << endl;
cout << "Name:" << stu.name << endl;
cout << "Sex:" << stu.sex << endl;
cout << "Age:" << stu.age << endl;
return 0;
}
📇执行结果
📇相关概念
结构体
是C和C++等编程语言中用于表示一组不同类型数据的数据结构。它允许您将多个不同类型的数据项组合成一个单一的复合类型,以便作为一个单元进行处理。结构体通常用于表示现实世界中的实体或概念,这些实体或概念由多个相关的属性组成。
结构体与类的关系
在C语言中,结构体只是一种数据结构,不包含函数。但在C++中,结构体与类非常相似,实际上,它们几乎是完全相同的。唯一的区别是默认的访问级别:在结构体中,如果不指定访问修饰符,则成员默认为
public
;而在类中,如果不指定访问修饰符,则成员默认为private
。但这只是默认的行为,您可以在结构体中使用private
或protected
成员,也可以在类中使用public
成员。结构体与类的关系可以总结如下:
- 相似性:在C++中,结构体和类都可以包含数据成员和成员函数。它们都可以有构造函数、析构函数、访问修饰符和继承关系。因此,从功能上看,结构体和类几乎是完全相同的。
- 访问级别的默认差异:如上所述,结构体和类在成员访问级别上有默认的差异。但这并不意味着结构体不能用于封装或隐藏数据——您可以通过在结构体中使用访问修饰符来实现这一点。
- 历史与用法:从历史上看,结构体最初是为了表示简单的数据结构而引入的,而类则是为了支持面向对象编程而引入的。因此,在某些编程风格或约定中,人们可能会倾向于使用结构体来表示不包含复杂行为或仅用于数据聚合的类型,而使用类来表示具有更复杂行为或需要封装的类型。然而,这并不是一个严格的规则,只是一种编程习惯。
- 继承与多态:结构体和类在继承和多态方面的行为是相同的。它们都可以作为基类或派生类使用,并且都支持虚函数和多态性。
🐋4.8 联合体
🧩题目
以3种方式输入学生的成绩:等级、是否通过、分数。
📇算法思路
——
⌨️算法代码
#include <string>
#include <iostream>
using namespace std;
class ExamInfo{ // 类ExamInfo
private: // 私有成员
string name; // 科目
enum MODE{ GRADE, PASS, PERCENTAGE } mode; // 枚举类型,用于表示成绩
union Mark { // 联合体:用于存储实际的成绩
char grade; // 等级成绩(如'A', 'B', 'C'等)
bool pass; // 通过/未通过状态(true表示通过,false表示未通过)
int percent;// 百分比分数(0-100)
}mark;
public: // 公有成员
ExamInfo(string name, char grade):name(name), mode(GRADE){ mark.grade = grade; } // 构造函数,接受 科目,等级 为输入,并赋值为 name, mark.grade
ExamInfo(string name, bool pass) :name(name), mode(PASS){ mark.pass = pass; } // 构造函数,接受 科目,通过 为输入,并赋值为 name, mark.pass
ExamInfo(string name, int percent) :name(name), mode(PERCENTAGE){ mark.percent = percent; } // 构造函数,接受 科目,通过 为输入,并赋值为 name, mark.percent
void show(); // 成员函数,用于显示考试信息(根据成绩的表示方式输出相应的成绩信息)
};
// 类ExamInfo成员函数
void ExamInfo::show() {
cout << name << ":";
switch (mode) {
case GRADE: cout << mark.grade; break;
case PASS: cout << (mark.pass ? "PASS" : "FAIL"); break;
case PERCENTAGE: cout << mark.percent << "%"; break;
}
cout << endl;
}
int main()
{
ExamInfo course1("English", 'B'); // 调用构造函数,创建对象course1 科目英语, 等级B
ExamInfo course2("Calculus", true); // 调用构造函数,创建对象course2 科目微积分, 通过考试
ExamInfo course3("C++ Programming", 85); // 调用构造函数,创建对象course3 科目C++, 成绩85
course1.show(); // 调用类成员函数,显示course1
course2.show(); // 调用类成员函数,显示course2
course3.show(); // 调用类成员函数,显示course3
return 0;
}
📇执行结果
📇相关概念
联合体
在这段程序中,
union
(联合体)被用于Mark
的定义,它的作用是允许在同一块内存区域存储不同的数据类型。具体地说,Mark
联合体可以存储一个字符类型的等级成绩(grade
)、一个布尔类型的通过状态(pass
),或者一个整数类型的百分比分数(percent
)。但是,这三个成员变量共享同一块内存,所以它们不能同时被使用或赋值;在任何时候,只有最近一次被赋值的成员变量是有效的。
🐋4.9 枚举类
⌨️算法代码
“#include <iostream>
using namespace std;
enum class Side{Right, Left };
enum class Thing{Wrong, Right }; // 不冲突
int main()
{
Side s = Side::Right;
Thing w = Thing::Wrong;
// cout << (s == w) << endl; // 编译错误,无法直接比较不同枚举类
return 0;
}
📇代码说明
在这段代码中,定义了两个枚举类:
Side
和Thing
。枚举类(也称为“强类型枚举”)是 C++11 引入的新特性,它提供了一种更加严格和类型安全的枚举方式。
enum class Side{Right, Left };
定义了一个名为Side
的枚举类,它有两个枚举值:Right
和Left
。这些值表示某种“方向”或“侧面”。
enum class Thing{Wrong, Right };
定义了另一个名为Thing
的枚举类,它同样有两个枚举值:Wrong
和Right
。这些值可能表示某种“事物”的“正确”或“错误”状态。重要的是要注意,尽管
Side
和Thing
枚举类中都有一个名为Right
的枚举值,但它们分属于不同的枚举类,因此是不同的类型和值。在 C++ 中,不同枚举类的枚举值之间不能直接进行比较或运算,除非你显式地将它们转换为能够比较的类型(例如整数)。
🐋4.10 组合类例题
🧩题目
构造类compuper。
📇算法思路
——
⌨️算法代码
#include <iostream>
using namespace std;
enum CPU_RANK { P1 = 1, P2, P3, P4, P5, P6, P7 };
class CPU {
private:
CPU_RANK rank;
int frequency;
float voltage;
public:
// 构造函数
CPU(CPU_RANK r, int f, float v) : rank(r), frequency(f), voltage(v) {
cout << "构造了一个CPU!" << endl;
}
// 复制构造函数
CPU(const CPU& other) : rank(other.rank), frequency(other.frequency), voltage(other.voltage) {
cout << "复制构造了一个CPU!" << endl;
}
// 析构函数
~CPU() { cout << "析构了一个CPU!" << endl; }
// 外部接口函数
CPU_RANK GetRank() const { return rank; }
int GetFrequency() const { return frequency; }
float GetVoltage() const { return voltage; }
void SetRank(CPU_RANK r) { rank = r; }
void SetFrequency(int f) { frequency = f; }
void SetVoltage(float v) { voltage = v; }
void Run() { cout << "CPU开始运行!" << endl; }
void Stop() { cout << "CPU停止运行!" << endl; }
};
enum RAM_Type {DDR2 = 2, DDR3, DDR4};
class RAM {
private:
enum RAM_Type type;
unsigned int frequency;
unsigned int size;
public:
// 构造函数
RAM(RAM_Type t, unsigned int f, unsigned int s) : type(t), frequency(f), size(s) {
cout << "构造了一个RAM!" << endl;
}
// 复制构造函数
RAM(const RAM& other) : type(other.type), frequency(other.frequency), size(other.size) {
cout << "复制构造了一个RAM!" << endl;
}
// 析构函数
~RAM() { cout << "析构了一个RAM!" << endl; }
RAM_Type GetType() const { return type; }
unsigned int GetFrequency() const { return frequency; }
unsigned int GetSize() const { return size; }
void SetType(RAM_Type t) { type = t; }
void SetFrequency(int f) { frequency = f; }
void SetSize(int s) { size = s; }
void Run() { cout << "RAM开始运行!" << endl; }
void Stop() { cout << "RAM停止运行!" << endl; }
};
enum CDROM_Interface { SATA, USB };
enum CDROM_Install_type {external, built_in };
class CD_ROM {
private:
CDROM_Interface interface_type;
unsigned int cache_size;
CDROM_Install_type install_type;
public:
// 构造函数
CD_ROM(CDROM_Interface i, unsigned int s, CDROM_Install_type it) : interface_type(i), cache_size(s), install_type(it) {
cout << "构造了一个CD_ROM!" << endl;
}
// 复制构造函数
CD_ROM(const CD_ROM& other) : interface_type(other.interface_type), cache_size(other.cache_size), install_type(other.install_type) {
cout << "复制构造了一个CD_ROM!" << endl;
}
// 析构函数
~CD_ROM() { cout << "析构了一个CD_ROM!" << endl; }
CDROM_Interface GetInterfaceType() const { return interface_type; }
unsigned int Get_Size() const { return cache_size; }
CDROM_Install_type GetIntallType () const { return install_type; }
void SetInterfaceType(CDROM_Interface i) { interface_type = i; }
void SetSize(int s) { cache_size = s; }
void SetIntallType(CDROM_Install_type i) { install_type = i; }
void Run() { cout << "CD_ROM开始运行!" << endl; }
void Stop() { cout << "CD_ROM停止运行!" << endl; }
};
class COMPUTER {
private:
CPU my_cpu;
RAM my_ram;
CD_ROM my_cdrom;
unsigned int storage_size;
unsigned int bandwidth;
public:
// 构造函数
COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b);
// 复制构造函数
COMPUTER(const COMPUTER& other) : my_cpu(other.my_cpu), my_ram(other.my_ram), my_cdrom(other.my_cdrom), storage_size(other.storage_size), bandwidth(other.bandwidth) {
cout << "复制构造了一个COMPUTER!" << endl;
}
// 析构函数
~COMPUTER() { cout << "析构了一个COMPUTER!" << endl; }
void Run(){
my_cpu.Run();
my_ram.Run();
my_cdrom.Run();
cout << "COMPUTER开始运行!" << endl;
}
void Stop(){
my_cpu.Stop();
my_ram.Stop();
my_cdrom.Stop();
cout << "COMPUTER停止运行!" << endl;
}
};
// 构造函数
COMPUTER::COMPUTER(CPU c, RAM r, CD_ROM cd, unsigned int s, unsigned int b):my_cpu(c), my_ram(r), my_cdrom(cd), storage_size(s), bandwidth(b) {
cout << "构造了一个COMPUTER!" << endl;
}
int main()
{
CPU a(P6, 300, 2.8);
a.Run();
a.Stop();
cout << "*********************\n";
RAM b(DDR3, 1600, 8);
b.Run();
b.Stop();
cout << "*********************\n";
CD_ROM c(SATA, 2, built_in);
c.Run();
c.Stop();
cout << "*********************\n";
COMPUTER my_computer(a, b, c, 128, 10);
cout << "*********************\n";
my_computer.Run();
my_computer.Stop();
cout << "*********************\n";
return 0;
}
📇执行结果
📇代码解释
类的组合程序,构造类,复制构造类与4.5相似~~
在C++中,当我们创建包含其他类对象的复合类(如
Computer
类包含CPU
、RAM
和CD_ROM
类对象)时,会涉及到对象的构造和复制构造。以下是一个跟踪输出示例节选,展示了在构造Computer
对象时子对象的构造和析构过程:********************* 复制构造了一个CD_ROM! 复制构造了一个RAM! 复制构造了一个CPU! 复制构造了一个CPU! 复制构造了一个RAM! 复制构造了一个CD_ROM! 构造了一个COMPUTER! 析构了一个CPU! 析构了一个RAM! 析构了一个CD_ROM! *********************
从输出中可以看出,在构造
Computer
对象之前,首先对其子对象进行了复制构造(这可能是因为它们是通过值传递或作为临时对象创建的)。然后,执行了Computer
的构造函数。最后,当Computer
对象被销毁时,它的子对象也被相应地析构。话说,我本人没有看出来子对象构造2次的必要性(传参复制构造1次,调用复制构造1次),而且程序也确实使用了引用传值不应该出现重复构造的情况,暂时没有想明白这是为什么。AI回答:
- 需要注意的是,这个输出可能反映了特定的代码实现方式,其中子对象在传递给
Computer
构造函数之前被复制构造了一次,然后在Computer
内部又被构造了一次。这通常不是最佳实践,因为它可能导致不必要的对象复制和性能开销。- 理想情况下,我们应该通过引用或指针传递子对象,以避免不必要的复制。另外,如果子对象在
Computer
构造函数中被再次构造,那么可能存在设计上的问题或误解。通常,子对象应该在Computer
的构造函数中只被构造一次。
📇相关概念
析构函数
析构函数(Destructor)是C++中的一个特殊成员函数,它的主要作用是在对象生命周期结束时自动执行一些清理操作,比如释放对象在其生命周期中申请的资源(如动态分配的内存、打开的文件句柄等)。当对象的生命周期结束时,析构函数会被自动调用。
析构函数的特点如下:
- 析构函数的名称是在类名前加上一个波浪符(~)构成的。
- 析构函数没有返回值,也没有参数,因此不能重载。
- 析构函数可以是虚函数,当基类的指针指向派生类对象时,可以通过虚析构函数来释放派生类对象中的资源。
- 当程序中创建了一个对象(在栈上或堆上)时,析构函数会在对象生命周期结束时被自动调用。对于栈上的对象,当对象离开其作用域时,析构函数会被调用;对于堆上的对象,当使用delete操作符释放对象时,析构函数会被调用。
在析构函数中,通常会编写一些释放资源的代码,以确保对象在销毁时能够正确地清理其所占用的资源,从而避免内存泄漏等问题。例如,如果一个类在构造函数中动态分配了内存,那么在析构函数中就应该释放这块内存。
需要注意的是,析构函数并不负责删除对象本身,而是负责清理对象所占用的资源。对象的删除是由操作系统或内存管理器来负责的。在C++中,当对象的生命周期结束时,析构函数会被自动调用,然后对象的内存会被释放回操作系统或内存管理器中。
🔚结语
博文到此结束,写得模糊或者有误之处,期待小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶🌫️😶🌫️
我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟
同系列的博文:🌸数据结构_梅头脑_的博客-CSDN博客
同博主的博文:🌸随笔03 笔记整理-CSDN博客