讀古今文學網 > 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次。

這類例子很多,著眼點都是減少循環的次數,不再贅述。