ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

张三思评博客:C++11新特性快看-乱序优化版

2021-01-31 14:01:22  阅读:124  来源: 互联网

标签:11 std 函数 初始化 int C++ 类型 乱序


标题: 【C++ Primer】C++11特性总结
作者: lx青萍之末
原文链接: https://blog.csdn.net/daaikuaichuan/article/details/79240196

文章目录

* 1、long long 类型
* 2、列表初始化
* 3、nullptr 空指针
* 4、constexpr变量
* 5、constexpr函数
* 6、using类型别名
* 7、auto类型指示符
* 8、decltype类型指示符
* 9、范围for语句
* 10、尾置返回类型
* 11、强枚举类型
* 12、=default 生成默认构造函数
* 13、类对象成员的类内初始化
* 14、lambda表达式与bind函数
* 15、可调用对象与function
* 16、move语义与右值引用
* 17、智能指针share_ptr,unique_ptr
* 18、STL容器
* 19、其他内容

C++11包括大量的新特性:主要特征像lambda表达式和移动语义,实用的类型推导关键字auto,更简单的容器遍历方法,和大量使模板更容易使用的改进。这一系列教程将包含所以以上特性。
很明显,C++11为C++带来了大量的新特性。C++11将修复大量缺陷和降低代码拖沓,比如lambda表达式的支持将使代码更简洁。像移动语义这种特性会提高语言内核的基础效率,使你可以写出更快的代码。对模板系统的优化可以使你更容易写出泛型的代码。

3、nullptr 空指针

C++11中新加入的字面值表示不指向任何对象的空指针,以前我们常常用一个预定义的宏NULL来表示空指针,实际上NULL的值是0,新标准推荐使用nullptr而不是NULL。

6、using类型别名

类型别名其实早在C语言中就有了,一般情况下我们使用关键字typedef来声明一个类型的别名,在C++11中增加了另一种声明类型别名的方法就是使用using关键字,using关键字在C++11以前一般用来引用命名空间。

typedef int INT;  // 右侧符号代表左侧
using INT2 = int; // 左侧符号代表右侧
 
INT a = 20;
INT2 b = 30;

7、auto类型指示符

如果编译器在定义一个变量的时候可以推断出变量的类型,不用写变量的类型,你只需写auto即可:

auto str = "sissie";
assert(typeid(str) == typeid(const char *));

在迭代器中使用auto,简化代码:

std::vector<std::string> vs{{"sissie", "robin", "playjokes", "sky", "hatlonely"}};
for (auto it = vs.begin(); it != vs.end(); ++it) {
    std::cout << *it << ", ";
}

在模板中使用auto:

template <typename BuiltType, typename Builder>
void makeAndProcessObject(const Builder& builder)
{
    BuiltType val = builder.makeObject();
    // do stuff with val
}
MyObjBuilder builder;
makeAndProcessObject<MyObj>(builder);

// 使用auto只需要一个模板参数,让编译器自动推导
template <typename Builder>
void makeAndProcessObject(const Builder& builder)
{
    auto val = builder.makeObject();
    // do stuff with val
}
MyObjBuilder builder;
makeAndProcessObject(builder);

1、long long 类型

long long 类型实际上没有在C++ 98中存在,而之后被C99标准收录,其实现在市面上大多数编译器是支持 long long
的,但是这个类型正式成为C++的标准类型是在C++11中。标准要求long long至少是64位也就是8个字节。一个字面常量使用LL后缀表示long
long类型,使用ULL后缀表示unsigned long long 类型。

2、列表初始化

C++11中全面加入了列表初始化的功能,包括对vector,map,值类型,struct等等都可以使用列表初始化,还可以在函数中返回一个花括号括起来的列表,而在这之前我们只能对数组进行列表初始化。

【Note】:C++有哪几种情况只能用初始化列表,而不能用赋值?

(1)对于const和reference类型成员变量,它们只能够被初始化而不能做赋值操作,因此只能用初始化列表;
因为常量不能被赋值,只能被初始化,所以必须在初始化列表中完成;C++的引用也一定要初始化,所以必须在初始化列表中完成。

  1. 引用必须被初始化,指针不必;

  2. 引用初始化以后不能被改变,指针可以改变所指的对象;

  3. 不存在指向空值的引用,但是存在指向空值的指针。

    (2)子类初始化父类的私有成员。
    (3)需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化)。

    //数组列表初始化
    int xx[5]={1,2,3,4,5};
    int yy[]={6,7,8,9,0};

    //值类型进行初始化
    int a{10};
    int b={10};
    int c={10.123}; // 编译器报错,g++ 5.3.1当列表初始化用于值类型的时候,如果有精度损失,编译器会报错。

    //列表初始化还可以用结构体
    typedef struct Str{
    int x;
    int y;
    }Str;
    Str s = {10,20};

    //列表初始化类,必须是public成员,如果含有私有成员会失败
    class Cls{
    public:
    int x;
    int y;
    };
    Cls c = {10,20};

    //vector不仅可以使用列表初始化,还可以使用列表进行赋值,数组不能用列表赋值
    vectorv1={1,2,3,4,5,6,7,8,9}; // 初始化
    vectorv2;
    v2={3,4,5,6,7}; //赋值

    //map列表初始化
    map<string ,int> m = {
    {“x”,1},
    {“y”,2},
    {“z”,3}
    };

    //用函数返回初始化列表只展示关键代码,相关头文件自行添加
    //同理结构体,类,map的返回也可以使用初始化列表返回
    vector getVector()
    {
    return {1,2,3,4,5};
    }

    int main()
    {
    vector v = getVector();
    cout<<v[0]<<v[1]<<v.size()<<endl;
    return 0 ;
    }

9、范围for语句

std::vector<std::string> vs{{"sissie", "robin", "playjokes", "sky", "hatlonely"}};
for (const auto& name: vs) {
    std::cout << name << ", ";
}

12、=default 生成默认构造函数

在C++的类中,如果我们没有定义构造函数,编译器会为我们合成默认的无参构造函数,如果我们定义了构造函数,则编译器就不生成默认构造函数了,但是如果我们定义构造函数同时也希望编译器生成默认构造函数呢?
C++11中可以通过在构造函数的声明中直接 =default的方式要求编译器生成构造函数。

class ClassName{
    public:
        ClassName(int x);
        ClassName()=default; // 显示要求编译器生成构造函数
};

4、constexpr变量

我们在定义常量的时候一般使用const来定义,一个常量必须在定义的时候进行初始化,并且之后不可更改。一个常量必须使用一个常量表达式进行初始化,并且在编译期间就可以得到常量的值,但是如何确定一个表达式就是常量表达式呢,C++11提供了一个新的关键字constexpr,使用该关键字定义的常量,由编译器检查为其赋值的表达式是否是常量表达式,例如:

int a = 20 ;
constexpr int x =  a; 

编译器编译的时候就会报错说a并不是常量。显然constexpr关键字将常量表达式的检查转交给编译器处理,而不是程序员自己,所以使用constexpr定义常量要比const安全。

张三思评:
这里的意思是 x 被顺便声明为常量吗?

5、constexpr函数

普通的函数一般是不能用来为constexpr常量赋值的,但是C++11允许定义一种constexpr的函数,这种函数在编译期间就可以计算出结果,这样的函数是可以用来为constexpr赋值的。定义constexpr函数需要遵守一些约定,函数的返回类型以及所有形参的类型都应该是字面值,一般情况下函数体中必须有且只有一条return语句。

constexpr int size()
{
    return 42;
}
 
constexpr int si = size();

执行初始化的时候编译器将函数的调用替换成结果值,constexpr函数体中也可以出现除了return之外的其他语句,但是这些语句在运行时不应该执行任何操作,例如空语句,using声明等。constexpr函数允许其返回值并非是一个字面值,例如:

constexpr int size(int s)
{
    return s*4;
}
 
int a = 20;
const int b = 30;
constexpr int c = 40;
constexpr int si = size(a);  //error a是一个变量所以函数返回的是一个可变的值
constexpr int si1 = size(20); //ok 函数返回的实际上是一个常量
constexpr int si2 = size(b);  //ok
constexpr int si3 = size(c);  //ok

由上可知constexpr函数并不一定返回常量,如果应用于函数的参数是一个常量表达式则返回常量,否则返回变量,而该函数调用到底是一个常量表达式还是非常量表达式则由编译器来判断。这就是constexpr的好处。

张三思评:
这里的应用场景是什么? …我晕了.

13、类对象成员的类内初始化

class ClassName
{
        public:
                int x = 10; //C++11 之前是不允许的
};

14、lambda表达式与bind函数

C++11中得lambda表达式用来定义和创建匿名函数,lambda表达式语法形式如下:

[ capture ] ( params ) mutable exception attribute -> ret { body }      // 完整的 lambda 表达式形式
[ capture ] ( params ) -> ret { body }                                  // const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值
[ capture ] ( params ) { body }                                         // 省略了返回值类型的 lambda 表达式
[ capture ] { body }                                                    // 省略了参数列表,类似于无参函数 f()

lambda表达式是一个可以被调用的代码单元,相当于一个内联函数,有参数和返回值以及函数体。但是跟函数不同的是,lambda表达式可以定义在函数的内部,一个完整的lambda表达式具有如下形式:

[捕获列表](参数列表) mutable -> 返回类型 {函数体}



int main()
{
    auto add= [](int a, int b)->int{
        return a + b;
    };
    int ret = add(1,2);
    std::cout << "ret:" << ret << std::endl;
    return 0;
}

lambda可以省略参数列表(如果没有参数的话),可以省略返回类型,但是不能省略捕获部分与函数体部分,即使捕获列表为空,也要有一个空的[],lambda有两种捕获,一种是值捕获,一种是引用捕获。如果是值捕获那么lambda中获得的是捕获的变量的副本,如果是引用捕获则获得的是引用,可以在lambda内部修改引用的变量的值,如上x是值捕获,y是引用捕获,lambda中默认是值捕获,如果变量前面添加&则是引用捕获,另外lambda中还有两种形式的引用捕获,例如[=]表示值捕获所有可见的变量,而[&]则表示引用捕获所有可见变量。如果希望值捕获所有可见变量,但是又有个别变量采用引用捕获呢,[=,&x]表示值捕获所有可见变量,同时引用捕获x。而[&,x]则表示引用捕获所有可见变量,x采用值捕获的方式。

lambda的捕获表达式中的内容转换成函数不可行,C++11提供了bind函数来完成这样的操作。

#include <functional> //bind()
#include <iostream>
using namespace std;
using namespace std::placeholders; // _1,_2所在的命名空间
int f(int x,int y,int a,int b)
{
  return a+b+x+y;
}
 
void main()
{
  int x = 10;
  int y = 20;
 
  auto f_wrap = bind(f,x,y,_1,_2);
  cout<<f_wrap(33,44)<<endl; // _1,_2是占位符,表示调用f_wrap的时候_1是第一个参数,_2是第二个参数。最终会被替换成调用  f(10,20,33,44)
}

8、decltype类型指示符

decltype 返回操作数的类型,可以对基本上任何类型使用decltype,包括函数的返回值:

int ia[10];
decltype(ia) ib;    // int ib[10];

新的函数返回值声明语法,把返回类型放在函数声明的后面,用auto代替前面的返回类型:

// 这两种函数声明方式等效
int multiply(int x, int y);
auto multiply(int x, int y) -> int;

// 返回auto
template <typename Builder>
auto makeAndProcessObject(const Builder& builder) -> decltype(builder.makeObject())
{
    auto val = builder.makeObject();
    // do stuff with val
    return val;
}

张三思评:
为什么要有这种花里胡哨的东西?

对于引用类型decltype有一些特别的地方:
(1)decltype如果作用于一个引用类型,其得到的还是一个引用类型:

int a = 20 ;
int &b = a;
decltype(b) c ;  // Error c是引用类型必须赋值
decltype(b) d = a; // OK  d是引用类型,指向a

(2)如果一个表达式是一个解指针引用的操作,decltype得到的也是一个引用类型:

int a = 20 ;
int *p = &a;
decltype(*p) c = a;  // c的类型是int&
c = 50;
cout<<a<<endl;  // 输出50

(3)当decltype作用于一个变量的时候,变量加不加括号是有区别的,例如:

int a = 20;
decltype(a) b = 30; //ok b的类型是 int
decltype((a)) c = a ; // ok c的类型是int& 其关联变量 a

张三思评:
这么多复杂的东西引入进来, 难怪C++越来越臃肿了…请问这些东西的存在意义是什么, 可以用简答的其他语法形式来实现, 为什么要引入无关的东西? 真他妈垃圾语言.

15、可调用对象与function

C++语言中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的对象以及重载子函数调用运算符的类。
不同类型可能具有相同的调用形式,所以C++11中提供了名为function的标准库类型,定义在头文件中,该类型用来存储一个可调用的对象,统一了所有可以像函数一样调用的调用形式,例如:

#include <functional>
#include <iostream>
using namespace std;
 
int add(int x,int y)
{
    return x+y;
}
 
class Add
{
    public:
        int operator()(int x,int y)
        {
            return x+y;
        }
};
 
void main()
{
  //function模版的参数是函数调用形式,包括返回类型,以及参数列表个数和类型
    function<int(int,int)> f1 = add;  //函数指针
    function<int(int,int)> f2 = Add(); // 可调用类对象
    function<int(int,int)> f3 = [](int x,int y){return x+y;}; //lambda表达式
     
    cout<<f1(10,20)<<" "<<f2(30,40)<<" "<<f3(50,60)<<endl;
}

16、move语义与右值引用

左值和右值是针对表达式而言,表达式之后依然存在的对象是左值,表达式之后不再存在的临时对象为右值,左值可以对其取地址,右值不能。

int i = 0;
std::string hello = "hello";
std::string world = "world";
const int& ri = 1;

// 左值:i, ++i, hello, world
// 右值:i++, hello + world, ri

为了支持移动操作,C++11中使用了一种称为右值引用的类型。移动操作是什么呢,一般情况下我们将一个对象赋值给另一个对象的时候会调用对象到拷贝构造函数或者拷贝赋值运算符。而移动构造函数和移动赋值运算符则用来将数据从一个对象移动到另一个对象。在很多情况下对象拷贝之后需要被销毁,此时使用移动操作会大幅提高性能。右值引用被使用之后,其关联的对象会被销毁。右值引用使用两个&&表示,例如
int && 表示右值引用,而int &则是左值。通过C++11标准库提供的函数 std::move()可以得到某一对象的右值引用。

TestClass::TestClass(TestClass &&t) noexcept //移动构造函数不应该抛出任何异常
:x(t.x),y(t.y),z(t.z),p(t.p)
{
  t.p=nullptr; // 实现移动操作之后需要保证被移动的对象的析构是安全的,所以源对象的指针成员置为空,随后t的析构函数将会被自动调用,t被销毁。
}

std::vector<TestClass> v;
TestClass tc;
v.push_back(std::move(tc));

10、尾置返回类型

要想引入尾置类型,我们还得从复杂的类型声明说起。

int arr[10] = {0}; //定义一个含有10个int元素的数组。

int (*p_arr)[10] = &arr; //注意:int *p_arr[10] 表示一个数组,有10个元素,元素类型是int*

int (*func(char x))[10]; //要定义一个函数,这个函数接受一个char类型的参数,并返回一个指向10个int类型数组的指针呢

C++11的另外一个特性,尾置返回类型,任何函数都可以使用尾置返回类型, 这种形式对于返回类型比较复杂的函数最有效,比如上面的函数可以使用如下方式:

auto func(char x) -> int(*) [10]; 

这种形式将函数的返回类型写在函数声明的最后面,并且在函数形参列表后面加上 ->
符号,然后紧接着是函数需要返回的类型,由于函数的返回类型被放在了形参列表之后,所以在函数名前面使用一个 auto替代。

11、强枚举类型

在标准C++中,枚举类型不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可以进行比较。C++03
唯一提供的安全机制是一个整数或一个枚举型值不能隐式转换到另一个枚举别型。 此外,枚举所使用整数类型及其大小都由实现方法定义,皆无法明确指定。
最后,枚举的名称全数暴露于一般范围中,因此C++03两个不同的枚举,不可以有相同的枚举名。 (好比 enum Side{ Right, Left }; 和
enum Thing{ Wrong, Right }; 不能一起使用。)

C++11 引进了一种特别的 “枚举类”,可以避免上述的问题。使用 enum class 的语法来声明:

enum class Enumeration
{
  Val1,
  Val2,
  Val3 = 100,
  Val4 /* = 101 */,
};

此种枚举为类型安全的。枚举类型不能隐式地转换为整数;也无法与整数数值做比较。 (表示式 Enumeration::Val4 == 101
会触发编译期错误)。
枚举类型所使用类型必须显式指定。在上面的示例中,使用的是默认类型 int,但也可以指定其他类型:

enum class Enum2 : unsigned int {Val1, Val2};

17、智能指针share_ptr,unique_ptr

简单地说,智能指针只是用对象去管理一个资源指针,同时用一个计数器计算当前指针引用对象的个数,当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,计数器为1,此时在销毁指针管理对象的同时,也把指针管理对象所管理的指针进行delete操作。
这里写图片描述

unique_ptr:作用域结束之后自动释放资源,不可复制,可以移动
shared_ptr:通过引用计数共享资源,当引用计数为0时,自动释放资源
weak_ptr: 一个shared_ptr的弱引用,不修改引用计数,为了解决循环引用问题而引入。

#include <cassert>
#include <memory>

int main() {
    {
        std::unique_ptr<int> upi(new int(6));
    }

    {
        // 用make_shared来初始化shared_ptr
        std::shared_ptr<int> spi = std::make_shared<int>(6);
        // use_count获取引用计数
        assert(spi.use_count() == 1);
        {
            std::shared_ptr<int> spi_shared(spi);
            assert(spi.use_count() == 2);
        }
        assert(spi.use_count() == 1);
    }

    {
        std::shared_ptr<int> spi = std::make_shared<int>(6);
        assert(spi.use_count() == 1);

        // 通过shared_ptr来构造weak_ptr
        std::weak_ptr<int> wpi(spi);
        // weak_ptr不改变引用计数
        assert(spi.use_count() == 1);
        assert(wpi.use_count() == 1);

        // lock() 获取weak_ptr引用的shared_ptr
        assert(*wpi.lock() == 6);
        // expired() 返回引用的对象是否已经释放
        assert(!wpi.expired());
    }

    return 0;
}

18、STL容器

(1)std::array :

array是C++11新引入的数组类型,和std::vector不同的是array的长度是固定的,不能动态拓展。

template <class T, std::size_t N> struct array

std::array<int, 3> a1{{1, 2, 3}};
std::sort(a1.begin(), a1.end());

张三思评:
那么 array 的意义在哪? 是为了完全替代内置数组吗?可是语法并不简洁啊!!
(2)std::forward_list :

C++11引入的新类型,forward_list是单链表(std::list是双链表),只需要顺序遍历的场合,forward_list能更加节省内存,插入和删除的性能高于list。

std::forward_list<int> fli{{1, 2, 3, 4}};

(3)unordered :

std::set 
std::multiset 
std::map 
std::multimap

用平衡树实现的有序的容器,插入、删除和查找的时间复杂度都是O(nlogn)。

std::unordered_set 
std::unordered_multiset 
std::unordered_map 
std::unordered_multimap

C++11引入的新类型,用hash实现的无序的容器,插入、删除和查找的时间复杂度都是O(1),在不关注容器内元素顺序的场合,使用unordered的容器能获得更高的性能。

19、其他内容

(1)static_assert:做编译期间的断言,因此叫做静态断言,其语法:static_assert(常量表达式,提示字符串)。
(2)tuple:适用于存储不同类型和不同数量的元素。 (3)bitset:位图,适用于进制操作和海量数据排序。
(4)正则表达式:regex则是C++11中新增的正则表达式库。

参考

http://www.cnblogs.com/wangqiguo/p/5635441.html
http://www.cnblogs.com/feng-sc/p/5710724.html
https://segmentfault.com/a/1190000003004734#articleHeader6

标签:11,std,函数,初始化,int,C++,类型,乱序
来源: https://blog.csdn.net/silly1195056983/article/details/113470151

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有