类的初始化列表以及const & static修饰成员

类的初始化列表以及const & static修饰成员

一:初始化列表

我们上一篇博客知道了对象的生成过程中,对象开辟了内存空间后就要进行初始化,而我们当时是这样初始化的:

现在,我们有另一种方法,也可以完成初始化:

我们看到第一步和第三步可以交给初始化列表完成,其实这两种方法都可以进行初始化,看个人喜好进行选择,不过有一些特殊情况只能选择第二种:

比如:

特殊情况1:类中的某些成员,其本身就是一个类或者结构,那么我们只能通过初始化列表对其进行初始化(需要初始化的数据成员是对象)

例如:类Student中的成员变量:

其中的birth是一个类Date实例化生成的一个对象:

那么我们可以看看类Student的构造函数以及拷贝构造函数对类Date生成的birth对象是如何进行初始化的:

#include <iostream>

class Date
{
public:
	Date(int y, int m, int d)
	{
		year = y;
		month = m;
		day = d;
	}

private:
	int year;
	int month;
	int day;
};

class Student
{
public:
	Student(char* name, int id, float score, int y, int m, int d):birth(y, m, d), mid(id)
	{
		mname = new char[strlen(name) + 1];
		strcpy(mname, name);
		//mid = id;
		mscore = score;
	}
	Student(const Student& rhs) :birth(rhs.birth)//Date birth = rhs.birth;
	{
		mname = new char[strlen(rhs.mname) + 1]();
		strcpy(mname, rhs.mname);
		mid = rhs.mid;
		mscore = rhs.mscore;
	}
	Student& operator=(const Student& rhs)
	{
		if (this != &rhs)
		{
			delete[] mname;
			mname = new char[strlen(rhs.mname) + 1];
			strcpy(mname, rhs.mname);
			mid = rhs.mid;
			mscore = rhs.mscore;
		}
		return *this;
	}
	~Student()
	{
		delete[] mname;
		mname = NULL;
	}
private:
	char* mname;
	int mid;
	float mscore;
	Date birth;//class Date类
};

int main()
{
	Student st1("zhangsan", 1, 96.5, 1996, 12, 15);
	return 0;
}

特殊情况2:类中的某些成员,其本身就是一个常成员变量或者引用成员变量,那么我们只能通过初始化列表对其进行初始化(需要初始化的成员是const修饰的常成员变量或者引用成员变量)

原因:因为我们都知道在C++中const修饰的变量我们叫常量,是一个立即数,编译阶段在使用的地方进行调换即可,其特点是声明时必须进行初始化,且初始化的值必须是常量,而引用我们在之前也学习过,其特点类似,也是在声明时就必须进行初始化,初始化的值必须可以取地址,且不能二次赋值。

所以:我们只能通过成员初始化列表对其进行初始化,不能在构造函数中对其进行初始化,因为走到构造函数中时,成员变量初始化阶段已经过了,这时进行初始化其实只是简单赋值,很显然,对其进行赋值是错误的。

例如:类Test的成员变量ma,被const修饰,则其变成了常成员变量,这时在构造函数中对其进行名义上的初始化是错误的。

我们对其进行修改,用构造函数的初始化列表对其进行初始化,这样可以正常编译通过:

#include <iostream>

class Test
{
public:
	Test(int a, int b) :mb(b), ma(a){}

	void Show()
	{
		std::cout << "ma:" << ma << std::endl;
		std::cout << "mb:" << mb << std::endl;
	}

private:
	const int ma;
	int mb;
};
int main()
{
	Test test1(10, 20);
	test1.Show();
	
	return 0;
}

其实不算特殊情况,就根据编译器效率来看,用构造函数的初始化列表也更好一点

原因:我们根据类对象的构造顺序显示,进入构造函数体后,是对成员变量的赋值操作,但是初始化和赋值显然是不同的,这样就出现的效率差异,如果不用初始化列表,那么类对自己的成员变量先进行的是一次隐式的初始化,再对其进行赋值操作,这个样子显然效率变低了,没有初始化列表一步到位效率高。

注意:构造函数需要初始化的成员变量,不论是否出现在构造函数的初始化列表中,都会在该处完成初始化,并且初始化的顺序和其类成员变量声明顺序一致,与列表的先后顺序无关,这里要特别注意,要保证两者逻辑顺序一致。

例如:

其结果和预想一致,先对ma进行初始化为10,在用ma的值初始化mb,所以结果都是10。

二:const修饰成员

const修饰的对象叫常对象

将方法变成常方法,方法名后面加const即可

常对象:

  • 常对象不能调用普通方法
  • 常对象只能调用常方法

普通对象:

  • 普通对象可以调用普通方法
  • 普通对象可以调用常方法

常方法与普通方法的关系:

  • 常方法不能调用普通方法
  • 普通方法可以调用常方法
  • 常方法可以调用常方法
  • 普通方法可以调用普通方法(这个我们平时就在用,这里就不测试了)

我们一一来测试:

①常对象不能调用普通方法

②:常对象只能调用常方法

③:普通对象可以调用普通方法

④:普通对象可以调用常方法

⑤:常方法不能调用普通方法

⑥:普通方法可以调用常方法

⑦:常方法可以调用常方法

三:static修饰成员

static修饰的成员变量是静态成员变量

静态成员变量特点:

  • 属于类作用域的,不属于对象私有。
  • 不属于对象私有,所以一定要在类外进行初始化。

static修饰的成员方法是静态成员方法

  • 静态成员方法遵循_cdecl调用约定,而不遵循_thiscall调用约定。(_thiscall调用约定:是一种依赖对象的调用约定
  • 因为不遵循_thiscall调用约定,所以静态成员方法不依赖对象调用(不依赖:可有可无)

静态成员(变量/方法)的访问方式:

  • 通过类作用域访问
  • 通过对象访问(其实对象也是通过其类作用域访问的)

静态的成员方法与(普通/静态)(成员变量/成员方法)之间的关系:

①:静态的成员方法不能访问普通的成员变量

②:静态的成员方法可以访问静态的成员变量,当然也可以访问全局变量

③:静态的成员方法不能调用普通的成员方法

④:静态的成员方法可以调用静态的成员方法

我们依次来测试验证:

①:静态的成员方法不能访问普通的成员变量

②:静态的成员方法可以访问静态的成员变量,当然也可以访问全局变量

③:静态的成员方法不能调用普通的成员方法

④:静态的成员方法可以调用静态的成员方法

四:其他零碎知识

①:我们这里了解一下编译器对类的扫描顺序

  • 首先处理完所有的类成员声明,接着才处理成员函数的定义,正因如此,所以成员函数可以使用类内定义的所有成员,即使有些成员的声明在它的后面。
  • 对于类成员的声明来说,是按从上到下顺序进行编译的,声明只能使用在之前已经声明过的成员。
  • 类名 --> 类成员声明(类成员方法需要扫描其返回值以及解析形参) --> 类成员方法定义

注意:在类中再存在一个类,这个类我们可以将其看作一个数据类型,只需要在编译阶段语法树上存放一下就OK,其编译顺序在成员方法的返回值和形参解析之后

例如:下方这个图的红框框:

②:explicit关键字(中文翻译:明确的)

作用:禁止隐式生成临时变量,不禁止显式生成,声明为explicit的构造函数不能在隐式转换中使用。

例如:

③:mutable关键字(中文翻译:易变的)

作用:为了突破const的限制设置的,与const刚好相反,被mutable修饰的成员变量(mutable只能修饰类的非静态成员变量),将永远处于可变的状态,即使在一个const函数中。

例如:

我们将其进行简单的修改,再进行测试:

这时候就会发现,被mutable修饰的成员变量mb,即使在常成员方法中也可以对其进行修改。

④:volatile关键字(中文翻译:易变的,不稳定的)

作用:volatile修饰的变量会使其变得很小心,每次使用的时候,都会从内存空间中重新读取这个变量的值不允许编译器对访问该变量的代码进行优化,从而可以保证对特殊地址的稳定访问。

例如:我们这里定义一个常变量,然后对其通过指针进行修改(本身不可以这样指向,但是强制类型转换非常暴力),我们可以看到在编译阶段就将准备打印中的a替换成了10,所以对其内存中存放的值的修改不会引起a的打印。

我们将其进行简单的修改,加上volatile关键字,再进行测试:

这时候我们可以发现,加上volatile关键字之后,打印结果变了,变成了20和20,这就体现出了volatile关键字的作用了,它修饰了变量a,所以运行时最后在用到这个变量a的地方从其内存空间中重新读取了一遍,这时将10更新为了20,结果才打印了两个20。

至此,类的初始化列表以及const&static修饰成员了解完毕。

猜你喜欢

转载自blog.csdn.net/IT_Quanwudi/article/details/87139947