news 2026/9/30 7:21:55

类和对象(四)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
类和对象(四)

在 C++ 的面向对象编程中,构造函数是每个类都绕不开的核心话题。它负责在对象创建时完成初始化,是对象生命周期中第一个被调用的成员函数。本文将继续深入探讨构造函数的进阶用法——初始化列表,并进一步讲解类型转换与static 成员这两个与对象创建、访问密切相关的知识点。通过本文的学习,你将理解为什么某些成员变量必须放在初始化列表中初始化,以及如何利用构造函数实现类型之间的转换、如何借助 static 成员实现跨对象的共享数据。

构造函数(续)

之前我们所讲的构造函数,是用于在实例化对象时对这个对象进行初始化。我们可以显式地定义构造函数,不定义时编译器也会自动生成。这里我们再深入讲解构造函数中最重要的部分:初始化列表。

关于我们之前Date类构造函数的写法是这样的:

classDate{public:Date(intyear=1,intmonth=1,intday=1){_year=year;_month=month;_day=day;}private:int_year;int_month;int_day;};

如果我们写成初始化列表,那么这个构造函数应该是这样的:

Date(intyear=1,intmonth=1,intday=1)//初始化列表的格式:_year(year)//将year的值赋给_year,_month(month)//同理,_day(day){;}

初始化列表的写法是:一个冒号后跟成员变量,括号内部是表达式或变量,用于初始化前面的成员变量,后面的成员变量初始化都跟在逗号后面。而且在初始化列表中每个成员只能出现一次。

函数体内部可以什么都不写,我们一般建议将初始化都写到初始化列表当中,因为所有的成员变量都要进入初始化列表。

成员变量进入初始化列表的顺序与成员变量声明的顺序完全一致,这里先进入初始化列表的是_year,然后是_month,最后是_day。

C++11 规定我们可以给成员变量设定一个缺省值,比如这里,我们给_year设定默认值为 2026,然后不在初始化列表里面初始化_year。

classDate{public:Date(intyear=1,intmonth=1,intday=1):_month(month),_day(day){;}private:int_year=2026;//设定缺省值,而非赋值int_month;int_day;};

对于这样一个构造函数,是如何进入初始化列表的呢?

这里_year先进入初始化列表,但是初始化列表没有显式写_year的初始化,所以编译器会去找_year的缺省值,将它的缺省值赋给_year。

后面的成员变量在初始化列表中都显式地写了,会用我们设定的值正常初始化。

像这样的成员变量,我们可以把它的初始化写在初始化列表,也可以写在函数体内,但有三种变量我们必须写在初始化列表中。

引用、const 修饰的变量、没有默认构造函数的类我们都必须在初始化列表中进行初始化。
这三类变量存在一个共同点,那就是必须在创建时就进行初始化,且只能初始化这一次。

classTime{public:Time(inthours,intminutes):_hours(hours),_minutes(minutes){;}private:int_hours;int_minutes;};classDate{public:Date(intyear=1,intmonth=1,intday=1):_month(month),_day(day)//必须写在初始化列表中,_i(day),_a(1),_t(4,5){;}private:int_year=2026;//设定缺省值,而非赋值int_month;int_day;int&_i;constint_a;Time _t;};

这三类对象必须进入初始化列表的原因都很好理解。关于成员变量进入初始化列表初始化的逻辑,我们可以用以下这张图来概括:

这也就是初始化列表的所有内容了,那么在构造函数的函数体内我们可以干什么呢?我们可以完成初始化列表做不到的事。

比如我们用 malloc 函数申请一块空间的时候,都要判断一下是否申请成功,这个判断我们就可以放到函数体内完成,因为初始化列表做不到。

classStack{public:Stack(intn=4):_a((int*)malloc(n*sizeof(int))),_top(0),_capacity(n){//判断是否申请成功if(_a==NULL){perror("malloc");exit(-1);}}private:int*_a;int_top;int_capacity;};

类型转换

我们来看一个类:

classA{public:A(inta=1):_a1(a){;}voidPrint(){cout<<_a1<<endl;}private:int_a1;};

然后我们用2去构造一个类对象,能成功吗?

intmain(){A a1=2;a1.Print();return0;}


结果发现成功了,为什么呢?这是因为发生了类型转换。

在 C 语言中,我们知道它支持内置类型转换,比如整型和浮点型、指针和整型等等,但不支持自定义类型的转换。

在 C++ 中是支持的,我们可以把内置类型转换为自定义类型,同时也支持自定义类型和自定义类型之间进行转换,但需要通过构造函数来实现。

内置类型转换为自定义类型

这里A a1 = 2就发生了类型转换,2先传过去构造了一个临时对象,该临时对象再拷贝构造a1,对于这种连续的构造+拷贝构造,会被编译器优化为直接构造。

我们知道临时对象具有常性,在之前的 const 引用中我们也知道,发生了类型转换的表达式必须使用 const 引用,所以我们想给它取别名应该这样写:

constA&a2=2;

不使用 const 修饰就会出现权限放大的问题,编译也不通过。

这是传一个参数的构造函数,那么对于要传两个参数以及更多参数的构造函数呢?我们应该如何用内置类型去直接构造?

classA{public:A(inta1=1,inta2=1):_a1(a1),_a2(a2){;}voidPrint(){cout<<_a1<<' '<<_a2<<endl;}private:int_a1;int_a2;};

如果我们直接用括号括起来,写成A a = (1,2),就相当于逗号表达式了,实际传过去的参数就变成了一个,所以要用大括号括起来:

A a1={1,2};a1.Print();


也可以写成这样:

Aa1({1,2});

自定义类型之间的转换

对于自定义类型之间的转换,则需要通过相应的构造函数来实现。

classA{friendclassB;public:A(inta1=1,inta2=1):_a1(a1),_a2(a2){;}voidPrint(){cout<<_a1<<' '<<_a2<<endl;}private:int_a1;int_a2;};classB{public:B(A&a):_b1(a._a1),_b2(a._a2){;}private:int_b1;int_b2;};intmain(){Aa1({1,2});B b1=a1;//类A去构造Breturn0;}

static成员

我们知道,每个类对象中都有自己的成员变量,它们都是相互独立的,那么存在 static 成员会是什么情况?

这里我们想统计类 C 对象的创建次数,但又不想外部可以直接修改,那么定义成全局变量显然不合理,考虑到类的封装性,我们把它定义成 static 成员变量。

//统计类C的创建次数classC{public:C(){_ret++;}C(constC&c){_ret++;}private://定义成静态成员变量staticint_ret;};

因为_ret属于整个类,是所有对象共有的,所以它不会进入初始化列表,我们需要在类外面初始化它,且只能初始化一次,需要指定类域来查找。

intC::_ret=0;//初始化成0

然后我们给外部提供一个接口,使外部可访问_ret的值,从而拿到类对象的创建次数。

staticintGet_ret(){return_ret;}

因为要访问的成员被static修饰了,所以我们也用 static 成员函数来访问该成员,它与普通成员函数的区别在于,内部不含有 this 指针,并且只能访问 static 成员(普通成员函数静态和非静态成员变量都可访问)。

intmain(){C c1;Cc2(c1);C c3;cout<<c3.Get_ret()<<endl;return0;}

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

二分查找系列一

前言 二分查找属于最恶心&#xff0c;细节最多&#xff0c;最容易写出死循环的算法。但是同是也是很简单的算法&#xff0c;因为有模板而且很容易学会。主要应用与数组有序或者无序(有规律)的情况下。 模板主要是朴素二分模板、查找左边界的二分模板、查找右边界的二分模板。…

作者头像 李华
网站建设 2026/9/30 7:21:07

开发者的偷懒小工具:CodeFencer,让代码块粘贴更舒服一点

作为一名开发者&#xff0c;每次写技术博客最烦的事情是什么&#xff1f;对我来说&#xff0c;不是排版&#xff0c;不是配图&#xff0c;而是粘贴代码。从编辑器复制一段代码&#xff0c;贴到 CSDN 里&#xff0c;格式乱了&#xff0c;还得手动选中、点代码块、选语言。或者更…

作者头像 李华
网站建设 2026/9/30 7:20:29

柔性CDMO不是“万能产线”:医药中间体共线生产的四条红线

柔性CDMO凭借多品种灵活生产的优势&#xff0c;成为创新药企业降本提速的核心选择。但行业普遍存在一个认知误区&#xff1a;将柔性产能等同于万能产能&#xff0c;认为医药中间体与精细化工产品可随意共线生产。事实上&#xff0c;合规柔性生产有明确边界&#xff0c;柔性不等…

作者头像 李华
网站建设 2026/9/30 7:20:19

开题毫无思路?Okbiye 开题报告模块,帮你迈过毕设第一道大关

开题报告是毕业论文的起点&#xff0c;也是很多毕业生遇到的第一个难关。开题报告不仅仅是简单的文字填写&#xff0c;需要完整讲清楚研究背景、研究价值、国内外研究现状、研究方法、技术路线、创新点与进度规划。很多同学刚接触课题&#xff0c;研究思路零散&#xff0c;不知…

作者头像 李华
网站建设 2026/9/30 7:20:10

标准化机房服务器从零配置与运维落地项目

一、项目总体概述 1.1项目背景 本次机房标准化建设项目需完成40台物理服务器统一操作系统部署、网络标准化配置、内网服务搭建及常态化运维体系落地。传统单台U盘、光驱装机方式效率低下、配置不统一、人工误差率高,无法满足批量服务器上线需求。为实现机房服务器系统部署标准…

作者头像 李华
网站建设 2026/9/30 7:18:38

阶乘【牛客tracker 每日一题】

阶乘 时间限制&#xff1a;1 秒 空间限制&#xff1a;256M 网页链接 牛客tracker 牛客tracker & 每日一题&#xff0c;完成每日打卡&#xff0c;即可获得牛币。获得相应数量的牛币&#xff0c;能在【牛币兑换中心】&#xff0c;换取相应奖品&#xff01;助力每日有题做&a…

作者头像 李华