【C++】再谈构造函数 - 构造函数赋值 && 初始化列表 && explicit关键字

316 阅读2分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

构造函数体的赋值

  1. 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
  2. 但,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

初始化列表

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

class Date {
public:
	Date(int year, int month, int day)
		:_year(year),
		_month(month),
		_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

【注意】

  • 初始化只能有一次
  • 类中的 引用成员变量、const 成员变量、 没有默认构造函数的自定义类型成员 必须放在初始化列表的位置进行初始化。
class A {
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};

class B {
public:
	B(int a, int n, int ref)
		:_aobj(a),
		_n(n),
		_ref(ref)
	{}
private:
	A _aobj;  // 没有默认构造函数的类
	int& _ref; // 引用
	const int _n; // const
};
  • 不管有没有使用初始化列表,对于自定义类型成员变量,一定会先使用初始化类别初始化
class A {
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};

class B {
public:
	B(int a = 100)
	{
		cout << "B(int a)" << endl;
	}
private:
	A _aobj; 
};

int main()
{
	B b;
	return 0;
}

输出

A(int a = 0)
B(int a)
  • 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

explicit 关键字

构造函数不仅可以构造初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

class A {
public:
	//A(int a = 0)
	//	:_a(a)
	//{
	//	cout << "A(int a = 0)" << endl;
	//}

	explicit A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};

int main()
{
	// A a(100);

	A b = 100;
	return 0;
}

用一个整形变量给A;类型对象赋值。 实际上编译器会用2019构造一个无名对象,最后用无名对象给b进行赋值。

在这里插入图片描述 explicit 修饰构造函数,将会禁止单参构造函数的隐式转换。

this 指针

C++编译器给每个 非静态的成员函数 增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户都是透明的,即用户不需要来传递,编译器自动完成。

==特性==

  1. this 指针的类型:类类型 * const
  2. 只能在 成员函数 的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

【问题】

  • this指针存在哪里?

一般情况下是在栈(形参)。有些编译器会放在寄存器中,如vs2013放在寄存器 ecx 中。

  • this 指针可以为空吗?
class A
{
public:
	A(int a = 0) {_a = a; }
	void Print() { cout << "Print()" << endl; }
	void Num() { cout << _a << endl; }
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->Print();
	p->Num();
	return 0;
}
  1. p->Print(); 正常运行
  2. p->Num();运行崩溃

在这里插入图片描述

==分析==

  1. p虽然是空指针,但是p调用成员函数不会编译报错,因为空指针不是语法错误,编译器检查不出来。
  2. p虽然是空指针,但是p调用成员函数也不会出现空指针访问。因为成员函数没有存在对象里面。
  3. 这里会把p作为实参传递给隐藏 this指针。