C++ 自动类型推断
C++语言提供了自动类型推断的机制,用于简化代码书写,这是一种很不错的特性,使用auto
和decltype
都可以完成自动类型推断的工作,而且都工作在编译期,这表示在运行时不会有任何的性能损耗。
一、auto自动类型推断
auto
自动类型推断的机制和函数模板的推断机制很相似,auto
就类似于模板中的T
。
(1.1) auto变量以传值方式初始化
一句话总结:抛弃掉对象的const和引用属性,新对象就像一个全新的副本;对于指针,会抛弃其顶层const属性,保留其底层const属性。
int main(int argc, char *argv[])
{
int a = 0;
int &a_ref = a;
const int ca = 0;
const int &ca_ref = ca;
const int *const pa = &a;
int arr[] = {1, 2, 3};
const carr[] = {1, 2, 3};
// 一、传值方式。
auto b1 = a; // b1为int, auto为int
auto b2 = a_ref; // b2为int, auto为int
auto b3 = ca; // b3为int, auto为int
auto b4 = ca_ref; // b4为int, auto为int
auto b5 = pa; // b5为const int *, auto为const int *
auto b6 = arr; // b6为int*, auto为int*
auto b7 = carr; // b7为const int*, auto为const int*
auto b8 = main; // b8为int(*)(int, char**), auto为int(*)(int,char**)
return 0;
}
(1.2) auto变量的指针或者引用类型初始化
一句话总结:引用属性被抛弃,但是const属性会被保留;对于指针类型,所有的const属性都被保留。
int main(int argc, char *argv[])
{
int a = 0;
int &a_ref = a;
const int ca = 0;
const int &ca_ref = ca;
const int *const pa = &a;
int arr[] = {1, 2, 3};
const int carr[] = {1, 2, 3};
// 二、传引用或指针方式。
auto &b1 = a; // b1为int&, auto为int
auto &b2 = a_ref; // b2为int&, auto为int
auto &b3 = ca; // b3为const int&, auto为const int
auto &b4 = ca_ref; // b4为const int&, auto为const int
auto &b5 = pa; // b5为const int *const &, auto为const int *const
auto &b6 = arr; // b6为int*&, auto为int*
auto &b7 = carr; // b7为const int *&, auto为const int *
auto &b8 = main; // b8为int(&)(int, char**), auto为int(int,char**)
return 0;
}
(1.3) auto变量的万能引用初始化
见万能引用即可:C++引用详解。
二、decltype自动类型推断
decltype
相对于auto
来讲更加温和,因为它不会丢弃任何的一个属性(不像auto
一样可能丢弃常量和引用)。当我们不想用表达式的值来初始化变量,而仅希望推断表达式的数据类型时,就可以使用decltype
。
(2.1) decltype推断变量的类型
decltype
推断的方式非常简单:原来的类型是什么,推断的结果就是什么。
int main(int argc, char *argv[])
{
int a = 0;
int &ra = a;
const int &cra = a;
int *pa = &a;
const int *cpa = pa;
const int *const ccpa = pa;
decltype(a) b1 = a; // b1为int
decltype(ra) b2 = a; // b2为int&
decltype(cra) b3 = a;// b3为const int&
decltype(pa) b4 = nullptr; // b4为int*
decltype(cpa) b5 = nullptr; // b5为const int *
decltype(ccpa) b6 = nullptr; // b6为const int *const
return 0;
}
(2.2) decltype推断表达式的类型
与推断变量不同的是,decltype表达式中的内容如果是左值,那么将推断为引用类型:
int main(int argc, char *argv[])
{
int a = 0;
int *pa = &a;
decltype(1 + 2) b1; // 推断为int。
decltype(*pa) b2 = a; // 由于*pa是左值,因此b的类型为int&。
decltype((a)) b3 = a; // 由于(a)是表达式,且是左值,因此b的类型为int&。
return 0;
}
(2.3) decltype推断函数或函数的调用结果
个人认为这是decltype
最强大的地方,因为此时并不会实际地调用函数,而仅仅会推断类型(和sizeof
十分相似):
#include <string>
std::string func() { return std::string(); }
int main(int argc, char *argv[])
{
decltype(func) a; // a为函数声明:std::string();
decltype(func) *b = nullptr; // b为函数指针:std::string(*)();
decltype(func) &c = func; // c为函数引用:std::string(&)();
decltype(func().c_str()) d = nullptr; // d为const char *
// 不会实际调用func()和c_str()。
return 0;
}
(2.4) decltype
的特殊用法示例
(2.4.1) 利用decltype
来提高变量声明的代码兼容性
利用decltype
可以在模板编程中实现数据类型的动态效果:
template<typename T>
class container_copier
{
public:
typename T::iterator _begin;
typename T::iterator _end;
container_copier(typename T::iterator begin, typename T::iterator end)
: _begin(begin), _end(end) {}
void copy() { /* ... */ }
};
这个模板可以应付普通的容器类型,但是无法针对常量容器对象进行操作:
#include <vector>
int main(int argc, char *argv[])
{
std::vector<int> vec = {1, 2, 3, 4, 5};
container_copier<std::vector<int>> cpr1(vec.begin(), vec.end());
cpr1.copy(); // OK.
const std::vector<int> cvec = {1, 2, 3, 4, 5};
container_copier<const std::vector<int>> cpr2(vec.begin(), vec.end()); // 错误
return 0;
}
错误的原因是:对于常量容器,begin
函数返回的是const_iterator,而不是iterator。如果不适用自动类型推断,我们就不得不进行偏特化:
template<typename T>
class container_copier<const T>
{
public:
typename T::const_iterator _begin;
typename T::const_iterator _end;
container_copier(typename T::const_iterator begin, typename T::const_iterator end)
: _begin(begin), _end(end) {}
void copy() { /* ... */ }
};
只有这样才能够通过编译。这样重复地编写偏特化代码无疑是很麻烦的。但是使用了自动类型推断之后,事情就简单多了:
#include <vector>
template<typename T>
class container_copier
{
public:
decltype(T().begin()) _begin;
decltype(T().end()) _end;
container_copier(decltype(T().begin()) begin,
decltype(T().end()) end)
: _begin(begin), _end(end) {}
void copy() { /* ... */ }
};
int main(int argc, char *argv[])
{
std::vector<int> vec = {1, 2, 3, 4, 5};
container_copier<std::vector<int>> cpr1(vec.begin(), vec.end());
cpr1.copy(); // OK.
const std::vector<int> cvec = {1, 2, 3, 4, 5};
container_copier<const std::vector<int>> cpr2(vec.begin(), vec.end()); // OK.
return 0;
}
这样,通过自动类型推断,就可以由编译器来动态地决定究竟是使用iterator
还是const_iterator
。
(2.4.2) 利用decltype
来提高函数返回类型的兼容性
有时,我们希望根据函数入参的某个方法的返回类型来决定函数的返回类型,那么就可以利用自动类型推断:
template<typename T>
auto func(T&& t) -> decltype(t.foo())
{
// ...
return t.foo();
}
这样,就可以不显示地指定返回类型,而让返回类型随着foo()方法的类型而变化。
三、decltype(auto)类型推断(c++14)
decltype(auto)
的语义是:要求编译器自动推断需要自动推断的类型。使用decltype(auto)
的主要目的是:由于auto
会抛弃掉引用或是常量属性,但是有时却需要自动类型推断来保留这些属性,因此decltype
可以将auto
丢弃的属性给找回来。
int main(int argc, char *argv[])
{
int a = 0;
const int &a_ref = a;
auto b1 = a_ref; // b1为int
// 如果我们想保留b1怎么办呢?
// 方法一:
decltype(auto) b2 = a_ref; // 等价于:decltype(a_ref) b2 = a_ref;
// b2为const int &
// 方法二:
auto &b3 = a_ref;
return 0;
}
一个常见的坑是:当decltype
推断左值表达式时,会产生引用类型;然而如果返回一个局部变量的引用将是危险的:
decltype(auto) func()
{
int i = 30;
// ...
return(i); // 错误,因为这等价于:decltype((i)), 这将返回i的引用,引发错误。
// return i; 正确。
}
C++ 自动类型推断的更多相关文章
- C++11新特性:自动类型推断和类型获取
声明:本文是在Alex Allain的文章http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-functi ...
- Java 8新特性探究(三)泛型的目标类型推断
简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...
- c++11 类型推断
自动类型推断 当编译器能够在一个变量的声明时候就推断出它的类型,那么你就能够用auto关键字来作为他们的类型: auto x = 1; 编译器当然知道x是integer类型的.所以你就不用int了.接 ...
- C++11特性——变量部分(using类型别名、constexpr常量表达式、auto类型推断、nullptr空指针等)
#include <iostream> using namespace std; int main() { using cullptr = const unsigned long long ...
- Swift语言指南(四)--类型安全和类型推断
原文:Swift语言指南(四)--类型安全和类型推断 Swift是一门类型安全语言,类型安全语言需要代码里值的类型非常明确.如果你的代码中有部分值需要String类型,你就不能错误地传递Int. 鉴于 ...
- java 11 局部变量类型推断
什么是局部变量类型推断? var javastack = "javastack"; System.out.println(javastack); 大家看出来了,局部变量类型推断就是 ...
- Java 10 实战第 1 篇:局部变量类型推断
现在 Java 9 被遗弃了直接升级到了 Java 10,之前也发过 Java 10 新特性的文章,现在是开始实战 Java 10 的时候了. 今天要实战的是 Java 10 中最重要的特性:局部变量 ...
- java泛型-自定义泛型方法与类型推断总结
下面是自定义泛型方法的练习: package com.mari.generic; import java.util.ArrayList; import java.util.Collection; im ...
- scala学习手记20 - 方法返回类型推断
除了推演变量的类型,scala也会推演方法的返回类型.不过这里有一处需要注意:方法返回类型的推演依赖于方法的定义方式.如果用等号"="定义方法,scala就会推演方法返回类型:否则 ...
随机推荐
- 【题解】NOIP2016 提高组 简要题解
[题解]NOIP2016 提高组 简要题解 玩具迷题(送分) 用异或实现 //@winlere #include<iostream> #include<cstdio> #inc ...
- Theia架构
上一篇:Theia——云端和桌面版的IDE 架构概述 本节描述了Theia的整体架构. Theia被设计为一个可以在本地运行的桌面应用程序,也可以在浏览器和远程服务器之间工作.为了支持这两种工作方式, ...
- leetcode.310最小高度树
对于一个具有树特征的无向图,我们可选择任何一个节点作为根.图因此可以成为树,在所有可能的树中,具有最小高度的树被称为最小高度树.给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点. ...
- JAVA字节码文件之常量池
一.常量池的内容 一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库,比如java类中定义的方法与变量信息.常量池中主要存储两类常量:字面量(文本字符 ...
- echarts在一个折线/柱状图浮窗显示多条数据
解决问题就在data里面,首先 data里面是可以json数组形式,如官方API上的 name:”“, value:”“,等 value是echart识别折线图的key值.1.来看数据格式 data: ...
- ruby 编写控制台进度条
ruby 中,$stdout.flush 让控制台当前行内容可以重写,以此我们可以做出进度条的效果. def set_progress(index, char = '*') print (char * ...
- 修饰符new与override
new:在作为修饰符时,可以隐藏从父类的继承的成员. override:修改父类的方法.属性. 上代码比较清楚: using System; using System.Collections.Gene ...
- 1z0-062 题库解析3
The hr user executes the following query on the employees table but does not issue commit, rollback, ...
- 【UEFI】---基于UEFI编程的基本思路
最近基于UEF在写代码的时候,发现由于粗心总是出现很多问题,而且都是一些小问题.虽然UEFI玩了挺久,但是也没梳理一下思路.借此机会整理一下: UEFI对复杂的BIOS代码做了很好的封装和模块化. ...
- 阿望教你用vue写扫雷(超详细哦)
前言 话说阿望还在大学时,某一天寝室突然停网了,于是和室友两人不约而同地打开了扫雷,比相同难度下谁更快找出全部的雷,玩得不亦乐乎,就这样,扫雷伴我们度过了断网的一周,是整整一周啊,不用上课的那种,可想 ...