文章目录
- 构造函数?
- 初始化列表
- explicit关键字
- 匿名对象
构造函数?
初始化列表
前面已然介绍过构造函数,但并未完全结束,构造函数有很多种写法,有带缺省参数的,有全缺省的,不带缺省参数的…但用前面的方法,都是对里面成员变量的一种赋值,也就是说,这是给类中每一个成员变量一个初始值
但这并不是初始化,这里要分清楚什么是初始化,什么是赋初值
简单来说,它们的一个区别就是初始化只能初始化一次,但是赋值可以多次赋值
因此构造函数中就引入了初始化列表的概念,初始化列表可以做到给类内的成员函数初始化,下面写一个具体的例子来表示
#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;
};
上面就是对初始化列表的概述,通过这样的写法可以对参数进行初始化
这里就体现了初始化和赋值的区别:
- 如果采用的是初始化,那么每个成员变量在初始化列表中只能初始化一次
- 类内如果有引用成员函数,const成员变量和自定义成员且没有默认构造函数时,就要写在初始化列表的位置
- 尽量使用初始化列表进行初始化,不管是否用初始化列表,对于自定义类型的成员变量都会先使用初始化列表进行初始化
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
下面来对第二条进行解读,同时也能更好的理解上面的原理
- 有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上就可以很好的体现出来
- 引用
还有一大使用场景就是在引用中可以体现,这里就不再写错误的示范了
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,但如果正常来说我是需要创建一个对象,再通过对象去引用这个类内的成员函数,这是十分繁琐的,如果使用匿名对象,我不关注这个对象是谁,这个对象是多少,我只关心能不能引用类内的成员函数,因此就可以这样使用,相比起使用定义对象的方法来看,这样的方法的生命周期更短