在這一章我們重點講解幾個字符串及內存的標準庫函數,這些函數都是標準庫中提前準備好了的,但是我們有必要了解它們的功能,並且模擬它們的實現,這對我們將來的程序編寫有很多指導性意義。
字符串函數
strlen()
這個函數我相信大家再熟悉不過了,就是普通的計算字符串長度的函數,不過我們要注意這個函數傳入的參數必須是一個以空字符結尾的字符串。不然就會出現未定義行爲。這個函數返回的長度是字符串不包含末尾空字符的長度。這點很重要,很多同學都會在這點上出錯。接下來我們對這個函數進行基本實現。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
size_t Strlen(const char* str)
{
assert(str != NULL);//有效性檢驗
size_t i = 0;
while(str[i] != '\0')
{
++i;
}
return i;
}
int main()
{
char str[] = "123";
printf("%d\n", Strlen(str));
}
[misaki@localhost test]$ ./Main
3
我們在實現函數的時候一定要注意有效性檢驗,這裏只做了最基本的檢驗,在實際開發中要注意的遠遠要多餘此。
strcpy()
這個函數是字符串複製函數,可以將兩個參數中第二個字符串的值完全賦值給第一個字符串,但是要注意第一個字符串一定要有足夠大小的空間。接下來提供一種實現思路。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strcpy(char* destination, const char* source)
{
assert(destination != NULL);
assert(source != NULL);
size_t i;
for(i = 0; i < strlen(source); i++)
{
destination[i] = source[i];
}
destination[i] = '\0';
return destination;
}
int main()
{
char str[10] = {0};
char str2[] = "12314524";
Strcpy(str, str2);
printf("str2 = %s\n", str2);
printf("str = %s\n", str);
}
[misaki@localhost test]$ ./Main
str2 = 12314524
str = 12314524
strcat()
這個是字符串拼接函數,功能是可以將第二個字符串拼接到第一個字符串的末尾,不過要注意第一個字符串要有足夠大的空間。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strcat(char* destination, const char* source )
{
size_t a = strlen(destination);
size_t b = strlen(source) + 1;
for(size_t i = 0; i < b; i++)
{
destination[a] = source[i];
a++;
}
return destination;
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
printf("str = %s, str2 = %s\n", str, str2);
Strcat(str, str2);
printf("拼接後:str = %s, str2 = %s\n", str, str2);
}
[misaki@localhost test]$ ./Main
str = abc, str2 = 12314524
拼接後:str = abc12314524, str2 = 12314524
strcmp()
這個是字符串比較函數,比較前後兩個字符串的大小,如果前一個大於後一個則返回正數,相等返回0,小於返回負數。然而比較字符串的時候則是通過兩個字符串從前致後每個字符串的字符的ASCII碼的大小來進行比較的,如何相同則比較下一個字符知道出現第一個不相等的字符位置,然後再返回相應的結果。這點從下面的實現代碼上可以明確看出。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int Strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
while(*str1 != '\0' && *str2 != '\0')
{
if(*str1 < *str2)
{
return -1;
}
else if(*str1 > *str2)
{
return 1;
}
else
{
++str1;
++str2;
}
}
if(*str1 == '\0' && *str2 != '\0')
{
return -1;
}
if(*str1 != '\0' && *str2 == '\0')
{
return 1;
}
if(*str1 == '\0' && *str2 == '\0')
{
return 0;
}
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
if(Strcmp(str, str2) > 0)
{
printf("str > str2\n");
}
else if(Strcmp(str, str2) == 0)
{
printf("str == str2\n");
}
else
{
printf("str < str2\n");
}
}
[misaki@localhost test]$ ./Main
str > str2
strncpy()
這個函數與strcpy()
類似,不過它的功能增加了可以將指定數量的字符複製到目標字符串中,同時這個函數在一些處理上有一些變化。當我們給出的數字大於原字符串的長度,則目標字符串要用空字符補充多出來長度,如果小於或原字符串的長度則末尾不會自動添加空字符,因此我們尤其要除以這一點,然而這些規則都是C的標準中進行規定的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strncpy(char* destination, const char* source, size_t num)
{
assert(destination != NULL);
assert(source != NULL);
size_t i = 0;
while(i < num && source[i] != '\0')
{
destination[i] = source[i];
i++;
}
if(i == num)
{
return destination;
}
if(source[i] == '\0')
{
for(; i < num; i++)
{
destination[i] = '\0';
}
return destination;
}
}
int main()
{
char str[1024] = {0};
char str2[] = "12314524";
printf("str2 = %s\n", str2);
Strncpy(str, str2, 3);
printf("複製三個字符後str = %s\n", str);
}
[misaki@localhost test]$ ./Main
str2 = 12314524
複製三個字符後str = 123
strncat()
strncat()
函數與strncpy()
函數類似,也是同樣加入了可以指定字符數量地將源字符串拼接致目標字符串的末尾。至於其中的特殊情況,比如num參數大於小於或等於原字符串的處理在官方文檔上有明確要求和規定,這裏不再贅述。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* Strncat(char* destination, const char* source, size_t num)
{
assert(destination != NULL);
assert(source != NULL);
size_t i = 0;
while(destination[i] != '\0')
{
i++;
}
size_t j = 0;
while(j < num && source[j] != '\0')
{
destination[i] = source[j];
j++;
i++;
}
destination[i] = '\0';
return destination;
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
Strncat(str, str2, 3);
printf("拼接三個字符後:str = %s\n", str);
}
[misaki@localhost test]$ ./Main
str = abc
str2 = 12314524
拼接三個字符後:str = abc123
strncmp()
strncmp()
與strcmp()
類似,也是字符串比較函數,同樣的也是將第一個字符串中的指定數量的字符與第二個字符串進行比較,返回規則也是完全一致。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int Strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 != NULL);
assert(str2 != NULL);
size_t i = 0;
while(str1[i] == str2[i] && i < num && str1[i] != '\0' && str2[i] != '\0')
{
i++;
}
if(i == num)
{
return 0;
}
if(str1[i] == '\0' && str2[i] == '\0')
{
return 0;
}
if(str1[i] > str2[i])
{
return 1;
}
if(str1[i] < str2[i])
{
return -1;
}
}
int main()
{
char str[1024] = "abc";
char str2[] = "12314524";
int a = Strncmp(str, str2, 3);
int b = Strncmp(str, str2, 0);
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
printf("比較前三個字符串的結果是:%d\n", a);
printf("比較前零個字符串的結果是:%d\n", b);
}
[misaki@localhost test]$ ./Main
str = abc
str2 = 12314524
比較前三個字符串的結果是:1
比較前零個字符串的結果是:0
strstr()
這個函數的功能相比之前的就較爲複雜,是在str1
查找爲str2
的子串。這裏的內容與數據結構略有相關,簡單來說就是在第一個字符串中找是否存在一串和第二個字符串完全一致的串,如果存在則返回第一次出現的首字符的指針,如果不存在返回NULL
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
const char* Strstr(const char* str1, const char* str2)
{
assert(str1 != NULL);
assert(str2 != NULL);
if(*str1 == '\0' || *str2 == '\0')
{
return NULL;
}
const char* black_ptr = str1;
while(*black_ptr != '\0')
{
const char* red_ptr = black_ptr;
const char* sub_ptr = str2;
while(*red_ptr != '\0' && *sub_ptr != '\0' && (*red_ptr == *sub_ptr))
{
++red_ptr;
++sub_ptr;
}
if(*sub_ptr == '\0')
{
//找到了
return black_ptr;
}
++black_ptr;
}
//沒找到
return NULL;
}
int main()
{
char str[1024] = "123456";
char str2[] = "234";
const char* str3 = Strstr(str, str2);
printf("str = %s\n", str);
printf("str2 = %s\n", str2);
printf("str2中存在str並打印:%s\n",str3);
}
[misaki@localhost test]$ ./Main
str = 123456
str2 = 234
str2中存在str並打印:23456
strtok()
這個函數是字符串分割函數,它可以將指定的字符串以指定的字符進行分割,並且隨着參數的該表可以記錄上次分割結果並且繼續上一次分割進度繼續進行分割,這個字符串函數使用和實現較爲複雜,這裏不做細緻講解。
strerror()
strerror()
是錯誤信息報告函數,根據錯誤返回的錯誤碼可以將其使用對應的錯誤信息並且打印。這裏舉個例子。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>//要使用錯誤碼我們需要引入這個頭文件
#include <string.h>
int main()
{
FILE* file;
file = fopen("1.txt", "r");//打開這個文件實際上我的目錄下並沒有這個文件因此就會產生錯誤,
if(file == NULL)
{
printf("%s\n", strerror(errno));//打印最後一個錯誤碼的錯誤信息
}
}
[misaki@localhost test]$ ./Main
No such file or directory
不難發現打印的錯誤信息和我們編譯錯誤信息十分相似,是的我們可以通過這種方式提醒用戶哪裏出現錯誤,但是目前這種報錯方式十分落後。
內存操作函數
常用的內存操作函數有很多,這裏重點介紹兩個,並且加以實現。
memcpy()
這個函數與strncpy()
十分類似,不過此時複製的已經不僅僅只能是字符串了,而是任何類型的數據都可以進行復制,最後一個參數num
給出的則是目標複製的字節數。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void* Memcpy(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
for(size_t i = 0; i < num; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
int main()
{
int arr[] = {11,14,26};
int arr2[3];
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
Memcpy(arr2, arr, sizeof(arr));
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr2[i]);
}
printf("\n");
}
[misaki@localhost test]$ ./Main
11 14 26
11 14 26
memmove()
這個函數與memcpy()
類似,不過這個函數解決了memcpy()
中兩個緩衝區重疊可能會導致複製錯誤的問題,因此相比於memcpy()
我更推薦於使用這個函數。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
void* Memcpy(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
for(size_t i = 0; i < num; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* Memmove(void* dest, const void* src, size_t num)
{
assert(dest != NULL);
assert(src != NULL);
char* pdest = (char*)dest;
char* psrc = (char*)src;
if(pdest > psrc && pdest < psrc + num)//緩衝區重疊,特殊處理
{
for(int64_t i = num - 1; i >= 0; i--)
{
pdest[i] = psrc[i];
}
}
else
{
Memcpy(dest, src, num);
}
return dest;
}
int main()
{
int arr[] = {11,14,26};
int arr2[3];
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr[i]);
}
printf("\n");
Memmove(arr2, arr, sizeof(arr));
for(int i = 0; i < 3; i++)
{
printf("%d\t", arr2[i]);
}
printf("\n");
}
[misaki@localhost test]$ ./Main
11 14 26
11 14 26
除了以上這些字符串函數和內存操作函數,在C語言中還有比較常用的字符處理及數學函數函數,都包含在標準庫中,這些函數十分簡單,容易上手,但是在開發中會給我們帶來很多方便,這裏不再詳細介紹。
今天是除夕,預祝大家新年快樂!學業進步!