C++:类和对象(下)---对类和对象深入一些的理解

构造函数?

初始化列表

前面已然介绍过构造函数,但并未完全结束,构造函数有很多种写法,有带缺省参数的,有全缺省的,不带缺省参数的…但用前面的方法,都是对里面成员变量的一种赋值,也就是说,这是给类中每一个成员变量一个初始值

但这并不是初始化,这里要分清楚什么是初始化,什么是赋初值
简单来说,它们的一个区别就是初始化只能初始化一次,但是赋值可以多次赋值

因此构造函数中就引入了初始化列表的概念,初始化列表可以做到给类内的成员函数初始化,下面写一个具体的例子来表示

#include <iostream>
using namespace std;

class Date
{
    
    
public:
	Date(int year = 1900, 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;
};

上面就是对初始化列表的概述,通过这样的写法可以对参数进行初始化

这里就体现了初始化和赋值的区别:

  1. 如果采用的是初始化,那么每个成员变量在初始化列表中只能初始化一次
  2. 类内如果有引用成员函数,const成员变量和自定义成员且没有默认构造函数时,就要写在初始化列表的位置
  3. 尽量使用初始化列表进行初始化,不管是否用初始化列表,对于自定义类型的成员变量都会先使用初始化列表进行初始化
  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

下面来对第二条进行解读,同时也能更好的理解上面的原理

  1. 有const成员变量
#include <iostream>
using namespace std;

class Date
{
    
    
public:
	//Date(int year = 1900, int month = 1, int day = 1)
	//	: _year(year)
	//	, _month(month)
	//	, _day(day){}
	Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
		_tmp = tmp;
	}
	void Print()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _tmp;
};

这里的构造函数对吗?很显然是不能编译通过的,原因也很简单,这里的_tmp是const修饰的变量,怎么能给它赋值?解决方法有两个,第一个是可以给它在声明的时候就给它一个值,这是可以的,但使用初始化列表可以完美解决这个问题

#include <iostream>
using namespace std;

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
		: _year(year)
		, _month(month)
		, _day(day)
		, _tmp(tmp) {
    
    }
	//Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
	//{
    
    
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//	//_tmp = tmp;
	//}
	void Print()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
		cout << "const tmp  " << _tmp << endl;;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _tmp = 1;
};

这里也能更好理解赋值和初始化的区别,从const上就可以很好的体现出来

  1. 引用

还有一大使用场景就是在引用中可以体现,这里就不再写错误的示范了

class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
		: _year(year)
		, _month(month)
		, _day(day)
		, _tmp(tmp) {
    
    }

	//Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
	//{
    
    
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//	//_tmp = tmp;
	//}
	void Print()
	{
    
    
		cout << _year << "/" << _month << "/" << _day << endl;
		cout << "const tmp  " << _tmp << endl;;
	}
private:
	int _year;
	int _month;
	int _day;
	int& _tmp;
};

explicit关键字

C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用

在了解explicit前,就必须先知道隐式转换是什么了
显式转换一定不陌生,就是强行转换,因此隐式转换就是编译器自动发生转换
比如说下面这样的例子:

int main()
{
    
    
	int i = 0;
	double d = 1.2;
	int& p = d;
	return 0;
}

这里编译是通不过的,原因是因为d是double类型因此编译不通过吗?其实不然

在这里插入图片描述
在编译器的处理中,会把d先生成一个临时变量,再进行赋值,而我们都知道临时变量是具有常性的,因此这里并不能把一个常属性的数交给引用来处理

因此这里只需要加上const,进行权限的缩小,就可以用常引用接纳常性变量了

const int& p = d;

其实,上述过程中把变量d从double类型转换到int类型就是一个隐式类型转换,我们并没有进行转换,但是编译器依旧自己转换了,并且生成了临时变量导赋值失败

而explicit的存在就是不能让隐式转换存在,因此我们不能把类型局限在int和double类型,要加入类的类型

class A
{
    
    
public:
	A(int a = 10) :_a(a) {
    
    }
private:
	int _a;
};

int main()
{
    
    
	A a;
	a = 2;
	return 0;
}

看看上面的代码发生了什么?把2赋给了一个类?如果你知道了隐式类型转换,那么就不难理解这段代码,这就是把2进行隐式类型转换,把它转换成了类A,里面成员变量_a的值是2,然后又进行了一次赋值

那么explicit就可以登场了,在初始化列表前面加上explicit:

class A
{
    
    
public:
	explicit A(int a = 10) :_a(a) {
    
    }
private:
	int _a;
};

int main()
{
    
    
	A a;
	a = 2;
	return 0;
}

此时,隐式转换就不存在了,因此这里的赋值就不复存在了,因为2在这里真的就是一个数字2

匿名对象

C++中引入了匿名对象的概念,简单来说就是没有名字的对象

class A
{
    
    
public:
	explicit A(int a = 10) :_a(a) {
    
    }
private:
	int _a;
};

int main()
{
    
    
	A(2);
	return 0;
}

这里看起来很奇怪,但这是可以通过的,C++内部是允许这样做的,它和有名对象的区别在于:
匿名对象的生命周期只在这一行,结束后就进行析构,而正常生成的对象需要在main函数结束后才会进行析构,普通生成的对象的生命周期是它的局部域

那匿名对象有什么用?

假设有这样的情景

class A
{
    
    
public:
	explicit A(int a = 10) :_a(a) {
    
    }
	void Print()
	{
    
    
		cout << "Print" << endl;
	}
private:
	int _a;
};

int main()
{
    
    
	A a1;
	a1.Print();
	A().Print(); // 匿名对象
	return 0;
}

假如这里我只需要调用类内的成员函数print,但如果正常来说我是需要创建一个对象,再通过对象去引用这个类内的成员函数,这是十分繁琐的,如果使用匿名对象,我不关注这个对象是谁,这个对象是多少,我只关心能不能引用类内的成员函数,因此就可以这样使用,相比起使用定义对象的方法来看,这样的方法的生命周期更短

猜你喜欢

转载自blog.csdn.net/qq_73899585/article/details/131987909
今日推荐