1.使用初始化器列表{}来进行一致且通用的初始化
使用初始化列表器可以进行一致且通用的初始化。不受类型的限制,在所有的场景中都能使用。
1)初始化简单内置变量
如 int a = {3};
也可以不使用等号如 int a {3};
2)初始化容器vector
vector<int> nums {1,2,3,4,5};
使用初始化器列表时等号可写可不写;
优点:
使用列表初始化能够防止窄化转换(即列表初始化禁止下列转换),有以下几种情况:
(1)如果一个整型存不下另一种整型的值,则后者不会被转换成前者;
(2)如果一个浮点型存不下另一个浮点型的值,则后者不会被转换成前者;
(3)浮点型不能转换成整型值;
(4)整型值不能转换成浮点型的值。
int x1 {2.7}; //错误,不允许double到int的转换
char c1 {260}; //错误,char占一个字节,260不能表示成一个char,会发生窄化转换
int x2 {3}; //正确
参考资源:初始化列表
2.推断类型:auto和decltype()
C++提供了两种从表达式中推断数据类型的机制:
1)auto
auto根据对象的初始化器推断对象的数据类型,可能是变量、const或者constexpr的类型,当不需要显示指定数据的类型时,一般使用auto,
如:
auto flag = true;//变量的类型为bool
auto ch = 'x'; //变量的类型为char
auto z = sqrt(y); //变量的类型为sqrt函数的返回类型
vector<int> arg {1,2,3,5};
vector<int>::iterator it = arg.begin(); //等价于auto it = arg.begin();很明显,第二种方式代码更简洁。
注意⚠️
(1)当变量采用列表初始化器进行初始化时,使用auto来推断变量的类型会出错,所以使用auto时应该选择 = 的初始化形式;
(2)可以为推断出的类型添加修饰符或说明符,如const 和& ;
示例:
void f( vector<int>& v )
{
for (const auto& x : v)
{
语句;//
}
}
2)decltype(expr)
decltype(expr)推断的对象不是一个简单的初始化器,有可能是函数的返回类型或类成员的类型。
比如
template
vector
即使用decltype来推断表达式的类型,这是auto做不到的,auto只能在变量初始化的时候进行推断,而这里并不想另外定义一个初始化的变量。
常量表达式constexpr
c++提供了两种与“常量”有关的概念:
1)constexpr:编译时求值;
2)const:在作用域内不改变其值;
常量表达式是指值不会改变且在编译的过程中就能够得到计算结果的表达式。
例如:
int x1 = 7;
const x2 = x1; //编译通过,但x2不是常量表达式
constexpr x3 = 9; //常量表达式
constexpr x4 = x1; //错误,初始化器x1不是常量表达式
constexpr x5 = x3; //正确,初始化器x3是常量表达式
⚠️:1)如果constexpr的初始化器不能在编译时求值,编译器会报错。
2)声明为constexpr的变量一定是一个常量,而且必须用常量表达式来进行初始化。
空指针nullptr
在C++11之前,使用0或NULL来表示空指针,但11标准中,使用nullptr能够避免在整数(如0或NULL(NULL是宏))和指针之间发生混淆。
有以下两种情况:
1)当重载函数既可以接受指针也可以接受整数时,在引入nullptr之前,传入0会引起混淆;
2)宏定义NULL在不同的实现中定义有所不同,NULL可能是0,也可能是0L,也会引起混淆;
nullptr只有一个,可以用于任意指针类型;
范围for语句
范围for语句可以依次访问指定范围内的每个元素,示例如下:
int sum(vector<int>& v)
{
int s = 0;
for (int x : v)
{
s += x;
}
return s;
}
for (int x : v)可以读作对于v中的每个x,这里的x的作用域是整个for语句。
当然,也可以使用元素的引用,这种引用方式可以修改元素的值,也可以在拷贝元素的代价昂贵时使用。
例如:
//元素自增
void incr1(vector<int>& v)
{
for (int& x : v)
{
x++;
}
}
//元素引用
template<class T> T accum(vector<T>& v)
{
T sum = 0;
for (const T& x : v)
{
sum += x;
}
return sum;
}
注意⚠️
(1)使用范围for语句的前提是冒号后面的表达式必须是一个序列;
(2)范围for语句,不能同时访问多个元素,也不能同时遍历多个序列;
lambda表达式
利用lambda表达式可以方便的定义和创建匿名函数,以简化编程工作。
lambda表达式的语法形式如下:
[函数对象参数] (形参列表) mutable 或 exception声明 -> 返回类型 {函数体}
即lambda表达式主要分为5个部分:[函数对象参数]、(形参列表)、mutable 或 exception声明、-> 返回类型、{函数体}。
下面分别对这5部分进行分析:
1)[函数对象参数]
标识一个 Lambda 表达式的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数。函数对象参数只能使用那些到定义 Lambda 为止Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类的 this)。函数对象参数有以下形式:
(1)空
没有任何函数对象参数。
(2)=
函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
(3)&
函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
(4)this
函数体内可以使用 Lambda 所在类中的成员变量。
(5)a
将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
(6)&a
将 a 按引用进行传递。
(7)a,&b
将 a 按值传递,b 按引用进行传递。
(8)=,&a,&b
除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
(9)&,a,b
除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
2)(形参列表)
没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种
方式进行传递。
3)mutable或exception声明
这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
4)-> 返回值类型
标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
5){函数体}
标识函数的实现,这部分不能省略,但函数体可以为空。
#include
#include
#include
using namespace std;
bool cmp(int a, int b)
{
return a < b;
}
int main()
{
vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
vector<int> lbvec(myvec);
sort(myvec.begin(), myvec.end(), cmp); // 旧式做法
cout << "predicate function:" << endl;
for (int it : myvec)
{
cout << it << ' ';
}
cout << endl;
// Lambda表达式
sort(lbvec.begin(), lbvec.end(),[](int a, int b) -> bool { return a < b;});
cout << "lambda expression:" << endl;
for (int it : lbvec)
{
cout << it << ' ';
}
}
在C++11之前,我们使用STL的sort函数,需要提供一个谓词函数。如果使用C++11的Lambda表达式,我们只需要传入一个匿名函数即可,方便简洁,而且代码的可读性也比旧式的做法好多了。
参考资源