我們平時寫代碼對字符進行操作時,常常會用到庫函數裏的字符操作符,那這些庫裏的操作符究竟是怎麼計算的,現在我們來對這些庫函數進行剖析,並且來模擬實現一下
常見的庫函數字符串操作符
1、strcmp
2、strcpy
3、strcat
4、strstr
5、strchr
6、strlen
7、memcpy
8、memmove
1、strcmp
先來看看庫函數裏的strcmp描述
strcmp返回值是int行,如果string1大於string2,則返回的是一個大於0的數,如果string1小於string2,則返回的是一個小於0的數,string1等於string2,則返回
strcmp的源碼如下:
int __cdecl strcmp (
const char * src,
const char * dst
)
{
int ret = 0 ;
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
++src, ++dst;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
我們要看的是while循環這個語句, ! (ret = *(unsigned char *)src - *(unsigned char *)dst)意思是拿指針變量src所指向的字符值(即*src)減去指針變量dst所指向的字符值(即*dst),差值賦給ret,再取非運算,最後與*dst進行與運算
接下來我們來對strcmp模擬實現一下
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
int my_strcmp(const char *str1,const char *str2)
{
assert(str1);
assert(str2);
while(*str1 == *str2)
{
if(*str1 == '\0') //當兩個字符相等並且其中一個爲'\n'時,字符串中所有字符比較完
{
return 0;
}
str1++;
str2++;
}
return *str1-*str2; //若str1大於str2,返回正值,小於就返回負值
}
int main()
{
char *p = "abcdef";
char *q = "abcde";
int ret = my_strcmp(p,q);
printf("%d\n",ret);
system("pause");
return 0;
}
模擬strcmp函數的返回值若大於0,說明str1大於str2,小於0是str1小於str2,相等爲0;
strcpy函數
先來看看庫函數裏的strcmp描述
strcpy是將一個字符串的內容拷貝到另外一個字符串中,每一個函數返回目標字符串。沒有返回值是保留顯示一個錯誤。
strcmp的源碼如下:
#include <assert.h>
char *strcpy(char *dst, const char *src) {
assert((dst != NULL) && (src != NULL));
char *tmp = dst;
while ((*dst++ = *src++) != '\0')
{
;
}
return tmp;
}
tmp保留目標字符串的其實位置#define _CRT_SECURE_NO_WARNINGS 1#include <windows.h>#include <assert.h>#include <stdio.h>#include <string.h>char *my_strcpy(char *str,const char* src){char *tmp = str; //保留str起始位置assert(str);assert(src);while(*str++ = *src++){;}return tmp;}int main(){char arr1[] = "abcdefghikj";char arr2[] = "123456789";char* ret = my_strcpy(arr2,arr1);printf("%s\n",ret);system("pause");return 0;} 上面程序可以將arr1中的字符拷貝到arr2中。但是程序能不能完整的通過呢?
答案是可以拷貝過去,但會出現堆棧溢出
在執行while(*str++ = *src++)這一句時,當src的長度大於str時,*src++指向j,而*str++指向的內容不是本數組的內容,所以在訪問這一塊空間時會出現越界,導致溢出!
所以在對目標數組的訪問時,應當給目標數組指定大於源數組大小空間。
char arr1[] = "abcdefghikj";
char arr2[20] = "123456789";
先來看看庫函數裏的strcmp描述
strcat函數簡單來說就是將一個字符串追加到另一個字符串的後面,
例如:
將world追加到hello 的後面
strcat源碼:
Char* strcat ( char * dst , const char * src )
{
char * cp = dst;
while( *cp !=’\0’)
cp++;
while( (*cp++ = *src++)!=’\0’ ) ;
return( dst );
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <assert.h>
#include <stdio.h>
char *my_strcat(char *str,const char *src)
{
char *tmp = str; //將str先給一個char* 變量tmp,tmp保留目標字符串的其實位置
assert(str);
assert(src);
while(*str) //將str++到最後'\0'處
str++;
while(*src)
*str++=*src++; //將源字符串的元素一個一個的追加到目標字符串空間,知道源字符串追加完
return tmp;
}
int main()
{
char arr1[20] = "hello "; //注意目標字符串要有大小,不然容易發生溢出
char arr2[] = "world!";
char* ret = my_strcat(arr1,arr2);
printf("%s\n",ret);
system("pause");
return 0;
}
注意,源目標字符串不能改變,所以最好加const來修飾。上面的strcpy也一樣!
strstr函數
先來看看庫函數裏的strcmp描述
strstr函數是判定str2是不是str1的子串,如果是就返回str1中首次出現str2的地方,如果不是就返回NULL
strstr模擬實現:
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
char *my_strstr(const char* str1,const char* str2)
{
const char *tmp = str1; //tmp保留目標字符串的其實位置
assert(str1);
assert(str2);
while(*tmp)
{
const char *s1 = tmp; //只是查找不改變tmp的內容
const char *s2 = str2;
while((*s1)&&(*s2)&&(*s1 == *s2)) //當s1,s2不等於0,並且找到s1中和s2相等的第一個元素時才能查找一下個
{
s1++;
s2++;
}
if(*s2 == 0)
{
return (char *)tmp; //tmp是const修飾過得,要將tmp轉換爲char*型
}
tmp++;
}
return NULL;
}
int main()
{
char *p = "abcdefg";
char *q = "def";
char *ret = my_strstr(p,q);
printf("%s\n",ret);
system("pause");
return 0;
}
注意,所有的字符串都只是比較,不能改變內容,所以在創建時都要加const
這裏要是一個字符串中有幾個另一個字符串,則返回的是第一次出現時的位置
例如:str1 = "abcdefabcdef" str2 = "cdef";返回的位置是第三個元素的位置
strchr函數
先來看看庫函數裏strchr的描述
strchr和strstr一樣,strchr是在str1裏找一個字符,如果有則返回第一次出現該字符的位置,如果沒有則返回NULL
直接來看strchr的模擬實現:
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
char *my_strchr(const char *str1,int ch)
{
const char *tmp = str1;
assert(str1);
while(*tmp)
{
const char *s1 = tmp;
if(*s1 == ch)
return (char *)tmp;
tmp++;
}
return NULL;
}
int main()
{
char *p = "abcdef";
int ch = 'd';
char *ret = my_strchr(p,ch);
printf("%s\n",ret);
system("pause");
return 0;
}
和strstr一樣,如果出現多次,則返回第一次出現的位置
strlen函數
先看看庫函數裏strlen的描述
strlen是測量字符串的長度的函數,在遇到\0時停下了。
在求字符串長度時,通常都是用sizeof和strlen,兩個在對字符串操作時的計算不一樣,strlen不包括'\0',而sizeof包括'\0'
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
int my_strlen(char *arr)
{
/*assert(arr); //用指針加1來計算字符串長度
int count = 0;
while(*arr++)
{
count++;
}
return count;*/
if(*arr!='\0')
{
return 1+my_strlen(arr+1); //一遞歸的形式計算長度
}
return 0;
}
int main()
{
char arr[]="abcdef";
int ret = 0;
ret = my_strlen(arr);
printf("%d\n",ret);
system("pause");
return 0;
}
上面的求字符串長度用了兩種方式,第一種是常用的使用指針的方式來計算長度,第二種用遞歸的形式來計算,遞歸計算起來調用時間比指針長,在長度較長時不建議用遞歸來計算。memcpy函數
先來看看庫函數裏memcpy的描述
memcpy 函數用於 把資源內存(src所指向的內存區域) 拷貝到目標內存(dest所指向的內存區域);拷貝多少個?有一個size變量控制 拷貝的字節數
memcpy模擬實現:
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
void *my_memcpy(void *str,const void *src,size_t n)
{
char *pstr = (char *)str;
char *psrc = (char *)src;
assert(str);
assert(src);
while(n--)
{
*pstr++ = *psrc++;
}
return str;
}
int main()
{
char p[20] = "abcdefghijk";
int num = 5;
my_memcpy(p+3,p,num);
printf("%s\n",p);
system("pause");
return 0;
}
上面的程序將指定的長度5,起始位置爲首元素位置,拷貝到第四位往後的位置.但是這種以內存拷貝的方式拷貝時,會出現重疊,上面的代碼最後的程序結果應該是abcabcabijk
用圖示的方法來解讀一下
這就是memcpy在拷貝函數是的過程,爲了解決這個重疊的問題引進了memmove函數
memmove函數
先來看看庫函數裏的memmove描述
memmove函數和memcpy函數功能一樣,memmove解決了重疊的問題
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
void *my_memcpy(void *str,const void *src,size_t n)
{
void *pstr = str;
assert(str);
assert(src);
while((((char *)src)<pstr) && (pstr<(char *)src+n))
{
pstr = (char *)pstr+n-1;
src = (char *)src+n-1;
while(n--)
{
*(char *)pstr = *(char *)src;
pstr = (char *)pstr -1;
src = (char *)src -1;
}
}
return str;
}
int main()
{
char p[20] = "abcdefghijk";
int num = 5;
my_memcpy(p+3,p,num);
printf("%s\n",p);
system("pause");
return 0;
}
memcpy在拷貝的過程中是從前面一個一個拷貝,直到拷貝結束,這樣才造成的重疊,那如果從後向前拷貝是不是就不會有重疊的情況了呢?
用圖示的方法模擬實現時看到可以實現不重疊,那代碼運行結果是不是和模擬的結果一樣呢
猜想沒錯,看了計算機在用memmove時就是從後向前拷貝,什麼時候可以用向前拷貝呢
當(((char *)src)<pstr) && (pstr<(char *)src+n),也就是說目標位置位於起始位置和起始位置加上要拷貝的大小的位置中間時,適合用從後向前拷貝,在其他時候前後都行。