news 2026/10/19 7:22:27

从零开始的C++生活——第二讲——02

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始的C++生活——第二讲——02

接着讲类和对象的相关知识。

今天我们首先介绍一下类的默认成员函数。

默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数,一个类在不手动写的前提下会默认生成6个成员函数,这六个中最重要的是前四个,注意的是C++11后还会增加两个默认成员函数。

这四个值得注意的默认成员函数分别是构造函数、析构函数、拷贝构造、赋值重载。

针对默认成员函数,我们需要从以下2个角度思考,首先,当我们不手动实现的时候,编译器默认生成的函数是否满足我们的需求?如果不满足,我们应该如何手动实现?

首先介绍一下构造函数。

构造函数相当重要,但是构造函数的名字本身就存在一个误区,他并不是用来构造的,你以为构造函数可以开辟空间创造对象,那你想多了,他只是用来初始化的。

构造函数有以下几个特点:

函数名和类名相同

没有返回值

对象在实例化时系统会自动调用对应构造函数

构造函数可以重载

如果我们没有主动显式定义构造函数,C++会自动生成一个无参的构造函数,倘若用户自己实现了,那编译器就不会再生成。

class A { public: A(int d) { c = d; } int add(int a, int b) { return a + b; } private: int c; };

以上就是定义和调用构造函数的过程。

很显然,这是我们自己写的构造函数。事实上,构造函数还有一种叫做默认构造函数,这里要跟上边那个无参的做区分,上边那个无参的构造函数是我们不显示实现时的默认构造函数,还有两种,一个是全缺省构造函数,一个是无参构造函数,这两个都是我们可以手动实现的默认构造函数,三者只能存在一个,向我们刚刚写的这个一个参数的,就不是默认构造函数。简单来讲,默认构造函数就是不传参也可以调用的构造函数 。

当我们不写构造函数时,编译器默认生成的构造函数对内置类型成员变量的初始化是不做要求的,这完全取决于编译器,可能会初始化,可能就是随机值,但是,对自定义类型的成员变量,调用编译器自己写的构造函数时,会自动调用自定义类型的默认构造函数。如果自定义类型里我们手动实现了非默认构造函数,那么系统就会因为找不到这个自定义类型的默认构造函数而报错,我们需要初始化列表解决这个问题,现在先不讲解。

有一点需要注意,当我们调用无参的构造函数时不需要加(),如果你加了,系统会因为分不清你这里是调用还是声明而报错。

其实通过上述情况你也能看出来,大部分时间,我们都是需要自己写构造函数的。

接下来是析构函数。

如果你觉得构造函数类似于数据结构的init方法,那么析构就是destroy,和构造函数正相反。

析构函数并不是对这个对象进行销毁,而是对对象内需要释放资源的资源进行释放。

析构函数是类名前加~

和构造类似,析构函数无返回值,不一样的是,析构函数不允许含参数,换句话说,析构函数不允许重载,只有一个。

假如我们不手动实现析构函数,那么编译器也会自己默认生成一个。

当对象的生命周期结束时,系统会自动调用析构函数。

和构造函数类似,编译器自己生成的析构函数不会对内置类型处理,对于外置类型,会自动调用他们的析构函数。

这里需要注意,假如我们显式实现了析构函数,且没有对自定义类型进行处理,系统也会自动调用自定义类型的析构函数,也就是说,无论怎么样,自定义类型的析构函数一定会被调用。也就是说,如果你自己写一个析构函数,只需要处理你的内置类型就好,自定义类型交给系统。

感觉析构函数很多场景下是不用自己写的对吧?其实简单来说,如果你需要释放内置类型资源,那你就要手动实现析构函数,反之则不需要。

C++规定,一个局部域内的多个对象,后定义的先析构。

接下来,我们介绍一下拷贝构造函数。

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,拷贝构造是一个特殊的构造函数。

拷贝构造函数是构造函数的重载。

C++规定,传值传参自定义类型或者传值返回自定义类型会自动调用这个自定义类型的拷贝构造函数,如果你写拷贝构造函数采用传值的方式,编译器会报错,为什么呢?当你传参自定义类型,会调用它的拷贝构造函数,因为你写的拷贝构造是传值传参,所以接着会再次调用拷贝构造,无穷无尽直至程序崩溃。虽然一般我们的拷贝构造函数只有一个当前类类型的引用这一个参数,但从语法的角度是可以有其他参数的,只不过都要有默认值,也就是缺省参数。

C++规定,自定义类型进行拷贝行为必须调用拷贝构造,所谓拷贝行为,你传值传参或者传值返回本质都是拷贝。

如果没有显示的定义拷贝构造,编译器会自动生成拷贝构造函数,自动生成的拷贝构造函数会对内置类型成员变量完成浅拷贝,所谓浅拷贝就是一个字节一个字节拷贝进去,对自定义类型的成员变量则会调用他们自己的拷贝构造函数。

补充一点,为了避免对引用传参的修改,一般加上const修饰。

那你可能会有点疑惑,既然全是内置类型的时候会实现拷贝,自定义类型还会调用他们自己的拷贝构造,那我为啥还要自己实现?明明已经达到效果了。

我这里用一段代码演示一下。

#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; class Arr { public: Arr(int size) { _a = (int*)malloc(sizeof(int) * size); _capacity = size; _top = 0; } private: int* _a; int _capacity; int _top; }; int main() { Arr a(4); Arr a1 = a; return 0; }

看这段代码的执行效果,你发现了什么?看起来好像没啥问题,但是,两个不同对象的_a成员变量指向的却是一块空间,这显然是不符合逻辑的对吧。

因此我们总结一下,当你的成员变量要向内存申请资源时,就要手动的实现拷贝构造函数。话句话讲,我们知道编译器默认实现的析构函数不对内置类型处理,但是会自动调用自定义对象的析构函数,当我们需要写析构函数的时候就代表我们类中有资源需要释放,和拷贝构造函数实现的前提是一致的。

值得注意的是,引用返回必须要保证引用对象没有被销毁,你返回一个函数内部定义的局部变量的引用那合适吗?都成野引用了。

前边我们提到const引用的传参,一般来讲我们不涉及修改实参时,都最好传const引用,但是Swap函数是涉及到实参的交换的,所以传const引用反而解决不了问题。

除了上边写到的拷贝构造方式,我们也可以用Arr a(b);的方法调用,这是一样的。

在讲赋值运算符重载函数之前,先介绍一下运算符重载。

当我们涉及到自定义类型的比较或者加减什么其他的运算的时候用系统内置的运算符是不够用的,C++支持运算符的重载,我们可以自己重载运算符达到这种效果。

class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; bool operator==(const Date& a, const Date& b) { return a._year == b._year && a._month == b._month && a._day == b._day; } int main() { Date a(2026, 10, 18); Date b = a; if (b == a) { cout << "true" << endl; } if (operator==(a, b)) { cout << "true" << endl; } return 0; }

这里我们重载了Date类型的==运算符。事实上,C++也规定类型对象使用运算符时必须要调用重载运算符,不然会编译报错。

在上边例子中operator==就是这个重载运算符函数的函数名,重载运算符参数个数和运算符作用的运算对象个数相同。

重载运算符函数也可以写成成员函数,你自己想想,本身重载运算符函数就是和类型绑定的,不然你重载了给谁用?所以自然是可以写成成员函数,上一讲我们还提到过,成员函数默认隐式传入一个this指针,之所以前边讲那三个默认成员函数没怎么提这个事是因为写法上不太影响,但是这里,如果我们要写在类型中,传参个数就要少一个,因为this本身指向的对象就是其中一个操作数。

class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } bool operator==(const Date& a) { return _year == a._year && _month == a._month && _day == a._day; } //private: int _year; int _month; int _day; }; //bool operator==(const Date& a, const Date& b) //{ // return a._year == b._year // && a._month == b._month // && a._day == b._day; //} int main() { Date a(2026, 10, 18); Date b = a; if (b == a) { cout << "true" << endl; } if (a.operator==(b)) { cout << "true" << endl; } return 0; }

当运算符重载后,其优先级和结核性与对应的内置类型运算符保持一致。

注意,我们不能通过运算符重载增加一个新的运算符,这是不被允许的,C++规定.*, ::, sizeof, ?:, .

这五个运算符不允许重载。

C++规定重载运算符必须有一个类类型的参数,你不能修改内置类型的运算符含义对吧,本来1<2返回true,你非要给他改成false这从逻辑上都会不合理。

倘若成员和全局都定义了运算符重载函数,优先调成员的。

还有一点细节,当我们实现全局的运算符重载函数时,其实你是没办法访问成员对象的,因为默认是private嘛,目前来讲,除了放到公有、重载为成员函数以外还可以采用提供get方法,JAVA经常喜欢用这种方法实现封装。

铺垫了这么多,终于要来到这一讲的末尾,最后为大家介绍类型中的默认成员函数赋值运算符重载函数。

赋值运算符重载函数用于两个已经存在的对象之间的直接拷贝赋值,这里和拷贝构造的区别是拷贝构造是将一个对象赋值给另一个要创建的对象。

赋值运算符重载函数的参数建议写成引用传参,和拷贝构造不同的是,这里不传引用也不会报错,为什么呢?你想啊,传值进来调用自定义类型的拷贝构造,但是人家的拷贝构造可是正儿八经引用传参,所以拷贝构造完可以正常返回,并不会造成错误。

除此之外呢,赋值运算符重载是有返回值的,同样最好写成引用返回,一个道理,为了提高效率。

没有显示实现时,和拷贝构造类似,编译器默认生成的赋值运算符重载函数会执行浅拷贝(对内置类型),外置类型则是会调用他们的赋值运算符重载函数。

同样和拷贝构造类似,当类型没有显式实现析构函数时也就不需要弄一个手动的赋值运算符重载函数了,因为你这里没有额外资源。

多说一点,赋值运算符重载函数之所以有返回值是为了支持连续赋值的情况。

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

顶级PS室内效果图后期教程:家装客厅/书房/酒吧餐厅4章实战案例

资源概述 这是一套以Photoshop为核心工具的室内效果图后期处理教程&#xff0c;整体围绕"效果图华丽逆袭"这一主题展开。资源按照章节结构组织&#xff0c;通过不同场景的实际案例&#xff0c;帮助学习者掌握从普通渲染图到高质量室内效果图的后期处理流程。 配套内…

作者头像 李华
网站建设 2026/10/19 7:21:31

用纯C实现UDP字典服务:理解无连接协议的关键

做网络编程练手项目&#xff0c;大部分人第一站是 TCP 回显服务&#xff0c;第二站是聊天室。但我越来越觉得&#xff0c;想真正理解"网络协议的本质是权衡"这件事&#xff0c;光写 TCP 是不够的&#xff0c;UDP 才是那个逼你思考的题目。所以我用纯 C 在 Linux 上写…

作者头像 李华
网站建设 2026/10/19 7:21:25

分类与目标检测评估指标详解:从准确率、F1到mAP的避坑指南

做过一段时间模型评估的人大概都有过这种体验&#xff1a;模型在测试集上准确率明明有98%&#xff0c;可一跑到真实业务场景里就露馅了&#xff0c;被业务方追着问"你这模型是不是不行"。问题往往不是模型坏了&#xff0c;而是你从一开始就选错了度量标准。机器学习指…

作者头像 李华
网站建设 2026/10/19 7:19:54

函数 函数 函数

函数的概念库函数自定义函数形参和实参return语句数组做函数参数嵌套调用和链式访问函数的声明和定义 1. 函数的概念 C语言中函数的概念可以理解为子程序&#xff1a;函数就是一段用于完成某项特定任务的代码。 C语言的程序实际上是由无数个小的函数组合而成的。也可以说&#…

作者头像 李华
网站建设 2026/10/19 7:19:36

DeepSeek LeetCode 384. 打乱数组 Java实现

LeetCode 384. 打乱数组 Java 实现 这道题的核心是 Fisher–Yates 洗牌算法&#xff08;也叫 Knuth 洗牌算法&#xff09;&#xff0c;它能保证 n! 种排列等概率出现。 关键点 保存原始数组的副本&#xff1a;reset() 要能恢复到初始状态&#xff0c;所以构造时必须克隆一份 or…

作者头像 李华