网站一般在哪里找,做一个小程序,wordpress标题数据表,网络规划设计师2022预测案例模拟题文章目录 第7章 基本类型7.1 整数类型7.1.1 C99中的整数类型7.1.2 整型常量7.1.3 C99中的整型常量7.1.4 整数溢出7.1.5 读/写整数 7.2 浮点类型7.2.1 浮点常量7.2.2 读/写浮点数 7.3 字符类型7.3.1 字符操作7.3.2 有符号字符和无符号字符7.3.3 算术类型7.3.4 转义序列7.3.5 字符… 文章目录 第7章 基本类型7.1 整数类型7.1.1 C99中的整数类型7.1.2 整型常量7.1.3 C99中的整型常量7.1.4 整数溢出7.1.5 读/写整数 7.2 浮点类型7.2.1 浮点常量7.2.2 读/写浮点数 7.3 字符类型7.3.1 字符操作7.3.2 有符号字符和无符号字符7.3.3 算术类型7.3.4 转义序列7.3.5 字符处理函数7.3.6 用scanf和printf读/写字符7.3.7 用getchar和putchar读/写字符 7.4 类型转换7.4.1 常规算术转换7.4.2 赋值过程中的转换7.4.3 C99中的隐式转换7.4.4 强制类型转换 7.5 类型定义7.5.1 类型定义的优点7.5.2 类型定义和可移植性 7.6 sizeof运算符问与答总结 第7章 基本类型
7.1 整数类型
C语言支持两种根本不同的数值类型整数类型也称整型和浮点类型也称浮点型。整数类型的值是整数而浮点类型的值则可能还有小数部分。整数类型又分为两类有符号整数和无符号整数。 有符号整数如果为正数或零那么最左边的位符号位为0如果是负数则符号位为1。因此最大的16位整数的二进制表示是0111_1111_1111_1111对应的数值是32767即2^15 - 1。 最大的32位整数的二进制表示是0111_1111_1111_1111_1111_1111_1111_1111对应的数值是 2147483647即2^31 - 1。 不带符号位最左边的位是数值的一部分的整数称为无符号整数。最大的16位无符号整数是65535即2^16 - 1而最大的32位无符号整数是4294967295即2^32 - 1。默认情况下C 语言中的整型变量都是有符号的也就是说最左位保留为符号位。若要告诉编译器变量没有符号位需要把它声明成unsigned类型。 无符号整数主要用于系统编程和底层与机器相关的应用。第20章将讨论无符号整数的常见应用在此之前我们通常回避无符号整数。 C语言的整数类型有不同的大小int类型通常为32位老的CPU上可能为16位。为了存储很大的整型C语言提供了长整型为了节省空间指示编译器以比正常存储小的空间来存储一些数这样的数称为短整型。 为了使构造的整数类型正好满足需要可以指明变量是long类型或short类型、singed类型或unsigned类型共有下列6中组合可以产生不同的类型 short int (或者short)unsigned short int (或者unsigned short)intunsigned intlong int (或者long)unsigned long int (或者unsigned long) 请注意C语言允许通过省略单词int来缩写整数类型的名字就像上面括号里那样C程序员经常会省略int。 6种整数类型的每一种所表示的取值范围都会根据机器的不同而不同但是有两条所有编译器都必须遵守的原则。
C标准要求short int、int和long int中的每一种类型都要覆盖一个确定的最小取值范围详见23.2节。其次标准要求int类型不能比short int类型短long int类型不能比int类型短。但是short int类型的取值范围有可能和int类型的范围是一样的int类型的取值范围也可以和long int的一样。 在16位机上的整数类型, 通常short int和int有相同的取值范围(int占2个字节16bits)。 在32位机上的整数类型, 通常int和long int有相同的取值范围(int占4个字节32bits)。 在64位机上的整数类型, short占2两个字节16bitsint占4个字节32bitslong占8个字节64bits。(注意有的64位机器情况可能不一样尝试使用sizeof()函数确定具体大小) 对于特定的实现确定整数类型范围的一种方法是检查limits.h头23.2节。该头是标准库的一部分其中定义了表示每种整数类型的最大值和最小值的宏。 7.1.1 C99中的整数类型
C99提供了两个额外的标准整数类型: long long int和unsigned long long int。这两个long long类型要求至少64位宽。
C99中把short int、int、long int和long long int类型以及signed char类型 7.3 节称为标准有符号整型而把 unsigned short int、unsigned int、unsigned long int和unsigned long long int类型以及unsigned char类型 7.3 节和_Bool类型 5.2 节称为标准无符号整型。
除了标准的整数类型以外C99标准还允许在具体实现时定义扩展的整数类型包括有符号的和无符号的。例如编译器可以提供有符号和无符号的128位整数类型。
7.1.2 整型常量 常量——在程序中以文本形式出现的数而不是读、写或计算出来的数。 C语言允许用十进制基数为10、八进制基数为8和十六进制基数为16形式书写整型常量。 十进制常量包含0~9中的数字但是一定不能以零开头 15、255、32767八进制常量包含0~7中的数字而且必须以零开头 017、0377、077777十六进制常量包含0~9中的数字和a~f中的字母(可以是大写也可以是小写)而且总是以0x开头 0xf、0xff、0x7ff 请注意八进制和十六进制只是书写数的方式它们不会对数的实际存储方式产生影响。整数都是以二进制形式存储的跟表示方式无关。 任何时候都可以从一种书写方式切换到另一种书写方式甚至可以混合使用10 015 0x20 的值为55十进制。八进制和十六进制更适用于底层程序的编写。
十进制整型常量的类型通常为int但如果常量的值大得无法存储在int型中那就用long int类型。如果出现long int不够用的罕见情况编译器会用unsigned long int做最后的尝试。确定八进制和十六进制常量的规则略有不同编译器会依次尝试int、unsigned int、long int和unsigned long int类型直至找到能表示该常量的类型。(有符号-无符号-有符号-无符号) 要强制编译器把常量作为长整数来处理只需在后边加上一个字母L(or l) 15L、0377L、0x7fffL 要指明是无符号常量可以在常量后边加上字母U(or u) 15U、0377U、0x7fffU L和U可以结合使用以表明常量既是长整型又是无符号的0xffffffffUL。字母L、U的顺序和大小写无所谓。 7.1.3 C99中的整型常量 在C99中以LL或ll两个字母大小写要一致结尾的整型常量是long long int型的。如果在LL或ll的前面或后面增加字母U或u则该整型常量为unsigned long long int型。 C99确定整型常量类型的规则与C89有些不同。对于没有后缀U、u、L、l、LL、ll的十进制常量其类型是int、long int或long long int中能表示该值的“最小”类型。对于八进制或者十六进制常量可能的类型顺序为int、unsigned int、long int、unsigned long int、long long int和unsigned long long int。常量后面的任何后缀都会改变可能类型的列表。例如以U或u结尾的常量类型一定是unsigned int、unsigned long int和unsigned long long int中的一种以L或l结尾的十进制常量类型一定是long int或long long int中的一种。如果常量的数值过大以至于不能用标准的整数类型表示则可以使用扩展的整数类型。
7.1.4 整数溢出 对整数执行算术运算时其结果有可能因为太大而无法表示。例如对两个int值进行算术运算时结果必须仍然能用int类型来表示否则表示结果所需的数位太多就会发生溢出。 整数溢出时的行为要根据操作数是有符号型还是无符号型来确定。有符号整数运算中发生溢出时程序的行为是未定义的。回顾4.4节的介绍可知未定义行为的结果是不确定的。最可能的情况是仅仅是运算的结果出错了但程序也有可能崩溃或出现其他意想不到的状况。 无符号整数运算过程中发生溢出时结果是有定义的正确答案对2的N次方取模其中n是用于存储结果的位数。例如如果对无符号的16位数65535加1其结果可以保证为0。 7.1.5 读/写整数 因为%d只适用于int类型。读写无符号整数、短整数和长整数需要一些新的转换指定符。 读写无符号整数时使用字母u、o或x代替转换说明中的d。如果使用u说明符该数将按十进制读写o表示八进制x表示十六进制。
unsigned int u;scanf(%u, u); /* reads u in base 10 */
printf(%u, u); /* writes u in base 10 */
scanf(%o, u); /* reads u in base 8 */
printf(%o, u); /* writes u in base 8 */
scanf(%x, u); /* reads u in base 16 */
printf(%x, u); /* writes u in base 16 */读写短整数时在d、o、u或x前面加上字母h
short s; scanf(%hd, s);
printf(%hd, s);读写长整数时在d、o、u或x前面加上字母l
long l; scanf(%ld, l);
printf(%ld, l);读写长长整数时仅限C99在d、o、u或x前面加上字母ll
long long ll; scanf(%lld, ll);
printf(%lld, ll);7.2 浮点类型 有些时候需要变量能存储带小数点的数或者能存储极大数或极小数。这类数可以用浮点因小数点是“浮动的”而得名格式进行存储。C语言提供了3种浮点类型对应三种不同的浮点格式。 float单精度浮点数。double双精度浮点数。long double扩展精度浮点数。
当精度要求不严格时例如计算带一位小数的温度float类型是很适合的类型。double提供更高的精度对绝大多数程序来说够用了。long double支持极高精度的要求很少会用到。
C标准没有说明float、double和long double类型提供的精度到底是多少因为不同计算机可以用不同方法存储浮点数。大多数现代计算机遵循IEEE 754标准即IEC 60559的规范。
单精度32位和双精度64位。数值以科学记数法的形式存储每一个数都由三部分组成符号、指数和小数。指数部分的位数说明了数值的可能大小程度而小数部分的位数说明了精度。
float类型的精度为6个数字double为15个数字。long double类型的长度随着机器的不同而变化而最常见的大小是80位和128位。在一些机器上float可以有和double相同的数值集合或者double可以有和long double相同的数值集合。可以在头float.h 23.1 节中找到定义浮点类型特征的宏。 在C99中浮点类型分为两种一种是实浮点类型包括float、double和long double类型另一种是C99新增的复数类型27.3 节包括float complex、double complex和long double complex。 7.2.1 浮点常量 浮点常量可以有许多种书写方式。例如下面这些常量全都是表示数57.0的有效方式 57.0、 57.、 57.0e0、 57E0、 5.7e1、 5.7e1、 .57e2、 570.e-1
浮点常量必须包含小数点或指数其中指数指明了对前面的数进行缩放所需的10的幂次。如果有指数则需要在指数数值前放置字母E或e。可选符号或-可以出现在字母E或e的后边。
默认情况下浮点常量以双精度数(double)的形式存储。这条规则通常不会引发任何问题因为在需要时double类型的值可以自动转换为float类型值。
为了表明只需要单精度可以在常量的末尾处加上字母F或f如57.0F而为了说明常量必须以long double格式存储可以在常量的末尾处加上字母L或l如57.0L。 冷门小知识C99提供了十六进制浮点常量的书写规范。十六进制浮点常量以0x或0X开头跟十六进制整型常量类似。这一特性很少用到。 7.2.2 读/写浮点数 转换说明%e、%f和%g用于读写单精度浮点数(float)。读写double和long double类型的值所需的转换说明略有不同。 读取double类型的值时在e、f或g前放置字母l
double d; scanf(%lf, d);/*
注意只能在scanf函数格式串中使用l不能在printf函数格式串中使用。在printf函数格式串中转换e、f和g可以用来写float类型或double类型的值。 C99允许printf函数调用中使用%le、%lf 和%lg不过字母l不起作用。
*/读写long double类型的值时在e、f或g前放置字母L
long double ld; scanf(%Lf, ld);
printf(%Lf, ld)7.3 字符类型
char类型的值可以根据计算机的不同而不同因为不同的机器可能会有不同的字符集。 当今最常用的字符集是美国信息交换标准码ASCII字符集 附录E它用7位代码表示128个字符。在ASCII码中数字0~9用0110000~0111001码来表示大写字母A~Z用1000001~1011010码来表示。ASCII码常被扩展用于表示256个字符相应的字符集Latin-1包含西欧语言和许多非洲语言中的字符。 char类型的变量可以用任意单字符赋值
char ch; ch a; /* lower-case a */
ch A; /* upper-case A */
ch 0; /* zero */
ch ; /* space *///注意字符常量需要用单引号括起来而不是双引号7.3.1 字符操作 在C语言中字符的操作非常简单因为存在这样一个事实C语言把字符当作小整数进行处理。毕竟所有字符都是以二进制的形式进行编码的而且无须花费太多的想象力就可以将这些二进制代码看成整数。 例如在ASCII码中字符的取值范围是0000000~1111111可以看成0~127的整数。字符a的值为97A的值为650的值为48而 的值为32。在C语言中字符和整数之间的关联是非常强的。 当计算中出现字符时C语言只是使用它对应的整数值。假设下面的例子采用ASCII字符集。
char ch;
int i; i a; /* i is now 97 */
ch 65; /* ch is now A */
ch ch 1; /* ch is now B */
ch; /* ch is now C */ 可以像比较数那样对字符进行比较。下面的if语句测试ch中是否含有小写字母如果有那么它会把ch转换为相应的大写字母。
if (a ch ch z) ch ch - a A;字符拥有和数相同的属性这一事实会带来一些好处。例如可以让for语句中的控制变量遍历所有的大写字母
for (ch A; ch Z; ch){//.......
}7.3.2 有符号字符和无符号字符 通常有符号字符的取值范围是-128~127无符号字符的取值范围是0~255。 C语言标准没有说明普通char类型数据是有符号型还是无符号型有些编译器把它们当作有符号型来处理有些编译器则将它们当作无符号型来处理。 标准C允许使用单词signed和unsigned来修饰char类型:
signed char sch;
unsigned char uch;/*
注意不要假设char类型默认为signed或unsigned。如果有区别用signed char或unsigned char代替char。
*/由于字符和整数之间的密切关系C89采用术语整值类型integral type来统称整数类型和字符类型。枚举类型16.5节也属于整值类型。 C99不使用术语“整值类型”而是扩展了整数类型的含义使其包含字符类型和枚举类型。C99中的_Bool型5.2节是无符号整数类型。 7.3.3 算术类型 整数类型和浮点类型统称为算术类型。 下面是C99的算术类型总结分类
整数类型 字符类型char有符号整型包括标准的signed char、short int、int、long int、long long int和扩展的无符号整型包括标准的unsigned char、unsigned short int、unsigned int、unsigned long int、unsigned long long int、_Bool和扩展的枚举类型。 浮点类型 实数浮点类型float、double、long double复数类型float_Complex、double_Complex、long double_Complex
7.3.4 转义序列 转义序列共有两种字符转义序列character escape和数字转义序列numeric escape。 转义序列\a(警报符)、\b(回退符)、\f(换页符)、\r(回车符)、\t(水平制表符)和\v(垂直制表符)表示常用的ASCII控制字符 转义序列\n表示ASCII 码的回行符转义序列\\允许字符常量或字符串包含字符\转义序列\允许字符常量包含字符而转义序列\则允许字符串包含字符转义序列\?很少使用。
字符转义序列使用起来很容易但是它们有一个问题转义序列列表没有包含所有无法打印的ASCII字符只包含了最常用的字符。字符转义序列也无法用于表示基本的128个ASCII字符以外的字符。
数字转义序列可以表示任何字符所以它可以解决上述问题。 为了把特殊字符书写成数字转义序列首先需要查找字符的八进制或十六进制值。例如ASCII码中的ESC字符十进制值为27对应的八进制值为33对应的十六进制值为1B。上述八进制或十六进制码可以用来书写转义序列。 八进制转义序列由字符\和跟随其后的一个最多含有三位数字的八进制数组成。(此数必须表示为无符号字符所以最大值通常是八进制的377)例如可以将转义字符写成\33或\033。跟八进制常量不同转义序列中的八进制数不一定要用0开头。十六进制转义序列由\x和跟随其后的一个十六进制数组成。虽然标准C对十六进制数的位数没有限制但其必须表示成无符号字符因此如果字符长度是8位那么十六进制数的值不能超过FF。若采用这种表示法可以把转义字符写成\x1b或\x1B的形式。字符x必须小写但是十六进制的数字例如b不限大小写。
作为字符常量使用时转义序列必须用一对单引号括起来。例如表示转义字符的常量可以写成\33或\x1b的形式。转义序列可能有点隐晦所以采用#define的方式给它们命名通常是个不错的主意
#define ESC \33 /* ASCII escape character */转义序列不是唯一一种用于表示字符的特殊表示法。三联序列25.3节提供了一种表示字符#、[、\、]、^、{、|、}和~的方法这些字符在一些语言的键盘上是打不出来的。 C99增加了通用字符名25.4节采用unicode编码以\uXXXX的形式表示字符X为十六进制数。通用字符名跟转义序列相似不同之处在于通用字符名可以用在标识符中。 7.3.5 字符处理函数
之前提到过如何将小写字母转换为大写字母然而一种更快捷的方法是调用C语言的toupper库函数
ch toupper(ch); /* convert ch to upper case */toupper函数在被调用时检测参数本例中为ch是否为小写字母。如果是它会把参数转换成相应的大写字母否则toupper函数会返回参数的值。
调用toupper函数的程序需要在顶部放置下面这条#include指令
#include ctype.h这个库叫做字符分类函数库包含了一些用于处理字符的函数用于测试字符的性质如字母、数字、空白字符等以及进行字符大小写转换等操作。
下面是ctype.h库中常见的一些函数
isalnum(int c):检查字符是否为字母或数字isalpha(int c):检查字符是否为字母iscntrl(int c):检查字符是否为控制字符isdigit(int c):检查字符是否为数字isgraph(int c):检查字符是否可打印但不是空白字符islower(int c):检查字符是否为小写字母isprint(int c):检查字符是否可打印ispunct(int c):检查字符是否为标点字符isspace(int c):检查字符是否为空白字符空格、制表符、换行符等isupper(int c):检查字符是否为大写字母isxdigit(int c):检查字符是否为十六进制数字tolower(int c):将字符转换为小写toupper(int c):将字符转换为大写 这些函数通常以整数作为参数接受一个字符的ASCII码值作为输入并返回一个非零值真或零值假表示字符是否具有特定的性质。这些函数可以用于字符的分类、验证和转换对于文本处理和字符处理非常有用。 7.3.6 用scanf和printf读/写字符 转换说明%c允许scanf函数和printf函数对单个字符进行读/写操作 char ch;
scanf(%c, ch); /* reads a single character */
printf(%c, ch); /* writes a single character */在读入字符前scanf函数不会跳过空白字符。如果下一个未读字符是空格那么在前面的例子中scanf函数返回后变量ch将包含一个空格。为了强制scanf函数在读入字符前跳过空白字符需要在格式串中的转换说明%c前面加上一个空格
scanf( %c, ch); /* skips white space, then reads ch *///scanf格式串中的空白意味着“跳过零个或多个空白字符”7.3.7 用getchar和putchar读/写字符
C语言还提供了另外一些读/写单个字符的方法。特别是可以使用getchar函数和 putchar函数来取代scanf函数和printf函数。putchar函数用于写单个字符:
putchar(ch);每次调用getchar函数时它会读入一个字符并将其返回。为了保存这个字符必须使用赋值操作将其存储到变量中 ch getchar(); /* reads a character and stores it in ch */事实上getchar函数返回的是一个int类型的值而不是char类型的值原因将在后续章节中讨论。因此如果一个变量用于存储getchar函数读取的字符其类型设置为int而不是char也没啥好奇怪的。和scanf函数一样getchar函数也不会在读取时跳过空白字符。 执行程序时使用gerchar函数和putchar函数是可以节约时间的相比scanf函数和printf函数。有下面两个原因 这两个函数比scanf函数和printf函数简单得多因为scanf函数和printf函数是设计用来按不同的格式读/写多种不同类型数据的。为了额外的速度提升通常getchar函数和putchar函数是作为宏14.3节来实现的。
getchar函数还有一个优于scanf函数的地方因为返回的是读入的字符(而scanf函数返回的是成功匹配的输入项数量)所以getchar函数可以应用在多种不同的C语言惯用法中包括搜索字符或跳过所有出现的同一字符的循环。 下面是两个常用的惯用法含义十分隐晦但是值得学习。
//跳过输入行的剩余部分
while (getchar() ! \n) /* skips rest of line */;/*
getchar函数对搜索字符的循环和跳过字符的循环都很有用。
下面这个语句利用getchar函数跳过不定数量空格字符
当循环终止时变量ch将包含getchar函数遇到的第一个非空白字符。
*/
while ((ch getchar()) ) /* skips blanks */;小知识如果在同一个程序中混合使用getchar函数和scanf函数请一定要注意。scanf函数往往会遗留下它“扫视”过但未读取的字符包括换行符。思考一下如果试图先读入数再读入字符的话下面的程序片段会发生什么 printf(Enter an integer: );
scanf(%d, i);
printf(Enter a command: );
command getchar();在读入i的同时scanf函数调用将留下没有消耗掉的任意字符包括但不限于换行符。getchar函数随后将取回第一个剩余字符(结果是command存储的是换行符)但这不是我们所希望的结果。 为了说明字符的读取方式下面编写一个程序来计算消息的长度。在用户输入消息后程序显示长度 /*
Enter a message: Brevity is the soul of wit.
Your message was 27 character(s) long
消息的长度包括空格和标点符号但是不包含消息结尾的换行符。
*/
#include stdio.hint main() {int count 0;printf(Enter a message: );while((getchar()) ! \n)count;printf(Your message was %d character(s) long, count);return 0;
}7.4 类型转换 C语言则允许在表达式中混合使用基本类型。在单个表达式中可以组合整数、浮点数甚至是字符。当然在这种情况下C编译器可能需要生成一些指令将某些操作数转换成不同类型使得硬件可以对表达式进行计算。 例如如果对16位short型数和32位int型数进行加法操作那么编译器将安排把16位short型值转换成32位值。如果是int型数据和float型数据进行加法操作那么编译器将安排把int型值转换成为float格式。这个转换过程稍微复杂一些因为int型值和float型值的存储方式不同。 由编译器自动处理的转换称为隐式转换(implicit conversion)。 同时允许程序员使用强制运算符执行显示转换(explicit conversion)。 当发生下列情况是会进行隐式转换
当算术表达式或逻辑表达式中操作数的类型不相同时。C语言执行所谓的常规算术转换。当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。当函数调用中的实参类型与其对应的形参类型不匹配时。当return语句中表达式的类型和函数返回值的类型不匹配时
7.4.1 常规算术转换 常规算术转换可用于大多数二元运算符包括算术运算符、关系运算符和判等运算符的操作数。 例如假设变量f为float类型变量i为int类型。常规算术转换将应用在表达式f i的操作数上因为两者的类型不同。显然把变量i转换成float类型匹配变量f的类型比把变量f转换成int类型匹配变量i的类型更安全。 常规算术转换的策略是把操作数转换成可以安全地适用于两个数值的“最狭小的”数据类型。粗略地说如果某种类型要求的存储字节比另一种类型少那么这种类型就比另一种类型更狭小。为了统一操作数的类型通常可以将相对较狭小类型的操作数转换成另一个操作数的类型来实现这就是所谓的提升。最常用的提升是整值提升integral promotion它把字符或短整数转换成int类型或者某些情况下是unsigned int类型。 执行常规算术转换的规则可以划分成两种情况
任一操作数的类型是浮点类型的情况。按照float - double - long double将类型较狭小的操作数进行提升。也就是说如果一个操作数的类型为long double那么把另一个操作数的类型转换成long double类型。否则如果一个操作数的类型为double类型那么把另一个操作数转换成double类型。否则如果一个操作数的类型是float类型那么把另一个操作数转换成float类型。注意这些规则涵盖了混合整数和浮点类型的情况。例如如果一个操作数的类型是long int类型并且另一个操作数的类型是double类型那么把long int类型的操作数转换成double类型。两个操作数的类型都不是浮点类型的情况。首先对两个操作数进行整值提升保证没有一个操作数是字符类型或短整型。然后按照int - unsigned int - long int - unsigned long int对较狭小的操作数进行提升。有一种特殊情况只有在long int类型和unsigned int类型长度相同比如32位时才会发生。在这类情况下如果一个操作数的类型是long int而另一个操作数的类型是unsigned int那么两个操作数都会转换成unsigned long int类型。 小知识当有符号操作数和无符号操作数组合起来时有符号操作数会被“转换”为无符号的值。转换过程中需要加上或者减去n1的倍数其中n是无符号类型能表示的最大值。这条规则可能会导致某些隐蔽的编程错误。 因为此类陷阱的存在所以最好尽量避免使用无符号整数特别是不要把它和有符号整数混合使用。 下面的例子显示了常规算术转换的实际执行情况
char c;
short int s;
int i;
unsigned int u;
long int l;
unsigned long int ul;
float f;
double d;
long double ld; i i c; /* c is converted to int */
i i s; /* s is converted to int */
u u i; /* i is converted to unsigned int */
l l u; /* u is converted to long int */
ul ul l; /* l is converted to unsigned long int */
f f ul; /* ul is converted to float */
d d f; /* f is converted to double */
ld ld d; /* d is converted to long double *7.4.2 赋值过程中的转换 与常规算术转换不同赋值过程中C语言会遵循另一条简单的转换规则那就是把赋值运算右边的表达式转换成左边变量的类型。如果变量的类型至少和表达式类型一样“宽”那么这种转化将没有任何障碍。 char c;
int i;
float f;
double d; i c; /* c is converted to int */
f i; /* i is converted to float */
d f; /* f is converted to double */其他情况下是有问题的。把浮点数赋值给整型变量会丢掉该数的小数部分 int i; i 842.97; /* i is now 842 */
i -842.97; /* i is now –842 */此外把某种类型的值赋给类型更狭小的变量时如果该值在变量类型范围之外那么将得到无意义的结果甚至更糟。 c 10000; /*** WRONG ***/
i 1.0e20; /*** WRONG ***/
f 1.0e100; /*** WRONG ***/这类赋值可能会导致编译器或lint之类的工具发出警告。 如果浮点常量被赋值给float型变量那么建议在浮点常量尾部加上后缀f例如f 3.1415926f; 如果没有后缀常量3.14159将是double类型可能会触发警告消息。 7.4.3 C99中的隐式转换 C99中的隐式转换和C89中的隐式转换略有不同这主要是因为C99增加了一些类型_Bool、long long类型、扩展的整数类型和复数类型 为了定义转换规则C99允许每个整数类型具有“整数转换等级”。下面按从最高级到最低级的顺序排列 long long int, unsigned long long intlong int, unsigned long intint, unsigned intshort int, unsigned short intchar, signed char, unsigned char_Bool
C99用整数提升integer promotion取代了C89中的整值提升integral promotion可以将任何等级低于int和unsigned int的类型转换为int只要该类型的所有值都可以用int类型表示或unsigned int。 C99中执行常规算术转换的规则可以分为两种情况 任一操作数的类型是浮点类型的情况。只要两个操作数都不是复数型规则与前面一样。两个操作数的类型都不是浮点类型的情况。首先对两个操作数进行整数提升。如果这时两个操作数的类型相同过程结束。否则依次尝试下面的规则一旦遇到可应用的规则就不再考虑别的规则。 如果两个操作数都是有符号型或者都是无符号型将整数转换等级较低的操作数转换为等级较高的操作数的类型。如果无符号操作数的等级高于或等于有符号操作数的等级将有符号操作数转换为无符号操作数的类型。如果有符号操作数类型可以表示无符号操作数类型的所有值将无符号操作数转换为有符号操作数的类型。否则将两个操作数都转换为与有符号操作数的类型相对应的无符号类型。 另外所有算术类型都可转换为_Bool类型。如果原始值为0则转换结果为0否则结果为1。 7.4.4 强制类型转换 虽然C语言的隐式转换使用起来非常方便但我们有些时候还需要从更大程度上控制类型转换。基于这种原因C语言提供了强制类型转换。 下面的例子显示了使用强制类型转换表达式计算float类型值小数部分的方法
float f, frac_part;
frac_part f - (int)f;强制类型转换表达式可以用于显示那些肯定会发生的类型转换
i (int)f; /* f is converted to int */顺便提一下C语言把(类型名)视为一元运算符。一元运算符的优先级高于二元运算符因此编译器会把表达式: (float) dividend / divisor 解释为 ((float) dividend) / divisor 有些时候需要使用强制类型转换来避免溢出。思考下面这个例子:
long i;
int j 1000; i j * j; /* overflow may occur */乍看之下这条语句没有问题。表达式j * j的值是1 000 000并且变量i是long int类型的所以应该能很容易地存储这种大小的值不是吗问题是当两个int类型值相乘时结果也应该是int类型的但是j * j的结果太大以致在某些机器上无法表示为int型从而导致溢出。幸运的是可以使用强制类型转换避免这种问题发生
i (long) j * j;因为强制运算符的优先级高于*所以第一个变量j会被转换成long int类型同时也迫使第二个j进行转换。注意语句
i (long) (j * j); /*** WRONG ***/是不对的因为溢出在强制类型转换之前就已经发生了。
7.5 类型定义
前面的章节中我们利用#define指令创建了一个宏但是一个更好的办法是利用所谓的类型定义特性比如
typedef int Bool;注意Bool是新类型的名字。还要注意我们使用首字母大写的单词Bool。将类型名的首字母大写不是必需的只是一些C语言程序员的习惯。
采用typedef定义Bool会导致编译器在它所识别的类型名列表中加入Bool。现在Bool类型可以和内置的类型名一样用于变量声明、强制类型转换表达式和其他地方了。例如可以使用Bool声明变量
Bool flag; /* same as int flag; */编译器将把Bool类型看成是int类型的同义词因此变量flag实际就是一个普通的int类型变量。
7.5.1 类型定义的优点
类型定义使程序更加易于理解假定程序员仔细选择了有意义的类型名。例如假设变量cash_in和变量cash_out将用于存储美元数量。可以这样做
typedef float Dollars;
Dollars cash_in, cash_out; 这样的写法比float cash_in, cash_out;更有实际意义
不仅如此类型定义还可以使程序更容易修改。如果稍后决定Dollars实际应该定义为double类型那么只需要改变类型定义就足够了
7.5.2 类型定义和可移植性 类型定义是编写可移植程序的一种重要工具。程序从一台计算机移动到另一台计算机可能引发的问题之一就是不同计算机上的类型取值范围可能不同。如果i是int类型的变量那么赋值语句 i 100000;在使用32位整数的机器上是没问题的但是在使用16位整数的机器上就会出错。 可移植性技巧为了更大的可移植性可以考虑使用typedef定义新的整数类型名。 可惜的是这种技术无法解决所有的问题因为类型定义的变化可能会影响对应变量的使用方式。我们至少需要改动使用了对应类型变量的printf函数调用和scanf函数调用比如用转换说明%ld替换%d。 C语言库自身使用typedef为那些可能因C语言实现的不同而不同的类型创建类型名。这些类型的名字经常以_t结尾比如ptrdiff_t、size_t和wchar_t。这些类型的精确定义不尽相同下面是一些常见的例子
typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;在C99中stdint.h头使用typedef定义占用特定位数的整数类型名。例如int32_t是恰好占用32位的有符号整型。这是一种有效的定义方式能使程序更易于移植。
7.6 sizeof运算符
sizeof(类型名) sizeof运算符允许程序获取存储指定类型的值所需要的内存空间其值是一个无符号整数代表存储属于类型名的值所需要的字节数。 表达式sizeof(char)的值始终为1但是对其他类型计算出的值可能会有所不同。在32位的机器上表达式sizeof(int)的值通常为4。注意sizeof运算符是一种特殊的运算符因为编译器本身通常就能够确定sizeof表达式的值。 通常情况下sizeof运算符也可以应用于常量、变量和表达式。如果i和j是整型变量那么sizeof(i)在32位机器上的值为4这和表达式sizeof(ij)的值一样。跟应用于类型时不同sizeof应用于表达式时不要求圆括号我们可以用sizeof i代替sizeof(i)。但是由于运算符优先级的问题圆括号有时还是需要的。编译器会把表达式sizeof i j解释为(sizeof i) j这是因为sizeof作为一元运算符的优先级高于二元运算符。为了避免出现此类问题本书在sizeof表达式中始终加上圆括号。 显示sizeof值要注意sizeof表达式的类型是size_t一种由实现定义的类型。size_t一定是无符号整型。 问与答 问17.1节说到%o和%x分别用于以八进制和十六进制书写无符号整数。那么如何以八进制和十六进制书写普通的有符号整数呢 答只要有符号整数的值不是负值就可以用%o和%x显示。这些转换导致printf函数把有符号整数看作无符号的换句话说printf函数将假设符号位是数的绝对值部分。只要符号位为0就没有问题。如果符号位为1那么printf函数将显示出一个超出预期的大数。 问2但是如果是负数该怎么办呢如何以八进制或十六进制书写它 答没有直接的方法可以书写负数的八进制或十六进制形式。幸运的是需要这样做的情况非常少。当然我们可以判定这个数是否为负数然后自己显示一个负号符号数的绝对值
if (i 0) printf(-%x, -i);
else printf(%x, i);问3浮点常量为什么存储成double格式而不是float格式 答由于历史的原因C语言更倾向于使用double类型float类型则被看作次要的。思考Kernighan和Ritchie的The C Programming Language一书中关于float的论述“使用float类型的主要原因是节省大型数组的存储空间或者有时是为了节省时间因为在一些机器上双精度计算的开销格外大。”经典C要求所有浮点计算都采用双精度的格式。C89和C99没有这样的要求。 问4十六进制的浮点常量是什么样子使用这种浮点常量有什么好处 答十六进制浮点常量以0x或0X开头且必须包含指数指数跟在字母P或p后面。指数可以有符号常量可以以f、F、l或L结尾。指数以十进制数表示但代表的是2的幂而不是10的幂。例如0x1.Bp3表示1.6875×2^3 13.5。十六进制位B对应的位模式为1011因为B出现在小数点的右边所以其每一位代表一个2的负整数幂把它们2^1 2^3 2^4相加得到0.6875。
十六进制浮点常量主要用于指定精度要求较高的浮点常量包括e和π等数学常量。十进制数具有精确的二进制表示而十进制常量在转换为二进制时则可能受到舍入误差的些许影响。十六进制数对于定义极值例如float.h头中宏的值常量也是很有用的这些常量很容易用十六进制表示但难以用十进制表示。 问5为什么使用%lf读取double类型的值却用%f显示它呢 答这是一个很难回答的问题。首先注意scanf函数和printf函数都是不同寻常的函数因为它们都没有将函数的参数限制为固定数量。scanf函数和printf函数有可变长度的参数列表 26.1 节。当调用带有可变长度参数列表的函数时编译器会安排float参数自动转换成为double类型其结果是printf函数无法区分float类型和double类型的参数。这解释了在printf函数调用中为何可以用%f既表示float类型又表示double类型的参数。
另外scanf函数是通过指针指向变量的。%f告诉scanf函数在所传地址位置上存储一个float类型值而%lf告诉scanf函数在该地址上存储一个double类型值。这里float和double的区别是非常重要的。如果给出了错误的转换说明那么scanf函数将可能存储错误的字节数量更不用说float类型的位模式可能不同于double类型的位模式。 问6什么时候需要考虑字符变量是有符号的还是无符号的 答如果在变量中只存储7位的字符那么不需要考虑因为符号位将为零。但是如果计划存储8位字符那么变量可能最好是unsigned char类型。思考下面的例子
ch \xdb如果已经把变量ch声明成char类型那么编译器可能选择把它看作有符号的字符来处理许多编译器这么做。只要变量ch仅作为字符来使用就不会有什么问题。但是如果ch用在一些需要编译器将其值转换为整数的上下文中那么可能就有问题了转换为整数的结果将是负数因为变量ch的符号位为1。
还有另外一种情况在一些程序中习惯使用char类型变量存储单字节的整数。如果编写了这类程序就需要决定每个变量应该是signed char类型还是unsigned char类型这就像需要决定普通整型变量应该是int类型还是unsigned int类型一样。 问7使用转义序列\?的目的是什么 答转义序列\?与三联序列25.3节有关因为三联序列以??开头。如果需要在字符串中加入??那么编译器很可能会把它误认为三联序列的开始。用\?代替第二个?可以解决这个问题。 问8既然getchar函数的读取速度更快为什么仍然需要使用scanf函数读取单个的字符呢 答虽然scanf函数没有getchar函数读取的速度快但是它更灵活。正如前面已经看到的格式串%c可以使scanf函数读入下一个输入字符 %c可以使scanf函数读入下一个非空白字符。而且scanf函数也很擅长读取混合了其他数据类型的字符。假设输入数据中包含一个整数、一个单独的非数值型字符和另一个整数。通过使用格式串%d%c%d就可以利用scanf函数读取全部三项内容。 问9在什么情况下整值提升会把字符或短整数转换成unsigned int类型 答如果int类型整数没有大到足以包含所有可能的原始类型值那么整值提升会产生unsigned int类型。因为字符的长度通常是8位所以几乎总会转换为int类型可以保证int类型至少为16位长度。有符号短整数也总可以转换为int类型但无符号短整数是有疑问的。如果短整数和普通整数的长度相同例如在16位机上那么无符号短整数必须被转换为unsigned int类型因为最大的无符号短整数在16位机上为65 535要大于最大的int类型数即32 767。 问10如果把超出变量取值范围的值赋值给变量究竟会发生什么 答:粗略地讲如果值是整值类型并且变量是无符号类型那么会丢掉超出的位数如果变量是有符号类型那么结果是由实现定义的。把浮点数赋值给整型或浮点型变量的话如果变量太小而无法承受会产生未定义的行为任何事情都可能发生包括程序终止。 问11为什么C语言要提供类型定义呢定义一个BOOL宏不是和用typedef定义一个Bool类型一样好用吗 答类型定义和宏定义存在两个重要的不同点。首先类型定义比宏定义功能更强大。具体来说数组和指针类型是不能定义为宏的。假设我们试图使用宏来定义一个“指向整数的指针”类型
#define PTR_TO_INT int *声明
PTR_TO_INT p, q, r;在处理以后会变成
int * p, q, r;可惜的是只有p是指针q和r都成了普通的整型变量。类型定义不会有这样的问题。
其次typedef命名的对象具有和变量相同的作用域规则定义在函数体内的typedef名字在函数外是无法识别的。另外宏的名字在预处理时会在任何出现的地方被替换。 问12本书中提到“编译器本身通常就能够确定sizeof表达式的值”。难道编译器不总能确定sizeof表达式的值吗 答在C89中编译器总是可以的但在C99中有一个例外。编译器不能确定变长数组8.3节的大小因为数组中的元素个数在程序执行期间是可变的。 总结 本文是作者阅读《C语言程序设计现代方法第2版·修订版》时所做笔记日后会持续更新后续章节笔记。欢迎各位大佬阅读学习如有疑问请及时联系指正希望对诸位有所帮助Thank you very much