5.4 複製和串接

你可以使用這一節說明的函式來複製字串和陣列的內容,或是添加字串的內容到另一個字串, ‘str’和 ‘mem’函式宣告在標頭檔string.h中而 ‘wstr’跟 ‘wmem’函式宣告在檔案wchar.h裡,要記得這一節裡所提函式的引數順序有幫助的方法就是跟指定表示式一樣,有目的陣列指定在來源陣列的左邊,所有這些函式都會傳回目的陣列的位址。

假如來源跟目的陣列重疊大部分的函式無法正確運作,例如,目的陣列的開頭跟來源陣列結尾重疊,來源陣列的部份原始內容可能在複製前就被覆寫,更糟的是,在這個情況下的字串函式標記字串結尾的空字元會遺失,複製函式可能在迴圈中卡住然後丟棄配置給你程式所有的記憶體。

在重疊陣列之間有複製問題的所有函式都會在手冊中說明清楚,除了本節的函式外,有一些函式像是sprintf (見 Formatted Output Functions)和 scanf (見 Formatted Input Functions)。

— 函式: void * memcpy (void *restrict to, const void *restrict from, size_t size)

memcpy 函式複製 from 物件最前面的 size 位元組到 to 物件的最前面,如果 to 和 from 陣列重疊的話,這個函式會變成未定義的;假如有可能重疊可以改用 memmove 。

memcpy 的傳回值是 to的值。

這裡有一個如何使用memcpy 來複製陣列內容的例子:

          struct foo *oldarray, *newarray;
          int arraysize;
          ...
          memcpy (new, old, arraysize * sizeof (struct foo));

— 函式: wchar_t * wmemcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

wmemcpy 函式複製 wfrom 物件最前面的 size 寬字元到 wto物件的最前面,如果wto 和 wfrom  陣列重疊的話,這個函式會變成未定義的;假如有可能重疊可以改用 wmemmove 。

下面是 wmemcpy 可能實作的方式但還有最佳化的可能。

          wchar_t *
          wmemcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,
                   size_t size)
          {
            return (wchar_t *) memcpy (wto, wfrom, size * sizeof (wchar_t));
          }

wmemcpy 的傳回值是 wto的值。

此函式在ISO C90 修訂1時被提出。

— 函式: void * mempcpy (void *restrict to, const void *restrict from, size_t size)

mempcpy 函式跟 memcpy 函式幾乎相同,它複製 from 物件最前面的 size 位元組到 to,它不是傳回 to 的值而是傳回一個指向 to 物件最前面後的 size 位址的指標,例如,值是((void *) ((char *) to + size))。

這在連續不斷的記憶體位置做一些物件的複製是有用的。

          void *
          combine (void *o1, size_t s1, void *o2, size_t s2)
          {
            void *result = malloc (s1 + s2);
            if (result != NULL)
              mempcpy (mempcpy (result, o1, s1), o2, s2);
            return result;
          }

這個函式是GNU擴充的。

— 函式: wchar_t * wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

wmempcpy 函式跟 wmemcpy 函式幾乎相同,它複製 wfrom 物件最前面的 size 寬字元到 wto 物件,它不是傳回 wto 的值而是傳回一個指向 wto 物件最前面後的size 位址的指標,例如值是 wto + size

這在連續不斷的記憶體位置做一些物件的複製是有用的。

下面是 wmemcpy 可能實作的方式但還有最佳化的可能。

          wchar_t *
          wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,
                    size_t size)
          {
            return (wchar_t *) mempcpy (wto, wfrom, size * sizeof (wchar_t));
          }

這個函式是GNU擴充的。

— 函式: void * memmove (void *to, const void *from, size_t size)

memmove 從 from 複製 size 位元組到 to 的 size 位元組即使這兩個空間的區塊有重疊,假如碰上重疊的情形,memmove 會小心地複製 from 區塊原始值,包括那些也屬於 to 區塊的重疊位元組。

memmove 的傳回值是 to的值。

— 函式: wchar_t * wmemmove (wchar_t *wto, const wchar_t *wfrom, size_t size)

wmemmove 從 wfrom 複製 size 寬字元到 wto 的 size 寬字元即使這兩個空間的區塊有重疊,假如碰上重疊的情形,memmove 會小心地複製 wfrom 區塊的原始值,包括那些也屬於 wto區塊的重疊位元組。

下面是 wmemcpy 可能實作的方式但還有最佳化的可能。

          wchar_t *
          wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,
                    size_t size)
          {
            return (wchar_t *) mempcpy (wto, wfrom, size * sizeof (wchar_t));
          }

wmemmove 的傳回值是 wto 的值。

這個函式是GNU擴充的。

— 函式: void * memccpy (void *restrict to, const void *restrict from, int c, size_t size)

這個函式會從 from 複製不超過 size 位元組到 to,假如發現一個位元跟 c 匹配的話,傳回值是一個到 c 的指標被複製,或是假如在 from 最前面的 size 位元組沒有出現 c 的匹配位元組時則傳回一個空指標。

範例:

#include<string.h>
main()
{
char a[]=”string[a]”;
char b[]=”string(b)”;
memccpy(a,b,’b’,sizeof(b));
printf(“memccpy():%sn”,a);
}

執行:memccpy():string(b]

— 函式: void * memset (void *block, int c, size_t size)

這個函式會把記憶體空間 block 前面的 size 位元組的範圍,用字元 c 把它填滿(轉換成非負整數),它傳回 block 指標。

— 函式: wchar_t * wmemset (wchar_t *block, wchar_t wc, size_t size)

這個函式會把 block 前面的 size 寬字元的範圍,用寬字元 wc 把它填滿,它傳回 block 指標。

— 函式: char * strcpy (char *restrict to, const char *restrict from)

這會從字串 from(包括結束的空字元)複製字元到字串 to,像 memcpy一樣,假如字串有重疊函式會變成未定義,傳回值是 to的值。

— 函式: wchar_t * wcscpy (wchar_t *restrict wto, const wchar_t *restrict wfrom)

這會從字串 wfrom (包括結束的空寬字元)複製寬字元到字串 wto,像 wmemcpy一樣,假如字串有重疊函式會變成未定義,傳回值是 to的值。

— 函式: char * strncpy (char *restrict to, const char *restrict from, size_t size)

這個函式跟 strcpy 類似但是只會複製 size 個字元到 to。

假如 from 的長度大於 size,那麼 strncpy 只會複製前面的 size 個字元,注意這裡不會有空值終止符寫到 to裡。

假如 from 長度小於 size,那麼 strncpy 會複製所有的 from, 後面會會填滿空字元到 size 個字元,這很少用,但是由 ISO C 標準指定。

假如字串有重疊 strncpy 會變成未定義。

使用 strncpy 而不使用 strcpy 是避免關於寫超過 to配置空間的結束錯誤,然而它也會使你的程式比一般的使用還慢:複製一個可能很小的字串到一個相當大的緩衝區裡,在這種情況下, size 可能很大,而這時候 strncpy 會浪費很可觀的時間在複製空字元。

— 函式: wchar_t * wcsncpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

這個函式跟 wcscpy 類似但是只會複製 size 個寬字元到 wto。

假如 wfrom 的長度大於 size,那麼 wcsncpy 只會複製前面的 size 個寬字元,注意這裡不會有空值終止符寫到 wto裡。

假如 wfrom 的長度小於 size,那麼 wcsncpy 會附製所有的 wfrom,後面會填滿空的寬字元到 size 個寬字元,這很少用,但是 ISO C 標準指定。

假如字串有重疊 wcsncpy 會變成未定義。

使用 wcsncpy 而不使用 wcscpy 是避免關於寫超過 wto 配置空間的結束錯誤,然而它也會使你的程式比一般的使用還慢:複製一個可能很小的字串到一個相當大的緩衝區裡,在這種情況下, size 可能很大,而這時候 wcsncpy 會浪費很可觀的時間再複製空的寬字元。

— 函式: char * strdup (const char *s)

這函式複製具空字元結束的字串 s 到新配置的字串,這個字串是使用 malloc配置的;見 Unconstrained Allocation,假如 malloc 不能配置新字串的空間, strdup 傳回一個空指標,否則它會傳回新字串的指標。

— 函式: wchar_t * wcsdup (const wchar_t *ws)

這函式複製具空字元結束的寬字元字串 ws 到新配置的字串,這個字串是使用 malloc配置的;見 Unconstrained Allocation,假如 malloc 不能配置新字串的空間, wcsdup 傳回一個空指標,否則它會傳回新的寬字元字串的指標。

這個函式是GNU擴充的。

— 函式: char * strndup (const char *s, size_t size)

這函式類似 strdup 但最多只能複製 size 個字元到新配置的字串。

假如 s 的長度大於 size,那麼 strndup 只會複製前面 size 個字元並新增一個結束的空值終止符,否則所有的字元會被覆製,字串會有終止符。

這函式不同於 strncpy 差別在於目的字串會有終止符。

strndup 是GNU擴充的。

— 函式: char * stpcpy (char *restrict to, const char *restrict from)

這函式像 strcpy,除了它是傳回字串 to 結尾的指標(也就是,to + strlen (from)及結束字元的位址)而不是最前面的位址。

例如,這個程式使用 stpcpy 來串接 ‘foo’ 跟 ‘bar’ 來產生‘foobar’,然後印出。

          
          #include <string.h>
          #include <stdio.h>

          int
          main (void)
          {
            char buffer[10];
            char *to = buffer;
            to = stpcpy (to, "foo");
            to = stpcpy (to, "bar");
            puts (buffer);
            return 0;
          }

這函式不是 ISO 或 POSIX 標準,而且在Unix系統上也不常用,但我們也沒有創造這個函式,或許它是來自 MS-DOG。

假如字串有重疊的話函式會變成未定義的,這個函式宣告在 string.h。

— 函式: wchar_t * wcpcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom)

這函式像 wcscpy,除了它傳回字串 wto 結尾的指標(也就是, wto + strlen (wfrom)及結束自元的地址)而不是最前面的位址。

這函式不是 ISO 或 POSIX 標準,但在開發自己的GNU C 函式庫時候很有用。

假如字串有重疊的話 wcpcpy 會變成未定義的。

wcpcpy 是 GNU 擴充而且宣告在 wchar.h

— 函式: char * stpncpy (char *restrict to, const char *restrict from, size_t size)

這函式跟 stpcpy 類似但是會複製 size 個字元到 to

假如 from 長度大於 size,那麼 stpncpy 只會複製前面 size 個字元並傳回最後面複製的字元指標,注意在這種情況下不會有空值終止符寫入 to

假如 from 長度小於 size,那麼 stpncpy 複製所有的 from,之後會填滿空字元直到 size 個字元,這很少用到,但是在使用 strncpy 的實作下是有用的, stpncpy 傳回第一個寫入的空字元指標。

這函式不是 ISO 或 POSIX 標準,但在開發自己的GNU C 函式庫時候很有用。

假如字串有重疊的話,這函式會變成未定義的,這函式宣告在 string.h

— 函式: wchar_t * wcpncpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

這函式類似 wcpcpy 但是只複製 wsize 個字元到 wto

假如 wfrom 長度大於 size,那麼 wcpncpy 只會複製前面 size 個寬字元並傳回最後面複製的非空寬字元的指標,注意再這種情況下不會有空值終止符寫入 wto

假如 wfrom 長度小於 size,那麼 wcpncpy 複製所有的 wfrom,之後會填滿空字元直到 size 個字元,這很少用到,但是在使用 wcsncpy 的實作下是有用的, wcpncpy 傳回第一個寫入的空字元指標。

這函式不是 ISO 或 POSIX 標準,但在開發自己的GNU C 函式庫時候很有用。

假如字串有重疊的話,這函式會變成未定義的。

wcpncpy 是 GNU 擴充且宣告在 wchar.h

— 巨集: char * strdupa (const char *s)

這個巨集類似 strdup 但是會使用 alloca 而不是使用 malloc 來配置新字串(見 Variable Size Automatic), 當然這意味著傳回的字串有跟使用 alloca配置記憶體區塊一樣的限制。

出於顯而易見的原因,strdupa 只是一個巨集的實作;你不能從這個函式取得位址,除了這個限制外,它是有用的函式,下面的程式碼顯示了使用 malloc 會是浪費的狀況。

          
          #include <paths.h>
          #include <string.h>
          #include <stdio.h>

          const char path[] = _PATH_STDPATH;

          int
          main (void)
          {
            char *wr_path = strdupa (path);
            char *cp = strtok (wr_path, ":");

            while (cp != NULL)
              {
                puts (cp);
                cp = strtok (NULL, ":");
              }
            return 0;
          }

請注意呼叫直接使用 path 來呼叫 strtok 是不正確的,它也不允許在 strtok 的引數列表中呼叫 strdupa ,因為 strdupa 使用alloca (見 Variable Size Automatic)會影響傳遞的參數。

這函式只在GNU CC使用時才能用。

— 巨集: char * strndupa (const char *s, size_t size)

這函式類似 strndup 但是像 strdupa 一樣,它使用 alloca 配置新的字串見 Variable Size Automatic,  strndupa 跟 strdupa 一樣有相同的好處跟限制。

這函式以巨集的方式實作,就像 strdupa 一樣, 就像 strdupa 一樣這個巨集也必須不能在函式呼叫中做為參數列表來使用。

strndupa 只在GNU CC使用時才能用。

— 函式: char * strcat (char *restrict to, const char *restrict from)

strcat 函式類似 strcpy,但是 from 的字元會加到 to的後面,而不會覆寫它,那就是, from 的第一個字元會覆寫 to 結尾的空字元。

跟 strcat 一樣的定義是:

          char *
          strcat (char *restrict to, const char *restrict from)
          {
            strcpy (to + strlen (to), from);
            return to;
          }

假如字串有重疊這函式會變成未定義的。

— 函式: wchar_t * wcscat (wchar_t *restrict wto, const wchar_t *restrict wfrom)

wcscat 函式類似 wcscpy,但是 wfrom 的字元會加到 wto的後面,而不會覆寫它,那就是, wfrom 的第一個字元會覆寫 wto結尾的空字元。

跟 wcscat 一樣的定義是:

          wchar_t *
          wcscat (wchar_t *wto, const wchar_t *wfrom)
          {
            wcscpy (wto + wcslen (wto), wfrom);
            return wto;
          }

假如字串有重疊這函式會變成未定義的。

使用 strcat 或 wcscat 函式的程式設計師(或是下面的 strncat 或 wcsncar 函式)很容易地會被認為是偷懶跟不顧後果的,幾乎所有的情況下參與的字串是已知的(最好是如此不然你如何確認緩衝區的配置是足夠的?), 或至少,假如我們持續追蹤不同函式呼叫的結果,我們就會瞭解,使用 strcat/wcscat是很沒有效的,為了找到字串的結尾真正的複製才會開始,這樣很多時間會浪費掉,一個常見的例子:

     /* This function concatenates arbitrarily many strings.  The last
        parameter must be NULL.  */
     char *
     concat (const char *str, ...)
     {
       va_list ap, ap2;
       size_t total = 1;
       const char *s;
       char *result;

       va_start (ap, str);
       va_copy (ap2, ap);

       /* Determine how much space we need.  */
       for (s = str; s != NULL; s = va_arg (ap, const char *))
         total += strlen (s);

       va_end (ap);

       result = (char *) malloc (total);
       if (result != NULL)
         {
           result[0] = '0';

           /* Copy the strings.  */
           for (s = str; s != NULL; s = va_arg (ap2, const char *))
             strcat (result, s);
         }

       va_end (ap2);

       return result;
     }

這看起來相當簡單,特別是真正字串複製是在第二個迴圈,但是這無害的幾行隱藏了一個重大的效能缺失,只要想一下10個100個位元組的字串必須串,對第二個字串來說我們必須搜尋100個位元組來知道要加入下一個字串,對所有的字串要找到中間結果的結尾總共要比較5500!假如我們使用搜尋的配置來結合複製,我們可以更有效率地改寫這個函式:

     char *
     concat (const char *str, ...)
     {
       va_list ap;
       size_t allocated = 100;
       char *result = (char *) malloc (allocated);

       if (result != NULL)
         {
           char *newp;
           char *wp;
           const char *s;

           va_start (ap, str);

           wp = result;
           for (s = str; s != NULL; s = va_arg (ap, const char *))
             {
               size_t len = strlen (s);

               /* Resize the allocated memory if necessary.  */
               if (wp + len + 1 > result + allocated)
                 {
                   allocated = (allocated + len) * 2;
                   newp = (char *) realloc (result, allocated);
                   if (newp == NULL)
                     {
                       free (result);
                       return NULL;
                     }
                   wp = newp + (wp - result);
                   result = newp;
                 }

               wp = mempcpy (wp, s, len);
             }

           /* Terminate the result string.  */
           *wp++ = '0';

           /* Resize memory to the optimal size.  */
           newp = realloc (result, wp - result);
           if (newp != NULL)
             result = newp;

           va_end (ap);
         }

       return result;
     }

知道了輸入字串可以調整記憶體的配置,在這裡我們要指出的差異就是我們根本不需要使用 strcat,我們一直追蹤目前中間結果的長度,這樣我們可以安全地搜尋字串結尾然後使用mempcpy,請注意因為我們處理字串所以我們也不用看起來很自然的 stpcpy ,因為我們已經知道字串的長度而這就變成不需要了,因此可以使用較快速的記憶體複製的函式,這個範例一樣可以用在處理寬字元的情形。

當程式設計師覺得需要使用 strcat 時要三思,看看程式碼是否能重新改寫為可以計算結果的好處,再次提醒:幾乎不需要使用 strcat。

— 函式: char * strncat (char *restrict to, const char *restrict from, size_t size)

這函式像 strcat 除了 from 不能超過 size 個字元附加到 to 的結尾,字串的空字元也會附加到 to,所以 to 全部配置的大小至少是 size + 1 位元組的長度會比原來的長度長。

strncat 函式會像這樣實作:

          char *
          strncat (char *to, const char *from, size_t size)
          {
            to[strlen (to) + size] = '0';
            strncpy (to + strlen (to), from, size);
            return to;
          }

假如字串有重疊 strncat 會變成未定義的。

— 函式: wchar_t * wcsncat (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

這函式像 wcscat 除了 wfrom 不能超過 size 個字元附加到 wto 的結尾,字串的空字元也會附加到 wto,所以 wto 全部配置的大小至少是 size + 1 位元組的長度會比原來的長度長。

The wcsncat function could be implemented like this:

          wchar_t *
          wcsncat (wchar_t *restrict wto, const wchar_t *restrict wfrom,
                   size_t size)
          {
            wto[wcslen (to) + size] = L'0';
            wcsncpy (wto + wcslen (wto), wfrom, size);
            return wto;
          }

假如字串有重疊 wcsncat 會變成未定義的。

這裡有一個範例說明 strncpy 跟 strncat 的使用(寬字元的版本也是一樣),注意在 strncat 呼叫裡,size 參數如何計算來避免字元陣列的緩衝區溢出。

     
     #include <string.h>
     #include <stdio.h>

     #define SIZE 10

     static char buffer[SIZE];

     int
     main (void)
     {
       strncpy (buffer, "hello", SIZE);
       puts (buffer);
       strncat (buffer, ", world", SIZE - strlen (buffer) - 1);
       puts (buffer);
     }

這個程式的輸出像這樣:

     hello
     hello, wo

— 函式: void bcopy (const void *from, void *to, size_t size)

這是 memmove 的替代函式已經有些廢棄,源自 BSD,注意它不完全跟 memmove一樣,因為引數的順序不同,而且沒有傳回值。

— 函式: void bzero (void *block, size_t size)

這是 memset 的替代函式已經有些廢棄,源自 BSD,注意它也不跟 memset 一樣,因為它只能儲存的值是0。

呼,翻的真累,讀到這裡給個讚吧,或是有錯來個留言吧,

下一節: ,上一節: String Length,這一章: String and Array Utilities

感謝你看到這裡,很快就可以離開了,但最好的獎勵行動就是按一下幫我分享或留言,感恩喔~

點我分享到Facebook

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *