C++11 新特性整理 (1)

C++11对模板细节的改进:
1. C++11改善了编译器的解析规则,尽可能地将多个右尖括号(>)解析为模板参数结束符,例如在C++98中:

Foo<A<int> > xx;   // 两个右尖括号必须有空格,否则会被编译器解析成右移操作符
return 0;

在C++11中则完全取消了这种限制。

2. 模板的别名:
typedef可以重定义类型,例如:

typedef unsigned int uint;

但是无法重定义模板,在C++11中出现了重定义一个模板的语法:

using 的别名语法涵盖了typedef的全部功能:

template<typename Val>
using map_str_int = std::map<std::string, Val>;

map_str_int<int> map1;

而在C++98中,定义模板的别名只能通过一个外敷类来实现:

template<typename Val>
struct map_str_int    // 外敷类
{
    typedef std::map<std::string, Val> type;
};

map_str_int<int>::type map1;

列表初始化:

C++11中引入了列表初始化的概念,C++98中,只有数组和结构体可以进行列表初始化:

// 数组
int array[] = { 1,2,3 };

// 结构体
struct A
{
    int x;
    int y;
} a = {1, 2};

C++11中,列表初始化的范围增大了,现在它可以用于任何类型对象的初始化:

class Foo
{
public:
    Foo(int) {}
};

int main()
{
    Foo a1(23);        // 常规的初始化
    Foo a2 = { 123 };  // 列表初始化,虽然有=,但是也是初始化
    Foo a3 { 123 };    // 与a2等价

    int b1 = { 3 };
    int b2{ 3 };     // 列表初始化

    int c1[3]{ 1,2,3 };

    // POD类型的初始化  
    // plain old data , 可以直接使用memcpy复制的对象
    struct A
    {
        int x;
        struct B
        {
            float y;
            float z;
        } b;
    };

    A a = { 2, {2.3, 4.4} };
    A b { 2,{ 2.3, 4.4 } };

    // 指针初始化
    int* d1 = new int{ 12 };
    int* arr = new int[3] { 1,2,4 };


    return 0;
}

// 列表初始化可以直接使用在函数的返回值上面
struct Value
{
    int x;
    int y;
    Value(int x, int y) {}
};

Value func()
{
    return {12, 22};
}

列表初始化中的一些细节问题:
在这里需要区分一下:列表初始化被用于自定义类型的时候:
1. 自定义的类型中没有构造函数

聚合类型初始化,以拷贝的形式,用初始化列表中的值来初始化成员

// 结构体
struct A
{
    int x;
    int y;
} a = {1, 2};    // C++98 聚合类型初始化

2. 自定义的类型中有构造函数

利用构造函数进行初始化

struct B
{
    int x;
    int y;
    B(int x, int y) {}
} b = {1, 2};

在使用列表初始化时,如果类型满足以下的条件,C++会认为它是一个聚合体:
1. 类型是普通的数组

2. 类型是一个类,且满足:

a. 没有自定义的构造函数

b. 没有基类

c. 没有虚函数

d. 没有private或者protected的非静态数据成员(也就是说非静态的数据成员不能是private或者protected)

e. 不能有{}和=直接初始化的非静态数据成员

例如:

// 有private或者protected的数据成员
struct ST
{
    int x;
    int y;
private:
    int z;
};

ST s(1, 2, 3);   // invalid 

// 有private或者protected的静态数据成员
struct SU
{
    int x;
    int y;
protected:
    static int z;
};

SU s(1, 2);   // valid protected的时静态数据成员

// 有== {} 初始化的非静态数据成员
struct SN
{
    int x;
    int y = 3;
};

SN s(1, 4);

对于非聚合类型的对象,如果需要使用列表初始化,就需要自定义构造函数才可以.

聚合类型的定义是非递归的:当一个类的非静态成员是非聚合类型的时候,这个类也有可能是聚合类型。

例如:

struct ST
{
    int x;
    double y;
private:
    int z;
};

struct Foo
{
    ST st;    // 非聚合类型
    int x;
    double y;
};

// 初始化
Foo foo{ {}, 1, 2.4 };     // 第一个{}相当于调用ST的无参构造函数

所以,列表初始话的过程可以总结为:对于聚合类型,使用初始化列表相当于对其中的每个元素分别赋值;对于非聚合类型,则需要先定义一个合适的构造函数,是用初始化列表相当于调用对应的构造函数。

STL中的容器也支持初始化列表的操作,例如:

// 初始化列表
int arr[] {1,3,4};
// container
std::map<std::string, int> mp = { {"a", 2}, {"b", 4}, {"c", 6} };
std::set<int> ss {1,2,3,4};
std::vector<int> v {1,2,3,4};

STL中的容器是通过使用std::initializer_list实现对初始化列表的支持的,std::initializer_lis用来接收传进来的参数,然后将接收到的参数传入到容器中,例如,自定义一个容器:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <memory>
#include <map>
#include <vector>

class myVector
{
private:
    std::vector<int> content;

public:
    myVector(std::initializer_list<int> list)    //构造函数
    {
        // 使用初始化列表相当于调用构造函数, 构造函数将传进来数据进行处理
        for (auto iter = list.begin(); iter != list.end(); iter++)
        {
            // 将list中的数据放入容器
            content.push_back(*iter);
        }
    }
};


class myMap
{
private:
    std::map<std::string, int> content;

public:
    using pair_t = std::map<std::string, int>::value_type;   // using重定义  pair的类型
    myMap(std::initializer_list<pair_t> list)
    {
        for (auto iter = list.begin(); iter != list.end(); iter++)
        {
            content.insert(*iter);
        }
    }

};


int main()
{
    myVector v1 {1,2,3,4};
    myMap mp1{ {"a", 1},{"b", 2},{"c", 3} };
    return 0;
}

通过上述的方法,了解了std::initializer_list的工作原理,它用来接受初始化列表,此外,它还可以作为函数的参数进行传递,例如:(传递同类型参数)

template<typename T>
void func(std::initializer_list<T> l)
{
    for (auto iter = l.begin(); iter != l.end(); iter++)
    {
        std::cout << *iter << " ";
    }
    std::cout << std::endl;
}

int main()
{
    func({ "hello","ya","oaix" });
    return 0;
}

std::initializer_list的细节介绍:

1. 首先,它是一个轻量级的容器,内部定义了iterator等容器必须的概念

2.他可以按照任意的长度接受初始化列表,但是列表中的元素必须是同一种类型的

3.3个成员接口,begin, end, size

4. 只能被整体初始化或者赋值

5. 返回的迭代器是只读的,不能用返回的迭代器对元素进行修改

实际上,std::initializer_list的效率是非常高的,因为他保存的不是初始化列表中元素的拷贝,而实元素的引用,所以不能用于函数返回值

防止类型收窄:

除了上面介绍的功能之外,初始化列表还有一个作用,那就是防止类型收窄,在以下的几种情况中,会出现类型收窄:
1. 从浮点隐式的转化为整数

’2.从高精度隐式的转化为低精度

3. 从整型数隐式的转化为浮点数,并且超出了浮点数的范围

4. 从整型数隐式的转化为一个长度较短的整型数,且超出了范围

如果使用初始化列表,就可以防止上述的错误:

int a = 1.1;   // ok
int b {1.2};   // 报错

float fa = 1e40; // ok
float fb {1e40}; // 报错 超出范围


int x = 1024;

char y = x;   //ok
char z {x};   // 报错

只要遇到类型收窄的情况,初始化列表就会报错。

原文链接: https://www.cnblogs.com/ncepubye/p/12724014.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    C++11  新特性整理 (1)

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/396511

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月6日 上午11:22
下一篇 2023年4月6日 上午11:22

相关推荐