做门的网站建设,wordpress搬家后分类打不开,有一个做名片的网站,贵州建设厅网站办事大厅开始学习第七章其余部分。7.3.4 转义序列正如在前面示例中见到的那样#xff0c;字符常量通常是用单引号括起来的单个字符。然而#xff0c;一些特殊符号#xff08;比如换行符#xff09;是无法采用上述方式书写的#xff0c;因为它们不可见#xff08;非打印字符#…开始学习第七章其余部分。
7.3.4 转义序列正如在前面示例中见到的那样字符常量通常是用单引号括起来的单个字符。然而一些特殊符号比如换行符是无法采用上述方式书写的因为它们不可见非打印字符或者无法从键盘输入。因此为了使程序可以处理字符集中的每一个字符C语言提供了一种特殊的表示法——转义序列。escape翻译成“转义”而计算机领域的escape特指符号系统的规则转换。转义序列共有两种字符转义序列 和 数字转义序列 。在3.1节已经见过了一部分字符转义序列下表给出了完整的字符转义序列集合。字符转义序列名称转义序列警报响铃符\a回退符\b换页符\f换行符\n回车符\r水平制表符\t垂直制表符\v反斜杠 \\问号 \?单引号\双引号\转义序列\a 、\b 、\f 、\r 、\t 和\v 表示常用的ASCII控制字符 转义序列\n 表示ASCII码的回行符转义序列\\ 允许字符常量或字符串包含字符\ 转义序列\ 允许字符常量包含字符 而转义序列\ 则允许字符串包含字符 转义序列\? 很少使用。字符转义序列使用起来很容易但是它们有一个问题转义序列列表没有包含所有无法打印的ASCII字符只包含了最常用的字符。字符转义序列也无法用于表示基本的128个ASCII字符以外的字符。数字转义序列可以表示任何字符所以它可以解决上述问题。为了把特殊字符书写成数字转义序列首先需要在类似附录E那样的表中查找字符的八进制或十六进制值。例如某个ASCII码转义字符十进制值为27对应的八进制值为33对应的十六进制值为1B。上述八进制或十六进制码可以用来书写转义序列。八进制转义序列 由字符\ 和跟随其后的一个最多含有三位数字的八进制数组成。此数必须表示为无符号字符所以最大值通常是八进制的377。例如可以将转义字符写成\33 或\033。跟八进制常量不同转义序列中的八进制数不一定要用0开头。十六进制转义序列 由\x 和跟随其后的一个十六进制数组成。虽然标准C对于十六进制数的位数没有限制但其必须表示成无符号字符因此如果字符长度是8位那么十六进制数的值不能超过FF 。若采用这种表示法可以把转义字符写成\x1b 或\x1B的形式。字符x 必须小写但是十六进制的数字例如b 不限大小写。作为字符常量使用时转义序列必须用一对单引号括起来。例如表示转义字符的常量可以写成\33 或\x1b 的形式。转义序列可能有点隐晦所以采用#define 的方式给它们命名通常是个不错的主意
#define ESC \33 /* ASCII escape character */正如3.1节看到的那样转义序列也可以嵌在字符串中使用。转义序列不是唯一一种用于表示字符的特殊表示法。三字符序列提供了一种表示字符# 、[ 、\ 、] 、^ 、{ 、| 、}和~ 的方法这些字符在一些国家的键盘上是打不出来的。 C99增加了通用字符名。通用字符名跟转义序列相似不同之处在于通用字符名可以用在标识符中。7.3.5 字符处理函数前面已经讲过如何使用if 语句把小写字母转换成大写字母
if (a ch ch z)ch ch -a A;但是这不是最好的方法。一种更快捷且更易于移植的转换方法是调用C语言的toupper 库函数
ch toupper(ch); /* converts ch to upper case */toupper 函数在被调用时检测参数本例中为ch 是否是小写字母。如果是它会把参数转换成相应的大写字母否则 toupper函数会返回参数的值。上面的例子采用赋值运算符把toupper 函数返回的值存储在变量ch 中。当然也可以同样简单地进行其他的处理比如存储到另一个变量中或用if 语句进行测试
if (toupper(ch) A) ...调用toupper 函数的程序需要在顶部放置下面这条#include 指令
#include ctype.htoupper 函数不是在C函数库中唯一实用的字符处理函数。后续会描述全部字符处理函数并且给出使用示例。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 */回顾3.2节的内容scanf 格式串中的空白意味着“跳过零个或多个空白字符”。因为通常情况下scanf 函数不会跳过空白所以它很容易检查到输入行的结尾检查刚读入的字符是否为换行符。例如下面的循环将读入并且忽略掉当前输入行中剩下的所有字符
do {scanf(%c, ch);
} while (ch ! \n);下次调用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 函数也不会在读取时跳过空白字符。执行程序时使用getchar 函数和putchar 函数胜于scanf 函数和printf 函数可以节约时间。getchar 函数和putchar 函数执行速度快有两个原因。第一个原因是这两个函数比scanf 函数和
printf 函数简单得多因为scanf 函数和printf 函数是设计用来按不同的格式读/写多种不同类型数据的。第二个原因是为了额外的速度提升通常getchar 函数和putchar 函数是作为宏来实现的。getchar 函数还有一个优于scanf 函数的地方因为返回的是读入的字符所以getchar 函数可以应用在多种不同的C语言惯用法中包括用在搜索字符或跳过所有出现的同一字符的循环中。思考下面这个scanf 函数循环前面我们曾用它来跳过输入行的剩余部分
do {scanf(%c, ch);
} while (ch ! \n);用getchar 函数重写上述循环得到下面的代码
do {ch getchar();
} while (ch ! \n);把getchar 函数调用移到控制表达式中可以精简循环
while ((ch getchar()) ! \n);这个循环读入一个字符把它存储在变量ch 中然后测试变量ch 是否不是换行符。如果测试结果为真那么执行循环体循环体实际为空接着再次测试循环条件从而引发读入新的字符。实际上我们并不需要变量ch 可以把getchar 函数的返回值与换行符进行比较[惯用法]
while (getchar() ! \n); /* skips rest of line */这个循环是非常著名的C语言惯用法虽然这种用法的含义是十分隐晦的但是值得学习。getchar 函数对于搜索字符的循环和跳过字符的循环都很有用。思考下面这个利用getchar 函数跳过不定数量的空格字符的语句[惯用法]
while ((ch getchar()) ); /* skips blanks */当循环终止时变量ch 将包含getchar 函数遇到的第一个非空白字符。注意如果在同一个程序中混合使用getchar 函数和scanf 函数请一定要注意。scanf 函数倾向于遗留下它“扫视”过但未读取的字符包括换行符。思考一下如果试图先读入数再读入字符的话下面的程序段会发生什么
printf(Enter an integer: );
scanf(%d, i);
printf(Enter a command: );
command getchar();在读入i 的同时scanf 函数调用将会留下没有消耗掉的任意字符包括但不限于换行符。getchar 函数随后将取回第一个剩余字符但这不是我们所希望的结果。
程序014 确定消息的长度为了说明字符的读取方式下面编写一个程序来计算消息的长度。在用户输入消息后程序显示长度Enter a message: Brevity is the soul of wit.
Your message was 27 character(s) long.消息的长度包括空格和标点符号但是不包含消息结尾的换行符。程序需要采用循环结构来实现读入字符和计数器自增操作循环在遇到换行符时立刻终止。我们既可以采用scanf 函数也可以采用getchar 函数读取字符但大多数C程序员愿意采用getchar 函数。采用简明的while 循环书写的程序如下length.c
/* Determines the length of a message */
#include stdio.h
int main(void)
{char ch;int len 0;printf(Enter a message: );ch getchar();while (ch ! \n) {len;ch getchar();}printf(Your message was %d character(s) long.\n, len);return 0;
}回顾有关while 循环和getchar 函数惯用法的讨论我们发现程序可以缩短成如下形式length2.c
/* Determines the length of a message */
#include stdio.h
int main(void)
{int len 0;printf(Enter a message: );while (getchar() ! \n)len;printf(Your message was %d character(s) long.\n, len);return 0;
}
7.4 类型转换在执行算术运算时计算机比C语言的限制更多。为了让计算机执行算术运算通常要求操作数有相同的大小即位的数量相同并且要求存储的方式也相同。计算机可能可以直接将两个16位整数相加但是不能直接将16位整数和32位整数相加也不能直接将32位整数和32位浮点数相加。C语言则允许在表达式中混合使用基本类型。在单个表达式中可以组合整数、浮点数甚至是字符。当然在这种情况下C编译器可能需要生成一些指令将某些操作数转换成不同类型使得硬件可以对表达式进行计算。例如如果对16位short 型数和32位int 型数进行加法操作那么编译器将安排把16位short 型值转换成32位值。如果是int型数据和float 型数据进行加法操作那么编译器将安排把int 型值转换成为float 格式。这个转换过程稍微复杂一些因为int 型值和float 型值的存储方式不同。因为编译器可以自动处理这些转换而无需程序员介入所以这类转换称为隐式转换 。C语言还允许程序员使用强制运算符执行显式转换 。先讨论隐式转换显式转换推迟到本节的最后进行。遗憾的是执行隐式转换的规则有些复杂主要是因为C语言有大量不同的算术类型。当发生下列情况时会进行隐式转换。当算术表达式或逻辑表达式中操作数的类型不相同时。C语言执行所谓的常用算术转换 。
① 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。② 当函数调用中的实参类型与其对应的形参类型不匹配时。③ 当return 语句中表达式的类型和函数返回值的类型不匹配时。这里将讨论前两种情况其他情况将留到第9章进行介绍。7.4.1 常用算术转换常用算术转换可用于大多数二元运算符包括算术运算符、关系运算符和判等运算符的操作数。例如假设变量f 为float 类型而变量i 为int 类型。常用算术转换将会应用在表达式f i 的操作数上因为两者的类型不同。显然把变量i 转换成float 类型匹配变量f 的类型比把变量f 转换成int 类型匹配变量i 的类型更安全。整数始终可以转换成为float 类型可能会发生的最糟糕的事是精度会有少量损失。相反把浮点数转换成为int 类型将有小数部分的损失更糟糕的是如果原始数大于最大可能的整数或者小于最小的整数那么将会得到一个完全没有意义的结果。
常用算术转换的策略是把操作数转换成可以安全地适用于两个数值的“最狭小的”数据类型。粗略地说若某种类型要求的存储字节比另一种类型少则这种类型就比另一种类型更狭小。为了统一操作数的类型通常可以将相对较狭小类型的操作数转换成另一个操作数的类型来实现这就是所谓的提升 。 最常用的提升是整值提升 它把字符或短整数转换成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 类型。注意当把有符号操作数和无符号操作数组合时把有符号操作数“转换”成无符号的值。转换过程中需要加上或者减去的倍数其中是无符号类型能表示的最大值。这条规则可能会导致某些隐蔽的编程错误。假设int 类型的变量 i 的值为-10而unsigned int 类型的变量u 的值为10。如果用 运算符比较变量 i 和变量 u 那么期望的结果应该是1真。但是在比较前变量 i 转换成为unsigned int 类型。因为负数不能被表示成无符号整数所以转换后的值将不再为-10而是加上4 294 967 296的结果假定4 294 967 295是最大的无符号整数即4 294 967 286。因而i u 比较的结果将为0。有些编译器会在程序试图比较有符号数与无符号数时给出一条类似“comparison between signedand unsigned”的警告消息。由于此类陷阱的存在所以最好尽量避免使用无符号整数特别是不要把它和有符号整数混合使用。下面的例子显示了常用算术转换的实际执行情况
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 我们从第2章开始就一直是这么做的
f 3.14159f;如果没有后辍常量3.14159 将是double 类型可能会引起警告消息。7.4.3 C99中的隐式转换C99中的隐式转换和C89中的隐式转换略有不同这主要是因为C99增加了一些类型_Bool 、long long 类型、扩展的整数类型和复数类型。为了定义转换规则C99允许每个整数类型具有“整数转换等级”。下面按从最高级到最低级的顺序排列。(1) long long int 、unsigned long long int
(2) long int 、unsigned long int
(3) int 、unsigned int
(4) short int 、unsigned short int
(5) char 、signed char 、unsigned char
(6) _Bool简单起见这里忽略了扩展的整数类型和枚举类型。C99用整数提升取代了C89中的整值提升可以将任何等级低于int 和unsigned int 的类型转换为int 只要该类型的所有值都可以用int 类型表示或unsigned int 。与C89一样C99中执行常用算术转换的规则可以划分为两种情况。任一操作数的类型是浮点类型的情况 。只要两个操作数都不是复数型规则与前面一样。两个操作数的类型都不是浮点类型的情况 。首先对两个操作数进行整数提升。如果这时两个操作数的类型相同过程结束。否则依次尝试下面的规则一旦遇到可应用的规则就不再考虑别的规则
如果两个操作数都是有符号型或者都是无符号型将整数转换等级较低的操作数转换为等级较高的操作数的类型如果无符号操作数的等级高于或等于有符号操作数的等级将有符号操作数转换为无符号操作数的类型。如果有符号操作数类型可以表示无符号操作数类型的所有值将无符号操作数转换为有符号操作数的类型。否则将两个操作数都转换为与有符号操作数的类型相对应的无符号类型。
另外所有算术类型都可以转换为_Bool 类型。如果原始值为0则转换结果为0否则结果为1。7.4.4 强制类型转换虽然C语言的隐式转换使用起来非常方便但我们有些时候还需要从更大程度上控制类型转换。基于这种原因C语言提供了强制类型转换。强制类型转换表达式的格式如下[强制转换表达式] (类型名) 表达式这里的 类型名 表示的是表达式应该转换成的类型。下面的例子显示了使用强制类型转换表达式计算float类型值小数部分的方法
float f, frac_part;
frac_part f - (int) f;强制类型转换表达式(int) f 表示把f 的值转换成int 类型后的结果。C语言的常用算术转换则要求在进行减法运算前把(int)f 转换回float 类型。f 和(int)f 的不同之处就在于f 的小数部分这部分在强制类型转换时被丢掉了。强制类型转换表达式可以用于显示那些肯定会发生的类型转换
i (int) f; /* f is converted to int *它也可以用来控制编译器并且强制它进行我们需要的转换。思考下面的例子
float quotient;
int dividend, divisor;
quotient dividend / divisor;正如现在写的那样除法的结果是一个整数在把结果存储在quotient 变量中之前要把结果转换成float 格式。但是为了得到更精确的结果可能需要在除法执行之前把dividend 和divisor 的类型转换成float 格式的。强制类型转换表达式可以完成这一点
quotient (float) dividend / divisor;变量divisor 不需要进行强制类型转换因为把变量dividend 强制转换成float 类型会迫使编译器把divisor 也转换成float 类型。
顺便提一下C语言把(类型名) 视为一元运算符。一元运算符的优先级高于二元运算符所以编译器会把表达式
(float) dividend / divisor解释为
((float) dividend) / divisor如果感觉有点混淆那么注意还有其他方法可以实现同样的效果
quotient dividend / (float) divisor;或者
quotient (float) dividend / (float) divisor;有些时候需要使用强制类型转换来避免溢出。思考下面这个例子
long i;
int j 1000;
i j * j; /* overflow may occur */乍看之下这条语句没有问题。表达式j * j 的值是1 000 000并且变量 i 是long int 类型的所以i 应该能很容易地存储这种大小的值不是吗问题是当两个int 类型值相乘时结果也应该是int 类型的但是j * j 的结果太大以致在某些机器上无法表示成int 型从而导致溢出。幸运的是可以使用强制类型转换避免这种问题的发生
i (long) j * j;因为强制运算符的优先级高于* 所以第一个变量j 会被转换成long int 类型同时也迫使第二个j 进行转换。注意语句
i (long) (j * j); /*** WRONG ***/是不对的因为溢出在强制类型转换之前就已经发生了。7.5 类型定义5.2节中我们使用#define 指令创建了一个宏可以用来定义布尔型数据
#define BOOL int但是 一个更好的设置布尔类型的方法是利用所谓的类型定义的特性
typedef int 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 将用于存储美元数量。把Dollars 声明成
typedef float Dollars;并且随后写出
Dollars cash_in, cash_out;这样的写法比下面的写法更有实际意义
float cash_in, cash_out;类型定义还可以使程序更容易修改。如果稍后决定Dollars 实际应该定义为double 类型的那么只需要改变类型定义就足够了
typedef double Dollars;Dollars 变量的声明不需要进行改变。如果不使用类型定义则需要找到所有用于存储美元数量的float 类型变量这显然不是件容易的工作并且改变它们的声明。7.5.2 类型定义和可移植性类型定义是编写可移植程序的一种重要工具。程序从一台计算机移动到另一台计算机可能引发的问题之一就是不同计算机上的类型取值范围可能不同。如果i 是int 类型的变量那么赋值语句
i 100000;在使用32位整数的机器上是没问题的但是在使用16位整数的机器上就会出错。可移植性技巧 为了更大的可移植性可以考虑使用typedef定义新的整数类型名。假设编写的程序需要用变量来存储产品数量, 取值范围在0~50 000。为此可以使用long int 类型的变量因为这样保证可以存储至少在2 147 483 647以内的数但是用户更愿意使用int 类型的变量因为算术运算时int 类型值比long int 类型值运算速度快同时int 类型变量占用的空间较少。我们可以定义自己的“数量”类型而避免使用int 类型声明数量变量
typedef int Quantity;并且使用这种类型来声明变量
Quantity q;当把程序转到使用较短整数的机器上时需要改变Quantity 的定义
typedef long Quantity可惜的是这种技术无法解决所有的问题因为Quantity 定义的变化可能会影响Quantity 类型变量的使用方式。我们至少需要改动使用了Quantity 类型变量的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 (类型名)的值是一个无符号整数代表存储属于类型名的值所需要的字节数。表达式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 这是一种由实现定义的类型。在C89中最好在显示前把表达式的值转换成一种已知的类型。size_t 一定是无符号整型所以最安全的方法是把sizeof 表达式强制转换成unsigned long 类型C89中最大的无符号类型然后使用转换说明符%lu 显示
printf(Size of int: %lu\n, (unsigned long) sizeof(int));在C99中size_t 类型可以比 unsigned long 更长。但C99中的printf 可以直接显示出size_t 类型值而不需要强制转换。方法是在转换说明中的一般整数通常用u 代码前使用字母z
printf(Size of int: %zu\n, sizeof(int)); /* C99 only */问答
1、浮点常量为什么存储成double 格式而不是float 格式答由于历史的原因C语言更倾向于使用double 类型float 类型则被看成是“二等公民”。思考Kernighan和Ritchie的The CProgramming Language 一书中关于float 的论述“使用float 类型的主要原因是节省大型数组的存储空间或者有时是为了节省时间因为在一些机器上双精度计算的开销格外大。”经典C要求所有浮点计算都采用双精度的格式。C89和C99没有这样的要求。
这让我联系到现在AI大模型精度用的就是浮点类型fp8、fp16等用它的原因之一就是能大幅节省存储空间。
2、为什么C语言要提供类型定义呢定义一个BOOL 宏不是和用typedef 定义一个Bool 类型一样好用吗答类型定义和宏定义存在两个重要的不同点。首先类型定义比宏定义功能更强大。特别是数组和指针类型是不能定义为宏的。
假设我们试图使用宏来定义一个“指向整数的指针”类型
#define PTR_TO_INT int *
声明
PTR_TO_INT p, q, r;
在处理以后将会变成
int * p, q, r;
可惜的是只有p 是指针q 和r 都成了普通的整型变量。类型定义不会有这样的问题。其次typedef 命名的对象具有和变量相同的作用域规则定义在函数体内的typedef 名字在函数外是无法识别的。另一方面宏的名字在预处理时会在任何出现的地方被替换掉。
练习题7.1节1. 给出下列整数常量的十进制值。
(a) 077
(b) 0x77
(c) 0XABCa这是八进制数按权值展开即可b这是十六进制数按权值展开即可c这是十六进制数按权值展开即可7.2节2. 下列哪些常量在C语言中不是合法的区分每一个合法的常量是整数还是浮点数。(a) 010E2
(b) 32.1E5
(c) 0790
(d) 100_000
(e) 3.978e-2(a)推测八进制表示后面是十进制指数可能不合法。验证输出了1000推测错误。修正因为后面E明确为科学计数法故010被认为是10进制浮点数会被识别成double类型的浮点数。(b) 32.1E5合法。E后面阶码为5为double类型浮点数。验证输出3210000.000000推测正确。(c) 0790不合法。8进制数这里9超过了基数范围。验证 error: invalid digit 9 in octal constantstdin(d)100_000合法整型变量但要看编译器支持情况C11之后支持。C99之前会报错[Error] invalid suffix _000 on integer constant(e) 正确e科学计数法阶码为-2。被识别成double类型的浮点数。3、下列哪些类型在C语言中不是合法的(a) short unsigned int
(b) short float
(c) long double
(d) unsigned long(a)合法 无符号短整型。(b)不合法不可以类型不能定义为短整型又定义为单精度浮点型。[Error] both short and float in declaration specifiers(c)合法c99的长浮点型(d)合法无符号长整型7.3节4. 如果变量c是char 类型那么下列哪条语句是非法的(a) i c; /* i has type int */
(b) c 2 * c - 1;
(c) putchar(c);
(d) printf(c);(a) 合法(b)合法(c)合法(d)不合法需要格式说明符。5. 下列哪条不是书写数65的合法方式假设字符集是ASCII。(a) A
(b) 0b1000001 65
(c) 0101 65
(d) 0x41 65
#include stdio.hint main(void)
{printf(%d\n, A);printf(%d\n, 0b1000001);printf(%d\n, 0101);printf(%d\n, 0x41);return 0;
}都能打印656. 对于下面的数据项指明char 、short 、int 、long 类型中哪种类型是足以存储数据的最小类型。
(a) 一个月的天数
(b) 一年的天数
(c) 一天的分钟数
(d) 一天的秒数(a)1-31 char(b) 366 short(c) 1440 short(d) 86400 int/long7.4节9. 假设变量i 和变量j 都是int 类型那么表达式i / j a 是什么类型(i / j) int类型(i / j) aa由char类型提升到int类型所以最后为int类型。10.假设变量i 是int 类型变量j 是long int 类型并且变量k 是unsigned int 类型那么表达式i (int)j * k 是什么类型(int)j被强制转为int类型j*k中j再被提升到unsigned int类型最后i也被提升到unsigned int类型。故最后是unsigned int类型。前面提到int不要与unsigned int混合计算负数计算或比较会出现意外情况11.假设变量i 是int 类型变量f 是float 类型变量d 是double 类型那么表达式i * f / d 是什么类型double类型。12. 假设变量i 是int 类型变量f 是float 类型变量d 是double 类型请解释在执行下列语句时发生了什么转换d i f;int - float - double13. 假设程序包含下列声明
char c \1;
short s 2;
int i -3;
long m 5;
float f 6.5f;
double d 7.5;给出下列每个表达式的值和类型。(a) c * i (c) f / c (e) f - d(b) s m (d) d / s (f) (int) f(a)int(b)long(c)float(d)double(e)double(f)int14. 下列语句是否总是可以正确地计算出f 的小数部分假设f 和frac_part 都是float 类型的变量
frac_part f - (int) f;如果不是那么出了什么问题数字极小会出现精度问题所以更好的方法是使用modf函数。7.5节15. 使用typedef 创建名为Int8 、Int16 和Int32 的类型。定义这些类型以便它们可以在你的机器上分别表示8位、16位和32位的整数。#include stdint.h// 定义跨平台的8/16/32位整数类型
typedef int8_t Int8; // 精确8位有符号整数
typedef uint8_t UInt8; // 精确8位无符号整数
typedef int16_t Int16; // 精确16位有符号整数
typedef uint16_t UInt16; // 精确16位无符号整数
typedef int32_t Int32; // 精确32位有符号整数
typedef uint32_t UInt32; // 精确32位无符号整数int main(void)
{return 0;
}编程题1. 如果i * i 超出了int 类型的最大取值那么6.3节的程序square2.c 将失败通常会显示奇怪的答案。运行该程序并确定导致失败的 n 的最小值。尝试把变量i 的类型改成short 并再次运行该程序。不要忘记更新printf 函数调用中的转换说明然后尝试改成long 。从这些实验中你能总结出在你的机器上用于存储整数类型的位数是多少吗
/* Prints a table of squares using a for statement */
#include stdio.h
int main(void)
{int i;short n;printf(This program prints a table of squares.\n);printf(Enter number of entries in table: );scanf(%hd, n);for (i 1; i n; i)printf(%10d%10d\n, i, i * i);return 0;
}2. 修改6.3节的程序square2.c 每24次平方后暂停并显示下列信息
Press Enter to continue...显示完上述消息后程序应该使用getchar 函数读入一个字符。getchar 函数读到用户录入的回车键才允许程序继续。/* Prints a table of squares using a for statement */
#include stdio.h
int main(void)
{int i, n;char ch;printf(This program prints a table of squares.\n);printf(Enter number of entries in table: );scanf(%d, n);getchar(); //要吃掉scanf返还的换行符for (i 1; i n; i){printf(%10d%10d\n, i, i * i);if(i % 24 0)while((ch getchar()) ! \n); }return 0;
}3. 修改7.1节的程序sum2.c 对double 型值组成的数列求和。
/* Sums a series of numbers (using long int variables) */
#include stdio.h
#include math.h
int main(void)
{double n, sum 0.0;printf(This program sums a series of numbers.\n);printf(Enter numbers (0 to terminate): );scanf(%lf, n);while (fabs(n) 1e-9) {sum n;scanf(%lf, n);}printf(The sum is: %f\n, sum);return 0;
}4. 编写程序可以把字母格式的电话号码翻译成数值格式
Enter phone number: CALLATT
2255288如果没有电话在身边参考这里给出的字母在键盘上的对应关系2ABC 3DEF 4GHI 5JKL 6MNO 7PQRS 8TUV 9WXYZ 。原始电话号码中的非字母字符例如数字或标点符号保持不变Enter phone number: 1-800-COL-LECT
1-800-265-5328可以假设任何用户输入的字母都是大写字母。#include stdio.h
#include ctype.h
int main(void)
{char c;printf(Enter phone number: );while((c getchar()) ! \n){if(isalpha(c) isupper(c)){switch (c) {case A: case B: case C:putchar(2);break;case D: case E: case F:putchar(3);break;case G: case H: case I:putchar(4);break;case J: case K: case L:putchar(5);break;case M: case N: case O:putchar(6);break;case P: case Q: case R: case S:putchar(7);break;case T: case U: case V:putchar(8);break;case W: case X: case Y: case Z:putchar(9);break;default: break;}}else putchar(c);}return 0;
}5. 在十字拼字游戏中玩家利用小卡片组成单词每个卡片包含字母和面值。面值根据字母稀缺程度的不同而不同。面值有1——AEILNORSTU2——DG3——BCMP4——FHVWY5——K8——JX10——QZ。编写程序通过对单词中字母的面值求和来计算单词的值
Enter a word: pitfall
Scrabble value: 12编写的程序应该允许单词中混合出现大小写字母。提示 使用toupper 库函数。#include stdio.h
#include ctype.h
int main(void)
{char c;int v 0;printf(Enter a word: );while((c getchar()) ! \n){c tolower(c);switch(c){case a: case e: case i: case l: case n: case o: case r: case s: case t: case u:v;break;case d: case g:v 2;break;case b: case c: case m: case p:v 3;break;case f: case h: case v: case w: case y:v 4;break;case k:v 5;break;case j: case x:v 8;break;case q: case z:v 10;break; }}printf(Scrabble value: %d, v);return 0;
}6. 编写程序显示sizeof(int) 、sizeof(short) 、sizeof(long) 、sizeof(float) 、sizeof(double) 和sizeof(long double) 的值。
#include stdio.h
int main(void)
{printf(sizeof(int) %zu\n, sizeof(int));printf(sizeof(short) %zu\n, sizeof(short));printf(sizeof(long) %zu\n, sizeof(long));printf(sizeof(long long) %zu\n, sizeof(long long));printf(sizeof(float) %zu\n, sizeof(float));printf(sizeof(double) %zu\n, sizeof(double));printf(sizeof(long double) %zu\n, sizeof(long double));return 0;
}7. 修改第3章的编程题6使得用户可以对两个分数进行加、减、乘、除运算在两个分数之间输入 、- 、* 或/ 符号。
#include stdio.hint main(void)
{int num1, denom1, num2, denom2, result_num, result_denom;char sign;printf(Enter two fractions separated by a sign:);scanf(%d/%d%c%d/%d, num1, denom1, sign, num2, denom2);switch(sign){case :result_num num1 * denom2 num2 * denom1; //通分-分子result_denom denom1 * denom2; //通分-分母break;case -:result_num num1 * denom2 - num2 * denom1; //通分-分子result_denom denom1 * denom2; //通分-分母break;case *:result_num num1 * num2; //通分-分子result_denom denom1 * denom2; //通分-分母break; case /:result_num num1 * denom2;result_denom denom1 * num2;break;default:break; }printf(The result is %d/%d\n, result_num, result_denom);return 0;
}8. 修改第5章的编程题8要求用户输入12小时制的时间。输入时间的格式为时∶分 后跟A、P、AM或PM大小写均可。数值时间和AM/PM之间允许有空白但不强制要求有空白。有效输入的示例如下
1:15P
1:15PM
1:15p
1:15pm
1:15 P
1:15 PM
1:15 p
1:15 pm可以假定输入的格式就是上述之一不需要进行错误判定。#include stdio.h
#include ctype.h
int main(void)
{int h, m;int t;char c;printf(Enter a 12-hour time: );scanf(%d:%d %c, h, m, c);t toupper(c) A ? h * 60 m : h * 60 m 12 * 60;if(t (480 583) / 2)printf(Closest departure time is 8:00 a.m., arriving at 10:06 a.m.);else if (t (583 679) / 2)printf(Closest departure time is 9:43 a.m., arriving at 11:52 a.m.);else if(t (679 767) / 2)printf(Closest departure time is 11:19 a.m., arriving at 1:31 p.m.);else if(t (767 840) / 2)printf(Closest departure time is 12:47 a.m., arriving at 3:00 p.m.);else if(t (840 945) / 2)printf(Closest departure time is 2:00 p.m., arriving at 4:08 p.m.);else if(t (9451140) / 2)printf(Closest departure time is 3:45 p.m., arriving at 5:55 p.m.);else if(t (1140 1305) / 2)printf(Closest departure time is 7:00 p.m., arriving at 9:20 p.m.);else if(t (135 480) / 2)printf(Closest departure time is 9:45 p.m., arriving at 11:58 p.m.);else printf(Closest departure time is 9:45 p.m., arriving at 11:58 p.m.);return 0;
}9. 编写程序要求用户输入12小时制的时间然后用24小时制显示该时间
Enter a 12-hour time: 9:11 PM
Equivalent 24-hour time: 21:11参考编程题8中关于输入格式的描述。#include stdio.h
#include ctype.h
int main(void) {int h, m;char c;printf(Enter a 12-hour time: );scanf(%d:%d %c, h, m, c);if (toupper(c) A) {printf(Equivalent 24-hour time: %d:%d, h, m);}else {printf(Equivalent 24-hour time: %d:%d, h 12, m);}return 0;
}
10. 编写程序统计句子中元音字母a、e、i、o、u的个数
Enter a sentence: And thats the way it is.
Your sentence contains 6 vowels.#include stdio.h
#include ctype.hint main(void) {int cnt 0;char c;printf(Enter a sentence: );while ((c getchar()) ! \n) {switch(toupper(c)){case A: case E: case I: case O : case U:cnt;break;default:break;}}printf(Your sentence contains %d vowels., cnt);return 0;
}
11. 编写一个程序根据用户输入的英文名和姓先显示姓氏其后跟一个逗号然后显示名的首字母最后加一个点
Enter a first and last name: Lloyd Fosdick
Fosdick, L.用户的输入中可能包含空格名之前、名和姓之间、姓氏之后。#include stdio.h
#include ctype.hint main(void) {char f, c;printf(Enter a first and last name: );while(!isalpha(f getchar()));while((c getchar()) ! );while((c getchar()) );putchar(c);while((c getchar()) ! (c ! \n)){putchar(c);}printf(, %c., toupper(f));return 0;
}
12. 编写程序对表达式求值
Enter an expression: 12.5*3
Value of expression: 10.5表达式中的操作数是浮点数运算符是 、- 、* 和/ 。表达式从左向右求值所有运算符的优先级都一样。#include stdio.hint main(void) {char sign;float n, r 0.0f;printf(Enter an expression: );scanf(%f, r);while ((sign getchar()) ! \n) {scanf(%f, n);if (sign ) r n;else if (sign -) r - n;else if (sign *) r * n;else if (sign /) r / n;}printf(Value of expression: %g\n, r);return 0;
}
13. 编写程序计算句子的平均词长
Enter a sentence: It was deja vu all over again.
Average word length: 3.4简单起见程序中把标点符号看作其前面单词的一部分。平均词长显示一个小数位。#include stdio.hint main(void) {char c;int words 0, cnt 0;printf(Enter a sentence: );while((c getchar()) ! \n){if(c || c .) words;if(c ! ) cnt;}printf(Average word length: %.1f, cnt * 1.0f / words);return 0;
}
14. 编写程序用牛顿方法计算正浮点数的平方根
Enter a positive number: 3
Square root: 1.73205设 x是用户输入的数。牛顿方法需要先给出 x平方根的猜测值y我们使用1。后续的猜测值通过计算 y和 x/y的平均值得到。下表给出了求解3的平方根的过程。xyx/yy和x/y的平均值3132321.51.7531.751.714291.7321431.732141.731961.7320531.732051.732051.73205注意 y的值逐渐接近x的平方根。为了获得更高的精度程序中应使用double 类型的变量代替float 类型的变量。当y的新旧值之差的绝对值小于0.00001和y的乘积时程序终止。提示 调用fabs 函数求double 类型数值的绝对值。为了使用fabs 函数需要在程序的开头包含math.h 头。#include stdio.h
#include math.h
int main(void) {int x;double y 1.0, div, avg y;printf(Enter a positive number: );scanf(%d, x);do{y avg;div x / y;avg (y div) / 2.0;}while(fabs(y - avg) 1E-5 * y);printf(Square root: %g, y);return 0;
}
15. 编程计算正整数的阶乘
Enter a positive integer: 6
Factorial of 6: 720
(a) 用short 类型变量存储阶乘的值。为了正确打印出n的阶乘n的最大值是多少
(b) 用int 类型变量重复(a)。
(c) 用long 类型变量重复(a)。
(d) 如果你的编译器支持long long 类型用long long 类型变量重复(a)。
(e) 用float 类型变量重复(a)。
(f) 用double 类型变量重复(a)。
(g) 用long double 类型变量重复(a)。
在(e)(g)几种情况下程序会显示阶乘的近似值不一定是准确值。(a)
#include stdio.h
#include math.h
int main(void) {int n;short fact 1;printf(Enter a positive integer: );scanf(%d, n);for(int i 1; i n; i){fact * i;}printf(Factorial of %d: %hd, n, fact);
}
最多能算到7!(b)最多能算到12(c)最多能算到12(d)最多能算到20(e) float类型约6-7位有效数字最大n34之后精度显著下降(f) double类型约15-16位有效数字最大n170171!会溢出为inf(g) long double类型通常80位约18-19位有效数字最大n1754具体值取决于编译器实现下次准备更新第8章-数组。