96SEO 2026-02-23 12:22 2
}
做了几年开发,这个技术点一直是面试高频考点,今天系统梳理一下核心知识点和实战经验。
id="content_views">
这一篇我主要围绕着C++类中的默认成员函数来展开介绍,在这里我依然引用上一篇的例子来给大家引入C++类的默认成员函数。
一个汽车工厂,每当客户下单时,总会有一辆标准的轿车自动组装完成,神奇的是,当你完美的复制了这家工厂,你也可以生产出一摸一样的标准轿车。
在C++中,每个类就相当于这个“工厂”,即使你什么都没有写,编译器也会悄悄塞给你六个默认成员函数:它们自动完成对象的“组装”(构造)、“克隆”(拷贝构造)、“复制”(赋值)、“销毁”(析构)等基础工作,下面我们一起来揭开这些“幕后工人”的神秘面纱吧。
在C++中,默认成员函数指的是在类中没有声明,编译器会自动为类生成的成员函数。
C++标准规定了6种默认成员函数:默认构造函数、析构函数、拷贝构造函数、拷贝复制运算符、移动构造函数、移动赋值运算符。
在这6个默认成员函数种前4种最为重要(主要围绕这4种展开介绍),一旦你显式声明了拷贝构造、拷贝赋值或析构中的任意一个,编译器就认为你的类需要“特殊管理”(通常涉及资源所有权),从而不再自动生成移动操作。
这种设计迫使你思考类的拷贝语义与移动语义是否一致,避免隐式生成的错误。
下面让我来认识一下默认成员函数吧。
每个类都分别定义了他的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制成员变量的初始化,这些函数就成为构造函数。
构造函数的任务就是初始化类成员变量,无论我们什么时候创建类对象,都会执行他的构造函数。
构造函数的函数名和类名相同,它和其他函数不同的是,构造函数没有返回值类型(不需要写void),除此之外类似于其他的函数,构造函数也有一个参数列表(可能为空)和一个函数体(可能为空),类可以有多个构造函数,和其他重载函数一样,不同的构造函数之间必须参数和参数类型不一样,例如:
src="https://i-blog.csdnimg.cn/direct/ea001ea8a47d4063abbb4b1b1d77a030.png">
构造函数不能被声明成const的,当我们创建了类的一个const对象,直到构造函数完成初始化的过程,它才会具有“常量”属性。
2.1默认构造函数
类通过一个特殊的函数来进行控制默认初始化,这个函数就叫做默认构造函数,默认构造函数无需任何实参。
来看一个例子将上面的代码种的构造函数删除之后,默认构造函数是如何给类成员变量定义值的:
src="https://i-blog.csdnimg.cn/direct/ea66def27de143d6a4b11eb98c646350.png"
width="613">
可以看到我们中的类成员变量被赋给了一个随机值,这就是默认成员函数的一个特性:如果我们没有显式的定义类的构造函数,编译器会隐式地为我们创建一个默认构造函数来初始化我们的类成员。
默认构造函数按照下面的规则来初始化类成员变量:
- 如果类成员变量存在缺省值(初始值),就用它来初始化。
- 否则,就是默认构造初始化。
我们看一个简单的例子:
src="https://i-blog.csdnimg.cn/direct/68b9f609b7304610968fc612df1b3ffe.png"
width="582">
这时候我们有的小伙伴会这样想吧:既然类会自动生成默认构造函数,这么方便,那所有的类都让类来自己生成默认构造函数好了。
这种想法是错误的,类的默认构造函数只适用于简单的类,
这里有几个原因:
- 对于某些类来说,默认构造函数可能执行错误的操作,如果我们的类中存在内置类型和复合类型(数组和指针),对他们进行初始化,他们的值将未被定义的。
含有内置类型或者复合类型成员的类应该在类的内部初始化类成员变量,或者定义一个构造函数。
针对这个原因我给大家举一个简单点的例子:
!important">
#includeusing
obj;obj.printValues();//obj.setValue(10);
//同样会出现问题return
src="https://i-blog.csdnimg.cn/direct/74f3e02be1c445fb9e081417e0c65dd4.png"
width="809">
在上面这段代码种,m_value的值是未定义,可能是内存中上次残留的数,m_ptr是没有任何合法指向的,他就是一个野指针,通过对它的解引用访问或者修改内存,程序会直接崩溃!
当你的类包含内置类型或指针时,绝对不能依赖编译器生成的默认构造函数。
- 如果类中包含一个其他的类类型的成员并且这个类成员还没有默认构造函数,那么编译器将无法初始化该类成员,对于这样的类来说,我们必须自己定义构造函数,否则这个类将没有可以用的构造函数。
这也是一个原因,我们也来简单的举一个例子:
src="https://i-blog.csdnimg.cn/direct/2dbd4483db744f38a4a35bd2d42990c7.png"
width="1334">
在这里我首先就想到了二叉树,那就使用二叉树为大家举了个简单的例子,在TreeNode类中我们自己定义了一个构造函数,从而没有
TreeNode()
这种默认构造函数,在BinaryTree类中我们假设编译器会为我们生成一个默认构造函数
BinaryTree()。
但实际上,这个生成的构造函数会去尝试调用类成员的默认构造函数。
我们的
TreeNode
没有默认构造函数!
所以BinaryTree类的默认构造函数就被编译器“隐式地删除”,导致BinaryTree本身也无法被实例化。
构造函数大致分为:无参构造、全缺省构造和默认构造,这三个只能存在一个,一山还不容二虎呢,这三个要是同时存在那还得了,无参构造和全缺省构造虽然构成函数重载,但是调用会发生歧义,简单举一下这三个构造的例子:
punlic://无参构造Myclass(){val=1;}//全缺省构造Myclass(int
n=1){val=n;}
2.2初始化列表
在刚刚我举例二叉树那里的例子时,TreeNode类中类成员变量初始化方式和我们常见的不一样,这里我使用了初始化列表。
初始化列表是C++构造函数地一个重要组成部分,它允许我们在创建变量时直接初始化类成员变量,而不是默认初始化再赋值。
它位于构造函数参数列表之后,函数体之前,使用
"开头,用"
y_value):_x(x_value),_y(y_value){}
private:int
在构造函数中我们每一个类成员变量都会走一遍初始化列表,如果类成员变量没有在构造函数的初始化列表显式的初始化,那么这个类成员变量会在函数体之前执行默认构造并完成初始化,我们对上面的例子进行一个简单的修改来看看结果:
src="https://i-blog.csdnimg.cn/direct/86dedaaed48f484ebce20755d589368c.png"
width="633">
在这里我只让Myclass类中的_x走初始化列表显式初始化,_y没有显式地初始化,但他还是默认初始化了一个随机值。
那为什么我们需要初始化列表呢?
根本原因是C++创建对象地过程(内存分配和初始化的分离),下面我们深入地讲解一下(很重要):
踩坑记录:
我在实际项目中遇到过一个问题,这个配置在开发环境正常,但生产环境会报错。
后来发现是因为生产环境的版本不一致导致的。
建议大家在部署前一定要检查版本兼容性。
C++在创建一个对象的的过程可以分为两步:内存分配和初始化。
- 内存分配:当程序执行到了对象的定义点(比如在栈上为局部变量分配空间),系统会为这个对象分配足够的空间,这时候申请到的空间的内容还未初始化,这时候可能会存放之前使用留下的无用的数据,这个对象的生命周期并未开始。
- 初始化:初始化是让我们所创建的变量拥有一个有效的状态,初始化有很多种方法,但最核心的还是构造函数,构造函数又分为:初始化列表和函数体。
- 初始化列表:在变量进入函数体之前,初始化列表中的成员都会被初始化,对于类类型成员,会直接调用它所对应的构造函数,对于内置类型成员,如果初始化列表给了初始值,就用这个值来初始化,否则它们不会初始化,处于一个未定义状态。
- 构造函数体:在初始化列表执行完毕后,进入构造函数体。
此时,所有成员都已经经过初始化(或者至少已经分配了空间,对于内置类型如果没有在初始化列表中指定,则未初始化)。
我们可以对成员进行赋值、调用其他函数等操作。
这种分离意味着,如果我们没有在初始化列表中初始化成员,那么对于类类型的成员,编译器会在初始化列表中调用其默认构造函数(即使我们后面会在构造函数体中给它赋值)。
这可能导致额外的开销,因为默认构造可能做了些工作,而我们随后又用新值覆盖了它。
对于我们没有在初始化列表中指定的内置类型,我们在构造函数体中给它们赋值,实际上是让他们现处于一种未定义状态,然后赋值,完成初始化。
这里确实比较晦涩,打一个简单的比喻:想象你搬进一个新家(构造对象),内存分配就像你拿到了新家的钥匙,里面可能是空的或者之前住户的一些杂物(无用数据),初始化列表就像在搬进去之前,你请了一个装修队,他们按照你的要求把房子装修好,布置好基本的家具(初始化)。
构造函数体就像你搬进去之后,再根据个人喜好调整家具的位置,或者添加一些装饰(赋值)。
如果你没有请装修队(没有走初始化列表),那么你就得自己从空房子开始布置一切(在构造函数体中做所有事情)。
但问题在于,如果房子本身就带了一些基础装修(类成员有默认构造函数),那么开发商(编译器)会先做一个简单的装修(调用默认构造函数),然后你进去再重新调整,这可能会浪费一些工作。
我们再举一个简单的代码例子:
这是赋值,不是初始化。
str之前已经通过默认构造函数初始化了。
value
private:string
2.2.1必使用初始化列表的情况
我们完全可以根据自己的喜好来完成类成员的初始化,但是有三种情况必须使用初始化列表:引用成员变量、没有默认构造的类类型成员变量、const成员变量。
情况一:引用成员变量
endl;//}//错误尝试:在构造函数体内绑定引用Student(string
int&
zhangsanScore);s1.show();return
src="https://i-blog.csdnimg.cn/direct/3d5382b7c8dd4ec5b0bf460d94fcdfad.png"
width="799">
引用必须在定义时初始化,并且一旦初始化后就不能再绑定到其他变量。
所以在类中,引用成员必须在构造函数的初始化列表中初始化,不能在构造函数体内赋值(因为构造函数体内是赋值,而不是初始化绑定)。
情况二:没有默认构造的类类型成员
src="https://i-blog.csdnimg.cn/direct/5d59c60c34b949e789aa7a0c6d0501cc.png"
width="652">
如果一个类没有默认构造函数,那么我们在创建该类的对象时必须提供参数。
因此,在包含该类类型成员的类中,必须在初始化列表中调用该成员的带参构造函数,否则编译器会尝试调用默认构造函数但找不到,从而报错。
情况三:const成员变量
src="https://i-blog.csdnimg.cn/direct/bd68585a65d348659ab194b7012683c1.png"
width="730">
const变量必须在定义时初始化,之后不能再修改。
因此,const成员变量必须在构造函数的初始化列表中初始化,不能在构造函数体内赋值。
2.2.2成员初始化顺序
在初始化列表中,成员初始化顺序和它们在类中定义的先后顺序一样。
构造函数初始化列表中的初始化位置的先后并不影响实际初始化顺序,一般来说,初始化的顺序并没有太多的要求,如果一个成员是用另一个成员来初始化,那么这两个成员的初始化顺序就很关键了,举一个简单的例子:
从构造函数的初始值上来看仿佛是先用val的值初始化了b,然后再用b初始化a,实际上,实现初始化a,所以这个初始值的效果是试图使用未定义的b来初始化a。
在C++11时支持在成员变量声明时给缺省值,这个缺省值主要是给没有显式在初始化列表初始化的成员变量,下面是一个简单的例子:
public:Date():_year(2026),_day(21){cout<<_year<<"年"<<_month<<"月"<<_day<<"日"<
}=""
code="">
class="hljs">
src="https://i-blog.csdnimg.cn/direct/7914a70cdf2d4778ae7fcad48520c84c.png"
width="478">
初始化列表总结:
- 无论是否显示书写初始化列表,每个构造函数都存在初始化列表。
- 无论是否在初始化列表显式的初始化成员变量,每个成员变量都会走初始化列表。
style="text-align:
src="https://i-blog.csdnimg.cn/direct/62803e539fb140cab40873d61f8008f1.png">
3.析构函数
析构函数和构造函数的功能相反,析构函数也是C++类的一个成员函数,当对象声明周期结束时会自动调用,用于清理空间操作,它的名称与类名相同,但在前面加上波浪号
~。析构函数没有返回类型,也不接受任何参数。
其大致语法格式为:
在一个析构函数中,首先执行函数体,然后按初始化顺序的逆序进行销毁。
在析构函数中不存在类似构造函数的初始化列表来控制类成员变量的销毁,析构部分是隐式的,类成员变量销毁完全取决于其类型,销毁类类型的成员变量,会调用其对应的析构函数,内置类型没有析构函数,所以销毁内置类型什么都不要做。
隐式地销毁一个内置类型指针成员不会删除他的指向。
也就是说:当一个内置类型的指针被销毁,它本身会被销毁,但是它所指向的内存不会被销毁,可能会导致内存泄漏。
这里给大家举一个简单的例子:
src="https://i-blog.csdnimg.cn/direct/bd24cc33794a4b3db2d33bb6c1c22241.png"
width="632">
何时调用析构函数
- 离开作用于的局部变量,在离开作用域时会调用析构函数。
- 通过动态分配空间的对象,必须使用delete来触发析构函数,如果不delete会发生内存泄漏。
- 临时对象(匿名对象)在创建它的表达式结束时销毁。
class="post-meta-container">
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback