【C++】类和对象(3)--初始化列表(再谈构造函数)

本文详细解释了C++中初始化列表的概念、特性,包括成员变量的初始化顺序、引用和const的特殊处理,以及何时使用初始化列表和函数体内初始化。强调了初始化列表在构造函数中的重要性,尤其是在处理自定义类型和默认构造的情况。
摘要由CSDN通过智能技术生成

目录

一 引入

二 初始化列表概念

三 初始化列表特性

1 引用和const

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;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值

二 初始化列表概念

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。

class Date
{
public:
	//Date(int year, int month, int day)
	//{
	//    // 函数体内初始化
	//    _year = year;
	//    _month = month;
	//    _day = day;
	//}

	Date(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;
};




上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。

初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数

三 初始化列表特性

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

总之 这三类不能在函数内部初始化

1 引用和const

class Date
       {
       public:
              Date(int year, int month, int day)
              {
                  // 函数体内初始化
                  _year = year;
                  _month = month;
                  _day = day;
                  int& _ref;//引用 -->error
                  const int _n;//const -->error
              }
       
              void Print()
              {
                      cout << _year << '/' << _month << '/' << _day << endl;
              }
       
       private:
              // 声明
              int _year; // 缺省值
              int _month;
              int _day;
              int& _ref;//引用
              const int _n;//const
       };

这是为什么?

引用和const只能初始化而不是赋值

之前有讲过,若是我们自己不去实现构造函数的话,类中会默认提供一个构造函数来初始化成员变量,对于【内置类型】的变量不会处理,对【自定义类型】的变量会去调用它的构造函数。那么对于这里的_year, _month, _day, _ref, _n都属于内置类型的数据,所以编译器不会理睬,可是引用变量, const修饰的变量又必须要初始化,所以初始化列表诞生

有人说给个缺省值不就好了 这个办法确实是可以解决我们现在的问题,因为C++11里面为内置类型不初始化打了一个补丁,在声明的位置给到一个初始化值,就可以很好地防止编译器不处理的问题

但是现在我想问一个问题:如果不使用这个办法呢?你有其他方法吗?难道C++11以前就那它没办法了吗?

改正如下:

class Date
{
public:
	//Date(int year, int month, int day)
	//{
	//    // 函数体内初始化
	//    _year = year;
	//    _month = month;
	//    _day = day;
	//     int& _ref;//引用 -->error
	//     const int _n;//const -->error
	//}

	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
		, _ref(year)
		, _n(1)
	{
		// 初始化列表
	}

	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}

private:
	// 声明
	int _year; // 缺省值
	int _month;
	int _day;
	int& _ref;//引用
	const int _n;//const
};


2 混合使用

当然函数体内初始化和初始化列表可以混着用

Date(int year, int month, int day)
                      : _ref(year)
                      , _n(1)
              {
                      // 函数体内初始化
                      _year = year;
                      _month = month;
                      _day = day;
              }

3 自定义成员情况

class A
{
public:
	A(int a = 0)//默认构造
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};

class Date
{
public:
	Date(int year, int month, int day)
	{
		// 函数体内初始化
		_year = year;
		_month = month;
		_day = day;
	}

private:
	// 声明
	int _year; 
	int _month;
	int _day;
	A _aa;//调用默认构造
};

int main()
{
	Date d(2023, 11, 14);
	return 0;
}

1 如果A没有默认构造呢? -->初始化列表

class A
{
public:
	A(int a)//这不是默认构造
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};

class Date
{
public:
	Date(int year, int month, int day)
		:_aa(10)
	{
		// 函数体内初始化
		_year = year;
		_month = month;
		_day = day;
	}

private:
	// 声明
	int _year; 
	int _month;
	int _day;
	A _aa;//没有默认构造
};

int main()
{
	Date d(2023, 11, 14);
	return 0;
}

2 如果不想使用A的默认构造的值呢?-->初始化列表

class A
{
public:
	A(int a = 10)//默认构造
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};

class Date
{
public:
	Date(int year, int month, int day)
		:_aa(1000)
	{
		// 函数体内初始化
		_year = year;
		_month = month;
		_day = day;
	}

private:
	// 声明
	int _year; 
	int _month;
	int _day;
	A _aa;//调用默认构造
};

int main()
{
	Date d(2023, 11, 14);
	return 0;
}

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化

四 初始化列表中的初始化顺序

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

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();
}

先走的_a2, 此时_a1还没有初始化, 给的随机值,.

五 总结

初始化列表解决的问题:

1、必须在定义的地方显示初始化 引用 const 没有默认构造自定义成员

2、有些自定义成员想要显示初始化,自己控制

尽量使用初始化列表初始化

构造函数能不能只要初始化列表,不要函数体初始化?

不能,因为有些初始化或者检查的工作,初始化列表也不能全部搞定

80-100%初始化列表搞定,还有需要用函数体,他们可以混着用

我们来看看下面这段代码

class Stack
{
public:
    Stack(int n = 2)
        :_a((int*)malloc(sizeof(int)* n))
        , _top(0)
        , _capacity(n)
    {
        //...
        //cout << "Stack(int n = 2)" << endl;
        if (_a == nullptr)
        {
            perror("malloc fail");
            exit(-1);
        }

        memset(_a, 0, sizeof(int) * n);
    }

    //...

    int* _a;
    int _top;
    int _capacity;
};

class MyQueue
{
public:
    MyQueue(int n1 = 10, int n2 = 20)//形成默认构造函数
        :_s1(n1)
        , _s2(n2)
    {}

private:
    Stack _s1;
    Stack _s2;

    int _size = 0;
};

int main()
{
   
    MyQueue q1;
    MyQueue q2(100, 100);//有初始化列表那就先走初始化列表


    return 0;
}

这样我们就能控制我们怎样控制我们的默认构造.

这节是对上一节类和对象(2)构造函数的续集, 建议两个章节要有先后理解, 本节内容比较简单, 注重理解 上一节的博客【C++】类和对象(2)--构造函数-CSDN博客

析构函数 【C++】类和对象(4)--析构函数-CSDN博客

继续加油!  

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值