《C陷阱和缺陷》----第七章 可移植性缺陷

7.1 字符是有符号整数还是无符号整数

一个常见的错误认知就是:如果c是一个字符变量,使用(unsigned)就可得到与c等价的无符号整数。

这是会失败的,因为在将字符c转换为无符号整数时,c将首先被转换为int型整数,而此时可能得到非预期的结果。

7.2 逻辑运算符

如果我们想进行逻辑右移操作,就把我们想要进行右移的数的类型设为无符号类型,左边就会填充0,如果想进行算术右移,就一般把类型设为有符号类型。(VS2019环境下)

注意:移位操作时,移位的数目,不应该超过当前数据的类型所占的字节数,比如int型有32个字节,那么移位操作的数目应该为0到31,这在编译器上属于未定义的操作,同时,移位的数目不可以是负数。

7.3 内存位置0

除了赋值和比较运算外,出于其它任何目的使用NULL都是非法操作。

7.4 大小写转换

toupper是函数实现的,如下所示:

int toupper(int c)
{
	if(c >= 'a' && c <= 'z')
		return c+'A' - 'a';
	return c;
}

tolower与此类似

#define _toupper(c) ((c) + 'A' - 'a')
#define _tolower(c) ((c) + 'a' - 'A')

即使用toupper会对参数进行判定,如果不是小写字母就返回原来的参数,而_toupper则不会对函数进行判定。

附录

  1. 下面有两行代码:

    printf(s);
    printf("%s",s);
    

    两者的含义并不相同。第一个例子将把字符串s中的任何%s字符视为一个格式项的标志,因而其后的字符会被视为格式码。如果除%%之外的任何格式码再字符串s中出现,而后面又没有对应的参数,将会带来麻烦。而第二个例子中将会打印出任何空字符结尾的字符串。

    结论:如果我们想要这样(printf(s))进行打印,那么我们就必须将s字符串中的%变为%%,否则会出现打印错误。

  2. 如果一个short整数作为作为任何一个函数(也包括printf函数)的参数出现,它会被自动的扩展为一个正常长度的整数,即int型。

  3. 宽度修饰符绝对不会截断一个输出域(即小数点前面的数字),当我们使用宽度修饰符来按列对齐一组数字时,如果一个数值太大而不能被它所在的栏所容纳,那么它就会挤占同一行右侧紧邻数值的位置。

  4. 对于%e、%E和%f格式项,精度修饰符制定了小数点后应该出现的数字位数。除非标志(Flag)另有说明,否则仅当精度大于0时打印的数值中才会实际出现小数点。下面是例子:image-20220306223404638

  5. 对于%g和%G格式符,精度修饰符制定了打印数值中的有效数字位数。除非标志另有说明,否则非有效数字的0将被丢掉。如果小数点后不跟数字,则小数点也将被删除。

    image-20220306224021458

  6. 对于%格式项,精度修饰符(即小数点后面的数字)制定了将要从相应的字符串中打印的字符数。如果该字符串中包含的字符数少于精度修饰符所指定的字符数,输出的字符数就会少于精度修饰符所指定的数目。如果该字符串中包含的字符数多余精度修饰符所指定的字符数,输出的字符数将和精度修饰符所指定的字符数一致,即发生类似截断的情况。

    image-20220306225432533

  7. 标志自读+(放在%后面)的作用是,规定每个待打印的数值在输出时都应该以它的符号(正号或负号)作为第一个字符。因此,==非负数(包括0)==打印出来后,应该在最前面有一个正号。负数前面有一个负号。

  8. 空白字符作为标志字符时,它的含义是:如果某数是一个非负数,就在它的前面插入一个空白字符。如果标志字符+与空白字符同时出现在一个格式项中,最终的效果以标志字符+为准。

    例如:

    image-20220307095839128

    当在固定栏内按科学计数法打印数值,格式项% e和%+e要比正常的格式项%e有用的多。因为这时出现在非负数前面的正号(或者空白)保证了所有输出数值的小数点都会对齐。

    例如:

    image-20220307100958917

  9. 标志字符#的作用是对数值输出的格式进行微调,具体的格式与特定格式项有关。给%o格式项加上标志字符#的效果是:当有必要时增加数值输出的精度(只需让输出的第一个数字为0就已经做到了)。

    ==注意:%#o与0%o并不相同,因为0%o把数值0打印成00,而%#o的打印结果是0。==同理,%#x与%#X要求打印出来的十六进制数值前面分别加上0x或0X。

    标志字符#对浮点数格式的影响有两方面:

    • 它要求小数点必须被打印出来,即使小数点后没有数字也是如此
    • 如果用于%g或%G格式项,打印出的数值尾缀的0将不会被丢掉。

    例如:image-20220307102051616

  10. 可变域宽与精度

    我们如果要通过宏定义的方式来控制打印字符串的长度,比如我们大概率是会像下面这样使用:

    #define NAMESIZE 14
    printf(".......%.NAMESIZE ...",...,name,...);
    

    但是这样写一点用处也没有,因为预处理器的作用范围不能达到字符串的内部。即预处理期间是无法替换字符串内的宏定义。

    解决方案:

    我们需要用*替换修饰符宽修饰符或精度修饰符其中之一。在这种情况下,printf函数首先从参数列表中取得将要使用的域宽或精度的实际数值,然后使用该数值来打印任务。因此,上面的例子可以写成这样:

    printf("%*.%*s\n",NAMESIZE,NAMESIZE,name);
    

    上面的这段代码与下面的这段代码是一样的:

    printf("%*.%*s\n",14,14,name);
    

    看下面一个例子:

    printf("%*%\n",n);
    

    上式将在宽度为n个字符的域内以有段对齐的方式打印除一个%负号,换言之,就是先打印n-1个空白字符,后面再跟一个%负号。

    注意:如果*用于替换域宽修饰符,而与其相对用的参数的值为负数,那么效果相当于把负号作为-标识符来处理。。因此,上例中如果n为负数,输出结果首先是一个%负号,后面再跟-n-1个空格

  11. 有关于上面的总结:

    下面对几个进行区分:

    (1)%m.nd:m代表输出一共占m列,不够m列前面补空格,够m列不作任何处理,n代表这个数一共要有n列,不够前面补0,够n列不做任何处理。

    注意:m是把包括负号(正号、0x、0)在内一共是m个字符,而n是只算数字是总共n个数字。前者不够补空格,后者不够补0。

    image-20220307112620666

    在大多数场合下,我们都可以用%.来代替%0,效果非常接近。

    下面是例子:

    image-20220307114910383

    (2)%m.ns:m代表这个字符串一共输出m列,n代表取这个字符串前n个元素输出到整个m列的右侧,当n<m时,用空格补齐左边的余缺,当n>=m时,不做处理。

    下面是例子:

    image-20220307115944317

    (3)%m.nf:m代表这个数一共要输出m列,包括小数点和小数的位数,n代表小数的个数,当浮点数的小数的位数大于n时,采用四舍五入(1-5舍掉,6-9进位),小于n时,后面补0

    下面是例子:

    image-20220307120249656

  • 108
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 149
    评论
评论 149
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鹿九丸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值