C語言之庫函數的模擬與使用

 

C語言之庫函數的模擬與使用

 

在我們學習C語言的過程中,難免會遇到這樣的一種情況:

我們通常實現一個功能的時候,費盡心血的寫出來,卻有着滿滿的錯,這時卻有人來告訴你說:這個功能可以用相應的庫函數來實現。

這時你的心裏充滿着***。但這並不算壞事,至少加深了你對它的認識與記憶。

 

所以,今天來漫談一下 某些庫函數的模擬與實現。

而這篇我們主要來介紹一些處理字符和字符串的庫函數的使用和注意事項

內容大致如下:

1.求字符串長度 strlen

2.長度不受限制的字符串函數 strcpy strcat strcmp

3.長度受限制的字符串函數介紹 strncpy strncat strncmp

4.字符串查找 strstr strtok

5.錯誤信息報告 strerror perror

6.字符操作

7.內存操作函數 memcpy memmove memset memcmp

1.求字符串長度–strlen

 

 

正如所介紹那樣,此函數的功能就是求一個字符串的長度。而我們所需要做的就是傳一個字符指針進去。

示例:

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

int main ()
{
  char szInput[256];
  printf ("Enter a sentence: ");
  gets (szInput);
  printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
  return 0;
}

結果:

在此函數的使用過程中我們需要注意以下幾點:

1.字符串已經 '\0' 作為結束標誌
2.strlen函數返回的是在字符串中 '\0' 前面出現的字符個數(不包含 '\0' )。
3.參數指向的字符串必須要以 '\0' 結束。
4.注意函數的返回值為size_t,是無符號的( 易錯 )

我們來看一個對於第四點的例子:

#include <stdio.h> 
#include<string.h>
int main()
{
    const char* str1 = "abcdef"; 
    const char* str2 = "bbb"; 
    if (strlen(str2) - strlen(str1) > 0)
    {
        printf("str2>str1\n");
    }
    else
    {
        printf("srt1>str2\n");
    }
    return 0;
}

這個結果會是什麼呢?

相信會有人給出srt1>str2的結果,可編譯器給出的結果卻是:

 

 

 為什麼呢?

我們原以為 3-6=-3 這個式子沒錯啊,-3<0 執行 else 的結果

但 因為strlen的返回類型是一個unsigned形,所以相減之後的那個數會變為一個很大很大的數。

 

我們來模擬實現一下此函數

1.計數器方式

int my_strlen(const char* str)
{
    int count = 0;
    while (*str)
    {
        count++; 
        str++;
    }
    return count;
}

2.遞歸實現

int my_strlen(const char* str)
{
    if (*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str + 1);
}

3.指針-指針 實現

 

int my_strlen(char* s)
{
    char* p = s;
    while (*p != '\0')
        p++;
    return p - s;
}

 

2.長度不受限制的字符串函數

1.strcpy

這個函數可謂是見名知意,字符串的拷貝函數,我們來看看介紹:

實例:

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

int main()
{
    char str1[] = "Sample string";
    char str2[40];
    char str3[40];
    strcpy(str2, str1);
    strcpy(str3, "copy successful");
    printf("str1: %s\nstr2: %s\nstr3: %s\n", str1, str2, str3);
    return 0;
}

上述代碼幹了一個什麼事呢?

將str1中的內容拷貝到str2中

又將 copy successful  拷貝到str3中

結果:

 

同樣注意以下幾點:

Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
源字符串必須以
'\0' 結束。
會將源字符串中的
'\0' 拷貝到目標空間。
目標空間必須足夠大,以確保能存放源字符串。目標空間必須可變。

模擬實現strcpy

#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
    char* ret = dest;
    assert(dest != NULL); //斷言保證dest不為空指針
    assert(src != NULL);  //斷言保證src不為空指針

    while ((*dest++ = *src++))
    {
        ;
    }
    return ret;
}

2.strcat-連接兩個字符串

介紹如下:

 

 實例:

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

int main()
{
    char str[80];
    strcpy(str, "these ");
    strcat(str, "strings ");
    strcat(str, "are ");
    strcat(str, "concatenated.");
    puts(str);
    return 0;
}

作用:

先將 these 複製到 str 中,在逐次將 strings 、are、concatenated 連接到str上

結果:

 

 注意:

Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.

1.源字符串必須以 '\0' 結束。

2.目標空間必須有足夠的大,能容納下源字符串的內容。目標空間必須可修改。

還值得注意的一點就是:若自己追加自己將會「永無休止」的循環下去,為什麼呢?

 

 

模擬實現

#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
    char* ret = dest; 
    assert(dest != NULL); 
    assert(src != NULL); 
    while (*dest)            //找到\0的位置
    {
        dest++;
    }
    while ((*dest++ = *src++))//在此\0位置上覆蓋新的內容
    {
        ;
    }
    return ret;
}

 

3.strcmp-字符串比較函數

切記:不能用 == 來判斷兩個字符串是否相等

介紹:

 

 實例:

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

int main()
{
    char key[] = "apple";
    char buffer[80];
    do {
        printf("Guess my favorite fruit? ");
        fflush(stdout);
        scanf("%79s", buffer);
    } while (strcmp(key, buffer) != 0);
    puts("Correct answer!");
    return 0;
}

 作用:

定義一個key來表示自己喜歡的水果,

讓用戶不斷來輸入,

直到用戶輸入所指定內容

最後輸出 correct answer

 結果:

 

 注意:

This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached.

標準規定:
第一個字符串大於第二個字符串,則返回大於0的數字
第一個字符串等於第二個字符串,則返回0
第一個字符串小於第二個字符串,則返回小於0的數字

比較是比的相應字符的ASCII碼值,如果相等,則比較下一個以此類推…..

 

 模擬實現:

 

int my_strcmp(const char* src, const char* dest)
{
    int ret = 0;
    assert(src != NULL); 
    assert(dest != NULL);
    while (!(ret = *(unsigned char*)src - *(unsigned char*)dest) && *dest)//dest不為空,且如果相減結果為0,就繼續循環
    {
        ++src, ++dest;

        if (ret < 0)
            ret = -1; 
        else if (ret > 0)
            ret = 1;

    }
    
    return(ret);
}
#include<assert.h>
int my_strcmp(const char*s1, const char*s2)
{
    assert(s1 && s2);
    while (*s1 == *s2)
    {
        if (*s1 == '\0')
            return 0;
        s1++;
        s2++;
    }
    return *s1 - *s2;
}

 

3.長度受限制的字符串函數介紹

1.strncpy-從原始串複製自定義個字符到新串中

介紹:

 

 實例:


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

int main ()
{
  char str1[]= "To be or not to be";
  char str2[40];
  char str3[40];

  /* copy to sized buffer (overflow safe): */
  strncpy ( str2, str1, sizeof(str2) );

  /* partial copy (only 5 chars): */
  strncpy ( str3, str2, 5 );
  str3[5] = '\0';   /* null character manually added */

  puts (str1);
  puts (str2);
  puts (str3);

  return 0;
}

作用:

將str1中所有的字符複製到str2中

將str1中的前五個字符複製到str3中

結果:

 

 注意:

Copies the first num characters of source to destination. If the end of the source C string (which is
signaled by a null-character) is found before num characters have been copied, destination is padded
with zeros until a total of num characters have been written to it.
拷貝num個字符從源字符串到目標空間。 如果源字符串的長度小於num,則拷貝完源字符串之後,在目標的後邊追加0,直到num個。

模擬實現:

char* my_strncpy(char* destination, const char* source, size_t num)
{
    assert(destination && source);
    char* ret = destination;
    destination[num] = '\0';
    while (num--)
    {
        if (*source == '\0')
        {
            *destination = '\0';
            break;
        }
        *destination++ = *source++;
    }
    return ret;
}

 

2.strncat–從原始串取自定義個字符連接到新串中

介紹:

 

 

 示例:

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

int main()
{
    char str1[20];
    char str2[20];
    strcpy(str1, "To be ");
    strcpy(str2, "or not to be");
    strncat(str1, str2, 6);
    puts(str1);
    return 0;
}

作用:

將str2中的前6個字符連接到str1後面

結果:

 

注意:

Appends the first num characters of source to destination, plus a terminating null-character.
If the length of the C string in source is less than num, only the content up to the terminating nullcharacter is copied.

 

 

 模擬實現:

char* my_strncat(char* destination, const char* source, size_t num)
{
    assert(destination && source);
    char* ret = destination;
    while (*destination);//找到\0
    destination[num] = '\0';//放置\0
    while (num--)
    {
        *destination++ = *source++;
        if (*source == '\0')
        {
            *destination = '\0';
            break;
        }
    }
    return ret;
}

3.strncmp-自定義個字符比較

比較方式與strcmp相同

介紹:

 

 

 示例:

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

int main()
{
    char str[][5] = { "R2D2" , "C3PO" , "R2A6" };
    int n;
    puts("Looking for R2 astromech droids...");
    for (n = 0; n < 3; n++)
        if (strncmp(str[n], "R2xx", 2) == 0)
        {
            printf("found %s\n", str[n]);
        }
    return 0;
}

作用:

比較各字符串的前兩個字符是否為R2,如果相同,則輸出

結果:

 

 

 注意:

比較到出現另個字符不一樣或者一個字符串結束或者num個字符全部比較完。

 

模擬實現:

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

int my_strncmp(const char* str1, const char* str2, size_t num)
{
    assert(str1 && str2);
    while (num--)
    {
        if ((*str1 != *str2))
        {
            return *str1 - *str2;
        }
        str1++;
        str2++;
    }
    return 0;
}

4.字符串查找函數

1.strstr-在一個字符串中查找另一個字符串是否存在並返回相應的地址

介紹:

 

 

 示例:

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

int main()
{
    char str[] = "This is a simple string";
    char* pch;
    pch = strstr(str, "simple");
    if (pch != NULL)
        strncpy(pch, "sample", 6);
    puts(pch);
    return 0;
}

作用:

在str中查找simple是否存在

結果:

 

 

 注意:

Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.

模擬實現:

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

const char* my_strstr(const char* str1, const char* str2)
{
    assert(str1 && str2);
    const char* start = str1;
    while (*start)
    {
        const char* e1 = start;
        const char* e2 = str2;
        while ((*e1 == *e2) && (*e1) && (*e2))
        {
            e1++;
            e2++;
        }
        if (*e2 == '\0')
        {
            return (char*)start;
        }
        start++;
    }
    return NULL;
}

2.strtok-字符串分割函數

介紹:

 

 

 實例:

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

int main()
{
    char str[] = "EG:- This, a sample string.";
    char* pch;
    printf("Splitting string \"%s\" into tokens:\n", str);
    pch = strtok(str, " ,.-");
    while (pch != NULL)
    {
        printf("%s\n", pch);
        pch = strtok(NULL, " ,.-");
    }
    return 0;
}

作用:

將所給字符串用所給分隔符分割開;

結果:

 

 

 注意:

char * strtok ( char * str, const char * sep );
sep參數是個字符串,定義了用作分隔符的字符集合
第一個參數指定一個字符串,它包含了0個或者多個由sep字符串中一個或者多個分隔符分割的標記。
strtok函數找到str中的下一個標記,並將其用 \
0 結尾,返回一個指向這個標記的指針。(註:strtok函數會改 變被操作的字符串,所以在使用strtok函數切分的字符串一般都是臨時拷貝的內容並且可修改。)
strtok函數的第一個參數不為 NULL ,函數將找到str中第一個標記,strtok函數將保存它在字符串中的位置。
strtok函數的第一個參數為 NULL ,函數將在同一個字符串中被保存的位置開始,查找下一個標記。
如果字符串中不存在更多的標記,則返回 NULL 指針。

 

5.錯誤信息報告

 

 

 

 1.strerror-返回錯誤碼,所對應的錯誤信息。

介紹:

 

 

 示例:

#include <stdio.h>
#include <string.h>
#include <errno.h>//必須包含的頭文件

int main()
{
    FILE* pFile;
    pFile = fopen("unexist.ent", "r");
    if (pFile == NULL)
        printf("Error opening file unexist.ent: %s\n", strerror(errno));
    return 0;
}

結果:

 

 

 注意:

使用時必須包含頭文件:errno.h

2.perror-打印對應的錯誤信息

 

 

 示例:

#include <stdio.h>

int main()
{
    FILE* pFile;
    pFile = fopen("unexist.ent", "rb");
    if (pFile == NULL)
        perror("The following error occurred");
    else
        fclose(pFile);
    return 0;
}

結果:

 

 

 

6.字符分類函數:

 

 

 注意:

比較時,是一個一個字符的去比較

示例及結果:

#include <stdio.h>
#include <ctype.h>
int main ()
{
  int i;
  char str[]="c3po...";
  i=0;
  while (isalnum(str[i])) i++;
  printf ("The first %d characters are alphanumeric.\n",i);
  return 0;
}

 

 

 

#include <stdio.h>
#include <ctype.h>
int main()
{
    int i = 0;
    char str[] = "C++";
    while (str[i])
    {
        if (isalpha(str[i])) 
            printf("character %c is alphabetic\n", str[i]);
        else 
            printf("character %c is not alphabetic\n", str[i]);
        i++;
    }
    return 0;
}

 

 

 

7.內存操作函數

內存操作函數不僅可以處理字符串,還可以處理結構體,整形數組等。

1.memcpy–以位元組為單位複製

介紹:

 

 

 示例:

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

struct {
    char name[40];
    int age;
} person, person_copy;

int main()
{
    char myname[] = "Pierre de Fermat";

    /* using memcpy to copy string: */
    memcpy(person.name, myname, strlen(myname) + 1);
    person.age = 46;

    /* using memcpy to copy structure: */
    memcpy(&person_copy, &person, sizeof(person));

    printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);

    return 0;
}

結果:

 

 

 注意:

void * memcpy ( void * destination, const void * source, size_t num );

函數memcpy從source的位置開始向後複製num個位元組的數據到destination的內存位置。
這個函數在遇到
'\0' 的時候並不會停下來。
如果source和destination有任何的重疊,複製的結果都是未定義的

模擬實現:

void* my_memcpy(void* destination, const void* source, size_t num)
{
    assert(destination && source);
    void* ret = destination;
    while (num--)
    {
        *((char*)destination)++ = *((char*)source)++;
    }
    return ret;
}

2.memmove

介紹:

 

 

 示例:

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

int main ()
{
  char str[] = "memmove can be very useful......";
  memmove (str+20,str+15,11);
  puts (str);
  return 0;
}

結果:

 

 注意:

memmove和memcpy的差別就是memmove函數處理的源內存塊和目標內存塊是可以重疊的。

如果源空間和目標空間出現重疊,就得使用memmove函數處理。

模擬實現:

void* my_memmove(void* dest, const void*src, size_t count)
{
    void* ret = dest;
    assert(dest && src);
    if (dest < src)
    {
        //前->後
        while (count--)
        {
            *(char*)dest = *(char*)src;
            ++(char*)dest;
            ++(char*)src;
        }
    }
    else
    {
        //後->前
        while (count--)
        {
            *((char*)dest + count) = *((char*)src + count);
        }
    }
    return ret;
}

3.memset

介紹:

 

 示例:

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

int main ()
{
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}

結果:

 

 模擬實現:

void * Memset(void* src, int ch, size_t count)
{
    assert(src != NULL);
    void* start = src;
    while (count--)
    {
        *(char*)src = (char)ch;
        src = (char*)src + 1;
    }
    return start;
}

4.memcmp

介紹:

 

 示例:

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

int main ()
{
  char buffer1[] = "DWgaOtP12df0";
  char buffer2[] = "DWGAOTP12DF0";

  int n;

  n=memcmp ( buffer1, buffer2, sizeof(buffer1) );

  if (n>0) printf ("'%s' is greater than '%s'.\n",buffer1,buffer2);
  else if (n<0) printf ("'%s' is less than '%s'.\n",buffer1,buffer2);
  else printf ("'%s' is the same as '%s'.\n",buffer1,buffer2);

  return 0;
}

結果:

 

 注意:

比較從ptr1和ptr2指針開始的num個位元組

返回值同strcmp

模擬實現:

int my_memcmp(const void* p1, const void* p2, size_t count)//方法1
{
    assert(p1);
    assert(p2);
    char* dest = (char*)p1;
    char* src = (char*)p2;
    while (count-- && (*dest++ == *src++))
    {
        ;
    }
    if (count == 0)
        return 0;
    return *dest - *src;
}

 

 

 

若有有錯之處,還望大家多多指點!!!

 

本次講解就到這了,

如果大家對於函數的模擬實現都能掌握,那麼大家肯定對於上述函數有了深刻的理解。

 

Tags: