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

x33g5p2x  于2022-04-02 转载在 其他  
字(2.7k)|赞(0)|评价(0)|浏览(175)

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字符串中的%变为%%,否则会出现打印错误。

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

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

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

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

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

  1. 标志自读+(放在%后面)的作用是,规定每个待打印的数值在输出时都应该以它的符号(正号或负号)作为第一个字符。因此,==非负数(包括0)==打印出来后,应该在最前面有一个正号。负数前面有一个负号。
  2. 空白字符作为标志字符时,它的含义是:如果某数是一个非负数,就在它的前面插入一个空白字符。如果标志字符+与空白字符同时出现在一个格式项中,最终的效果以标志字符+为准。

例如:

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

例如:

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

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

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

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

例如:

  • 可变域宽与精度

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

#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个空格

  • 有关于上面的总结:

下面对几个进行区分:

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

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

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

下面是例子:

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

下面是例子:

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

下面是例子:

相关文章