C++11新特性之decltype类型推导
目录
一、decltype关键字
二、decltype的推导规则
1、表达式为单独变量
2、表达式为函数调用
3、表达式为左值,或者被()包围
三、 decltype的应用
在前面一章,小编已经对auto类型推导这个关键字进行了介绍,相信小伙伴们都知道了auto的用法以及限制了吧,不知道的小伙伴回头自己去搜索一下~~~。
一、decltype关键字
在这一章节中,小编要告诉你另外一个关键字,这个关键字也是作为类型推导用的,它是C++11新特性新加的一个关键字。这个关键字就是decltype,它和auto的功能一样,也是在编译器编译时期进行自动类型的推导。其实你可以把这个关键字看成是declare和type的缩写,这样是不是显得更加明确了呢?
看到这里,你肯定有一个疑问就是,为什么有了auto类型推导,还要有decltype这个关键字的出现呢?其实细心一点的话你可以发现auto关键字有一些限制,也就是上章节有讲到的限制,像定义变量的时候一定要初始化,不能作用于类的非静态变量。所以我们总结了auto并不是适用于所有的自动类型推导的场景,在某些特定环境下auto使用起来非常的不方便,甚至无法使用它,所以C++11新特性才会引出decltype这个关键字。
我们都知道auto定义变量的语法为:
auto serven_1 = 10;
可以看到auto是根据=右边的初始值来自动推导变量的类型,所以auto在定义的时候必须初始化;而同样decltype也有自己的语法,它的语法为:
decltype (exp) varname = value;
其中,varname表示变量名,value表示赋值变量的值,exp表示一个表达式,这就是和auto最大的区别了,decltype是根据表达式exp来自动推导出变量的类型,跟=右边的value没有关系,所以decltype在定义的时候可以不用初始化,因此,decltype定义变量也可以写成:
decltype (exp) varname;
这里需要注意exp表达式,通常一说到表达式,我们脑子里面想到的就是一个普通的表达式,像 x = 1等等,但是关键字decltype关键字中的表达式可以是复杂的表达式,复杂到你怀疑人生嘿嘿嘿。但是有一点需要注意的是必须保证表达式exp的结果是有类型的,不能是void。像exp在调用一个返回值类型为void的函数时,exp的结果也是void类型,此时编译器会给你直接报错了。
void show(){
cout<<"三贝勒文子!"<<endl;
}
decltype (show()) serven_2; // 编译器会一脸问号的问你,这是啥玩意???
如果使用下面的代码:
int show(){
cout<<"Serven"<<endl;
return 6;
}
int main(){
int serven_1 = 1;
decltype(serven_1) serven_2 = 3;
decltype(show()) serven_3 = 5;
cout<<serven_3<<endl; // 会打印5,但是不会打印函数show中的内容
}
decltype用法:
int serven_1 = 0;
decltype (serven_1) serven_2 = 1; // serven_2被推导为 int
decltype (1.2) serven_3; // serven_3被推导为 double,不初始化也可以
decltype (serven_3 + 100) serven_4; // serven_4被推导为 double
从上面的例子可以看到,decltype能够根据变量、字面量、带有运算符的表达式或者函数的返回类型推导出变量的类型。
二、decltype的推导规则
上面的例子让我们知道了decltype的一些初级用法,下面我们来了解一下decltype的高级用法吧,它的高级用法可以非常的复杂,当我们使用关键字来进行类型推导的时候,编译器有三个规则:
- 如果表达式exp是一个不被括号()包围的表达式,或者是个类成员访问表达式,或者是一个单独的变量,那么decltype(exp)的类型就是和exp一致的,这应该不难理解;
- 如果exp是函数调用,那么decltype(exp)的类型就和函数的返回值类型一致,但是注意一点就是函数的返回值不能是void;
- 如果exp是一个左值,或者被括号()包围,那么decltype(exp)的类型就是exp的引用,假如exp的类型为T,那么decltype(exp)的类型就是T&。
1、表达式为单独变量
#include <QCoreApplication>
#include <iostream>
#include<string>
using namespace std;
class SERVEN_PAR{
public:
static int ser1; // 类静态变量在类中不能初始化
string ser2;
int ser3;
float ser4;
};
int SERVEN_PAR::ser1 = 1; // 初始化类的静态变量
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int n = 0;
const int& r = n;
SERVEN_PAR SERVEN;
decltype(n) b = n; // n为int类型,b被推导为int类型
decltype(r) c = n; // r为const int&类型,c被推导为const int类型
decltype(SERVEN_PAR::ser1) d = 0; // ser1位类的一个int类型成员,那么d被推导为int类型
decltype(SERVEN.ser2) e = "https://www.baidu.com"; // ser2位类的一个string类型,那么e被推导为string类型
return a.exec();
}
2、表达式为函数调用
#include <QCoreApplication>
#include <iostream>
#include<string>
using namespace std;
int& func_int_first(int, char); // 返回值为int&
int&& func_int_second(void); // 返回值为int&&
int func_int(double); // 返回值为int
const int& func_cint_first(int,int,int); // 返回值为const int&
const int&& func_cint_second(void); // 返回值为const int&&
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int n = 10;
decltype(func_int_first(100,'S')) b = n; // b的类型为int&, 这里要赋初始值,因为int&引用需要初始化的,但是这里不能直接赋数值,因为这是左值引用
decltype(func_int_second()) c = 0; // c的类型为int&&,这里需要赋初始值,这是右值引用,所以可以直接赋数值
decltype(func_int(10.5)) d = 0; // d的类型为 int
decltype(func_cint_first(1,2,3)) x = n; // x 的类型为 const int &
decltype(func_cint_second()) y = 0; // y 的类型为 const int&&
return a.exec();
}
值得注意的是:这里的表达式只使用了函数的返回类型,不会执行函数里面的函数体内容的。代码中decltype(func_int_first(100,'S')) b = n;b的类型为int&, 这里要赋初始值,因为int&引用需要初始化的,但是这里不能直接赋数值,因为这是左值引用。而decltype(func_int_second()) c = 0;c的类型为int&&,这里需要赋初始值,这是右值引用,所以可以直接赋数值。
3、表达式为左值,或者被()包围
#include <QCoreApplication>
#include <iostream>
#include<string>
using namespace std;
class SERVEN_PAR{
public:
int x;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
SERVEN_PAR SERVEN;
/* 带有括号的表达式 */
decltype(SERVEN.x) b;
decltype((SERVEN.x)) c = b;
/* 加法表达式 */
int n = 0, m = 0;
decltype(n+m) d = 0; // n+m得到一个右值,复合推导规则一,推导结果为int
decltype(n = n+m) e = c; // n=n+m得到一个左值,复合推导规则三,推导结果为int
return a.exec();
}
上面的代码中包括了带有括号的表达式,和加法表达式两种,其中加法表达式里面分为了左值和右值,左值是指那些在表达式执行结束后依然存在的数据,也就是持久性的数据;右值是指那些在表达式执行结束后不再存在的数据,也就是临时性的数据,对表达式取地址操作,如果编译器报错了那就是右值,如果编译器不报错那就是左值。
三、 decltype的应用
我们知道了auto的使用比较简单,所以在一般的类型推导过程中,使用auto会比decltype更加方便。但是auto只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型的话,那么就得使用decltype关键字了,下面是一个模板的定义:
#include <vector>
using namespace std;
template <typename T>
class SERVEN_PAR {
public:
void func(T& container) {
m_it = container.begin();
}
private:
typename T::iterator m_it; //注意这里
};
int main(){
const vector<int> v;
SERVEN_PAR<const vector<int>> obj;
obj.func(v);
return 0;
}
单独看SERVEN_PAR类中的m_it成员的定义,很难看出有什么错误,但是在使用SERVEN_PAR类的时候,如果传入一个const类型的容器,编译器马上就会弹出一大堆错误。原因就在于T::iterator并不能包括所有的迭代器类型,当T是一个const容器时,应用使用const_iterator。要想解决这个问题,在之前的 C++98/03 版本下只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量,看起来也非常晦涩。但是有了 C++11 的 decltype 关键字,就可以直接这样写:
template <typename T>
class SERVEN_PAR {
public:
void func(T& container) {
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; //注意这里
};
欢迎关注小编的微信公众号 “三贝勒文子”,每天学习C++
https://blog.csdn.net/weixin_43340455/article/details/124868554
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」