名著阅读 > C语言解惑 > 16.3 设计存在的问题 >

16.3 设计存在的问题

第一篇已经讨论了很多典型错误,可以供学习参考。本节主要列举一些典型问题。这里没有用错误一词,而改用问题,就是说设计的语句本身没有错误,但没有达到要求,或者虽然达到要求,但存在效率问题。

16.3.1 没有涵盖全部条件

有时没有仔细审题,漏掉了控制程序执行的条件。请看下面的例子。

【例16.4】这个程序对输出的数据进行适当运算之后,如果a<b,则交换它们的值,然后输出两个数的关系。请找出该程序的问题。


#include <stdio.h>
int main
( 
)
{
     int a=0
,b=0
;
     printf
("
输入两个整数:"
,a
,b
);
     scanf
("%d%d"
,&a
,&b
);
     a=
(a=
(5*a
,3*a 
),a+9
);
     if
(a<b
) {
           a+=b
;b=a-b
;a-=b
;
      }
      printf
("%d>%d\n"
,a
,b
);
      return 0
;
}
  

这个程序只是判断“a<b”的情况,忽视了“a=b”的情况,运行时会产生如下结果:


输入两个整数:0 0
9>9
  

可能会有人问为何输出语句没有区分“a>b”和“a<b”的情况,其实在“a<b”的情况里,已经调整为“a>b”,所以只需一个输出语句。

在对a和b进行运算之后,先增加如下判定条件:


if
(a==b
)   printf
("%d=%d\n"
,a
,b
);
  

运行结果为:


输入两个整数:
0 9
9=9
9>9
  

由此可见,第2个的输出语句变成两者的公用语句。可能有人认为只要简单地将这条输出语句限制在“a<b”的复合语句之中执行即可,即:


if
(a<b
) {
      a+=b
;b=a-b
;a-=b
;
      printf
("%d>%d\n"
,a
,b
);
}
  

这是行不通的,因为破坏了原来的正确路径,当“a>b”,不需要执行第2个判断语句时,就没有相应的输出语句。即当输入“2-2”时,就不执行输出语句,造成错误。

为了不影响原来的输出,应该在输出“a=b”之后,直接结束程序的运行。下面就是修改后的正确程序。


#include <stdio.h>
int main
( 
)
{
     int a=0
,b=0
;
     printf
("
输入两个整数:"
,a
,b
);
     scanf
("%d%d"
,&a
,&b
);
     a=
(a=
(5*a
,3*a 
),a+9
);
     if
(b>a
)  b-=a
;
     else      b+=a
;
     if
(a==b
) {
           printf
("%d=%d\n"
,a
,b
);
           return 0
;
     }
      if
(a<b
) {
           a+=b
;b=a-b
;a-=b
;
     }
     printf
("%d>%d\n"
,a
,b
);
     return 0
;
}
  

三种情况的运行示范如下。


输入两个整数:
2 -2
15>13
输入两个整数:
2 0
15=15
输入两个整数:
5 9
33>24
  

需要注意的是,不能将程序的第2个分支部分修改为如下形式:


if
(a<b
)  printf
("%d>%d\n"
,b
,a
);
else     printf
("%d>%d\n"
,a
,b
);
  

这样的修改虽然保证了输出结果正确,但不符合原程序的要求,即交换a和b的值。

【例16.5】下面是一个求复数除法的程序。


typedef struct {
       double re
, im
;
}complex
;
complex p
(complex x
, complex y
)
{
   double d
;
   complex z
;
   d = y.re*y.re + y.im*y.im
;
   if
(d==0
) return z
;
   z.re = 
(x.re * y.re + x.im*y.im
)/d
;
   z.im = 
(x.im * y.re - x.re*y.im
)/d
;
   return
( z 
);
}
#include <stdio.h>
void main 
( 
)
{
     complex a
,b
,c
;
     a.im=0
;  a.re=1
;  b.im=1.0
;  b.re=1.0
;
     c=p
(a
,b
);
     printf
("%lf + %lfi\n"
,c.re
,c.im
);
}
  

这个程序的输出为:0.500000+-0.500000i

要求程序的输出为:0.500000-0.500000i

修改程序的设计,使它满足需要。这个程序能处理除数为零的情况吗?

【解答】要解决这个问题,可以简单地使用判断语句判断虚部的符号位,例如:


if
(c.im >= 0
) printf
("%lf + %lfi\n"
, c.re
, c.im
);
else printf
("lf - %lfi\n"
, c.re
, -c.im
);
  

也可以将符号存入一个字符型的变量中,作为符号位。假设符号位为flag,当符号为正时,flag的值为'+',符号为负时,其值为'\0'。这时就可以使用统一的输出语句


printf
("%lf%c%lfi\n"
, c.re
, flag
, c.im
);
  

不过,它的输出格式没有前者灵活。

在主程序中,没有区分除数为零的情况,所以这个程序不能处理除数为零的情况。

在除法程序中,当除数为零,执行语句


if
(d == 0
) return z
;
  

时,z是没有被初始化的,主程序的输出语句将输出随机数字。可能有的人认为可以使用语句


return 1
;
  

来解决这个问题。其实,这样也是不行的。因为p返回结构类型的函数,返回1变成返回整数值,与原来的类型不符,编译就会报错。对错误处理是使用exit函数,即


exit
(1
);
  

如果使用exit函数,需要增加头文件并修改函数名,这里暂不使用这种方法。

在p函数里将z初始化为0,当除数为0时,给出除数为零的信息并设置z.re为-1,通过语句


return z
;
  

直接退出函数,即改为


if
(d==0
) {
      printf
("
除数为零,结束运行。\n"
);
      z.re=-1
;
      return z
;
}
  

因为只是退出p函数,所以主程序里还需要处理除数为零的信息。这可以用p函数里面为z设置的信息来处理,即


if
(c.re == -1
)  return 0
;
  

完整的程序如下。


#include <stdio.h>
typedef struct {
      double re
, im
;
}complex
;
complex p
(complex x
, complex y
)
{
   double d
;
   complex z
;   z.re=0
; z.im=0
;
   d = y.re*y.re + y.im*y.im
;
   if
(d==0
) {
         printf
("
除数为零,结束运行。\n"
);
         z.re=-1
;
         return z
;
   }
   z.re = 
(x.re * y.re + x.im*y.im
)/d
;
   z.im = 
(x.im * y.re - x.re*y.im
)/d
;
   return
( z 
);
}
int main 
( 
)
{
     complex a
,b
,c
;
     a.im=0
;  a.re=1
;  b.im=1.0
;  b.re=1.0
;
     c=p
(a
,b
);
     if
(c.re==-1
)  return 0
;
     if
(c.im>=0
) printf
("%lf + %lfi\n"
,c.re
,c.im
);
     else printf
("%lf - %lfi\n"
,c.re
, -c.im
);
     return 0
;
}
  

如果使用exit函数,就可以直接在函数里结束程序运行,免去在主函数里还要判别的重复动作。不过,这时不能再使用p函数名,p的名字在stdlib.h中已经有定义,所以把函数名改为pe。使用字符变量flag存储符号。

完整的程序如下。


#include <stdlib.h>
#include <stdio.h>
typedef struct {
  double re
, im
;
}complex
;
complex pe
(complex x
, complex y
)
{
   double d
;
   complex z
;
   d = y.re*y.re + y.im*y.im
;
   if
(d==0
) {
        printf
("
被除数为零,结束运行。\n"
);
        exit
(1
);
   }
   z.re = 
(x.re * y.re + x.im*y.im
)/d
;
   z.im = 
(x.im * y.re - x.re*y.im
)/d
;
   return
( z 
);
}
int main 
( 
)
{
     complex a
,b
,c
;
     char flag='\0'
;
     printf
("
输入第1
个复数:"
);
     scanf
("%lf%lf"
,&a.re
,&a.im
);
     printf
("
输入第2
个复数:"
);
     scanf
("%lf%lf"
,&b.re
,&b.im
);
     c=pe
(a
,b
);
     if
(c.im>=0
) {
           flag='+'
;
           printf
("%lf%c%lfi\n"
,c.re
,flag
,c.im
);
      }
     else printf
("%lf%c%lfi\n"
,c.re
,flag
,c.im
);
   return 0
;
}
  

程序示范运行后的输出结果如下。


输入第1
个复数:
1 0
输入第2
个复数:
1 1
0.500000-0.500000i
输入第1
个复数:
4 3
输入第2
个复数:
2 1
2.200000+0.400000i
输入第1
个复数:
5 9
输入第2
个复数:
0 0
除数为零,结束运行。
  

其实,如果是自己编写程序,一般都要自力更生,不要依靠被调用的程序。也就是在自己组织输入数据时,应该剔除不合理的数据。

16.3.2 条件超出范围

这种情况是指设计的条件过宽,超出范围而造成程序运行结果不正确。

【例16.6】这是计算具有从1开始的10个自然数的数组前5项之和的程序,要求将计算结果用如下形式输出。


1+2+3+4+5=15
  

下面是它的源程序。


#include <stdio.h>
int main 
( 
)
{
     int a[ ]={1
,2
,3
,4
,5
,6
,7
,8
,9
,10}
;
     int i=0
, sum=0
;
     for
(i=0
;i<5
;i++
){
           sum=sum+a[i]
;
           printf
("%d+"
,a[i]
);
     }
     printf
("=%d\n"
,sum
);
}
  

这个程序的实际输出为:


1+2+3+4+5+=15
  

由输出结果可见,比要求多输出一个“+”号。一种方法是在for循环体内解决,例如将1条输出语句改为if~else语句实现。


if
(i 
!= 4
) printf
("%d+"
,a[i]
);
else printf
("%d"
,a[i]
);
  

另一种方法是按计算顺序解决,把最后一次的计算剥离出去单独处理,这就不需要判断语句了。完整的程序如下。


#include <stdio.h>
int main 
( 
)
{
     int a[ ]={1
,2
,3
,4
,5
,6
,7
,8
,9
,10}
;
     int i=0
, sum=0
;
     for
(i=0
;i<4
;i++
){
           sum=sum+a[i]
;
           printf
("%d+"
,a[i]
);
     }
     printf
("%d=%d\n"
,a[i]
,sum+a[i]
);
     return 0
;
}
  

【例16.7】下面的程序用来求1+2+3+…+n≤10000时的最大的n值。


#include <stdio.h>
int main
( 
)
{
     int sum
, i
;
     sum=0
;
     i=0
;
     while
(sum<=10000
)
     {
           ++i
;
           sum+=i
;
     }
     printf
("n=%d\n"
,i
);
     return 0
;
}
  

程序输出为:


n=141
  

这个计算结果并不正确,因为语句


sum<=10000
  

判定的条件是必要条件。计算肯定要使条件满足才能停止循环。符合条件时的i值已经超过1次,所以应该减去1次,即140次。

可能有人以为使用do~while不用减1,其实是一样的,因为循环的条件一样。后者虽然先计算后判别条件,但不满足大于10000时,它会继续循环。一旦满足,当然就已经多计算一次了。下面是do~while的演示程序。


#include <stdio.h>
int main
( 
)
{
        int sum=0
;
        int i=0
;
        do{
             ++i
;
             sum+=i
;
      }while
(sum<=10000
);     // 
循环结束时,i
会多加1
而sum
会多加i
      printf
("1+2+3+
…+%d=%d\n"
,i-1
, sum-i
);     // 
减去多记的部分
      return 0
;
}
  

运行结果如下:


1+2+3+
…+140=9870
  

其实,while和do~while还是有细微区别的,稍不注意也会使输出结果不符合要求。请看下面两个例子的比较。

【例16.8】计算两个数字之差,直到输入数字为0时停止计算。


#include <stdio.h>
int main
( 
)
{
      int a
,b
,x
;
      printf
("
输入两个数字:"
);
      do {
              scanf 
( "%d %d"
,&a
,&b 
);
              x=a-b
;
              printf 
( "x=%d\n"
, x 
);
              printf
("
输入两个数字:"
);
      } while 
( a
!=0&&b
!=0 
);
      printf
("
退出程序!\n"
);
      return 0
;
}
  

运行结果如下:


输入两个数字:
6 89
x=-83
输入两个数字:
0 8
x=-8
输入两个数字:退出程序!
  

显然,这个程序输出了不需要的信息。问题在于它先执行运算后判断条件。可以推知,这个程序开始执行时,如运行实例所示,当输入数字中有一个为0,则输出结果就含多余信息。

应该先判断再执行,修改的程序如下。


#include <stdio.h>
int main
( 
)
{
    int a
,b
,x
;
    printf
("
输入两个数字:"
);
    scanf 
( "%d %d"
, &a
,&b 
);
    while 
( a
!=0 && b
!=0 
)    //
任意一个为0
则退出
    {
        x=a-b
;
        printf 
( "x=%d\n"
, x 
);
        printf
("
输入两个数字:"
);
        scanf 
( "%d %d"
, &a
,&b 
);
    }
    printf
("
退出程序!\n"
);
    return 0
;
}
  

运行示范如下:


输入两个数字:0 9
退出程序!
输入两个数字:5 68
x=-63
输入两个数字:89 3
x=86
输入两个数字:8 0
退出程序!
  

16.3.3 减少循环次数

这种情况是指设计的程序运行结果正确,但应该改进以提高效率。一般是针对循环控制而言,即应减少循环的次数以提高程序的效率。这里举几个典型的例子进行比较说明。

1.寻找逃犯

【例16.9】一辆汽车撞人后逃跑,4个目击者提供如下线索:

甲:牌照三、四位相同;

乙:牌号为31xxxx;

丙:牌照五、六位相同;

丁:三~六位是一个整数的平方。

为了从这些线索中求出牌照号码,只要求出后四位再加上310000即可。这个四位数又是前两位相同,后两位也相同,互相又不相同并且是某个整数的平方。利用计算机计算速度快的特点,把所有可能的方式都试一下,从中找出符合条件的数。这就是所谓的穷举法。参考程序如下:


#include <stdio.h>
void main
( 
)
{
     int i
,j
,k
,c
;
     for
(i=1
; i<=9
; i++
)
           for
(j=0
; j<=9
; j++
)
                 if
(i
!=j
)
                 {
                       k=i*1000+i*100+j*10+j
;
                       for
(c=1
; c*c<k
; c++
);
                             if
(c*c==k
)
                                   printf
("
牌照号码是: %ld
。\n"
,310000+k
);
                 }
}
  

运行输出如下:


牌照号码是:317744
  

因为后面4位数,1000的平方根大于31,所以穷举实验时,变量c不需从1开始,而可以从31开始寻找一个整数的平方。为了提高效率,for语句可以改为如下形式:


for
(c=31
;  c*c<k
;  c++
);
  

2.百钱买百鸡问题

【例16.10】设每只母鸡值3元,每只公鸡值2元,两只小鸡值1元。现要用100元钱买100只鸡,问能同时买到母鸡、公鸡、小鸡各多少只?

如果要求程序在找到解的同时,输出循环的次数。希望寻找一个循环次数较少的算法。

设母鸡、公鸡、小鸡分别为i、j、k只,则可以列出如下两个方程:

这里有3个未知数,所以是一个不定方程。要求同时买到母鸡、公鸡、小鸡,也就是给出一个限制条件:任何一个不能为0。这需要使用三重循环,通过枚举找出所有符合条件的解答。

小鸡需要从2开始,每次增加2。由于已经考虑让i和j从1开始枚举,所以不需要判别如下附加条件:


i*j*k
!=0
//
参考程序
#include <stdio.h>
int main
( 
)
{
     int m=0
,n=0
,sum=0
;
     int i
,j
,k
;
     for
(i=1
;i<100
;i++
)
     {
       ++sum
;
       for
(j=1
; j<100
;j++
)
       {
             ++sum
;
            for
(k=2
;k<100
;k=k+2
)
            {
                   ++sum
;
                   m=i+j+k
;
                   n=3*i+2*j+k/2
;
                   if
((m==100
)&&
(n==100
)) {
                        printf
("
母鸡:%2d 
公鸡:%2d 
小鸡:%2d\n"
,i
,j
,k
);
                   }
             }
         }
      }
      printf
("
一共循环%d
次。\n"
,sum
);
      return 0
;
}
  

程序运行结果如下:


母鸡: 2  
公鸡:30  
小鸡:68
母鸡: 5  
公鸡:25  
小鸡:70
母鸡: 8  
公鸡:20  
小鸡:72
母鸡:11  
公鸡:15  
小鸡:74
母鸡:14  
公鸡:10  
小鸡:76
母鸡:17  
公鸡: 5  
小鸡:78
一共循环490149
次。
  

其实,第3层就循环了480249次。

考虑到母鸡为3元一只,100元都买母鸡,最多也只能买33只。要求每个品种都要,小鸡只能为偶数,因此最多为30只,即第一循环变量i可从1到30。

公鸡为2元一只,最多能买50只。因为至少需要1只母鸡和2只小鸡,所以公鸡不会超出50-3=47(只)。因循环时已经决定枚举的母鸡数i,一只母鸡相当1.5只公鸡,所以第二层循环时,公鸡j只要从1到47-1.5i即可。

因为i+j+k=100,所以直接求得k=100-i-j,不再需要第3层循环,即:


     k=100-i-j
;
     if
(3*i+2*j+0.5*k==100
)
        printf
("
母鸡:%2d   
公鸡:%2d   
小鸡:%2d\n"
, i
,  j
,  k
);
//
改进的算法
#include <stdio.h>
int main
( 
)
{
    int k=0
,sum=0
;
    int i
,j
;
    for
(i=1
;i<=30
;i++
)
    {
           ++sum
;
           for
( j=1
; j<=47-1.5*i
;j++
)
           {
               ++sum
;
               k=100-i-j
;
               if
(3*i+2*j+0.5*k==100
)
               {
                      printf
("
母鸡:%2d 
公鸡:%2d 
小鸡:%2d\n"
,i
,j
,k
);     }
               }
       }
       printf
("
一共循环%d
次。\n"
,sum
);
       return 0
;
}
  

程序运行结果如下:


母鸡: 2 
公鸡:30 
小鸡:68
母鸡: 5 
公鸡:25 
小鸡:70
母鸡: 8 
公鸡:20 
小鸡:72
母鸡:11 
公鸡:15 
小鸡:74
母鸡:14 
公鸡:10 
小鸡:76
母鸡:17 
公鸡: 5 
小鸡:78
一共循环735
次。
  

其中第二层循环705次。

3.鸡兔同笼

【例16.11】大约在1500年前,《孙子算经》中记载了一个有趣的问题。书中是这样叙述的:“今有鸡兔同笼,上有三十五头,下有九十四足,问鸡兔各几何?”

解答思路是这样的:假如砍去每只鸡、每只兔一半的脚,则每只鸡就变成了“独角鸡”,每只兔就变成了“双脚兔”。由此可知:

(1)鸡和兔的脚的总数就由94只变成了47只;

(2)如果笼子里有一只兔子,则脚的总数就比头的总数多1。因此,脚的总只数47与总头数35的差,就是兔子的只数,即47-35=12(只)。

(3)知道兔子的只数,则鸡的只数为:35-12=23(只)。

这一思路新颖而奇特,其“砍足法”也令古今中外数学家赞叹不已。这种思维方法叫化归法。化归法就是在解决问题时,先不对问题采取直接的分析,而是将题中的条件或问题进行变形,使之转化,直到最终把它归成某个已经解决的问题。

下面使用计算机来求解鸡兔同笼问题。

设鸡为i只,兔为j只,则有:

使用i和j分别表示两层循环,逐次枚举试验,当满足上述条件时,就可求出鸡有i只,兔有j只。下面是按此思想编写的程序,sum表示执行循环的总次数。


//
鸡兔同笼
#include <stdio.h>
int main
()
{
    int sum=0
;
    int i
,j
;
    for
( i=1
;i<35
; i++
)
    {
          sum++
;
          for
( j=1
;j<35
;j++
)
          {
             sum++
;
             if
((i+j==35
)&&
(2*i+4*j==94
))
                       printf
("
鸡有%d
只,兔有%d
只。\n"
,i
,j
);
          }
     }
     printf
("
一共循环%d
次。\n"
,sum
);
     return 0
;
}
  

程序运行结果如下:


鸡有23
只,兔有12
只。
一共循环1190
次。
  

其实,第二个循环执行1156次。由此可见,这个循环次数很大,所以应该减少第二个循环的次数。如果将它改为“j=35-i”,则会降为595次。

通过分析鸡兔关系,可以改进程序的效率。

(1)两只鸡和一只兔子的脚数相等,所以鸡头的数量不会超过三分之二,即i<25,j<13。

(2)给定一个i,j的初始值应该是35-i。


//
改进的算法
#include <stdio.h>
int main
()
{
    int sum=0
;
    int i
,j
;
    for
( i=1
;i<24
; i++
)
    {
         sum++
;
         for
( j=35-i
;j<13
;j++
)
         {
                 sum++
;
                 if
((i+j==35
)&&
(2*i+4*j==94
))
                         printf
("
鸡有%d
只,兔有%d
只。\n"
,i
,j
);
             }
         }
         printf
("
一共循环%d
次。\n"
,sum
);
}
  

程序运行结果如下:


鸡有23
只,兔有12
只。
一共循环24
次。
  

其实,要等到j=35-i<13时,才进入第二个循环,而且仅执行1次。

由这两个例子可见,循环次数的控制很重要。

5.复制字符串

【例16.12】下面是一个复制字符串的程序,找出提高效率的解决方法。


#include <stdlib.h>
#include <stdio.h>
char *mycopy
(char *dest
,char *src
)
{
      if
(dest == NULL || src == NULL
)
          return dest
;
      while
(*dest++=*src++
);
      return dest
;
}
void main 
( 
)
{
      char s2[16]="how are you
?"
, s1[16]=" "
;
      mycopy
(s1
,s2
);
      printf
(s1
);    //
输出how are you
?
      printf
("\n"
);
}
  

【分析】程序主要的开销是while语句。在语句


while
(*dest++=*src++
);
  

中,首先要对src指针变量进行读操作,读出src所指向的地址,再对这个地址进行读操作。同样,对dest指针变量也要进行类似操作,读出dest所指向的地址,再对这个地址进行写操作。即对变量本身有两次读操作,根据对变量所指向的地址,进行读写操作,还要分别执行“++”操作,总共进行6次操作。

由于它分别对dest和src进行3次操作,造成效率降低。假设地址相差len,即有


int len=dest-src
;
while
(*
(src+len
)=*src++
);
  

这就把对目标地址dest的访问,变成对源地址src访问的一个增量len,则以后只要读一次内存,再加上这个源地址的增量len,就可以代替对目的地址的访问。这就将6次操作降为4次,提高了效率。


//
提高效率的程序
#include <stdlib.h>
#include <stdio.h>
char *mycopy
(char *dest
,char *src
)
{
     int len=dest-src
;
     if
(dest == NULL || src == NULL
)
        return dest
;
     while
(*
(src+len
)=*src++
);
     return dest
;
}
void main 
( 
)
{
      char s2[16]="how are you
?" 
, s1[16]=" "
;
      mycopy
(s1
,s2
);
      printf
(s1
);
      printf
("\n"
);
}
  

但是这个办法仍然是1个1个字节地复制字符串。内存是按32位,4个字节存储数据的,使用整数指针,就可以按4字节访问字符串。

【例16.13】演示按4个字节赋值字符串的例子。


#include <stdio.h>
void main 
( 
)
{
      char s2[32]="0123456789ABCDEF12"
;
      char s1[32]=" "
;
      int *p
, *p2
, i=0
;
      p=
(int*
)s2
;
      p2=
(int*
)s1
;
      *p2=*p
;
      for
(i=0
;i<5
;i++
,*p2=*
(p+i
))
             printf
("%s
,%x
,%x\n"
,(char*
)p2
,*p2
,*
(p+i
));
}
  

这个程序只是演示使用整数指针p2每次从字符串中得到4个字符的过程。注意要将字符类型指针强制转换为整数指针,第1次使用“*p2=*p;”使得*p2获得p指向地址里第1个4字节字符。在for语句中使用“*(p+i)”读取下一个4字节内容。第5次只有2个字节(字符1和2)内容,但它还有一个结束符‘\0’,所以实际是3个字节的有效内容。

从下面给出程序输出结果可知,输出分为3列,左边是赋值给*p2的4个字符,中间是这4个字符所对应的ASCII编码。右边是*(p+i)的内容,也用ASCII码表示,所以与中间的内容完全一样。


0123
,33323130
,33323130
4567
,37363534
,37363534
89AB
,42413938
,42413938
CDEF
,46454443
,46454443
12
,3231
,3231
  

从这里得到启示,先把字符按4个字节复制,不足4字节则按位复制,这样就会大大提高效率。这里先使用对源字符串求长度的方法,为了完成复制字符串,需要同步移动指针p和p2。

【例16.14】演示按4个字节复制字符串的例子。


#include <stdio.h>
void main 
( 
)
{
      char s2[32]="0123456789ABCDEF12"
;
      char s1[32]=" "
;
      char *cp=s2
;
      int *p
,*p2
,i=0
,len=0
;
      while
(*cp
!='\0'
) 
;
      {
           len++
;
           cp++
;
      }
      len++
;
      printf
("%d\n"
,len
);
      if
(len%4==0
) len=len/4
;
      else len=len/4+1
;
      printf
("%d\n"
,len
);
      p=
(int*
)s2
;
      p2=
(int*
)s1
;
      for
(i=0
;i<len
;i++
){
          *p2=*p
;
          printf
("%s
,%x
,%x\n"
,(char*
)p2
,*p2
,*
(p+i
));
          p2++
;p++
;
      }
      printf
(s1
);
      printf
("\n"
);
}
  

程序增加求字符串长度和循环次数的调试信息。复制输出仍然分为3列,最后输出复制给字符数组s1的内容。


19
5
0123
,33323130
,33323130
4567
,37363534
,42413938
89AB
,42413938
,3231
CDEF
,46454443
,0
12
,3231
,12ffc0
0123456789ABCDEF12
  

包含头文件“string.h”就可使用库函数strlen求字符串长度,即


len=strlen
(s2
)+1
;
  

如果设计一个宏,专门用来判断是否是一个完整的4字节,如果是,则按4字节复制,否则按逐字节复制,这样就可以简化程序的设计,下面给出完整的例子。

【例16.15】使用宏来实现按4个字节复制字符串的参考程序。


#include <stdlib.h>
#include <stdio.h>
#define CONTAIN_OF_ZERO_BYTE
(n
) \
(((n-0x01010101
) & 
(~n
)) & 0x80808080
)
char *mycopy
(char *dest
,char *src
)
{
      int len=dest-src
;
      int *d
,*s
;
      d=
(int *
)dest
;                    //
强制转换成整数指针类型
      s=
(int *
)src
;                    //
强制转换成整数指针类型
      if
(dest == NULL || src == NULL
)
         return dest
;
      while
(1
)
      {
           if
(!CONTAIN_OF_ZERO_BYTE
(*s
))
           {
                printf
("*s%x  "
,*s
);          //
准备复制4
个字节的内容
               printf
("%s\n"
,(char *
)(s+1
));     //
尚没有完成复制的字符串
                *d=*s
;
                s++
;
                d++
;
                continue
;
           }
           src=
(char *
)s
;                    //
强制转换回字符指针类型
           printf
("*src%x  %s\n"
,*src
,src+1
);     //
演示逐字节复制
           while
(*
(src+len
)=*src++
)
              printf
("*src%x %s\n"
,*src
,src+1
);     //
演示逐字节复制
           break
;
      }
      return dest
;
}
void main 
( 
)
{
      char s2[32]="0123456789ABCDEF12"
;
      char s1[32]=" "
;
      mycopy
(s1
,s2
);
      printf
(s1
);
      printf
("\n"
);
}
  

程序中加入的输出信息是为了帮助理解执行过程。选择特殊的字符串也是为了能容易看出复制过程。

程序运行输出如下:


*s33323130  0123456789ABCDEF12
*s37363534  456789ABCDEF12
*s42413938  89ABCDEF12
*s46454443  CDEF12
*src31  12
*src32  2
*src0
0123456789ABCDEF12
  

程序输出的左边是每次复制给目标数组的内容,右边是尚没有复制的内容。执行4次以后,剩下"12'\0'",改为一个一个地复制,共执行3次。

这类例子很多,着眼点都是减少循环的次数,不再赘述。