第3章 说明和类型
本章描述变量、函数和类型的说明与初始化。C语言包括一个标准数据类型的标准集。
你还可以增加自己的数据类型,称为“派生的类型”,即通过已经定义的类型说明新的类型。
讨论的主题如下:
*说明概述
*存储类
*指示符
*类型修饰符
*说明符和变量说明
*解释复杂说明
*初始化
*基本类型的存储
*不完整的类型
*类型定义说明
*扩展存储类属性
--------------------------------------------------------------------------------返回顶端
说明概述
一个“说明“指出一组标识符的解释和属性。
导致为标识符的对象或函数保留存储的说明称之为“定义”。变量、函数和类型的C说明具有这样的语法:
语法
说明:
说明指示符 初始化说明符表opt
说明指示符:
存储类指示符 属性序列opt
说明指示符opt
/*属性序列opt是Microsoft特殊处*/
类型指示符 说明指示符opt
类型修饰符 说明指示符opt
初始化说明符表:
初始化说明符
初始化说明符表
初始化说明符
初始化说明符:
说明符
说明符=初始化器
注意:说明的语法在后面不再重复。后面使用的语句通常以说明符非终结符开头。
初始化说明符表中的说明包含命名的标识符;初始化是初始化器的简称。初始化说明符表是用逗号分隔的说明符序列,每个都有附加类型信息或一个初始化器或两者都有。说明符包含任何说明的标识符。说明指示符非终结符由类型序列和指出连接、存储期的存储类指示符以及说明符表示的实体类型最小的部分组成。因此,说明由存储类指示符、类型指示符、类型修饰符、说明符和初始化器的某种组合组成。
说明可以包含属性序列中列出的一个或多个任选属性。这些Microsoft特定属性实现函数的变型,这在本书中详细讨论,对于属性列表,参见本卷后面的附录A“C语言语法总结”。在一个变量说明的一般格式中,类型指示符给出该变量的数据类型。类型指示符可以是复合的,可以用const或volatile修饰。说明符给出了变量名称,说明一个数组或指针类型的修饰。例如:
int const *fp;
说明一个名称为fp的变量为不能修改的(const)int值的指针。你可以使用多个以逗号分隔的说明符在一个说明中定义一个以上的变量。
一个说明必须至少有一个说明符,或者它的类型指示符必须说明一个结构标志、联合标志或一个枚举器的成员。一个说明符是一个这样的标识符,可以分别用方括号([])、星号(*)或圆括号(())修饰来说明一个数组、指针或函数。当你说明简单变量(例如字符、整数和浮点项)或一些简单变量的结构和联合时,该说明符也是一个标识符。有关更多信息,参见本章后面的“说明符和变量说明”。
所有定义都隐含地说明,但不是所有说明都是定义。例如,以extern存储类指示符开头的说明是“引用”,而不是“定义”说明。如果外部变量在其定义之前被引用,或者如果它定义在另一个使用的源文件中,必须使用一个extern说明。“引用”说明不分配存储,在说明中变量也不能初始化。
在变量说明中需要一个存储类或一个类型(或两者都需要)。除了__declspec外,在一个说明中只允许有一个存储类指标符,不是所有的存储类指示符都允许在每个上下文中。
__declspec存储类允许与其它存储类指示符一起使用,允许使用多次。一个说明的存储类指示符影响被说明的项是如何存储和初始化的、以及程序的哪部分可以引用该项。
定义在C中的存储类指示符终结符包括auto、extern、register、static和typedef。另外,Microsoft
C包括存储类指示符终结符_ _declspec。所有除typedef和_ _declspec外的存储类指示符终结符都在“存储类”中讨论。有关typedef的信息参见本章后面的“typedef说明”。有关_
_declspec的信息参见“扩充的存储类属性”。
在一个源程序中说明的位置以及该变量是否存在其它说明对于确定变量的生存期是重要的因素。可以有多个重新说明,但只有一个定义。但一个定义可以出现在多个转换单元中。对于内部连接的对象,这个规则分别应用于每个转换单元,因为内部连接的对象对一个转换单元来说是唯一的。对于外部连接的对象,这个规则应用于整个程序。有关可见性的更多信息,参见第2章的“生存期、范围、可见性和连接”。
类型指示符提供了一些标识符数据类型的信息,缺省类型指示符是int。有关更多信息,参见本章后面的“类型指示符”。类型指示符也可以定义类型标志、结构和联合组成成分名称以及枚举常量。有关更多信息,参见本章后面的“枚举说明”、“结构说明”和“联合说明”。
有两个类型修饰符终结符:const和volatile。这些修饰符指出另外的类型特性,它们仅与访问通过l值确定类型的对象有关。有关const和volatile的更多信息,参见本章后面的“类型修饰符”。有关l值的定义,参见第4章“表达式和赋值”中的“L值和R值表达式”。
--------------------------------------------------------------------------------返回顶端
存储类
一个变量的“存储类”确定该项是“全局的”生存期还是“局部的”生存期。C调用这两个生存期:“static”和“automatic”。一个具有全局生存期的项在整个程序执行中都存在并有一个值。所有函数都有全局生存期。
自动变量或具有局部生存期的变量,在每次把控制传给定义它们的块时都分配新的存储。当执行返回时,该变量不再具有有意义的值。
C提供如下存储类指示符:
语法
存储类指示符:
auto
register
static
extern
typedef
_ _declspec(扩充的说明修饰符序列)/*Microsoft特定的*/
除了__declspec外,你在一个说明的说明指示符中只能使用一个存储类指示符。如果没有存储类指示符,在一个块中的说明建立自动对象。
用auto或register指示符说明的项具有局部生存期。用static或extern指示符说明的项具有全局生存期。
因为typedef和_ _declspec语义上不同于其它4个存储类指示符终结符,把它们分开进行讨论。有关typedef的特定信息,参见“typedef说明”。
有关__declspec的特定信息,参见“扩充存储类属性”。
变量和函数说明在源文件中的位置也影响存储类和可见性。所有函数定义外面的说明被说成出现在“外部层”。函数定义内部的说明出现在“内部层”。
每个存储类指示符的精确含义依赖于两个因素:
该说明出现在外部层还是内部层。该项是说明成变量还是函数。下面两节“外部层说明的存储类指示符”和“内部层说明的存储类指示符”描述了每种说明中的存储类指示符终结符并解释了从变量中忽略存储类指示符的缺省行为。“函数说明的存储类指示符”讨论了在函数中使用存储类指示符。
外部层说明的存储类指示符
外部变量是文件范围的变量,它们定义在任何函数的外面,对于很多函数是可用的。函数只能在外部层定义,因此,不能嵌套。缺省地,所有同名称的外部变量和函数的引用指向同一对象,这意味着它们具有“外部连接”(你可以使用static关键词覆盖它,有关static的详细情况参见本章后面的信息)。
在外部层的变量说明是变量定义(“定义说明”)或其它地方定义的变量的引用(“引用说明”)。
一个初始化变量的外部变量说明(隐含或显式地)是该变量的一个定义说明。在外部层的定义可以有几种格式:
*你用static存储类指示符说明的变量。你可以用一个常量表达式显式地初始化该static变量,正如“初始化”中描述的。如果你省略该初始化器,该变量缺省初始化为0,例如,这里有两个定义变量k的语句。
static int k=16
static int k;
*一个你在外部层显式初始化的变量。例如,int j=3;是变量j的一个定义。
在外部层说明变量时(也就是在所有函数的外面),你可以使用static或extern存储类指示符或整个地省略该存储类指示符。你不能在外部层使用auto和register存储类指示符终结符。
一旦一个变量在外部层定义了,它在余下的转换单元中是可见的。该变量在同一个源文件中其说明的前面是不可见的。在该程序的其它源文件中也是不可见的,除非有一个引用说明使它成为可见的,正如下面说明的。
static的相关规则包括:
*所有块外面的以不带static关键字方式说明的变量总是在整个程序中保留它们的值。为了限制它们对特定转换单元的访问,你必须使用static关键字,这给它们“内部连接”。为了使它们对整个程序而言是全局的,省略显式存储类或使用关键字extern(参见下面表中的规则),这给它们“外部连接”。内部和外部连接在第2章“程序结构”中的“连接”中讨论。
*你可以在一个程序中的外部层一个变量仅定义一次,可以在不同转换单元中用相同的名称和static存储类指示符定义另外的变量。由于每个static定义仅在它自己的转换单元中可见,因此不会出现冲突。这提供了这样的标识符名称的有用方式,该标识符在一个转换单元的函数中实现共享,但对其它转换单元是不可见的。
* static存储类指示符也可以应用于函数。如果说明一个函数为static,它的名称在被说明的文件之外是不可见的。
使用extern的规则是:
* extern存储类指示符说明一个其它地方说明的变量的引用,你可以使用一个extern说明使其它源文件中的定义是可见的,或者使一个变量在相同源文件中其定义的前面是可见的。一旦你在外部层说明了一个变量的引用,该变量在说明引用出现的转换单元的余下部分都是可见的。
*对于一个有效的extern引用,所指的变量必须定义一次,而且在外部层仅一次。这个定义 (没有extern存储类)可以在任何构成程序的转换单元中。
例子
如下例子说明了外部说明:
/************************************************
SOURCE FILE ONE
*************************************************/
extern int i; /*引用i,定义在下面*/
void next(void); /*函数原型*/
void main()
{
i++;
printf("%d\n",i); /*i等于4*/.
next();
}
int i=3; /*i的定义*/
void next(void)
{
i++;
printf("%d\n",i); /*i等于5*/
other();
}
/************************************************
SOURCE FILE TWO
*************************************************/
extern int i; /*引用i,定义在下面*/
/*i在第一个源文件中*/
void other(void)
{
i++;
printf("%d\n",i); /*i等于6*/
}
这个例子中的两个源文件包含i的三个外部说明。只有一项是“定义说明”,它为:
int i=3;
定义全局变量i并用初始值3对其进行初始化。在第一个源文件的顶部i的“引用”说明使用extern使该全局变量在这个文件中它的定义说明之前是可见的。在第二个源文件中i的引用说明使它在这个源文件中是可见的。如果一个变量的定义实例在转换单元中没有提供,编译器假设有一个:
extern int x;
引用说明和一个定义引用:
int x=0;
出现在该程序的另外转换单元中。
所有三个函数main、next和other执行同样的功能;它们将i增1,并打印它。打印值4、5和6。
如果变量i还没有初始化,它自动设置为0。在这种情况下,打印值1、2和3。有关变量初始化的信息参见本章后面的“初始化”。
内部层说明的存储类指示符
你可以在内部层为变量说明使用任何四种存储类指示符终结符。当你从这样的说明省略存储类指示符时,缺省的存储类是auto。因此在一个C程序中很少看见auto关键字。
auto存储类指示符
auto存储类指示符说明一个自动变量,即一个具有局部生存期的变量。一个auto变量仅在说明它的块中是可见的。说明auto变量包括初始化语句,正如“初始化”节中讨论的。由于具有auto存储类的变量不会自动初始化,你可以在说明它们时显式对其初始化或者在该块的语句中赋给初始值。未初始化的auto变量的值是不确定的(对于auto或register存储类的局部变量,如果给出一个初始化语句,那么每次进入其范围内都进行初始化)。
一个内部static变量(一个带有局部或块范围的static)可以被带有任何外部的或static项的地址初始化,而不能被带有其他auto项的地址初始化,因为auto项地址不是一个常量。
register存储类指示符
Microsoft特殊处
Microsoft C/C++编译器并不赞成寄存器变量的用户要求。但为了兼容性,与register关键字关联的所有其它语义都被编译器允准。例如,你不能向一个寄存器对象应用单目地址运算符(&),也不能对数组使用register关键字。
Microsoft特殊处结束
static存储类指示符
在内部层用static存储类指示符说明的变量具有全局生存期,但仅在其说明的块中是可见的,对于常量字符串,使用static是有用的,因为它减小了在经常调用的函数频繁的初始化的额外开销。
如果你不显式初始化一个static变量,它缺省初始化为0,在一个函数内部,static导致分配存储并作为一个定义,内部静态变量提供的私有、持久存储仅对单个函数是可见的。
extern存储类指示符
用extern存储类指示符说明的变量是定义在外部层该程序的任何源文件中同名变量的一个引用。内部extern说明用于使外部层变量定义在该块中是可见的。除非在外部层有其它说明,否则用extern关键字说明的一个变量仅在其说明的块是中可见的。
例子
这个例子说明了内部和外部层说明:
#include
int i=1;
void other(void);
void main()
{
/* 引用i,定义在上面 */
extern int i;
/*初始化值是0,a仅在main中可见*/
static int a;
/* 如果可以,b存储在一个寄存器中, */
register int b=0;
/* 缺省存储类是auto */
int c=0;
/*打印的值是1,0,0,0*/
printf("%d\n%d\n%d\n%d\n",i,a,b,c);
other();
return;
}
void other(void)
{
/* 全局i的地址赋给指针变量 */
static int *external_i=&i;
/* i重新定义,全局i不再可见 */
int i=16;
/* a仅在other函数内可见 */
static int a=2;
a +=2;
/* 打印的值是16,4,1 */
printf("%d\n%d\n%d\n",i,a,*external_i);
}
本例中,变量i的定义在外部层,并有初值1,在main函数中的一个extern说明用来说明该外部层i的一个引用。static变量a缺省为0,因为省略了初始化语句。调用printf打印值1、0、0和0。
在other函数中,全局变量i的地址用来初始化该static指针变量external_i。这个工作因为全局变量有static生存期,其地址在程序执行中不改变。接着,变量i重新定义为局部变量并有初始16。这个重新定义不影响外部层i的值,它通过使用它的局部变量的名称而隐藏。全局变量i的值现在仅在这个块中通过指针external_i间接访问。试图把auto变量i的地址赋给一个指针是不行的,由于它可能在每次进行该块时都不相同。变量a作为一个static变量说明并初始化为2。这不会与main中的a冲突,因为在内部层的static变量仅在它们说明的块中是可见的。
变量a增大2,给出4作为结果。如果other函数在同一程序中再次调用,a的初始值为4。内部static变量在程序退出,然后重新进入说明它们的块中时保存它们的值。
函数说明的存储类指示符
你可以在函数说明中使用static或extern存储类指示符。函数总是有全局生存期。
Microsoft特殊处
在内部层的函数说明和外部层的函数说明具有相同的含义。这意味着一个函数从说明的点到该转换单元的其余部分中都是可见的,甚至在局部范围中说明亦如此。
Microsoft特殊处结束
函数的可见性规则有点不同于变量的可见性规则,如下:
*说明为static的一个函数仅在定义它的源文件中是可见的。在相同源文件中的函数可以调用static函数,但在其它源文件中不能用其名称直接访问。你可以用相同的名称在不同的源文件中没有冲突地说明另一个static函数。
*以extern说明的函数在该程序的所有源文件中都是可见的(除非你以后重新说明这样的一个函数为static)。任何函数可以调用一个extern函数。
*省略存储类指示符的函数说明缺省是extern的。
Microsoft特殊处
Microsoft允许重新定义一个extern标识符为static。
Microsoft特殊处结束
--------------------------------------------------------------------------------返回顶端
指示符
在说明中类型指示符定义一个变量或函数说明的类型。
语法
类型指示符:
void
char
short
int
long
float
double
signed
unsigned
结构或联合指示符
枚举指示符
typedef名称
signed char、signed int、signed short int和signed long int类型与它们的unsigned对应部分以及enum都称之为“整型”。float、double和long
double类型指示符被称为“浮点型”。你可以在一个变量或函数说明中使用任何整型或浮点型。如果在一个说明中没有提供一个类型指示符,把它作为int。
任选关键字signed和unsigned可以位于或紧跟任何整型,除enum之外,都可以单独作为类型指示符,这种情况下,它们分别作为signed
int和unsigned int。当单独使用时,关键字int假设为signed。当单独使用时,关键字long和short作为long
int和short int。
枚举类型考虑为基本类型。枚举类型的类型指示符在本章后面的“枚举说明”中讨论。
关键字void有三种用途:指出一个函数返回值、指出一个不接受参量的函数的参量类型表以及指出一个非指定的类型的指针。你可以使用void类型说明没有返回值的函数或说明一个未指定类型的指针。有关void出现在一个函数名称之后的括号中的信息,参见第6章“函数”中的“参量”。
Microsoft特殊处
现在的类型检测是ANSI兼容的,这意味着类型short和int是不同的类型。例如,在Microsoft C编译器中的重新定义被以前的编译器版本接受。
int myfunc();
short myfunc();
下面的例子还产生一个关于不同类型之间间接赋值的警告:
int *pi;
short *ps;
ps=pi; /*现在产生警告*/
Microsoft C编译器也产生关于不同符号的警告,例如:
signed int *pi;
unsigned int *pu;
pi=pu; /*现在产生警告*/
类型void表达式求值是为了副作用。你不能以任何方式使用(不存在的)一个有类型void的表达式的值,也不能把一个void表达式(隐含或显式转换)转换成除void之外的任何类型。
如果你在需要一个void表达式的上下文中使用一个其它类型的表达式,它的值被丢弃。
为了与ANSI规格一致,void **不能用作int **。只有void *可以用作一个未指定类型的一个指针。
Microsoft特殊处结束
你可以用typedef说明建立另外的类型指示符,正如在本章后面“typedef说明”中描述的有关每种类型的尺寸的信息。
数据类型指示符和等价者
本书通常使用表3.1中列出的类型指示符的格式,而不是长格式,它假设char类型缺省是有符号的。因此,在本书中,char等价于signed
char。
表3.1 类型指示符和等价者
| 类型指示符 |
等价者 |
| signed char1 |
char |
| signed int |
signed, int |
| signed short int |
short, signed short |
| signed long int |
long, signed long |
| unsigned char |
- |
| unsigned int |
unsigned |
| unsigned short int |
unsigned short |
| unsigned long int |
unsigned long |
| float |
- |
| long double2 |
- |
1. 当你使char类型缺省为无符号的(通过指定/J编译器选项),你不能简写signed char为char。
2. 在32位操作系统中,Microsoft C编译器把long double映射成double。
Microsoft特殊处
你可以指定/J编译器选项改变缺省char类型从有符号的到无符号的。当这个任选项起作用时,char与unsigned char相同,你必须使用signed关键字说明一个有符号字符值。如果一个char值显式说明为有符号的,/J选项对它没影响,当宽度变为int类型时其值进行符号扩充。当宽度变为ing类型时,char类型进行0扩充。
Microsoft特殊处结束
--------------------------------------------------------------------------------返回顶端
类型修饰符
类型修饰符给一个标识符两种属性之一。const类型修饰符说明一个对象是不可修改的。voatile类型说明一个项,其值在有时超出它出现的程序的范围例如并发执行线程时可以改变。
两个类型修饰符const和volatile在一个说明中仅出现一个。类型修饰符可以与任何类型指示符一起使用,但它们不能出现在多项说明中的第一个逗号之后。
例如,以下说明是合法的:
typedef volatile int VI;
const int ci;
以下说明不是合法的:
typedef int *i, volatile *vi;
float f, const cf;
类型修饰符仅当在表达式中作为l值访问标识符时是相关的。有关l值和表达式的信息参见第4章“表达式和赋值”的“L值和R值表达式”。
语法
类型修饰符:
const
volatile
如下是合法的const和volatile说明:
int const *p_ci; /* 常量int的指针*/
int const (*p_ci), /* 常量int的指针*/
int *const cp_i; /* int的常量指针*/
int (*const cp_i); /* int的常量指针*/
int volatile vint; /* volatile整数*/
如果一个数组类型的规格包括类型修饰符,被修饰是其元素,而不是数组类型。如果一个函数类型的规格包括修饰符,其行为是不确定的。volatile和const都不影响对象的值的范围或算术属性。
这里列出如何使用const和volatile:
* const关键字可以用于修饰任何基本的或集合类型,或者任何类型的对象的指针或者一个typedef。如果一个项仅用const类型修饰符说明,它的类型作为const
int。一个const变量可以初始化或放在一个只读存储区域中。const关键字对于说明const的指针是有用的,因为这需要该函数不以任何方式改变该指针。
*编译器假设在程序的任何地方,一个volatile变量可以通过使用或修改它的值的未知进程来访问。因此,不管命令行指定的优化,必须生成每个赋值的代码或一个volatile变量引用的代码,甚至它的出现没有作用亦如此。
如果单独使用volatile,假设是int。volatile类型指示符用于提供对特定存储器位置可靠的访问。对于可以通过信号处理器,通过并发执行程序或通过特定硬件例如存储器映射的I/O控制寄存器来访问或改变的数据对象使用volatile。你可以为生存期声明一个变量为volatile,或者造型单个引用为volatile。
*一个项可以是const和volatile,在这种情况下该项不会由它自己的程序合理修改,但可以由一些异步进程修改。
--------------------------------------------------------------------------------返回顶端
说明符和变量说明
本章其余部分描述了变量类型的格式和含义,其总结在如下表中。特别地,下面的几节解释如何说明如下内容。
变量类型 说明
简单变量 具有整型或浮点型的单个值变量
数组 由相同类型的元素集合组成的变量
指针 指向其它变量的变量,包含变量的位置,以地址格式存储代替值
枚举变量 具有整型的简单变量,保存一个命名的整数常量集合中的一个值
结构 由具有不同类型的值的集合组成的变量
联合 由同一存储空间中几个不同类型的值组成的变量
一个说明符是指出引入到程序中的名称的一个说明,它可以包括修饰符,例如*(指针)和任何Microsoft调用约定的关键字。
Microsoft特殊处
在说明符中:
__declspec(thread) char *var;
char是类型指示符,_ _declspec和*是修饰符,var是标识符的名称。
Microsoft特殊处结束
你可以使用说明符说明指定类型值的数组、指定类型值的指针和指定类型返回值的函数。出现在数组和指针说明中的说明符在本章后面描述。
语法
说明符:
指针opt直接说明符
直接说明符:
标识符
(说明符)
直接说明符[常量表达式opt]
直接说明符(参数类型表)
直接说明符(标识符表opt)
指针:
*类型修饰符表opt
*类型修饰符表opt指针
类型修饰符表:
类型修饰符
类型修饰符表 类型修饰符
注意:有关一个说明符的引用语法,参见本章开头的“说明概述”中的说明的语法或参见本卷后面的附录A“C语言语法总结”。
当一个说明符由一个非修饰符的标识符组成时,说明的项有一个基本类型。如果一个星号(*)出现在一个标识符的左边,该类型修改为一个指针类型。如果该标识符紧跟方括号[],该类型修改为一个数组类型。有关说明中优先级解释的更多信息,参见本章后面的“解释更复杂的说明符”。每个说明符至少说明一个标识符。一个说明符必须包括一个完整说明的类型指示符。该类型指示符给出了一个数组类型的元素的类型、指针类型所指对象的类型或者一个函数的返回值。
数组和指针说明在本章后面有更详细的讨论。如下例子说明几种简单的说明符格式:
int list[20] /*说明一个名称为list,20个整数的数组*/
char *cp; /*说明一个char值的指针*/
double func(void) /*说明一个名称为func的函数,没有参量,返回值为一个double值*/
int *aptr[10]; /*说明一个有10个指针的数组*/
Microsoft特殊处
Microsoft C编译器不限制修饰一个算术、结构或联合类型的说明符的个数。该个数仅受限于可用的存储器空间。
Microsoft特殊处结束
简单变量说明
简单变量的说明是直接说明符的最简单的格式,它指出变量的名称和类型,它还指出该变量的存储类和数据类型。
在变量说明中需要存储类或类型(或两者),没有类型的变量(例如var;)产生警告。
语法
说明符:
指针opt 直接说明符
直接说明符:
标识符
标识符:
非数字
标识符 非数字
标识符 数字
对于算术、结构、联合、枚举和void类型以及由typedef命名表示的类型,在一个说明中可以使用简单说明符,因为该类型指示符提供所有类型信息。指针、数组和函数类型需要更复杂的说明符。
你可以使用一个标识符表,标识符之间用逗号分隔,以便在同一说明中指出几个变量。在该说明中定义所有变量具有相同的基本类型。例如:
int x,y; /*说明两个int类型的简单变量*/
int const z=1; /*说明类型为int的变量值*/
变量x和y为了一个特殊实现保存int类型定义的集中的任何值。简单对象z初始化为1,并且是不可修改的。如果z的说明是一个非初始化的静态变量或具有文件范围,它接受一个初始值0并也是不可修改的。unsigned
long reply,flag; /*说明两个命名为reply和flag的变量*/
在这个例子中,两个变量reply和flag都有unsigned long类型并保存无符号整数值。
枚举说明
一个枚举是由一个命名的整数常量组成的。一个枚举类型说明给出了枚举标志的名称(任选的)并定义命名的整数标识符的集合(称为“枚举集”、“枚举常量”、“枚举器”或“成员”)。一个具有枚举类型的常量存储该类型定义的枚举集的值之一。
enum类型的变量可以用在指标表达式中,作为所有算术和关系运算符的操作数,枚举提供了替代#define预处理器命令的另一种方法,利用它为你生成值和遵守正常范围规则的优点。
在ANSI C中,定义一个枚举常量的值的表达式总是具有int类型,因此与一个枚举变量关联的存储是单个int值需要的存储。一个枚举常量或一个枚举类型的值可以用在C语言允许一个整数表达式使用的任何地方。
语法
enum指示符:
enum标识符opt{枚举器表}
enum标识符
可选标识符命名由枚举器表定义的枚举类型。这个标识符经常称为该表指定的枚举的“标志”。一个类型指示符的格式为:
enum标识符{枚举器表}
说明该标识符是由枚举器表指定的枚举的标志。该枚举器表定义了该“枚举器内容”。该枚举器表在下面详细描述。
如果一个标志的说明是可见的,后续使用该标志而省略枚举器表的说明指出以前说明的枚举的类型。该标志必须指的是一个定义的枚举类型,该枚举类型必须在当前范围中。因为枚举类型定义在其它地方,该枚举器表不出现在这个说明中。从枚举派生的类型的说明和枚举类型的typedef说明可以在该枚举类型定义之前使用该枚举标志。
语法
枚举器表:
枚举器
枚举器表,枚举器
枚举器:
枚举常量
枚举常量=常量表达式枚举常量:
标识符
在枚举器表中的每个枚举器常量命名该枚举集的值。缺省地,第一个枚举常量与值0关联,该表中的下一个枚举常量与值(常量表达式+1)的值关联,除非你显式地将其与其它值关联。一个枚举常量的名称等价于它的值。
你可以使用枚举常量=常量表达式来覆盖值的缺省序列。因此,如果枚举常量=常量表达式出现在该枚举器表中,该枚举常量与常量表达式给定的值关联。该常量表达式必须有int类型且可以为负数。
如下规则应用于一个枚举集的成员:
*一个枚举集可以包含重复的常量值。例如,你可以用两个不同的标识符与0关联,它们是同一个集中,可能是mull和zero。
*枚举集中的标识符必须不同于同一范围中具有相同可见性的其它标识符,包括普通变量名称和其它枚举表中的标识符。
*枚举标志遵守一般范围规则,它们必须不同于相同可见性的其它枚举、结构和联合标志。
例子
这些例子说明了枚举的说明:
enum DAY /* 定义一个枚举类型 */
{
saturday,/*命名day并说明具有该类型的变量workday */
sunday=0,
monday,
tuesday,
wednesday, /*wednesday与3关联 */
thursday,
friday
} workday;
缺省地值0与saturday关联。标识符sunday显式设置为0。余下的标识符根据缺省给出1到5的值。在以下例子中,一个集DAY的值赋给变量today:
enum DAY today=wednesday;
注意,枚举常量的名称用作赋值。由于DAY枚举类型是以前说明的,只有枚举标志DAY是必要的。
为了显式地把一个整数值赋给一个枚举的数据类型的变量,使用一个类型造型:
workday=(enum DAY) (day_value-1);
这个造型在C中推荐,但是不需要的。
enum BOOLEAN/*说明一个称为BOOLEAN的枚举数据类型*/
{
false,/*false=0,true=1 */
true
};
enum BOOLEAN end_flag,match_flag; /* BOOLEAN类型的两个变量*/
这个说明也可以指定为:
enum BOOLEAN {false,true} end_flag,
match_flag;\
或
enum BOOLEAN {false,true} end_flag;
enum BOOLEAN match_flag;
使用这三个变量的例子如下:
if (match_flag == false)
{
. . . /*语句*/
}
end_flag = true;
可以说明未命名的枚举器数据类型。省略数据类型的名称,但可以说明变量。变量response是这样定义的类型的变量:
enum {yes, no} response;
结构说明
一个“结构说明”命名一个类型并指定具有不同类型变量值(称为结构的“成员”或域)的序列。一个称为“标志”的任选标识符给出结构类型的名称,可以用在后面对结构类型的引用中,一个结构类型的变量保存该类型定义的整个序列。在C中结构类似于其它语言中众所周知的“记录”。
语法
结构或联合标识符:
结构或联合标识符opt{结构说明表}
结构或联合标识符结构或联合:
struct
union
结构说明表:
结构说明
结构说明表 结构说明
结构的内容定义为:
结构说明:
指示符修饰符表
结构说明符表指示符修饰符表:
类型指示符 指示符修饰符表opt
类型修饰符 指示符修饰符表opt
结构说明表:
结构说明符
结构说明符表,
结构说明符结构
说明符:
说明符一个结构类型的说明不为一个结构设置另外空间,它只是一个模块,便于后面说明结构变量。
前面定义的标识符(标志)可以用于指向其它地方定义的一个结构类型。在这种情况下,结构说明表不能重复直到它的定义是可见的。结构的指针的说明和结构类型的typedef可以在该结构类型被定义之前使用该结构标志。但必须在其任何域的尺寸的实际使用之前遇到该结构定义。这是类型和类型标志的不完整定义。为了完成这个定义,必须在相同范围的后面出现一个类型定义。
结构说明表指出结构成员的类型和名称。一个结构说明表参量包含一个或多个变量或位域说明。
在结构说明表中说明的每个变量定义为该结构类型的一个成员,在结构说明表中的变量说明和本章讨论的其它变量的说明具有相同的格式,除了该说明不能包含存储类指示符或初始化语句外,该结构成员可以具有除void之外的任何类型、一个不完整的类型或一个函数类型。
一个成员不能说明成为具有包含该成员的结构的类型,但一个成员可以说明成为包含该成员的结构的指针,该结构类型有一个标志。这允许你建立结构链表。
结构与其它标识符遵循同样的范围规则。结构标识符必须不同于具有相同可见性的其它结构、联合和枚举标志。
在结构说明表中的每个结构说明在该表中必须是唯一的。但一个结构说明表中的标识符名称不必与其它普通变量名或其它结构说明表中的标识符相区分。
通过在文件范围层说明,还可以访问嵌套的结构。例如,给出这样的说明:struct a
{
int x;
struct b
{
int y
} var2;
} var1;
如下说明都是合法的:
struct a var3;
struct b var4;
例子
这些例子说明了结构的说明:
struct employee/*定义一个命名为temp的结构变量*/
{
char name[20];
int id;
long class;
}temp;
employee结构有三个成员,name、id和class。name成员是一个20个元素的数组;id和class分别是int和long类型的简单成员。标识符employee是结构标识符。
struct employee student, faculty, staff;
这个例子定义了三个结构变量:student、faculty和staff。每个结构都有三个成员的相同表。这些成员被说明为有结构类型employee,定义在前面的例子中:
struct/*定义一个无名的结构和a*/{
float x,y;
} complex; /*结构变量命名为complex*/
complex结构具有两个float类型的成员x和y,该结构类型没有标志,因此是未命名或无名的。struct sample /*定义一个命名为x的结构*/
{
char c;
float *pf;
struct sample *next;
} x;
该结构开头两个成员是一个char变量和一个float值的指针。第三个成员next说明为定义的结构类型(sample)的指针。
在不需要命名的标志是无名结构是有用的。这是一个说明定义所有结构实例的情况。
例如:
struct
{
int x;
int y;
} mystruct;
嵌入的结构通常是无名的。
struct somestruct
{
struct /*无名的结构*/
{
int x,y;
} point;
int type;
} w;
Microsoft特殊处
编译器允许一个无尺寸的或0尺寸的数组作为一个结构的最后成员,如果一个常量数组的尺寸在不同情况下也不同时,这是有用的。这样的一个说明如下:
struct标识符
{
说明集
类型 数组名称[];
};
无尺寸的数组只能作为一个结构的最后成员。包含无尺寸的数组说明的结构可以嵌套在其它结构中,以及在包含它的结构中没有更多成员需要说明。不允许这样结构的数组,当sizeof运算符应用于这种类型的变量或这个类型本身时,该数组的尺寸假设为0。
在该结构是另一个结构或联合的成员时,没有一个说明符也可以指定该结构说明。域名称提升到包括它的结构中。例如,一个较少命令的结构如下:
struct s
{
float y;
struct
{
int a,b,c;
};
char str[10];
} *p_s;
.p_s->b = 100; /*s结构中一个域的引用*/
有关结构引用的信息,参见第4章“表达式和赋值”中的“结构和联合成员”。
Microsoft特殊处结束
位域
除了说明一个结构或联合的成员外,一个结构说明符还可以指定位的个数,称为一个位域,其长度由说明符的域名后的冒号来设置。一个位域解释为一个整型。
语法
结构说明符:
说明符
类型指示符 说明符opt:常量表达式
该常量表达式指出域的位域。说明符的类型指示符必须是unsigned int、signed int或int,且常量表达式必须是非负整数。如果其值为0,该说明不是说明符。不允许有位域的数组、位域的指针和返回位域的函数。任选说明符命名位域。位域只能说明为一个结构的一部分。取地址运算符(&)不能应用于位域的组成成分。
无名的位域不能被引用,它们的内容在运行时是不可预料的。它们为了赋值目的可以用作“哑元”域。一个其宽度指定为0的无名位域保证在该结构说明表中紧跟它的成员从一个int边界开始存储。
位域也必须足够长以便包含位模式,例如,这样的两个语句是非法的:
short a:17, /*非法的!*/
int long y:33/*非法的!*/
如下例子定义一个二维结构数组并命名为screen:
struct
{
unsigned short icon : 8;
unsigned short color : 4;
unsigned short underline : 1;
unsigned short blink : 1;
}
screen[25][80];
该数组包含2000个元素,每个元素是一个包含四个位域成员:icon、color、underline和blink的结构。每个结构的尺寸是两个字节。
位域与整数类型具有相同的语义。这意味着一个表达式中使用一个位域的方式与相同基本类型的变量使用的方式完全相同,而不论在位域中有多少位。
Microsoft特殊处
以int定义的位域处理为有符号的。一个Microsoft扩充ANSI C标准允许位域有char和long类型(signed和unsigned)。具有基本类型long、short或char(signed或unsigned)的无名的位域强制赋给适合于该基本类型的边界。
在一个整数从最低有效数字位到最高有效数字位都分配位域,在以下代码中:
struct mybitfields
{
unsigned short a : 4;
unsigned short b : 5;
unsigned short c : 7;
} test;
void main(void)
{
test.a=2;
test.b=31;
test.c=0;
}
这些位排列如下
00000001 11110010
cccccccb bbbbaaaa
由于8086处理器簇在高位字节之前存储整数的低位字节,上面的整数0x01F2在物理存储器中先存储0xF2,接着是0x01。
Microsoft特殊处结束
结构的存储和赋值
Microsoft特殊处
结构成员以它们说明的顺序存储:第一个成员具有最低的存储器地址,最后的成员具有最高的存储器地址。
每个数据对象有一个对齐要求。对于结构,该要求是它的成员最大的要求值。每个对象分配一个偏移量以便:
偏移量 % 对齐要求 == 0
如果整数类型具有相同的尺寸,且如果下一个位域适合于当前分配单元且通过该位域的公共对齐要求的统一而没有边界交叉,则调整位域为相同的1、2或4字节分配单元。
为了节省空间或使现存数据结构一致,你可以更紧凑或更松散地存储结构。/Zp[n]编译器选项和#pragma pack控制结构数据如何“紧凑”进入存储器。当你使用/Zp[n]选项时,这里n是1、2、4、8或16,第一个结构成员之后的每个结构成员存储一个字节边界,该边界是域的对齐要求或紧凑尺寸(n)。这里是很小的,表示成一个公式,字节边界是:
min(n,sizeof(项))
这里的n是/Zp[n]选项表示的紧凑尺寸,项是结构成员。缺省的紧凑尺寸是/Zp8。为了使用pack编译指示以指出紧凑,该紧凑是非命令行中为一个特定结构所指的紧凑,给出pack编译指示,这里紧凑尺寸在该结构之前取值为1、2、4、8或16。为了在命令行重新安装该紧凑,指出不带参量的pack编译指示。
对于Microsoft C编译器,位域缺省为long尺寸。结构成员在该类型的尺寸或/Zp[n]尺寸上对齐,这里是较小的。缺省尺寸为4。
Microsoft特殊处结束
联合说明
一个“联合说明”指出变量值的集合,它是任选的,一个标志命名该联合。该变量值称为联合的成员,可以有不同类型,联合类似于其它语言中的“可变记录”。
语法
结构或联合指示符:
结构或联合 指示符opt {结构说明表}
结构或联合 标识符结构或联合:
struct
union
结构说明表:
结构说明
结构说明表 结构说明
联合内容定义为:结构说明:
指示符修饰符表 结构说明符表;
指示符修饰符表:
类型指示符 指示符修饰符表op
类型修饰符 指示符修饰符表opt
结构说明符表:
结构说明符
结构说明符表,结构说明符
具有union类型的变量存储这个类型定义的值之一。相同的规则控制结构和联合说明。联合也可以有位域。
联合的成员不能有一个不完整的类型、类型void或函数类型。因此成员不能是该联合的一个实例但可以是说明的联合类型的指针。
一个联合类型说明只是一个模板,存储器不保存直到说明该变量。注意:如果说明一个有两个类型的联合,但该联合用其它类型访问,其结果是不可靠的。例如,说明一个float和int的联合,存储一个float值,但以后作为int访问它的值。在这种情况下,该值依赖于float值的内部存储。其整数值是不可靠的。
例子
如下是联合的例子
union sign/*一个定义和一个说明*/
{
int svar;
unsigned uvar;
} number;
这个例子用sign类型定义了一个联合,说明一个命名为number的变量,它有两个成员:
svar是一个有符号整数和一个无符号整数uvar。这个说明允许number的当前值作为一个有符号的或无符号的值存储。与这个联合类型关联的标志是sign。
union /*定义一个名称为screen的一个二维数组*/
{
struct
{
unsigned int icon : 8;
unsigned color : 4;
} window1; int screenval;
} screen[25][80];
screen数组包含2000个元素,该数组的每个元素是具有两个成员:Window1和screenval的单个联合。window1成员是具有两个位域的成员:icon和color的结构。screenval成员是一个int。在任何时候,每个联合成员保存screenval表示的int或window1表示的结构。
Microsoft特殊处
当一个联合是另一个结构或联合的成员时可以无名地说明嵌套的联合。如下是一个无名联合的例子:
struct str
{
int a,b;
union /*无名的联合*/
{
char c[4];
long l;
float f;
};
char c_array[10];
} my_str;
my_str.l == 0L; /*my_str联合中一个域的引用*/
联合经常嵌套在一个结构中,该结构包括这样的一个域,在任何特定时间,给出包含在该联合中数据的类型。下面是这样的一个联合的说明的例子:
struct x
{
int type_tag;
union
{
int x;
float y;
}
}
有关引用联合的信息参见第4章“表达式和赋值”中的“结构和联合成员”。
Microsoft特殊处结束
联合的存储
与一个联合变量关联的存储是该联合中最大成员所需要的存储。当存储较小成员时,该联合变量可以包含未用的存储器空间。所有成员存储在相同存储器中并起始于相同的地址。
每次把一个值赋给不同成员时,原来存储的值被覆盖。例如:
union /*定义一个名称为x的联合*/
{
char *a,b;
float f[20];
} x;
x联合的成员根据它们说明的次序依次是一个char值的指针、一个char值和一个float值的数组。x分配的存储是具有20个元素的数组f所需要的存储,因为f是最长的联合成员。
由于该联合没有关联的标志,它的类型是未命名或“无名的”。
数组说明
一个“数组说明”命名数组和指定其元素的类型,它还定义了数组中的元素个数。具有数组类型的变量是数组元素类型的指针。
语法
说明
说明指示符 初始化说明符表opt;
初始化说明符表:
初始化说明符
初始化说明符表,初始化说明符
初始化说明符:
说明符
说明符=初始化器
说明符:
指针opt 直接说明符直接
说明符:
直接说明符[常量表达式opt]
因为常量表达式是任选的,其语法有两种格式:
*第一种格式定义一个数组变量。方括号中的常量表达式参量指出该数组中的元素个数,该常量表达式如果出现必须具有整数类型,并且具有一个大于0的值。每个元素具有类型指示符给定的类型。它可以是非void之外的任何类型。一个数组元素不能是一个函数类型。
*第二种格式说明一个在其它地方定义的变量。它省略方括号中的常量表达式,但不是方括号。只有在以前初始化该数组并说明它为一个参量或在程序中其它地方显式定义的一个数组的引用时才可使用这种格式。
在两种格式中,直接说明符命名该变量并可以修改该变量的类型。直接说明符后的方括号([])修改该说明符为一个数组类型。
类型修饰符可以应用在一个数组类型对象的说明中,但该修饰符应用于元素而不是数组本身。
你可以在数组说明符之后跟随一个括起的常量表达式表来说明一个数组的数组(一个“多维”数组),格式为:
类型指示符 说明符[常量表达式][常量表达式]...
括号中的每个常量表达式定义给定维数的元素个数:二维数组有两个括起的表达式、三维数组有三个括起的表达式等等。如果你已经初始化该数组,说明它是一个参数或者说明它作为程序中其它地方显式定义的一个数组的引用,那么你可以省略第一个常量表达式。
你可以通过使用复杂说明符定义各种类型的指针数组,正如本章后面“解释更复杂的说明符”中描述的。
数组以行方式存储,例如,如下数组由两行组成,每行三列:
char A[2][3];
首先存储第一行的三个列,接着是第二行的三个列。这意味着最后一个下标更快速改变。
为了引用一个数组的单个元素,使用一个下标表达式,正如第4章“表达式和赋值”中的“后缀运算符”中描述的。
例子
这些例子说明了数组的说明;
float matrix[10][15];
该二维数组命名为matrix,有150个元素,每个元素具有float类型。
struct
{
float x,y;
}complex[100];
它是一个结构数组的说明。这个数组有100个元素,每个元素是包含两个成员的结构。
extern char *name[];
这个语句说明一个指向char的数组的类型和名称。name的实际定义出现在其它地方。
Microsoft特殊处
整数的类型需要保存一个数组的最大尺寸是size_t的尺寸,它定义在STDDEF.H头文件中。size_t是一个unsigned
int,其范围是0x00000000到0x7CFFFFFF。
Microsoft特殊处结束
数组的存储
与一个数组类型关联的存储是其所有元素需要的存储。一个数组的所有元素从第一个元素到最后一个元素以相邻方式存储并逐步增大存储器位置。
指针说明
一个“指针说明”命名一个指针变量并指定该变量指向的对象的类型。以指针说明的一个变量保存一个存储器地址。
语法
说明符:
指针opt直接说明符
直接说明符:
标识符
(说明符)
直接说明符[常量表达式opt]
直接说明符(参数类型表)
直接说明符(标识符表opt)
指针:
*类型修饰符表opt
*类型修饰符表opt指针
类型修饰符表:
类型修饰符
类型修饰符表 类型修饰符
类型指示符给出该对象的类型,它可以是任何基本的、结构或联合类型。指针变量也可以指向函数、数组和其它函数(有关说明和解释更复杂指针类型的信息,参见本章后面的“解释更复杂的说明符”)。
通过使类型指示符为void,你可以推迟该类型规格到指针所指地方。这样的一个项作为“void的指针”写成void*。一个说明为void的指针的变量可以用于任何类型的对象的指针。但为了在指针上或所指对象上执行更多操作,对于每种操作,所指的类型必须显式指定(类型char
*和类型void *的变量是赋值兼容的,而不使用类型造型)。这样的转换与一个类型造型是一致的(有关更多信息,参见第4章“表达式和赋值”中的“类型造型转换”)。
类型修饰符可以是const或volatile,或者两者。它们分别指出该指针不能被程序本身修改(const)或者该指针可以被某个超出程序控制的进程合理地修改(volatile)(有关更多信息参见本章前面的“类型修饰符”)。
说明符命名变量,可以包括一个类型修饰符。例如,如果说明符表示一个数组,该指针的类型可以修改为一个数组的指针。
你可以在你定义结构、联合或枚举器类型之前说明一个结构、联合或枚举器类型的指针。你可以通过使用如下例子所示的结构或联合标志来说明指针。这样的说明是允许的,因为编辑器不需要知道该指针变量分配的结构或联合的空间的尺寸。
例子
如下例子说明指针的说明:
char *message; /*说明
message的指针变量*/
message指针指向char类型的变量。
int *pointers[10];/*说明一个指针数组*/
pointers数组有10个元素,每个元素是int类型变量的指针。
int (*pointer)[10]; /*说明有10个元素的数组的指针*/
pointer变量指向一个有10个元素的数组,该数组中的每个元素具有int类型。
int const *x; /*说明一个指针变量x,指向一个常量值*/
指针x可以修改为指向不同的int值,但它指向的值是不能修改的。
const int some_object = 5;
int other_object = 37;
int *const y = &fixed_object;
const volatile *const z = &some_object;
int *const volatile w = &some_object;
在这个说明中的变量y说明为一个int值的常量指针。它指向的值可以修改,但指针本身必须总是指向相同的位置:fixed_object的地址。类似地,z是一个常量指针,但它也说明为一个int的指针,其值不能由程序修改。另一个指示符volatile指出虽然由z所指的const
int的值不能由程序修改,但可以由与程序并发运行的进程合理地修改。w的说明指出该程序不能改变所指的值,程序不能修改该指针。
struct list *next, *previous; /*
使用list的标志*/这个例子说明两个指针变量next和previous,指向结构类型list。这个说明可以出现在list结构类型定义之前(参见下一个例子),只要list类型定义与该说明具有同样的可见性。
struct
{
char *token;
int count;
struct list *next,
} line;
变量line具有list命名的结构类型,list结构类型有三个成员:第一个是char值的指针、第二个是int值、第三个是另一个list结构的指针。
struct id
{
unsigned int id_no;
struct name *pname;
} record;
变量record具有结构类型id。注意pname说明为另一个结构类型name的指针,这个说明可以出现在name类型定义之前。
地址存储
一个地址所需的存储数量和地址的含义取决于编译器的实现。不同类型的指针不保证具有相同的长度。因此,sizeof(char *)不必等于sizeof(int*)。
Microsoft特殊处
对于Microsoft C编译器,sizeof(char *)等于sizeof(int *)。
基指针
Microsoft特殊处
对于Microsoft32位C编译器,一个基指针是距离一个32位指针基32位偏移量。基地址对于练习控制分配对象的段是有用的,由此减小可执行文件的长度和提高执行速度。通常地,指出一个基指针的格式为:
类型 __based(基)说明符
基地址的“基指针”的变化能使一个指针的规格像一个基一样。而基指针是开始于指针开头的存储器段的一个偏移量。基于指针地址的指针是32位编译中有效__based关键字仅有的格式。在这样的编译中,它们是距离一个32位基的32位位移。
基于指针的指针的一个用途是为了包含指针的持久(persistent)标识符。由基于一个指针的指针组成一个链表可以存储到磁盘中,然后用保持有效的指针重新加载到存储器的另一个地方。
如下例子给出了一个基本指针的指针。
void *vpBuffer;
struct llist_t
{
void __based(vpBuffer) *vpData;
struct llist_t __based(vpBuffer) *llNext;
};
指针vpBuffer被赋给在程序中某个后面的点分配的存储器的地址。该链表相对于vpBuffer的值进行重新定位。
Microsoft特殊处结束
抽象说明符
一个抽象说明符(abstract declarator)是没有一个标识符的说明符,由一个或多个指针、数组或函数修饰符组成。指针修饰符(*)总是位于一个说明符中标识符的前面;数组([])和函数(())修饰符紧跟标识符。知道这些,你可以确定标识符出现在一个抽象说明符中的位置并相应地解释该说明符。有关复杂说明符的另外信息和例子,参见下一节“解释更复杂的说明符”。通用的typedef可以用来简化说明符,参见本章后面的“typedef说明”。
抽象说明符可以是复杂的,一个复杂抽象说明符中的圆括号指出一个特定的解释,正如说明中它们为复杂说明符所做的那样。
这些例子说明抽象说明符:
int * /*一个int类型的指针的类型名称*/
int *[3] /*三个int指针的数组*/
int (*)[5] /*五个int的数组的指针*/
int *() /*没有参数并返回int的指针的函数*/
/* 一个没有参数并返回int的指针的函数的指针*/
int (*)(void)
/*一个未指定元素个数的数组,其元素是这样的函数的指针,每个函数有一个unsigned int
参数和其它未指定个数的参数并返回int*/
int (*count[]) (unsigned int,...)
注意:抽象说明符不允许由一个空括号集()组成,因为这是模糊的。不可能确定该隐含的标识符是否属于括号内(这种情况中它是一个不能修改的类型)或是否在括号之前(这种情况下它是一个函数类型)。
--------------------------------------------------------------------------------返回顶端
解释更复杂的说明符
你可以在括号中包含任何说明符来指出一个“复杂说明符”的特殊解释。一个复杂说明符是由多个数组、指针或函数修饰符修饰的标识符。你可以将各种数组、指针和函数修饰符的组合应用于单个标识符。通常,typedef可以用来简化说明。参见本章后面的“typedef说明”。
在解释复杂说明符中,方括号和圆括号(也就是该标识符右边的修饰符)比星号(也就是该标识符左边的修饰符)更优先。方括号和圆括号具有相同的优先级且从左到右结合。在说明符完全解释后,最后应用类型指示符。通过使用圆括号你可以覆盖缺省的结合次序并强制一个特定的解释。但标识符名称不使用圆括号,这可能误解释为参数表。
解释复杂说明符的简单方法是使用如下四个步骤“从里向外”读:
1. 从标识符开始,直接查找右边的方括号和圆括号(如果有)。
2. 解释这些方括号或圆括号,然后查找右边的星号。
3. 如果在任何时候遇到一个右圆括号,回到前面对圆括号里的内容应用规则 1和规则2。
4. 应用类型指示符。
char *( *(*var)() )[10];
^ ^ ^ ^ ^ ^ ^
7 6 4 2 1 3 5
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
在这个例子中,步骤按顺序标出,并解释如下:
1. 标识符var说明
2. 一个指针
3. 一个函数返回
4. 一个指针
5. 一个有10个元素的数组
6. 一个指针
7. char值例子以下例子说明其它复杂的说明以及圆括号如何影响一个说明的含义的。
int *var[5];/*指向int值的数组*
/数组修饰符比指针修饰符具有更高的优先级,因此var说明为一个数组。指针修饰符应用于该数组元素的类型;因此,该数组元素是int值的指针。
int (*var)[5]; /* int值的数组的指针*/
在这个var的说明中,圆括号给出的指针修饰符比数组修饰符的优先级高,var说明成5个int值的数组的指针。
long *var (long,long); /*返回long指针的函数 */
函数修饰符的优先级也比指针修饰符的级别高。因此这个var的说明用来说明var是一个函数,其返回一个long值的指针。该函数说明为两个long值作为参量。
long(*var)(long,long), /*返回long的函数的指针*/
这个例子类似于前面的一个,圆括号给出的指针修饰符比函数修饰符具有更高的优先级,var被说明为一个返回long值的函数的指针。同样,该函数有两个long参量。
struct both/*函数指针的数组*/
{ /*返回结构*/
int a;
char b;
} (*var[5])(struct both,struct both);
一个数组的元素不能是函数,但这个说明证明了如何说明函数指针的数组。在这个例子中,var说明成一个函数的5个指针的数组,该函数返回具有2个成员的结构。该函数的参量说明成具有相同结构类型both的两个结构。注意,把*var[5]括起来的圆括号是必须的,没有它们,该说明是非法的,试图说明一个函数的数组,如下:
/*非法的*/
struch both *var[5](struct both,struch both);
如下语句说明一个指针数组:
unsigned int *(*const *name[5][10])(void);
name数组有50个元素,以多维数组的形式组织,其元素是一个指针的指针,它是一个常量,这个常量指针指向一个函数,该函数没有参数并返回一个无符号类型的指针。
下一个例子是一个函数,它返回一个具有3个double值的数组的指针:
double(*var(double(*)[3]))[3];
在这个说明中,函数返回一个数组的指针,因为返回数组的函数是非法的。这里var说明成一个函数,该函数返回具有三个double值的数组的指针。该函数var有一个参量,该参量象返回值,它是具有三个double值的数组的指针。该参量类型是由抽象说明符给出的。
在参量类型中星号外的圆括号是需要的,没有它们,该参量类型是三个double值的指针的数组。有关抽象说明符的讨论和例子,参见本章前面的“抽象说明符”。
union sign /*指针数组的数组*/
{ /*联合的指针*/
int x;
unsigned y;
} **var[5][5];
本例说明了如何放置圆括号来改变说明的含义。在这个例子中,var是联合指针的5元素数组的指针的5元素数组。对于如何使用typedef避免复杂说明的例子,参见“typedef说明”。
初始化
一个“初始化器”是赋给说明变量的值的序列。你可以通过在变量说明中将一个初始化器应用于说明符中来设置一个变量的初值。把值或初始化器的值赋给变量。
下面几小节描述如何初始化标量、集合和字符串类型的变量。“标量类型”包括所有算术类型加上指针。“集合类型”包括数组、结构和联合。
初始化标量类型
当初始化标量类型时,赋值表达式的值赋给该变量。应用赋值的转换规则(有关转换规则的信息,参见第4章“类型转换”)。
语法
说明:
说明指示符 初始化说明符表opt;
说明指示符:
存储类指示符 说明指示符opt
类型指示符 说明指示符opt
类型修饰符 说明指示符opt
初始化说明符表:
初始化说明符
初始化说明表,初始化说明符
初始化说明符:
说明符
说明符=初始化器/*对于标量初始化*/
初始化器:
赋值表达式你可以初始化任何类型的变量,要遵守如下规则:
*在文件范围层说明的变量可以初始化。如果你不显式初始化外部层的变量,它缺省地初始化为0。
*一个常量表达式可以用于初始化任何用static存储类指示符说明的全局变量,被说明为static的变量在程序执行开始被初始化。如果你不显式初始化一个全局static变量,它缺省地初始化为0,每个具有指针类型的成员都赋值一个空指针。
*用auto或register存储类指示符说明的变量在每次执行控制传给说明该变量的块时都进行初始化。如果你在一个auto或register变量说明中省略一个初始化器,该变量的初始化值是不确定的。对于自动和寄存器值,初始化器并不限制是一个常量,它可以是包含以前定义值的任何表达式,甚至是函数调用。
*外部变量说明的初始化值和所有static变量的初始值,不论是外部还是内部的,都必须是一个常量表达式(有关更多信息,参见第4章中的“常量表达式”)。由于任何外部说明的或静态变量的地址是常量,它可以用于初始化一个内部说明的static指针变量。但auto变量的地址不能用于一个静态初始化器,因为对于该块的每次执行
它可能有不同的值。你可以使用常量或变量初始化auto和register变量。
*如果一个标识符的说明有块范围,且该标识符有外部连接,该说明不能有初始化。例子以下例子说明初始化:
int x=10
整数变量x初始化为常量表达式10。
register int *px=0;
指针px初始化为0,产生一个“空”指针。
const int c=(3*1024);
这个例子使用一个常量表达式(3*1024)初始化c为一个不能修改的常量值,因为有const关键字。
int *b=&x;
该语句用另一个变量x的地址初始化指针b。
int *const a=&z;
指针a用变量z的地址进行初始化。由于它为const,因此变量a只能初始化,不能修改。它总是指向相同的位置。
int GLOBAL;
int function(void)
{
int LOCAL;
static int *lp=&LOCAL;/*非法初始化*/
static int *gp=&GLOBAL; /*合法初始化*/
register int *rp=&LOCAL; /*合法初始化*/
}
全局变量GLOBAL在外部层说明,因此它有全局生存期。局部变量LOCAL有auto存储类,仅在说明它的函数执行期间有地址。因此,试图用LOCAL的地址初始化static指针变量lp是不允许的。static指针变量gp可以初始化为GLOBAL的地址,因为该地址总是相同的。类似地,*rp可以初始化,因为rp是一个局部变量,具有非常量的初始化器,每次进入该块,LOCAL有新地址,然后把它赋给rp。
一初始化集合类型
个“集合”类型是一个结构、联合或数组类型。如果一个集合类型包含集合类型的成员,可以递归应用初始化规则。
语法
初始化器:
{初始化器表} /*对于集合初始化*/
{初始化器表,}
初始化器表:
初始化器
初始化器表,初始化器
初始化器表是由逗号分隔的初始化器的表,该表中每个初始化器是一个常量表达式或一个初始化器表。因此,初始化器是嵌套的。这个格式对于初始化一个集合类型的集合成员时很有用,正如下一小节的例子说明的。但如果一个自动标识符的初始化器是单个表达式,它不需要是一个常量表达式,对于标识符的赋值只不过需要相应的类型。
对于每个初始化器表,常量表达式的值依次赋给该集合变量的对应成员。
如果初始化器表的值比一个集合类型的少,对于外部和静态变量,该集合类型的余下成员初始化为0。一个自动标识符不显式初始化,则其初始值是不确定的。如果初始化器表的值比一个集合类型的多,则产生一个错误。这些规则应用于每个嵌入的初始化器表,以及整个集合。
一个结构的初始化器是一个相同类型的表达式或包含在花括号({})中的成员的初始化器的表。无名的位域成员不进行初始化。当初始化一个联合时,初始化器表必须是单个常量表达式。该常量表达式的值赋给联合的第一个成员。
如果一个数组有未知的尺寸,初始化器的成员确定该数组的尺寸,其类型也变成完整的。在C中没有方法指出一个初始化器的重复,或者初始化一个数组中间的元素而不提供其前面的值。如果你在程序中需要这种操作,在汇编语言中编写该例程。注意:初始化器的成员可以设置数组的尺寸:
int x[]={0,1,2}
但如果你指出尺寸和给出错误的初始化器个数,编译器会产生一个错误。
Microsoft特殊处
一个数组的最大尺寸由size_t定义,它定义在STDDEF.H头文件中。size_t是从0x00000000到0x7CFFFFFF范围的一个unsigned
int值。
Microsoft特殊处结束
例子
这个例子给出了一个数组的初始化器:
int p[4][3]=
{
{1,1,1},
{2,2,2},
{3,3,3,},
{4,4,4,},
};
这个语句说明p为一个4×3数组,并初始化它的第一行的元素为1,第二行的元素为2,如此等等直到第四行。注意第三行和第四行的初始化器表在最后常量表达式之后包含逗号。最后的初始化器表({4,4,4,},)也跟有逗号,这些额外的逗号是允许的但并不需要。分隔常量表达式只需要一个逗号,分隔初始化器表也只需要一个逗号。
如果集合成员没有嵌入的初始化器,值简单地依次赋给子集合的每个元素。因此,前面例子中的初始化与下面的语句是等价的:
int p[4][3]=
{ 1,1,1,2,2,2,3,3,3,4,4,4
};
初始化器表中的单个初始化器可以用花括号括起来,这样有助于明确上述例子。当你初始化一个集合变量时,你必须小心适当地使用花括号和初始化器表。如下的例子更详细地说明了编译器对花括号的解释。
typedef struct
{
int n1,n2,n3;
} triplet;triplet
nlist[2][3] =
{ {{1,2,3},{4,5,6},{7,8,9}}, /*行1*/
{{10,11,12},{13,14,15},{16,17,18}} /*行2*/
};
在这个例子中,nlist说明为2×3结构数组,每个结构有3个成员,该初始化器的第1行赋给nlist的第一行,如下:
1. 行1的第一个左花括号指示该编译器开始初始化nlist的第一个集合成员(也就是nlist[0])。
2. 第二个左花括号指出开始初始化nlist[0]的第一个集合成员(也就是nlist[0][0]处的结构)。
3. 第一个右花括号终止结构nlist[0][0]的初始化,下一个左花括号开始nlist[0][1]的初始化。
4. 该过程继续直到该行结束,这时封闭的右花括号终止nlist[0]的初始化。行2以同样的方式把值赋给nlist的第二行。注意,行1和行2封闭初始化器的最外面的花括号是需要的。如下例省略了外面的花括号,构造时产生一个错误:
triplet nlist[2][3]= /*这引起一个错误*/
{
{1,2,3},{4,5,6},{7,8,9}, /*行1*/
{10,11,12},{13,14,15},{16,17,18} /*行2*/
};
在这个构造中,行1的第一个左花括号开始初始化nlist[0],它是三个结构的一个数组。
值1、2和3赋给第一个结构的三个成员。当遇到下一个右花括号(值3之后的)时,完成nlist[0]的初始化,该三结构数组中的两个余下结构自动初始化为0,类似地,{4,5,6}初始化nlist的第二行中的第一个结构。nlist[1]的余下两个结构设置为0。当编译器遇到下一个初始化器表({7,8,9}),它试图初始化nlist[2]。由于nlist只有两行,这个试图导致一个错误。
在下面的例子中, X的三个int成员分别初始化为1,2和3。
struct list
{
int i,j,k;
float m[2][3];
} x = {
1,
2,
3,
{4.0,4.0,4.0}
};
在上述list结构中,m的第一行中的三个元素初始化为4.0;m的下行的元素缺省初始化为0.0。
union
{ char x[2][3];
int i,j,k;
} y=
{
{
{′1′},
{′4′}
}
};
在这个例子中,联合变量y被初始化。该联合的第一个元素是一个数组,因此,该初始化器是一个集合初始化器。初始化器表 {‘1'}把值赋给该数组的第一行。由于该表中只有一个值,第一列中的元素初始化为字符1,该行中余下两个元素缺省初始化为0。类似地,x的第二行的第一个元素
初始化为字符4,该行中余下的两个元素初始化值0。
初始化字符串
你可以用一个字符串文字(或宽字符串文字)初始化一个字符(或宽字符)数组,例如:
char code[]="abc";
初始化code为一个4元素的字符数组,第4个元素是空字符,以终止所有字符串文字。
一个标识符表的长度只能等于初始化标识符的个数。如果你指定一个数组的尺寸小于字符串的长度,额外的字符被忽略。例如,如下说明初始化code为一个三元素的字符数组:
char code[3]="abcd";
仅将初始化器的开头三个字符赋给code。字符d和该字符串结尾的空格字符被丢弃。注意将建立一个非终结的字符串(也就是,没有一个0值标志结尾)并产生一个诊断消息指出这个条件。
说明:
char s[]="abc",
t[3]="abc";等价于:char s[]={′a′, ′b′, ′c′, ′\0′},
t[3]={ ′a′, ′b′, ′c′};
如果字符串短于指定的数组尺寸,该数组余下的元素初始化为0。
Microsoft特殊处
在Microsoft C中,字符串文字的长度可达2048个字节
Microsoft特殊处结束
--------------------------------------------------------------------------------返回顶端
基本类型的存储
表3.2总结了与每个基本类型关联的存储。
表3.2 基本类型的尺寸
| 类型 |
存储 |
| char,unsigned char, signedchar |
1字节 |
| short, unsigned short |
2字节 |
| int,unsigned int |
4字节 |
| long,unsigned long |
4字节 |
| float |
4字节 |
| double |
8字节 |
| long double |
8字节 |
C数据类型属于通用目录。“整数”类型包括char、int、short、long、signed、unsigned和enum。“浮点”类型包括float、double和long
double。“算术”类型包括所有浮点和整数类型。
类型char
char类型用于存储可表示字符集的一个成员的整数值。这个整数值是对应指定字符的ASCII码。
Microsoft特殊处
一个unsigned char的字符值具有从0到0xFF十六进制值的范围。一个signed char具有从0x80到0x7F的范围。这些范围分别转换为0到255十进制和-128到+127十进制。编译器选项/J改变缺省值从signed变为unsigned。
Microsoft特殊处结束
类型int
一个带符号或无符号int项的尺寸是特殊机器上一个整数的标准尺寸,例如,在16位操作系统中,int类型通常是16位或2个字节。在32位操作系统中,int类型通常是32位或4个字节。因此,int类型根据目标环境等价于short
int或long int类型,unsigned int类型等价于unsigned short或unsigned long类型。int类型都表示有符号的值,除非有其它指定。
类型指定符int和unsigned int(或简称unsigned)定义C语言的某种特征(例如,enum类型)。在这些情况下,特殊实现的int和unsigned
int的定义确定了实际存储。
Microsoft特殊处
有符号整数以2的补码格式表示。对于负数,最高位保存符号1,对于正数和0,它为0。值的范围在表1.3中给出,它们来自LIMITS.H头文件。Microsoft特殊处结束
注意:int和unsigned int类型在C程序中广泛使用,因为它们允许一个特定机器以其最有效的方式处理整数值。但由于int和unsigned
int类型的尺寸是变化的,依赖于特定int尺寸的程序不能移植到其它机器中。为了使程序更具移植性,你可以使用一个sizeof运算符的表达式代替硬码数据尺寸(正如第4章“表达式和赋值”中“sizeof运算符”讨论的)。
带尺寸的整数类型
Microsoft特殊处
Microsoft特殊处支持带尺寸的整数类型。你可以使用_ _intn类型指示符说明8、16、32或64位整数变量,这里n是整型变量的尺寸,以位为单位。n的值可以是8、16、32或64。下面的例子说明了四种带尺寸的整型:
__int8 nSmall; //说明8位整数__
int16 nMedium;//说明16位整数
__int32 nLarge; //说明32位整数_
_int64 nHuge; //说明64位整数
开头三个带尺寸整型是具有相同尺寸的ANSI类型的同义词,这对于编写多平台交叉移植的代码是有用的。注意__int8数据类型与char类型是同义词,__int16与short类型是同义词,
_ _int32与int类型是同义词。__int64类型没有等价的ANSI对应部分。
Microsoft特殊处结束
float类型
浮点数使用IEEE(电子电气工程师协会)格式,单精度浮点类型有4个字节,由一个符号位,一个8位超过127二进制指数和一个23位尾部。尾部表示一个1.0到2.0之间的数。由于尾数的高端位总是1,它不存储在这个数中。这种表示给出浮点类型近似从3.4E-38到3.4E+38的范围。你根据应用的需要说明一个变量为浮点数或双精度数。这两种类型之间的差别是它们能表示、它们需要存储的有效位数和它们的范围。表3.3说明了它们在有效位数和存储要求方面的差异。
表3.3 浮点类型
| 类型 |
有效数字 |
字节个数 |
| 浮点 |
6-7 |
4 |
| 双精度 |
15-16 |
8 |
浮点变量用一个尾部表示,它包含该数值和指数的值,指数包含该数值的大小次序。表3.4说明了每个浮点类型分配给尾部和指数的位数。任何浮点或双精度数最重要的位总是符号位,如果它为1,该数值认为是负数;否则,该数值认为是正数。
表3.4 指数和尾部的长度
| 类型 |
指数长度 |
尾部长度 |
| 浮点 |
8位 |
23位 |
| 双精度 |
11位 |
52位 |
因为指数以无符号格式存储,指数用它的可能值的一半进行偏置。对于浮点类型,该偏置为127;对于双精度类型,该偏置为1023。你可以通过从指数值中减去偏置值来计算实际的指数值。
尾数以大于或等于1且小于2的二进制分数进行存储。对于浮点和双精度类型,在尾数中最重要的位置中隐含一个引导的1,因此,尾部实际上分别是24和53位长,即使该最重要的位没有存储在存储器中。
代替上面描述的存储方法,浮点软件包以反向规格化的数值存储二进制浮点数值,“反向规格化的数值”是具有保留指数值的非0浮点数值,尾数最重要的位总是0。使用反向规格化格式,一个浮点数值的范围可以在精度上进行扩充。你不能控制一个浮点数是以规格化的格式还是反向规格化的格式表示。浮点软件包不使用反向规格化的格式,除非指数小于规格化的格式中能表示的最小值。
表3.5给出了每种浮点类型的变量中能存储的最小值和最大值。这个表中列出的值仅应用于规格化的浮点数值。反向规格化的浮点数值具有一个更小的最小值。注意80x87寄存器中保留的数值总是以80位规格化的格式表示的。仅当存储32位或64位浮点变量(float类型和long类型的变量)时数值可以以反向规格化的格式进行表示。
表3.5 浮点类型的范围
| 类型 |
最小值 |
最大值 |
| 浮点 |
1.175494351E-383 |
1.402823466E+38 |
| 双精度 |
2.2250738585072014E-308 |
1.7976931348623158E+308 |
如果精度不是很重要的,考虑为浮点变量使用浮点类型;相反如果精度是最重要的标准,使用双精度类型。
浮点变量可以提升为一种更大有效位数的类型(从浮点类型到双精度类型)。当你在浮点变量上执行算术运算时这种提升经常出现。算术运算总是取最高精度作为变量的高精度。
例如,考虑如下类型说明:
float f_short;
double f_long;
long double f_longer;
f_short=f_short*f_long;
在上述例子中,变量f_short提升为双精度类型,且由f_long相乘。那么结果在赋给f_short之前舍入为浮点类型。
在下面例子中(使用前面例子中的说明),算术运算在变量的浮点(32位)精度上进行的。然后把结果提升为双精度类型:
f_longer=f_short*f_short;
double类型具有double类型的双精度值有8个字节。该格式类似于float格式,除了它有11位超过1023指数和一个52位尾数加上隐含的高端1位外。该格式给出double类型的近似范围是从1.7E-308到1.7E+308。
Microsoft特殊处
double类型包含64位:1个符号位,11个指数位和52个尾数位。它的范围是+/-1.7E308,至少有15个数字的精度。
Microsoft特殊处结束
long double类型
一个变量的值的范围由给定位数内部表示的最小值和最大值定界。但因为C的约定规则(在第4章“表达式和赋值”中的“类型转换”中详细讨论),你不能在表达式中为一个特定类型的常量总是使用最大值或最小值。
例如,常量表达式-32768由算术负运算符(-)应用于常量32768组成的,由于32768太大不能作为一个short int数表示,它被给以long类型。结果,常量表达式-32768为long类型。
你只能通过类型造型强制为short类型来以short int表示-32768。在类型造型中没有信息丢弃,因为-32768可以以2字节进行内部表示。在十进制表示中,值65000作为一个有符号的常量,它给以long类型,因为65000不适合于short。这样的65000值只能通过类型造型强制为unsignedshort类型或给定该值以八进制或十六进制表示,或者指定它为65000U来作为unsigned
short类型表示。你可以造型强制这个值为unsigned short类型而不丢失信息,因为作为一个无符号数值,65000适合于以2个字节表示。
Microsoft特殊
long double包含80个位:1位表示符号,15位表示指数,64位表示尾数。
它的范围是+/-1.2E4932,其精度至少为19个数字。虽然long double和double是分开的类型,但long double和double的表示是一致的。
Microsoft特殊处结束
--------------------------------------------------------------------------------返回顶端
不完整类型
一个不完整类型是描述一个标识符而缺乏确定该标识符尺寸所需信息的类型。一个“不完整类型”可以是:
*一个没有指出其成员的结构类型。
*一个没有指出其成员的联合类型。
*一个没有指出维数的数组类型。void类型是一个不能完成的不完整类型。为了完成一个不完整类型,指出缺少的信息,如下例子说明如何建立和完成不完整的类型。
*为了建立一个不完整结构类型,说明一个结构类型不指出它的成员。在如下例子中,PS指针指向一个称为student的不完整结构类型:
struct student *ps;
*为了完成一个不完整结构类型,在后面同一范围内用其指定的成员说明相同的结构类型:
struct student
{
int unm;
} /*student结构现在完整了*/
*为了建立一个不完整的数组类型,说明一个数组类型而不指定它的重复计数,例如:
char a[]; /*a有不完整类型*
/*为了完成一个不完整的数组类型,在后面同一范围内用指定的重复计数说明相同的名称:
char a[25]; /*a现在有一个完整的类型*/
--------------------------------------------------------------------------------返回顶端
typedef说明
一个typedef说明是一个用typedef作为存储类的说明。该说明符变成一个新类型。你可以使用typedef说明为C已经定义的类型或你已说明过的类型构造更短或更有意义的名称。
typedef名称允许你封装可以改变的详细的实现。
一个typedef说明的解释方式与变量或函数说明的解释方式相同,但该标识符代替该说明指定的假设类型,变成该类型的同义词。
语法
说明:
说明指示符 初始化说明符表opt
说明指示符:
存储类指示符 说明指示符opt
类型指示符 说明指示符opt
类型修饰符 说明指示符opt
存储类指示符:
typedef类型指示符:
void
char
short
int
long
float
double
signed
unsigned
结构或联合指示符
枚举指示符
typedef名称typedef名称:
标识符
注意,一个typedef说明不能建立类型,它为现存类型或以其它方式指定类型的名称建立一个同义词。当一个typedef名称用作一个类型指示符时,它可以用某些类型指示符进行组合,但不能是其它的。可接受的修饰符包括const和volatile。
typedef名称与普通标识符共享名称空间(有关更多信息,参见第2章“程序结构”中的“名称空间”),因此,一个程序可以有一个typedef名称和相同名称的局部范围标识符。例如:
typedef char FlagType;
int main()
{
}
int myproc(int)
{
int FlagType;
}
当说明一个与typedef相同名称的局部范围标识符时,或当在同一范围或更内层范围中说明一个结构或联合的成员时,必须指出类型指示符。如下例子说明了这种约束:
typedef char FlagType;
const FlagType x;
为了为一个标识符,一个结构成员或者一个联合成员重新使用FlagType名称,必须提供其类型:
const int FlagType; /*需要类型指示符*0/
如下这样是不足够的:
const FlagType; /*不完整规格*/
因为FlagType作为该类型的一部分,不是一个重新说明的标识符。如下说明是一个非法的说明:
int ; /*非法说明*/
你可以用typedef说明任何类型,包括指针、函数和数组类型。你可以在定义一个结构或联合类型之前说明该结构或联合类型的指针的typedef名称,以及该定义与该说明具有相同的可见性。
typedef名称可以用于提高代码的可读性。所有如下的三个signal说明都精确指出相同类型,开头一个没有使用任何typedef名称:
typedef void fv(int),(*pfv)(int); /*typedef说明*
/void (*signal(int,void(*)(int)))(int);
fv *signal(int,fv*); /*
使用typedef类型*/pfv signal(int,pfv); /*使用typedef类型*/例子下面的例子说明了typedef说明:typedef
int WHOLE; /*说明WHOLE为int的同义词*/注意WHOLE现在可以用在变量说明中,例如WHOLE i;或const
WHOLEi;但说明long WHOLE i;是非法的。
typedef struct club
{
char name[30];
int size,year;
}GROUP;
这个语句说明GROUP是一个具有三个成员的结构类型。因为也指定了一个结构标志club,typedef名称(GROUP)或结构标志可以用在说明中,你必须用标志使用结构关键字,不能用typedef名称使用结构关键字。
typedef GROUP *PG; /*使用前面typedef名称说明一个指针*/
类型PG说明为GROUP类型的一个指针,它又被定义为一个结构类型。
typedef void DRAWF(int,int );
这个例子提供DRAWF类型,它作为一个函数,没有返回值,有两个int参量。例如,说明:DRAWF box;等价于说明:
void box(int,int);
--------------------------------------------------------------------------------返回顶端
扩充的存储类属性
Microsoft特殊处
扩充的属性语法简化和标准化C语言的Microsoft特殊处扩充。使用扩充的属性的存储类属性包括thread、naked、dllimport和dllexport。
特定存储类信息的扩充属性语法使用__declspec关键字,它指出一个给定类型的实例,采用Microsoft特殊存储类属性(thread、naked、dllimport或dllexport)进行存储。其它存储类修饰符的例子包括static和extern关键字,但这些关键字是ANSI
C部分,因此不能被扩充属性语法覆盖。
语法
存储类指示符:
__declspec(扩充的说明修饰符序列)/*
Microsoft特殊处*/
扩充的说明修饰符序列:
扩充的说明修饰符opt
扩充的说明修饰符序列
扩充的说明修饰符扩充的说明修饰符:
thread
naked
dllimport
dllexport
用空白分隔说明修饰符。注意扩充的说明修饰符序列可以为空,在这种情况下,__declspec没有作用。
thread、naked、dllimport和dllexport存储类属性仅是它们应用的数据或函数的说明的特性(property);它们不能重新定义函数本身的类型属性。thread属性只影响数据。naked属性只影响函数。dllimport和dllexport属性影响函数和数据。
Microsoft特处殊结束
DLL输入和输出
Microsoft特殊处
dllimport和dllexport存储类修饰符是C语言的Microsoft特殊处扩充。这些修饰符定义DLL的客户界面(可扩充文件或另外的DLL)。有关使用这些修饰符的特殊信息,参见第6章“函数”中的“DLL输入和输出函数”。
Microsoft特殊处结束
Naked
Microsoft特殊处
naked存储类属性是C语言的Microsoft特殊处扩充。编译器为用naked存储类属性说明的函数生成没有序言和结尾部分的代码。当你需要使用内联函数代码编写自己的序言/结尾部分代码序列时naked函数很有用。
naked函数对于编写虚拟设备驱动程序是有用的。
有关使用naked函数的特定信息,参见第6章“函数”中的“naked函数”。Microsoft特殊处结束
4thread局部存储
Microsoft特殊处
thread局部存储(TLS)是在一个给定的多线程进程中每个线程分配特定线程数据的机制。在标准多线程程序中,数据在一个给定进程的所有线程中共享,而线程局部存储是分配每个线程数据的机制。有关线程完整的讨论,参见联机“平台SDK”文档中的“进程和线程”。
Microsoft C语言包括扩充的存储类属性,thread与__declspec关键字一起使用说明一个线程局部变量。例如,如下代码说明一个整数线程局部变量并用一个值对其初始化:
_ _declspec(thread)int tls_i=1;
当你说明静态约束线程局部变量时必须遵守这些指南:
* _ _declspec(thread)的使用可以与DDL输入的延迟加载进行交互。
*你只能把thread属性应用于数据说明和定义。它不能用于函数说明或定义。
例如,如下代码产生一个编译器错误:
#define thread __declspec(thread)thread void func();/*错误*/
*你只能在具有静态存储期的数据项上指定thread属性。这包括全局数据(static和extern)和局部静态数据。不能用thread属性说明自动数据。例如,如下代码产生编译器错误;
#define thread __declspec(thread)
void funcl()
{
thread int tls_i; /*错误*/
}
int func2(thread int tls_i); /*错误*/
{
return tls_I;
}
l你必须为线程局部数据的说明和定义使用thread属性,而无论该说明和定义是出现在相同文件或分开文件中,例如,如下代码产生一个错误:
#define thread __declspec(thread)
extern int tls_i;/* 这产生一个错误,因为*/
int thread tls_i;/* 说明和定义不相同*/
*你不能使用thread属性作 为一个类型修饰符。例如,如下代码产生一个编译器错误:char *ch_ _declspec(thread);/*错误*/l一个线程局部变量的地址不能作为常量,涉及到该地址的任何表达式不能作为一个常量表达式。这意味着你不能使用一个线程局部变量的地址作为一个指针的初始化器。例如,编译器标志的如下代码有一个错误:
#define thread __declspec(thread)
thread int tls_i;int *p=&tls_i; /*错误*/
* C允许用涉及自身引用的表达式初始化一个变量,但仅对非静态范围的对象,例如:
#define thread __declspec(thread)
thread int tls_i=tls_i; /*错误*/
int j=j; /*错误*/
thread int tls_i=sizeof(tls_i) /*正确*/
注意一个初始化变量的sizeof表达式不构成对自身的引用,因而是允许的。
* __declspec(thread)的使用可以与DLL输入的延迟交互。有关使用线程属性的更多信息,参见联机“Microsoft
Visual C++6.0程序员指南”中的“多线程主题”
Microsoft特殊处结束
|