3——关键字,变量定义和声明


C语言的关键字

ANSI C规定了标准的语言有32个关键字,9种控制语句,C99新增了5个关键字,而C11又新增了7个关键字,程序的书写形式自由,每一条语句以英语半角分号“;”表示结束。区分大小写,也就是说Apple和apple在C语言中是完全不同的东西。

关键字表征了一些C语言内置的语言特性,在我们其他的变量命名或者常量命名,函数命名中将不得再重复使用。比如说你不可以命名这样一个变量

intcase = 1 ;

这样的操作将会是非法操作,然后由编译器报错提示你编译失败。 首先先列举出这些关键字,接下来的章节将会或多或少的讲解ANSI C的这些关键字。


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


 

C99新增的关键字
inline restrict Bool Complex _Imaginary

C11新增的关键字
_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
Table 4
数据类型 中文名 最小储存字节 典型储存字节
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)) ;
}

Figure 1

从表中我们可以看出,并没有谈到register的类型大小,因为register类型比较特殊,我们讲到后面的时候在提及。


知识点二

内存的大小很大,为了能够高效的使用内存,经常把内存分为以下几个部分。
1, 栈区(Stack)
由编译器自动分配释放,存放函数的形参值,局部变量值等,类似与数据结构中的栈结构,都是FILO(First In Last Out)的。
2, 堆区(Heap)
一般由程序员自己分配释放,如果程序员不释放,程序结束时可能会被操作系统释放,如果没有操作系统或者程序是属于不断循环不会结束的,那么可能会造成内存泄漏(Memory Leak)等后果。
3, 全局区(静态区)(Static)
存放全局变量和静态变量,初始化后的全局变量和晶体变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由操作系统释放内存。
4, 字符常量区
存放常量类型的数据,比如常量字符串等,程序结束后由操作系统释放内存。
5, 程序代码区
存放程序的二进制代码,经常放在大容量的储存设备比如flash,硬盘,在需要使用的时候才加载到内存中。



如果诸位没有接触过内存管理相关的内容,可能不能很好的理解以上的内容,没关系,我们接下来会一一讨论到这些问题,在这里我们只要有这个概念——内存存在分区,就足够了。



定义是什么

接下来,有了这些预备知识之后,我们就可以继续讨论究竟什么是变量定义了。变量定义,粗浅的说就是给这个变量预先分配一个内存空间,但是究竟是谁分配,怎么分配?在什么时候分配?你有没有考虑过这些问题呢?要回答这个问题,我们就要先回顾下上一节知识点二的内容,内存被分为了很多个区域,这些区域用于储存不同种类的数据。其中有个叫做栈区的我们首先要拿出来讨论下。我们首先给出下面一段代码,与其在ARM下汇编出来的代码。

其中我要解释一下这里的一些汇编指令,一个是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的确是个在别的文件中定义过了变量,那么肯定就会报错了,如果没有,那么就可以顺利编译过,但是我们为什么要想不开这样定义变量呢?是吧。




[1]  变量,常量,函数的名字都叫做标识符。

[2]  并没有经常使用的string字符串类型和complex复数类型,因为C语言是和计算机硬件紧密相关的,而计算机硬件是没有直接实现complex和string的,因此就在设计C语言的时候没有设计这两种常用的类型,在Java中是有内置的string类型的。

posted @ 2016-12-14 14:08  FesianXu  阅读(155)  评论(0编辑  收藏  举报