初始化列表
与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
为什么使用初始化列表
初始化类的成员有两种方式:
1.使用初始化列表
2.在构造函数体内进行赋值操作。
使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?由上面的测试可知, 使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。
常用的初始化方式:
class Student
{
public:
Student(string n, int a)
{
name = n;
age = a;
}
private:
string name;
int age;
};
这样可以达到预期效果,但却不是最佳做法。因为在构造函数中,是对name进行赋值,不是初始化,而string对象会先调用它的默认构造函数,再调用string类的赋值构造函数。对于age,因为int是内置类型,应该是赋值的时候获得了初值。
- 要对成员进行初始化,而不是赋值,可以采用初始化列表(member initialization list)改写为如下:
class Student
{
public:
Student(string n, int a) :name(n), age(a) {}
private:
string name;
int age;
};
结果与上例相同,不过在初始化的时候调用的是string的拷贝构造函数,而上例会调用两次构造函数(默认构造函数,赋值函数),从性能上会有不小提升。
必须使用初始化列表的成员
-
常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
-
const数据成员,不能像常规的构造函数来初始化
#include<iostream>
using namespace std;
class A
{
public:
A(int a, int b, int c)
{
ma = a;
mb = b;
mc = c;//error
}
private:
int ma;
int mb;
const int mc;//常量数据成员
};
- const变量一定要在创建的时候初始化
const int a = 1;//yes
const int b;
b = 1;//no
- 在构造函数中为什么不能使用赋值语句为const成员提供值
从概念上讲,调用构造函数时,对象在程序进入构造函数函数体之前被创建。也就是说,调用构造函数的时候,先创建对象,再进入函数体。对于A()构造函数来说,先为三个成员分配内存,然后再进入函数体使用常规的赋值方式将值存储到内存中。
- const成员应该在分配内存的时候进行初始化。对此,c++提供了初始化列表语法来完成这项工作。
#include<iostream>
using namespace std;
class A
{
public:
A(int a, int b, int c) :ma(a), mb(b), mc(c) {}//ok
private:
int ma;
int mb;
const int mc;//常量数据成员
};
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值。所以也要写在初始化列表里面
class Agency {};
class Agent
{
public:
Agent(Agency& b) :belong(b) {}
private:
Agency& belong;
};
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
class A
{
public:
A(int a) :ma(a) {}
private:
int ma;
};
class B
{
public:
B(A& a1 )
{
a = a1;//error 类A不存在默认构造函数
}
private:
A a;
};
以上代码无法通过编译,因为B的构造函数中a = a1这一行实际上分成两步执行:
- 调用A的默认构造函数来初始化a1
由于A没有默认的构造函数,所以1 无法执行,故而编译错误
使用初始化列表
class A
{
public:
A(int a) :ma(a) {}
private:
int ma;
};
class B
{
public:
B(A& a1) :a(a1) {}//ok
private:
A a;
};
C++98成员变量初始化
- 在声明类的时候,对于静态常量类型,并且是枚举或者是整型的变量可以使用=在声明时初始化。
int a = 7;
class A {
static const int ma = 7; // ok
const int mb = 7; //error:无static
static int mc = 7; //error:无const
static const int md = a; // error:初始化值不是常量表达式
static const string me ="abc" //error:非整型
};
- 对于不符合上述要求的静态变量可以在类外使用=进行初始化
- 对于非静态类型可以在初始化列表进行初始化
- 使用()对自定义类型进行初始化
- 使用{}对元素集合统一初始化
C++11中的成员变量初始化
- 允许非静态成员变量的初始化有多种形式:初始化列表; 使用等号=或花括号{}进行就地的初始化
- 可以为同一成员变量既声明就地的列表初始化,又在初始化列表中进行初始化,只不过初始化列表总是看起来“后作用于”非静态成员。
- 初始化列表的效果总是优先于就地初始化的。
class A
{
public:
private:
int ma = 0;
int mb = { 1 };
int mc{ 2 };
};
等价于:
class A
{
public:
A() :ma(0), mb(1), mc(2) {}
private:
int ma;
int mb;
int mc;
};
列表初始化优点
- 可以用列表初始化一个容器(vector,list,map,set…),不必调用成员函数添加元素
int main()
{
vector<int> c{ 1, 3, 5 };
map<int, float> d = { {1, 1.0f}, {2, 2.0f}, {3, 3.0f} };
}
- 可以防止类型收窄,即对可能造成信息损失的类型转换,提示警告或者直接报错
int main()
{
long double ld = 3.1415926;
int a{ ld }, b{ ld }; //error,转换未执行,因为存在丢失信息的危险
//int a(ld), b(ld); //true,转换执行,且确实丢失了部分值
}