自动类型推导
<p>c++11中添加了自动推导变量的类型<code>auto</code>,以及<code>decltype</code>表示函数的返回值。</p><h2 id="auto">auto</h2>
<p><code>auto</code>可以像别的语言一样自动推导出变量的实际类型。</p>
<p>在实际中,<code>auto</code>像是一个”占位符“,使用<code>auto</code>声明的变量必须要进行初始化,以<strong>让编译器推导出它的实际类型,在编译时将<code>auto</code>换成真正的类型。</strong></p>
<p>语法:</p>
<pre><code class="language-c++">auto 变量名 = 变量值
</code></pre>
<p>实际使用例子:</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
int main() {
//没有const修饰
auto x = 3.14; //double
auto y = 520;//int
auto z = 'a';//char
//auto nb; //语法错误
//auto double nbl; //语法错误
int temp = 110;
auto* a = &temp; //&temp:int* auto*:int* auto:int
auto b = &temp;//auto:int*
auto& c = temp;//auto:int
auto d = temp; //auto:int
//有const修饰
int tmp = 250;
const auto a1 = tmp;//auto:int a1:const int
auto a2 = a1; //a1不是指针也不是引用 auto:int a2:int
const auto& a3 = tmp; //a3:const int& auto:int
auto& a4 = a3; //a3是引用类型 a3:const int& a4:const int& auto&:const int& auto:const int
auto* pt4 = &a1; //&a1是地址 a1:const int pt4:const int* auto:const int
system("pause");
return 0;
}
</code></pre>
<p>需要注意的是:</p>
<p>在auto和指针、引用结合在一起时,推导的结果会保留const、volatile关键字(volatile表示变量,经常修改的变量)</p>
<ul>
<li>当变量不是指针或者引用类型时,推导的结果中不会保留const、volatile关键字。</li>
<li>当变量时指针或者引用类型时,推导的结果中会保留const、volatile关键字。</li>
</ul>
<p>就如上述代码中的 <code>//有const修饰</code>后面的代码,需要注意变量是否为指针或者引用类型。</p>
<h3 id="auto不能推导的4个情况">auto不能推导的4个情况</h3>
<h5 id="1-不能作为函数参数使用因为只有在函数调用的时候才会给函数参数传递实参auto要求必须要给修饰的变量赋值因此二者不矛盾">1. 不能作为函数参数使用,因为只有在函数调用的时候才会给函数参数传递实参,auto要求必须要给修饰的变量赋值,因此二者不矛盾。</h5>
<pre><code class="language-c++">int func(auto a, auto b) {//error
cout << "a: " << a << ", b: " << b << endl;
}
</code></pre>
<h5 id="2不能用于类的非静态成员变量的初始化">2.不能用于类的非静态成员变量的初始化</h5>
<pre><code class="language-c++">class Test {
auto v1 = 0; //error
static auto v2 = 0; //error,类的静态非常量成员不允许在类内部直接初始化
static const auto v3 = 10; //ok
};
</code></pre>
<h5 id="3-不能使用auto关键字定义数组">3. 不能使用auto关键字定义数组</h5>
<pre><code class="language-c++">int func() {
int array[] = { 1, 2, 3, 4, 5 }; //定义数组
auto t1 = array; //ok, t1被推导为 int* 类型
auto t2[] = array; //error, auto无法定义数组
auto t3[] = { 1, 2, 3, 4, 5 }; //error, auto无法定义数组
}
</code></pre>
<h5 id="4-无法使用auto推导出模板参数">4. 无法使用auto推导出模板参数</h5>
<pre><code class="language-c++">template <typename T>
struct Test {};
int func() {
Test<double> t;
Test<auto> t1 = t; //error,无法推导出模板类型
return 0;
}
</code></pre>
<h3 id="auto常用用途">auto常用用途</h3>
<h5 id="1-用于stl容器的遍历">1. 用于STL容器的遍历</h5>
<p>在遍历时我们会写:<code>map<int, string>::iterator it = mp.begin();</code>这样的迭代器,在有了auto之后可以用<code>auto</code>代替<code>map<int, string>::iterator</code></p>
<pre><code class="language-c++">#include <iostream>
#include <map>
using namespace std;
int main() {
//key:int , value:string
map<int, string>mp;
mp.insert(make_pair(1, "ace"));
mp.insert(make_pair(2, "sabo"));
mp.insert(make_pair(3, "luffy"));
//map<int, string>::iterator it = mp.begin();
auto it = mp.begin();
for (; it != mp.end(); it++) {
cout << "key: " << it->first << ", value: " << it->second << endl;
}
system("pause");
return 0;
}
</code></pre>
<p>如上述代码,在有了auto之后迭代器的定义简单方便了很多。</p>
<h5 id="2-用在泛型编程">2. 用在泛型编程</h5>
<p>在使用模板的时候,很多情况下我们不知道变量应该定义成什么类型,比如下面的代码:</p>
<pre><code class="language-c++">#include <iostream>
#include <map>
using namespace std;
class T1 {
public:
static int get() {
return 10;
}
};
class T2 {
public:
static string get() {
return "hello, world";
}
};
//template<class T, typename P>
template <class T>
void func() {
auto ret = T::get();
//P ret = T::get();
cout << "ret: " << ret << endl;
}
int main() {
func<T1>();
func<T2>();
//func<T1, int>();
//func<T2, string>();
system("pause");
return 0;
}
</code></pre>
<p>上述代码中<code>T1::get()</code>返回int类型,<code>T2::get()</code>返回string类型。在模板中调用时就能不确定返回什么类型的值,使用auto解决了这一问题,如果不用auto只能像注释中的那样多定义一个模板参数来确定返回的值时什么类型。</p>
<h2 id="decltype">decltype</h2>
<p>C++11增加了decltype关键字,它是在编译器编译的时候推导出一个表达式的类型;</p>
<p>decltype不需要定义变量,不需要初始化变量也可以推导类型。语法:<code>decltype(表达式)</code></p>
<p>decltype是"declare type"的缩写,译为"声明类型"。decltype的推导是在编译时完成的,只是用于推导表达式的类型,并不会计算表达式的值,如下:</p>
<pre><code class="language-c++">int a = 10;
decltype(a) b = 99; //a:int b:int
decltype(a+3.14) c = 3.14159;//a+3.14:double c:double
decltype(a+b*c) d = 234.2343;//a+b*c:double d:double
decltype(a) e; //a:int e:int
</code></pre>
<p>decltype只是使用了括号中表达式的类型,后面的变量定义和括号中表达式值的大小无关。</p>
<p>auto只能推导已初始化的变量类型,decltype推导的可以不进行初始化。</p>
<h3 id="decltype的推导规则">decltype的推导规则</h3>
<p>decltype的3个场景的使用规则:</p>
<h5 id="1-表达式为普通变量或者普通表达式或者类表达式">1. 表达式为普通变量或者普通表达式或者类表达式</h5>
<p>表达式为普通变量或者普通表达式后者类表达式时,decltype推导出的类型和表达式的类型是一样的。</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
class Test {
public:
int num = 9;
string text;
static const int value = 110;
};
int main() {
int x = 99;
const int& y = x;
decltype(x) a = x; //x:int a:int
decltype(y) b = x; //y:const int& b:const int&
decltype(Test::value) c = 0; //Test::value : const int c:const int
Test t;
decltype(t.text) d = "hello, world"; //t.text : string d:string
system("pause");
return 0;
}
</code></pre>
<h5 id="2表达式是函数调用">2.表达式是函数调用</h5>
<p>表达式是函数调用的时候,decltype推导出的类型和函数返回值是一致的。</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
class Test {
public:
int num = 9;
string text;
static const int value = 110;
};
//函数声明
int func_int() {};
int& func_int_r() {};
int&& func_int_rr() {};
const int func_cint() {};
const int& func_cint_r() {};
const int&& func_cint_rr() {};
const Test func_ctest() {};
int main() {
int n = 100;
decltype(func_int()) a = 0; //func_int():int a:int
decltype(func_int_r()) b = n;//func_int_r():int& b:int&
decltype(func_int_rr()) c = 0; //func_int_rr():int&& c:int&&
decltype(func_cint()) d = 0; //func_cint():const int d:int (这里是因为func_cint()函数返回的值是一个纯右值,就会被推导为int;对于右值而言只有类类型可以携带const、volatile限定符,其他需要忽略这两个限定符)
decltype(func_cint_r()) e = 0; //func_cint_r():const int& e:const int&
decltype(func_cint_rr()) f = 0; //func_cint_rr():const int&& f:const int&&
decltype(func_ctest()) g = Test(); //func_ctest:const Test g:const Test
system("pause");
return 0;
}
</code></pre>
<p>上述代码中<code>func_cint()</code>返回的是一个纯右值(再<code>func_cint()</code>运行之后不再存在数据,也就是返回的是临时性的数据),对于纯右值而言,只有类类型会携带const、volatile限定符,其他需要忽略这两个限定符。</p>
<blockquote>
<p>上例中用到了int&&等右值引用,这里补充一下右值引用相关内容(以int&&为例):</p>
<p>int&是左值引用,绑定到可命名、可持久存在的对象;</p>
<p>C++11引入了int&&右值引用</p>
<p>int&&为右值引用,绑定到临时对象或将要被销毁的对象,用于移动语义和完美转发。</p>
<pre><code class="language-c++">int a = 10;
int& b = a; //正确
int& c = 30;//错误
int&& r = a;//错误
int&& d = 30; //正确
int&& e = 20+10; //正确
</code></pre>
<p>int&&的最重要的应用:移动语义</p>
<p>在移动右值的时候可以避免深拷贝,性能更高:</p>
<pre><code class="language-c++">vector<int> a = {1,2,3};
vector<int> b = std::move(a);
</code></pre>
<p>其中<code>std::move()</code>是c++11引入的一个函数,它的作用是:把一个对象强制转换成右值引用,从而触发移动语义,<strong>注意:<code>std::move()</code>本身并不会移动任何数据,它只是一个类型转换。</strong></p>
<pre><code class="language-c++">#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> a = {1,2,3};
vector<int> b = std::move(a);
cout << a.size() << endl; // 可能为0
}
</code></pre>
<p>上述代码中的<code>vector<int> b = std::move(a)</code>发生的是:</p>
<p>a->内存数据</p>
<p>移动后</p>
<p>b->内存数据</p>
<p>a->空</p>
<p>移动之后b接管了a的资源,a变成空对象</p>
<p>移动有别于拷贝,新的变量接管了原来变量的内存数据,原来变量就成为空对象了。</p>
<p>注意:</p>
<p><code>std::move()</code>不会移动对象。只是说:这个对象的资源可以被拿走了。</p>
<p>真正移动的是:移动构造函数 或者 移动赋值运算符</p>
</blockquote>
<h5 id="3表达式是一个左值或者被括号包围">3.表达式是一个左值,或者被括号()包围</h5>
<p>表达式是一个左值,或者被括号()包围时,使用decltype推导出的是表达式类型的引用(如果有const、volatile限定符不能忽略)</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
class Test {
public:
int num = 9;
string text;
static const int value = 110;
};
int main() {
const Test obj;
//带有括号的表达式
decltype(obj.num) a = 0; //obj.num:int a:int
decltype((obj.num)) b = a;//obj.num:int b:const int& 加了括号是引用,这里就是一个obj.num的引用,obj是const的,obj的里面的成员就也是const的
//加法表达式
int n = 0, m = 0;
decltype(n + m) c = 0; //n+m:int c:int 这里就是一个普通的表达式
decltype(n = n + m) d = n; //n=n+m :int d:int& 这里n是一个左值,所以是引用
system("pause");
return 0;
}
</code></pre>
<p>如上述代码<code>obj.num</code>是<code>int</code>类型,<code>(obj.num)</code>是<code>int&</code>类型,所以b是<code>const int&</code>类型</p>
<p><code>n + m</code>是普通的表达式,是右值,<code>n = n + m</code>这里的n进行赋值后是左值,所以是<code>int&</code>类型</p>
<h3 id="decltype的常用用途">decltype的常用用途</h3>
<p>decltype多引用在泛型编程中,比如我们编写一个类模板,在里边添加遍历容器的函数,代码如下:</p>
<pre><code class="language-c++">#include <iostream>
#include <list>
using namespace std;
template<class T>
class Container {
public:
void print(T& t) {
for (m_it = t.begin(); m_it != t.end(); m_it++) {
cout << "value: " << *m_it << endl;
}
cout << endl;
}
private:
decltype(T().begin()) m_it; //需要补充内容:T()是T的临时变量。。。。。。
};
int main() {
list<int> ls{ 1, 2, 3, 4, 5, 6, 7 };
Container<list<int>> c;
c.print(ls);
const list<int> ls1{ 1, 2, 3, 4, 5, 6, 7 };
Container<const list<int>> c1;
c1.print(ls);
system("pause");
return 0;
}
</code></pre>
<p>如果我们15行写<code>T::iterator m_it;</code>编译器会报错,无法得知这里要定义一个什么类型的iterator。</p>
<p>有了decltype就很好的解决了这个问题:<code>decltype(T().begin()) m_it</code>这里推导出的一定是一个迭代器类型的。</p>
<blockquote>
<p>其中T()是T的临时变量,它在这里创建一个T类型的临时对象(默认构造),以便于可以调用begin()函数,从而让<code>decltype</code>可以推导出迭代器类型。</p>
</blockquote>
<h2 id="返回类型后置">返回类型后置</h2>
<p>在泛型编程中,可能需要通过参数的运算来确定返回值的类型,比如:</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
template<typename R, typename T, typename U>
R add(T t, U u) {
return t + u;
}
int main() {
int x = 520;
double y = 13.14;
auto res = add<decltype(x+y),int,double>(x, y);
cout << "res = " << res << endl;
system("pause");
return 0;
}
</code></pre>
<p>上述代码中R是由传入的阐述 T 和 U 决定的,在调用模板函数时,我们通多<code>decltype(x+y)</code>自动推导函数返回值的类型。</p>
<p>但是在实际开发中,很少能知道<code>add()</code>函数的内部是怎么样的,也就无法确定返回值的类型了。</p>
<p>在c++11中增加了返回值类型后置语法,它是将decltype和auto结合起来完成返回类型的推导。语法如下:</p>
<pre><code class="language-c++">//符号 -> 后边跟随的是函数返回值的类型
auto func(参数1, 参数2, ...) -> decltype(参数表达式)
</code></pre>
<p>其中<code>decltype(参数表达式)</code>中的表达式不需要一定和<code>func()</code>内部的操作有关,只做类型推导的作用。auto会追踪decltype()推导出的类型。</p>
<p>使用返回值类型后置语法后,之前的案例可以改为:</p>
<pre><code class="language-c++">#include <iostream>
using namespace std;
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u){
return t + u;
}
int main() {
int x = 520;
double y = 13.14;
auto res = add(x, y);
cout << "res = " << res << endl;
system("pause");
return 0;
}
</code></pre>
<p>使用了返回值类型后置后,函数的调用变的更加方便;再加上方法的默认模板参数,这里的add就可以不需要<>直接传输参数<code>add(x,y)</code>。</p><br><br>
来源:https://www.cnblogs.com/ggkx/p/19696601 回复:
LZ总结得很详细啊!C++11的这两个特性确实非常实用。
说说我自己的使用感受:
[*]auto在遍历STL容器和写泛型代码时特别方便,特别是像map、vector这种迭代器类型很长的,用auto能省不少代码。不过要注意lz说的那些不能推导的情况,尤其是函数参数不能用auto这点刚开始经常有人踩坑。
[*]decltype配合auto的返回值后置语法真的很强大,之前写模板函数要手动指定返回类型,现在可以让编译器自动推导,代码简洁很多。
[*]还有一个点想补充一下,auto在C++14之后还可以用在lambda表达式参数上,比如:
auto lambda = [](auto a, auto b){ return a + b; };
[*]decltype在C++14还有个配套的特性叫decltype(auto),可以更方便地推导函数的返回类型,比单独的auto更灵活。
总的来说,这些特性让C++写起来越来越像动态语言了,但又能保持静态类型的性能优势,确实是很大的进步!
頁:
[1]