news 2026/5/23 1:37:43

C++类和对象,运算符重载,动态内存开辟

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++类和对象,运算符重载,动态内存开辟

前言

在C++的学习进阶之路上,面向对象编程(OOP) 是绕不开的核心知识点,而类与对象的封装、运算符重载、动态内存管理、拷贝构造、析构函数等核心语法,更是从理论走向实践的关键关卡。很多初学者在啃完基础语法后,常常陷入“懂概念却不会写代码”的困境,面对日期校验、对象拷贝、动态数组管理、运算符重载等实际场景,不知如何将零散知识点串联起来,也容易忽略静态成员、const修饰、引用传参、内存泄漏等细节问题,导致代码报错不断、难以运行。

为了帮大家打通理论与实践的壁垒,本次我将以图书信息管理+日期合法性校验为实战场景,从零实现一个简易的图书管理系统。这个项目围绕Date日期类和Book图书类展开,完整覆盖了构造函数、析构函数、拷贝构造函数、赋值运算符重载、输入输出流重载、动态数组扩容、图书增删查改等核心功能,同时针对代码中常见的漏洞(如静态成员未初始化、引用缺失、动态内存错误、const修饰遗漏等)进行逐一修复,全程不新增多余变量与函数,最大程度保留基础代码逻辑,非常适合C++面向对象阶段的学习者对照练习、查漏补缺。

通过这份实践代码,你不仅能夯实类与对象的核心语法,还能掌握实际编程中代码调试、漏洞修复的思路,真正做到学以致用,彻底攻克C++面向对象编程的入门难点。接下来,就让我们一起走进这份代码,开启实战学习之旅。

#include<iostream> #include<string> 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 || _month>12) { error = 0; cout << _year << "/" << _month << "/" << _day << "中的月无效" << endl; } // 校验日期:1~当月最大天数 if (_day<1 || _day>(*this).GetDay()) { error = 0; cout << _year << "/" << _month << "/" << _day << "中的日无效" << endl; } return error; } // =重载,返回Date&是为了支持连续赋值:(a=b)=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 #include<iostream> #include<string> 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的位置, 之后每个字节偏移量依次为1,2……。 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 a=0; // 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 Nclass&d){}; //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 || _month>12) { error = 0; cout << _year << "/" << _month << "/" << _day << "中的月无效" << endl; } // 校验日期:1~当月最大天数 if (_day<1 || _day>(*this).GetDay()) { error = 0; cout << _year << "/" << _month << "/" << _day << "中的日无效" << endl; } return error; } // =重载,返回Date&是为了支持连续赋值:(a=b)=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*ptr=new T(val);销毁:delete ptr; // 开辟: T*ptr=new 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的位置, 之后每个字节偏移量依次为1,2……。


2.引入对齐数的概念;每个成员变量都有自己的对齐数,整个对象的最大对齐数为成员变量中对齐数的最大值。 对齐数是成员变量大小与默认对齐数的最小值(每个编译器的默认对齐数都会有所不同,在vs中默认对齐数是8)。


3.每个成员变量按照定义的顺序依次在和自己对齐数是整数倍关系的偏移量自己处开始向下存储sizeof(成员变量)个字节。


4.对象的大小是从最后一个变量存储空间的末尾计算总共存储的字节数并相上取整数倍到对象的最大对齐数。


5.嵌套自定义类型中一个自定义成员变量的对齐数为其最大对齐数,按照对齐方法再进行如上步骤可求解其对象的大小

五,this 指针与成员函数
1.成员函数的使用方法:
例子:
Class A{
public:
int a=0;
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 Nclass&d){};


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*ptr=new T(val);销毁:delete ptr;


开辟: T*ptr=new 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

结语

从一个简单的日期类,到完整的图书管理模块,再到动态数组、运算符重载、对象生命周期管理,这一段代码串联起了C++面向对象编程中最核心、最易出错、也最能体现编程思维的知识点。

它没有复杂的框架与花哨的封装,只依靠最基础的类、构造、析构、拷贝、赋值、重载与动态内存,完成了一个可运行、可扩展、可理解的小型实战项目。在这个过程中,我们不仅修复了编译错误、链接问题、逻辑漏洞,更重要的是理解了为什么要写const、为什么要用引用、为什么要处理深拷贝、为什么要检查容量——这些正是从“会写代码”走向“写好代码”的关键一步。

C++的学习从来不是死记硬背语法,而是在实践中理解规则、在错误中沉淀经验。希望这份小小的图书管理系统,能成为你面向对象学习路上的一块扎实基石。往后无论是数据结构、算法实现,还是工程开发,这些底层思想都会一直陪伴你,让你写得更稳、更清晰、更专业。

路虽远,行则将至;代码虽繁,练则必精。愿你在C++的路上稳步前行,不断精进。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 1:29:39

OpenClaw安全防护指南:Qwen3-14B私有镜像的权限管控策略

OpenClaw安全防护指南&#xff1a;Qwen3-14B私有镜像的权限管控策略 1. 为什么需要安全防护&#xff1f; 当第一次看到OpenClaw能够自动操作我的电脑时&#xff0c;那种兴奋感很快被一个现实问题冲淡&#xff1a;如果这个AI助手突然"发疯"删除了我的工作目录怎么办…

作者头像 李华
网站建设 2026/5/23 1:29:35

自学嵌入式第六天

函数指针的小应用有两个整数a和b,由用户输人1,2或3。如输入1,程序就给出a和b中大者,输人2,就给出a和b中小者,输人3,则求a与b之和首先封装三个取值函数定义一个函数指针&#xff0c;通过switch语句选择1&#xff0c;2&#xff0c;3指向不同的函数&#xff08;类型相同&#xff0…

作者头像 李华
网站建设 2026/5/22 22:33:54

宿主机与虚拟机网络配置打通

Kali 虚拟机网络配置笔记 一、基础网络模式 1. 桥接模式 (Bridged) 目的&#xff1a;让虚拟机加入物理局域网配置&#xff1a; 选择物理网卡&#xff08;非VMnet1/VMnet8&#xff09;启用"复制物理网络连接状态"&#xff08;推荐笔记本用户&#xff09; 结果&#xf…

作者头像 李华
网站建设 2026/5/23 1:32:42

AI+XR老年康复智慧实训室,让学生在智慧实训中练就真本领

在“新双高”建设深入推进、人工智能技术加速赋能职业教育的时代背景下&#xff0c;国家推出一系列政策&#xff0c;明确提出“深化人工智能、虚拟仿真数字技术与职业教育深度融合”“构建产教融合人才生态”的要求。恒点推出AIXR老年康复智慧实训教学系统&#xff0c;响应战略…

作者头像 李华
网站建设 2026/5/23 1:29:39

python数据分析实战案例

一、项目整体设计思想本项目围绕班级学生信息分析与微信好友数据分析两个核心案例&#xff0c;采用「数据读取→数据清洗→多维度分析→可视化呈现→结论洞察」的全流程设计&#xff0c;核心技术栈为&#xff1a; Python pandas pyecharts snownlp wordcloud 腾讯云AI &am…

作者头像 李华
网站建设 2026/5/23 1:30:38

I2C土壤湿度传感器Arduino驱动库详解

1. 项目概述 I2CSoilMoistureSensor 是一款专为 Catnip Electronics&#xff08;现由 Miceuz 主导开发&#xff09;推出的 IC 接口土壤湿度传感器设计的轻量级 Arduino 库。该传感器硬件基于 Chirp 系列设计&#xff08;开源地址&#xff1a;https://github.com/Miceuz/i2c-moi…

作者头像 李华