分类: C/C++
2012-03-15 10:29:33
编译器并不是都实现了C标准,不同的C语言实现会有细微的差别。
使用最新的C特性会更容易编写而且不容易出错,但可能造成在某些早期的编译器上无法工作。为了提高可移植性,要在新旧用法之间进行取舍。
7.2标示符名称的改变某些C语言的实现把一个标识符中处出现的所有字符作为有效字符处理,而有的C实现会自动截断一个长标识符的名称的尾部。
ANSI标准所能保证的只是,C实现必须能区分前6个字符不同的外部名称,而这个定义中不区分字母的大小写。因此print_fields和print_float这样的名称不恰当,State和STATE这样的命名方式也不明智。
7.3整数的大小对于不同长度的整形:short、int、long,C语言规定:
(1) 三者的长度是非递减的;(2)一个int整数足够大以能够容纳任何数组的下标;(3)字符长度由硬件长度决定。
因为不同的整形长度在不同位的机器上的实现不同,为了提高可移植性,最好声明整形变量为long型。可以在程序开始定义一个新类型名,以后一改全改:
typdef long tenmil ;
7.4字符是有符号整数还是无符号整数
在将一个字符值转换为整数值时,有的编译器会将字符作为有符号数处理:将符号位进行复制;而有的编译器将其作为无符号数,在其多余的位上直接填0。
如果编程者关注一个最高位为1的字符的数值是正还是负,那么可将这个字符声明为unsigned char型,这样在转换为整数时,无论什么编译器,都会简单的在其空位填0.
(unsigned) c 可得到与c等价的无符号数的认识是错误的,因为在将c转换为无符号数的时候,c会首先被转换为int型整数,从而可能得到意想不到的结果。正确的做法:
(unsigned char) c,因为一个unsigned char型的字符在转换为无符号整数时无需被转换为int型,而是直接进行转换。
下面是在vc6.0上的测试:
char c = -25; int d = (unsigned char)c; cout< cout< |
char c = 125; int d = (unsigned char)c; cout< cout< |
7.5移位运算符
1 向右移位时,如果是无符号数,则左侧空出的位填0,如果是有符号数则有的C语言实现用0填充空出的位,有的用符号位的数字填充空出的位。
2 如果移位对象是n位,则允许移位的数目必须大于等于0,严格小于n。
即n如果是int型,则n>>32,n>>-1都是非法的。
3 有符号的向右移位运算并不等同于除以2的某次幂!
-1>>1 一般不为0(在vc6.0中为-1),而(-1)/2为0.
-3>>1 在vc6.0中为-2,而不是-1。
4 移位运算要比除法运算快得多:
mid = (low + high) >>1;要比mid = (low+high)/2快得多,但这里要注意low+high要确保为非负。
7.6内存位置0null指针并不指向任何对象,除非用于赋值或比较运算,出于其他目的使用null指针都是非法的。
有的C语言实现允许对内存位置0进行读,有的允许读和写,有的不允许读。即使允许读,其内容一般也是一堆垃圾信息。
char * p = NULL;
cout<<*p<
因此这段程序在某些机子上输出“一堆垃圾信息”,有的运行失败。
7.7除法运算时发生的截断对于a除以b商q,余r:
q = a/b; r=a%b;
我们希望满足以下三条:
1 q*b+r = a; 2如果a的正负号改变,则q的符号也跟着改变,但其绝对值不变;3 b>0时,r>=0 && r
但这3条不可能同时成立,大多数程序设计语言放弃了第3条,只余数与被除数的符号保持相同。
C语言只保证第一条,以及当a>=0,b>0时,|r|<|b|,以及r>=0;这比第2条和第3条的限制性要弱得多。
7.8随机数的大小ANSI中定义了RAND_MAX,等于随机数的最大取值,早起的c实现没有包含这个常数。
Cout<
起初大小写转换被实现为宏:
#define toupper(c) ((c)>=’a’ &&(c)<=’z’ ? (c)+’A’-‘a’ : (c))
#define tolower(c) ((c)>=’A’ && (c)<=’Z’ ? (c)+’a’-‘A’ : (c))
但如果遇到类似toupper(*p++)的情况时,后果不堪设想;
故后来又实现为函数:
int toupper(c)
{
if(c >= ‘a’ && c <= ‘z’)
return c + ‘A’- ‘a’;
return c;
}
但这样增加了函数调用的开销。
7.10首先释放,然后重新分配调用realloc时,将指向一块已经分配内存空间的指针和要重新分配的大小传递给realloc,可以调整这块内存大小,期间可能涉及到内存拷贝。
在第七版UNIX参考手册中,规定如果指针指向的是最近一次malloc、realloc、calloc分配的内存,即使这块内存已经被释放,realloc函数仍然可以工作,早起的realloc函数还要求在重新分配内存大小时需事先释放掉这块内存。现在,这种做法并不提倡。
7.11可移植性问题的一个例子将long型整数转换为其10进制表示,并对10进制表示中的每个字符调用函数指针所指向的函数:
void printNum(long n, void (*p)(char))
{
if(n < 0)
{
(*p)('-');
n = -n;
}
if(n > 10)
printNum(n/10, p);
(*p)((int)(n%10) + '0'); //因为在某些机器上int和long的内部表示不同,所以强制转换为int
}
void printChar(char c)
{
printf("%c",c);
}
问题1:
(*p)((int)(n%10) + '0');在这句中,用+’0’来得到整数对应的字符表示,这种方式的假定前提是机器的字符集中数字是顺序排列的,没有间隔的,这对ASCII 和EBCDIC字符集来说是对的,对符合ANSI的C实现来说也是对的,但有的机器的字符集并非如此,故可能导致可移植性问题。
解决方法:
将(*p)((int)(n%10) + '0'); 替换为(*p)("0123456789"[n % 10]);
因为一个字符串常量可以用来表示一个数组,所以在数组名出现的地方都可以用字符串常量来替换。
问题2:
if(n < 0)
{
(*p)('-');
n = -n;
}
当n为负数时,我们n=-n,这个操作可能引起溢出,因为基于2的补码的计算机允许表示对 负数的取值范围要大于正数的取值范围,即如果long有k位以及一个符号位,则其表示范围为-2K却不能表示2K,所以为了不出现溢出,要保证不将n转换为对应的正数。可以用一个函数判断正负号,而另一个函数针对负数进行处理,(因为负数的表示范围要大):
void printNum(long n, void (*p)(char))
{
if(n < 0)
{
(*p)('-');
printNeg(n, p);
}
else
printNeg(-n, p);
}
void printNeg(long n, void (*p)(char))
{
if(n < -10)
printNeg(n/10, p);
(*p)("0123456789"[-(n%10)]);//注意不要将其写为-n%10,因为这涉及到将n转换为整数
}
问题3:
对于n%10和n/10,因为在整数除法和取余运算中,当一个操作数为负数时,其结果与机器的具体实现有关,因此n%10当n为负数时有可能是正数,故"0123456789"[-(n%10)会出现错误。因此要检查余数是否在合理的范围内,如果不是则要适当调整两个变量:
void printNeg(long n, void (*p)(char))
{
long q = n/10;
int r = n%10;
if(r > 0)
{
r -= 10; //调整余数为负
q++; //调整商
}
if(n < -10)
printNeg(q, p);
(*p)("0123456789"[-r]);//注意不要将其写为-n%10,因为这涉及到将n转换为整数
}
因此本程序的最终高可移植版本为:
void printNum(long n, void (*p)(char))
{
if(n < 0)
{
(*p)('-');
printNeg(n, p);
}
else
printNeg(-n, p);
}
void printNeg(long n, void (*p)(char))
{
long q = n/10;
int r = n%10;
if(r > 0)
{
r -= 10; //调整余数为负
q++; //调整商
}
if(n < -10)
printNeg(q, p);
(*p)("0123456789"[-r]);//注意不要将其写为-n%10,因为这涉及到将n转换为整数
}
void printChar(char c)
{
printf("%c",c);
}
Exercise 7.2函数atol的作用是接受一个指向以null结尾的字符串的指针作为参数,返回一个对应的long型的整数值。假定:
1 作为输入参数的指针指向的字符串总是代表合法的long型整数值,因此atol无需检查输入是否越界;
2 唯一合法的输入字符是数字和正负号。输入字符串在遇到第一个非法的字符时结束。
请写出atol的一个可移植的版本。
Answer:
这里主要考虑问题是避免中间结果溢出,即使最终结果在取值范围内也是如此。如果将一个负数转换为正数再处理,很有可能发生溢出,下面的程序只是用负数和0来得到函数的结果,从而避免了溢出。
long atol(char* s)
{
long r = 0;
int neg = 0;
switch(*s) //判断第一个字符是正负号还是其他
{
case '-':
neg = 1;
/*此处没有break*/
case '+':
s++;
break;
}
while(*s >= '0' && *s <= '9')//只有字符在0-9之间时才进行计算
{
int n = *s++ - '0';
if(neg)
n = -n;
r = r * 10 + n;
}
return r;
}