尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
类和对象(三)
我们紧接上回继续深入讲解类的默认成员函数。在 C 中编译器会在特定条件下自动生成这些成员函数它们虽不显式出现在代码里却在对象的构造、拷贝、赋值与析构等关键生命周期环节中扮演着不可或缺的角色。理解它们的生成规则与行为是编写高效、安全 C 代码的重要基础。拷贝构造拷贝构造函数是类的默认成员函数之一用于以已存在的同类对象为蓝本创建并初始化一个新对象。例如Date类classDate{public:Date(intyear1,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_day;}private:int_year;int_month;int_day;};我们用Date类实例化出对象d1和d2然后用d1去初始化d2是这样写的intmain(){Dated1(2026,9,13);//创建一个实例化对象Dated2(d1);//用已经初始化的对象d1去初始化d2return0;}为了增强代码的可读性将d1拷贝给d2的操作也可以这样写Date d2 d1和Date d2(d1)是一个意思。拷贝构造函数如何写呢它是构造函数的一个重载形式第一个参数必须是类类型的引用后面也可以跟多个参数但这些参数必须设置缺省值。我们Date类的拷贝构造函数就可以这么写//拷贝构造函数Date(constDated)//第一个参数必须是类类型引用{_yeard._year;_monthd._month;_dayd._day;}因为我们不会修改传过来的对象的值所以可以用const修饰更安全些。至于为什么第一个参数必须是类类型引用而非单纯的类类型呢这个问题就要涉及到函数的传值传参了。1. 为什么第一个参数必须是类类型引用例如我们定义一个Func函数它的函数参数为Date类型。voidFunc(Date d){;}我们调用该函数时形参d会拷贝传过来的Date类对象的内容这个过程中就会调用拷贝构造函数。因此我们调用Func函数首先进入的其实是拷贝构造函数拷贝完成后才会进入Func内部的函数体。所以如果我们的拷贝构造函数第一个参数不是引用的话就会出现大问题造成无穷递归。2. 浅拷贝与深拷贝浅拷贝可以简单理解为值的拷贝上面我们写的Date类的拷贝构造函数就是一个浅拷贝。当我们没有显式编写拷贝构造函数时编译器会自动生成一个拷贝构造函数。对于内置类型它会完成值拷贝浅拷贝对于自定义类型则会调用对应的拷贝构造函数完成拷贝。我们来看一个典型场景classStack{public:Stack(intn4){_array(int*)malloc(sizeof(int)*n);_Top0;_capacityn;}~Stack(){if(_array!NULL)free(_array);_arrayNULL;_Top0;_capacity0;}private:int*_array;int_Top;int_capacity;};};因为我们手动申请了空间所以要显式编写析构函数去释放。这里我们没有编写拷贝构造函数就会出问题。intmain(){Stack st1;Stackst2(st1);return0;}也就是在析构函数的时候出现了问题。因为编译器自动生成的拷贝构造函数只进行浅拷贝指针_array也是这导致了一个问题st1和st2中的指针_array指向了同一块内存空间。每个对象在销毁时都要调用析构函数这导致这块内存空间被重复释放了两次程序直接崩溃了。编译器自动生成的拷贝构造函数不能满足我们的需求所以我们需要显式地定义拷贝构造函数来解决浅拷贝问题。Stack(Stackst){if(st._array!NULL){//申请和st一样大的空间_array(int*)malloc(sizeof(int)*st._capacity);if(_arrayNULL){perror(malloc);exit(-1);}//将内容按字节拷贝memcpy(_array,st._array,sizeof(int)*_capacity);_Topst._Top;_capacityst._capacity;}}这样st1内容拷贝给st2的时候两个对象的成员变量_array指向的就是两块一样大小且不同的空间了在析构时不会产生冲突。一般来说需要显式定义析构函数的类也需要显式定义拷贝构造函数。运算符重载运算符重载允许我们为自定义类型重新定义运算符的行为使对象能够像内置类型一样使用、-、、等运算符进行运算和操作。它本质上是编写一个名为operator后跟运算符符号的特殊成员函数。合理使用运算符重载可以让代码更直观、更符合直觉但也要遵循运算符原有的语义和优先级避免滥用导致可读性下降。运算符重载函数模板如下//函数名为关键字operator 要重载的运算符Dateoperator(Dated,intday){;}可以看到运算符重载是具有返回值类型的但在使用它时还有一些注意事项1.运算符重载的参数列表中必须有一个类类型的参数。2.C规定有五个运算符不能重载.*: ?sizeof( ).:: 运算符重载的参数个数与运算符的操作数个数相同例如二元运算符有两个参数一元运算符就有一个。默认第一个参数作为左操作数第二个参数作为右操作数。运算符重载为成员函数的时候因为成员函数隐含地有 this 指针指向一个类对象且是参数列表第一个参数所以参数个数要减一。比如说Date类重载运算符为它的成员函数和全局函数是这样的classDate{public:intGetMonthDay(intyear,intmonth){intMonthDay[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};if(month2((year%40year%100)||year%4000))return29;returnMonthDay[month];}//函数声明Dateoperator(intday);private:int_year;int_month;int_day;};//运算符重载DateDate::operator(intday){_dayday;while(_dayGetMonthDay(_year,_month)){_day-GetMonthDay(_year,_month);_month;if(_month13){_year;_month1;}}return*this;}这里将运算符重载写成全局函数Date operator(Date d, int day)就有问题了因为_year、_month、_day都是类的私有成员外部是无法直接访问到的对此我们有三种解决办法使用Get函数声明为类的友元函数定义成类的成员函数这里我们先声明为类的友元函数友元函数就是在函数前加关键字friend在类里声明该函数就可以访问类里的私有成员了。friend Date operator(Date d, int day)Dateoperator(Dated,intday){d._dayday;while(d._dayd.GetMonthDay(d._year,d._month)){d._day-d.GetMonthDay(d._year,d._month);d._month;if(d._month13){d._year;d._month1;}}returnd;}调用该函数方法如下intmain(){Dated1(2026,9,13);d130;//调用运算符重载函数return0;}也可以显式地调用运算符重载函数。d1.operator(d1,30);//显示调用运算符重载区分前置、后置 /–对于、--运算符来说有前置和后置之分那么编译器是如何区分的呢C 规定后置版本比前置版本多一个int类型的参数。//前置Dateoperator(Dated);//后置Dateoperator(Dated,int);//前置--Dateoperator--(Dated);//后置--Dateoperator--(Dated,int);/运算符重载对于和运算符重载是流插入符cout作为固定的左操作数配合使用cout是ostream类的对象所以参数中有一个是ostream类型然后返回ostream类型的引用重载为Date类的成员函数是这样的ostreamoperator(ostreamout){out_year年_month月_day日endl;returnout;}相应地运算符重载也是如此istreamoperator(istreamin){cout请输入年月日;in_year_month_day;returnin;}但是我们之前提到过运算符重载函数默认第一个参数为左操作数第二个参数为右操作数成员函数隐含的this指针是放在第一个参数位置的这就导致我们调用这两个运算符重载函数时要这样写intmain(){Date d2;//调用运算符重载函数d2cin;//d2.operator(cin);d2cout;//d2.operator(cout);return0;}这跟我们平时写的完全颠倒了看起来很不习惯为了统一代码规范我们通常将这两个运算符重载为全局函数将ostream/istream类型的参数放在第一个位置。ostreamoperator(ostreamout,Dated){outd._year年d._month月d._day日endl;returnout;}istreamoperator(istreamin,Dated){cout请输入年月日;ind._yeard._monthd._day;returnin;}intmain(){Dated1(2026,9,13);cind1;coutd1;return0;}赋值运算符重载赋值运算符是类的六大默认成员函数之一当我们没有显式定义时编译器生成的函数会完成浅拷贝操作也会出现与拷贝构造函数相同的空间问题。通常赋值运算符是支持连续赋值的所以我们需要把表达式的值返回Date类的赋值运算符重载是这样写的//赋值运算符重载Dateoperator(constDated){_yeard._year;_monthd._month;_dayd._day;return*this;}intmain(){Dated1(2026,9,13);Dated2(d1);Date d3;//连续赋值d3d2d1;//d3.operator(d2.operator(d1));return0;}一般情况下需要显式编写析构函数的类也需要显式编写赋值运算符重载函数。取地址符重载取地址符重载函数和const取地址符重载函数都是类的默认成员函数我们不显式编写时编译器也会自动生成完成返回对象地址的功能。一般不需要我们去显式编写除非不想让别人获取这个对象的地址或者恶意返回一个伪造的地址。//取地址符重载Date*operator(){returnthis;}//const取地址符重载constDate*operator()const{returnthis;}这个后面跟的 const 是什么意思我们下面会讲到。而且这两个函数隐含的 this 指针类型不同所以构成函数重载。const 成员函数const 成员函数的意思就是将成员函数的this指针类型由类类型 * const变为const 类类型 * const。例如Date类的 const 成员函数就是把this指针类型由Date * const变为const Date * const。当我们创建一个Date类对象然后被const修饰时这时我们调用Print函数就会出现错误。classDate{public:Date(intyear1,intmonth1,intday1){_yearyear;_monthmonth;_dayday;}voidPrint(){cout_year/_month/_day;}private:int_year;int_month;int_day;};intmain(){constDated1(2026,9,13);d1.Print();return0;}这个问题本质上就是权限的放大我们用const修饰这个对象是不希望内容被修改而Print函数内部隐含的this指针类型为Date * const是允许修改对象内容的。所以我们需要把this指针的类型改为const Date * const这样才不会产生权限放大的问题。将其变为const成员函数的操作是在函数参数列表后加const声明和定义都要加。voidPrint()const{cout_year/_month/_day;}因此为了支持更多操作对于不修改对象内容的成员函数我们都可以将其写成const函数。总结到这里类的六大默认成员函数就全部讲完了它们分别是构造函数拷贝构造函数析构函数赋值运算符重载取地址符重载含 const 版本其中构造函数、拷贝构造函数、析构函数、赋值运算符重载这四个编译器会在特定条件下自动生成但自动生成的版本往往只进行浅拷贝对于需要动态申请资源的类我们必须手动实现深拷贝否则就会出现内存重复释放、程序崩溃等问题。而取地址符重载和 const 成员函数一般不需要我们手动编写编译器自动生成的版本已经能够满足绝大多数需求除非有特殊的安全需求。一句话总结默认成员函数是 C 类的基础设施理解它们的生成规则与行为是编写高效、安全 C 代码的关键。
RELATED

相关推荐

Web前端JS逆向实战:从加密参数定位到签名算法还原

Web前端JS逆向实战:从加密参数定位到签名算法还原

不算标题党地说,这篇就是讲“当你在浏览器里看到一串看不懂的加密参数时,怎么一步步把它逆出来”。我自己做前端调试和安全评估这些年,处理过不少这类“加了壳”的web端项目,最近又拿某个内容社区平台的web端练了一轮手&#xff0…

📅 2026/9/15 7:29:16
Cadence17.4初始化设置

Cadence17.4初始化设置

目录 一、软件介绍 二、Capture CIS初始化设置 三、PCB Editor初始化设置 四、创建公共原理图库、焊盘库和封装库 一、软件介绍 绘制原理图: 使用模板绘制PCB封装: 又叫Allegro,绘制PCB封装和布局布线: 绘制焊盘&#xff…

📅 2026/9/15 7:29:16
软考中项项目管理:项目质量管理12大默写考点与冲关技巧

软考中项项目管理:项目质量管理12大默写考点与冲关技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/15 7:29:16
MORE NEWS

更多资讯

📰

马尾辫效应:长尾分布与时序预测中的尾部问题治理指南

1. 一个被低估的细节:为什么顶尖团队都在死磕“马尾辫”先别笑,我说的不是发型师眼里的马尾辫,而是搜索、推荐、图像识别、视频理解、姿态估计这些系统里那个甩不掉的尾巴结构——无论是用户搜索词后面拖着的长尾意图,还是视频里人…

📰

2026年AI编程实战地图:场景化工具链协同指南

1. 这不是工具清单,而是一份2026年AI编程生产力的实战地图“2026年AI编程工具大全,33个主流工具一次看懂”——看到这个标题,你脑子里浮现的可能是一页密密麻麻的软件名官网链接一句话介绍的表格。但我要坦白告诉你:那种清单&…

📰

wordpress登陆入口修改实战案例:新手告别域名服务器搞不懂的焦虑

wordpress登陆入口修改实战案例:新手告别域名服务器搞不懂的焦虑 很多安徽转行做网站的新手,一听到“修改WordPress后台”就头大。脑子里全是乱麻:域名到底指哪?服务器又在哪?我是不是得先买个服务器才能动代码?别慌,这种“域名服务…

📰

RAG检索评估:把忠实度与噪声敏感度做成CI门禁的完整实现

RAG 上线后最常见的一类故障不是"模型变笨了",而是答案开始胡说,但你翻日志翻不出原因——检索回来的 chunk 看着没问题,生成也没报错,可回答就是不对。根因通常藏在两个地方:检索阶段把关键段落漏掉了&…

📰

智能任务协同Agent:自动化流水线新范式

智能任务协同Agent技术文档 1. 概述 智能任务协同Agent是面向多任务自动化场景的智能代理程序,核心目标是拆解复杂业务目标、调度子任务、协同多工具执行流程,自动完成任务规划、状态管理、结果校验,降低人工介入成本。该Agent支持串行、并行…

📰

类和对象(三)

我们紧接上回,继续深入讲解类的默认成员函数。在 C 中,编译器会在特定条件下自动生成这些成员函数,它们虽不显式出现在代码里,却在对象的构造、拷贝、赋值与析构等关键生命周期环节中扮演着不可或缺的角色。理解它们的生成规则与行…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬