C語言進階教程之字符串&內(nèi)存函數(shù)
前言:
字符串是一種非常重要的數(shù)據(jù)類型,但是C語言不存在顯式的字符串類型,C語言中的字符串都以字符串常量的形式出現(xiàn)或存儲在字符數(shù)組中。字符串常量適用于那些對它不做修改的字符串函數(shù)。同時,C 語言提供了一系列庫函數(shù)來對操作字符串,這些庫函數(shù)都包含在頭文件 string.h 中。
一、求字符串長度
strlen
size_t strlen ( const char * str );
- 字符串已經(jīng) '\0' 作為結(jié)束標志,strlen函數(shù)返回的是在字符串中 '\0' 前面出現(xiàn)的字符個數(shù)(不包含 '\0' )
- 參數(shù)指向的字符串必須要以 '\0' 結(jié)束
- 注意函數(shù)的返回值為size_t,是無符號的( 易錯 )
- 學會strlen函數(shù)的模擬實現(xiàn)
源字符串必須以 '\0' 結(jié)束。 會將源字符串中的 '\0' 拷貝到目標空間。 目標空間必須足夠大,以確保能存放源字符串。 目標空間必須可變。 學會模擬實現(xiàn)。
例1:
#include <stdio.h>
#include <string.h>
int main()
{
int len1 = strlen("abcdef");
printf("%d\n", len1); //6
char arr[] = {'a','b','c','d','e','f'};
//錯誤寫法
int len2 = strlen(arr);
printf("%d\n", len2); //隨機值
return 0;
}
執(zhí)行結(jié)果:

例2:
int main()
{
if(strlen("abc") - strlen("abcdef") > 0)
printf("hehe\n");
else
printf("haha\n");
system("pause");
return 0;
}
執(zhí)行結(jié)果:

由于此時的strlen沒有進行定義,它的默認類型為無符號類型,那么兩個無符號數(shù)相加減最后得出的數(shù)是一個很大的數(shù),所以最后的結(jié)果必然是大于0的
strlen函數(shù)的模擬實現(xiàn)
(1)計數(shù)器
#include <stdio.h>
#include <assert.h>
int my_strlen(const char *str)
{
int count = 0;
assert(str != NULL);
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
(2)指針-指針
#include <stdio.h>
int my_strlen(const char *str)
{
const char *p = str;
while(*p != '\0')
{
p++;
}
return p-str;
}
int main()
{
int len = 0;
char arr[10]="abcdef";
len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
(3)遞歸
不創(chuàng)建臨時變量求字符串長度
#include<stdio.h>
int my_strlen(const char *str)
{
if(*str=='\0')
return 0;
else
return 1+my_strlen(str+1);
}
int main()
{
int len = 0;
char arr[10]="abcdef";
len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
二、長度不受限制的字符串函數(shù)
strcpy
char* strcpy(char * destination, const char * source);
- 將按源指向的 C 字符串復制到按目的地指向的陣列中,包括終止空字符 (并停止在那個時候)
- 源字符串必須以 '\0' 結(jié)束
- 會將源字符串中的 '\0' 拷貝到目標空間
- 目標空間必須足夠大,以確保能存放源字符串
- 目標空間必須可變。 學會模擬實現(xiàn)
例:
#include <stdio.h>
int main()
{
char arr1[] = "abcdefghi";
char arr2[] = "bit";
//錯誤示范
//char *arr1 = "abcdefghi";
//p指向常量字符串,而常量字符串無法被修改
//char arr2[] = {'b','i','t'};
//由于此時沒有給'\0',由于找不到'\0'會導致向后越界訪問
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
strcpy函數(shù)的模擬實現(xiàn)
#include <stdio.h>
#include <assert.h>
char *my_strcpy(char *dest, const char *src)
{
assert(dest != NULL);
assert(src != NULL);
char *ret = dest;
//拷貝src指向的字符串到dest指向的空間,包含'\0'
while(*dest++ = *src++)
{
;
}
//返回目的的空間的初始地址
return ret;//'\0'
}
int main()
{
char arr1[] = "abcdefgh";
char arr2[] = "bit";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
strcat
char * strcat ( char * destination, const char * source );
- 將源字符串的副本附加到目的地字符串。終止無效字符在目的地由源的第一個字符覆蓋,并在源的末尾包含空字符由目的地兩者的連結(jié)形成的新字符串
- 源字符串必須以 '\0' 結(jié)束
- 目標空間必須有足夠的大,能容納下源字符串的內(nèi)容
- 目標空間必須可修改
- 字符串自己如何給自己追加
例:
#include <stdio.h>
int main()
{
char arr1[30] = "hello\0xxxxxxx";
char arr2[] = "wolrd";
strcat(arr1, arr2);
printf("%s\n", arr1);
//錯誤示范
char arr3[] = "hello";
char arr4[] = "world";
strcat(arr3, arr4);
printf("%s\n", arr3);
//由于arr3數(shù)組沒有定義空間大小
//此時就開辟了'hello\0'6個字節(jié)
//當arr4數(shù)組追加過去后就產(chǎn)生了越界訪問,產(chǎn)生報錯
return 0;
}
調(diào)試結(jié)果:

執(zhí)行結(jié)果:

總結(jié):此時我們可以看到,arr2數(shù)組內(nèi)的的字符串從arr1數(shù)組中的'\0'開始進行追加
當arr2數(shù)組內(nèi)的字符串追加過去后,后面的‘\0'也一并追加了過去
當目標空間不夠大時,就會造成訪問越界
strcat函數(shù)的模擬實現(xiàn)
#include <stdio.h>
#include <assert.h>
char *my_strcat(char *dest, const char *src)
{
assert(dest != NULL);
assert(src);
char *ret = dest;
//1.找到目的字符串的'\0'
while(*dest != '\0')
{
dest++;
}
//2.追加
while(*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[30] = "hello";
char arr2[] = "wolrd";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
strcmp
int strcmp (const char * str1, const char * str2 );
此函數(shù)開始比較每個字符串的第一個字符。 如果它們彼此相等,則繼續(xù)使用以下對,直到字符不同或到達終止空字符為止。
標準規(guī)定:
- 第一個字符串大于第二個字符串,則返回大于0的數(shù)字
- 第一個字符串等于第二個字符串,則返回0
- 第一個字符串小于第二個字符串,則返回小于0的數(shù)字
- 那么如何判斷兩個字符串?
#include <stdio.h>
#include <string.h>
int main()
{
char *p1 = "qbc";
char *p2 = "abc";
// int ret = strcmp(p1, p2);
// printf("%d\n", ret);
if(strcmp(p1, p2) > 0)
{
printf("p1 > p2\n");
}
else if(strcmp(p1, p2) == 0)
{
printf("p1 == p2\n");
}
else if(strcmp(p1, p2) < 0)
{
printf("p1 < p2\n");
}
return 0;
}
strcmp函數(shù)的模擬實現(xiàn)
#include <stdio.h>
int my_strcmp(const char *str1, const char *str2)
{
assert (str1 && str2);
// 比較
while(*str1++ == *str2++)
{
if(*str1 == '\0')
{
return 0;//等于
}
}
if(*str1 > *str2)
return 1;//大于
else
return -1;//小于
//return (*str1 - *str2);
//通過相減判斷大于或小于
}
}
int main()
{
char *p1 = "abcdef";
char *p2 = "abqwe";
int ret = my_strcmp(p1, p2);
printf("ret = %d\n", ret);
return 0;
}
三、長度受限制的字符串函數(shù)
strncpy
char * strncpy ( char * destination, const char * source, size_t num );//單位是字節(jié)
- 將源的前 num 個字符復制到目標。 如果在復制 num 個字符之前找到源 C 字符串的結(jié)尾(由空字符表示),則用零填充目標,直到總共寫入了 num 個字符。
- 拷貝num個字符從源字符串到目標空間。
- 如果源字符串的長度小于num,則拷貝完源字符串之后,在目標的后邊追加0,直到num個。
例1:
int main()
{
char arr1[10] = "abcdefgh";
char arr2[] = "bit";
stcncpy(arr1, arr2, 6);
return 0;
}
調(diào)試結(jié)果:

例2:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[10] = "abcdefgh";
char arr2[] = "bit";
strncpy(arr1, arr2, 6);
return 0;
}
調(diào)試結(jié)果:

由此可見,strncpy函數(shù)能拷貝任意長度的字符,當拷貝的字符長度不夠拷貝數(shù)時,用 '\0' 進行補充,直到拷貝數(shù)相等
strncpy函數(shù)的模擬實現(xiàn)
#include <stdio.h>
#include <assert.h>
void my_strncpy(char *dest, const char *src, int n)
{
assert(src);
char* p1 = dest;
const char* p2 = src;
while (n--)
{
*p1++ = *p2++;
}
}
int main()
{
char arr1[20] = "hello"
char arr2[] = "world";
my_strncpy(arr1, arr2, 3);
return 0;
}
strncat
char * strncat ( char * destination, const char * source, size_t num );
- 將源的前 num 個字符附加到目標,再加上一個終止空字符
- 如果 source 中 C 字符串的長度小于 num,則只復制終止空字符之前的內(nèi)容。
例1:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[30] = { "hello\0xxxxxx" };
char arr2[] = "world";
strncat(arr1, arr2, 4);
return 0;
}
調(diào)試結(jié)果:

例2:
int main()
{
char arr1[30] = { "hello\0xxxxxxxxx" };
char arr2[] = "world";
strncat(arr1, arr2, 8);
return 0;
}
調(diào)試結(jié)果:

由此可見,不管追加幾個數(shù),都會在追加字符串后加上 '\0',而一旦追加數(shù)超過了追加字符串的長度,在追加完整字符串后面再加上'\0'后便結(jié)束了
strncat函數(shù)的模擬實現(xiàn)
#include<stdio.h>
#include<assert.h>
#include<string.h>
void my_strncat(char* dest, const char* src, int len1, int len2, int n)
{
char* ret = dest + len1;
assert(src);
assert(n <= len2);
while ((n--) && (*ret++ = *src++))
{
;
}
}
int main()
{
char arr1[20] = "abcd";
char arr2[] = "efghi";
int len1 = strlen(arr1);
int len2 = strlen(arr2);
int n = 0;
scanf("%d", &n);
my_strncat(arr1, arr2, len1, len2, n);
return 0;
}
strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
比較到出現(xiàn)另個字符不一樣或者一個字符串結(jié)束或者num個字符全部比較完。
例:
#include <stdio.h>
#include <string.h>
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcqwer";
int ret = strncmp(p1, p2, 4);
printf("%d\n", ret);
}
執(zhí)行結(jié)果:


strncmp函數(shù)的模擬實現(xiàn)
#include<stdio.h>
#include<assert.h>
#include<string.h>
int my_strncmp(const char *dest, const char *src, int n)
{
int ret = 0;
assert(dest);
assert(src);
assert(n);
while( (n--) && !(ret = (unsigned char)*dest-(unsigned char)*src) && *dest )
{
dest++;
src++;
}
return ret;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcedef";
int n = 0;
int ret = 0;
int i = 0;
scanf("%d",&n);
ret = my_strncmp(arr1, arr2, n);
if(ret == 0)
{
for(i=0; i<n; i++)
printf("%c",arr1[i]);
printf("=");
for(i=0; i<n; i++)
printf("%c",arr2[i]);
}
else if(ret < 0)
{
for(i=0; i<n; i++)
printf("%c",arr1[i]);
printf("<");
for(i=0; i<n; i++)
printf("%c",arr2[i]);
}
else
{
for(i=0; i<n; i++)
printf("%c",arr1[i]);
printf(">");
for(i=0; i<n; i++)
printf("%c",arr2[i]);
}
return 0;
}
四、字符串查找
strstr
char * strstr ( const char *, const char * );
返回指向 str1 中第一次出現(xiàn) str2 的指針,如果 str2 不是 str1 的一部分,則返回空指針。
例:
int main()
{
char *p1 = "abcdefabcdef";
char *p2 = "def";
char * ret = strstr(p1, p2);
if(ret == NULL)
{
printf("子串不存在");
}
else
{
printf("%s\n", ret);
}
system("Pause");
return 0;
}
執(zhí)行結(jié)果:

由此可得出,當有主字符串中存在兩個及以上子串時,優(yōu)先按第一次出現(xiàn)相同的地址進行打印,并且會一直打印完剩下的字符串
strstr函數(shù)的模擬實現(xiàn)
#include <stdio.h>
#include <assert.h>
//KMP 算法
char *my_strstr(const char *p1, const char *p2)
{
assert(p1 != NULL);
assert(p2 != NULL);
char *s1 = NULL;
char *s2 = NULL;
char *cur = (char*)p1;//當前指針current
if(*p2 == '\0')
{
return (char*)p1;
}
while(*cur)
{
s1 = cur;
s2 = (char*)p2;
while(*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if(*s2 == '\0')
{
return cur;//找到子串
}
cur++;
}
return NULL;//找不到子串
}
int main()
{
char *p1 = "abcdef";
char *p2 = "def";
char * ret = my_strstr(p1, p2);
if(ret == NULL)
{
printf("子串不存在\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
strtok
char * strtok (char *str, const char *sep);
- sep參數(shù)是個字符串,定義了用作分隔符的字符集合
- 第一個參數(shù)指定一個字符串,它包含了0個或者多個由sep字符串中一個或者多個分隔符分割的標記。
- strtok函數(shù)找到str中的下一個標記,并將其用 \0 結(jié)尾,返回一個指向這個標記的指針。(注:strtok函數(shù)會改 變被操作的字符串,所以在使用strtok函數(shù)切分的字符串一般都是臨時拷貝的內(nèi)容并且可修改。)
- strtok函數(shù)的第一個參數(shù)不為 NULL ,函數(shù)將找到str中第一個標記,strtok函數(shù)將保存它在字符串中的位置。
- strtok函數(shù)的第一個參數(shù)為 NULL ,函數(shù)將在同一個字符串中被保存的位置開始,查找下一個標記。
- 如果字符串中不存在更多的標記,則返回 NULL 指針。
例:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "qpzyahxf@163.com";
char *p = "@.";
char buf[1024] = {0};
//strtok會改變字符串內(nèi)容,buf防止原字符串被切割(保護原始數(shù)據(jù))
strcpy(buf, arr);
//切割buf中的字符串
char *ret = NULL;
for(ret = strtok(arr, p); ret != NULL; ret = strtok(NULL, p))
{
printf("%s\n", ret);
}
// char *ret = strtok(arr, p);
// printf("%s\n", ret);
// ret = strtok(NULL, p);
// printf("%s\n", ret);
// ret = strtok(NULL, p);
// printf("%s\n", ret);
return 0;
}
執(zhí)行結(jié)果:

strtok函數(shù)的模擬實現(xiàn)
#include <stdio.h>
char *my_strtok(char *str1 ,char *str2)
{
static char *p_last = NULL;
if(str1 == NULL && (str1 = p_last) == NULL)
{
return NULL;
}
char *s = str1;
char *t = NULL;
while(*s != '\0')
{
t = str2;
while(*t != '\0')
{
if(*s == *t)
{
p_last = s + 1;
if( s - str1 == NULL)
{
str1 = p_last;
break;
}
*s = '\0';
return str1;
}
t++;
}
s++;
}
return NULL;
}
int main()
{
char arr[] = "qpzyahxf@163.com";
char *ret = NULL;
char *p = "@.";
for(ret = my_strtok(arr, p); ret != NULL; ret = my_strtok(NULL, p))
{
printf("%s\n", ret);
}
return 0;
}
五、錯誤信息報告
strerror
char * strerror(int errum);
返回錯誤碼,所對應的錯誤信息。
例1:
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
//1-10錯誤碼的返回返回信息
for(i = 0; i <= 10; i++)
{
printf("%d %s\n", i, strerror(i));
}
system("pause");
return 0;
}
執(zhí)行結(jié)果:

在實際在使用的時候,錯誤碼并非由我們來控制的,而是接收系統(tǒng)返回的錯誤信息
例2:
#include <stdio.h>
#include string.h>
#include <errno.h>
char *str = strerror(errno);//需引用頭文件errno.h
printf("%s\n", str);
//errno 是一個全局的錯誤碼的變量
//當c語言的庫函數(shù)在執(zhí)行過程中,發(fā)生了錯誤,就會把對應的錯誤碼,復制到errno中
例3:
#include <stdio.h>
#include <errno.h>
int main()
{
//打開文件
FILE *pf = fopen("test.txt", "r");
if(pf == NULL)
{
printf("%s\n", strerror(errno));//知道錯誤的原因
}
else
{
printf("open file success.\n");
}
return 0;
}
執(zhí)行結(jié)果:

六、字符操作
字符分類函數(shù)
| 函數(shù) | 如果他的參數(shù)符合下列條件就返回真 |
| iscntrl | 任何控制字符 |
| isspace | 空白字符:空格‘ ',換頁‘\f',換行'\n',回車‘\r',制表符'\t'或者垂直制表符'\v' |
| isdigit | 十進制數(shù)字 0~9 |
| isxdigit | 十六進制數(shù)字,包括所有十進制數(shù)字,小寫字母a~f,大寫字母A~F |
| islower | 小寫字母a~z |
| isupper | 大寫字母A~Z |
| isalpha | 字母a~z或A~Z |
| isalnum | 字母或者數(shù)字,a~z,A~Z,0~9 |
| ispunct | 標點符號,任何不屬于數(shù)字或者字母的圖形字符(可打印) |
| isgraph | 任何圖形字符 |
| isprint | 任何可打印字符,包括圖形字符和空白字符 |
注:0位假,非0為真
字符轉(zhuǎn)換
int tolower ( int c ); //tolower 轉(zhuǎn)小寫字母 int toupper ( int c ); //toupper 轉(zhuǎn)大寫字母
例1:
#include <stdio.h>
#include <ctype.h>
int main()
{
char ch = tolower('Q');
putchar(ch);
system("pause");
return 0;
}
執(zhí)行結(jié)果:

例2:
#include <stdio.h>
#include <ctype.h>
int main()
{
//大寫字母轉(zhuǎn)小寫
char arr[] = "No Mercy";
int i = 0;
while(arr[i])
{
if(isupper(arr[i]))
{
arr[i] = tolower(arr[i]);
}
i++;
}
printf("%s\n", arr);
return 0;
}
執(zhí)行結(jié)果:

七、內(nèi)存操作函數(shù)
在之前的學習中,我們了解了字符串拷貝可以使用strcpy函數(shù),但是strcpy函數(shù)具有局限性。
當拷貝的數(shù)據(jù)不是字符串時,比如說int類型、float類型,還能使用strcpy函數(shù)嗎?
strcpy函數(shù)在拷貝的時候是以\0為字符串拷貝的結(jié)束標志,那么在拷貝其它類型數(shù)據(jù)的時候,拷貝該結(jié)束的時候不一定存在\0。所以使用strcpy函數(shù)肯定是行不通的。那怎么辦呢?
此時我們就可以使用memcpy函數(shù)-- - 內(nèi)存拷貝函數(shù),用來拷貝任意類型數(shù)據(jù)。
memcpy
void *memcpy ( void * destination, const void * source, size_t num ); //void* - 通用類型的指針-無類型指針 //dest destination 表示內(nèi)存拷貝的目的位置 //src source 表示內(nèi)存拷貝的起始位置 //size_t num 表示內(nèi)存拷貝的字節(jié)數(shù)
- 函數(shù)memcpy從source的位置開始向后復制num個字節(jié)的數(shù)據(jù)到destination的內(nèi)存位置
- 這個函數(shù)在遇到 '\0' 的時候并不會停下來
- 如果source和destination有任何的重疊,復制的結(jié)果都是未定義的。
例:
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[5] = { 0 };
memcpy(arr2, arr1, sizeof(arr1));
return 0;
}
調(diào)試結(jié)果:

memcpy函數(shù)的模擬實現(xiàn)
void *my_memcpy ( void *dest, const void *src, size_t num)
{
void *ret = dest;
assert(dest && src);
while (num--)
{
//*(char*)dest = *(char*)src;
//dest = (char*)dest + 1;//++(char*)dest
//src = (char*)src + 1;//++(char*)src
*((char*)dest)++ = *((char*)src)++;
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
return 0;
}
memmove
void * memmove ( void * destination, const void * source, size_t num ); //void* - 通用類型的指針-無類型指針 //dest destination 表表示內(nèi)存移動的目的位置 //src source 表示內(nèi)存移動的起始位置 //size_t num 表示移動內(nèi)存的字節(jié)數(shù)
和memcpy的差別就是memmove函數(shù)處理的源內(nèi)存塊和目標內(nèi)存塊是可以重疊的
如果源空間和目標空間出現(xiàn)重疊,就得使用 比特科技 memmove函數(shù)處理
memmove函數(shù)的模擬實現(xiàn)
void *my_memmove( void *dest, const void *src, size_t num)
{
void * ret = dest;
assert(dest && src);
if (dest <= src || (char *)dest >= ((char *)src + num))
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//從后向前拷貝
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr1[10] = {0,1,2,3,4,5,6,7,8,9};
int arr2[10] = {0};
my_memmove(arr1 + 2, arr1, 20);
return 0;
}
memset
void* memset(void* dest, int c, size_t count);
作用:Sets buffers to a specified character.(將緩沖區(qū)設(shè)置為指定的字符)
以字節(jié)為內(nèi)存設(shè)置單位
例:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdefg";
memset(arr, '*', 4);
printf("%s", arr);
return 0;
}
執(zhí)行結(jié)果:

memcmp
int memcmp ( const void * ptr1,
const void * ptr2,
size_t num );
比較從ptr1和ptr2指針開始的num個字節(jié)
返回值如下

例:
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 1,2,3,4,8 };
int ret = memcmp(arr1, arr2, sizeof(arr1));
if (ret > 0)
{
printf("arr1 > arr2");
}
else if (ret == 0)
{
printf("arr1 == arr2");
}
else
{
printf("arr1 < arr2");
}
return 0;
}
執(zhí)行結(jié)果:

總結(jié)
到此這篇關(guān)于C語言進階教程之字符串&內(nèi)存函數(shù)的文章就介紹到這了,更多相關(guān)C語言字符串&內(nèi)存函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解C語言中freopen()函數(shù)和fclose()函數(shù)的用法
這篇文章主要介紹了詳解C語言中freopen()函數(shù)和fclose()函數(shù)的用法,是C語言入門學習中的基礎(chǔ)知識,需要的朋友可以參考下2015-08-08
C++實現(xiàn)LeetCode(692.前K個高頻詞)
這篇文章主要介紹了C++實現(xiàn)LeetCode(692.前K個高頻詞),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08

