• C++11:自动类型推导auto/decltype


    在 C++11 中增加了很多新的特性,比如可以使用 auto 自动推导变量的类型,还能够结合 decltype 来表示函数的返回值。使用新的特性可以让我们写出更加简洁,更加现代的代码。

    1.auto用法

    在 C++11 之前 auto 和 static 是对应的,表示变量是自动存储的,但是非 static 的局部变量默认都是自动存储的,因此这个关键字变得非常鸡肋,在 C++11 中他们赋予了新的含义,使用这个关键字能够像别的语言一样自动推导出变量的实际类型。

    使用语法如下:

    auto 变量名 = 变量值

    根据上述语法,来列举一些简单的例子:

    1. auto x = 3.14; // x 是浮点型 double
    2. auto y = 520; // y 是整形 int
    3. auto z = 'a'; // z 是字符型 char
    4. auto nb; // error,变量必须要初始化
    5. auto double nbl; // 语法错误, 不能修改数据类型

    不仅如此,auto 还可以和指针、引用结合起来使用也可以带上 const、volatile 限定符,在不同的场景下有对应的推导规则,规则内容如下:

    • 当变量不是指针或者引用类型时,推导的结果中不会保留 const、volatile 关键字
    • 当变量是指针或者引用类型时,推导的结果中会保留 const、volatile 关键字

    先来看一组变量带指针和引用并使用 auto 进行类型推导的例子:

    1. int temp = 110;
    2. auto *a = &temp;
    3. auto b = &temp;
    4. auto &c = temp;
    5. auto d = temp;
    • 变量 a 的数据类型为 int*,因此 auto 关键字被推导为 int类型
    • 变量 b 的数据类型为 int*,因此 auto 关键字被推导为 int* 类型
    • 变量 c 的数据类型为 int&,因此 auto 关键字被推导为 int类型
    • 变量 d 的数据类型为 int,因此 auto 关键字被推导为 int 类型

    在来看一组带 const 限定的变量,使用 auto 进行类型推导的例子:

    1. int tmp = 250;
    2. const auto a1 = tmp;
    3. auto a2 = a1;
    4. const auto &a3 = tmp;
    5. auto &a4 = a3;
    • 变量 a1 的数据类型为 const int,因此 auto 关键字被推导为 int 类型
    • 变量 a2 的数据类型为 int,但是 a2 没有声明为指针或引用因此 const 属性被去掉,auto 被推导为 int
    • 变量 a3 的数据类型为 const int&,a3 被声明为引用因此 const 属性被保留,auto 关键字被推导为 int 类型
    • 变量 a4 的数据类型为 const int&,a4 被声明为引用因此 const 属性被保留,auto 关键字被推导为 const int 类型

    1.2auto的限制

    auto 关键字并不是万能的,在以下这些场景中是不能完成类型推导的:

    1.不能作为函数参数使用。因为只有在函数调用的时候才会给函数参数传递实参,auto 要求必须要给修饰的变量赋值,因此二者矛盾。

    1. int func(auto a, auto b) // error
    2. {
    3. cout << "a: " << a <<", b: " << b << endl;
    4. }

    2.不能用于类的非静态成员变量的初始化

    1. class Test
    2. {
    3. auto v1 = 0; // error
    4. static auto v2 = 0; // error,类的静态非常量成员不允许在类内部直接初始化
    5. static const auto v3 = 10; // ok
    6. }

    3.不能使用 auto 关键字定义数组

    1. int func()
    2. {
    3. int array[] = {1,2,3,4,5}; // 定义数组
    4. auto t1 = array; // ok, t1被推导为 int* 类型
    5. auto t2[] = array; // error, auto无法定义数组
    6. auto t3[] = {1,2,3,4,5};; // error, auto无法定义数组
    7. }

    4.无法使用auto推导出模板参数

    1. template <typename T>
    2. struct Test{}
    3. int func()
    4. {
    5. Test<double> t;
    6. Test<auto> t1 = t; // error, 无法推导出模板类型
    7. return 0;
    8. }

    1.3auto的应用

    了解了 auto 的限制之后,我们就可以避开这些场景快乐的编程了,下面列举几个比较常用的场景:

    1.前面我更新了STL基础学习系列戳这里->>>Oorik领域博主

     对于容器的遍历,我们在迭代器上动动手脚,使用auto简化书写

    在 C++11 之前,定义了一个 stl 容器之后,遍历的时候常常会写出这样的代码:

    1. #include
    2. int main()
    3. {
    4. map<int, string> person;
    5. map<int, string>::iterator it = person.begin();
    6. for (; it != person.end(); ++it)
    7. {
    8. cout<" "<
    9. }
    10. cout<
    11. return 0;
    12. }

    可以看到在定义迭代器变量 it 的时候代码是很长的,写起来就很麻烦,使用了 auto 之后,就变得清爽了不少:

    1. #include
    2. int main()
    3. {
    4. map<int, string> person;
    5. // 代码简化
    6. for (auto it = person.begin(); it != person.end(); ++it)
    7. {
    8. cout<" "<
    9. }
    10. cout<
    11. return 0;
    12. }

    2.用于泛型编程,在使用模板的时候,很多情况下我们不知道变量应该定义为什么类型,比如下面的代码:

    1. #include
    2. #include
    3. using namespace std;
    4. class T1
    5. {
    6. public:
    7. static int get()
    8. {
    9. return 10;
    10. }
    11. };
    12. class T2
    13. {
    14. public:
    15. static string get()
    16. {
    17. return "hello, world";
    18. }
    19. };
    20. template <class A>
    21. void func(void)
    22. {
    23. auto val = A::get();
    24. cout << "val: " << val << endl;
    25. }
    26. int main()
    27. {
    28. func();
    29. func();
    30. return 0;
    31. }

    在这个例子中定义了泛型函数 func,在函数中调用了类 A 的静态方法 get () ,这个函数的返回值是不能确定的,如果不使用 auto,就需要再定义一个模板参数,并且在外部调用时手动指定 get 的返回值类型,具体代码如下:

    1. #include
    2. #include
    3. using namespace std;
    4. class T1
    5. {
    6. public:
    7. static int get()
    8. {
    9. return 0;
    10. }
    11. };
    12. class T2
    13. {
    14. public:
    15. static string get()
    16. {
    17. return "hello, world";
    18. }
    19. };
    20. template <class A, typename B> // 添加了模板参数 B
    21. void func(void)
    22. {
    23. B val = A::get();
    24. cout << "val: " << val << endl;
    25. }
    26. int main()
    27. {
    28. funcint>(); // 手动指定返回值类型 -> int
    29. func(); // 手动指定返回值类型 -> string
    30. return 0;
    31. }

    2. decltype

    在某些情况下,不需要或者不能定义变量,但是希望得到某种类型,这时候就可以使用 C++11 提供的 decltype 关键字了,它的作用是在编译器编译的时候推导出一个表达式的类型,语法格式如下:

    decltype (表达式)

    decltype 是 “declare type” 的缩写,意思是 “声明类型”。decltype 的推导是在编译期完成的,它只是用于表达式类型的推导,并不会计算表达式的值。来看一组简单的例子:

    1. int a = 10; //根据a相关表达式推导b类型,不计算a相关的表达式值
    2. decltype(a) b = 99; // b -> int
    3. decltype(a+3.14) c = 52.13; // c -> double
    4. decltype(a+b*c) d = 520.1314; // d -> double

    可以看到 decltype 推导的表达式可简单可复杂,在这一点上 auto 是做不到的,auto 只能推导已初始化的变量类型。

    2.1decltype推导规则

    通过上面的例子我们初步感受了一下 decltype 的用法,但不要认为 decltype 就这么简单,在它简单的背后隐藏着很多的细节,下面分三个场景依次讨论一下:

    1.表达式为普通变量或者普通表达式或者类表达式,在这种情况下,使用 decltype 推导出的类型和表达式的类型是一致的。

    1. #include
    2. #include
    3. using namespace std;
    4. class Test
    5. {
    6. public:
    7. string text;
    8. static const int value = 110;
    9. };
    10. int main()
    11. {
    12. int x = 99;
    13. const int &y = x;
    14. decltype(x) a = x;
    15. decltype(y) b = x;
    16. decltype(Test::value) c = 0;
    17. Test t;
    18. decltype(t.text) d = "hello, world";
    19. return 0;
    20. }
    • 变量 a 被推导为 int 类型
    • 变量 b 被推导为 const int & 类型
    • 变量 c 被推导为 const int 类型
    • 变量 d 被推导为 string 类型

    2.表达式是函数调用,使用 decltype 推导出的类型和函数返回值一致。

    1. class Test{...};
    2. //函数声明
    3. int func_int(); // 返回值为 int
    4. int& func_int_r(); // 返回值为 int&
    5. int&& func_int_rr(); // 返回值为 int&&
    6. const int func_cint(); // 返回值为 const int
    7. const int& func_cint_r(); // 返回值为 const int&
    8. const int&& func_cint_rr(); // 返回值为 const int&&
    9. const Test func_ctest(); // 返回值为 const Test
    10. //decltype类型推导
    11. int n = 100;
    12. decltype(func_int()) a = 0;
    13. decltype(func_int_r()) b = n;
    14. decltype(func_int_rr()) c = 0;
    15. decltype(func_cint()) d = 0;
    16. decltype(func_cint_r()) e = n;
    17. decltype(func_cint_rr()) f = 0;
    18. decltype(func_ctest()) g = Test();
    • 变量 a 被推导为 int 类型
    • 变量 b 被推导为 int& 类型
    • 变量 c 被推导为 int&& 类型
    • 变量 d 被推导为 int 类型
    • 变量 e 被推导为 const int & 类型
    • 变量 f 被推导为 const int && 类型
    • 变量 g 被推导为 const Test 类型

    函数 func_cint () 返回的是一个纯右值(在表达式执行结束后不再存在的数据,也就是临时性的数据),对于纯右值而言,只有类类型可以携带const、volatile限定符,除此之外需要忽略掉这两个限定符因此推导出的变量 d 的类型为 int 而不是 const int。

    3.表达式是一个左值,或者被括号 ( ) 包围,使用 decltype 推导出的是表达式类型的引用(如果有 const、volatile 限定符不能忽略)

    1. #include
    2. #include
    3. using namespace std;
    4. class Test
    5. {
    6. public:
    7. int num;
    8. };
    9. int main() {
    10. const Test obj;
    11. //带有括号的表达式
    12. decltype(obj.num) a = 0;
    13. decltype((obj.num)) b = a;
    14. //加法表达式
    15. int n = 0, m = 0;
    16. decltype(n + m) c = 0;
    17. decltype(n = n + m) d = n;
    18. return 0;
    19. }
    • obj.num 为类的成员访问表达式,符合场景 1,因此 a 的类型为 int
    • obj.num 带有括号,符合场景 3,因此 b 的类型为 const int&。
    • n+m 得到一个右值,符合场景 1,因此 c 的类型为 int
    • n=n+m 得到一个左值 n,符合场景 3,因此 d 的类型为 int&

    2.2decltype的应用

    关于 decltype 的应用多出现在泛型编程中。比如我们编写一个类模板,在里边添加遍历容器的函数,操作如下:

    1. #include
    2. using namespace std;
    3. template <class T>
    4. class Container
    5. {
    6. public:
    7. void func(T& c)
    8. {
    9. for (m_it = c.begin(); m_it != c.end(); ++m_it)
    10. {
    11. cout << *m_it << " ";
    12. }
    13. cout << endl;
    14. }
    15. private:
    16. ??? m_it; // 这里不能确定迭代器类型
    17. };
    18. int main()
    19. {
    20. const list<int> lst;
    21. Container<const list<int>> obj;
    22. obj.func(lst);
    23. return 0;
    24. }

    在程序的第 17 行出了问题,关于迭代器变量一共有两种类型:只读(T::const_iterator)和读写(T::iterator),有了 decltype 就可以完美的解决这个问题了,当 T 是一个 非 const 容器得到一个 T::iterator,当 T 是一个 const 容器时就会得到一个 T::const_iterator。

    更改后代码如下:

    1. #include
    2. #include
    3. using namespace std;
    4. template <class T>
    5. class Container
    6. {
    7. public:
    8. void func(T& c)
    9. {
    10. for (m_it = c.begin(); m_it != c.end(); ++m_it)
    11. {
    12. cout << *m_it << " ";
    13. }
    14. cout << endl;
    15. }
    16. private:
    17. decltype(T().begin()) m_it; // 这里不能确定迭代器类型
    18. };
    19. int main()
    20. {
    21. const list<int> lst{ 1,2,3,4,5,6,7,8,9 };
    22. Container<const list<int>> obj;
    23. obj.func(lst);
    24. return 0;
    25. }

    成熟时 

  • 相关阅读:
    JSON和全局异常处理
    Navisworks二次开发——图元属性获取
    隐私计算FATE-核心概念与单机部署
    docker安装tomcat8
    el-ement ui走马灯去除默认的切换显示
    .NET轻松实现支付宝服务窗网页授权并获取用户相关信息
    【数据结构练习】二叉树相关oj题集锦一
    软件测试技术复习
    c++ 类的实例化顺序
    蓝桥杯每日一题2023.10.5
  • 原文地址:https://blog.csdn.net/weixin_51609435/article/details/126541139