当前位置: 首页 > news >正文

C语言精要(第二章:基本数据类型)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

2.1  C语言基本数据类型

    在计算机术语中,把⼆进制数中的某⼀位数又称为⼀个⽐特(bit)。⽐特这个单位对于计算机⽽⾔,在度量上是最⼩的单位。除了⽐特之外,还有字节(byte)这个术语。⼀个字节由8个⽐特构成。在某些单⽚机架构下还引⼊了半字节(nybble或nibble)这个概念,表⽰4个⽐特。然后,还有字(word)这个术语。字在不同计算机架构下表⽰的含义不同。在x86架构下,⼀个字为2个字节;⽽在ARM等众多32位RISC体系结构下,⼀个字表⽰为4个字节。随着计算机带宽的提升,能被处理器⼀次处理的数据宽度也不断提升,因此出现了双字(double word)、四字(quad word)、⼋字(octa word)等概念。双字的宽度为2个字,四字宽度为4个字,所以它们在不同处理器体系结构下所占⽤的字节个数也会不同。

2.1.1  整数

    我们⽇常⽤的整数都是⼗进制数(Decimal),也就是我们通常所说的逢⼗进⼀。因为我们⼈类有⼗根⼿指,所以⾃然⽽然地会想到采⽤⼗进制的计数和计算⽅式。然⽽,现在⼏乎所有计算机都采⽤⼆进制数(Binary)编码⽅式,所以我们⽇常所⽤到的整数如果要⽤计算机来表⽰的话,需要表⽰成⼆进制的⽅式。至于二进制、八进制、十六进制的细节就不讲了,这些在计算机原理或汇编语言教材里都会讲。倒是Big Endian(大端)和Little Endian(小端)需要了解一下,这里也不讲,大家在网上可以找到。习惯上,⽤0或0o打头的数表⽰⼋进制数,0x打头的数表⽰⼗六进制数。⽐如,0123、0777表⽰⼋进制数;0x123,0xABCD表⽰⼗六进制数。

    在计算机中,整数又分为无符号整数和有符号整数。像short、int、long、long long,都是有符号整数(有负数)类型,而要表示无符号整数(无负数)类型,只需在前面加上unsigned前缀即可。bool和char比较特别,它们本质上也是整数类型,但它们必须无符号。

    C语⾔标准中没有明确规定每⼀种整数类型所占⽤的字节数,这些全都是由C语⾔的实现来定义的,但是C语⾔标准给了若⼲约束,所以C语⾔实现应该⾄少能满⾜这些约束。为了⽅便叙述,我们这⾥仍然根据主流桌⾯端编译器(GCC、Clang)以及主流32位与64位处理器环境的实现进⾏讲解。

2.1.1.1  short类型

    short类型(标准表达为signed short int类型,其中signed与int均可省略)我们⼀般称之为短整型。在我们通常的32位及64位系统下占⽤2个字节(即16位),其最⼩值为-215(即0x8000),最⼤值为215-1(即0x7FFF)。在C语⾔执⾏环境下,其最⼤、最⼩值分别定义为<limits.h>头⽂件中的SHRT_MAX和SHRT_MIN。short类型所对应的⽆符号类型为unsigned short(标准表达为unsigned short int,其中int可省)。它通常在32位及64位系统下占2个字节,最⼩值为0,最⼤值为216-1(即0xFFFF)。在C语⾔执⾏环境中,其最⼤值定义为<limits.h>头⽂件中的USHRT_MAX。

    short类型与unsigned short类型没有特别对应的整数字⾯量,它们可直接⽤int与unsigned int相应的整数字⾯量进⾏赋值。

2.1.1.2  int类型

    ⽤关键字int声明的⼀个整数对象具有int类型。在具体的C语⾔执⾏环境中,int数据的最⼩值与最⼤值分别定义为<limits.h>头⽂件中的INT_MIN和INT_MAX。在我们常⽤的32位与64位环境中,int默认为是带符号的(相当于signed int),占⽤4个字节(即32位),其最⼩值为-231(即0x80000000),最⼤值为231-1(即0x7FFFFFFF)。int所对应的⽆符号类型是unsigned int,通常在32位与64位环境下也占⽤4个字节,最⼩值为0,最⼤值为232-1(即0xFFFFFFFF)。在具体C语⾔执⾏环境中的最⼤值定义为<limits.h>头⽂件中的UINT_MAX。

    int类型对应的整数字⾯量可直接按照⾃然⽅式书写,⽐如0、-128、127、+2233等都默认表⽰为int类型。此外,整数字⾯量可以分别使⽤⼋进制、⼗进制以及⼗六进制的⽅式进⾏表达。⼋进制的整数字⾯量表达⽅式为以0打头,⽐如:01、023、-0477这些都是属于⼋进制整数字⾯量。⽽⼗六进制整数字⾯量则是以0x或0X打头,⽐如:0x123、-0x0045、0xabcdef这些都是有效的⼗六进制整数字⾯量。⽽其他没有任何前缀的整数字⾯量都表⽰为⼗进制整数。如果想要表达⼀个unsigned int类型的整数字⾯量,可在⼀般整数字⾯量后直接添加字母u或U。本书习惯上使⽤⼤写的U。⽐如0U、01U、-128U、2048U、+2233U等都属于unsigned int类型。当然,即便字⾯量后⾯不加U后缀,这些数也能赋值给unsigned int类型的对象,因为它们会被编译器进⾏默认转换。此外,当我们要声明⼀个unsigned int类型的对象时,int可以省略。⽐如,unsigned a=0;,其中对象a的类型即为unsigned int类型,=是⼀个赋值操作符(assignment operators),将其右操作数0赋值给左操作数a。

2.1.1.3  long类型

    long类型(标准表达为signed long int类型,其中signed与int均可省略)我们⼀般称之为长整型。在我们通常的32位环境下long类型占⽤4个字节(即32位),⽽在64位系统下,当前⼏个主流桌⾯编译器就有所区别了。MSVC与VS-Clang仍然为4个字节,⽽GCC与Clang则是8个字节(即64位)。long类型所对应的⽆符号类型为unsigned long(标准表达为unsigned long int,int可省),我们⼀般称之为⽆符号长整型,它的字节长度与long类型⼀致。在C语⾔执⾏环境中,long类型的最⼩值与最⼤值分别定义为<limits.h>头⽂件中的LONG_MIN与LONG_MAX。unsigned long类型的最⼤值定义为<limits.h>头⽂件中的ULONG_MAX,其最⼩值为0。

    long类型对应的整数字⾯量是在int整数字⾯量后⾯加上英⽂字母l或L,本书都⽤⼤写字母L作为后缀。unsigned long对应的整数字⾯量是在unsigned int字⾯量后⾯加上字母l或L,通常都是以UL作为后缀。⼀般情况下,我们直接⽤int字⾯量赋值给long类型的变量也不会有问题,但是当我们要表达⼀个超出int范围的整数时,我们就得加上后缀L,否则数据可能会被截断。不过这⾥,不同的编译器会有不同⾏为。

    因为long和unsigned long在不同环境下字节长度不同,所以我们在定义⼀个整数对象时应当尽量避免使⽤long类型,除⾮涉及系统相关的⼀些属性。⽐如C语⾔标准库中将获取⽂件当前位置(ftell)等函数的返回类型作为long类型。但对于⼀般应⽤程序⽽⾔,我们需要慎⽤long类型。

2.1.1.4  long long类型

    C语⾔标准对long long(标准表达为signed long long int,其中signed与int可省)类型提得不多,仅仅阐述了long long类型的精度⾄少为long int类型的精度。不过在当前⼏⼤主流桌⾯编译器中,⽆论是32位系统还是64位系统,long long的宽度均为8个字节(即64位)。其最⼩值为-263,最⼤值为263-1。long long对应的⽆符号类型为unsigned long long(标准表达为unsigned long long int,int可省),同样也是8字节的宽度,最⼩值为0,最⼤值为264-1。在C语⾔执⾏环境下,long long的最⼩值与最⼤值分别定义为<limits.h>头⽂件中的LLONG_MIN与LLONG_MAX。unsigned long long类型的最⼤值定义为<limits.h>头⽂件中的ULLONG_MAX。

    long long对应的整数字⾯量表⽰为int整数字⾯量后加后缀ll或LL,通常采⽤LL后缀。unsigned long long对应的整数字⾯量表⽰为unsigned int整数字⾯量后⾯加后缀ll或LL,本书采⽤ULL作为后缀。

2.1.1.5  bool(布尔)类型

    在计算机编程语⾔中,布尔类型的对象是⼀个⼆值数据对象。布尔类型⽤于表达真假逻辑关系,⼀般⽤true表⽰真,false表⽰假。产⽣布尔值的表达式称为逻辑表达式或关系表达式(⽐如,⼤于、等于、⼩于、不等于等关系操作的结果)。在C11标准中,布尔类型⽤关键字_Bool声明,并说明布尔类型只要能够存放0和1值就⾏,也就是⾄少为1个⽐特。所以现在⼤部分对_Bool的C语⾔实现都将它作为1个字节的宽度。此外,_Bool类型不能⽤signed和unsigned来修饰。

    在C语⾔刚被创建的时候,它并不具备“布尔类型”这个概念,⽽仅仅⽤0(浮点数则为0.0)与对象⽐较来判定真假。如果对象的值等于零,那么表⽰“假”,否则表⽰“真”。所以,即便从C99开始引⼊了_Bool布尔类型,之前的这个约定依然沿⽤。为了能与C++兼容,C语⾔从C99标准开始就引⼊了<stdbool.h>头⽂件,⾥⾯⽤bool这个宏来定义_Bool,⽤true定义为1,false定义为0。bool、true以及false都不属于C语⾔中的关键字,它们仅属于标准库中定义的类型和常量。在C11标准的语⾔核⼼中,依然只定义了_Bool这个关键字表⽰布尔类型,⽽没有定义真值和假值的字⾯量。所以,我们在使⽤布尔类型的对象时,最好引⼊<stdbool.h>头⽂件,然后⽤bool定义布尔类型对象,⽤true表⽰真值常量,false表⽰假值常量。

2.1.1.6  char(字符)类型

    C语⾔中⽤关键字char来声明⼀个字符类型。C11标准阐明了⼀个char类型的对象必须⾄少能存放基本执⾏字符集,并且如果⼀个基本执⾏字符存放在⼀个char类型的对象中的话,那么该char类型的对象的值必须保证为⾮负整数。所以,通常C语⾔的实现都会将char类型的宽度设置为⼀个字节,这样正好⾄少能存放ASCII码字符集。

    这⾥各位需要当⼼的是,有些编译器会默认将char类型设定为⽆符号的,即char类型的整数是⼀个⽆符号的8位整数。所以,我们如果要⽤char类型定义⼀个带符号的8位整数,需要显式地使⽤signed char,这⾥signed不应该被省略。如果要声明⼀个⽆符号8位整数,则使⽤unsigned char。C11标准明确指出,char、signed char与unsigned char统称为字符类型,但三者在类型上是不兼容的,尽管char在数值表⽰范围上可能与unsigned char相同,或与signed char相同。因此如前所述,我们在编写程序的时候,⽤signed char来指定8位带符号整数;unsigned char来指定⽆符号8位整数;char⽤于指定⼀个基本字符对象。signed char的最⼤、最⼩值分别定义为<limits.h>中的SCHAR_MAX与SCHAR_MIN。unsigned char的最⼤值定义为<limits.h>中的UCHAR_MAX,最⼩值为0。char的最⼤、最⼩值分别定义为<limits.h>中的CHAR_MAX和CHAR_MIN。

    8位带符号与⽆符号的整数字⾯量没有特定的字⾯量表⽰⽅式,直接⽤int与unsigned int类型的整数字⾯量即可。⽽字符字⾯量则是⽤单引号,⾥⾯包含⼀个或多个字符。⽐如'a'、'123'都是有效的字符字⾯量。C11标准明确规定,⼀个字符字⾯量具有int类型,如果将⼀个字符字⾯量赋值给⼀个char类型的对象,那么将该字符字⾯量的最低有效位赋值给它,⽐如在我们通常的执⾏环境中就是将字符字⾯量的最低字节赋值给char类型的对象。

    在C语⾔中,不是所有的字符字⾯量都能回显在⽂本编辑器中,另外还有⼀些字符具有特殊作⽤,⽐如换⾏、制表符等,⽽且像单引号本⾝也表⽰⼀个字符字⾯量的开头或结尾,所以对于这些特殊字符,我们通过使⽤转义字符的⽅式来表⽰它们。下⾯列举⼀下C语⾔中的转义字符。

    (1)单引号:⽤\'。
    (2)双引号:⽤\"。
    (3)问号:⽤\?,不过⼀般我们可以直接使⽤'?',⽆需使⽤此转义字符。
    (4)倒斜杠\:⽤\\。
    (5)⽤⼋进制编码表⽰的⼀个字符:\后⾯紧跟1到3个⼋进制数。⽐如:\7、\12、\123等。
    (6)⽤⼗六进制编码表⽰⼀个字符:\x后⾯跟⼀个⼗六进制数。⽐如:\x0a、\x30等。
    注意:\x后⾯所跟的所有能有效表⽰为⼗六进制数的字符(即0~9,⼤写字母A~F以及⼩写字母a~f)都作为当前单个⼗六进制编码的字符,直到遇到⽆法有效表⽰⼗六进制数的字符为⽌。另外,如果\x后⾯不是紧跟⼀个有效的⼗六进制字符,那么编译器将会报错。所以\x后必须⾄少跟⼀个有效的⼗六进制字符。
    (7)\a:表⽰报警。该字符会产⽣⼀个可听到的或可见到的警报,但不改变当前的游标位置。
    (8)\b:表⽰回退。该字符将当前游标移动到当前⾏的前⼀个位置。
    (9)\f:表⽰换页。该字符将当前游标移动到下⼀个逻辑页的初始位置。
    (10)\n:表⽰换⾏。该字符将当前游标移动到下⼀⾏的初始位置。
    (11)\r:表⽰回车。该字符将当前游标移动到当前⾏的初始位置。
    (12)\t:表⽰⽔平制表符。该字符将当前游标移动到当前⾏的下⼀个⽔平表格单元位置。
    (13)\v:表⽰垂直制表符。该字符将当前游标移动到下⼀垂直表格单元位置的初始位置。
    (14)\0:表⽰空。值为0的字符在C语⾔中⼀般⽤于字符串的结束符。C语⾔标准库中的很多库函数都以\0字符作为⼀个字符串末尾的判断依据。

2.1.1.7  宽字符以及Unicode字符类型

    从C99标准中引⼊了wchar_t类型来表⽰⼀个多字节字符。wchar_t并不是C语⾔的⼀个关键字,⽽是定义在<stddef.h>头⽂件中的⼀个宏类型。wchar_t类型在不同环境,其长度也可能不⼀样,C语⾔标准没有规定它必须占⽤多少字节。当前编译器⼀般将wchar_t定义为4个字节的宽度,有些⽼的编译器可能为2个字节。宽字符的字⾯量为⼀般字符字⾯量前加⼤写字母L前缀,这⾥各位要注意,必须是⼤写字母,不能是⼩写的。⽐如,L'a'、L'你'等都属于wchar_t类型的宽字符字⾯量。宽字符在C语⾔中的定义⽐较模糊,它主要根据当前系统的语⾔环境设置,可能是UTF-16编码、GB2312、拉丁系编码格式等。宽字符在不同语⾔环境下,其相应的所显⽰出来的字样都可能会不同。由此,C语⾔标准组织在C11标准中引⼊了Unicode字符类型。

    C11中主要引⼊了UTF-8字符串、UTF-16字符以及字符串类型和UTF-32字符及字符串类型。正如在2.6节所描述的,UTF-8编码的长度范围为1~4个字节,所以在C语⾔中可以直接⽤char类型来表⽰当前⼀个UTF-8编码字符的⼀个字节,它已经涵盖了基本的ASCII码。如果要表⽰中⽂、⽇⽂等UTF-8字符的话,则需要使⽤字节数组。C11中,引⼊了新的头⽂件<uchar.h>,其中定义了UTF-16字符类型----char16_t以及UTF-32字符类型----char32_t。不过C语⾔标准委员会做得⾮常灵活,在标准中提到,当C语⾔编译器预先定义了__STDC_UTF_16__这个宏时,char16_t才保证被⽤作为UTF-16编码;当预先定义了__STDC_UTF_32__这个宏时,char32_t才保证被⽤作为UTF-32编码;否则char16_t和char32_t可能会留作其他字符编码类型使⽤。在C11中,UTF-16的字⾯量是在普通字符字⾯量前加⼩写字母u,⽐如u'a'、u'我'等都是UTF-16字符字⾯量;UTF-32的字⾯量是在普通字符字⾯量前加⼤写字母U,⽐如U'b'、U'好'等都是UTF-32字符字⾯量。同样,C11标准没有明确提到char16_t与char32_t的宽度,现在编译器⼀般将char16_t定义为unsigned short类型,占2个字节;将char32_t定义为unsignedint类型,占4个字节。

    需要注意的是,头⽂件<uchar.h>尚未包含在macOS等部分Unix系统中,所以我们⽤unsigned short来代替char16_t,或者我们可以⽤代码清单5-8的代码⾃⼰建⼀个uchar.h头⽂件。⽽在Windows系统中倒是已经包含了,各位可以在VS-Clang、MinGW等编译器中直接使⽤。不过⽆论在哪种系统环境下,GCC和Clang编译器对UTF-16以及UTF-32字符的字⾯量都已经⽀持。

2.1.1.8  size_t与ptrdiff_t类型

    size_t在之前的标准中主要⽤于sizeof操作符的返回类型。C11标准引⼊了_Alignof操作符之后,它的返回类型也是size_t。size_t定义在<stddef.h>头⽂件中。通常我们使⽤size_t作为⼀个指针(或地址)转换⼀个整数的⽅式,它⼀般是⽆符号的。在MSVC与MS-Clang编译器中,32位环境下被定义为unsigned int,64位环境下被定义为unsigned long long。在GCC和Clang编译器中,⽆论是32位还是64位环境,size_t都被定义为unsigned long,因为unsigned long在GCC和Clang中,在32位环境下是32位的,在64位环境下是64位的。这么⼀来,⽆论是哪个编译器,size_t数据类型都能存放当前系统环境下的⼀个地址长度。我们将在5.5节详细讲解sizeof操作符。

    ptrdiff_t类型⽤于两个指针相减后的结果类型,它是带符号的,在<stddef.h>头⽂件中定义。在通常C语⾔实现中,它的宽度与size_t相同,仅有的区别是ptrdiff_t是带符号的,⽽size_t则往往是⽆符号的。

2.1.2  C语言中的标准整数类型

    在前⾯所讲述的整数类型中,我们已经提起过像int、long之类的类型在不同的编译运⾏环境下可能会有不同字节长度,尤其是long类型。为了能使代码适应更⼴泛的编译执⾏环境,我们在编写C语⾔代码时可以考虑使⽤从C99标准就已经引⼊的标准整数类型。标准整数类型⼀般被定义在<stdint.h>头⽂件中,主要包含以下⼏类。
    (1)固定宽度的整数类型:当前标准能够⽀持int8_t、uint8_t、int16_t、uint16_t、int32_t、uint32_t、int64_t、uint64_t这些常⽤的类型。使⽤这些类型之后,我们就⽆需纠结signed char的字节宽度、short的字节宽度、long的字节宽度等都是多少,因为这些类型从字⾯上就已经表明了它们分别占多少字节。⽐如int8_t的宽度就是1个字节(8⽐特);int32_t则是4个字节(32⽐特)。此外,以int作为前缀的类型表⽰是带符号的类型;以uint为前缀的类型表⽰⽆符号类型。所以,我们今后写C语⾔代码时应当优先考虑这些标准整数类型。除了这些常⽤的标准整数类型外,C11标准还定义了其他固定宽度的标准类型,不过这些类型都是可选的,C语⾔实现没必要⼀定⽀持,⽐如:int24_t、uint24_t、int40_t、uint40_t、int48_t、uint48_t、int56_t、uint56_t。
    (2)最⼩宽度整数类型:这些整数类型表⽰⾄少需要满⾜所指定的⽐特位数,但允许占⽤更多的⽐特位。这些类型主要有:int_least8_t、uint_least8_t、int_least16_t、uint_least16_t、int_least32_t、uint_least32_t、int_least64_t、uint_least64_t。此外,还有可选的24、40、48、56⽐特宽度的最⼩宽度整数类型。
    (3)最快最⼩宽度的整数类型:这些整数类型往往⽤于快速计算。它们与最⼩宽度整数类型类似,⾄少需要满⾜所指定的⽐特位数。不过与最⼩宽度整数类型不同的是,它们往往具有更快速的计算速度。⽐如说,有些硬件(⽐如AMD的基于GCN架构的GPU)具有24位整数的快速乘法计算,那么当程序员使⽤了int_fast24_t时,则能暗⽰编译器⽣成利⽤这种快速乘法的指令。这些类型主要包括:int_fast8_t、uint_fast8_t、int_fast16_t、uint_fast16_t、int_fast32_t、uint_fast32_t、int_fast64_t、uint_fast64_t。另外,还有可选的24、40、48、56⽐特宽度。
    (4)能存放对象指针的整数类型:该类型有intptr_t与uintptr_t两个。前者是带符号的,后者是⽆符号的。这个类型⽤于将⼀个对象的地址或是⼀个指针对象的值⽤⼀个整数存放起来。
    (5)最⼤宽度的整数类型:这种类型表⽰当前C语⾔实现能容纳所有整数的最⼤整数类型,有intmax_t和uintmax_t这两个。

2.1.3  浮点数

    浮点数与数学中实数的概念差不多。2.75、3.16E7、7.00和2e-8都是浮点数。注意,在一个值后面加上一个小数点,该值就成为一个浮点值。所以,7是整数,7.00是浮点数。显然,书写浮点数有多种形式。

    这里关键要理解浮点数和整数的储存方案不同。计算机把浮点数分成小数部分和指数部分来表示,而且分开储存这两部分。因此,虽然7.00和7在数值上相同,但是它们的储存方式不同。在十进制下,可以把7.0写成0.7E1。这里,0.7是小数部分,1是指数部分。

    对于一些算术运算(如,两个很大的数相减),比较整数而言,浮点数损失的精度更多。

    当前主流处理器⼀般都能⽀持32位的单精度浮点数与64位的双精度浮点数的表⽰和计算,并且能遵循IEEE754-1985⼯业标准。现在此标准最新的版本是2008,其中增加了对16位半精度浮点数以及128位四精度浮点数的描述。C语⾔标准引⼊了⼀个浮点模型,可⽤来表达任意精度的浮点数,尽管当前主流C语⾔编译器尚未很好地⽀持半精度浮点数与四精度浮点数的表⽰和计算。

    当前在C语⾔中有3种实数浮点类型,分别为float、double与longdouble。C语⾔标准仅仅规定了float类型的精度是double类型精度的⼦集;double类型精度是long double精度的⼦集。在⼀般C语⾔实现中,将float类型设定为32位单精度浮点型,并采⽤IEEE754中的规格化浮点数表⽰⽅法;将double类型设定为64位双精度浮点型,并采⽤IEEE754中的规格化浮点数表⽰⽅法;long double在x86架构处理器下表⽰扩展双精度浮点(80位浮点数,⼀般占⽤16个字节),这是Intel⾃⼰扩展出来的浮点数格式。⽽在ARM等其他处理器架构下,long double可能与double类型⼀样,表⽰双精度浮点类型,但宽度仍然可能是16字节,⽽不是8字节。在⼀些GPU、DSP或嵌⼊式处理器中,浮点类型可能⽀持部分IEEE754标准,甚⾄使⽤其他表⽰法也有可能,所以各位使⽤时需要注意。但在⼤部分桌⾯环境以及智能设备上都基本可以满⾜IEEE754标准。
    在C语⾔中,浮点数字⾯量的表达⽅式⾮常丰富,最基本的就是如0.1、-100.05等这种正常的⼗进制浮点在数学上的表⽰⽅法。在⼗进制浮点数后⾯添加f或F后缀,表⽰该字⾯量是float类型浮点数;不添加任何后缀表⽰double类型浮点字⾯量;添加l或L后缀表⽰long double类型的浮点字⾯量。另外,如果浮点数的⼩数部分为0,那么我们也可以写为:10.、-5.等形式,10.相当于10.0。同样,如果整数部分为0,那么我们也可以写作为.25、.1001等形式,.25相当于0.25。
    此外,C语⾔还引⼊了对浮点数的科学计数法的表⽰。这里不细讲。

2.2  数据精度与类型转换

    ⼏乎所有计算机编程语⾔都会涉及数据精度以及类型转换的问题。⼀般来说,⼀个类型所占⽤的字节个数越多(即宽度越⼤),其精度也就越⾼。在C语⾔中,将整数精度等级称为“整数转换等级”。

    C11标准提出以下约定:

    (1)任意两个不同类型的带符号整数不会具有相同等级,即使它们所表⽰的值⼀模⼀样。⽐如在32位环境中,⼀般int类型与long类型在整数数值上表现是完全⼀样的,都表⽰32位带符号整数,但long的等级仍然⾼于int的等级。
    (2)更⾼精度的带符号整数类型的转换等级应该⾼于较低精度的带符号整数类型的等级。
    (3)⼀个⽆符号整数类型的转换等级与其相应的带符号整数类型的等级相同。⽐如,short类型的转换等级与unsigned short类型的⼀样。
    (4)具体的带符号整数的转换等级如下(从⾼到低):long long int>long int>int>short int>signed char。
    (5)char的等级应该与signed char和unsigned char相同。
    (6)size_t与ptrdiff_t的等级不应该⾼于signed long int,除⾮C语⾔实现需要⽀持更⼤的对象。就这⼀点⽽⾔,GCC与Clang编译器在64位执⾏模式下将size_t的实现定义为unsigned long,这是合乎标准的;⽽MSVC与VS-Clang却将size_t定义为了unsigned long long,这显然没有遵守标准的⼀般规定。
    (7)_Bool类型(即布尔类型)的等级在所有标准整数类型中是最低的。

2.2.1  整数晋升

    对于⼀个整数类型,如果它的精度等级在计算过程中从低转换到⾼,那么这个过程称为“整数晋升”,它是⼀个隐式转换,⽆需程序员写投射操作符进⾏类型转换。在C语⾔标准中,之所以称为整数晋升是因为低转换等级提升到⾼转换等级类型,在整数数据上不会有任何变化,⽽仅仅是类型变为更⾼等级了。

2.2.2  带符号与⽆符号整数之间的转换

    当⼀个带符号(⽆符号)整数类型要转换为另⼀个⽆符号(带符号)整数类型时,如果转换⽬标类型有⾜够⼤的精度来容纳原始类型,那么转换后的值是保持不变的。
    当⼀个原始整数类型要转换为⽆符号整数类型时,如果⽬标⽆符号整数类型⽆法容纳原始整数,那么,原始整数值通过不断地加(或减)⽬标类型最⼤能表⽰的值再加1,直到该值恰好能落在⽬标⽆符号整数可表⽰范围内。⽐如,⼀个-129的short类型要转换为unsigned char类型,那么将-129加上unsigned char最⼤能表⽰的值255再加1,即-129+(255+1)=127。127正好在unsigned char所表⽰的范围内,加法停⽌,127就是最终转换到unsigned char类型的值。当然,这个是正式的数学上的表达⽅式。在实际
应⽤中,我们⽆需那么⿇烦去计算,直接将超出⽬标⽆符号类型的位全都舍去即可。⽐如,-129的16位⼆进制数为1111111101111111,如果将它转换为8位⽆符号类型,那么我们直接将⾼8位舍去,取其低8位作为⽬标⽆符号8位整数类型即可,所以转换后的结果就是01111111,即0x7F,对应于⼗进制数127。这是⾼精度原始整数转为低精度⽆符号整数的情况。如果是⼀个低精度的带符号整型转换为更⾼精度的⽆符号整数类型,那么需要先判断原始低精度整数的符号位,如果是0(表⽰⾮负整数),则⽤0填充到⾼精度⽆符号整数;如果是1(表⽰负整数),那么⽤1填充到⾼精度⽆符号整数。⽐如,带符号8位整数-1(⼆进制数为11111111)将它转为⽆符号16位整数,结果为1111111111111111,即65535。
    当⼀个原始类型要转为带符号整数类型时,如果⽬标带符号整数类型⽆法容纳原始整数,那么结果是由实现定义的,C语⾔实现也可选择发出异常信号。⽽现在主流C语⾔编译器(⽐如MSVC、GCC和Clang)对⽬标类型为带符号整数类型的转换与⽬标为⽆符号整数类型类似。如果是⾼精度整型转低精度带符号整型,那么直接做⼆进制数的⾼位截断即可。如果是低精度的⽆符号整型转⾼精度带符号整型,那么⾼位直接⽤0扩充。⽐如,⽆符号8位整数255转为带符号16位整数,仍然是255;⽆符号16位整数1023转为带符号8位整数,即0000001111111111转为带符号8位整数,直接截断⾼8位,保留低8位,得到11111111,结果为-1。
    综上所述,对于当前主流C语⾔编译器⽽⾔,整数类型转换主要看源操作数类型,如果源操作数是⽆符号整数类型,那么做低精度到⾼精度的整数类型转换时采⽤⾼位填0的⽅式;如果源操作数是带符号整数类型,那么做低精度到⾼精度的整数类型转换时采⽤的是⾼位填符号位的⽅式。⽽对于⾼精度到低精度的整数转换,⽆论源操作数是带符号整数还是⽆符号整数,都是采⽤⾼位截断的⽅式。

    在有些较⽼版本的编译器或者把编译器警告等级调得较⾼的情况下,⾼转换等级的整数类型转换为低转换等级的整数可能会出现警告,此时我们可以⽤投射操作符(cast operator)做显式的类型转换来规避这些警告。不过,C11标准提到的是,投射操作符主要⽤于涉及指针的类型转换,对于基本数据类型,可⽤也可不⽤。
    投射操作符⾮常简单,就是⽤圆括号将某个类型包围住。⽐如:(int)、(long long)等。投射操作符的优先级仅次于单⽬操作符,⽐乘法操作符优先级⾼。

2.2.3  浮点数与浮点数的转换以及浮点数与整数之间的转换

    浮点数之间的转换与整数之间的转换不同。因为⼀般处理器架构采⽤的是IEEE754规格化浮点表⽰法,所以⼤多数⼗进制浮点数⽆法精确地⽤⼆进制浮点数来表⽰,这是由于其尾数部分实际上都是通过2n进⾏相加拟合⽽成的。所以,我们在⽐较两个浮点数的时候必须谨慎使⽤==相等性操作符。
    如果⼀个处理器架构⽀持IEEE754标准,那么单精度浮点与双精度浮点的转换直接可根据IEEE754标准中浮点数的表⽰⽅式进⾏。对于单精度浮点数转双精度浮点数,双精度浮点数的符号位以及尾数⾼位有效数都不需要变动,仅仅是尾数低位添0;⽽阶码部分则是⽤原始单精度浮点的指数加上双精度浮点数指定的中经指数偏差即可。⽽双精度转单精度则可能会产⽣精度丢失。
    C11标准提到,⼀个有限浮点型实数(即它不是⼀个⾮数NaN,也不是⼀个⽆穷⼤数INF)可以转换为除布尔类型之外的其他所有整数类型,然后其⼩数部分被丢弃,也就是说它的值向零截断。如果⼀个浮点数转为较低精度的⽆符号整数(⽐如⼀个单精度浮点数转为⼀个⽆符号8位整数),那么该浮点数是先转为与它相同宽度的带符号整数(先转signed int),然后再转为相应更低精度的⽆符号整数(再转unsigned char),还是直接转为与低精度⽆符号整数相同宽度的带符号整数(先转signed char),然后再转为该相同精度的⽆符号整数(再转unsigned char),这⼀点在标准中没有提到,也是由实现定义的。同样,当⼀个整数转为⼀个浮点数时,倘若⽬标浮点数⽆法容纳原始整数的数值范围,那么结果也是未定义的。

转载于:https://my.oschina.net/u/943779/blog/2247675

相关文章:

  • Linux实现Cisco风格ACL之空想
  • Android自动化测试+性能监控预警系统搭建
  • Python 3.x 模块
  • java 不可不知的数据库知识-----事物
  • JavaScript 浏览器对象(三)
  • 命令行程序测试自动化
  • Linux命令行翻译工具
  • 保障邮件安全
  • Linux系统_Centos7下安装Nginx
  • 源码安装Apache服务器遇到的问题及解决方法
  • 优秀互联网高级测试工程师应该具备的能力
  • raid5实现原理
  • Go 可变参数和切片
  • Tomcat7 catalina.out 日志分割
  • storm drpc实例
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • CentOS7 安装JDK
  • ES2017异步函数现已正式可用
  • JavaScript对象详解
  • Java多态
  • k8s如何管理Pod
  • leetcode讲解--894. All Possible Full Binary Trees
  • sublime配置文件
  • 京东美团研发面经
  • 前端学习笔记之观察者模式
  • 区块链分支循环
  • 通过几道题目学习二叉搜索树
  • 新书推荐|Windows黑客编程技术详解
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 如何用纯 CSS 创作一个货车 loader
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • # 数论-逆元
  • (6)STL算法之转换
  • (二)linux使用docker容器运行mysql
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (四)JPA - JQPL 实现增删改查
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (原)本想说脏话,奈何已放下
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .Net 代码性能 - (1)
  • // an array of int
  • @cacheable 是否缓存成功_Spring Cache缓存注解
  • [ 蓝桥杯Web真题 ]-布局切换
  • [17]JAVAEE-HTTP协议
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [C#]winform部署yolov5-onnx模型
  • [C#基础知识系列]专题十七:深入理解动态类型
  • [C]编译和预处理详解
  • [CC2642R1][VSCODE+Embedded IDE+IAR Build+Cortex-Debug] TI CC2642R1基于VsCode的开发环境