<C语言漫谈录>3——关键字,变量定义和声明


C语言的关键字

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

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

intcase = 1 ;

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


ANSI C关键字
autobreakcasecharconstcotinuedefaultdo
doubleelseenumexternfloatforgotoif
intlongregisterreturnshortsignedsizeofstatic
structswitchtypedefunionunsignedvoidvolatilewhile


 

C99新增的关键字
inlinerestrictBoolComplex_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所示。

intshortcharlongfloatdoubleregister
Table 4
数据类型中文名最小储存字节典型储存字节
char字符型1 Byte1 Byte
int整型2 Bytes4 Bytes
short短整型不小于char2 Bytes
long 长整型4 Bytes4 Bytes
float单精度浮点型4 Bytes4 Bytes
double双精度浮点型8 Bytes8 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类型的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FesianXu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值