思维导图

目录
@[toc]
正文
- 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"; 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;
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>
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++; }
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_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_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));
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)); 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;
printf("%f %d\n", i, i);
memcpy(&i, &d, sizeof d);
printf("%f %"PRId64" ", i, i);
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;
if (dest >= (char*)src + count || src >= dest) { MyMemcpy(dest, src, count); } else { 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; }
|