(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
  1. #include<iostream>
  2. #include<memory>

  3. using namespace std;

  4. class A
  5. {
  6. public:
  7. A()
  8. {
  9. cout << "A()";
  10. }
  11. ~A()
  12. {
  13. cout << "~A()";
  14. }
  15. };

  16. int main()
  17. {
  18. auto_ptr<A> global;
  19. {
  20. cout << "Test1\n";
  21. auto_ptr<A> a(new A());
  22. }
  23. {
  24. cout << "\nTest2\n";
  25. A* b = new A();
  26. auto_ptr<A> a(b);
  27. global.reset(b);
  28. auto_ptr<A> c(b);
  29. }
  30. {
  31. cout << "\nTest3\n";
  32. A*b = new A[5];
  33. auto_ptr<A> a(b);
  34. }
  35. {
  36. cout << "\nTest3\n";
  37. unique_ptr<A> a(new A[5]);
  38. }
  39. cout << "\nEnd\n";
  40. system("pause");
  41. }

auto_ptr和unique_ptr的构造函数都声明为explicit,为了避免不知情的情况下出现的错误,不能存在隐式转换。

  1. std::auto_ptr<A> a = new A(); //编译错误
  2. 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_castreinterpret_castconst_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用来修饰一段代码,说明该函数在编译时便一定是个常数,便于编译器优化。

  1. // constexpr 声明factorial可以参与编译期的运算
  2. constexpr int factorial(int n)
  3. {
  4. return n <= 1? 1 : (n * factorial(n - 1));
  5. }
  6. int main()
  7. {
  8. std::cout << "4! = " ;
  9. constN<factorial(4)> out1; // computed at compile time

  10. volatile int k = 8; // disallow optimization using volatile
  11. std::cout << k << "! = " << factorial(k) << '\n'; // computed at run time
  12. }

类型推导

C++11引入了auto和decltype

  1. auto x = 1;
  2. auto y = 2;
  3. decltype(x+y) z;

  4. //尾返回类型
  5. template<typename T, typename U>
  6. auto add(T x, U y) -> decltype(x+y) {
  7. return x+y;
  8. }


  9. //C++直接支持
  10. template<typename T, typename U>
  11. auto add(T x, U y) {
  12. return x+y;
  13. }

区间迭代

  1. int array[] = {1,2,3,4,5};
  2. for(auto &x : array) {
  3. std::cout << x << std::endl;
  4. }


  5. //std::vector

  6. //原来
  7. int array[] = {1,2,3,4,5};
  8. for(auto &x : array) {
  9. std::cout << x << std::endl;
  10. }

  11. //现在
  12. // & 启用了引用, 如果没有则对 arr 中的元素只能读取不能修改
  13. for(auto &i : arr) {
  14. std::cout << i << std::endl;
  15. }

 列表初始化

可以用一种统一的方式对数组,结构体,类和所有STL容器进行初始化。

  1. #include<iostream>
  2. #include<map>
  3. #include<list>
  4. #include<vector>

  5. using namespace std;

  6. struct Foo
  7. {
  8. int x;
  9. int y;
  10. Foo(int, int){ cout << "Foo construction"; }
  11. };
  12. int main()
  13. {
  14. int arr[] = { 1, 2, 3, 4, 5 };
  15. std::map < int, int > map_t { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
  16. std::list<std::string> list_str{ "hello", "world", "china" };
  17. std::vector<double> vec_d { 0.0,0.1,0.2,0.3,0.4,0.5};
  18. Foo foo{1,2};
  19. for(auto& x : vec_d)
  20. {
  21. std::cout<<x<<" ";
  22. }
  23. std::cout<<"\n";
  24. }

也可以列表初始化构造函数,列表还可以作为普通函数的形参。

  1. #include <initializer_list>

  2. class Magic {
  3. public:
  4. Magic(std::initializer_list<int> list) {}
  5. };

  6. Magic magic = {1,2,3,4,5};
  7. std::vector<int> v = {1, 2, 3, 4}

  8. void func(std::initializer_list<int> list) {
  9. return;
  10. }

  11. func({1,2,3});

统一的语法来初始化任意对象

  1. struct A {
  2. int a;
  3. float b;
  4. };
  5. struct B {

  6. B(int _a, float _b): a(_a), b(_b) {}
  7. private:
  8. int a;
  9. float b;
  10. };

  11. A a {1, 1.1}; // 统一的初始化语法
  12. B b {2, 2.2};

 模板增强

  • 尖括号 ">"

在传统 C++ 的编译器中,>>一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码:

std::vector<std::vector<int>> wow;

这在传统C++编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。

  • 类型别名模板

在了解类型别名模板之前,需要理解『模板』和『类型』之间的不同。仔细体会这句话:模板是用来产生类型的。在传统 C++中,typedef可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如:

  1. template< typename T, typename U, int value>
  2. class SuckType {
  3. public:
  4. T a;
  5. U b;
  6. SuckType():a(value),b(value){}
  7. };
  8. template< typename U>
  9. typedef SuckType<std::vector<int>, U, 1> NewType; // 不合法

C++11 使用 using 引入了下面这种形式的写法,并且同时支持对传统 typedef 相同的功效:

通常我们使用 typedef 定义别名的语法是:typedef 原名称 新名称;,但是对函数指针等别名的定义语法却不相同,这通常给直接阅读造成了一定程度的困难。

  1. typedef int (*process)(void *); // 定义了一个返回类型为 int,参数为 void* 的函数指针类型,名字叫做 process
  2. using process = int(*)(void *); // 同上, 更加直观

  3. template <typename T>
  4. using NewType = SuckType<int, T, 1>; // 合法
  • 变长参数模板
template<typename... TS> class Magic;

获取参数个数

  1. template<typename ...Args>
  2. void magic(Args ...args)
  3. {
  4. std::cout << sizeof...(args) << std::endl;
  5. }

对参数进行解包

  1. template<typename T>
  2. void printf(T value)
  3. {
  4. std::cout << value << std::endl;
  5. }

  6. template<typename T, typename ...Args>
  7. void printf(T value, Args... args)
  8. {
  9. std::cout << value << std::endl;
  10. printf(args...);
  11. }

 列表初始化展开

  1. template<typename T, typename... Argsa>
  2. auto print(T value, Args... args)
  3. {
  4. std::cout<<value<<std::endl;
  5. return std::initializer_list<T>
  6. {([&]{std::cout<<args<<std::endl;}(), value)...};
  7. }

变长参数模板示例,求最大值

  1. #include<iostream>

  2. template<typename T>
  3. T max(T a, T b)
  4. {
  5. if(a>=b)
  6. {
  7. return a;
  8. }
  9. return b;
  10. }

  11. template<typename T,typename... Args>
  12. T max(T a, T b, Args... args)
  13. {
  14. if(a >= b)
  15. {
  16. return max(a, args...);
  17. }
  18. return max(b, args...);
  19. }

  20. int main()
  21. {
  22. std::cout<<max(1,5,3,2,11);
  23. }

模板增强可见https://blog.csdn.net/tennysonsky/article/details/77389891 


面对对象增强

  • 委托构造,同一个类中一个构造函数调用另一个构造函数
  • 继承构造,避免参数传递
  • 显式虚函数重载,override,final
  • 显式禁用默认函数

语言运行期的强化

Lambda表达式

  1. auto basicLambda = [] {std::cout << "Hello, world!" <<endl;};
  2. basicLambda();

捕获分为引用捕获[&]和值捕获[=],可以让编译器自行推导应用列表。

参数写在[](参数),在C++14中类型可以使用auto。

  1. auto process = [&](int a, int b){std::cout<<(a+b)*c<"\n";};
  2. process(a,b);

Lambda表达式又称为匿名函数,有时候也无需指定其名字而直接调用。

[&](){std::cout<<"Hello world!\n"}();

std::function

C++11 std::function是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储,复制和调用操作,它也是一种类型安全的函数的容器。

  1. #include <functional>
  2. #include <iostream>

  3. int foo(int para) {
  4. return para;
  5. }

  6. int main() {
  7. // std::function 包装了一个返回值为 int, 参数为 int 的函数
  8. std::function<int(int)> func = foo;

  9. int important = 10;
  10. std::function<int(int)> func2 = [&](int value){
  11. return 1 + value + important;
  12. };
  13. std::cout << func(10) << std::endl;
  14. std::cout << func2(10) << std::endl;
  15. }

std::bind/std::placeholder

  1. #include<functional>

  2. int foo(int a, int b, int c)
  3. {
  4. }

  5. int main()
  6. {
  7. //std::placeholders::_1表示对第一个参数占位
  8. auto bindFoo = std::bind(foo, std::placeholders::_1, 1, 2);
  9. bindFoo(1);
  10. }

右值引用

右值引用(及其支持的Move语意和完美转发)是C++11加入的重大语言特性之一。从实践角度讲,它能够完美解决C++

中长久以来为人诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。

为了测试,在编译时设置编译选项

-fno-elide-constructors
  1. class A
  2. {
  3. public:
  4. A() { std::cout << "Constructor" << std::endl; }
  5. A(const A&) { std::cout << "Copy Constructor" << std::endl; }
  6. ~A() {}
  7. };

  8. static A getA()
  9. {
  10. A a;
  11. return a;
  12. }

  13. int main()
  14. {
  15. std::cout<<i<<" "<<j<<"\n";
  16. A a = getA();

  17. return 0;
  18. }

如图,在这种情况下,调用了一次构造函数,return时调用了一次拷贝构造函数生成了一个临时的a,将它return回的时候返回给了main函数中的a,随机这个临时的a也就消失了。这个阶段浪费了不少的性能。

A &&a = getA();

但如果将它改成了右值引用,就只会调用一次拷贝构造函数用于生成临时的变量,临时的变量生命周期和右值引用的生命周期相等。

还有有关右值引用与深拷贝和转发详细可以参见https://www.cnblogs.com/qicosmos/p/4283455.html


新增容器


智能指针和引用计数

RAII与引用计数

引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。

引用计数不是垃圾回收,引用技术能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。

std::shared_ptr

  1. auto pointer = std::make_shared<int>(10);
  2. auto pointer2 = pointer;
  3. auto pointer3 = pointer;
  4. int *p = pointer.get(); //这样不会增加引用计数

  5. pointer2.reset();
  6. pointer3.reset();

  7. std::cout<<pointer.use_count()<<std::endl;

std::unique_ptr

独占指针,类似于之前的auto_ptr,

std::shared_ptr仍然无法解决的问题

  1. #include <iostream>
  2. #include <memory>

  3. class A;
  4. class B;

  5. class A {
  6. public:
  7. std::shared_ptr<B> pointer;
  8. ~A() {
  9. std::cout << "A 被销毁" << std::endl;
  10. }
  11. };
  12. class B {
  13. public:
  14. std::shared_ptr<A> pointer;
  15. ~B() {
  16. std::cout << "B 被销毁" << std::endl;
  17. }
  18. };
  19. int main() {
  20. std::shared_ptr<A> a = std::make_shared<A>();
  21. std::shared_ptr<B> b = std::make_shared<B>();
  22. a->pointer = b;
  23. b->pointer = a;

  24. return 0;
  25. }
(0)

相关推荐