声明:内容来自实验楼《C++ 11/14 高速上手教程》,由本人整理、实验得来。
一、nullptr
nullptr
出现的目的是为了替代 NULL
。在某种意义上来说,传统 C++ 会把 NULL
、0
视为同一种东西,这取决于编译器如何定义 NULL
,有些编译器会将 NULL
定义为 ((void*)0)
,有些则会直接将其定义为 0
。
因此应该用nullptr
来代替NULL
。
二、类型推导
C++11 引入了 auto
和 decltype
这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。
auto
auto i = 5; // i 被推导为 int
auto arr = new auto(10) // arr被推导为 int *
但是我认为像上面的应用最后自己定义类型,这样程序会比较清晰,那么在什么情况下时候使用auto
,最适合的是在迭代器中使用。
#include<iostream>
#include<vector>
using namespace std;
int main() {
auto a = 1; //a为int类型
cout << a << endl;
vector<int> v = { 1,2,3,4,5 };
//auto it=v.begin()相当于vector<int>::iterator it=v.begin();
for (auto it = v.begin(); it != v.end(); ++it)
cout << *it << " " ;
cout << endl;
}
**但是要注意的是:**auto 不能用于函数传参,因此下面的做法是无法通过编译的:
int add(auto x, auto y);
会出现编译错误
#include <iostream>
int main() {
auto i = 5; //i为int
int arr[10] = { 0 };
auto auto_arr = arr; //auto_arr为int* 类型
auto auto_arr2[10] = arr; //编译错误
return 0;
}
总结:在实际的编程中一般我只会将auto
用在迭代器的类型推导中。
decltype
decltype
关键字是为了解决 auto
关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 sizeof 很相似:
decltype(expression)
示例:
auto x = 1, y = 2;
decltype(x + y) z; //z的类型为int,他是x+y的返回值决定的。
三、尾返回类型
有时,我们会遇到不知道该让函数返回何种类型,如
add(T x,U y)那么是个函数的返回类型是不固定的,但是我们不能写成
decltype(x+y) add(T x, U y);
事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,x 和 y 尚未被定义,因此C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto 关键字将返回类型后置。
如下所示:
#include <iostream>
using namespace std;
template<typename T, typename U>
auto add(T x, U y) ->decltype(x+y) { //注意声明格式
return x + y;
}
int main() {
cout << add(3,3.5)<< endl;
return 0;
}
但是c++14又有了新的突破,直接用auto add(T x,U y)
即可,上述实验在vs2015都成功了。
四、区间迭代
C++11 引入了基于范围的迭代写法,这种写法更加的简单明了,很具有实用性。
#include <iostream>
#include<vector>
using namespace std;
int main() {
vector<int> v = { 1,2,3,4,5 };
for (auto it = v.begin(); it != v.end(); ++it) //迭代器遍历
cout << *it << " ";
cout << endl;
//如果要修改值,改为引用for (auto &c : v)
for (auto c : v) //极大减少代码量
cout << c << " ";
return 0;
}
五、初始化列表
初始化是一个非常重要的语言特性,最常见的就是对对象进行初始化。在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (plain old data,没有构造、析构和虚函数的类或结构体)类型都可以使用 {} 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用()
进行。这些不同方法都针对各自对象,不能通用。
vector<int> v = { 1,2,3,4,5 };//采用初始化列表进行初始化
class Foo {
private:
int value;
public:
Foo(int) {}
};
Foo foo(1); // 普通构造初始化
类中的构造函数中使用成员函数初始化:
#include <iostream>
#include<vector>
using namespace std;
class Base {
public:
Base(int a, int b) : x(a), y(b) {}; ///承运列表初始化
int x;
int y;
};
int main() {
Base base(1, 2);
cout << base.x << " " << base.y << endl;
return 0;
}
六、面向对象增强
委托构造
C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的:
#include <iostream>
#include<vector>
using namespace std;
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托 Base() 构造函数
value2 = 2;
}
};
int main() {
Base b(2);
std::cout << b.value1 << std::endl;
std::cout << b.value2 << std::endl;
}
继承构造
在传统 C++ 中,构造函数是得不到继承的。如果一个子类代码没有编写对应的构造函数,那么将不能够直接使用父类的构造函数, C++11 引入了继承构造函数的概念:
#include <iostream>
#include<vector>
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托 Base() 构造函数
value2 = 2;
}
};
class Subclass : public Base {
public:
int value3;
Subclass(int value3, int value2) : Base(value2) { // 继承父类构造
this->value3 = value3;
}
};
int main() {
Subclass s(3, 2);
std::cout << s.value1 << std::endl;
std::cout << s.value2 << std::endl;
std::cout << s.value3 << std::endl;
}
显式虚函数重载
有时我们会遇到意外重载虚函数的问题,例如:
struct Base {
virtual void foo();
};
struct SubClass: Base {
void foo();
};
SubClass::foo 可能并不是程序员尝试重载虚函数,只是恰好加入了一个具有相同名字的函数。另一个可能的情形是,当基类的虚函数被删除后,子类拥有旧的函数就不再重载该虚拟函数并摇身一变成为了一个普通的类方法,这将造成灾难性的后果。C++11 引入了 override
和 final
这两个关键字来防止上述情形的发生。
override
以前总以为overide是重写的意思,现在发现了自己的错误(too simple)。这里要特别注意重写和重载的区别。当重载虚函数时(这里指的是重载基类的虚函数,引入 override 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的虚函数,否则将无法通过编译:
struct Base {
virtual void foo(int);
};
struct SubClass : Base {
//在vs中显示的是重载
virtual void foo(int) override; // 合法
virtual void foo(float) override; // 非法, 父类没有此虚函数
};
final
final
则是为了防止类被继续继承以及终止虚函数继续重载引入的。
struct Base {
virtual void foo() final;
};
struct SubClass1 final : Base {
}; // 合法
struct SubClass2 : SubClass1 {
}; // 非法, SubClass 已 final
struct SubClass3 : Base {
void foo(); // 非法, foo 已 final
};