个人网站建设公司地址,网站建设需要多长时间,wordpress 收费下载,塘沽企业网站建设这里对C的类型推导方式进行一次全面的总结。
C中有三种类型推导的方式#xff0c;分别是模板、auto以及decltype()。以下分别介绍这三种方式的同异。
一 模板
假设有这样的函数模板和这样的调用#xff1a;
templatetypename T
void f(ParamType param);f(expr);…这里对C的类型推导方式进行一次全面的总结。
C中有三种类型推导的方式分别是模板、auto以及decltype()。以下分别介绍这三种方式的同异。
一 模板
假设有这样的函数模板和这样的调用
templatetypename T
void f(ParamType param);f(expr); //使用表达式调用fT的类型推导不仅取决于expr的类型也取决于ParamType的类型。下面份三种情况讨论这个事情。
1 ParamType是一个指针或引用但不是通用引用
在这种情况下类型推导会这样进行
如果expr的类型是一个引用忽略引用部分然后expr的类型与ParamType进行模式匹配来决定T 有如下例子
templatetypename T
void f(T param); //param是一个引用int x27; //x是int
const int cxx; //cx是const int
const int rxx; //rx是指向作为const int的x的引用//不同的调用中对param和T推导的类型会是这样
f(x); //T是intparam的类型是int
f(cx); //T是const intparam的类型是const int
f(rx); //T是const intparam的类型是const int在第二个和第三个调用中注意因为cx和rx被指定为const值所以T被推导为const int。
因为他们传递一个const对象给一个引用类型的形参时他们期望对象保持不可改变性也就是说形参是reference-to-const的。这也是为什么将一个const对象传递给以T类型为形参的模板安全的对象的常量性constness会被保留为T的一部分。
在第三个例子中注意即使rx的类型是一个引用T也会被推导为一个非引用 因为rx的引用性reference-ness在类型推导中会被忽略。 如果我们将f的形参类型T改为const T情况有所变化
templatetypename T
void f(const T param); //param现在是reference-to-constint x 27; //如之前一样
const int cx x; //如之前一样
const int rx x; //如之前一样f(x); //T是intparam的类型是const int
f(cx); //T是intparam的类型是const int
f(rx); //T是intparam的类型是const intcx和rx的constness依然被遵守但是因为现在我们假设param是reference-to-constconst不再被推导为T的一部分。 如果param是一个指针或者指向const的指针而不是引用情况本质上也一样:
templatetypename T
void f(T* param); //param现在是指针int x 27; //同之前一样
const int *px x; //px是指向作为const int的x的指针f(x); //T是intparam的类型是int*
f(px); //T是const intparam的类型是const int*2 ParamType是一个通用引用
其推导规则如下
如果expr是左值T和ParamType都会被推导为左值引用。这非常不寻常第一这是模板类型推导中唯一一种T被推导为引用的情况。第二虽然ParamType被声明为右值引用类型但是最后推导的结果是左值引用。如果expr是右值就使用正常的也就是情景一推导规则。 示例如下
templatetypename T
void f(T param); //param现在是一个通用引用类型int x27; //如之前一样
const int cxx; //如之前一样
const int rxcx; //如之前一样f(x); //x是左值所以T是int//param类型也是intf(cx); //cx是左值所以T是const int//param类型也是const intf(rx); //rx是左值所以T是const int//param类型也是const intf(27); //27是右值所以T是int//param类型就是int3 ParamType既不是指针也不是引用
当ParamType既不是指针也不是引用时我们通过传值pass-by-value的方式处理这意味着无论传递什么param都会成为它的一份拷贝——一个完整的新对象。事实上param成为一个新对象这一行为会影响T如何从expr中推导出结果。
和之前一样如果expr的类型是一个引用忽略这个引用部分如果忽略expr的引用性reference-ness之后expr是一个const那就再忽略const。如果它是volatile也忽略volatile
示例如下
templatetypename T
void f(T param); //以传值的方式处理paramint x27; //如之前一样
const int cxx; //如之前一样
const int rxcx; //如之前一样f(x); //T和param的类型都是int
f(cx); //T和param的类型都是int
f(rx); //T和param的类型都是int注意即使cx和rx表示const值param也不是const。这是有意义的。param是一个完全独立于cx和rx的对象——是cx或rx的一个拷贝。 但是考虑这样的情况, expr是一个const指针指向const对象expr通过传值传递给param
templatetypename T
void f(T param); //仍然以传值的方式处理paramconst char* const ptr //ptr是一个常量指针指向常量对象Fun with pointers;f(ptr); //传递const char * const类型的实参在这里解引用符号 * 的右边的const表示ptr本身是一个constptr不能被修改为指向其它地址也不能被设置为null解引用符号左边的const表示ptr指向一个字符串这个字符串是const因此字符串不能被修改。当ptr作为实参传给f组成这个指针的每一比特都被拷贝进param。像这种情况ptr自身的值会被传给形参根据类型推导的第三条规则ptr自身的常量性constness将会被省略所以param是const char*也就是一个可变指针指向const字符串。在类型推导中这个指针指向的数据的常量性constness将会被保留但是当拷贝ptr来创造一个新指针param时ptr自身的常量性constness将会被忽略。
两个特殊情况
1 数组实参
如果将一个数组传值给一个模板会发生什么
templatetypename T
void f(T param); //传值形参的模板f(name); //T和param会推导成什么类型?这里有一个函数的形参是数组但是数组声明会被视作指针声明这意味着下面的两个声明是等价的
void myFunc(int param[]);
void myFunc(int* param); //与上面相同的函数因为数组形参会视作指针形参所以传值给模板的一个数组类型会被推导为一个指针类型。这意味着在模板函数f的调用中它的类型形参T会被推导为const char*
const char name[] J. P. Briggs; //name的类型是const char[13]f(name); //name是一个数组但是T被推导为const char*但是现在难题来了虽然函数不能声明形参为真正的数组但是可以接受指向数组的引用所以我们修改f为传引用, 然后调用它
templatetypename T
void f(T param); //传引用形参的模板f(name); //传数组给fT被推导为了真正的数组这个类型包括了数组的大小在这个例子中T被推导为const char[13]f的形参对这个数组的引用的类型则为const char ()[13]。是的这种语法看起来简直有毒但是知道它将会让你在关心这些问题的人的提问中获得大神的称号。
2 函数实参 在C中不只是数组会退化为指针函数类型也会退化为一个函数指针我们对于数组类型推导的全部讨论都可以应用到函数类型推导和退化为函数指针上来。结果是
void someFunc(int, double); //someFunc是一个函数//类型是void(int, double)templatetypename T
void f1(T param); //传值给f1templatetypename T
void f2(T param); //传引用给f2f1(someFunc); //param被推导为指向函数的指针//类型是void(*)(int, double)
f2(someFunc); //param被推导为指向函数的引用//类型是void()(int, double)二 auto
auto是建立在模板类型推导的基础上的这里举出它们的同异点。
1 相同点
auto类型推导和模板类型推导有一个直接的映射关系比如以下模板
templatetypename T
void f(ParmaType param);f(expr); //使用一些表达式调用f当一个变量使用auto进行声明时auto扮演了模板中T的角色变量的类型说明符扮演了ParamType的角色。
比如以下示例
auto x 27; //这里的x的类型说明符是auto自己
const auto cx x; //这里的x的类型说明符是const auto
const auto rxcx; //这里的x的类型说明符是const auto 因此Item1描述的三个情景稍作修改就能适用于auto
情景一类型说明符是一个指针或引用但不是通用引用情景二类型说明符一个通用引用情景三类型说明符既不是指针也不是引用
auto类型推导和模板类型推导几乎一样的工作它们就像一个硬币的两面除了一个例外。下面来说说这个例外。
2 不同点
auto类型推导和模板类型推导的真正区别在于auto类型推导假定花括号表示std::initializer_list而模板类型推导不会这样确切的说是不知道怎么办。
auto x1 27; //类型是int值是27
auto x2(27); //同上
auto x3 { 27 }; //类型是std::initializer_listint值是{ 27 }
auto x4{ 27 }; //同上templatetypename T //带有与x的声明等价的
void f(T param); //形参声明的模板f({ 11, 23, 9 }); //错误不能推导出T然而如果在模板中指定T是std::initializer_list而留下未知T,模板类型推导就能正常工作
templatetypename T
void f(std::initializer_listT initList);f({ 11, 23, 9 }); //T被推导为intinitList的类型为//std::initializer_listintC14允许auto用于函数返回值并会被推导参见Item3而且C14的lambda函数也允许在形参声明中使用auto。但是在这些情况下auto实际上使用模板类型推导的那一套规则在工作而不是auto类型推导所以说下面这样的代码不会通过编译
auto createInitList()
{return { 1, 2, 3 }; //错误不能推导{ 1, 2, 3 }的类型
}std::vectorint v;
…
auto resetV [v](const auto newValue){ v newValue; }; //C14
…
resetV({ 1, 2, 3 }); //错误不能推导{ 1, 2, 3 }的类型三 decltype
decltype总是不加修改的产生变量或者表达式的类型。
对于T类型的不是单纯的变量名的左值表达式decltype总是产出T的引用即T。
C14支持decltype(auto)就像auto一样推导出类型但是它使用decltype的规则进行推导。
以下说明decltype的一个重要的用法
有如下函数
templatetypename Container, typename Index //可以工作
auto authAndAccess(Container c, Index i) //但是需要改良
{authenticateUser();return c[i];
}std::dequeint d;
…
authAndAccess(d, 5) 10; //认证用户返回d[5]//然后把10赋值给它//无法通过编译器对一个T类型的容器使用operator[] 通常会返回一个T对象比如std::deque就是这样。(但是std::vector有一个例外对于std::vectoroperator[]不会返回bool它会返回一个全新的对象, 这是一个例外)。
函数返回类型中使用auto编译器实际上是使用的模板类型推导的那套规则。如果那样的话这里就会有一些问题。正如我们之前讨论的operator[]对于大多数T类型的容器会返回一个T但是在模板类型推导期间表达式的引用性reference-ness会被忽略。基于这样的规则在这里d[5]本该返回一个int但是模板类型推导会剥去引用的部分因此产生了int返回类型。函数返回的那个int是一个右值上面的代码尝试把10赋值给右值intC11禁止这样做所以代码无法编译。
要想让authAndAccess像我们期待的那样工作我们需要使用decltype类型推导来推导它的返回值C期望在某些情况下当类型被暗示时需要使用decltype类型推导的规则C14通过使用decltype(auto)说明符使得这成为可能。
templatetypename Container, typename Index //最终的C14版本
decltype(auto)
authAndAccess(Container c, Index i)
{authenticateUser();return std::forwardContainer(c)[i];
}decltype(auto)的使用不仅仅局限于函数返回类型当你想对初始化表达式使用decltype推导的规则你也可以使用
Widget w;const Widget cw w;auto myWidget1 cw; //auto类型推导//myWidget1的类型为Widget
decltype(auto) myWidget2 cw; //decltype类型推导//myWidget2的类型是const Widget总结
以上是C类型推导的全部内容。充分使用C的类型推导能使我们尽可能简单的代码。