C++Primer 2.5: Dealing with Types
Dealing with Types
随着我们的程序变得越来越复杂,各种types的名字变得越来越复杂。有些types变得越来越难拼写,有些types的含义变得越来越难以理解(或歧义)。因此,我们需要一些方法来让简化types的名字。
1. Type Aliases
A
type aliasis a name that is a synonym(同义词) for another type.
Type alias实际上就是给一个类型取别名(alias)。使用shell的人应该熟悉别名的意思,比如给一个复杂的command取一个简单的别名,方便下次键入。同时,给type取别名还能让我们更加突出这个type的意义。
typedef关键字
一个传统的取别名的方法是通过typedef关键字。与定义变量类似,typedef关键字定义一个type alias,但是要注意此时declarators(即*和&)指的是这个别名本身是什么type。例如:
typedef int price; // price is an alias of int
typedef price cost, *pointer;
// cost is an alias of cost, pointer is an alias of price *(i.e. int *);
price b1 = 100; // b1 is int type
pointer p = &b1; // p is int * type
cout<<*p<<endl;
using关键字
C++11提供了一种通过using关键字的新方法定义type alias:
using price = int; // price is an alias of int
个人感觉虽然typedef是从C就开始使用的方法,但是新的关键字using使用起来更加直观。另外,typedef看上去用宏#define也可以实现类似效果,但是这两个并不一样,例如:
#define m_price int *
int main(int argc, char* argv[]){
int i = 42;
m_price pi1 = &i, pi2 = i;
// --> `int * pi1, pi2;` pi1 is a pointer, pi2 is an int
cout<<*pi1<<" "<<pi2<<endl;
typedef int *t_price;
t_price tpi1 = &i, tpi2 = &i; // both tpi1 and tpi2 are pointers
cout<<*tpi1<<' '<<*tpi2<<endl;
}
可以看到,#define只是简单的文本替换,所以pi1是指针,但是pi2却是int类型。但是typedef则是定义了一个类型的别名,并不是简单的文本替换,所以使用别名t_price定义的变量tpi1和tpi2都是指针。同时,因为typedef不是简单的文本替换,所以当其定义了一个指针变量的别名且和const关键字搭配的时候得到的是一个top-level const的指针,即指针本身是const类型。例如:
typedef int *pointer;
const pointer p = nullptr; // p is a constant pointer
pointer const p1 = nullptr; // same as above
int i = 42;
p = &i; // error: p is a top-level const pointer
typedef int &ref;
const ref r = i;
r = 1; // ok: can change i through r
cout<<r<<endl; // 1
注意,typedef不是简单的文本替换,所以不能将p读成:。const int * pp实际上是一个top-level const类型的指针,即指针本身是const。因为typedef int *pointer指的是定义了一个int类型的别名pointer,并且这个别名pointer是指针,即可以说pointer是int型指针。所以const在修饰pointer的时候实际上修饰的是int型指针,所以p是一个const pointer而不是指向const的object。通俗地说,就是使用typedef定义的新类型*pointer会被看作是一个整体,当const修饰pointer的时候即是修饰这个整体(但不是文本替换),即修饰这个指针本身。
另外,对于引用,因为引用本身不是object,所以const虽然修饰的也是&ref整体(即引用本身),但是引用本身并不存在const的context。所以,最终得到的是一个普通的引用(非const引用),这也是为什么虽然定义了const ref r = i,但是还是可以通过r来修改i。这个情况和constexpr关键字一样,详情可以查看here。
Conclusions:
typedef不是简单的文本替换,在对type取别名时会将declarator一起看作是一个整体类型,所以const是修饰这个整体别名而不是将其拆开单独修饰。
2. auto Type Specifier
autotells the compiler to deduce the type from theinitializer。
因为auto从它的initializer推断数据类型,所以这要求使用auto定义变量的时候必须初始化(有initializer)。在一个declaration中也可以使用auto定义多个变量,但这要求所有的变量的initializer必须是同一种type。换句话说,在一个declaration中,auto只能被推断成一种type。例如:
auto i = 0, *p = &i; // ok: i is int and p is a pointer to int
auto i = 42, f = 3.14;
// error: "auto" type is "double" for `f` entity, but was previously implied to be "int"
对于复合类型(compound types)和const类型的推断,auto有可能被推断为与initializer不一致的类型。因为compiler会按照常规的初始化规则来调整推断的类型:
auto对于引用类型会被推断为其所绑定的object的类型。auto对于const会忽略掉top-level const,即自身const的initializer会被推断为普通的base type。
例如:
int i = 42;
const int ci = i, &cri = ci;
auto b = ci; // b is an int, top-level const in ci is dropped
auto c = cri; // cri is an int, top-level const in ci(bound object) is dropped
auto d = &i; // d is an int* (a pointer)
auto e = &ci; // e is an const int* (a const pointer), low-level const is kept
/* specify top-level const or reference explicitly */
const auto f = ci; // f is a const int
auto &g = ci; // g is a const int &(top-level is kept)
上例中需要注意的是当我们明确要求auto推断为引用类型时,top-level const是不会被忽略的。也就是说g是const int &,因为其绑定的对象ci是top-level const类型。另外,关于const reference,其指的是不能通过reference去修改被绑定的值,而不是说被绑定的object是const类型,具体可以看here。
3. decltype Type Specifier
因为auto只能从initializer中推断类型,但是有时候我们希望compiler能够为我们从一个非initializer的表达式中推断变量类型。所以C++11提供了一个新的关键字decltype用来从expression中推断类型,但是不执行这个表达式。例如:
decltype(f()) sum = x; // sum has whatever type f returns
sum的类型将会是f()的返回值的类型。特别需要注意此时并不会执行f(),只是从其返回值的类型推断类型。decltype关键字并不会忽略top-level const和reference,例如:
const int ci = 42, &rci = ci;
decltype(ci) x = 0; // x is const int, top-level is kept
decltype(rci) y = x; // y is const int &, is bound to x
decltype(rci) z; // error: z is const int &, must be initialized
Tips:
在当前C++11标准中,
reference只有在decltype的context中被当做是reference本身,其余情况下一个引用都会被当做是其所引用的对象的别名来对待。
当用一个expression让decltype进行类型推断时,其有可能推断出表达式结果的类型,也有可能推断出一个reference类型。一般来说,如果expression产生的结果能够放在赋值操作的左边,那么decltype将得到一个引用类型。例如:
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // b is an int because expression yields an int result
decltype(*p) c;
// error: c is int & and must be initilized, because derefence p get an object can be assigned
对于b,虽然r是一个引用,但是这是一个expression (r+0),最终得到的结果是(42+0)即42,是一个int类型,所以此时b被推断为int类型。因为在decltype语境中引用并不等于被绑定的object的别名,所以如果我们想要通过decltype得到一个引用所绑定的对象的类型,我们可以将其作为表达式(r+0)来推断类型。
对于*p,因为p指针dereference的结果为i,是一个可以被赋值的object(即可以出现在赋值操作=左边的object),所以此时decltype推断得到的结果是一个引用,即c是一个int &类型。
另外,我们也可以强制decltype产生一个reference类型的推断:
decltype((i)) d; // error: d is int & and must be initialized
decltype(i) e; // ok: e is an int
int a, b = 2;
decltype(a = b) f; // error: f is int & and must be initialized
因为对于变量i,如果我们用括号将其括起来即(i),那么compiler会将其视为expression,即这个expression (i)产生的结果是一个可以被赋值的object(变量i可以被赋值),所以decltype会将其推断为引用类型。同理,在赋值表达式a=b中产生的结果为a,是一个可以被赋值的object,所以f是一个引用类型。但是一定要注意此时a=b的操作并没有被执行,即a还是原来的值而不是b的值。
Conclusions:
auto从initializer中推断数据类型,decltype从非initializer的表达式中推断数据类型。auto会自动忽略top-level const,decltype不会。auto会将引用类型推断为其绑定对象的类型,decltype会将引用类型推断为引用类型(想推断为其绑定对象类型可以用表达式(r+0))。decltype推断为引用类型还是expression结果本身的类型取决于这个expression的结果是不是可以被赋值的对象,且必须记住这个表达式实际上没有被执行。auto对于array会推断为pointer类型;decltype对于array会推断为array类型。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 gzrjzcx@qq.com
文章标题:C++Primer 2.5: Dealing with Types
文章字数:2.3k
本文作者:Alex Zou
发布时间:2019-11-23, 12:57:03
最后更新:2024-07-10, 03:02:36
原始链接:https://www.hellscript.cc/2019/11/23/subposts_cppPrimer/CPN-2-5-Dealing-with-Types/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。