C++Primer 2.5: Dealing with Types
Dealing with Types
随着我们的程序变得越来越复杂,各种types的名字变得越来越复杂。有些types变得越来越难拼写,有些types的含义变得越来越难以理解(或歧义)。因此,我们需要一些方法来让简化types的名字。
1. Type Aliases
A
type alias
is 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 * p
p
实际上是一个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
auto
tells 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" 转载请保留原文链接及作者。