前言
该篇文章讲述C++11新特性中
①列表初始化
(1)列表初始化的好处
(2)必须使用列表初始化的一些场合
(3)列表初始化中的顺序
②列表初始化防止类型收窄
③范围for循环
列表初始化
C++11推出了一种可以让我们更方便,更高效的初始化方法:列表初始化
构造函数的两个阶段
构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,先初始化阶段,后计算阶段
关键点
所有类类型的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的函数体中
初始化列表可以提升我们程序的性能,对于内置类型用不用初始化列表可能没什么区别,但如果是初始化自定义数据类型,可以少调用一次默认构造函数,在数据量非常大的时候,可以有效的帮助我们提升程序的运行效率
先来看个例子,Person类定义了默认构造函数,拷贝构造函数以及显示重载了赋值运算符,主要是为了方便结果的查看
class Person
{
public:
Person()
{
cout << "Person默认构造函数调用" << endl;
}
Person(const Person& p)
{
this->a = p.a;
cout << "Person拷贝构造函数调用" << endl;
}
Person& operator=(const Person& p)
{
this->a = p.a;
cout << "Person重载赋值运算符调用" << endl;
return *this;
}
int a;
};
自定义了 Text类,有Person类型的成员变量p,构造函数函数体内初始化p
class Text
{
public:
Person p;
Text(Person& p1)
{
p = p1;
}
};
主函数调用如下代码
int main()
{
Person p;
Text t(p);
return 0;
}
程序运行结果:
①Person p; 调用一次默认构造
② 初始化阶段对Text类的成员进行初始化,Text里的Person p;调用一次默认构造
③p=p1,重载赋值运算符的调用
那么如果使用初始化列表初始化自定义类型的数据,主函数不变,结果是怎么样的呢?
class Text
{
public:
Person p;
Text(Person& p1):p(p1){}
};
程序运行结果:
主函数代码是没变的,但运行结果完全不同,发现少调用了一次默认构造函数,因为在初始化列表中,会省去默认构造函数的过程,直接调用拷贝构造函数初始化p,所以我们在编写代码过程中,能用初始化列表就尽量使用初始化列表
必须使用初始化列表的一些场合
①常量成员初始化,因为常量成员只能初始化不能赋值,所以必须在初始化列表中
class Person
{
public:
Person(int num)
{
a = num;//错误,a是常量,不能被赋值
}
const int a = 10;
};
class Person
{
public:
Person(int num):a(num){} //正确,初始化列表
const int a = 10;
};
int main()
{
Person p(12);
cout<<p.a<<endl;//a的值为12
}
②没有默认构造函数的类类型,因为使用初始化列表可以不调用默认构造函数,直接在初始化列表中调用拷贝构造函数
class Person
{
public:
Person(int num):a(num){}
int a;
};
class Text
{
Person p;
Text(Person& p1)
{
p = p1;
}
};
这段代码是错误的,因为Person没有默认的构造函数,所以Text的成员Person p初始化时就会编译不通过,使用初始化列表就可以解决这个问题
class Person
{
public:
Person(int num):a(num){}
int a;
};
class Text
{
Person p;
Text(Person& p1):p(p1){}
};
③引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以需要初始化列表
列表初始化中的顺序
成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的
class Person
{
public:
Person(int i):a(i),b(a){} //正确,先用i初始化a,再用a初始化b
int a;
int b;
};
class Person
{
public:
Person(int i):b(i),a(b){} //错误,用b初始化a,但b此时未定义
int a;
int b;
};
因为a,b定义是a先于b,所以列表初始化先初始化a,用b初始化a,但此时b是未定义的,所以错误
列表初始化防止类型收窄
什么是类型收窄?可以简单理解为精度丢失
int a = 1024;
char c = a;
printf("%d\n",c);
上面代码不会报错,编译可以通过,但c输出值为0,为什么?因为char在内存中占1个字节,8位,转成int值最大表示为127,1024装不下呀,所以这时候就会出现类型收窄,但又不报错,咋办?
int a = 1024;
char c = {a};
使用初始化列表就可以避免这种问题,因为编译都通过不了,会告诉你这需要收缩转换,当然也不是所有编译器都支持这样,有些编译器还是允许通过的,例如qt
范围for循环
这是C++11引进的更为简单的for循环语句
//基本语法
for(变量:序列)
{
函数体
}
序列:可以是用花括号括起来的初始值列表、数组或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员,并且序列的范围要确定才能使用(形参中的数组不是数组,是指针变量,无法确定范围)
void func(int a[])
{
for(auto k:a)//错误,形参中的数组不是数组,是指针变量,无法确定范围
...
}
变量:序列中的每个元素都能转换成该变量的类型。确保类型相容最简单的办法是使用auto类型,这个关键字可以让编译器帮助我们指定合适的类型。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型
函数体:执行的操作
vector<int> vec = {1,2,3,4,5,6};
//传统写法
for (auto it = vec.begin(); it != vec.end(); it++)
{
cout << *it;
}
//范围for循环
for (auto it : vec) //相当于创建了一个局部变量,自动(it++)向后遍历
{
cout << it;
}
for (auto &it : vec) //引用更高效,并且可以改变原数组
{
cout << it;
}
这篇文章对你有帮助的话~就点个赞吧~
点赞收藏关注就是对我最大的支持~
一起学习C++11新特性~