从零掌握C++类与对象(上+中)—— 基础知识点全面总结

📅 2026/8/14 13:07:59
从零掌握C++类与对象(上+中)—— 基础知识点全面总结
前言C 的类和对象是面向对象编程的基石。这篇文章先把基础的知识点串联起来最后配合一个Date类的完整实现从类的定义一路写到运算符重载。每个核心函数都贴出完整可运行代码读完可以直接自己敲一遍。一、类的定义与封装1.1 类就是把数据和函数打包先用一个Person类感受一下最基本的语法classPerson{public:int_age;int_sex;string _name;// 类内声明 类内实现 → 默认内联voidInitInfo(){getline(cin,_name);cin_age;cin_sex;}// 类内声明类外实现voidPrintInfo();};// 类外实现必须加 类名::voidPerson::PrintInfo(){cout名字是_nameendl;cout性别是_sexendl;cout年龄是_ageendl;}关键点class结尾的分号不能省这是最经典的编译报错点。成员变量习惯加前缀_如_year来和局部变量区分不是强制是公司规范。类内实现的成员函数默认是inline编译器会尝试在调用处展开省一次函数调用开销。类外实现必须用类名::函数名::叫作用域操作符告诉编译器我是谁的成员。1.2 访问限定符给类装门禁public/protected/private三个关键字控制谁能访问类的成员classPerson{string _number;// 没写限定符class 默认 privatepublic:int_age;// 类外随便访问string _name;private:string _idCard;// 类外不能碰};关键点class默认privatestruct默认publicC 里struct也能写成员函数。常规做法成员变量扔private对外接口放public——这就是封装。protected和private在继承章节之前表现一样不用纠结。1.3 实例化设计图 vs 盖房子Date d1;// 创建对象实例化真正分配内存Dated2(2026,8,5);// 每个对象有自己独立的成员变量关键点类只是描述不占空间用类创建对象才分配内存。对象里只存成员变量不存成员函数——函数编译后扔在代码段所有对象共享。空类没有成员大小是1 字节纯粹占位表示这个对象存在过。对象大小遵循内存对齐规则VS 默认对齐数 8。1.4this指针对象自己的小抄每个成员函数都有一个隐藏参数this指向谁调用我的那个对象// 你写的voidDate::Print(){cout_year _month _dayendl;}// 编译器眼中的voidDate::Print(Date*constthis){coutthis-_year this-_month this-_dayendl;}关键点this是const指针Date* const不能给它赋值this nullptr编译报错。不能显式写在形参位置但函数体内可以显式用this-_year。经典面试题A* p nullptr; p-Print();—— 只要Print()里没访问成员变量就不会崩溃没解引用空指针。二、六个默认成员函数编译器送的全家桶构造函数、析构函数、拷贝构造函数、赋值运算符重载、两个取地址重载后两个不重要。学习套路就两问① 默认生成的行为够不够② 不够的话自己怎么写2.1 构造函数对象出生自动初始化构造函数替代了以前手写的Init对象创建时自动调用再也不会忘记初始化。// 全缺省构造函数 —— 不传参也能调默认构造Date::Date(intyear,intmonth,intday){_yearyear;_monthmonth;_dayday;}关键点函数名 类名没有返回值不用写void可以重载。默认构造有三种无参构造、全缺省构造、编译器生成的构造——它们共同点是不传实参就能调三者只能存在一个无参和全缺省并存会调用歧义。编译器默认生成的构造对int等内置类型不保证初始化值不确定对自定义类型成员会调它的默认构造。坑Date d3();会被编译器当成函数声明无参对象直接写Date d3;。2.2 析构函数对象临终自动清理Date::~Date(){// Date 类没有申请资源析构函数里什么都不用做// 编译器默认生成的析构就够用了}关键点写法~类名()无参无返回值一个类只能有一个。局部对象随函数栈帧销毁后定义的对象先析构栈的 LIFO。申请了资源malloc/new的类必须自己写析构否则资源泄漏没资源的类如Date可以不写。2.3 拷贝构造函数按样品复制对象用一个已有对象初始化另一个新对象时调用。这是最容易写出 Bug 的地方。// 拷贝构造函数// d2(d1) 或 Date d2 d1; 都会调这个Date::Date(constDated){_yeard._year;_monthd._month;_dayd._day;}关键点参数必须是自身类型的引用const Date d写成传值Date(Date d)会无限递归传值本身就要调拷贝构造直接编译报错。默认拷贝构造 逐字节浅拷贝。如果类里有指针成员如int* _a两个对象的指针指向同一块内存 → 析构时释放两次 → 崩溃。解决自己写深拷贝重新malloc一块同样大小的空间再复制内容。实用判断法则一个类如果显式写了析构说明有资源就大概率需要显式写拷贝构造全是内置类型且不指向资源的类如Date不用写。2.4 赋值运算符重载 vs 拷贝构造最容易混的一对拷贝构造是拿旧对象造新对象赋值重载是两个已存在的对象之间复制。// 赋值运算符重载// d2 d3 → d2.operator(d3)DateDate::operator(constDated){if(this!d)// 防止自赋值{_yeard._year;_monthd._month;_dayd._day;}return*this;// 返回引用支持 a b c 连续赋值}关键点Date d4 d1;是拷贝构造d4 刚出生d1 d3;是赋值重载两个都活着。参数用const 类名避免传值多一次拷贝返回类名支持连续赋值记得判断this ! d。默认赋值重载也是浅拷贝有资源的类要自己写深拷贝版本。三、运算符重载——以 Date 类为例运算符重载就是给、-、、这些符号赋予自定义类型的含义。Date类是练习运算符重载的最佳项目下面逐个拆解。3.1和核心在进位逻辑是核心复用——这是标准的复用技巧。// 日期 天数核心实现DateDate::operator(intday){_dayday;while(_dayGetMonthDay(_year,_month)){_day-GetMonthDay(_year,_month);_month;if(_month12){_month1;_year;}}return*this;}// 日期 天数复用 Date Date::operator(intday){Date temp*this;// 拷贝tempday;// 调operatorreturntemp;}关键点用while循环做进位——当_day超过当月天数时减掉当月天数、月份 1循环直到合法。月份超过 12 就进年重置为 1 月。不修改自身*this day后返回的是Date值不是引用产生临时对象原来的对象不受影响。GetMonthDay是辅助函数用静态数组monthDay[13]查表 特判闰年 2 月一句话带过。3.2-和-借位逻辑和对称方向反过来// 日期 - 天数核心实现DateDate::operator-(intday){while(_day0){--_month;if(_month1){_month12;--_year;}_dayGetMonthDay(_year,_month);}return*this;}// 日期 - 天数复用 -Date Date::operator-(intday){Date temp*this;temp-day;returntemp;}关键点_day减到负数时从上个月借天数加上上个月的天数月份 -1。月份小于 1 就借年重置为 12 月。-同样是复用-返回临时对象不改变自身。3.3 前置/ 后置后置版多一个int形参来区分int本身没实际意义只是语法标记。// 前置 d先加再返回自己DateDate::operator(){*this1;return*this;}// 后置 d先存临时对象再加返回临时对象Date Date::operator(int){Date tmp*this;*this1;returntmp;}关键点前置返回Date引用改完返回自己效率高。后置返回Date值先拷贝一份临时对象再改自己返回的是修改前的副本——“先用后加”。--完全对称把 1换成- 1即可。3.4 比较运算符用和推导一切不用每个都从零写用两个基础运算符就能推导出全部boolDate::operator(constDated){return_yeard._year_monthd._month_dayd._day;}boolDate::operator(constDated){if(_yeard._year)returntrue;if(_yeard._year)returnfalse;if(_monthd._month)returntrue;if(_monthd._month)returnfalse;return_dayd._day;}boolDate::operator(constDated){return*thisd||*thisd;}boolDate::operator(constDated){return!(*thisd);}boolDate::operator(constDated){return!(*thisd);}boolDate::operator!(constDated){return!(*thisd);}关键点按年→月→日的优先级逐级比较模仿字典序。写了和其余的!全部用逻辑组合推导代码量极少。这种写两个推导四个的做法在实际项目中非常实用。3.5 日期差小日期不断数次数intDate::operator-(constDated){// 假设 *this 是较大日期Date minDated;Date maxDate*this;intcount0;while(minDatemaxDate){minDate;count;}returncount;}关键点思路很简单小的日期不断数到和大的相等为止计数器就是天数差。时间复杂度 O(n)对于日期范围实际使用完全够用几年也就几千天。四、测试入口——让读者可复现#includeDate.hintmain(){Dated1(2026,8,5);d1.Print();// 2026 8 5Dated2(d1);// 拷贝构造d2.Print();// 2026 8 5d110000;d1.Print();// 2053年12月21日d2-10000;d2.Print();// 1999年3月2日Dated3(2026,1,1);intdiffd1-d3;cout相差 diff 天endl;return0;}把Date.h、Date.cpp和上面的main放进同一个项目编译运行就能看到效果。五、设计心得是基石是上层核心逻辑写直接return *this day一套逻辑两套接口杜绝重复代码。浅拷贝是定时炸弹指针成员 默认拷贝构造 两个对象指向同一块内存 → 析构两次 → 崩溃。有资源的类析构/拷贝构造/赋值重载要一起写。参数传引用能省拷贝除了后置/--必须返回值其他能返回引用就返回引用能传引用就传引用。用和推导全部比较运算符代码量从 6 个函数变成 2 个核心 4 个一行体可维护性好很多。类的分号、Date d3()的函数声明陷阱、拷贝构造参数必须传引用——这三点背下来考试和面试都省心。六、小结这篇文章覆盖了 C 类与对象上中两册的核心内容类的定义、访问限定符、this指针、六个默认成员函数、运算符重载以及一个完整的Date类实战。动手实操可以让记忆更深刻文中所有文件名/函数名如Date.h、operator、GetMonthDay均用反引号包裹防止 CSDN 自动链接。