你想对构造函数说些什么?

简介: 你想对构造函数说些什么?

回顾知识

在学习类的默认成员函数之前,先带大家复习一下什么是类,类是在C++中引进的新的类型,是一种自定义类型,实际上跟C语言中的结构体类似,但是是对结构体的升级,可以在类里面添加函数,也有对应的访问限定符private,public和protect;在C++中依旧可以使用结构体struct,因为C++要兼容C,只不过我们可以直接使用类名来实例化对象,还有一点就是class在不写访问限定符的时候,默认为private私有,而struct不写默认的是public,这也跟C++要兼容C有关,因为我们在C语言中struct的成员都是可以直接访问的。

一、 为什么要引入构造函数?

其实我们在实现一个日期类的时候,我们肯定是需求在实例化对象的时候就初始化好我们的日期,比如:2023年11月20日


在没有学习构造函数之前我们会怎么把日期传给我们的成员变量呢?来看下面的代码:

#include <iostream>
using namespace std;
class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date date;
    date.Init(2023, 11, 20);
    date.Print();
    return 0;
}

所以我们在不知道构造函数的存在之前,我们只会这样初始化日期,其实准确来说并不是初始化,我们只是简单的赋值,而真正的初始化是在初始化列表中实现的,因为初始化列表只能允许成员变量出现一次,所以也就赋值一次,这才是初始化!我们回到正题,如果每次写类的时候,都要这样实现初始化,神都会觉得麻烦,基于这样的理念,我们的构造函数就诞生了

二、 什么是构造函数?

构造函数是一种特殊的成员函数,跟我们之前所有的函数模样都不一样。

  1. 构造函数不用写返回值
  2. 构造函数的函数名是类名
  3. 构造函数允许重载
  4. 构造函数是在实例化对象时自动调用的
  5. 构造函数的参数可以给缺省值

下面就是构造函数的写法:

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year  = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

上面就是构造函数的一种简单的写法,构造函数就是用来给成员变量赋值的,记住是赋值不是初始化,构造函数体内可以进行多次赋值,这跟初始化的概念截然不同,在后面讲的初始化列表才是初始化;还有一点就是我们显式地去写了构造函数,编译器就不会生成了,这也就是说明了,默认成员函数是编译器自己会生成的。既然我们可以写构造函数,编译器可以生成构造函数,那构造函数的分类是什么呢?

三、 构造函数分为哪几种?

构造函数分为下面两类:

  1. 显式构造函数:必须传参的,如半缺省,无缺省;
  2. 默认构造函数:不需要传参的,如全缺省,无参和编译器默认生成的;


注意事项:

  1. 默认构造函数只允许存在一个;
  2. 写了显式构造函数,编译器就不会生成默认构造函数;
  3. 对于自定义类型的成员变量,当前类的构造函数会调用这个自定义类型的默认构造函数,如果这个自定义类型没有默认构造,就会报错;

四、 什么是初始化列表?

其实最常用的并不是构造函数,而是我们的初始化列表,它的存在解决了对于必须在定义的时候初始化的变量,和一些没有默认构造函数的自定义类型。

使用规则:冒号开头,逗号接应,末尾没有分号

class Date
{
public:
    Date(int year, int month, int day)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
private:
    int _year;
    int _month;
    int _day;
};

注意事项

  1. C++11 中针对内置类型成员不初始化的缺陷,又打了补丁。即:内置类型成员变量在类中声明时可以给默认值,其实就是给的初始化列表。
  2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

五、 对构造函数的提问

1. 为什么要自己写构造函数?

给大家举两个例子:

  1. 第一个是在日期类,编译器生成的默认构造函数是没办法给我们的日期正常赋值的,会赋值,但是是给随机值,所以不满足我们的需求,我们会写构造函数,给正确的值;
  2. 第二个是栈,顺序表,链表,二叉树等需要申请资源,开辟空间的,我们的编译器生成的默认构造函数,是没办法进行malloc和realloc等操作的,所以我们要自己写一个构造函数来开辟空间;
  3. 综上所述,我们写构造函数的原因就是基于编译器生成的默认构造函数没办法提供我们正常的需求。

2. 对于成员变量是自定义类型,我们如何构造?

大家在这里,要知道凡是类里面有自定义类型,无论是默认构造函数还是显式构造函数,都会调用这个自定义类型自己的默认构造函数。如果这个自定义类型没有默认构造函数,编译器会报错。

3. 初始化列表和构造函数的区别与联系?

区别:

  1. 我们构造函数准确的来说,不是给成员变量初始化,因为初始化只有一次,而我们的构造函数的内部可以多次给一个成员变量赋值,所以构造函数不是初始化,是赋值;
  2. 而初始化列表中的成员变量只会出现一次,所以就只会初始化一次。


关系:

  1. 可以在初始化列表和构造函数体内部同时使用,当我们需要一些变量初始化时需要条件判断,就要放在构造函数体内部。
  2. 能用初始化列表都用初始化列表,除非必须用构造函数。

4.为什么一定要用初始化列表来初始化引用和const类型?

因为我们在没有初始化列表的时候,我们写了构造函数,但是会发现这样一个现象,当没有进入构造函数的时候,成员变量都是随机值,那这个随机值是在哪里给的呢?答案就是初始化列表,所以初始化列表是一直存在的,只不过在没有显式地写的情况下,是默认给随机值的。

  1. 了解这些之后,我们再来谈谈const修饰的成员变量,因为const修饰的变量只能初始化一次,不可以被修改,所以在我们不写初始化列表时,默认的初始化列表就会初始化一次const,那接下来我们又在构造函数体内部赋值是肯定错误的;
  2. 那对于引用这样的变量,必须是在定义的时候初始化,如果我们不使用初始化列表,引用类型的成员变量就是默认初始化列表的随机值的别名,因为引用是改变不了指向的对象的,所以就会出错;基于这个原因,引用才必须在初始化列表中初始化;

5. 构造函数可以显式调用吗?

不可以显式调用,构造函数是默认自动调用的,会报错;但是可以用定义new的方法显式调用


相关文章
|
Java C++ 算法
带你读《JVM G1源码分析和调优》之二:G1的基本概念
本书尝试从G1的原理出发,系统地介绍新生代回收、混合回收、Full GC、并发标记、Refine线程等内容;同时依托于jdk8u的源代码介绍Hotspot如何实现G1,通过对源代码的分析来了解G1提供了哪些参数、这些参数的具体意义;最后本书还设计了一些示例代码,给出了G1在运行这些示例代码时的日志,通过日志分析来尝试调整参数并达到性能优化,还分析了参数调整可能带来的负面影响。
|
算法 安全 Java
(七)JVM成神路之GC分代篇:分代GC器、CMS收集器及YoungGC、FullGC日志剖析
在《GC基础篇》中曾谈到过分代以及分区回收的概念,但基础篇更多的是建立在GC的一些算法理论上进行高谈阔论,而本篇则重点会对于分代收集器的实现进行全面详解,其中会涵盖串行收集器、并行收集器、三色标记、SATB算法、GC执行过程、并发标记、CMS收集器等知识,本篇则偏重于分析GC机制的落地实现,也就是垃圾收集器(Garbage Collector)。
515 8
|
存储 算法 编译器
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
【C++ 函数 基础教程 第四篇】深入C++函数返回值:理解并优化其性能
812 1
|
Java 大数据 测试技术
Java对象头压缩---- 永久为Java应用“降本增效”
本文介绍了一下OpenJDK的最新技术,对象头压缩,来大幅优化Java对象的内存占用。
|
编译器
Mac下Clion编译错误:Undefined symbols for architecture x86_64
在使用CLion做LeetCode题编译时,突然出现了一下的情况: Undefined symbols for architecture x86_64: "Solution::isCommonPrefix(std:...
6397 0
|
存储 编译器 C++
2023-4-27-深入理解C++指针类型间强制转换
2023-4-27-深入理解C++指针类型间强制转换
480 0
|
SQL BI
SQL利用Case When Then多条件判断
CASE     WHEN 条件1 THEN 结果1     WHEN 条件2 THEN 结果2     WHEN 条件3 THEN 结果3     WHEN 条件4 THEN 结果4 .........     WHEN 条件N THEN 结果N     ELSE 结果X END Case具有两种格式。
7934 0
|
C++
【C/C++】uin8_t uint16_t uint32_t相互转换
uin8_t uint16_t uint32_t 数据类型相互转换
1109 0
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
481 0