接上一节C程序综合概述细节分析
C语言是高级语言中最精简的语言,它比其它OOP语言少了很多不必要的内部机制,C语言核心就是内存、数据结构和算法了,而数据类型这部分的内容对于C语言编程来说显得比较重要,在C程序设计时需要考虑到采用的数据类型、数据的大小、范围和精度。C语言的其它数据类型都由基本数据类型衍生,从根本来说,所有数据类型都可由整数类型表示,而整数又可表示为二进制,因为C语言相对较为底层的特点,所以这需要我们对数据类型有更清晰的认识。
C语言的基本数据类型可分为数值类型、字符类型和布尔类型,数值类型又可分为整型和浮点类型,下面我们从数据类型的基本知识开始对基本数据类型展开讨论。
一、数据类型基本知识
1、数据单位
数据类型的最基本衡量单位是什么?位宽(或位长),位宽指的是计算机存储一个数据所用的空间大小,位(Bit)是计算机最小的存储单位,如1010为4位,然后我们问4位可能存储多少种数据,能存储2的4次方16种数据,那数据范围呢?0-15,如果要求存储有符号的数据,则范围为-8到7(包括0,下面提到的数据类型都是类似的计算)。所以你可以看到数据的存储范围、大小和精度这些都是和存储该数据的位宽相关的。
字节(Byte)是计算机的基本存储单位,1字节等于8位,也就是说一般计算机对待每个数据都是按照最低8位对齐进行存储的,所以说字节是最基本的存储单位,而字(Word)是设计计算机时给定存储单位。
数据类型是在讨论计算机存储数据的方式,而各种数据类型存储数据使用的位宽和存储结构也不尽相同,C语言中sizeof运算符可以计算一种数据类型使用的字节长度(即位宽),如sizeof(int)计算整数类型的字节长度,编程中一般都使用字节单位而不是直接使用位宽,字节乘以8可以得到位宽。
2、变量与常量
数据可以用变量或常量表示,变量是可以改变的变量,而常量则是固定不变的字面值。使用任何变量之前都需要声明变量,声明后一般会有初始化操作,声明和初始化一个变量的语法如下:
int main(void){
// 1、声明变量:类型说明符 变量名;
int number;
// 2、初始化变量:变量名 = 值;
number = 9;
// 3、声明和初始化变量
int position = 7;
return 0;
}
声明变量的实质是在内存中创建一个变量,并分配到相应的内存空间,所以一个变量的模样是这样的:变量名(对应)内存地址(对应)一块内存空间,这块内存空间中存储变量实际的值。而声明变量也是个重要的操作,我们需要知道声明一个变量,该变量即同时拥有内存地址和内存空间了,有内存空间即有值,但是该值是不确定的,初始化操作则是给该内存空间的赋值。
3、格式化转换说明
学习数据类型需要掌握相应数据类型的输出转换说明,常用的地方主要是scanf和printf函数,转换说明由%号和后面字符组成,如%d对应整数类型输出,%f对应浮点类型输出,控制输出精度可以使用%.8f表示小数位为8位,%u对应无符号类型输出,%e表示指数形式输出,十六进制输出使用%x,八进制使用%o,保留#的样式使用%#,输出指针可以直接使用%p,数据类型对应的输出格式下面讨论数据类型的时候都会讨论到。
二、布尔类型
布尔(Bool)类型是最简单的类型,C99提供_Bool布尔类型,布尔类型只有两种值,true和false,用1和0表示,输出可使用%d。不管编译器是否支持_Bool类型,布尔类型都是存在的,可以用int类型表示,_Bool类型的大小为1字节8位,不过因为只有1和0两种值,所以比较简单,声明和使用布尔类型如下:
// 声明并初始化布尔类型变量hasLogin
_Bool hasLogin = 1;
printf("%d\n", hasLogin); // => 1
printf("%d\n", sizeof(_Bool)); // 输出1,表示1字节=8位
三、数值类型
1、整数类型
short int为短整型,可简写为short,一般为2字节16位大小(数据的存储大小依赖于编译器和操作系统),输出格式为%h,十进制输出%hd,十六进制输出%ho,八进制输出%hx。
整数int类型直接使用二进制存储,如十进制3的二进制为11,int类型的大小在16位编译器上为2字节16位,在32位编译器上为4字节32位,格式输出转换说明为%d。
long int长整型,可简写为long,该类型大小一般要求等于或大于int,声明的时候使用l或L后缀,否则默认是int,输出格式转换说明为%ld。C99新增long long int类型,使用转换说明%lld,声明是使用ll或LL后缀。
unsigned用于修饰类型,表示无符号类型,unsigned int表示无符号整型,可简写为unsigned,其它类型可相应加上unsigned进行修饰,无符号类型的主要特点是,使用相同大小的位宽可表示更大的数值,输出使用%u。
signed是C90添加的关键字,即有符号类型,默认即为signed类型,也可以显式声明为signed类型。
整数类型数据一般是计算机能处理最快速度的数据,而下面讨论到的浮点类型存储结构比整数复杂,处理速度也相对慢一些。
下面是分别声明、初始化和打印各种整型变量的实例:
// 创建short短整型变量,可简写为short
short int rgb = 45;
// 创建int型变量
int count = 128;
// 创建long int长整型变量,可简写为long
long int height = 1000L;
// 超长整型,可简写为long long
long long int square = 2000LL;
// 可简写为signed,但是其它类型不可以
signed int temp = -12;
// 可简写为unsigned,但是其它类型不可以
unsigned int age = 18;
//分别格式化输出,以及输出对应数据类型的大小,\t为制表符
printf("value\tsize(byte)\n");
printf("%hd\t%d\n", rgb, sizeof(short int));
printf("%d\t%d\n", count, sizeof(int));
printf("%ld\t%d\n", height, sizeof(long int));
printf("%lld\t%d\n", square, sizeof(long long int));
printf("%d\t%d\n", temp, sizeof(signed int));
printf("%u\t%d\n", age, sizeof(unsigned int));
其中要注意,无符号输出使用%u,长整型使用%lu或者%llu,长整型的数据赋值如果没有L后缀默认是int,没有匹配赋值可能会出现精度缺失的情况,另外如果赋值超过其数据类型的范围会重头计算赋值,所以使用某一个数据类型要考虑其数据范围,以上代码输出如下:
value size(byte)
45 2
128 4
1000 4
2000 8
-12 4
18 4
2、可移植类型
C语言提供可移植类型,主要是为了满足不同的开发场景,例如存储一个数据起码需要使用int类型的32位,但是换了平台其大小可能会改变,这时候就可以使用可移植类型中的int32_t,可移植类型在stdint.h头文件中,可移植类型主要有:
精确宽度整数类型:包括int8_t、int16_t、int32_t、int64_t
C99和C11提供最小宽度整数类型:包括int_least8_t、int_least16_t、int_least32_t、int_least64
C99和C11同时提供最快最小宽度类型(速度最快):包括int_fast8_t、int_fast16、int_fast32、int_fast64
C99提供最大有符号整数类型,如intmax_t,以及最大无符号整数类型unitmax_t。
3、浮点类型
浮点类型主要用来存储实数,包括整数和小数,可使用小数形式表示如1.4,或使用指数形式表示如1.43E10,C99提供十六进制的表示形式,浮点类型数据存储主要分小数部分和指数部分存储。浮点类型又可分为单精度浮点类型float、双精度浮点类型double和longdouble,使用非正常值可能会返回NaN,浮点数上溢可能会返回inf或infinity,或下溢低于正常值。
格式化输出使用%f,long double使用%Lf,精度控制使用如%.10f,表示小数部分为10位,%e输出指数的形式。
float类型至少有6位有效数字,使用4字节32位存储,其中1字节存储指数的值和符号,3字节表示非指数部分(也有可能反过来),float类型赋值要使用f或F作为结尾,如1.0F,1.0默认作为double类型。
double至少有10位有效数字,使用8字节64位存储,因此如果没有必要尽量不要使用double,而且处理速度也会比float慢。
long
double至少与double精度相同,该类型的值需要使用L作为结尾,输出格式为%Lf或%Le,下面是浮点类型数据的使用实例:
// 创建float类型变量,需要使用f或F作为结尾
float red = 1.02f;
// 创建double类型变量
double green = 314.15926;
// 创建long double类型变量,需要使用l或L结尾
long double blue = 678.321234567L;
// 下面分别输出各个变量的小数和指数形式的值,以及它们的数据大小
printf("decimal\t\texponent\tsize\n");
printf("%f\t%e\t%d\n", red, red, sizeof(float)); // float默认6位小数位
printf("%.10f\t%e\t%d\n", green, green, sizeof(double));
printf("%Lf\t%Le\t%d\n", blue, blue, sizeof(long double)); // Windows下使用MinGW编译输出有问题,Linux下输出正常(linux下为GCC4.8.4,mingw为GCC4.9.1)
要注意long
double的使用不同的编译器或不同的版本使用可能有问题,需要控制浮点数据的精度使用%.Nf,N为小数位数,下面是程序输出结果:
decimal exponent size
1.020000 1.020000e+000 4
314.1592600000 3.141593e+002 8
-0.000000 2.600594e+221 -1449881829
4、复数类型和虚数类型
这两种类型是C99新增的可选类型,新增的复数类型有float _Complex、double _Complex和long double _Complex,新增的虚数类型有float _Imaginary、double _Imaginary和long double _Imaginary,复数和虚数一般在数据科学计算中使用,但是这两种类型在主流的编译器中并没有支持得很好,我们在实际中用到复数的时候可以使用结构体表示。
四、字符类型
字符类型数据用于存储字母或标点符号这些数据,其存储形式是通过指定的字符编码存储字符对应的数值,所以一个字符实际存储的是这个字符对应的数值,常见的字符编码有ASCII字符编码和Unicode字符编码,一般使用的字符编码是ASCII编码。字符类型的数据使用1个字节8位的内存空间存储,常见的表示形式有’A’或直接使用数值如32,C90提供八进制的表示形式如’\012’,和十六进制的表示形式如’\x12’,输出转换说明使用%c,类型名为char,实际它也是一个整数,在使用小整数的情况可以使用char。
另外像换行符、制表符、蜂鸣声等这些也是字符,但是不能直接表示出来,这时就需要使用特殊字符序列表示这些字符,称为字符转义序列,常见的转义字符有\a表示蜂鸣声,\n换行,\t制表符,\\表示\,\’表示单引号等。
字符类型的使用实例如下:
// 创建一个字符变量
char ch_01 = 'A'; // 使用字符
char ch_02 = -16; // 使用数值,'?'
char ch_03 = '\x36'; // 十六进制
printf("%c\t%d\n", ch_01, ch_01); // 分别输出字符的字面量和对应的ASCII码值
printf("%c\t\t%d\n", ch_02, ch_02);
printf("%c\t%d\n", ch_03, ch_03);
printf("size: %d\n", sizeof(char)); // 一个字符占1字节8位
对于数据类型我们主要是关心数据的表示形式、内存大小、存储结构和格式化输出转换说明,例如长整型末尾要加L,long double类型结尾也要加L,float结尾也要加F,对于每个数据类型的内存大小尤其显得重要,后面的学习可以看到C语言基本就是面向内存编程的。