碼字不易,對你有幫助 點贊/轉發/關注 支持一下作者
微信搜公衆號:不會編程的程序圓
看更多幹貨,獲取第一時間更新
思維導圖
目錄
文章目錄
正文
- strlen & strlen_s
- getchar() & putchar()
- strcmp & strncmp()
- strcpy() & strncpy()
- strcat & strncat()
以上這幾個函數在我的另一篇文章中我已經詳細講過了。文章鏈接
這篇文章中,我們先主要來簡單實現一下這幾個函數,然後再討論其他函數。
序 老朋友們
myStrlen & myStrcat & myStrcpy
#include<stdio.h>
#include<assert.h>
int myStrlen(const char* str);
char* myStrcat(char* dest, const char* src);
char* myStrcpy(char* dest, const char* src);
int main(void) {
char str1[12] = "Today ";
char str2[6] = "sucks";
//str1 和 str2 不是空指針
if (str1 && str2) {
printf("str1 is :%s str2 is :%s\n", str1, str2);
printf("str1 has %d characters, str2 has %d characters\n", myStrlen(str1), myStrlen(str2));
printf("str1 + str2 = %s\n", myStrcat(str1, str2));
printf("Copy str2 to str1,now str1 is:%s\n", myStrcpy(str1, str2));
}
return 0;
}
int myStrlen(const char* str) {
assert(str != NULL);
const char* start = str;
while (*++str);
return str - start;
}
char* myStrcat(char* dest, const char* src) {
assert(dest && src);
char* ret = dest;
//找到 dest 中 '\0' 的位置
while (*++dest);
while (*dest++ = *src++);
return ret;
}
char* myStrcpy(char* dest, const char* src) {
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++);
return ret;
}
MyStrcmp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
// strcmp 返回值:
// 如果 str1 > str2, 返回的是一個比 0 大的數(不一定是 1)
// 如果 str1 < str2, 返回的是一個比 0 小的數(不一定是 -1)
// str1 > str2 在 VS 上可能是 1,在 linux gcc 中不一定是 1
// 爲了方便實現,我們就選擇返回 -1 和 1
int MyStrcmp(char* str1, char* str2) {
assert(str1 != NULL && str2 != NULL);
while (*str1 != '\0' && *str2 != '\0') {
if (*str1 > *str2) {
return 1;
}
else if (*str1 < *str2) {
return -1;
}
str1++;
str2++;
}
// 當循環退出時,str1 或 str2 達到字符串末尾 '\0'
// 對於 strcmp 來說,如果前面的字符都相同,那麼,短的字符串就小
// 因爲 '\0' 的 ASCII 值是 0,它是最小的(達到字符串結尾的字符串用'\0'來進行最後一次比較)
if (*str1 > * str2) {
return 1;
}
else if (*str1 < *str2) {
return -1;
}
else {
return 0;
}
}
int main(void) {
char str1[] = "haha";
char str2[] = "haha";
int ret = MyStrcmp(str1, str2);
if (ret > 0) {
printf("str1 > str2\n");
}
else if (ret < 0) {
printf("str1 < str2\n");
}
else {
printf("str1 == str2\n");
}
return 0;
}
始 新朋友們
1. strstr
char *strstr( const char* str, const char* substr );
定義於頭文件:<string.h>
查找
substr
所指的空終止字節字符串在str
所指的空終止字節字符串中的首次出現。不比較空終止字符。若
str
或substr
不是指向空終止字節字符串的指針,則行爲未定義。參數:
str - 指向要檢驗的空終止字節字符串的指針
substr - 指向要查找的空終止字節字符串的指針返回值:
指向於
str
中找到的子串首字符的指針,或若找不到該子串則爲 NULL 。若substr
指向空字符串,則返回str
。
想象實現 MyStrstr 是我們產品經理的需求,先來看看他的需求是什麼:
- 查找 字符串 substr 在字符串 str 中首次出現的位置(返回找到的字串的首字符的指針)
思路
- 我們先在 str 中找到 substr 的第一個元素
- 比較 str 的下一個字符與 substr 的下一個字符是否相等(可以循環實現)
- 如果 substr 中有一個字符與 str 中的是不一樣的,那麼 substr 應該從首個元素開始重新在 str 中繼續向下尋找,直到找到或者 str 結束
- 我忽略了一個條件:當 substr 重新從第一個元素開始在 str 中尋找時,str 應該重置爲 上一次 str 所在位置的下一個字符處(比如 “cacacat” 中尋找 “cacat”)
實現
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* MyStrstr(const char* str, const char* substr) {
const char* substr_begin = substr;
const char* str_next = str;
assert(str != NULL && substr != NULL);
if (*str == '\0' || *substr == '\0') {
return NULL;
}
while (*str_next != '\0') {
// substr 從頭開始
substr = substr_begin;
str = str_next;
if (*str == *substr) {
while (*str == *substr && *str != '\0' && *substr != '\0') {
str++;
substr++;
}
// 循環退出三種情況
if (*substr == '\0') {
return str_next;
}
if (*str == '\0') {
return NULL;
}
// 剩下的一種就是 str 的字串 和 substr 不是完全匹配的,重新找 substr 的第一個元素
}
str_next++;
}
}
int main(void) {
char str1[] = "Hello World";
char str2[] = "lo";
char* ptr = MyStrstr(str1, str2);
if (MyStrstr != NULL) {
printf("%s\n", ptr);
}
else {
printf("str2 在 str1 中不存在\n");
}
return 0;
}
2. strtok
瞭解這個函數即可。
char *strtok( char *str, const char *delim )
定義於頭文件 < string.h >
參數:
str - 指向要記號化的空終止字節字符串的指針 delim - 指向標識分隔符的空終止字節字符串的指針 返回值:
返回指向下個記號起始的指針,或若無更多記號則返回 NULL 。
注意:
此函數是破壞性的:它寫入 ‘\0’ 字符於字符串
str
的元素。特別是,字符串字面量不能用作strtok
的首參數。每次對
strtok
的調用都會修改靜態對象:它不是線程安全的。
strtok 中有個 static 修飾的變量記錄下來上次位置
int main(void) {
char str[] = "World is so beautiful!Hi,Look!";
char* pch;
pch = strtok(str, ",. !");
while (pch != NULL) {
printf("%s\n", pch);
pch = strtok(NULL, ",. !");
}
return 0;
}
// 輸出:
World
is
so
beautiful
Hi
Look
3. memcpy
void* memcpy( void *dest, const void *src, size_t count );
定義於頭文件 :<string.h>
從
src
所指向的對象複製count
個字符到dest
所指向的對象。兩個對象都被轉譯成 unsigned char 的數組。若訪問發生在 dest 數組結尾後則行爲未定義。若對象重疊(這違背
restrict
契約) (C99 起),則行爲未定義。若dest
或src
爲非法或空指針則行爲未定義。參數:
dest - 指向要複製的對象的指針 src - 指向複製來源對象的指針 count - 複製的字節數 返回值:
返回
dest
的副本,本質爲更底層操作的臨時內存地址,在實際操作中不建議直接使用此地址,操作完成以後,真正有意義的地址是dest本身。注意:
memcpy
可用於設置分配函數所獲得對象的有效類型。
memcpy
是最快的內存到內存複製子程序。它通常比必須掃描其所複製數據的 strcpy ,或必須預防以處理重疊輸入的 memmove 更高效。許多 C 編譯器將適合的內存複製循環變換爲
memcpy
調用。在嚴格別名使用禁止檢驗同一內存爲二個不同類型的值處,可用
memcpy
轉換值。
注:
void* 只包含地址,沒有內存空間大小這樣的信息,所以 void* 不能解引用,也不能進行運算
void* 是爲了兼容各種類型的指針,算是一種簡單的“泛型編程”。
簡單的用法:
int main(void) {
char src[] = "Once upon a midnight dreary", dest[4];
memcpy(dest, src, sizeof dest);
for (size_t i = 0; i < sizeof dest; i++)
putchar(dest[i]);
int* p = malloc(3 * sizeof(int));// 分配的內存沒有有效類型
int arr[3] = { 1, 2, 3 };
memcpy(p, arr, 3 * sizeof(int));// 分配到內存有了有效類型 int
return 0;
}
實現
#include<stdio.h>
#include<assert.h>
void* MyMemcpy(void* dest, const void* src, size_t count) {
assert(dest != NULL && src != NULL);
void* ret = dest;
for (size_t i = 0; i < count; i++) {
*(char*)dest = *(char*)src;// 逐字節複製
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return dest;
}
int main(void) {
char src[] = "Once upon a midnight dreary", dest[4];
MyMemcpy(dest, src, sizeof dest);
for (size_t i = 0; i < sizeof dest; i++)
putchar(dest[i]);
int* p = malloc(3 * sizeof(int));// 分配的內存沒有有效類型
int arr[3] = { 1, 2, 3 };
MyMemcpy(p, arr, 3 * sizeof(int));// 分配到內存有了有效類型 int
for (int i = 0; i < 3; i++) {
printf("%d ", *(p + i));
}
return 0;
}
思考
請看下例:
#include<stdint.h>
#include<inttypes.h>
#include<stdio.h>
#include<string.h>
int main(void) {
double d = 1.0;
int64_t i = 1;
i = d;
// 輸出:0.000000 5509945
printf("%f %d\n", i, i);
memcpy(&i, &d, sizeof d);
printf("%f %"PRId64" ", i, i);//#define PRId64 "lld"
//輸出:1.000000 4607182418800017408
return 0;
}
memcpy 該表了 i 作爲整型的內存佈局,所以 i 可以直接用 %f 輸出
4. memmove
void* memmove( void* dest, const void* src, size_t count )
定義於頭文件 :< string.h >
從
src
所指向的對象複製count
個字節到dest
所指向的對象。兩個對象都被轉譯成 unsigned char 的數組。對象可以重疊:如同複製字符到臨時數組,再從該數組到dest
一般發生複製。若出現 dest 數組末尾後的訪問則行爲未定義。若
dest
或src
爲非法或空指針則行爲未定義。參數:
dest - 指向複製目的對象的指針 src - 指向複製來源對象的指針 count - 要複製的字節數 返回值:
返回
dest
的副本,本質爲更底層操作的臨時內存地址,在實際操作中不建議直接使用此地址,操作完成以後,真正有意義的地址是dest本身。注意:
memmove
可用於設置由分配函數獲得的對象的有效類型。儘管說明了“如同”使用臨時緩衝區,此函數的實際實現不會帶來二次複製或額外內存的開銷。常用方法( glibc 和 bsd libc )是若目標在源之前開始,則從緩衝區開始正向複製,否則從末尾反向複製,完全無重疊時回落到更高效的 memcpy。
在嚴格別名時用禁止檢驗同一內存爲二個不同類型的值時,可使用
memmove
轉換值。
重疊的含義
實現
#include<stdio.h>
#include<assert.h>
void* MyMemcpy(void* dest, const void* src, size_t count) {
assert(dest != NULL && src != NULL);
void* ret = dest;
for (size_t i = 0; i < count; i++) {
*(char*)dest = *(char*)src;// 逐字節複製
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return dest;
}
void* MyMemmove(void* dest, const void* src, size_t count) {
assert(dest != NULL && src != NULL);
void* ret = dest;
char* pdest = (char*)dest;
char* psrc = (char*)src;
// 先判斷有沒有重疊
// 如果沒有重疊,應該用更爲高效的 memcpy
if (dest >= (char*)src + count || src >= dest) {
MyMemcpy(dest, src, count);
}
else {
// 分別指向 dest 和 src 的最後一個字符
pdest = pdest + count - 1;
psrc = psrc + count - 1;
while (count--) {
*pdest = *psrc;
pdest--;
psrc--;
}
return dest;
}
}
int main(void) {
char str[] = "123456789";
puts(str + 1);
MyMemcpy(str + 1, str, 3);
puts(str + 1);
MyMemcpy(str, "123456789", sizeof(str));
MyMemmove(str + 1, str, 3);
puts(str + 1);
return 0;
}
//輸出:
23456789
11156789
12356789
5. memcmp
int memcmp( const void* lhs, const void* rhs, size_t count );
參數:
lhs, rhs - 指向要比較的對象的指針 count - 要檢驗的字節數 返回值:
若
lhs
以字典序出現前於rhs
則爲負值。若
lhs
與rhs
比較相等,或 count 爲零則爲零。若
lhs
以字典序出現後於rhs
則爲正值。注意:
此函數讀取對象表示,而非對象值,而且典型地只對字節數組有意義:結構體可以含有填充字節而其值不確定,存儲於聯合體最近存儲成員後的任何字節的值是不確定的,且一個類型可以對相同值擁有二種或多種表示(對於 +0 和 -0 或 +0.0 和 –0.0 的相異編碼、類型中不確定填充位)。
簡單的應用:
#include<stdio.h>
#include<string.h>
int main(void) {
int arr1[] = { 0, 1, 2, 3 };
int arr2[] = { 0, 1, 2, 3 };
int arr3[] = { -0, 1, 2, 3 };
if(memcmp(arr1, arr2, sizeof arr1) == 0){
printf("arr1 == arr2\n");
}
else {
printf("arr1 != arr2\n");
}
if (memcmp(arr1, arr3, sizeof arr1) == 0) {
printf("arr1 == arr3\n");
}
else {
printf("arr1 != arr3\n");
}
return 0;
}
6. memset
void *memset( void *dest, int ch, size_t count );
定義於頭文件 <string.h>
複製值
ch
(如同以 (unsigned char)ch 轉換到 unsigned char 後)到dest
所指向對象的首count
個字節。若出現 dest 數組結尾後的訪問則行爲未定義。若
dest
爲空指針則行爲未定義。參數:
dest - 指向要填充的對象的指針 ch - 填充字節 count - 要填充的字節數 返回值:
dest
的副本,本質爲更底層操作的臨時內存地址,在實際操作中不建議直接使用此地址,操作完成以後,真正有意義的地址是dest本身。
簡單應用:
#include<stdio.h>
#include<string.h>
int main(void) {
char str[] = "Hello World";
memset(str, '\0', sizeof str);
puts(str);
return 0;
}
在 Github 上看更全的目錄:
https://github.com/hairrrrr/C-CrashCourse
以後的這個系列的代碼都會上傳上去,歡迎 star
以上就是本次的內容。
如果文章有錯誤歡迎指正和補充,感謝!
最後,如果你還有什麼問題或者想知道到的,可以在評論區告訴我呦,我可以在後面的文章加上你們的真知灼見。
關注我,看更多幹貨!
我是程序圓,我們下次再見。