小雨淅沥 - 代码基础
type: channel
link: https://rainerseventeen.cn/index.php/category/Code-Basic/
description: 写点代码相关的基本用法，以及一些数据结构等内容
items: PyTorch 笔记
items: OOP课程笔记：第九章 标准库容器和算法
items: OOP课程笔记：第八章 模板与泛型编程
items: OOP课程笔记：第七章 输入输出流类库
items: OOP课程笔记：第六章 运算符重载
items: OOP课程笔记：第五章 虚函数和多态性
items: OOP课程笔记：第四章 类的其他特性
items: OOP课程笔记：第三章 继承和派生类
items: OOP课程笔记：第二章 构造函数与析构函数
items: OOP课程笔记：第一章 类和对象

PyTorch 笔记
type: item
date: 2025-11-22T14:48:00+08:00
description: PyTorch 笔记本文章归类于 代码基础 类别中，因为 PyTorch 实际上是个代码框架，并不是DL理论内容本文章中的相关代码已经公开在 GitHub 上，详情参考链接： GitHub 仓库1 Dataset获取数据集和对应的 label有以下主要功能：如何获取每一个数据和对应的 label如何获取到数据集的总量大小常用数据集格式是：一个文件夹放置所有的图像，另外将不同的图像的 label 写在对应的 txt 文件中1.1 Override1.1.1 __init__()一般放全局变量，包括路径等内容，这里使用 hymenoptera_data 数据集作为例子hymenoptera_data/ └── train/ ├── ants/ │ ├── xxx1.jpg │ ├── xxx2.png │ └── ... └── bees/ ├── xxx1.jpg ├── xxx2.png └── ...from torch.utils.data import Dataset def __init__(self, root: str, label: str): """数据的初始化工作 Args: root (str): 数据的根路径 label (str): 某一个子文件夹的名称，也就是label """ # 这里是根据不同的数据集来更改的 # 以 hymenoptera_data 数据作为例子， label 下存放了所有的数据图片 self.root_dir = root # root 路径 self.label_dir = label # 标签信息 self.img_path = os.path.join(self.root_dir, self.label_dir) # root 路径地址拼接 self.img_list = os.listdir(self.img_path)1.1.2 __getitem__()获取某一个数据的具体信息def __getitem__(self, index: int): """获取某一个数据的具体信息 Args: index (int): 数据索引号 Returns: img (ndarray, RGB), label (str) """ img_path = os.path.join(self.img_path, self.img_list[index]) img = cv2.imread(img_path) # OpenCV 默认 BGR # 读取失败检查 if img is None: raise FileNotFoundError(f"Failed to read image: {img_path}") # 转成 RGB img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) label = self.label_dir1.1.3 使用实例if __name__ == '__main__': data_root = '/Users/rainer/MyFiles/Code/Learning/Pytorch/dataset/hymenoptera_data/train' ant_data = Hymenoptera(root=data_root, label='ants') bee_data = Hymenoptera(root=data_root, label='bees') train_dataset = ant_data + bee_data print(len(ant_data), len(bee_data)) print(len(train_dataset)) test_img, test_label = ant_data[3] # 简写，等同于 __getitem__ # OpenCV 显示需要 BGR，所以转回来 img_bgr = cv2.cvtColor(test_img, cv2.COLOR_RGB2BGR) cv2.imshow(test_label, img_bgr) cv2.waitKey(0) cv2.destroyAllWindows()2 Dataloader为网络的输入提供不同的数据形式Dataloader 一般不需要自己重写，只需要了解每一个参数的作用即可from torch.utils.data import DataLoader test_loader = DataLoader( test_data, batch_size=4, # 每次读取数据数量 shuffle=True, # 是否每次打乱顺序 num_workers=0, # 子进程个数 drop_last=False # 丢弃多余数据 )3 Tensorboa…

OOP课程笔记：第九章 标准库容器和算法
type: item
date: 2025-10-18T18:09:04+08:00
description: 第九章 标准库容器和算法C++ 标准模板库（ STL ）的核心是 容器、算法和迭代器用来实现常用算法和数据结构容器：将多个对象存储在单个容器对象 中，并对它们进行操作迭代器：对应用在容器或其他序列上的 指针的抽象算法：也称为标准算法或泛型算法，通过迭代器作用于容器，是实现常用算法的 函数模板1.容器概览容器类分类容器类描述头文件顺序容器vector可变大小数组<vector> deque双端队列<deque> list双向链表 array固定大小数组<array> forward_list单向链表<forward_list>关联容器map键/值对，每个键只能关联一个值 multimap键/值对，每个键可以关联多个值 set集合，每个元素都是唯一的<set> multiset集合，元素可以不唯一<set>无序关联容器unordered_map用哈希函数组织的 map<unordered_map> unordered_multimap用哈希函数组织的 multimap<unordered_map> unordered_set用哈希函数组织的 set<unordered_set> unordered_multiset用哈希函数组织的 multiset<unordered_set>容器适配器stack栈<stack> queue队列<queue> priority_queue优先级队列<queue>顺序容器： 依次存放和使用容器中的每个元素关联容器：允许 基于键快速检索容器中元素的值迭代器：指向容器和其他序列的指针的抽象2.顺序容器不知道用什么就用<vector>所有顺序容器都提供了 快速顺序 访问元素的能力在 添加 和 删除元素 以及 非顺序随机访问元素 方面有不同的性能考虑顺序容器存储结构访问元素插入/删除元素独特性vector可变大小数组快速随机访问尾部快速连续存储array固定大小数组快速随机访问不支持固定大小deque双端队列快速随机访问头尾位置快速 list双向链表双向顺序任意位置快速有额外空间开销forward_list单向链表单向顺序任意位置快速不支持 size 操作如果要求 随机访问 元素，使用 vector 或 deque 如果要求在容器中间插入 或 删除 元素，使用 list 或 forward_list如果需要在 头尾位置插入 或 删除 元素，但不会在中间位置插入或删除，使用 deque定义和初始化容器类都定义了 默认构造函数 和 拷贝构造函数标准库 array 类型 不支持普通的容器构造函数array 的大小也是类型的一部分，当 定义一个 array 时，要 指定元素类型和容器大小//适用于所有容器的定义方式 vector<string> svec; //创建一个存放 string 对象的空容器 vector<string> svec1(svec); //创建 svec 的副本 //下面两种方式仅适用于顺序容器 vector<string> svec2(10,”hi”); //创建大小为 10 的容器，用 " 初始化每个元素 vector<string> svec3(10); //创建大小为 10 的容器，每个元素是空串 //定义 array 时要指定类型和大小 array<int, 3> a1; //3个 int 元素，默认初始化：全局 0 ，局部自动随机值 array<int, 4> a2 = {1,2}; //1 2 0 0 array<string, 3> a3 = {"a","bb","ccc"}; //a bb ccc赋值与交换赋值运算符 将左操作数容器中的全部元素替换为右操作数容器中元素的副本赋值运算符要求 左右操作数的类型相同 assign 操作 用参数所指定的元素的副本替换左边容器的所有元素assign 允许 从一个不同但相容的类型赋值，或者从容器的 一个子序列赋值。仅顺序容器支持 assign 操作 。 array 允许赋值，但不支持assign ，也不允许用花括号包围的值列表进行赋值。//赋值相关的运算会导致指向左边容器内部的迭代器、引用和指针失效 //赋值运算符 c1 = c2; //c1 的内容替换为 c2 中元素的副本 c1 = {1, 2, 3}; //赋值后 c1 大小为 3 //seq.assign(b, e); list<string> names; vector<const char*> cstyle; names = cstyle; //错误：容器类型不匹配 names.assign(cstyle.begin(), cstyle.end()); //正确：元素之间可以类型转换 //seq.assign(n,t) list<string> slist1(1); //1 个…

OOP课程笔记：第八章 模板与泛型编程
type: item
date: 2025-10-18T18:08:00+08:00
description: 第八章 模板与泛型编程1.简介函数模板提供了一种描述通用算法的机制将算法处理的 数据类型参数化 ，从而既可以保留函数定义和函数调用的语义，又不必绕过 C++ 的类型检查程序员 将函数接口（参数表和返回类型）中的全部或者部分类型参数化 ，而函数体保持不变 。如果一个 函数的实现对一组实例都是相同的 ，区别仅在于每个实例处理不同的数据类型 ，那么该函数就可以定义为函数模板模板的定义方式template <typename T>2.应用用函数模板实现通用算法将具体算法实现中的 数据类型参数化 ，用 模板类型参数 代替，保留函数中与类型无关的代码，就可以得到函数模板的定义// 模板函数定义（修正原图中的语法错误） template <class Type> Type min(Type array[], int size) { Type min_val = array[0]; // 初始化最小值为首元素 for (int ix = 1; ix < size; ++ix) // 从第二个元素开始遍历 if (array[ix] < min_val) min_val = array[ix]; // 发现更小值时更新 return min_val; // 返回最小值 }使用模板非类型参数的算法实现模板类型参数 Type 在模板定义中作为类型标识符使用，表示数组的类型模板非类型参数 size 作为常量使用，表示数组的大小// 函数模板：返回定长数组的最小值（通过const引用传递数组） template <class Type, int size> Type min2(const Type (&array)[size]) { // 想传递数组的const引用 Type min_val = array[0]; // 初始化最小值为首元素 for (int ix = 1; ix < size; ++ix) // 遍历数组 if (array[ix] < min_val) min_val = array[ix]; // 更新最小值 return min_val; }3.函数模板的实例化函数模板本身并不是真正的函数定义，因为其中的类型信息不完整函数模板被调用时编译器首先对其进行实例化 ，生成真正整的函数定义实例，然后再 实施对函数实例的调用根据每次调用模板函数的不同实参，一个函数模板可以实例化得到多个不同的函数定义当调用一个函数模板时，编译器用 函数实参 推演 模板实参模板实例化在函数模板被调用或取地址时隐式发生// 版本1：接收数组指针和大小参数 template<class Type> Type min(Type array[], int size) { Type min_val = array[0]; for (int i = 1; i < size; ++i) if (array[i] < min_val) min_val = array[i]; return min_val; } // 版本2：接收数组的常量引用（编译期确定大小） template<class Type, int size> Type min2(const Type (&array)[size]) { Type min_val = array[0]; for (int i = 1; i < size; ++i) if (array[i] < min_val) min_val = array[i]; return min_val; }例如，当调用模板 min 时，编译器会用一个具体类型替换 Type调用模板 min2 时会用一个 具体类型 和一个 常量值 分别替换 Type 和 size在使用函数模板时，必须能够通过上下文为每一个模板实参确定 一个唯一的类型或值 ，否则会产生编译错误// 三类型参数的函数模板 template <class T1, class T2, class T3> T1 funcion(T2 arg1, T3 arg2) { /*...*/ } // 单类型参数的函数模板 template <class T> void goo(T a, T b) { /*...*/ } int main() { int x, y; double d; x = funcion(y, d); // 正确用法：x = funcion<int>(y, d); goo(x, d); // 正确用法：goo<double>(x, d); }显式指定模板实参在函数模板实例化时，函数名后面用尖括号<>指定模板实参函数实参可以应用隐式类型转换，转换成相应的函数参数显式模板实参应该只用在解决二义性时，或用在模板实参不能被推演出来的情况下// 三元模板函数定义 template <class…

OOP课程笔记：第七章 输入输出流类库
type: item
date: 2025-10-18T18:07:42+08:00
description: 第七章 输入输出流类库1.简介C++语言的 I/O 系统向用户提供一个统一的接口，使得程序的设计尽量与所访问的具体设备无关，在用户与设备之间提供了一个抽象的界面：输入输出流用标准流进行输入输出时，系统自动地完成数据类型的转换。 对于输入流，要将输入的字符序列形式的数据变换成计算机内部形式的数据（二进制或 ASCII）后，再赋给变量变换后的格式由变量的类型确定。对于输出流，将要输出的数据变换成字符串形式后，送到输出流（文件）中。2.重载输入输出运算符自定义的对象不能直接输入输出，需要对运算符 << >> 进行重载重载输入运算符在对象所在的类中 将重载这二个运算符的函数说明该类的 友元函数 。friend istream & operater >>(istream&, ClassName& ); // 在类内的声明 istream & operater >>(istream &is , A &a) // 在类外的声明 cin >> a; // 调用1 operator >> (cin,a) // 调用2，效果同上 其中 istream 作为返回值类型， operator作为函数名istream&作为左操作数，ClassName&作为右操作数返回值类型：类 istream 的引用， cin 中可以连续使用运算符cin >> a >> b;class incount { int c1, c2; public: incount(int a = 0, int b = 0) { c1 = a; c2 = b; } void show(void) { cout << "c1=" << c1 << "\t" << "c2=" << c2 << endl; } friend istream& operator>>(istream&, incount&);// 重载输入函数声明 }; istream& operator>>(istream& is, incount& cc) { is >> cc.c1 >> cc.c2; return is; } void main(void) { incount x1, x2; x1.show(); x2.show(); cin >> x1; cin >> x2; x1.show(); x2.show(); }重载输出运算符#include <iostream> using namespace std; class incount { int c1, c2; public: incount(int a = 0, int b = 0) { c1 = a; c2 = b; } void show(void) { cout << "c1=" << c1 << "\t" << "c2=" << c2 << endl; } friend istream& operator>>(istream&, incount&); friend ostream& operator<<(ostream&, incount&); }; // 重载输入函数定义 istream& operator>>(istream& is, incount& cc) { is >> cc.c1 >> cc.c2; return is; } // 重载输出函数定义 ostream& operator<<(ostream& os, incount& cc) { os << "c1=" << cc.c1 << "\t" << "c2=" << cc.c2 << endl; return os; } int main() { incount x1, x2; cout << x1 << x2; // 调用输出函数 cin >> x1; // 调用输入函数 cin >> x2; cout << x1 << x2; return 0; }3.文件流C++在头文件 fstream.h 中定义了 C++ 的文件流类体系文件分为使用 ASCII 和 二进制读取 两种方式文本文件的打开与关闭在文件操作前，需要将程序与被操作的文件联系起来使程序可以引用文件在程序内定义一个文件类的对象，由该对象与文件发生联系，程序内所有的与文件的操作都是对该对象的操作 。fstream infile ,outfile; // 两个对象，可以联系两个输入输出文件 ifstream infile; // 输入 ofstream outfile; // 输出ifstream infile； infile.open(“myfile1.txt”)打开文件 myfile1.txt 用于输入，并将这个文件与输入文件类对象 infile 建立联系在程序中，用到这个文件 myfile1.txt 的地方就用 infil…

OOP课程笔记：第六章 运算符重载
type: item
date: 2025-10-18T18:07:00+08:00
description: 第六章 运算符重载1.函数的重载所谓函数的重载是指完成不同功能的函数可以具有相同的函数名 C++的编译器是根据 函数的实参 来确定应该调用哪一个函数，仅返回值不同时，不能定义为重载函数2.运算符的重载重载的概念在下例中，编译系统中的运算符本身不能做这种运算若使下式可以运算，必须重新定义运算符，这种重新定义的过程成为运算符的重载。char str[4], c1[2]="a", c2[2]="b"; str= c1 + c2;class A{ float x, y; public: A(float a = 0, float b = 0){ x = a; y = b; } }; void main(void){ A a(2, 3), b(3, 4), c; c = a + b; // 两对象不能使用＋，必须重新定义＋ }运算符重载就是赋予已有的运算符多重含义C++ 通过重新定义运算符，使它能够用于特定类的对象执行特定的功能多态性的体现运算符的重载从另一个方面体现了OOP 技术的多态性， 且同一运算符根据不同的运算对象可以完成不同的操作。为了重载运算符，必须定义一个函数，并告诉编译器， 遇到这个 重载运算符 就调用该函数，由这个函数来完成该运算符应该完成的操作。 这种函数称为运算符重载函数，它通常是 类的成员函数或者是 友元函数。 运算符的操作数通常也应该是类的对象。ClassName operator <operator> (<args>){;} A operator + (A&); //重载了类 A 的 + 运算符operator 是定义运算符重载函数的关键字，与其后的运算符一起构成函数名。#include <iostream> using namespace std; class A{ int i; public: A(int a=0) { i = a; } void Show(void) { cout << "i=" << i << endl; } void AddA(A &a, A &b) // 利用函数进行类之间的运算 { i = a.i + b.i; } A operator+(A &a) // 重载运算符+ { A t; t.i = i + a.i; return t; } }; int main(){ A a1(10), a2(20), a3; a1.Show(); a2.Show(); a3 = a1 + a2; // 重新解释了加法，可以直接进行类的运算 a3.AddA(a1, a2); // 调用专门的功能函数 a3.Show(); return 0; }重载运算符限制重载运算符只能是两种参数情况：没有参数或带有一个参数对于只有一个操作数的运算符 如++ 在重载这种运算符时通常不能有参数；而对于有二个操作数的运算符只能带有一个参数 。 这参数可以是对象对象的引用 或其它类型的参数 。 在 C++ 中不允许重载有三个操作数的运算符只能对 C++ 中已定义了的运算符进行重载，当重载一个运算符时该运算符的优先级和结合律是不能改变的不能进行重载的运算符有6个，见下表名称符号成员访问运算符.成员指针访问运算符.* 和 ->*作用域运算符::大小运算符sizeof三目运算符? :3.前置后置运算符重载只具有一个操作数的运算符为单目运算符，最常用的为 ++ 及 --可以看出，虽然运算后对象的值一致，但先自加或后自加的重载运算符函数的返回值不一致， 必须在重载时予以区分<type> operator ++() {;} // 前置运算 <type> operator ++(int) {;} // 后置运算下面是一段可以直接运行的代码注意返回 *this 会返回一个对象的副本，并非原来的对象根据 return 返回的值可以区分前置和后置运算符#include <iostream> using namespace std; class incount { int c1, c2; public: incount(int a = 0, int b = 0) { c1 = a; c2 = b; } void Show(void) { cout << "c1=" << c1 << '\t' << "c2=" << c2 << endl; } incount operator++() { c1++; c2++; return *this; // 返回拷贝的副本，并非本身 } // 前置 incount operator++(int) // 后置 { incount c; c.c1 = c1; c.c2 = c2; c1++; c2++; return c; } }; int main() { incount ic1(10, 20), ic2(…

OOP课程笔记：第五章 虚函数和多态性
type: item
date: 2025-10-18T18:06:22+08:00
description: 第五章 虚函数和多态性多态性：调用同一个函数名，可以根据需要但实现不同的功能。编译时的多态性是函数重载，运行时的多态性使用虚函数实现运行时的多态性是指在程序执行之前，根据函数名和参数无法确定应该调用哪一个函数，必须在程序的执行过程中，根据具体的执行情况来动态地确定1.基类指针访问基类函数可以将一个派生类对象的地址赋给基类的指针变量basep 只能引用从基类继承来的成员。Base *basep; basep = &b // 基类地址 basep = &d; // 派生类地址下例中，使用指针调用的是基类中有的公有函数class Point{ float x, y; public: Point() {} Point(float i, float j){ x = i; y = j; } float area(void) {return 0.0;} }; const float Pi = 3.14159; class Circle : public Point{ // 类 Point 的派生类 float radius; public: Circle(float r){radius = r;} float area(void) {return Pi * radius * radius;} }; void main(void){ Point *pp; // 基类指针，可以将派生类对象的地址赋给基类指针 Circle c(5.4321); pp = &c; cout << pp->area() << // 调用的是基类中有的公有函数 }2.基类指针访问派生类函数若要访问派生类中相同名字的函数，必须将基类中的同名函数定义为虚函数 将不同的派生类对象的地址赋给基类的指针变量后，就可以动态地根据这种赋值语句调用不同类中的函数 。实现动态联编方式的前提：1.先要声明虚函数2.类之间满足赋值兼容规则3.通过指针与引用来调用虚函数将 area() 声明为虚函数，编译器对其进行动态联编，按照实际对象 c 调用了 Circle 中的函数 area() 。使 Point 类中的 area() 与 Circle 类中的 area() 有一个统一的接口。class Point{ float x, y; public: Point() {} Point(float i, float j){ x = i; y = j; } virtual float area(void) {return 0.0;} // 首先声明虚函数 }; const float Pi = 3.14159; class Circle : public Point // 类 Point 的派生类 { float radius; public: Circle(float r){radius = r;} float area(void) {return Pi * radius * radius;} // 虚函数再定义 }; void main(void){ Point *pp; Circle c(5.4321); pp = &c; cout << pp->area() << // 调用的是派生类中的函数 }一旦把基类的成员函数定义为虚函数，由基类所派生出来的所有派生类中，该函数均保持虚函数的特性在派生类中重新定义基类中的虚函数时，可不用关键字 virtual 来修饰这个成员函数注意：1.当在基类中把成员函数定义为虚函数后，在其派生类中定义的虚函数必须与基类中的虚函数同名 参数的类型 、顺序 、参数的个数必须一一对应，函数的返回的类型也相同 ，若函数名相同，但参数的个数不同或者参数的类型不同时，则属于函数的隐藏而不是虚函数 2.实现这种动态的多态性时，必须使用基类类型的指针变量 ，并使该指针指向不同的派生类对象并通过调用指针所指向的虚函数才能实现动态的多态性。3.虚函数必须是类的一个成员函数，不能是友元函数，也不能是静态的成员函数。4.在派生类中 没有重新定义虚函数时，与一般的成员函数一样，当调用这种派生类对象的虚函数时， 则调用其基类中的虚函数 。5.可把析构函数定义为虚函数，不能将构造函数定义为虚函数6.虚函数与一般的成员函数相比较调用时的执行速度要慢一些为了实现多态性，在每一个派生类中均要保存相应虚函数的入口地址表，函数的调用机制也是间接实现的因此除了要编写一些通用的程序 并一定要使用虚函数才能完成其功能要求外，通常不必使用虚函数7.一个函数如果被定义成虚函数，则不管经历多少次派生，仍将保持其虚特性，以实现“一个接口，多个形态”。关键字: overrideC++11的关键字 override 用来标记派生类中的 虚函数 ，使程序员覆盖基类虚函数的意图更清晰如果覆盖时不小心写错了形参列表，编译器会报…

OOP课程笔记：第四章 类的其他特性
type: item
date: 2025-10-18T18:05:32+08:00
description: 第四章 类的其他特性1.友元函数友元函数是一种定义在类外部的普通函数，能够访问类中私有成员和保护成员。友元函数需要在类体内进行说明，在前面加上关键字 friendfriend <type> FuncName(<args>);友元函数不是成员函数，用法也与普通的函数完全一致友元函数破坏了类的封装性和隐蔽性，使得非成员函数可以访问类的私有成员class A{ float x, y; public: A(float a, float b){ x = a; y = b; } float Sum() { return x + y; } friend float Sum(A &a) { return a.x + a.y; } // 必须使用类名引用来访问，因为友元函数不是类成员 }; void main(void){ A t1(4, 5), t2(10, 20); cout << t1.Sum() << endl; cout << Sum(t2) << endl; }类与类的沟通大多数情况是友元函数是某个类的成员函数，即A类中的某个成员函数是B类中的友元函数，这个成员函数可以直接访问B类中的私有数据。这就实现了类与类之间的沟通。下例中，在 B 中声明友元，在 A 中声明成员， 在类外定义函数 A::fun()class B; // 先定义类A，则首先对类B作引用性说明 class A{ // 类A的成员定义 public: void fun(B &); // 函数的原型说明 }; class B{ friend void A::fun(B &); // 定义友元函数 } void A::fun(B &b) {} // 函数的完整定义2.友元类可以在一个类中声明另一个类是其的友元类const float PI = 3.1415926; class A{ float r; float h; public: A(float a, float b){ r = a; h = b; } float Getr() { return r; } float Geth() { return h; } friend class B; // 定义类B为类A的友元 }; class B{ int number; public: B(int n = 1) { number = n; } void Show(A &a){ // 需要使用A的对象名来访问 cout << PI * a.r * a.r * a.h * number << endl; } // 求类A的某个对象*n的体积 }; void main(void){ A a1(25, 40), a2(10, 40); B b1(2); b1.Show(a1); b1.Show(a2); }3.派生类访问基类私有成员不管是按哪一种方式派生，基类的私有成员在派生类中都是不可见的。如果在一个派生类中要访问基类中的私有成员，可以将这个派生类声明为基类的友元。class M{ friend class N; // N为M的友元，可以直接使用M中的私有成员 private: int i, j; void show(void) { cout << "i=" << i << '\t' << "j=" << j << '\t'; } public: M(int a = 0, int b = 0){ i = a; j = b; } }; class N : public M{ // N为M的派生类 public: N(int a = 0, int b = 0) : M(a, b) {} void Print(void){ show(); cout << "i+j=" << i + j << endl; } }; void main(void){ N n1(10, 20); M m1(100, 200); // m1.show(); //私有成员函数，在类外不可调用 n1.Print(); }4.静态成员类所产生的所有对象中的静态成员均共享一个存储空间，这个空间是在编译的时候分配的。静态数据成员在类定义中，用关键字 static 修饰的数据成员称为静态数据成员。必须在文件作用域中，对静态数据成员作一次且只能作一次定义性说明可以通过静态数据成员名前加上类名和作用域运算符，可直接引用静态数据成员class A{ int x,y; static int z; public: void Setxy(int a, int b){ x=a; y=b;} }; A a1, a2; // 共用一个z int A::z = 0; // 必须对静态成员作一次定义性说明 静态成员函数可以将类的成员函数定义为静态的成员函数。即使用关键字 static 来修饰…

OOP课程笔记：第三章 继承和派生类
type: item
date: 2025-10-18T18:04:37+08:00
description: 第三章 继承和派生类继承性是面向对象程序设计中最重要的机制，这种机制提供了无限重复利用程序资源的一种途径。通过C++语言中的继承机制，可以扩充和完善旧的程序设计以适应新的需求。这样不仅可以节省程序开发的时间和资源，并且为未来程序增添了新的资源。在C++中所谓“继承”就是在一个已存在的类的基础上建立一个新的类已存在的类称为 基类(base class) 或 父类(father class)。新建立的类称为 派生类(derived class) 或 子类(son class) 。1.派生新增内容派生类则除了继承基类的所有可引用成员变量和成员函数外还可另外定义本身的成员变量和处理这些变量的函数语法格式：Access 是继承方式，有 private , public , protected 三种class ClassName:<Access>BaseClassName{ private: ...... ；//私有成员说明 public: ...... ；//公有成员说明 protected: ...... ；//保护成员说明 ｝2.派生方式及访问权限在派生时，原有属性的访问权限在派生类中有所不同。基类成员类型public 继承protected 继承private 继承publicpublicprotectedprivateprotectedprotectedprotectedprivateprivate不可访问不可访问不可访问总结：public 继承不改变基类属性protected 继承将原来的 public 降级为 protected ，其余不变private 继承会将所有的属性变为private ，基类的 private 仍然不可访问派生类属于原有基类外， private 永远不可访问3.抽象类当定义了一个类，这个类只能用作基类来派生出新的类，而不能用这种类来定义对象时，称这种类为抽象类。将类的构造函数或析构函数的访问权限定义为保护时，这种类为抽象类。class A{ int x, y; protected: A(int a, int b){ x = a; y = b; } // 基类初始化 public: void ShowA() { cout << "x=" << x << '\t' << "y=" << y << '\n'; } }; class B : public A{ int m; A a1(1, 1); // 不合法，派生类中不允许创建基类对象（因为属于类外） public: B(int a, int b, int c) : A(a, b) // 可以在派生类中调用A的构造函数 { m = c; } void Show(){ cout << "m=" << m << '\n'; ShowA(); } }; void main(void){ B b1(1, 2, 3); // 可以定义派生类对象 b1.Show(); A aa(2, 3); // 不合法，不允许创建A的对象 }4.多重继承可以用多个基类来派生一个类class ClassName:<Access> Class1, <Access> Class2 { private: ...... ；//私有成员说明; public: ...... ；//公有成员说明; protected: ...... ；//保护的成员说明; }; class D:public A,protected B,private C{ ....//派生类中新增加成员 };5.初始化对象成员构造函数不能被继承，派生类的构造函数必须调用对象所属类的构造函数来初始化对象成员。派生类构造函数的调用顺序如下：1.基类的构造函数2.对象成员的构造函数3.派生类的构造函数当撤销派生类对象时，析构函数的调用正好相反。当调用基类的构造函数时，根据继承参数表中顺序来调用。class Base1{ int x; public: Base1(int a){ x = a; cout << "调用基类1的构造函数 !\n "; } ~Base1() { cout << "调用基类1的析构函数 !\n "; } }; class Base2{ int y; public: Base2(int a){ y = a; cout << "调用基类2的构造函数 !\n "; } ~Base2() { cout << "调用基类2的析构函数 !\n "; } }; class Derived : public Base2, public Base1 { int z; public: Derived(int a, int b) : Base1(a), Base2(20) { z = b; cout << "调用派生类的构造函数 !\…

OOP课程笔记：第二章 构造函数与析构函数
type: item
date: 2025-10-18T18:03:28+08:00
description: 第二章 构造函数与析构函数构造函数是在创建对象时，使用给定的值来将对象初始化。析构函数是在系统释放对象前，对对象做一些善后工作。构造函数是类的成员函数，系统约定构造函数名必须与类名相同。构造函数可以带参数、可以重载，同时没有返回值class A{ float x, y; public: A(float a, float b){ x = a; y = b; } //构造函数，初始化对象 float Sum(void) { return x + y; } void Set(float a, float b){ x = a; y = b; } Print(void) { cout << "x=" << x << '\t' << "y=" << y << endl; } }; void main(void){ A a1(2.0, 3.0); // 定义时调用构造函数初始化 A a2(1.0, 2.0); a2.Set(10.0, 20.0); // 利用成员函数重新为对象赋值 a1.Print(); a2.Print(); }注意点:1.构造函数的函数名必须与类名相同。2.在定义构造函数时，不能指定函数返回值的类型，也不能指定为 void 类型。3.一个类可以定义若干个构造函数。当定义多个构造函数时，必须满足函数重载的原则。4.构造函数可以指定参数的缺省值。5.若定义的类要说明该类的对象时，构造函数必须是公有的成员函数。如果定义的类仅用于派生其它类时，则可将构造函数定义为保护的成员函数。6.由于构造函数属于类的成员函数，它对私有数据成员、保护的数据成员和公有的数据成员均能进行初始化1.缺省构造函数若没有显式定义构造函数，系统默认缺省的构造函数。缺省的构造函数并不对所产生对象的数据成员赋初值，即新产生对象的数据成员的值是*不确定的。class A{ float x, y; public: A() {} // 隐含的缺省的构造函数 void Print(void) { cout << x << '\t' << y << endl; } }; A a1, a2; 2.对局部对象，静态对象，全局对象的初始化对于局部对象，每次定义对象时，都要调用构造函数。对于静态对象，是在首次定义对象时，调用构造函数的，且由于对象一直存在，只调用一次构造函数。对于全局对象，是在 main() 执行之前调用构造函数的。3.参数初始化列表通过参数初始化列表来实现对数据成员的初始化，在构造函数首部实现。Box∷Box(int h,intw,intlen):height(h),width(w),length(len){}注意：如果数据成员是数组，则应当在构造函数的函数体中用语句对其赋值class Student{ public: Student(int n, charchar s, nam[]) : num(n), sex(s) // 定义构造函数 { strcpy(name, mam); // 数组在函数体完成赋值 } // 函数体 private: int num; char sex; char name[20]; };1.在定义类时，只要显式定义了一个类的构造函数，则编译器就不产生缺省的构造函数2.所有的对象在定义时，必须调用构造函数，不存在没有构造函数的对象class A{ float x, y; public: A(float a, float b){ x = a; y = b; } void Print(void) { cout << x << '\t' << y << endl; } }; void main(void){ A a1; // 会发生错误，没有构造函数调用 A a2(3.0, 30.0); }4.构造函数与 new 运算符可以使用 new 运算符来动态地建立对象。建立时要自动调用构造函数，以便完成初始化对象的数据成员。最后返回这个动态对象的起始地址。用 new 运算符产生的动态对象，在不再使用这种对象时，必须用 delete 运算符来释放对象所占用的存储空间。用 new 建立类的对象时，可以使用参数初始化动态空间。class A{ float x, y; public: A(float a, float b){ x = a; y = b; } A(){ x = 0; y = 0; } void Print(void) { cout << x << '\t' << y << endl; } }; void main(void){ A *pa1, *pa2; pa1 = new A(3.0, 5.0); // 用new动态开辟对象空间，初始化 pa2 = new A; // 用new动态开辟空间，调用构造函数初始化 pa1->Print(); …

OOP课程笔记：第一章 类和对象
type: item
date: 2025-10-18T18:01:00+08:00
description: 第一章 类和对象1.基本概念对象是由一组属性(attribute)和一组行为(behavior)构成的。在C++中，每个对象都是由数据和函数(即操作代码)这两部分组成的。面向对象程序设计中每一组数据都是有特定的用途的，是某种操作的对象。也就是说，一组操作调用一组数据。把相关的数据和操作放在一起，形成一个整体，与外界相对分隔。这就是面向对象的程序设计中的对象。在C++，中对象的类型称为类(class)。类代表了某一批对象的共性和特征。类是对象的抽象，而对象是类的具体实例(instance)。class Student{ private : char Name[20]; float Math; float Chinese; public : float average; void SetName(char *name); void SetMath(float math); void SetChinese(float ch); float GetAverage(void); }; // 分号注意不要少2.关键字关键字成员类型作用域private私有成员只允许该类内的成员函数使用私有的成员数据public公有成员可以在类内或类外自由使用protected保护成员仅允许该类及该类的派生类3.类的成员如果未加说明，类中成员默认的访问权限是private，即私有的。在下示例中，在类外不能直接使用 x 或 y ，必须通过 Setxy()给 x 或 y 赋值，通过 Print() 输出 x 或 y 。class A{ float x, y; public: void Setxy(float a, float b){ x = a; y = b; } void Print(void){ cout << x << ‘\t’ << y << endl;} };成员函数与成员数据的定义不分先后，可以先说明函数原型，再在类体外定义函数体。class A{ float x, y; public: void Setxy(float a, float b); // 体内声明函数 void Print(void); }; void A::Setxy(float a, float b){ // 体外定义 x = a; y = b; } void A::Print(void){ cout << x << '\t' << y << endl; }注意两点 :1.类具有封装性，并且类只是定义了一种结构，所以类中的任何成员数据均不能使用关键字 extern ， auto 或 register 限定其存储类型。2.在定义类时，只是定义了一种导出的数据类型，并不为类分配存储空间，所以，在定义类中的数据成员时，不能对其初始化。class Test { int x = 5,y = 6; // 是不允许的 extern float x; // 是不允许的 };struct 与 class在C++语言中，结构体类型 struct 只是类 class 的一个特例。结构体类型与类的唯一的区别在于：在类中，其成员的缺省的存取权限是私有的；而在结构体类型中，其成员的缺省的存取权限是公有的。内联成员函数当我们定义一个类时，可以在类中直接定义函数体。这时成员函数在编译时是作为内联函数来实现的。同时，我们也可以在类体外定义类的内联成员函数，在类体内声明函数在类体外定义时，在成员函数的定义前面加上关键字 inline 。class A{ float x, y; public: void Setxy(float a, float b); // 声明内联函数 void Print(void); }; inline void A::Setxy(float a, float b){ // 定义内联函数 x = a; y = b; } inline void A::Print(void){ cout << x << 't' << y << endl;}4.对象在定义类时，只是定义了一种数据类型，即说明程序中可能会出现该类型的数据，并不为类分配存储空间。类的变量我们称之为对象，对象是类的实例。在建立对象时，只为对象分配用于保存数据成员的内存空间，而成员函数的代码为该类的每一个对象所共享。对象的使用一个对象的成员就是该对象的类所定义的成员，有成员数据和成员函数，引用时同结构体变量类似，用 . 运算符。class A{ float x, y; public: float m, n; void Setxy(float a, float b){ x = a; y = b; } void Print(void){ cout << x << '\t' << y << endl; } }; void main(void){ A…