C++构造函数

2023-05-16

文章目录

  • 1 构造函数的定义及特征
  • 2 默认构造函数
    • 2.1 合成的默认构造函数
      • 2.1.1 默认初始化
      • 2.1.2 合成的默认构造函数初始化类成员变量的规则
      • 2.1.3 有些场合不能使用合成的默认构造函数
    • 2.2 全缺省的默认构造函数(参数都为默认实参的构造函数)
  • 3 构造函数初始化列表
    • 3.1 初始化列表的书写方式
    • 3.2 为什么使用初始化列表?

1 构造函数的定义及特征

构造函数是类的一种特殊成员函数,其主要作用是对类的成员进行初始化,保证每个类成员都有一个合适的初始值。

构造函数有以下特征

  • 函数名与类名一致
  • 没有返回值
  • 对象实例化时自动调用对应的构造函数
  • 支持函数重载

构造函数语法:类名(){}

需要注意的是, 没有返回值和返回值为空是两码事。返回值为空指的是该函数有返回值,其中返回值为void类型。而支持重载则说明,在一个类中,可以同时存在多个构造函数。

在代码段1中,People类提供了两个构造函数,其中第一个是无参构造函数,第二个是有参构造函数。在test函数中创建对象时,father调用的是无参构造函数,而son调用的有参构造函数,说明构造函数重载为对象的实例化提供了多种初始化方式,而对象实例化时自动调用对应的构造函数,保证了对象一定可以被初始化。

代码段1:

class People
{
public:
    //无参构造函数 
    People(){} = 

    //有参构造函数
    People(int age, std::string sex, std::string name)
    {
        _age = age;
        _sex = sex;
        _name = name;
    }
private:
    int _age;
    std::string _sex = "男"; //C++11新标准规定,可以为数据成员提供一个类内初始值。
                        //创建对象时,类内初始值用于初始化数据成员
    stf::string _name;
};

void test()
{
    People father;       //不能写成People father(),否则会认为是函数的声明;
    People son(5, "男", "张三");
}

2 默认构造函数

默认构造函数是不用传参就可以调用的构造函数,其主要有三种:

  1. 在我们没有显式地定义类的构造函数时,编译器会自动提供一个构造函数,称为合成的默认构造函数
  2. 我们自己定义的无参构造函数,如代码1中的无参构造函数。
  3. 我们自己定义的全缺省的构造函数。

2.1 合成的默认构造函数

2.1.1 默认初始化

在讲合成的默认构造函数之前,需要先了解默认初始化

如果定义变量时没有指定初值,则变量会被默认初始化,赋予一个默认值。在C++中,变量有内置类型和自定义类型,内置类型如int、float、char等,而自定义类型是用class、struct、union自己定义的类型。变量的默认值是什么,取决于其变量类型

在代码段1中,People类中的_age变量为内置类型,如果调用无参构造进行初始化,该变量将不被初始化。在VS2019编译器中,报出如下警告:
在这里插入图片描述

执行之后,从监视窗口我们可以获知对象father和son的初始化结果,如下图:

在这里插入图片描述

可以看出,对象father的_age在初始化后,依然为随机值,也就是说_age并未初始化。而father的_sex被初始化为"男",是因为我们为_sex提供了类内初始值。C++11标准规定,可以为数据成员提供一个类内初始值,创建对象时,类内初始值用于初始化数据成员。father的_name初始化后为空串。C++中,string类规定若没有指定初始值,则默认初始化为空串

一个未初始化的内置类型变量的值是未定义的,如果对该变量进行拷贝或者以其他形式来访问,可能会导致错误。

2.1.2 合成的默认构造函数初始化类成员变量的规则

我们先来看一段代码:

代码段2

 class Cube
    {
    private:
        int _length;
        int _width;
        int _height;
    };
    
    void test()
    {
        Cube c1;
    }

该代码段定义了一个Cube类,其成员变量都是私有的int类型,初始化前后的值如下面两图所示。

初始化前:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EfjQXHFl-1628063908686)(C:\Users\Chenxu Gong\AppData\Roaming\Typora\typora-user-images\image-20210526013320882.png)]

初始化后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-08aPw4Wr-1628063908687)(C:\Users\Chenxu Gong\AppData\Roaming\Typora\typora-user-images\image-20210526013347470.png)]

可以发现,初始化前后数值都是随机值。那难道默认的构造函数不能有效初始化成员变量,其根本没有任何作用

答案是否定的。

根据我们在2.1.1中所述,默认初始化时,变量的默认值取决于变量类型。在Cube类中,三个成员变量均为int型,变量不会被初始化。如果成员变量中的类型是某种自定义类型,效果则完全不一样,且看代码段3。

代码段3

class People
{
public:
    //参数都为默认实参的构造函数,详见2.2
    People(int age = 22, std::string sex = "female", std::string name = "Sefanie")
    {
        _age = age;
        _sex = sex;
        _name = name;
    }
private:
    int _age;
    std::string _sex;
    std::string _name;
};

class Family
{
private:
    People mother;
    People Father;
    People Son;
};

int main()
{
    Family fa;
    return 0;
}

我们创建了一个Family类,其中有三个成员变量,三个成员变量均为People类,在创建对象fa后,其三个成员变量均被初始化。我们从监视窗口可以获知,fa的三个成员变量均得到有效初始化。这是因为Family类的合成的默认构造函数调用了People类的默认构造函数(1.2.2中将讲述),从而实现了三个成员变量的有效初始化、

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6WmMeA0Y-1628063908688)(C:\Users\Chenxu Gong\AppData\Roaming\Typora\typora-user-images\image-20210527160643957.png)]

当类内变量为默认类型时,合成的默认构造函数按照如下规则进行初始化:

  1. 如果有类内初始值,则用它来初始化成员变量
  2. 否则执行默认初始化

当类内变量为自定义类型,且该自定义类型存在默认构造函数时,通过调用成员变量的默认构造函数来实现成员变量的初始化。

2.1.3 有些场合不能使用合成的默认构造函数

当类没有声明任何成员变量时,编译器才会提供合成的默认构造函数,然而在以下场合,不推荐使用合成的默认构造函数:

  • 类内存在内置类型或者复合类型(如数组和指针),且未提供类内初始值时。此时成员变量将不会被初始化,使用未初始化的变量可能会造成不可预计的后果。
  • 合成的默认构造函数是通过调用该自定义类型的默认构造函数来初始化类成员的,当类内存在自定义类型且该自定义类型没有默认构造函数时,编译器无法初始化该成员。若我们将代码段3改为代码段4,编译器将会报错。

代码段4

class People
{
public:
    //此处是和代码3的唯一区别,即少了默认实参,此时People不再有默认构造函数,该代码段将报错
    People(int age, std::string sex, std::string name)
    {
        _age = age;
        _sex = sex;
        _name = name;
    }
private:
    int _age;
    std::string _sex;
    std::string _name;
};

class Family
{
private:
    People mother;
    People Father;
    People Son;
};

int main()
{
    Family fa;
    return 0;
}

2.2 全缺省的默认构造函数(参数都为默认实参的构造函数)

在代码段3中,Family类的构造函数中,每个参数都有默认实参。根据我们在2.1.3中的描述,若类内存在自定义类型的成员变量,合成的默认构造函数必须通过调用这个成员变量的默认构造函数来实现对其的初始化。如果该成员变量不存在默认构造函数,编译器将报错。

而实际上,代码段3并未报错,也就是说,当构造函数的所有参数都有默认实参时,该构造函数也是默认构造函数。

在大多数情况下,我们都要自己写构造函数,推荐使用全缺省的方式来初始化成员变量,这样能适应大多数场景

3 构造函数初始化列表

3.1 初始化列表的书写方式

构造函数的唯一目的就是为数据成员赋初值,因此在实际应用中,构造函数的函数体往往用以下的方式来书写。我们将代码段3的People类修改如下:

代码段5

class People
{
public:
    //原始版本的构造函数
    //People(int age = 22, std::string sex = "female", std::string name = "Sefanie")
    //{
    //    _age = age;
    //    _sex = sex;
    //    _name = name;
    //}
    
    //初始化列表版本的构造函数
    People(int age = 22, std::string sex = "female", std::string name = "Sefanie"):_age(age),_sex(sex),_name(name){}
private:
    int _age;
    std::string _sex;
    std::string _name;
};

在这个定义中,在冒号和花括号之间出现了新的部分,花括号内为空实现,这个新出现的部分称为构造函数的初始化列表。其重要作用是为新创建的对象的几个成员变量赋初值。

语法:构造函数():属性1(值1),属性2(值2)... {}

为了巩固初始化列表的写法,我们再来看一个例子

代码段6

class Date
{
public:
    //_month和_day都初始化为1,_year用传入的参数进行初始化
    Date(int year):_year(year),_month(1),_day(1){}
    
    //_year, _month, _day都用传入的参数进行初始化
    Date(int year, int month, int day) :_year(year), _month(month), _day(day) {}
private:
	int _year;
	int _month;
	int _day;
};

3.2 为什么使用初始化列表?

在代码段1,3,4中的构造函数,是以赋值的方式实现构造函数的,而在代码段4,5中,是以初始化的方式实现构造函数的。如果没有在构造函数的初始化列表这种显式地初始化成员,那么在进入函数体之前,会先进行默认初始化,函数体内再进行赋值操作。

赋值的方式和初始化的方式与底层效率有关,前者是先初始化后赋值,而后者是直接初始化数据成员的。

其次,**如果类数据成员是const类型或者引用,必须将其初始化,而不能在函数体内才赋值。**因为const就表明该成员变量是只读属性,因此函数体内是无法改变const属性的成员变量的,而引用定义的时候,就需要初始化,如下例:

代码段7

class A
{
public:
    A(int i);
private:
    int _i;
    const int _j;
    int& _k;
};

//错误示例
//A::A(int i)
//{
//    _i = i;
//    _j = i; //函数体内无法修改const,错误
//    _k = _i; //引用应该在定义时或者初始化列表中进行初始化,错误
//}

//正确
A::A(int i) :_i(i), _j(i), _k(_i) {}

如果按照代码段7中的错误示例书写,在VS2019中将会报如下错误。也就是说,初始化const和引用类型的变量的唯一机会就是在初始化列表中构造初始值。
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++构造函数 的相关文章

  • C++ 构造函数和New运算符

    算法和数据结构就是编程的一个重要部分 xff0c 你若失掉了算法和数据结构 xff0c 你就把一切都失掉了 系统会自动在栈中为每个变量开辟内存空间 xff0c 以保证数值被合理地存放 由于栈是系统自动分配的 xff0c 因此速度较快 xff
  • C++big three(构造函数、拷贝构造函数,拷贝赋值函数)

    一个类中只要带有指针类型的成员 xff0c 就必须自己写出big three xff08 构造函数 拷贝构造函数 xff0c 拷贝赋值函数 xff09 xff0c 如果没有指针类型的成员 xff0c 大部分情况下可以用默认的 字符串类是一个
  • C++之struct构造函数(2010-10-19 15:04:47)

    C 43 43 之struct构造函数 2010 10 19 15 04 47 转载 标签 xff1a cpp struct 构造函数 校园 分类 xff1a C C PlusPlus 在网络协议 通信控制 嵌入式系统的C C 43 43
  • 构造函数设置为private,会怎样。

    构造函数设置为private 会怎样 1 无法静态的创建对象了 即不能通过 A a这种方式创建对象了 只能通过在类的内部的静态成员函数中new一个对象 动态的创建对象 include
  • JAVA随笔——关于构造函数与this关键字和static关键字

    函数重载 函数重载根据参数不同来进行分别 参数顺序不同也是函数重载 并非同一个函数 例如 public class OverloadingOrder public static void main String args f 1 z f f
  • Tip of the Week #61: Default Member Initializers

    Tip of the Week 61 Default Member Initializers Originally posted as Totw 61 on Nov 12 2013 by Michael Chastain mec deskt
  • 【深入理解C++】调用父类的构造函数

    文章目录 1 子类的构造函数默认会调用父类的无参构造函数 2 调用父类的构造函数 1 子类的构造函数默认会调用父类的无参构造函数 include
  • C++中将构造函数或析构函数定义为private

    今天面试被问到了这个单例模式常用到的技术手段 下面进行分析 很多情况下要求当前的程序中只有一个object 例如一个程序只有一个和数据库的连接 只有一个鼠标的object 通常我们都将构造函数的声明置于public区段 假如我们将其放入pr
  • C++四大特性——多态 的总结

    我们都知道 C语言和C 的区别就是 C语言是面对过程的程序设计 而C 是面对对象的程序设计 面对对象的程序设计有4大特性 分别是 抽象 封装 继承 多态 今天我们就来总结一下多态的内容 多态 分为静态多态和动态多态 静态多态 编译器在编译期
  • 不要在构造和析构函数中调用虚函数

    注 本文对应Effective C 条款9 基类是一个模拟股票交易的类 成员函数logTransaction 是记录每一笔交易 具体买 卖派生类实现自己的记录函数 基类的构造函数中调用了这个虚函数 先来思考下面这段代码 include
  • C++【对象模型】

    文章目录 索引 一 默认构造函数 1 何时默认构造函数会自动生成 2 编译器合成有用的构造函数四种情况 2 1 类中内含带有默认构造的类成员 2 2 带有默认构造的基类 2 3 带有虚函数的类 2 4 带有一个虚基类的类 索引 C 对象模型
  • Java private类构造函数笔记

    前言 最近在看Android源码的时候遇到了private类构造函数 于是写了一个测试小程序 帮助理解 代码 class TestPrivate private TestPrivate System out println TestPriv
  • C++ 类 :有参数构造函数与无参数构造函数使用注意事项

    构造函数的作用是在创建类的对象时进行类对象初始化的 在 C 中 每个类都有且必须有构造函数 如果用户没有自行编写构造函数 则 C 自动提供一个无参数的构造函数 称为默认构造函数 这个默认构造函数不做任何初始化工作 一旦用户编写了构造函数 则
  • 结构体中存在string类型成员

    include
  • 全面理解java中的构造方法以及this关键字的用法(超详细)

    Hello 各位铁汁们 我是小 儿哈 今天我又来更新我的Java基础学习博客了 本篇主要内容概述 1 如何用构造方法初始化对象 2 为啥要有this这个关键字 3 this 属性名访问成员变量 成员方法 4 this 方法名 this 的用
  • Java 构造函数的详解

    我们人出生的时候 有些人一出生之后再起名字的 但是有些人一旦出生就已经起好名字的 那么我们在java里面怎么在对象一旦创建就赋值呢 1 构造方法的作用 构造方法作用 对对象进行初始化 如图 2 构造函数与普通函数的区别 1 一般函数是用于定
  • C++中的类——构造函数

    一 什么是构造函数 每个类都分别定义了它的对象被初始化的方式 类通过一个或几个特殊的成员函数来控制其对象的初始化过程 这些函数叫构造函数 构造函数的任务是初始化类对象的数据成员 无论何时只要类的对象被创建 就会执行构造函数 二 构造函数的定
  • 工厂函数和构造函数的区别

    工厂函数和构造函数的区别 下面是给出的两个都是实现 定义使用值的范围 的函数 第一个工厂函数 工厂模式 用以创建并初始化类的实例 而且给出了一个表示 值的范围 的类定义了原型对象 第二个是使用构造函数代替工厂函数来实现相同功能的代码段 下面
  • Java基础十一(private、this关键字和构造函数)

    私有private关键字 成员变量是否一定需要全部向外界访问 如果需要向外界访问 则public 如不需要向外界访问 则private 但是一般而言 都会将成员变量私有化 给成员变量 private是彻底不想给外界类中不需要对外提供的内容都
  • 默认构造函数、拷贝构造函数、析构函数、赋值构造函数

    最近老是有人问我拷贝构造函数和赋值构造函数 说实话 我会用 但这个概念还真是搞不太清楚 真烦 概念问题少问我 学习笔记 1 析构函数 每个类只有一个析构函数 2 构造函数 每个类可以有多个构造函数 包括 默认构造函数 拷贝构造函数 赋值构造

随机推荐