C++11新特性学习笔记

  1. 新增long long类型。在32位编译系统:int占四字节,与long相同;在64为编译系统:int占四字节,long占8字节。
  2. 列表初始化。用花括号来初始化变量得到了全面应用:
    vector<string> v1{"a", "an", "the"};
    vector<string> v2{10, "a"};
  3. nullpter变量。

  4. constexpr。C++11允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

  5. 类型别名声明。使用别名声明来定义类型的别名:

    using StrList = std::List<string>;
  6. auto类型说明符。C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。显然auto定义的变量必须有初始值。

    auto i = 0, *p = &i;
  7. decltype类型指示符。它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

    decltype(f()) sum = x;
  8.  

    类内初始化。C++11新标准规定,可以为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。
    如果是内置类型的变量未被显示初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他形式访问此类值将引发错误。

  9. 使用auto或decltype缩写类型。

  10. string类及其他大多标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size_type即是其中的一种。在具体使用的时候,我们往往不会记住这个名字,用auto类型说明符来推断变量类型是最好不过的了。
  11. 范围for语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
    for (declaration : expression)
        statement
    
    //eq:
    string str("some string");
    for (auto c : str)
        cout << c << endl;
  12. 定义vector对象的vector(向量的向量)。现在可以不需要那两个右尖括号之间的空格了。

  13. vector对象的列表初始化。

  14. 容器的cbegin和cend函数。为了便于专门得到const_iterator类型的返回值。

  15. 标准库begin和end函数。这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。这两个函数定义在iterator头文件中。

    int ia[] = {0, 1, 2, 3};
    int *beg = begin(ia);    //指向ia首元素的指针
    int *last = end(ia);     //指向arr尾元素的下一位置的指针
  16. 使用auto和decltype简化声明。

    int ia[3][4];
    //原来的做法
    int (*p)[4] = ia;    //p指向含有4个整数的数组
    p = &ia[2];          //p指向ia的尾元素
    
    //C++11的做法
    //p指向含有4个整数的数组
    for (auto p = ia; p != ia + 3; ++p)
    {
        //q指向4个整数数组的首元素,也就是说,q指向一个整数
        for (auto q = *p; q != *p + 4; q++)
            cout << *q << ' ';
        cout << endl;
    }
  17. 除法的舍入规则。在除法运算中,如果两个运算对象的符号相同则商为正(如果不为0的话),否则商为负。C++语言的早起版本允许结果为负值的商向上或向下取整。C++11新标准则规定商一律向0取整(即直接切除小数部分);

  18. 用大括号包围的值列表赋值。C++11新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象:

    int k = 0;
    k = {3.14};    //错误:窄化转换
    vector<int> vi;
    vi = {0, 1, 2, 3, 4};
  19. 将sizeof用于类成员。C++11新标准允许我们使用作用域运算符来获取类成员的大小。通常情况下只有通过类的对象才能访问到类的成员,但是sizeof运算符无须我们提供一个具体的对象,因为要想知道类成员的大小无须真的获取该成员。

  20. initializer_list 形参。如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参。initializer_list是一种标准库类型,用于表示某种特定类型的值的数组。initializer_list类型定义在同名的头文件中。具体使用方法:

    void error_msg(initializer_list<string> il)
    {
        for (auto beg = il.begin(); beg != il.end(); ++beg)
            cout << *beg << " ";
        cout << endl;
    }
  21. 初始化列表返回值。C++11新标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空,临时量执行值初始化;否则,返回的值由函数的返回类型决定。eg:

    vector<string> process()
    {
    	string expected;
    	string actual;
    	//..
    	if (expected.empty())
    	{
    		return{};
    	}
    	else if (expected == actual)
    	{
    		return{"functionX", "okay"};
    	}
    	else
    	{
    		return{ "functionX", expected, actual };
    	}
    }
  22. 使用尾置返回类型。(和Lambda表达式有点相似)在C++11新标准中还有一种可以简化函数指针声明的方法,就是使用尾置返回类型。任何函数的定义都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或者数组的引用。尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,我们在本应该出现返回类型的地方放置一个auto:

    //C++98的写法
    int (*func(int i))[10]
    
    //C++11的写法
    //func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
    auto func(int i) -> int(*)[10];
  23. 使用decltype简化返回类型定义。接上述,还有一种情况,如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。例如,下面的函数返回一个指针,该指针根据参数i的不同指向两个已知数组中的某一个:
    int odd[] = { 1, 3, 5, 7, 9 };
    int even[] = { 0, 2, 4, 6, 8 };
    //返回一个指针,该指针指向含有5个整数的数组
    decltype(odd) *arrPtr(int i)
    {
    	return (i % 2) ? &odd : &even; //返回一个指向数组的指针
    }
  24. constexpr函数。constexpr函数是指能用于常量表达式(是指值不会改变并且在编译过程就能得到计算结果的表达式,注意const int sz = get_size();//sz不是常量表达式)的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。

  25. 使用 = default生成默认的构造函数。和其他函数一样,如果= default在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认情况下不是内联的。(一般配合类内初始值使用,如果你的编译器不支持类内初始值,那么你应该使用构造函数初始值列表来初始化类的每个成员)

  26. 类对象成员的类内初始化。类内初始值必须使用=的初始化形式(初始化Screen的数据成员时所用的)或者花括号括起来的直接初始化形式。

    class Window_mgr
    {
    private:
    	//默认情况下,一个Window_mgr包含一个标准尺寸的空白Screen
    	std::vector<Screen> screens{Screen(24, 80, ' ')};
    };
    
  27. 委托构造函数。C++11新标准扩展了构造函数初始值的功能,使得我们可以定义所谓的委托构造函数(delegating constructor)。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。

  28. 使用string对象处理文件名。在新C++标准中,文件名既可以库类型string对象,也可以是C风格字符数组。旧版本的标准库只允许C风格字符数组。

    ifstream in(ifile);    //构造一个ifstream并打开给定文件
    ofstream out;          //输出文件流未关联到任何文件
  29. array和forward_list容器。array和forward_list是新C++标准增加的类型。与内置数组相比,array是一种更安全、更容易使用的数组类型。与内置数组类似,array对象的大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。对其他容器而言,size保证是一个快速的常量时间的操作。

  30. 容器的cbegin和cend函数。以c开头的版本是C++新标准引入的,用以支持auto与begin和end函数结合使用。过去,没有其他选择,只能显示声明希望使用哪种类型的迭代器。

  31. 容器的列表初始化。

    list<string> authors =  {"Milton", "Shakespeare", "Austen"};
  32. 容器的非成员函数swap。swap操作交换两个相同容器的内容。调用swap之后,两个容器中的元素将会交换,swap只是交换了两个容器的内部数据结构。元素不会被移动的事实意味着,除string外,指向容器的迭代器、引用和指针在swap操作之后都不会失效。它们仍指向swap操作之前所指向的那些元素。但是,在swap之后,这些元素已经属于不同的容器了。
    在新标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。而早期标准库版本只提供成员函数版本的swap。非成员版本的swap在泛型编程中是非常重要的。统一使用非成员版本的swap是一个好习惯。

  33. 容器insert成员的返回类型。在新标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。(在旧版本的标准库中,这些操作返回void。)如果范围为空,不插入任何元素,insert会将第一个参数返回。

    //运行时错误:迭代器表示要拷贝的范围,不能指向与目的位置相同的容器
    slist.insert(slist.begin(), slist.begin(), slist.end());
  34. 容器的emplace成员。emplace成员函数使用参数在容器管理的内存空间中直接构造元素。

    //在c的末尾构造一个Sales_data对象
    //使用三个参数的Sales_data构造函数
    c.emplace_back("978-0590353403", 25, 15.99);
  35. shrink_to_fit。在新标准库中,我们可以调用shrink_to_fit来要求deque、vector或string退回不需要的内存空间。此函数指出我们不再需要任何多余的内存空间。

  36. string的数值转换函数。

    int i = 42;
    string s = to_string()i;     //将整数i转换为字符表示形式
    double d = stod(s);          //将字符串s转换为浮点数
    
    //要转换为数值的string中第一个非空白符必须是数值中可能出现的字符:
    string s2 = "pi = 3.14";
    //转换s中以数字开始的第一个子串,结果d = 3.14
    d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
  37. lambda表达式。一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式:
    [capture list] (parameter list) -> return type {function body}

  38. lambda表达式中的尾置返回类型。即需要用->显示指明返回类型。

  39. 标准库bind函数。可以将bind函数看作一个新的名为bind的标准库函数,它定义在头文件functional中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
    调用bind的一般形式为:
    auto newCallable = bind(callable, arg_list);
    其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
    arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,依次类推。

    #include <iostream>
    #include <memory>
    #include <string>
    #include <functional>
    #include <vector>
    #include <algorithm>
    #include <map>
    using namespace std;
    
    bool check_size(const string &s, string::size_type sz)
    {
    	return s.size() >= sz;
    }
    
    void main()
    {
    	vector<string> words = {"hi", "hello", "cxx", "gary"};
    	//check6是一个可调用对象,接受一个string类型的参数
    	//并用此string和值6来调用check_size
    	auto check6 = bind(check_size, std::placeholders::_1, 6);
    	string s = "hello";
    	bool b1 = check6(s);
    
    	//使用bind,我们可以将原来基于lambda的find_if调用:
    	//找到以第一个满足if条件的字符串
    	int sz = 3;
    	auto wc = find_if(words.begin(), words.end(), [sz](const string &a)
    	{
    		return a.size() >= sz;
    	});
    	//替换为如下使用check_size的版本:
    	auto wc_new = find_if(words.begin(), words.end(),
    		bind(check_size, std::placeholders::_1, sz));
    }
  40. 关联容器的列表初始化。

    	map<string, string> authors = {
    		{"Joyce", "James"},
    		{"Austen", "Jane"},
    		{"Dickens", "Charles"}
    	};
  41. 列表初始化pair的返回类型。

    pair<string, string> author("James", "Joyce");
    //创建pair对象的函数
    pair<string, int> process(vector<string> &v)
    {
    	if (!v.empty())
    	{
    		return {v.back(), v.size()};
    	}
    	else
    	{
    		return pair<string, int>();
    	}
    }
  42. pair的列表初始化。

    	map<string, size_t> word_count;
    	string word;
    	//向word_count插入word的4种方法
    	word_count.insert({word, 1});
    	word_count.insert(make_pair(word, 1));
    	word_count.insert(pair<string, size_t>(word, 1));
    	word_count.insert(map<string, size_t>::value_type(word, 1));
  43. 无序容器。新标准定义了4个无序关联容器(unordered associative container)。这些容器不是使用比较运算符来组织元素,而是使用一个哈希函数(hash function)和关键字类型的==运算符。在关键字类型的元素没有明显的序关系的情况下,无序容器是非常有用的。在某些应用中,维护元素的序代价非常高昂,此时无序容器也很有用。

  44. 智能指针。为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。

        std::weak_ptr<std::string> wkPtr = strPtr;
        strPtr.reset();
        std::shared_ptr<std::string> strPtrtmp = wkPtr.lock();
        if (strPtrtmp)
        {
    	    strPtrtmp->clear();
        }
  45. 动态分配对象的列表初始化。

    	int *pi = new int(1024);	//pi指向的对象的值为1024
    	string *ps = new string(10, '9');	//*ps为“9999999999”
    	//vector有10个元素,值依次为0到9
    	vector<int> *pv = new vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  46. auto和动态分配。如果我们提供了一个括号包围的初始化器,就可以使用auto从此初始化器来推断我们想要分配的对象的类型。但是,由于编译器要用初始化器的类型来推断要分配的类型,只有当括号中仅有单一初始化器时才可以使用auto:

    	//p1指向一个与obj类型相同的对象,该对象用obj进行初始化
    	auto p1 = new auto(obj);
    
    	//错误:括号中只能有单个初始化器
    	auto p2 = new auto{a, b, c};
  47. function类模板。C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。和其他对象一样,可调用的对象也有类型。例如,每个lambda有它自己唯一的(未命名)类类型;函数及函数指针的类型则由其返回值类型和实参类型决定,等等。function的出现是为了解决下列问题:

    #include <string>
    #include <vector>
    #include <map>
    using namespace std;
    
    //普通函数
    int add(int i, int j) { return i + j; }
    
    //lambda,其产生一个未命名的函数对象类
    auto mod = [](int i, int j) {return i % j; };
    
    //函数对象类
    struct divide {
    	int operator()(int denominator, int divisor)
    	{
    		return denominator / divisor;
    	}
    };
    
    void test2()
    {
    	binops.insert({"+", add});	//正确
    	binops.insert({"%", mod});	//错误:mod不是一个函数指针;实际是可行的,书上说的也不全对。。
    	binops.insert({ "/", divide() });	//错误:无法将参数 1 从“initializer list”转换为“std::pair<const _Kty, _Ty> &&”
    
    	cout << binops["%"](10,2);	//输出0
    }

    我们可以使用一个名为function的新的标准库类型解决上述问题,function定义在functional头文件中。
    在此例中,参考其他模板,我们在一对尖括号内指定类型:

    function<int(int, int)>
    function<int(int, int)> f1 = add;
    
    cout << f1(4, 2) << endl;        //打印6

    使用这个funciton类型我们可以重新定义map:
     

        //其中的元素可以是函数指针、函数对象或者lambda
    	map<string, function<int(int, int)>> binops = {
    		{"+", add},
    		{"-", std::minus<int>()},
    		{"/", divide()},
    		{"*", [](int i, int j) {return i * j; }},
    		{"%", mod}
    	};
    
    	cout << endl << binops["*"](4,5);

    注意我们不能(直接)将重载函数的名字存入function类型的对象中:

  48. 右值引用:为了支持移动操作,新标准引入了一种新的引用类型——右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用。我们通过&&来获取右值引用。右值引用有一个重要的性质——只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。
    左值引用:我们不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。
    右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上:
    左值持久:右值短暂
    右值引用只能绑定到临时对象:1.所引用的对象将要被销毁;2.该对象没有其他用户

    虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显示地将一个左值转换为对象的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。

  49. 标准库mem_fn类模板。

  50.  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

parkseyoung

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值