前言在C的学习进阶之路上面向对象编程OOP 是绕不开的核心知识点而类与对象的封装、运算符重载、动态内存管理、拷贝构造、析构函数等核心语法更是从理论走向实践的关键关卡。很多初学者在啃完基础语法后常常陷入“懂概念却不会写代码”的困境面对日期校验、对象拷贝、动态数组管理、运算符重载等实际场景不知如何将零散知识点串联起来也容易忽略静态成员、const修饰、引用传参、内存泄漏等细节问题导致代码报错不断、难以运行。为了帮大家打通理论与实践的壁垒本次我将以图书信息管理日期合法性校验为实战场景从零实现一个简易的图书管理系统。这个项目围绕Date日期类和Book图书类展开完整覆盖了构造函数、析构函数、拷贝构造函数、赋值运算符重载、输入输出流重载、动态数组扩容、图书增删查改等核心功能同时针对代码中常见的漏洞如静态成员未初始化、引用缺失、动态内存错误、const修饰遗漏等进行逐一修复全程不新增多余变量与函数最大程度保留基础代码逻辑非常适合C面向对象阶段的学习者对照练习、查漏补缺。通过这份实践代码你不仅能夯实类与对象的核心语法还能掌握实际编程中代码调试、漏洞修复的思路真正做到学以致用彻底攻克C面向对象编程的入门难点。接下来就让我们一起走进这份代码开启实战学习之旅。#includeiostream #includestring using namespace std; class Date { //友元函数声明可以放置在类中的任意位置 friend std::istream operator (std::istream in, Date d); friend std::ostream operator (std::ostream out, const Date d); // 加const支持常量对象输出 private: int _year 1900; int _month 1; int _day 1; public: // 构造函数带缺省参数创建对象时自动校验日期 Date(int a 1900, int b 1, int c 1) :_year(a), _month(b), _day(c) { (*this).CheckDate(); // 调用日期校验函数保证初始日期合法 } ~Date()//析构函数 { } // 拷贝构造函数深拷贝成员变量完成对象复制 Date(const Date d) { _year d._year; _month d._month; _day d._day; } int GetDay();//获取指定月份的日期 int CheckDate();//判断日期的有效性 Date operator (const Date d);//加const支持常量对象赋值 }; // 获取指定月份的最大天数平年2月28天闰年2月29天其余月份按固定天数返回 int Date::GetDay() { // 数组下标对应月份0号位置占位方便直接用月份下标取值 int month_days[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; // 判断闰年能被400整除 或 能被4整除且不能被100整除 if (_month 2 ((_year % 400 0) || (_year % 4 0 _year % 100 ! 0))) { return 29; } else return month_days[_month]; // 非2月直接返回对应月份固定天数 } // 日期合法性校验有效返回1无效返回0同时打印错误提示 int Date::CheckDate() { int error 1; // 默认为合法日期 // 校验月份范围1-12 if (_month 1 || _month12) { error 0; cout _year / _month / _day 中的月无效 endl; } // 校验日期1~当月最大天数 if (_day1 || _day(*this).GetDay()) { error 0; cout _year / _month / _day 中的日无效 endl; } return error; } // 重载返回Date是为了支持连续赋值(ab)c; Date Date::operator(const Date d) { if (this ! d)//防止自赋值地址相同则无需赋值 { // 逐成员赋值完成对象拷贝 _day d._day; _year d._year; _month d._month; } return *this; // 返回当前对象支持链式调用 } // 输入重载循环读取直到输入合法日期 std::istream operator (std::istream in, Date d) { cout 请按照顺序依次输入年/月/日 endl; in d._year d._month d._day; // 日期无效则重复输入直到合法 while (d.CheckDate() 0) { cout 请按照顺序依次输入年/月/日 endl; in d._year d._month d._day; }; return in; } // 输出重载格式化打印日期 std::ostream operator (std::ostream out, const Date d) { out d._year / d._month / d._day; return out; } class Book { // 友元参数为引用避免拷贝直接操作原对象 friend std::istream operator (istream in, Book b); friend std::ostream operator (ostream out, const Book b); private: string _id 0001; //编号 string _name 未知; //书名 string _author 匿名;//作者 Date _d; //出版日期 public: // 构造函数所有参数补全缺省值成为默认构造函数 Book(string id 0001, string name 未知, string author 匿名, const Date d Date()) :_id(id), _name(name), _author(author), _d(d)//使用初始化列表的构造函数 { } ~Book()//析构函数 { } // 拷贝构造函数参数加const符合规范 Book(const Book b) { _id b._id; _name b._name; _author b._author; _d b._d; } Book operator(const Book b); string getname()const; string getid()const; }; // 重载防止自赋值逐成员拷贝 Book Book::operator(const Book b) { if (this ! b)//防止自赋值 { _id b._id; _author b._author; _name b._name; _d b._d; } return *this; } std::istream operator (istream in, Book b) { cout 请按照书籍标码/名字/作家/存储日期 的顺序输入 endl; // 依次读取书籍信息日期会自动调用Date的输入重载 in b._id b._name b._author b._d; return in; } std::ostream operator (ostream out, const Book b) { out 标码 b._id endl 书名 b._name endl 作家 b._author endl 日期 b._d endl; return out; } // 常量成员函数获取书名不修改对象 string Book::getname()const { return _name; } // 常量成员函数获取编号不修改对象 string Book::getid()const { return _id; } class List { private: Book* _Book; // 动态数组存储书籍 int _capacity 0;// 数组容量 int _size 0; // 已存储书籍数量 // 扩容检查容量不足时扩容避免越界 void CheckCapacity() { if (_capacity _size) { // 新容量空则为4否则翻倍 int newcapacity (_size 0 ? 4 : 2 * _capacity); Book* newbook new Book[newcapacity]; for (int i 0; i _size; i) { newbook[i] _Book[i]; } // 释放旧内存指向新数组更新容量 delete[] _Book; _Book newbook; _capacity newcapacity; } } public: static int num; // 构造函数初始化动态数组、容量、大小 List(Book* b nullptr, int capacity 0, int size 0) :_Book(b), _capacity(capacity), _size(size) { //空对象时初始化容量避免扩容异常 if (_capacity 0) { _Book new Book[4]; _capacity 4; } } ~List() { delete[]_Book; // 释放动态数组内存 } // 拷贝构造函数深拷贝防止浅拷贝内存泄漏 List(const List list) { _capacity list._capacity; _size list._size; _Book new Book[_capacity]; for (int i 0; i _size; i) { _Book[i] list._Book[i]; } } //增加图书 void Push(); //查找图书 int findByName(); int findById(); //删除图书 void Pop(int n); //打印所有图书信息 void print(); }; int List::num 0; // 全局书籍总数 // 增加图书先扩容再输入大小1总数1 void List::Push() { CheckCapacity(); cin _Book[_size]; _size; num; } // 按书名查找找到返回下标未找到返回-1 int List::findByName() { string name; cout 请输入待查找图书的名字 endl; cin name; for (int i 0; i _size; i) { if (name _Book[i].getname()) { cout 找到图书下标为 i endl; cout _Book[i] endl; return i; } } cout 找不到相关信息书籍 endl; return -1; } // 按编号查找找到返回下标未找到返回-1 int List::findById() { string id; cout 请输入待查找图书的标码 endl; cin id; for (int i 0; i _size; i) { if (id _Book[i].getid()) { cout 找到图书下标为 i endl; cout _Book[i] endl; return i; } } cout 找不到相关信息书籍 endl; return -1; } // 删除图书按下标删除后续元素前移大小-1 void List::Pop(int n) { // 校验下标合法性 if (n 0 || n _size) { cout 下标非法删除失败 endl; return; } // 后续元素依次前移覆盖待删除元素 for (int i n; i _size - 1; i) { _Book[i] _Book[i 1]; } _size--; // 无需delete仅减少有效元素个数 num--; cout 删除成功 endl; } // 打印所有图书遍历数组逐个输出 void List::print() { if (_size 0) { cout 暂无图书信息 endl; return; } for (int i 0; i _size; i) { cout 第 i 1 本图书 endl; cout _Book[i] endl endl; } } // 日期类完整测试输入、拷贝构造、赋值重载 void TestDate() { cout 日期类输入测试 endl; Date d1; cin d1; cout 输入的日期 d1 endl; cout 拷贝构造函数测试 endl; Date d3 d1; cout 拷贝后的日期 d3 endl; cout 构造函数赋值重载测试 endl; Date d2(2023, 12, 1); d3 d2; cout 原日期 d2 endl; cout 赋值后日期 d3 endl; } // 书籍类完整测试构造、输入、拷贝、赋值、输出 void TestBook() { cout 书籍类默认构造测试 endl; Book b1; cout b1 endl; cout 书籍类输入测试 endl; Book b2; cin b2; cout 输入的书籍信息 endl; cout b2 endl; cout 书籍类拷贝构造测试 endl; Book b3 b2; cout 拷贝后的书籍信息 endl; cout b3 endl; cout 书籍类赋值重载测试 endl; Book b4; b4 b2; cout 赋值后的书籍信息 endl; cout b4 endl; } // 整体功能测试图书管理系统增删查印全流程 void testAll() { cout 图书管理系统整体测试 endl; List list; // 测试添加3本图书 cout -----添加3本图书----- endl; list.Push(); list.Push(); list.Push(); // 测试打印所有图书 cout -----打印所有图书----- endl; list.print(); // 测试按书名查找 cout -----按书名查找----- endl; list.findByName(); // 测试按编号查找 cout -----按编号查找----- endl; list.findById(); // 测试删除图书 cout -----删除下标为0的图书----- endl; list.Pop(0); list.print(); } // 主函数调用所有测试函数 int main() { cout ********************日期类测试开始******************** endl; TestDate(); cout ********************日期类测试结束******************** endl endl; cout ********************书籍类测试开始******************** endl; TestBook(); cout ********************书籍类测试结束******************** endl endl; cout ********************整体功能测试开始******************** endl; testAll(); cout ********************整体功能测试结束******************** endl; return 0; }本程序旨在模拟一个图书管理系统框架通过三个类来实现图书的增加删除查找功能。下面是有相关知识点注释的代码#define _CRT_SECURE_NO_WARNINGS 1 #includeiostream #includestring using namespace std; /*类的基础语法 * 一.class Nclass {……}或者是struct Nstruct{……};注意不要少掉结尾分号 类是c语言中结构体类型的升级版 不仅可以在类中定义变量 还可以定义一定的方法函数 * 二.类中通过成员访问操作符(public,private,potected)来决定外界对类中成员的访问权限 * public:其下声明的成员为公有外界可访问使用时加上“Nclass::”后接成员变量即可 * private:私有声明的成员为私有外界不可访问只能在本类中使用 * protected暂时不作了解 * 以上三个操作符的作用成员变量的规则都是 * 1.成员访问操作符……成员访问操作符 ……间的成员都和第一个成员访问操作符指定的访问权限相符 * 2.若成员访问操作符……类尾 ……间的成员都和出现的成员访问操作符指定的访问权限相符 * 3.如果整个类中都没有成员访问操作符出现使用class定义的类成员全是私有 使用struct定义的类成员全是公有。 * 三.在类中定义的函数默认是内联函数 * 四.类是一种自定义类型使用他创建变量的过程叫做对象实例化 Nclass n; 可以将Nclass看作是模具n是由模具而产生的物品n被称为对象. 对于n的大小计算和c语言结构体变量的大小计算相类似符合以下条件 1.对象的存储空间都是由变量占据的成员函数不占空间,没有成员变量的类被称为空类大小默认为1 第一个成员变量和内存给类对象开辟的空间的第一个字节对齐记这个字节为偏移量为0的位置 之后每个字节偏移量依次为12……。 2.引入对齐数的概念每个成员变量都有自己的对齐数整个对象的最大对齐数为成员变量中对齐数的最大值。 对齐数是成员变量大小与默认对齐数的最小值每个编译器的默认对齐数都会有所不同在vs中默认对齐数是8。 3.每个成员变量按照定义的顺序依次在和自己对齐数是整数倍关系的偏移量自己处开始向下存储sizeof(成员变量)个字节。 4.对象的大小是从最后一个变量存储空间的末尾计算总共存储的字节数并相上取整数倍到对象的最大对齐数。 5.嵌套自定义类型中一个自定义成员变量的对齐数为其最大对齐数按照对齐方法再进行如上步骤可求解其对象的大小 * */ class Date { //友元函数声明可以放置在类中的任意位置 friend std::istream operator (std::istream in, Date d); friend std::ostream operator (std::ostream out, const Date d); // 加const支持常量对象输出 private: int _year 1900; int _month 1; int _day 1; public: //五this 指针与成员函数 // 1.成员函数的使用方法 // 例子 // Class A{ // public: // int a0; // void test // { // a; // } // A d; // d.test();都是按照对象.成员函数的形式使用 // 2.可以看到成员函对d对象的属性改变时不用传参是因为成员函数隐藏了第一个参数一个指针 // 我们将它称为是this指针指向d,不用显式实现。所有成员函数的第一个参数都是this指针。 //默认函数常见的五类构造函数析构函数复制构造函数重载运算符重载运算符 //一、构造函数是进行对象初始化时的相关工作 // 1.不自定义构造函数时编译器会自动生成一个默认构造函数,它会进行如下操作; // 内置类型成员变量编译器随机赋值自定义类型变量的初始化就会调用它的默认构造函数下面将 // 2.自定义构造函数语法 Nclass (参数表):初始化列表{函数主体} // 3.初始化列表 // 1.语法成员变量一成员变量二……。中可以是形参也可以是参数表中形参的运算式 // 2.每个成员变量的初始化都要走初始化列表初始化顺序不是按照初始化列表中的顺序 // 而是按照在类中声明的先后顺序给成员变量后赋一个值可作为初始化列表的缺省值 // 3.const成员变量引用成员变量没有默认构造函数的成员变量必须在初始化列表中初始化 // // 4.默认构造函数不传参就可以调用的构造函数;具体可以分为三类 // 参数表中参数全缺省的构造函数无参自定义构造函数编译器自动生成的构造函数 // 二、析构函数 // 1.不自定义析构函数编译器也会生成一个析构函数但是它什么都不会干 // 2.自定义析构函数语法~Nclass({函数主体} // 3.析构函数是为了在对象销毁是进行资源的清理如销毁动态内存开辟的空间防止内存的泄露 // 调用自定义成员变量的析构函数 // 4.不同空间初始化对象析构函数的调用规则 // 1.整体规则先创建后析构 // 2.先析构局部再析构全局和静态 // //三复制构造函数 //1.语法Nclass(const Nclassd){}; //2.c规定进行对象的拷贝型初始化就会调用对象的复制构造函数 //拷贝型初始化的行为有用已存在的对象给新对象初始化函数传参时传一个对象函数返回值为一个对象 //3.浅拷贝和深拷贝 //浅拷贝一个字节一个字节地拷贝 //深拷贝当拷贝的内容涉及相关的资源如果是一个指针就会拷贝一份指针指向的空间将新指针指向新拷贝的空间 //4.编译器自动生成的复制构造函数都只进行浅拷贝工作换言之想要进行深拷贝工作就得自定义复制构造函数 // 构造函数带缺省参数创建对象时自动校验日期 //四、运算符重载 //1.语法 返类型 operator 运算符参数表{函数主体} //2.使用具体见代码 //3.不可重载的运算符.* :: sizeof ?: . Date(int a 1900, int b 1, int c 1) :_year(a), _month(b), _day(c) { (*this).CheckDate(); // 调用日期校验函数保证初始日期合法 } ~Date()//析构函数 { } // 拷贝构造函数深拷贝成员变量完成对象复制 Date(const Date d) { _year d._year; _month d._month; _day d._day; } int GetDay();//获取指定月份的日期 int CheckDate();//判断日期的有效性 Date operator (const Date d);//加const支持常量对象赋值 }; // 获取指定月份的最大天数平年2月28天闰年2月29天其余月份按固定天数返回 int Date::GetDay() { // 数组下标对应月份0号位置占位方便直接用月份下标取值 int month_days[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; // 判断闰年能被400整除 或 能被4整除且不能被100整除 if (_month 2 ((_year % 400 0) || (_year % 4 0 _year % 100 ! 0))) { return 29; } else return month_days[_month]; // 非2月直接返回对应月份固定天数 } // 日期合法性校验有效返回1无效返回0同时打印错误提示 int Date::CheckDate() { int error 1; // 默认为合法日期 // 校验月份范围1-12 if (_month 1 || _month12) { error 0; cout _year / _month / _day 中的月无效 endl; } // 校验日期1~当月最大天数 if (_day1 || _day(*this).GetDay()) { error 0; cout _year / _month / _day 中的日无效 endl; } return error; } // 重载返回Date是为了支持连续赋值(ab)c; Date Date::operator(const Date d) { if (this ! d)//防止自赋值地址相同则无需赋值 { // 逐成员赋值完成对象拷贝 _day d._day; _year d._year; _month d._month; } return *this; // 返回当前对象支持链式调用 } // 输入重载循环读取直到输入合法日期 std::istream operator (std::istream in, Date d) { cout 请按照顺序依次输入年/月/日 endl; in d._year d._month d._day; // 日期无效则重复输入直到合法 while (d.CheckDate() 0) { cout 请按照顺序依次输入年/月/日 endl; in d._year d._month d._day; }; return in; } // 输出重载格式化打印日期 std::ostream operator (std::ostream out, const Date d) { out d._year / d._month / d._day; return out; } //友元 //1.友元声明分为友元类声明友元函数声明 //2.语法在A类中使用 friend 类/函数就表明被声明的类或函数就可以突破成员访问限定符的限制 // 即使是私有成员也可以使用 //3.友元关系不具有传递性A是B的友元B是C的友元但A不是C的友元 //4.友元关系是单向的A是B的友元B不是A的友元 //动态内存开辟 //1.语法开辟 T*ptrnew T(val);销毁delete ptr; // 开辟 T*ptrnew T[n]{val1,val2,……};销毁delete []ptr; //2.动态内存开辟并初始化一个多成员变量的类A // A*ptr{{a1,b1,……}{a2,b2,……}……} // 或者使用匿名对象 // A*ptr{A(a1,b1,……),B(a2,b2,……,……} //malloc realloc calloc free 和 new delete的区别 //1.前一组动态内存开辟的方法函数后一组是关键字 //2.malloc 参数只有一个且开辟的空间中的值没有初始化 // calloc 参数有两个分别为数据个数和数据类型大小开辟空间中的值被初始化为0 // realloc 是在原开辟的空间上进行扩容参数为原数据的地址和新空间的字节数 //3. new 开辟空间可以初始化不用手动计算空间的字节数开辟连续空间也只需要传入数据的个数 //4.new 的开辟空间实则是有两步组成调用operator new 这一步包括两个动作调用malloc和抛异常处理 // 第二步如果数据是自定义类型就要调用构造函数 //5.delete 进行动态开辟内存的销毁分为两步第一步如果资源是自定义类型就要调用析构函数 // 第二步调用operator delete class Book { // 友元参数为引用避免拷贝直接操作原对象 friend std::istream operator (istream in, Book b); friend std::ostream operator (ostream out, const Book b); private: string _id 0001; //编号 string _name 未知; //书名 string _author 匿名;//作者 Date _d; //出版日期 public: // 构造函数所有参数补全缺省值成为默认构造函数 Book(string id 0001, string name 未知, string author 匿名, const Date d Date()) :_id(id), _name(name), _author(author), _d(d)//使用初始化列表的构造函数 { } ~Book()//析构函数 { } // 拷贝构造函数参数加const符合规范 Book(const Book b) { _id b._id; _name b._name; _author b._author; _d b._d; } Book operator(const Book b); string getname()const; string getid()const; }; // 重载防止自赋值逐成员拷贝 Book Book::operator(const Book b) { if (this ! b)//防止自赋值 { _id b._id; _author b._author; _name b._name; _d b._d; } return *this; } std::istream operator (istream in, Book b) { cout 请按照书籍标码/名字/作家/存储日期 的顺序输入 endl; // 依次读取书籍信息日期会自动调用Date的输入重载 in b._id b._name b._author b._d; return in; } std::ostream operator (ostream out, const Book b) { out 标码 b._id endl 书名 b._name endl 作家 b._author endl 日期 b._d endl; return out; } // 常量成员函数获取书名不修改对象 string Book::getname()const { return _name; } // 常量成员函数获取编号不修改对象 string Book::getid()const { return _id; } //static 成员 //1.是整个类的实例化对象公有的不存储在任何一个类对象开辟的内存空间中 //2.static 成员变量是在类中声明在类外初始化例A类中static T a;类外T A::a…… //3.static 成员函数只能访问static成员变量因为没有this指针作为其隐藏的第一个参数 class List { private: Book* _Book; // 动态数组存储书籍 int _capacity 0;// 数组容量 int _size 0; // 已存储书籍数量 // 扩容检查容量不足时扩容避免越界 void CheckCapacity() { if (_capacity _size) { // 新容量空则为4否则翻倍 int newcapacity (_size 0 ? 4 : 2 * _capacity); Book* newbook new Book[newcapacity]; for (int i 0; i _size; i) { newbook[i] _Book[i]; } // 释放旧内存指向新数组更新容量 delete[] _Book; _Book newbook; _capacity newcapacity; } } public: static int num; // 构造函数初始化动态数组、容量、大小 List(Book* b nullptr, int capacity 0, int size 0) :_Book(b), _capacity(capacity), _size(size) { //空对象时初始化容量避免扩容异常 if (_capacity 0) { _Book new Book[4]; _capacity 4; } } ~List() { delete[]_Book; // 释放动态数组内存 } // 拷贝构造函数深拷贝防止浅拷贝内存泄漏 List(const List list) { _capacity list._capacity; _size list._size; _Book new Book[_capacity]; for (int i 0; i _size; i) { _Book[i] list._Book[i]; } } //增加图书 void Push(); //查找图书 int findByName(); int findById(); //删除图书 void Pop(int n); //打印所有图书信息 void print(); }; int List::num 0; // 全局书籍总数 // 增加图书先扩容再输入大小1总数1 void List::Push() { CheckCapacity(); cin _Book[_size]; _size; num; } // 按书名查找找到返回下标未找到返回-1 int List::findByName() { string name; cout 请输入待查找图书的名字 endl; cin name; for (int i 0; i _size; i) { if (name _Book[i].getname()) { cout 找到图书下标为 i endl; cout _Book[i] endl; return i; } } cout 找不到相关信息书籍 endl; return -1; } // 按编号查找找到返回下标未找到返回-1 int List::findById() { string id; cout 请输入待查找图书的标码 endl; cin id; for (int i 0; i _size; i) { if (id _Book[i].getid()) { cout 找到图书下标为 i endl; cout _Book[i] endl; return i; } } cout 找不到相关信息书籍 endl; return -1; } // 删除图书按下标删除后续元素前移大小-1 void List::Pop(int n) { // 校验下标合法性 if (n 0 || n _size) { cout 下标非法删除失败 endl; return; } // 后续元素依次前移覆盖待删除元素 for (int i n; i _size - 1; i) { _Book[i] _Book[i 1]; } _size--; // 无需delete仅减少有效元素个数 num--; cout 删除成功 endl; } // 打印所有图书遍历数组逐个输出 void List::print() { if (_size 0) { cout 暂无图书信息 endl; return; } for (int i 0; i _size; i) { cout 第 i 1 本图书 endl; cout _Book[i] endl endl; } } // 日期类完整测试输入、拷贝构造、赋值重载 void TestDate() { cout 日期类输入测试 endl; Date d1; cin d1; cout 输入的日期 d1 endl; cout 拷贝构造函数测试 endl; Date d3 d1; cout 拷贝后的日期 d3 endl; cout 构造函数赋值重载测试 endl; Date d2(2023, 12, 1); d3 d2; cout 原日期 d2 endl; cout 赋值后日期 d3 endl; } // 书籍类完整测试构造、输入、拷贝、赋值、输出 void TestBook() { cout 书籍类默认构造测试 endl; Book b1; cout b1 endl; cout 书籍类输入测试 endl; Book b2; cin b2; cout 输入的书籍信息 endl; cout b2 endl; cout 书籍类拷贝构造测试 endl; Book b3 b2; cout 拷贝后的书籍信息 endl; cout b3 endl; cout 书籍类赋值重载测试 endl; Book b4; b4 b2; cout 赋值后的书籍信息 endl; cout b4 endl; } // 整体功能测试图书管理系统增删查印全流程 void testAll() { cout 图书管理系统整体测试 endl; List list; // 测试添加3本图书 cout -----添加3本图书----- endl; list.Push(); list.Push(); list.Push(); // 测试打印所有图书 cout -----打印所有图书----- endl; list.print(); // 测试按书名查找 cout -----按书名查找----- endl; list.findByName(); // 测试按编号查找 cout -----按编号查找----- endl; list.findById(); // 测试删除图书 cout -----删除下标为0的图书----- endl; list.Pop(0); list.print(); } // 主函数调用所有测试函数 int main() { cout ********************日期类测试开始******************** endl; TestDate(); cout ********************日期类测试结束******************** endl endl; cout ********************书籍类测试开始******************** endl; TestBook(); cout ********************书籍类测试结束******************** endl endl; cout ********************整体功能测试开始******************** endl; testAll(); cout ********************整体功能测试结束******************** endl; return 0; }相关知识点总结如下类的基础语法一.class Nclass {……}或者是struct Nstruct{……};注意不要少掉结尾分号类是c语言中结构体类型的升级版不仅可以在类中定义变量还可以定义一定的方法函数二.类中通过成员访问操作符(public,private,potected)来决定外界对类中成员的访问权限public:其下声明的成员为公有外界可访问使用时加上“Nclass::”后接成员变量即可private:私有声明的成员为私有外界不可访问只能在本类中使用protected暂时不作了解以上三个操作符的作用成员变量的规则都是1.成员访问操作符……成员访问操作符 ……间的成员都和第一个成员访问操作符指定的访问权限相符2.若成员访问操作符……类尾 ……间的成员都和出现的成员访问操作符指定的访问权限相符3.如果整个类中都没有成员访问操作符出现使用class定义的类成员全是私有 使用struct定义的类成员全是公有。三.在类中定义的函数默认是内联函数四.类是一种自定义类型使用他创建变量的过程叫做对象实例化 Nclass n; 可以将Nclass看作是模具n是由模具而产生的物品n被称为对象.对于n的大小计算和c语言结构体变量的大小计算相类似符合以下条件1.对象的存储空间都是由变量占据的成员函数不占空间,没有成员变量的类被称为空类大小默认为1 第一个成员变量和内存给类对象开辟的空间的第一个字节对齐记这个字节为偏移量为0的位置 之后每个字节偏移量依次为12……。2.引入对齐数的概念每个成员变量都有自己的对齐数整个对象的最大对齐数为成员变量中对齐数的最大值。 对齐数是成员变量大小与默认对齐数的最小值每个编译器的默认对齐数都会有所不同在vs中默认对齐数是8。3.每个成员变量按照定义的顺序依次在和自己对齐数是整数倍关系的偏移量自己处开始向下存储sizeof(成员变量)个字节。4.对象的大小是从最后一个变量存储空间的末尾计算总共存储的字节数并相上取整数倍到对象的最大对齐数。5.嵌套自定义类型中一个自定义成员变量的对齐数为其最大对齐数按照对齐方法再进行如上步骤可求解其对象的大小五this 指针与成员函数1.成员函数的使用方法例子Class A{public:int a0;void test{a;}A d;d.test();都是按照对象.成员函数的形式使用2.可以看到成员函对d对象的属性改变时不用传参是因为成员函数隐藏了第一个参数一个指针我们将它称为是this指针指向d,不用显式实现。所有成员函数的第一个参数都是this指针。默认成员函数常见的五类构造函数析构函数复制构造函数重载运算符重载运算符一、构造函数1.构造函数是进行对象初始化时的相关工作不自定义构造函数时编译器会自动生成一个默认构造函数,它会进行如下操作;内置类型成员变量编译器随机赋值自定义类型变量的初始化就会调用它的默认构造函数下面将2.自定义构造函数语法 Nclass (参数表):初始化列表{函数主体}3.初始化列表1.语法成员变量一成员变量二……。中可以是形参也可以是参数表中形参的运算式2.每个成员变量的初始化都要走初始化列表初始化顺序不是按照初始化列表中的顺序而是按照在类中声明的先后顺序给成员变量后赋一个值可作为初始化列表的缺省值3.const成员变量引用成员变量没有默认构造函数的成员变量必须在初始化列表中初始化4.默认构造函数不传参就可以调用的构造函数;具体可以分为三类参数表中参数全缺省的构造函数无参自定义构造函数编译器自动生成的构造函数二、析构函数1.不自定义析构函数编译器也会生成一个析构函数但是它什么都不会干2.自定义析构函数语法~Nclass({函数主体}3.析构函数是为了在对象销毁是进行资源的清理如销毁动态内存开辟的空间防止内存的泄露调用自定义成员变量的析构函数4.不同空间初始化对象析构函数的调用规则1.整体规则先创建后析构2.先析构局部再析构全局和静态三复制构造函数1.语法Nclass(const Nclassd){};2.c规定进行对象的拷贝型初始化就会调用对象的复制构造函数拷贝型初始化的行为有用已存在的对象给新对象初始化函数传参时传一个对象函数返回值为一个对象3.浅拷贝和深拷贝浅拷贝一个字节一个字节地拷贝深拷贝当拷贝的内容涉及相关的资源如果是一个指针就会拷贝一份指针指向的空间将新指针指向新拷贝的空间4.编译器自动生成的复制构造函数都只进行浅拷贝工作换言之想要进行深拷贝工作就得自定义复制构造函数四、运算符重载1.语法 返类型 operator 运算符参数表{函数主体}2.使用具体见代码3.不可重载的运算符.* :: sizeof ?: .友元1.友元声明分为友元类声明友元函数声明2.语法在A类中使用 friend 类/函数就表明被声明的类或函数就可以突破成员访问限定符的限制即使是私有成员也可以使用3.友元关系不具有传递性A是B的友元B是C的友元但A不是C的友元4.友元关系是单向的A是B的友元B不是A的友元static 成员1.是整个类的实例化对象公有的不存储在任何一个类对象开辟的内存空间中2.static 成员变量是在类中声明在类外初始化例A类中static T a;类外T A::a……3.static 成员函数只能访问static成员变量因为没有this指针作为其隐藏的第一个参数动态内存开辟1.语法开辟 T*ptrnew T(val);销毁delete ptr;开辟 T*ptrnew T[n]{val1,val2,……};销毁delete []ptr;2.动态内存开辟并初始化一个多成员变量的类AA*ptr{{a1,b1,……}{a2,b2,……}……}或者使用匿名对象A*ptr{A(a1,b1,……),B(a2,b2,……,……}malloc realloc calloc free 和 new delete的区别1.前一组动态内存开辟的方法函数后一组是关键字2.malloc 参数只有一个且开辟的空间中的值没有初始化calloc 参数有两个分别为数据个数和数据类型大小开辟空间中的值被初始化为0realloc 是在原开辟的空间上进行扩容参数为原数据的地址和新空间的字节数3. new 开辟空间可以初始化不用手动计算空间的字节数开辟连续空间也只需要传入数据的个数4.new 的开辟空间实则是有两步组成调用operator new 这一步包括两个动作调用malloc和抛异常处理第二步如果数据是自定义类型就要调用构造函数5.delete 进行动态开辟内存的销毁分为两步第一步如果资源是自定义类型就要调用析构函数第二步调用operator delete结语从一个简单的日期类到完整的图书管理模块再到动态数组、运算符重载、对象生命周期管理这一段代码串联起了C面向对象编程中最核心、最易出错、也最能体现编程思维的知识点。它没有复杂的框架与花哨的封装只依靠最基础的类、构造、析构、拷贝、赋值、重载与动态内存完成了一个可运行、可扩展、可理解的小型实战项目。在这个过程中我们不仅修复了编译错误、链接问题、逻辑漏洞更重要的是理解了为什么要写const、为什么要用引用、为什么要处理深拷贝、为什么要检查容量——这些正是从“会写代码”走向“写好代码”的关键一步。C的学习从来不是死记硬背语法而是在实践中理解规则、在错误中沉淀经验。希望这份小小的图书管理系统能成为你面向对象学习路上的一块扎实基石。往后无论是数据结构、算法实现还是工程开发这些底层思想都会一直陪伴你让你写得更稳、更清晰、更专业。路虽远行则将至代码虽繁练则必精。愿你在C的路上稳步前行不断精进。