【C -> Cpp】由C迈向Cpp (5):深入构造函数

标题:【C -> Cpp】由C迈向Cpp(5)

@水墨不写bug


(图片来源于网络)


不抵制失败,携手失败,迈向成功


正文开始:

(一)深入理解构造函数

        在之前的讲解中,我们已经对 不写会默认生成的 常用的 成员函数有了一个基本的认识,对于一个类而言,用一句话概括常用的成员函数的功能:

        构造函数:完成对象的实例化(定义)。

        析构函数:完成对象销毁前资源清理。

        拷贝构造:在创建对象的同时完成赋值。

        赋值重载:将一个对象赋值给另一个对象。


        尽管我们已经对这些成员函数们有一个整体的认识了。然而,我们需要抓一抓构造函数的细节,以便于对它有一个更深的认识:


        构造函数分为两部分-->  初始化列表和函数体


(1)初始化列表 

使用方式:

        跟在构造函数的函数头后,在函数体之前

        每一个成员变量都用 “  变量名()”初始化;

        第一个成员变量之前为 “  ”,成员变量之间用  “ ”分隔。


示例:

#include <iostream>
using namespace std;
class Date
{
public:
    //构造函数
    Date(int year = 0, int month = 0, int day = 0)
        :_year (year)
        ,_month (month)
        ,_day  (day)
    {}
    
private:
    int _year;
    int _month;
    int _day;
};

        构造函数有一个举足轻重的功能:初始化列表。

        一般情况下,几乎所有 成员变量 都可以在初始化列表 初始化。

(成员变量不论是内置类型,还是自定义类型)

        初始化列表是成员变量初始化的唯一方式。因为从本质上,在构造函数函数体内部对成员变量的赋值之后还可以多次改变,所以构造函数内对成员变量赋值不是初始化,而是赋初值。

不同对象的初始化方式:

        1.引用成员变量、const成员变量、自定义类型的成员变量(这个自定义类型没有默认构造)这三类 必须在初始化列表初始化。

        2.自定义类型对象和内置类型对象的初始化

        ###初始化列表无论你写不写它总是存在的。当然,写初始化列表,并一个不漏的初始化是最好的情形。但是如果不写,可能就会发生一些意想不到的问题###


        不写初始化列表——>

  •         内置对象 在Cpp 语法上没有明确规定,取决于编译器的不同。有些编译器会对内置类型处理,不过大多数编译器不会对内置对象处理。
  •         自定义对象 会调用其默认构造函数,如果调用默认构造函数失败,报出错误。

什么是调用默认构造函数失败?

/************************************/

        要理解这个问题,我们先看看什么是默认构造

  •  i, 自己没有写,编译器自动生成的的构造函数是无参数的构造函数,也就是默认构造;
  • ii,自己写了一个函数,但是没有参数,也可以被编译器识别为默认构造函数,这时编译器就不会再自动生成一个构造函数了;
  • iii,自己写了一个函数,有参数,但是参数全缺省,这也可被编译器识别为默认构造函数,这时编译器就不会再自动生成一个构造函数了;

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


原文链接:https://blog.csdn.net/2301_79465388/article/details/138410081

/***********************************/

       

        也就是说明:

        如果没有默认构造,并且编译器也无法生成时,就会调用默认构造函数失败。

        3.成员变量的缺省值是对初始化列表的补充。

        

        由于不写初始化列表——>大多数编译器不会对内置对象处理。所以C++11新增成员变量的缺省值,缺省值的给出是 非常灵活的,请看如下示例中的 P类

示例: 

        


#include<iostream>
#include<cstdlib>
using namespace std;
class Stack
{
public:

	Stack()
	{
		int* tem = (int*)realloc(_arr, sizeof(int) * n);
        if (tem == nullptr)
        {
	        perror("realloc fail");
	        exit(-1);
        }
        _arr = tem;

    }
private:
	int _top = 0;
	int* arr = (int*)malloc(sizeof(int)*4);
};


class P
{
public:

	P()
	{}

	double re_Pi()
	{
		return 3.1415926;
	}
private:

/*成员默认值给出是非常灵活的*/

    //可以是常量值
	int _a = 0;
	char _c = 'c';

    //常量表达式
	long _b = 8 + 3;

    //函数返回值
	double pi = re_Pi();

    //动态申请的空间指针
    int* ptr = (int*)malloc(sizeof(int)*4);

    //另一个对象并调用构造
	Stack st = 10;
};

通过单步调试,我们可以观察的很清楚 :

1.首先进入P的构造函数,准备进入初始化列表(没有写也是存在的)

2.进入初始化列表,用默认值初始化成员变量。

 默认值为函数返回值也是可以正常进入的:

 对于栈的初始化也是可以正常找到默认构造的:

初始化完成:


        4,成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
示例:


/*这个程序的输出结果是什么*/


class A
{
public:
    A(int a)
    :_a1(a)
    ,_a2(_a1)
    {}

    void Print() 
    {
    cout<<_a1<<" "<<_a2<<endl;
    }

private:
    int _a2;
    int _a1;
};

int main() 
{
    A aa(1);
    aa.Print();
}

        其实结果是输出:     {   1     随机值    }

(因为初始化列表是根据成员变量声明顺序来初始化的,由于先声明_a2,所以先初始化_a2,这时_a1还是随机值,所以_a2初始化是被随机值初始化的,是无效的)

 构造函数的初始化列表总结:

        &能在初始化列表的尽量都在初始化列表初始化,如果实在不能再考虑在构造函数的函数体内进行赋值&

(2)explicit关键字

用explicit修饰构造函数,将会禁止构造函数的隐式转换

        构造函数 其实做了比我们想象的更多的事:

class Date
{
public:

    // 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
    
    explicit Date(int year)
        :_year(year)
        {}

   

    Date& operator=(const Date& d)
    {
        if (this != &d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};

 对于这个类,当我们构造对象

int main()
{
    Date d1(2024);
    d1 = 2023;
    return 0;
}

        2023赋值给d1,构造函数则完成了将整形值隐式类型转换成Date类的临时对象,然后通过赋值重载将临时对象的值赋值给d1。


1.对于任意的单参数的构造函数,当我们传入的类型和对象类型不一致时,都会发生这一转换。

什么是单参数

  • 1. 构造函数只有一个参数
  • 2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
  • 3. 全缺省构造函数

 这也是构造函数让我们用的很方便的原因。

2.如果在构造函数之前加上explicit,就禁止了隐式类型转换。

这样,编译会报错:

加上 explicit之后无法完成类型转换:

d1 = 2023;

编译报错


(图片来源于网络)


完~

相关推荐

  1. c/c++】cppc函数扩展

    2024-05-16 09:52:11       31 阅读
  2. CPP Weekly --C++17

    2024-05-16 09:52:11       44 阅读
  3. c++】cpp之引用

    2024-05-16 09:52:11       36 阅读
  4. c++】cpp类和对象

    2024-05-16 09:52:11       23 阅读
  5. C#】与cpp异同总结

    2024-05-16 09:52:11       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-16 09:52:11       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-16 09:52:11       101 阅读
  3. 在Django里面运行非项目文件

    2024-05-16 09:52:11       82 阅读
  4. Python语言-面向对象

    2024-05-16 09:52:11       91 阅读

热门阅读

  1. Gateway基本配置的参数以及yml示例

    2024-05-16 09:52:11       35 阅读
  2. error in ./src/assets/css/element-variables.scss

    2024-05-16 09:52:11       32 阅读
  3. matlab实现马尔科夫链

    2024-05-16 09:52:11       37 阅读
  4. SpringBoot自定义Starter

    2024-05-16 09:52:11       33 阅读
  5. lambda函数(匿名函数)的使用

    2024-05-16 09:52:11       35 阅读
  6. 在本地设备上配置 Git 忽略特定文件

    2024-05-16 09:52:11       34 阅读
  7. 解释 Git 的基本概念和使用方式。

    2024-05-16 09:52:11       30 阅读
  8. 51 单片机[2-3]:LED流水灯

    2024-05-16 09:52:11       36 阅读
  9. Visual studio的使用

    2024-05-16 09:52:11       32 阅读
  10. 什么是单线服务器?

    2024-05-16 09:52:11       28 阅读