nullptr替代 NULL
传统C++会把NULL、0视为同一种东西,主要取决于编译器如何定义NULL,有些编译器会将NULL定义为((void*)0),有些则会定义为0。(在C语言中,NULL被定义为(void*)0,而在C++语言中,NULL则被定义为整数)但是在《C++ Primer 5th》这本书中P49提到:NULL是预处理变量,在头文件cstdlib中定义,它的值就是0。
C++不允许直接将 void* 隐式转换为其他类型,但是如果NULL被定义成 ((void*)0),那么当编译char * ch=NULL时,NULL只好被定义成0。从而导致C++中重载混乱
例如:
void f(char* ch);
void f(int);
对于这两个函数来说,如果 NULL 又被定义为了 0 那么 foo(NULL); 这个语句将会去调用 foo(int),从而引发错误。
为了解决这个问题,C++11引入了nullptr关键字,专门来区分空指针,0的问题。
nullptr 的类型为 nullptr_t,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
const class nullptr_t{
public:
template<class T> inline operator T*() const{ return 0; }
template<class C, class T> inline operator T C::*() const { return 0; }
private:
void operator&() const;
} nullptr = {};
但是,nullptr同样存在一定的问题
例如:
#include <iostream>
using namespace std;
void fun(char* p)
{
cout << "char* p" << endl;
}
void fun(int* p)
{
cout << "int* p" << endl;
}
void fun(int p)
{
cout << "int p" << endl;
}
int main()
{
fun((char*)nullptr);//语句1
fun(nullptr);//语句2
fun(NULL);//语句3
return 0;
}
结果:
出现重载函数调用不明确的错误,主要是由于传入nullptr指针仍然存在无法区分实际调用那个函数
如果将此行注释,则
类型推导
C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来进行判断变量的类型。
auto
auto 让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值。
注意:
- 语句中所有变量的初始基本数据类型必须一样
auto sz = 0, pi = 3.14; //错误
- auto不能用于函数传参
int add(auto x, auto y);//错误
- auto不能用于推导数组类型
#include <iostream>
int main() {
auto i = 5;
int arr[10] = {0};
auto auto_arr = arr;
auto auto_arr2[10] = arr;
return 0;
}
输出:
decltype
有的时候我们还会遇到这种情况,我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有可能是函数的返回类型为某表达式的值类型。在这些时候auto显得就无力了,所以C++11又引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。
decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。
区间迭代
基于范围for循环
最常用的 std::vector 遍历将从:
std::vector<int> arr(5, 100);
for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {
std::cout << *i << std::endl;
}
变成:
// & 启用了引用
for(auto &i : arr) {
std::cout << i << std::endl;
}
初始化列表
C++11 提供了统一的语法来初始化任意的对象,例如:
struct A {
int a;
float b;
};
struct B {
B(int _a, float _b): a(_a), b(_b) {}
private:
int a;
float b;
};
A a {1, 1.1}; // 统一的初始化语法
B b {2, 2.2};
Lambda表达式
Lambda 表达式,实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。
Lambda 表达式的基本语法如下:
[ caputrue ] ( params ) opt -> ret { body; };
- capture是捕获列表;
- params是参数表;(选填)
- opt是函数选项;可以填mutable,exception,attribute(选填)
mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
exception说明lambda表达式是否抛出异常以及何种异常。
attribute用来声明属性。 - ret是返回值类型(拖尾返回类型)。(选填)
- body是函数体。
新增容器
std::forward_list
std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。
右值引用和move
理解不到位,后续再更新吧
学习过程中,主要参考了jiange_zh的文章