(3条消息) C++11/14/17
目录
环境准备
被弃用的特性
常量字符串赋值需要使用const char*
与C的兼容性
语言可用性的强化
类型推导
区间迭代
列表初始化
模板增强
面对对象增强
语言运行期的强化
Lambda表达式
std::function
std::bind/std::placeholder
右值引用
新增容器
智能指针和引用计数
RAII与引用计数
std::shared_ptr
std::unique_ptr
std::shared_ptr仍然无法解决的问题
环境准备
在IDE工具中选择编译器,加入编译命令 -std=c++11
被弃用的特性
(弃用暗示避免使用,但仍是标准库的一部分)
常量字符串赋值需要使用const char*
char* str = "Hello world!";
这里的char*和const char*都是指向一个常量字符串,如果有另外一个char* str2指向的常量字符串和str一模一样,那么其实str和str2也的地址是完全相同的,这有点类似java的常量池。
auto_ptr
被弃用,应使用unique_ptr
。
#include<iostream>
#include<memory>
using namespace std;
class A
{
public:
A()
{
cout << "A()";
}
~A()
{
cout << "~A()";
}
};
int main()
{
auto_ptr<A> global;
{
cout << "Test1\n";
auto_ptr<A> a(new A());
}
{
cout << "\nTest2\n";
A* b = new A();
auto_ptr<A> a(b);
global.reset(b);
auto_ptr<A> c(b);
}
{
cout << "\nTest3\n";
A*b = new A[5];
auto_ptr<A> a(b);
}
{
cout << "\nTest3\n";
unique_ptr<A> a(new A[5]);
}
cout << "\nEnd\n";
system("pause");
}
auto_ptr和unique_ptr的构造函数都声明为explicit,为了避免不知情的情况下出现的错误,不能存在隐式转换。
std::auto_ptr<A> a = new A(); //编译错误
std::auto_ptr<A> a(new A()); //编译成功
关于explicit 更详细的解释 https://www.cnblogs.com/cutepig/archive/2009/01/14/1375917.html
auto_ptr和unique_ptr一样不能处理数组,因为它们析构的时候,都只是delete ptr,而不是delete []ptr。
register
关键字被弃用。- C 语言风格的类型转换被弃用,应该使用
static_cast
、reinterpret_cast
、const_cast
来进行类型转换
与C的兼容性
C++不是C的一个超集,在编写C++时,也应该尽可能地避免诸如void *之类的程序风格,而在不得不使用C时,应该使用extern "C"这种特性,将C语言的代码与C++代码进行分离编译,再统一链接这种做法。
https://www.cnblogs.com/skynet/archive/2010/07/10/1774964.html
语言可用性的强化
nullptr用来区分空指针和0,和传统C++会把NULL、0视为同一种东西,这会在C++中重载特性中发生混乱。
constexpr用来修饰一段代码,说明该函数在编译时便一定是个常数,便于编译器优化。
// constexpr 声明factorial可以参与编译期的运算
constexpr int factorial(int n)
{
return n <= 1? 1 : (n * factorial(n - 1));
}
int main()
{
std::cout << "4! = " ;
constN<factorial(4)> out1; // computed at compile time
volatile int k = 8; // disallow optimization using volatile
std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
}
类型推导
C++11引入了auto和decltype
auto x = 1;
auto y = 2;
decltype(x+y) z;
//尾返回类型
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
//C++直接支持
template<typename T, typename U>
auto add(T x, U y) {
return x+y;
}
区间迭代
int array[] = {1,2,3,4,5};
for(auto &x : array) {
std::cout << x << std::endl;
}
//std::vector
//原来
int array[] = {1,2,3,4,5};
for(auto &x : array) {
std::cout << x << std::endl;
}
//现在
// & 启用了引用, 如果没有则对 arr 中的元素只能读取不能修改
for(auto &i : arr) {
std::cout << i << std::endl;
}
列表初始化
可以用一种统一的方式对数组,结构体,类和所有STL容器进行初始化。
#include<iostream>
#include<map>
#include<list>
#include<vector>
using namespace std;
struct Foo
{
int x;
int y;
Foo(int, int){ cout << "Foo construction"; }
};
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
std::map < int, int > map_t { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
std::list<std::string> list_str{ "hello", "world", "china" };
std::vector<double> vec_d { 0.0,0.1,0.2,0.3,0.4,0.5};
Foo foo{1,2};
for(auto& x : vec_d)
{
std::cout<<x<<" ";
}
std::cout<<"\n";
}
也可以列表初始化构造函数,列表还可以作为普通函数的形参。
#include <initializer_list>
class Magic {
public:
Magic(std::initializer_list<int> list) {}
};
Magic magic = {1,2,3,4,5};
std::vector<int> v = {1, 2, 3, 4}
void func(std::initializer_list<int> list) {
return;
}
func({1,2,3});
统一的语法来初始化任意对象
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};
模板增强
尖括号 ">"
在传统 C++ 的编译器中,
>>
一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码:
std::vector<std::vector<int>> wow;
这在传统C++编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。
- 类型别名模板
在了解类型别名模板之前,需要理解『模板』和『类型』之间的不同。仔细体会这句话:模板是用来产生类型的。在传统 C++中,typedef
可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如:
template< typename T, typename U, int value>
class SuckType {
public:
T a;
U b;
SuckType():a(value),b(value){}
};
template< typename U>
typedef SuckType<std::vector<int>, U, 1> NewType; // 不合法
C++11 使用
using
引入了下面这种形式的写法,并且同时支持对传统 typedef
相同的功效:
通常我们使用
typedef
定义别名的语法是:typedef 原名称 新名称;
,但是对函数指针等别名的定义语法却不相同,这通常给直接阅读造成了一定程度的困难。
typedef int (*process)(void *); // 定义了一个返回类型为 int,参数为 void* 的函数指针类型,名字叫做 process
using process = int(*)(void *); // 同上, 更加直观
template <typename T>
using NewType = SuckType<int, T, 1>; // 合法
变长参数模板
template<typename... TS> class Magic;
获取参数个数
template<typename ...Args>
void magic(Args ...args)
{
std::cout << sizeof...(args) << std::endl;
}
对参数进行解包
template<typename T>
void printf(T value)
{
std::cout << value << std::endl;
}
template<typename T, typename ...Args>
void printf(T value, Args... args)
{
std::cout << value << std::endl;
printf(args...);
}
列表初始化展开
template<typename T, typename... Argsa>
auto print(T value, Args... args)
{
std::cout<<value<<std::endl;
return std::initializer_list<T>
{([&]{std::cout<<args<<std::endl;}(), value)...};
}
变长参数模板示例,求最大值
#include<iostream>
template<typename T>
T max(T a, T b)
{
if(a>=b)
{
return a;
}
return b;
}
template<typename T,typename... Args>
T max(T a, T b, Args... args)
{
if(a >= b)
{
return max(a, args...);
}
return max(b, args...);
}
int main()
{
std::cout<<max(1,5,3,2,11);
}
模板增强可见https://blog.csdn.net/tennysonsky/article/details/77389891
面对对象增强
委托构造,同一个类中一个构造函数调用另一个构造函数
继承构造,避免参数传递
显式虚函数重载,override,final
显式禁用默认函数
语言运行期的强化
Lambda表达式
auto basicLambda = [] {std::cout << "Hello, world!" <<endl;};
basicLambda();
捕获分为引用捕获[&]和值捕获[=],可以让编译器自行推导应用列表。
参数写在[](参数),在C++14中类型可以使用auto。
auto process = [&](int a, int b){std::cout<<(a+b)*c<"\n";};
process(a,b);
Lambda表达式又称为匿名函数,有时候也无需指定其名字而直接调用。
[&](){std::cout<<"Hello world!\n"}();
std::function
C++11 std::function是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储,复制和调用操作,它也是一种类型安全的函数的容器。
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function 包装了一个返回值为 int, 参数为 int 的函数
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value){
return 1 + value + important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
}
std::bind/std::placeholder
#include<functional>
int foo(int a, int b, int c)
{
}
int main()
{
//std::placeholders::_1表示对第一个参数占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
bindFoo(1);
}
右值引用
右值引用(及其支持的Move语意和完美转发)是C++11加入的重大语言特性之一。从实践角度讲,它能够完美解决C++
中长久以来为人诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。
为了测试,在编译时设置编译选项
-fno-elide-constructors
class A
{
public:
A() { std::cout << "Constructor" << std::endl; }
A(const A&) { std::cout << "Copy Constructor" << std::endl; }
~A() {}
};
static A getA()
{
A a;
return a;
}
int main()
{
std::cout<<i<<" "<<j<<"\n";
A a = getA();
return 0;
}
如图,在这种情况下,调用了一次构造函数,return时调用了一次拷贝构造函数生成了一个临时的a,将它return回的时候返回给了main函数中的a,随机这个临时的a也就消失了。这个阶段浪费了不少的性能。
A &&a = getA();
但如果将它改成了右值引用,就只会调用一次拷贝构造函数用于生成临时的变量,临时的变量生命周期和右值引用的生命周期相等。
还有有关右值引用与深拷贝和转发详细可以参见https://www.cnblogs.com/qicosmos/p/4283455.html
新增容器
智能指针和引用计数
RAII与引用计数
引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
引用计数不是垃圾回收,引用技术能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。
std::shared_ptr
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer;
auto pointer3 = pointer;
int *p = pointer.get(); //这样不会增加引用计数
pointer2.reset();
pointer3.reset();
std::cout<<pointer.use_count()<<std::endl;
std::unique_ptr
独占指针,类似于之前的auto_ptr,
std::shared_ptr仍然无法解决的问题
#include <iostream>
#include <memory>
class A;
class B;
class A {
public:
std::shared_ptr<B> pointer;
~A() {
std::cout << "A 被销毁" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> pointer;
~B() {
std::cout << "B 被销毁" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->pointer = b;
b->pointer = a;
return 0;
}