::max() << std::endl;
std::cout << "Minimum short value: " << std::numeric_limits::min() << std::endl; 理解类型声明
简单的类型声明很容易理解:
int i
然而,有时也会有十分复杂的类型声明:
double **d[8] //hmm...
char *(*(**foo [][8])())[] //augh! foo到底是什么?
为了理解上面的声明,可以遵循下面的三个规则:
1.从变量名开始(在上例中就是d或者foo)
2.以数据类型结尾(double或者char)
3.如果可以的话向右,必须的话则向左(括号可能会让你改变方向)
例如:
表达式 意义
double **d[8];
double **d[8]; d 是 … double
double **d[8]; d 是 8维数组 … double
double **d[8]; d 是 8维数组且成员为 指针且指向 … double
double **d[8]; d 是 8维数组且成员为 指针且指向 指针且指向 double
另外一个例子:
表达式 意义
char *(*(**foo [][8])())[]
char *(*(**foo [][8])())[] foo 是 … char
char *(*(**foo [][8])())[] foo 是 一个数组 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 数组 … char
char *(*(**foo [][8])())[] foo 是 一个数组且成员为 8维数组且成员为 指针且指向 指针且指向 函数且返回 指针且指向 数组且成员为 指针且指向 char
译注:
我们也可以采用另外一种方法就是层层类型定义的方法来解读,不过是从外向内的.比如例子1:
typedef double * pDouble;
typedef pDouble* ppDouble;
typedef ppDouble a8ppDouble[8];
a8ppDouble d;
第二个例子:
typedef char * pchar;
typedef pchar apchar[];
typedef apchar * papchar;
typedef papchar fpapchar();
typedef papchar (*pfpapchar)();
typedef pfpapchar * ppfpapchar;
typedef ppfpapchar a8ppfpapchar[8];
typedef a8ppfpapchar aa8ppfpapchar[];
typedef aa8ppfpapchar foo;
附文:
下面是对Steve Friedl的参考文章的部分译文,因为这里也同样的是解说这两个例子,不过对于规则上述说更详细。
基本和派生类型
基本类型就是上边提到的那些基本类型,这里还包括了struct,enum和union(译注:因为这个说明是针对c语言的,所以对于c++来说还要加上一个class类型)
一个声明只能定义一种基本类型,并且它总是出现在声明式的最左边。基本类型可以被派生类型扩张或者说是修饰,并且c有三种派生类型:
* 指针且指向(pointer to...)
这里*表明这是一个指针,而且不言自明地它要指向某个东西
[] 数组且成员为(array of...)
数组可以是已定维数的--[10]--也可以是未定维数的--[],但是维数在我们理解声明具体含义的时候不是那么重要。典型地我们会把维数包含在声明里。这样可以明了的说“数组且成员为”某些东西。
() 函数且返回(function returning...)
函数通常是由一对圆括号来表明的--尽管可能在里面发现参数类型。参数列表其实在我们理解声明的时候是不重要的,可以忽略。应该注意到的是函数的括号和分组改变优先级的括号是不一样的:分组括号总是包围变量而函数括号却总是在变量名(即函数名)的右边。
如果函数不返回值则它是无意义的(我们可以使用void来表示返回为空)。
一个派生类型总是修饰一个基本变量或者另外一个派生变量。我们在理解声明的时候一定要包括介词(to,of,returning)。说“指针”而不是“指针且指向...”会导致你的声明分离。
操作符的优先级
几乎每一个c程序员都很熟悉操作符的优先级表。比如乘除的优先级要高于加减,但是括号可以改变这个优先级。这个对于通常的表达式很自然,但是这个规则也可以运用到声明语句中--它们是类型表达式而不是计算表达式。
数组符号[]和函数符号()比指针*有更高的优先级,这样我们可以推导出一些规则(这个规则和上边所提的是一样的,这里不再啰嗦,离子的推衍也不再重复)。
抽象声明
C标准也支持抽象声明。它可以用在需要类型描述但是又不需要实际变量的情况下--cast,抛掷,和作为sizeof的参数。比如:
int (*(*)())()
对于上边的问题--从哪里开始,答案是找到一个变量名可以放得下的地方,然后把它当成一个通常的声明。只有一个地方可以放置这个变量名并且找到它也是直截了当的。遵循以下语法规则:
- 在*的右边
- 在[]的左边
- 在函数操作符()的左边
- 在分组括号()的内部
由上面的例子我们可以看到有两个地方符合前三条规则:
int (*(* • ) • ())()
但是如果考虑则四条,则只有左边的位置符合了。
int (*(*foo)())()
它表示:
foo是 指针且指向 函数且返回 指针且指向 函数且返回 int
语义限制
注意不是所有的派生组合都被允许的.这样就可能声明一个合乎语法却没有语义内涵的东西来:
--不能有 数组且成员为函数
使用 数组且成员为 指针且指向 函数且返回.... (译注:但是我们知道函数的地址就是函数本身,所以可以说 数组且成员为 函数指针且返回)
--函数不能返回函数
同样使用函数指针代替
--函数不能返回数组
可以使用 函数且返回 指针且指向 数组且成员为...(译注:同样地我们知道数组名同时也是数组首元素的地址,还是该数组的地址)
--数组中只有最左的[]可以是未定义的
--void是限制类型,它只有在和指针*以及函数()在一起时才有效.