Chinaunix首页 | 论坛 | 博客
  • 博客访问: 616808
  • 博文数量: 87
  • 博客积分: 3399
  • 博客等级: 中校
  • 技术积分: 1422
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-17 21:20
文章分类

全部博文(87)

文章存档

2013年(1)

2012年(51)

2011年(33)

2010年(2)

分类: C/C++

2012-03-15 10:29:33

7.1应对C语言标准变更

编译器并不是都实现了C标准,不同的C语言实现会有细微的差别。

使用最新的C特性会更容易编写而且不容易出错,但可能造成在某些早期的编译器上无法工作。为了提高可移植性,要在新旧用法之间进行取舍。

7.2标示符名称的改变

某些C语言的实现把一个标识符中处出现的所有字符作为有效字符处理,而有的C实现会自动截断一个长标识符的名称的尾部。

ANSI标准所能保证的只是,C实现必须能区分前6个字符不同的外部名称,而这个定义中不区分字母的大小写。因此print_fieldsprint_float这样的名称不恰当,StateSTATE这样的命名方式也不明智。

7.3整数的大小

对于不同长度的整形:shortintlongC语言规定:

(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)/20.

-3>>1 vc6.0中为-2,而不是-1

4 移位运算要比除法运算快得多

mid = (low + high) >>1;要比mid = (low+high)/2快得多,但这里要注意low+high要确保为非负。

7.6内存位置0

null指针并不指向任何对象,除非用于赋值或比较运算,出于其他目的使用null指针都是非法的。

有的C语言实现允许对内存位置0进行读,有的允许读和写,有的不允许读。即使允许读,其内容一般也是一堆垃圾信息。

         char * p = NULL;

         cout<<*p<

因此这段程序在某些机子上输出“一堆垃圾信息”,有的运行失败。

7.7除法运算时发生的截断

对于a除以bq,余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<

7.9大小写转换

起初大小写转换被实现为宏:

#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参考手册中,规定如果指针指向的是最近一次mallocrealloccalloc分配的内存,即使这块内存已经被释放,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'); //因为在某些机器上intlong的内部表示不同,所以强制转换为int

}

void printChar(char c)

{

         printf("%c",c);

}

问题1

(*p)((int)(n%10) + '0');在这句中,用+’0’来得到整数对应的字符表示,这种方式的假定前提是机器的字符集中数字是顺序排列的,没有间隔的,这对ASCII EBCDIC字符集来说是对的,对符合ANSIC实现来说也是对的,但有的机器的字符集并非如此,故可能导致可移植性问题。

解决方法:

(*p)((int)(n%10) + '0'); 替换为(*p)("0123456789"[n % 10]);

因为一个字符串常量可以用来表示一个数组,所以在数组名出现的地方都可以用字符串常量来替换

问题2

         if(n < 0)

         {

                   (*p)('-');

                   n = -n;

         }

n为负数时,我们n=-n,这个操作可能引起溢出,因为基于2的补码的计算机允许表示对 负数的取值范围要大于正数的取值范围,即如果longk位以及一个符号位,则其表示范围为-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%10n/10,因为在整数除法和取余运算中,当一个操作数为负数时,其结果与机器的具体实现有关,因此n%10n为负数时有可能是正数,故"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;

}

阅读(1228) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~