4——其他内置数据类型



其他内置数据类型

我们在上一章讨论关键字,定义与声明的时候,提及过内置的数据类型,那时候我们提到了int整型,除了这个之外,我们还有很多内置的数据类型。这一个章节,我们不如专门讨论下数据类型。


int 整型

整型一般的典型大小是4 Bytes,也就是用32 bits去表示一个整数,整数(integer)就是没有小数部分的数,也就是定点数[1]的一种,储存方式类似于下图。编译器将这一串二进制串解释为一个整型数字。


Figure 1

其使用方式如:int var_name = 100 ;


char 字符型

字符型ANSI C中规定是1 Byte,也就是说按道理来说,不管是那个编译器,哪个平台都应该将char解释为1 Byte的数,其储存方式如Figure2所示。是不是感觉到很熟悉?是的,字符型的储存方式也是一串二进制串,只是编译器根据ASCII[2]表将其解释为字符而已。比如说十进制数65解释为字符’A’,其本质还是一个数字而已,也就是说char字符型其实和int整型除了大小不同,并没有本质的区别,这个需要特别注意。

Figure 2

其使用方式如:char var_name = ‘A’ ;或者char var_name = 65 ;

看到没有,对字符型的变量直接赋值整型数(只要不超过表示范围)或者字符’A’都是可以的。


long 长整型

长整型还是整型,其使用方式和整型没有不同,区别就是长整型可以表示的数的范围更大而已。需要知道的是,如果你觉得long不够大,那么你还可以long long长长整型的哦。

其使用方式如:long var_name = 100 ; long long var_name = 100 ;


short 短整型

这个也没什么好讲的,和整型差不多,就是表示数的范围小了而已。

其使用方式如: short var_name = 100 ;


float 单精度浮点型

单精度浮点数属于浮点数,可以理解为数学中的实数,2.75,3.15E7(3.15*10^7),7.00,2e-8这些都是属于浮点数,浮点数的特征就是小数点可以浮动,也就是可以表示小数的意思,和定点数形成了对立,定点数只能表示纯整数或者纯小数。

         需要注意的一点是,浮点数和整数的储存方案[3]是不同的,浮点数把一个实数分解为小数部分和指数部分,因此尽管7.00和7数学上等价,但是在计算机中储存方式完全不同,如Figure3所示为浮点数7.00的储存方式,Figure4为整型7的储存方式。

Figure 3[4]

Figure 4
其表示方式为

Figure 5(十进制示意版本)

这个是十进制的示意版本,实际在计算机中采用二进制版本,比如IEEE-754标准所规定的。其中用32位表示一个浮点数,结构图如Figure6


Figure 6  (IEEE-754标准规定版本)

其中,表示的数值为X = (-1)Sx(1+Mx2-23)x2E-127,比如5.0就表示为0x40A00000,1.2表示为0x3F99999A。

         在C语言中,浮点数有一些特殊的表示,比如7.00f,3.14f,3.14E3表示3.14乘上10的三次方,其中E换成e也一样,3.14E-3表示3.14乘上10的负三次方。

         其使用方式如:floatvar_name = 5.00 ; float var_name = 5.00f ;


double 双精度浮点数

和单精度浮点数float类似,只是其表示范围更大,比如说7.00在这里就表示成如Figure7这样,使用方式如:double var_name = 10.01;

double var_name = 10.01f ;


Figure 7 double类型的7.00储存方式


register 寄存器类型

register类型并不是一个典型的数据类型,这个关键字的意思是告诉编译器,在编译的时候,尽量把这个变量的值放在寄存器里面去,以提高其读取速度。注意这里只是说尽量,而不是一定,因为寄存器的数量毕竟有限,大概也就是十多二十个,如果定义一个 register a[50] ;这样的寄存器数组,肯定是没有那么多寄存器给编译器分配的,因此只能说编译器会尽量把这个变量放在寄存器里面,如果没有足够的寄存器,那么还是会分配到内存空间里面。

         寄存器的大小跟平台有关,一般都是和数据总线的宽度相同,比如说我的是64位计算机,那么我一个寄存器的大小就是64位。

         请注意,当我们谈到变量的地址[5]的时候,指的变量都是储存在内存中的,而寄存器是谈不到地址的,因此当我们学到指针之后,会发现这样的语句编译器是会报错的。

void main(void)

{

   register int var_name = 10 ;

    int *p= &var_name;


}
报错的原因就是因为取了寄存器变量的地址。就算这个寄存器变量有可能会因为寄存器数量不够而分配到内存空间中,但是他同样也有可能因为寄存器数量足够而把这个变量分配到寄存器之中,这个时候对寄存器取地址就是非法操作,因此编译器不允许我们,我们也不应该对寄存器变量取地址。


指针类型

指针,是C语言之中的一颗明珠,其概念倒是很简单,就是指的是变量或者函数的地址,其本质就是地址。那倒底我们谈了那么多的地址究竟是什么呢?

         我们要在那么大的一个内存区域里面放置,寻找一个变量,那不是一个容易的事情,这个就像快递小哥给你们送快递,在偌大的一个地区送快递没有准确的送件地址那是不可能达到的。因此,我们就必须要在内存中给每个区域编上地址,在内存中编址肯定不能像给门牌号编址那样直接来个“XX街XX号”的吧,既然是计算机,那肯定是会采用数字给内存编址的,就像Figure8这样。在Figure8中,前面的16进制串是地址如0x0041F900,后面的16进制数是这个内存单元的值如0x00。

         这里我们要注意到,这里的最小内存单元是以一个字节,也就是1 Byte为单位的,不能给一个单一的比特编上地址。

         也就是说,我们的指针其实就是左边的那串16进制数,比如说是0x0041F900这样的,其使用很简单,在定义的变量名之前加上*号表示这是个指针变量就可以了。如下所示:

int *ptr_var_int= NULL ; // (1)

char *ptr_var_char; // (2)

         正如(1)(2)所看到的,指针也是有相应的类型的,比如int *指向整型的指针,char *指向字符型的指针,此外还有long *,short *等等各种各样的指针类型,格式就是Var_type_name * 用变量类型加一个*就行了。注意,这里指的指针类型说的是指向内存单元的值的类型。

         然后我们看到NULL,这个是什么呢?这个表示空地址,实质上就是

(void *) 0也就是说NULL = (void *) 0也就是地址为0x00000000的地址,至于void 表示什么我们接下来再谈。

         (1)(2)两种类型定义出来的指针如Figure 8所示

Figure 8

Figure 9 内存地址

我们可以看到,给ptr_var_int 分配了NULL的地址,也就是0x00000000,

给ptr_var_char分配了一个编译器初始化的地址0xcccccccc,这样出来的值可能每次都不同,因此建议在定义指针的时候都要由程序员自己做初始化,不知道指向哪里的可以指向NULL。

那么我们要怎么取出一个变量的地址呢?我们这样做:

int *ptr_var= NULL ;

int var =10 ;

ptr_var = &var ;

       我们用&表示取出一个内存分配在栈区的变量的地址,因此&var 就可以取出var这个变量所在的内存单元的地址。结果如Figure10所示。

Figure 10

可以看出,ptr_var和&var(也就是var的地址)都是0x0030fc74,我们同样可以看下内存0x0030fc74这个地址指向的变量是否就是var = 10,如Figure11所示。可以明显看出这个地址指向的就是0x0a也就是十进制的10,符合我们的结论。

Figure 11
知道了怎么把一个变量的地址取出来,那么就要知道怎么把一个内存空间对应的地址的单元的值读出来对吧,我们通过下面的这种语句读取某个内存单元地址的值。

int *ptr_var= NULL ;

int var =10 ;

int var2 =0 ;

ptr_var = &var ;

var2 = *ptr_var ;

Figure 12
可以看出,var2通过var2 = *ptr_var这个操作,得到了ptr_var指向的内存单元的值。因此*也可以表示取出某个内存单元地址指向的值。但是在C语言中,受限于平台的操作系统的内存保护措施,你不能访问一个允许范围之外的地址的内容,比如如下的代码是会运行报错[6]的。这个我们暂且不详细讨论。

int *ptr_var= 0x00000001;

int var =*ptr_var ;

其报错信息为:

Figure 13

在单片机或其他没有操作系统的裸机中,这样的代码不会出现运行错误,因为没有操作系统提供内存保护,但是我们应该减少或者抵制这样的代码[7],因为可能造成各种不稳定的后果,而且代码的可移植性将会大大降低。


指针的存放位置

指针作为一种变量,那么他肯定也是需要内存空间存放的,那么指针究竟放在哪里呢?答案就是内存中的栈区。如下代码所定义的ptr_var将会保存在栈区之中。

int var =10 ;

int *ptr_var= &var;

同时,指针作为一种变量,其也是有大小的,我们用sizeof(int *)之类的代码可以看出指针的大小,在我这里,其大小是4个字节。
还记得我们上一节所说的吗,栈区的变量是可以取地址的,既然指针作为一种变量存放在栈区,那么他也是肯定可以被取地址的,那么我们就出现了二级指针,如下所示。
int **ptr2_var=NULL;
多了一个*号表示对*ptr_var取地址,这个过程可以迭代,也就是说可以出现三级指针,四级指针等等,但是我们一般只会用到一级,二级到三级指针,其他高级指针很少使用。
我们还是举个例子讨论二级指针。

int var =10 ;

int *ptr_var= &var;

int **show= NULL ;

int **ptr2_var= &ptr_var;

show = ptr2_var ;

然后老样子观察其内存空间,如Figure14所示。

Figure 14

可以看出show为二级指针,其地址为0x004dfeb0,其内存单元的值为0x004dfebc而这个值又是一个地址,其指向的内存单元的值为0x0a。多级指针看起来繁琐,其实在实际程序设计中很有作用。在这里我们仅是讨论了变量的指针,其实函数也是有指针的,我们后续再讨论。



void 空类型

void 表示的是空类型,诸位可能有些吃惊,为什么C语言要定义出一个空类型呢?这个空类型可以表示函数无法返回值,也可以用在指针的定义中,我们上一节谈到指针也是有类型的,它可以指向内存单元为整型的值,也可以指向内存单元为浮点数的值。但是如果我们在程序设计的时候并不能事先知道这个地址指向的值是多少该怎么办呢?于是我们可以这样用:

         void *ptr_var= NULL ;

         这样我们就得到了一个指向内存单元类型为空类型的指针,如Figure15所示。

Figure 15

当我们得知了这个内存单元应该的数据类型的时候,可以强制转换成该数据类型即可,这个强制转换我们将在以后专门讨论。

int var =10 ;

int var2 =0 ;

void *ptr_var= NULL ;

ptr_var = &var ;

var2 = *(int *)ptr_var;

可以看出var2得到了ptr_var指向的内存单元的值。

Figure 16

还有一个需要注意的是,我们可能经常会见到下面这样的代码:

int var =10 ;

int *ptr_var= &var;

void *ptr_void= NULL ;

ptr_var = ptr_void;

这样的代码编译器是可以正常编译的,也不会报错或者警告,为什么void *类型的指针可以直接赋值给int *型的指针呢?这个时候我们就要理解到,void空类型,既是什么都没有,但是他也就表示了所有类型,它可以表示所有类型,甚至是你自己定义的类型,而void *也就表示为所有的类型的指针,相当于所有类型的父类,是一个更广泛的类型,因此编译器允许void *赋值给其他任意类型的指针。

从本质上说,void *代表的其实是一个内存地址,仅仅就是个内存地址而已,也就不涉及这个地址指向的变量的类型的概念了。

         对于void空类型,我们要知道“空,既是什么都没有,既是什么都包含”“色即是空,空即是色”这种哲学思想既可以理解上述的操作了。


回顾NULL

NULL不是一种数据类型,实际上在C语言中,NULL经常是:

#define NULL(void *)0

其中的#define是C语言预编译器的一个命令,叫做宏定义,我们后续再详细讨论,这里只要知道NULL实际上等于(void *)0就够了,学过了指针和void空类型之后,(void *)其实很容易读懂,就是一个指针,其指向的内存的变量类型为void,我们之所以这样定义NULL,是因为这样定义可以使得NULL适用于所有的类型变量的地址。





[1] 小数点位置固定的数,称之为定点数。

[2] ASCII, American Standard Code for Information Interchange,美国信息交换标准代码

[3] ANSI C的浮点数符合IEEE-754标准

[4]这里的读数涉及计算机储存的大端模式和小端模式,我们将在后续讨论。

[5] 这里谈到的地址和指针,我们将会很快讨论到,这个是C语言的优点也是特点之一。这里看不懂,可以先跳过,涉及到这些概念之后再回来看。

[6] 并不是编译报错,请注意。

[7] 在汇编中倒不然,可以出现这种直接指定内存地址访问的代码。





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