19、初始化列表的使用(一个类调用另一个类的程序)

1、问题:类中是否可以定义const成员?

  • 下面的类定义是否合法?
    — 如果合法,ci 的值是什么,存储在哪里?
class Test
{
private:
	const int ci;
public:
	int getCI()
	{
		return ci;
	}
};

程序:

#include <stdio.h>
class Test
{
private:
	const int ci;
public:
	int getCI()
	{
		return ci;
	}
};
int main()
{
	Test t1;
	printf("t1.ci = %d\n", t1.getCI());
	return 0;
}

在这里插入图片描述
分析:从上面我们可以得出结论。在类里面我们是可以定义const 成员变量的,但是当我们通过这个类来定义对象的时候,编译器就会报错,报错的原因是在这里面有未初始化的const 成员,我们面临的问题就是怎么来初始化类里面的 const 成员。那我们能否通过构造函数来初始化const 成员变量呢?

程序:

#include <stdio.h>
class Test
{
private:
	const int ci;
public:
	Test()
	{
		ci = 10;
	}
	int getCI()
	{
		return ci;
	}
};
int main()
{
	Test t1;
	printf("t1.ci = %d\n", t1.getCI());
	return 0;
}

在这里插入图片描述
分析:报错的原因在于它说 ci 是一个只读变量,不能出现在赋值符号的左边。
在C++里面,const 这个关键字如果作用于类的成员变量之后,其实得到是一个只读变量。从错误当中我们看出,如果我们要对 ci 进行初始化,必须要在第8行进行。

同时从这里也可以看出,构造函数不能对成员变量进行初始化,它的本质就是对成员变量进行赋值,在后面我们学到的的初始化列表才能对成员变量进行真正的初始化

2、类成员的初始化

  • C++中提供了初始化列表对成员变量进行初始化
  • 语法规则
ClassName::ClassName() :m1(v1), m2(v1, v2), m3(v3)        //其实这就是在构造函数
{
	//some other initialize operation
}

初始化的位置在:构造函数之后,函数体之前

#include <stdio.h>
class Test
{
private:
	const int ci;
public:
	Test():ci(10)
	{
		//ci = 10;
	}
	int getCI()
	{
		return ci;
	}
};
int main()
{
	Test t1;
	printf("t1.ci = %d\n", t1.getCI());
	return 0;
}

在这里插入图片描述

  • 注意事项
    — 成员的初始化顺序与成员的声明顺序相同
    — 成员的初始化顺序与初始化列表中的位置无关
    初始化列表先于构造函数的函数体执行

大概意思就是说:初始化顺序按照的是你在成员变量的定义顺序,而不是按照你在初始化列表中的排列位置

当我们的构造函数开始执行的时候,我们的对象已经创建完毕了。所以说初始化列表既然是用来初始化,那么它必须在类对象创建的时候来进行执行,而不是对象都创建好了,才来进行初始化的工作。这个差异就是初始化和赋值的差异。

#include <stdio.h>
class Value
{
private:
	int mi;
public:
	Value(int i)
	{
		mi = i;
		printf("i = %d\n",i);
	}
	int getI()
	{
		return mi;
	}
};
class Test
{
private:
	Value m2;
	Value m3;
	Value m1;
public:
	Test()
	{
		
	}	
};
int main()
{

	return 0;
}

在这里插入图片描述
意思就是说m1,m2,m3没有合适的构造函数,所以我们可以先对它用初始化列表进行初始化,使它接下来能正常进入构造函数来运行里面的输出函数。

#include <stdio.h>
class Value
{
private:
	int mi;
public:
	Value()
	{

	}
	Value(int i)
	{
		mi = i;
		printf("i = %d\n",i);
	}
	int getI()
	{
		return mi;
	}
};
class Test
{
private:
	Value m2;
	Value m3;
	Value m1;
public:
	Test():m1(1),m2(2),m3(3)
	{
		
	}	
};
int main()
{
	Test t1;
	return 0;
}

在这里插入图片描述
从结果我们可以论证之前的结论,初始化列表无法决定成员的初始化顺序,初始化顺序只与成员的声明顺序一致成我们声明的顺序是2 3 1,结果可以论证

#include <stdio.h>
class Value
{
private:
	int mi;
public:
	Value()
	{

	}
	Value(int i)
	{
		mi = i;
		printf("i = %d\n",i);
	}
	int getI()
	{
		return mi;
	}
};
class Test
{
private:
	Value m2;
	Value m3;
	Value m1;
public:
	Test():m1(1),m2(2),m3(3)
	{
		printf("Test::Test()\n");
	}	
};
int main()
{
	Test t1;
	return 0;
}

在这里插入图片描述
从这个程序的结果我们可以看出程序运行的先后顺序。

首先对 t1 对象里面的成员进行初始化,由于它是另一个类的对象,调用另一个类的构造函数,再调用自己类的构造函数。

3、类中的 const 成员

  • 类中的 const 成员会被分配空间的
  • 类中的 const 成员的本质是只读变量
  • 类中的 const 成员只能在初始化列表中指定初始值
    编译器无法直接得到 const 成员的初始值,因此无法进入符号表成为真正意义上的常量

类中 const 成员它分配的空间是和整个类对象分配的空间一致。如果类对象分配的空间在栈上,那么 const 成员所分配的空间就在栈上;如果类对象分配的空间在堆上,那么const 成员所分配的空间就在堆上。

程序:证实通过指针可以改变const 成员变量的值

#include <stdio.h>
class Value
{
private:
	int mi;
public:
	Value()
	{

	}
	Value(int i)
	{
		mi = i;
		printf("i = %d\n",i);
	}
	int getI()
	{
		return mi;
	}
};
class Test
{
private:
	const int ci;
	Value m2;
	Value m3;
	Value m1;
public:
	Test():m1(1),m2(2),m3(3),ci(10)
	{
		printf("Test::Test()\n");
	}
	int getCI()
	{
		return ci;
	}
	void setCI(int v)
	{
		int* p = const_cast<int*>(&ci);
		*p = v;
	}
};
int main()
{
	Test t1;
	
	printf("t1.ci = %d\n",t1.getCI());
	t1.setCI(5);
	printf("t1.ci = %d\n", t1.getCI());

	return 0;
}

在这里插入图片描述
从最后的结果我们可以知道通过指针(setCI函数)可以改变const 成员变量的值,也就是类中的const 成员不是常量,而是只读变量。

小结:

  • 类中可以使用初始化列表对成员进行初始化
  • 初始化列表先于构造函数体执行
  • 类中可以定义 const 成员变量
  • const 成员变量必须在初始化列表中指定初值
  • const 成员变量为只读变量

分析:我来分析一下第二节的那个一个类调用另一个类的程序。
首先,一般我们都是在主函数定义对象。但是这里我们 Value 的成员变量就是 对象,所以我们这里是在类里面定义成员变量。所以就有一连串的初始化问题。其实不用初始化列表没关系,如果是我下面的程序:

#include <iostream>

using namespace std;
class Value
{
private:
	int m_value;
public:
	Value()
	{
		m_value = 0;
		cout << "Value()" << endl;
	}
	Value(int v)
	{
		m_value = v;
		cout << "Value(int v),v = " << v << endl;
	}

	int getValue()
	{
		return m_value;
	}
};

class Test
{
private:
	Value v1;
	Value v2;
	Value v3;
public:
	Test() //:v1(1), v3(3), v2(2)
	{
		cout << "Test()" << endl;
	}
};
int main()
{
	Test t;

	return 0;
}

因为这里我们是默认调用无参构造函数。但是如果我们要参数,我们必须要利用初始化列表,因为我们的成员变量只能是这样定义

	Value v1;
	Value v2;
	Value v3;

不能像主函数那样定义:

	Value v1(1);
	Value v2(2);
	Value v3(3);

这就是我们要利用初始化列表的原因。
主要原因就是这里的成员变量是对象,和以往我们的成员变量不一样,我们的任务是要完成初始化。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值