构造函数

157 阅读1分钟

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

💦 概念

❗ 场景 ❕

class Stack
{
public:
	void Init()
	{
		_a = nullptr;
		_top = _capacity = 0;	
	}
	void Push(int x);
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;
	s1.Push(3);
	
	return 0;
}

📝 说明

C 语言中,可能会忘记调用 Init 函数,然后一顿错误操作。这时 C++ 就能做到对象定义出来就初始化 —— 构造函数。

可以认为构造函数的出现就是为了替代 Init。

❗ 利用构造函数替代 Init ❕

class Stack
{
public:
	Stack()//1.函数名和类名相同//2.无返回值
	{
		_a = nullptr;
		_top = _capacity = 0;	
	}
	Stack(int capacity)
	{
		_a = malloc(sizeof(int)*capacity);
		if(_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);	
		}
		_top = 0;
		_capacity = capacity;
	}
	void Push(int x);
private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack s1;//3.对象实例化时编译器自动调用对应的构造函数
	Stack s2(10);//4.构造函数可以重载
	s1.Push(3);
	
	return 0;
}

📝 说明

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。

💦 特性

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

1️⃣ 函数名与类名相同。

2️⃣ 无返回值。

3️⃣ 对象实例化时编译器自动调用对应的构造函数。

4️⃣ 构造函数可以重载。

接下来我们就来理解几个概念用 ❓ ❔ 包含


❓ 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成 ❔

class A
{
public:
	A()
	{
		_a1 = 0;
		_a2 = 1;
		cout << "A()" << endl;
	}
private:
	int _a1;
	int _a2;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;	
	}
private:
	//内置类型/基本类型:int、char、指针 ... ———— 不会初始化
	int _year;
	int _month;
	int _day;
	//自定义类型:struct/class ———— 调用它的无参构造函数初始化
	A _a;
};
int main()
{
	Date d1;//对象实例化时自动调用构造函数
	d1.Print();//再调用Print函数
	
	return 0;
}

📝 说明:

  不是说我们不写构造函数,编译器会自动生成一个无参的默认构造函数 ❓

在这里插入图片描述

这也是早期 C++ 设计留下的一个缺陷,有些编译器会把这初始化为 0,1,1

C++ 把我们的变量分成内置类型 (int、char、指针 ...) 和自定义类型 (struct/class)

我们不写构造函数,编译器默认生成构造函数,但是编译器做了一个偏心的处理

1、内置类型不会初始化 2、自定义类型它会调用它的无参构造函数初始化

在这里插入图片描述

其实这块内容算是 C++ 在早期设计时的语法缺陷,这种偏心的处理导致语法反而复杂了,后面也发现了这个问题,但是为时已晚不能修改,因为 C++ 要向前兼容。好多书上也没有讲清楚,这也是很多人说 C++ 难学的地方。

关于编译器生成的默认构造函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成默认构造函数,但看起来默认构造函数又没什么用 ❓

这里说看起来没什么用主要体现在 d1 调用了编译器生成的默认构造函数,但是 _year、_month、_day 依然是随机值。

C++ 把类型分成内置类型 (基本类型) 和自定义类型,这里只是说它不会处理内置类型,而自定义类型它会去调用它的无参构造函数

到了 C++11 时 C++ 针对这里的情况做了补充 ❓

C++11 时,语法委员会针对这里打了一个补丁,也就是在变量声明中加上缺省值。

这个补丁雀实会给很多初学者造成困扰,很多人会认为它是初始化,其实它是缺省值。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
private:
	int _a1;
	int _a2;
};
class Date
{
public:
	Date()
	{
		_year = 2022;//只初始化_year,其它默认
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;	
	}
private:
	//C++11 缺省值
	int _year = 0;
	int _month = 1;
	int _day = 1;
	A _a;
};
int main()
{
	Date d1;
	d1.Print();
	
	return 0;
}

📝 说明

就同函数的缺省参数一样。如果构造函数里没有初始化,那么对于内置类型它会使用缺省值初始化

在这里插入图片描述

❓ 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数 (不传参数就可以调用的那个函数) ❔

class Date
{
public:
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//Date d1(3);ok
	return 0;
}

📝 说明

如上两个构造函数 Date 本来是可以构造函数重载的,但是他们不能同时存在 ~ Why ?❓

因为编译器调用的时候会出现歧义,一般情况下我们推荐写一个全缺省的构造函数,因为它比较好用。

这里有一个误区:有的童鞋会认为,我们不写,编译器默认生成的构造函数就叫做构造函数,这个是不对的,它还有无参的构造函数和全缺省的构造函数都是默认构造函数。

❓ 成员变量的命名风格 ❔

class Date
{
public:
	Date(int year)//不好的命名风格
	{
		year = year;	
	}
	Date(int year,int month)//好的命名风格
	{
		_year = year;
		_month = month;
	}
private:
	int year;
	int _year;
	int _month;
};
int main()
{
	Date d1(1);
	Date d2(1, 2);
	return 0;
}

📝 说明

当然这样不仅仅是规范的问题,还可能会造成运行时错误。

执行 Date d1 (1) ; 后,year 的值是 1 还是随机值 ❓

站在编译器的角度 this->year = year,所以是 1 ???

NO ~~,实际上在 Date 里它会采用就进原则,也可以认为是局部优先。

当 year = year 时,它会先找到形参,然后就变成了自己赋值给自己了。

当 _year = year 时,它先找局部变量,没找到,它会再外面的域搜索,找到了,编译器就会在前面加 this。

或者直接 this->year = year 但不推荐。