nullptr 与 constexpr
nullptr
nullptr出现的目的自然是替换NULL的低位。C++可能会将NULL、0视为同一种东西。这取决于编译器是如何定义的,有的编译器定义NULL为 ( (void * )0) ,有的直接定义为0,这样的化在程序中可能会出现意想不到的错误,例如它会破坏函数的重载功能,考虑下面的重载函数
void function(char *p);
void function(int x);
那么当调用 function(0)
时将会调用哪一个函数呢?这可能会在不同编译器产生不同结果。C++11引入nullptr 专门来区分0和NULL,nullptr的类型是nullptr_t
#include<iostream>
using namespace std;
void test(char*p){
puts("调用的是char *");
}
void test(int x){
puts("调用的是int");
}
int main(){
test(0);
test(nullptr);
return 0;
}
/*
调用的是int
调用的是char *
*/
今后尽量使用nullptr避免使用NULL
constexpr
C++本身具备常量表达式的概念,常量表达式(const expression)是指值不会改变并且在编译过程中就得到计算结果的表达式,如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。C++11 提供了 constexpr 让用户显式的声明函数或对象构造函数在编译器会成为常数,在C++11中常量constexpr修饰的函数可以采用递归形式。
- 不再允许字符串字面值常量赋值给一个 char *。如果需要用字符串字面值常量赋值和初始化一个char*,应该使用 const char * 或者 auto
char \*str = "hello world!"; // C++11中无法通过编译
const char *str = "hello world!"; // C++11使用
- C++98 异常说明、 unexcepted_handler、set_unexpected() 等相关特性被弃用,应该使用 noexcept。auto_ptr 被弃用,应使用 unique_ptr。
- register 关键字被弃用。
- bool 类型的 ++ 操作被弃用。
- C 语言风格的类型转换被弃用,应该使用 static_cast、reinterpret_cast、const_cast 来进行类型转换。
C++11的初始化列表
初始化是一个非常重要的语言特性,最常见的就是对对象进行初始化。在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (plain old data,没有构造、析构和虚函数的类或结构体)类型都可以使用 {} 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 () 进行。这些不同方法都针对各自对象。在传统C++中可以使用初始化列表如下:
#include<bits/stdc++.h>
using namespace std;
class A{
public:
int a, b, c;
};
int main(){
int arr[4] = {1, 2 ,3 ,4}; // 数组初始化
A a = A{1,2,3}; // 传统类列表初始化
return 0;
}
为了解决这个问题,C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 initializer_list,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁。可以缉拿个类模板initializer_list作为构造函数的参数,则初始化列表就智能用于构造该函数。值得注意的是列表中的元素必须是5同一种类型或者可以转化为同一种类型。STL中提供了initializer_list作为参数构造函数,考虑vector的下列实例:
vector<int> a1(10); // 定义了大小为10未初始化的int型向量
vector<int> a2{10}; // 使用initializer-list初始化,a2只有一个元素初始化为10
vector<int> a3{1, 2, 3}; // 使用initializer-list初始化,a3有三个元素1,2,3
偷文件initializer_list提供了对类模板initializer_list的支持。该类中包含成员函数begin()和end()可以获得列表范围。除了用于构造函数外,initilazer_list还可以作为常规函数的参数。下面编写一个函数sum来求和,演示该模板类作为函数参数的使用。
#include<iostream>
#include<initializer_list>
using namespace std;
double sum(initializer_list<double> L){
double tol = 0.0;
for(auto p = L.begin(); p != L.end(); p++){
tol += *p;
}
return tol;
}
int main(){
double tol = sum({1.0, 2, 3, 4.5});
cout << tol <<endl;
return 0;
}
在传统 C 和 C++中,参数的类型都必须明确定义,当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这样很不方便,而向python等语言就要显得智能得多。C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。
auto关键字
使用 auto 关键字是一个存储类型说明符,C++11将用于进行类型推导。这个要求进行显示初始化,
auto m = 120; // m是int类型
auto p = &m; // p是int类型指针
以前遍历一个迭代器需要这样做
vector<int>::iterator it;
for(it = vec.begin(); it != vec.end(); it++){
;
}
有了auto后,遍历方便很多,只需要
for(auto x : vec){
;
}
注意:auto 不能用于函数传参,这将无法通过编译的(考虑重载的问题,我们应该使用模板)。
decltype 关键字
关键字decltype 将变量的类型声明为表达式指定的类型。下面语句的意思是让y的类型与x相同:
int x = 5;
decltype(x) y;
再看下面例子
#include<bits/stdc++.h>
using namespace std;
int main(){
double x;
decltype(&x) y; // 定义y为double类型指针
y = new double{4.0};
cout << *y << endl;
return 0;
}
在定义类模板的时候特别有用,因为只有等到模板实例化后才知道具体类型
#include<bits/stdc++.h>
using namespace std;
template<typename T, typename U>
void func(T a, U b){
decltype (a*b) x;
}
int main(){
func(2, 2.5); // 此时x是double类型
func(2u, 6); // 此时x是unsinged int 类型
return 0;
}
特别注意,decltype指定类型可以是引用或者const,如下例子一样:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a = 1;
int &ra = a;
const int b = 3;
decltype(b) y = 4; // y的类型为const int
int c = 0;
decltype(ra) x = c; // x的类型是int &
return 0;
}
尾返回类型
C++11 增加了一种新语法,在函数名和函数列表后面指定返回类型
double f1(double, int); // 传统语法
auto f2(double, int) -> double; // 新语法 ,返回double 类型
这种设计将给C++带来很多方便,例如定义一个模板类返回2个数的和,传统方法需要写为:
template<class T, class U, class R>
R add(T a, U b){
return a + b;
}
由于不知道返回类型,这样传递返回类型是一种很丑陋的写法,有了后置返回类型,上述代码可以写为:
template<class T, class U>
auto add(T a, U b) -> decltype(a + b){
return a + b;
}