3——关键字,变量定义和声明
C语言的关键字
ANSI C规定了标准的语言有32个关键字,9种控制语句,C99新增了5个关键字,而C11又新增了7个关键字,程序的书写形式自由,每一条语句以英语半角分号“;”表示结束。区分大小写,也就是说Apple和apple在C语言中是完全不同的东西。
关键字表征了一些C语言内置的语言特性,在我们其他的变量命名或者常量命名,函数命名中将不得再重复使用。比如说你不可以命名这样一个变量
intcase = 1 ;
这样的操作将会是非法操作,然后由编译器报错提示你编译失败。 首先先列举出这些关键字,接下来的章节将会或多或少的讲解ANSI C的这些关键字。
auto | break | case | char | const | cotinue | default | do |
double | else | enum | extern | float | for | goto | if |
int | long | register | return | short | signed | sizeof | static |
struct | switch | typedef | union | unsigned | void | volatile | while |
inline | restrict | Bool | Complex | _Imaginary |
_Alignas | _Alignof | _Atomic | _Static_assert | _Noreturn | _Thread_local | _Generic |
因为并不是每个编译器都实现了C99和C11标准,为了程序的兼容性考虑,因此我们仅以ANSI C作为讨论的对象。
变量定义(Definition)
首先我们要知道什么叫做变量,很直接的,变量就是数值可以变化的量,其对立面就是常量——一旦初始化完成之后就不予许你去修改的数值。在C语言中,一切变量使用前都需要事先定义,否则将无法使用,属于一种强类型的编程语言。那么什么叫做定义呢?
定义,指的是C语言告诉编译器,我这个变量需要多少多少内存去储存,你事先要给我分配好相应的空间。例如:
int example_dex= 1;
int example_hex= 0x20;
其中,int表示这个变量example_dex的数据的类型是整数类型,你如果储存成小数那将会出现使用错误的。从上面的例子中我们也可以看出,要定义使用一个变量,那么肯定需要给这个变量命名,在C语言中,标识符[1]的命名规则如下:
Rule 1
需要注意的是,以上的仅是命名规则,在实际的编程任务中,我们还有一系列的编程规范,取变量函数的名字也是一种学问来的,不能说整个代码都是a,b,c,d的,导致整个程序的可读性极差。
接下来,在我们正式讨论定义这个概念之前,我们需要了解以下两个知识点。
知识点一
我们在这里首先先介绍一下ANSI C语言内置的数据类型,一共有以下七种内置的数据类型[2]。每一种类型的所占用的内存大小是不同的,在ANSI C中规定的数据类型大小如Table 4所示。
int | short | char | long | float | double | register |
数据类型 | 中文名 | 最小储存字节 | 典型储存字节 |
char | 字符型 | 1 Byte | 1 Byte |
int | 整型 | 2 Bytes | 4 Bytes |
short | 短整型 | 不小于char | 2 Bytes |
long | 长整型 | 4 Bytes | 4 Bytes |
float | 单精度浮点型 | 4 Bytes | 4 Bytes |
double | 双精度浮点型 | 8 Bytes | 8 Bytes |
其中,这些数据类型的大小除了char是固定的1个字节之外,其他的ANSI C都没有作出太明确的规定,因此都有可能会随着不同的平台,不同的编译器而变化,因此需要在程序设计中使用另一个关键字sizeof
sizeof表示让编译器求出这个系统中某个数据类型的数据大小,比如sizeof(int)可以求出int数据类型的大小,sizeof加上括号看起来很像是函数,其实是个关键字,因此需要注意下,不能取一个标识符名为sizeof的变量哦。
还有需要注意的是,sizeof不仅可以求出ANSI C内置的数据类型的大小,也可以求出自己定义的数据类型的大小,这个我们接下来讲到struct的时候再谈。
在64位的win7平台上VisualStudio 2012开发环境上,输入以下代码,可以求出相应数据类型的大小,得出了一个典型值。输出结果如Figure1所示。
#include <stdio.h>
void main(void)
{
printf("int = %d\r\n", sizeof(int)) ;
printf("short = %d\r\n", sizeof(short)) ;
printf("long = %d\r\n", sizeof(long)) ;
printf("char = %d\r\n", sizeof(char)) ;
printf("float = %d\r\n", sizeof(float)) ;
printf("double = %d\r\n", sizeof(double)) ;
}
从表中我们可以看出,并没有谈到register的类型大小,因为register类型比较特殊,我们讲到后面的时候在提及。
知识点二
内存的大小很大,为了能够高效的使用内存,经常把内存分为以下几个部分。1, 栈区(Stack)
由编译器自动分配释放,存放函数的形参值,局部变量值等,类似与数据结构中的栈结构,都是FILO(First In Last Out)的。
2, 堆区(Heap)
一般由程序员自己分配释放,如果程序员不释放,程序结束时可能会被操作系统释放,如果没有操作系统或者程序是属于不断循环不会结束的,那么可能会造成内存泄漏(Memory Leak)等后果。
3, 全局区(静态区)(Static)
存放全局变量和静态变量,初始化后的全局变量和晶体变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由操作系统释放内存。
4, 字符常量区
存放常量类型的数据,比如常量字符串等,程序结束后由操作系统释放内存。
5, 程序代码区
存放程序的二进制代码,经常放在大容量的储存设备比如flash,硬盘,在需要使用的时候才加载到内存中。
如果诸位没有接触过内存管理相关的内容,可能不能很好的理解以上的内容,没关系,我们接下来会一一讨论到这些问题,在这里我们只要有这个概念——内存存在分区,就足够了。
定义是什么
其中我要解释一下这里的一些汇编指令,一个是MOVS,他表示把立即数0x01移入寄存器R0中,而STRB,表示把寄存器中的一个字节移入内存中,而这里的SP便是刚才谈到的栈区的指针,我们称之为栈顶。MOVS R0, #0xFF ; STRB R0, [SP, #0x00]这两条指令其实就描述了编译器是如何给这种变量分配内存的。
编译器首先定义给内存划分了一定大小的区域,其中我们拿出栈区作为讨论对象,然后假设其栈顶的地址为0x00000000,那么以上的两条指令就相当于把0xFF这个值赋值到了内存0x00000000这个地址所指的内存中。其实这就是所谓的编译器自动给变量分配内存空间,置于栈区中这个意思,这里的分配是在编译阶段就分配完了的,而不是在程序运行的时候,这个注意。
当然,这里因为定义的数据类型是char类型,所以才分配了一个字节的空间,如果是其他类型的数据,将会分配相应大小的内存空间。
知道了定义是啥回事,那么就要知道怎么定义了,在C语言中定义一个变量很简单,格式如:
<变量数据类型> 变量名字 [ = 初始化数值] ;
其中的变量数据类型需要是合法的数据类型,变量名字是标识符的一种,需要是合法的命名规则产生的标识符,而初始化数值是可以填写或者不填写的。例子为:
int var_int =10 ;// (1)
char *var_ptr_char= NULL ; // (2)
long var_long ;// (3)
其中(3)的例子中没有进行初始化,那么编译器就会给你安排一个值自行给你初始化,而这个值是没有在标准中规定的,因此是不固定的,可能会随着不同的编译器而变化,因此我们在程序设计的时候定义变量的时候尽量自行初始化。这样编写的程序才具有高移植性。也可以像下面这段代码一样初始化:
int var_int =10 ;
int var = var_int;
既是在后面定义的变量可以直接赋予前面定义过的变量的值,但是要注意,这个时候两者的数据类型最好是一样的,如果不一致,可能会发生隐式转换甚至是编译器报错。
变量声明(Declaration)
有了上面定义的概念,就能够很好的区分开声明来了。从声明这个概念上来说,就是告诉编译器我这个变量有可能要被引用,做编译的时候请做好准备。变量的声明分为两种情况。
1, 一种是需要建立储存空间的定义,例如:int a ; 这语句在声明的时候就已经建立了储存空间了,这个叫做定义性声明,其实就是定义。但是在编译器中会有点不同。比如:
char a= 1 ; char a ;
这两个看上去相似的语句编译出来的汇编代码可能会不同,第一句可能就是MOVSR1, #1 ; STRB R1,[SP](如果后续没有使用这个值,也很可能会被编译器给优化掉)而第二句如果后面这个a一直没有被引用过,则可能编译器编译的时候压根就不理他,就像上一节中的char a =10 ;被优化了一样。这个行为叫做优化,编译器有着不同的优化水平。
2, 另一种是不需要建立储存空间的声明,例如:extern int a 其中的变量a是在其他文件中定义的,这里就是告诉编译器我这里的a已经被使用了,虽然不是我这里负责给你分配内存的,但是你可别取一个相同的变量名字哦,不然会被编译器报错的。这种叫做引用性声明,同一个变量的声明可以多次出现,但是同一个变量的定义只能出现一次。
externint a ; // 引用
void main(void)
{
int c ; // 定义性引用,看成定义也ok
int b = 10; // 定义
}
那如果出现extern int a =10 ;这样的语句会怎么样呢?如果这个a的确是个在别的文件中定义过了变量,那么肯定就会报错了,如果没有,那么就可以顺利编译过,但是我们为什么要想不开这样定义变量呢?是吧。