一、基础篇:每个程序员必须掌握的8大函数
1.1 strlen - 字符串长度测量仪
基础用法:
c
size_t strlen(const char *str);
// 示例:
char name[] = "Alice";
int length = strlen(name); // 返回5
模拟实现:(三种)
c
//strlen的模拟实现(三种)
//递归
//计数器
//指针-指针之间的元素数
int my_strlen(const char*s)//递归
{
assert(s);
if (*s == '\0')
{
return 0;
}
else
{
return 1 + my_strlen(++s);
}
}
int my_strlen(const char*s)//计数器实现
{
assert(s)
int count = 0;
while (*s != '\0')
{
count++;
s++;
}
return count;
}
int my_strlen(const char* s)//指针-指针
{
assert(s);
char* start = s;
while (*s != '\0')
{
s++;
}
return (s - start);
}
int main()
{
char string[] = "Hello,world!";
int ret = my_strlen(string);
printf("The num of the string is %d", ret);
return 0;
}
超市扫码枪原理:
就像扫码枪读取商品条形码时自动计算位数:
c
void process_barcode(const char* code) {
size_t code_len = strlen(code);
if(code_len != 13 && code_len != 8) {
trigger_error("无效条码");
}
// 处理有效条码...
}
1.2 strcpy/strncpy - 字符串复印机
基础用法:
c
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, size_t n);
// 示例:
char src[] = "Hello";
char dest[10];
strncpy(dest, src, sizeof(dest)-1); // 安全复制
dest[sizeof(dest)-1] = '\0'; // 确保终止符
模拟实现strcpy:
c
//strcpy的实现(n)
//char* strcpy(char * destination, const char * source );
//思路:一个字符串,一个可改并且空间够大的目标空间,会将'\0'拷贝
//两个指针++的形式
char* my_strcpy(char*dest,const char*src)
{
assert(dest);
assert(src);
char* start = dest;
//while (*src != '\0')//初级
//{
// *dest = *src;
// dest ++;
// src ++;
//}
//*dest = '\0';
while (*dest++ = *src++)//高级,先执行,再判断。\0就先放进去后判断为0才退出
{
;
}
return start;
}
int main()
{
char src[] = "Hello,world";
char dest[20];
//strcpy(dest, src);
char* ret = my_strcpy(dest, src);
printf("%s", ret);
return 0;
}
模拟实现strncpy:
c
//模拟实现strncpy
//char * strncpy ( char * destination, const char * source, size_t num )
// 思路:分两种情况来判断,第一种:拷贝的字符数小于原空间字符数
//第二种:拷贝的字符数大于原空间字符数
char* my_strncpy(char* dest, const char* str1, size_t n)
{
assert(dest);
assert(str1);
char* ret = dest;
size_t len = strlen(str1);
if (len >= n)
{
for (size_t c = 0; c < n; c++)
{
*(dest + c) = *(str1 + c);
}
dest[n] = '\0';
return ret;
}
else
{
for (size_t i = 0; i < len; i++)
{
*(dest + i) = *(str1 + i);
}
for (size_t j = len; j <n; j++)
{
*(dest + j) = '\0';
}
return ret;
}
}
int main()
{
char dest[100] = "";
char str1[] = "Hello,world";
printf("%s", my_strncpy(dest, str1, 3));
//dest[2] = '\0';
return 0;
}
快递面单打印系统:
防止地址信息过长导致打印错位:
c
void print_address_label(const char* address) {
char buffer[50];
strncpy(buffer, address, sizeof(buffer)-1);
buffer[sizeof(buffer)-1] = '\0';
thermal_printer_print(buffer);
}
1.3 strcat/strncat - 字符串拼接工
基础用法:
c
char *strcat(char *dest, const char *src);
char *strncat(char *dest, const char *src, size_t n);
// 示例:
char path[100] = "/home/";
strncat(path, username, sizeof(path)-strlen(path)-1);
模拟实现strcat:
c
//模拟实现strcat
//char *strcat(char *dest, const char *src);
//思路:现在目标空间找到\0,然后从\0处往后复制,
// 一直到src不为\0处停下,最后再补一个\0作为字符串的终止符
char* my_strcat(char*dest,const char*src)
{
assert(dest);
assert(src);
char* ret = dest;
/* while (*dest)
{
dest++;
}*/
dest = dest + strlen(dest);//通过指针运算找到\0的指针
while (*src)
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
return ret;
}
int main()
{
char dest[20] = "Hello ";
char src[] = "world!";
//char* ret = strcat(dest, src);
char* ret = my_strcat(dest, src);
printf("%s",ret);
return 0;
}
模拟实现strncat:
c
//模拟实现strncat
//char * strncat ( char * destination, const char * source, size_t num );
//思路:找到目标空间的'\0',往后拷贝,分两种情况
//第一:if追加的字符数小于原来的字符数,后面补'\0'
//第二:大于,也补"\0'
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest);
assert(src);
char* ret = dest;
dest = dest + strlen(dest);
if (num < strlen(src))
{
for (size_t i = 0; i < num; i++)
{
*(dest + i) = *(src + i);
}
dest[num] = '\0';
}
else
{
for (size_t i = 0; i < strlen(src); i++)
{
*(dest + i) = *(src + i);
}
dest[strlen(src)] = '\0';
}
return ret;
}
int main()
{
char dest[100] = "Hello,";
char src[] = "world";
//char* ret = strncat(dest, src, 2);
char* ret = my_strncat(dest, src, 10);
printf("%s", ret);
return 0;
}
银行账户系统:
安全生成账户流水号:
c
void generate_account_id(char* buffer, size_t size) {
const char* prefix = "BOC-";
time_t now = time(NULL);
strncpy(buffer, prefix, size-1);
buffer[size-1] = '\0';
char timestamp[20];
snprintf(timestamp, sizeof(timestamp), "%ld", now);
strncat(buffer, timestamp, size-strlen(buffer)-1);
}
1.4 strcmp/strncmp - 字符串裁判员
基础用法:
c
int strcmp(const char *str1, const char *str2);
int strncmp(const char *str1, const char *str2, size_t n);
// 示例:
if(strcmp(input, "quit") == 0) exit(0);
模拟实现strcmp:
c
//模拟实现strcmp
//思路:取这两个字符串的指针,遍历比较,比ASCLL的值,相减
int my_strcmp(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
while (*str1 == *str2)//相等为一类,相等的时候判断是否为0,不为0,继续++比较
{
if (*str1 == '\0')
return 0;
str1++;
str2++;
}
return *str1 - *str2;//分类讨论,正负分为一类
}
int main()
{
char str1[100];
char str2[100];
printf("请输入两个字符串\n");
printf("第一个字符串为:");
scanf("%99s", str1);
printf("\n第二个字符串为:");
scanf("%99s", str2);
printf("\n比较两个字符串的大小\n");
if (my_strcmp(str1, str2) > 0)
{
printf("%s大,%s小", str1,str2);
}
else if (my_strcmp(str1, str2) == 0)
{
printf("%s与%s一样大", str1, str2);
}
else
{
printf("%s小,%s大", str1,str2);
}
return 0;
}
智能门禁系统:
比对门禁卡ID与数据库记录:
c
void check_access_card(const char* card_id) {
char valid_ids[][16] = {"0123ABCD", "4567EFGH"};
for(int i=0; i<2; i++){
if(strncmp(card_id, valid_ids[i], 8) == 0) {
unlock_door();
return;
}
}
trigger_alarm();
}
1.5 memset - 内存橡皮擦
基础用法:
c
void *memset(void *str, int c, size_t n);
// 示例:
int arr[10];
memset(arr, 0, sizeof(arr)); // 数组清零
医院叫号系统:
每天营业结束重置系统状态:
c
void daily_reset(SystemStatus* status) {
memset(status->waiting_queue, 0, sizeof(status->waiting_queue));
memset(status->current_number, 0, sizeof(status->current_number));
status->total_patients = 0;
}
1.6 memcpy - 内存搬运工
基础用法:
c
void *memcpy(void *dest, const void *src, size_t n);
// 示例:
int src[5] = {1,2,3,4,5};
int dest[5];
memcpy(dest, src, sizeof(src));
模拟实现:
c
//模拟实现memcpy
//void * memcpy ( void * destination, const void * source, size_t num );
//思路:与strcpy的实现类似,但是他是可以接受任意类型的指针的,要强转为char*
//一个个字节来复制,返回起始地址
void* my_memcpy(void* dest, const void* s, size_t num)
{
assert(dest);
assert(s);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)s;
dest = (char*)dest + 1;
s = (char*)s + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
//memcpy(arr2, arr1, 20);//这里的数字是占多少个字节
my_memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
交通信号灯控制系统:
备份当前路口信号状态:
c
void backup_traffic_status(TrafficLight* lights, int count) {
static TrafficLight backup[10];
memcpy(backup, lights, count * sizeof(TrafficLight));
// 当主系统故障时恢复备份
}
1.7 memmove - 智能搬运工
基础用法:
c
void *memmove(void *dest, const void *src, size_t n);
// 示例:
char str[] = "123456789";
memmove(str+2, str, 5); // 结果为"121234589"
模拟实现:
c
//模拟实现memmove
//void * memmove ( void * destination, const void * source, size_t num );
//分前后两种情况,1.从前向后拷贝,2.从后向前拷贝
void* my_memmove(void* dest, const void* s, size_t num)
{
assert(dest && s);
void* ret = dest;
if (dest < s)
{
//从前向后
while (num--)
{
*(char*)dest = *(char*)s;
dest = (char*)dest + 1;
s = (char*)s + 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)s + num);
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1+5, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
地铁时刻表调整:
动态调整列车运行区间数据:
c
void adjust_schedule(TrainSchedule* schedule, int offset) {
memmove(schedule->times + offset,
schedule->times,
(MAX_STATIONS - offset) * sizeof(Time));
// 更新调整后的时刻表
}
1.8 strstr - 字符串探测器
基础用法:
c
char *strstr(const char *haystack, const char *needle);
// 示例:
char log[] = "Error:404 Not Found";
if(strstr(log, "404")) send_alert();
模拟实现strstr:
c
模拟实现strstr
char * strstr ( const char * str1, const char * str2);
思路:创三个指针变量来记录地址,cur先指向str1,将cur赋给s1,
s2赋给str2,判断相等,不相等,cur++,相等的话,s1与s2各自往下走
如果不相等,就跳出来,cur此时的位置找不到,更新到下一个位置,
s1与s2重新回到原来的地方,继续往下找,直到找到为止,前提是不能为\0
当s2为\0时,说明在寻找的指针中包含了s2所有的东西,返回cur当前所指向的地方
特殊情况:str2为空字符时,就返回str1的地址
如果s1取到\0,那就跳到cur++,重复多几次最终让cur变为\0就可以NULL结束
char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = NULL;
const char* s2 = NULL;
const char* cur = str1;
if (*str2 == '\0')
{
return str1;
}
while (*cur)
{
s1 = cur;
s2 = str2;
while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0')
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cur;
}
cur++;
}
return NULL;
}
int main()
{
char string1[] = "Hello,world";
char string2[] = "ell";
char* ret = my_strstr(string1, string2);
if (ret != NULL)
{
printf("%s\n", ret);
}
else
{
printf("找不到");
}
return 0;
}
论文查重系统:
快速定位重复段落:
c
void check_plagiarism(const char* paper) {
const char* samples[] = {"量子计算", "区块链", "机器学习"};
for(int i=0; i<3; i++){
if(strstr(paper, samples[i])) {
highlight_match(samples[i]);
}
}
}
二、实战篇:真实项目中的综合应用
2.1 用户注册系统
c
#define USERNAME_MAX 20
#define PASSWORD_MAX 32
typedef struct {
char username[USERNAME_MAX];
char password_hash[PASSWORD_MAX];
} User;
int register_user(const char* username, const char* password) {
// 检查用户名长度
if(strlen(username) >= USERNAME_MAX) {
return -1; // 用户名过长
}
// 安全拷贝用户名
strncpy(new_user.username, username, USERNAME_MAX-1);
new_user.username[USERNAME_MAX-1] = '\0';
// 密码哈希处理
sha256(password, new_user.password_hash);
// 写入数据库
return save_to_database(&new_user);
}
2.2 物联网设备通信
c
#pragma pack(push, 1)
struct SensorPacket {
uint32_t timestamp;
float temperature;
float humidity;
uint16_t checksum;
};
#pragma pack(pop)
void process_sensor_data(uint8_t* raw_data) {
struct SensorPacket packet;
// 拷贝原始数据
memcpy(&packet, raw_data, sizeof(packet));
// 校验数据完整性
if(calculate_crc(raw_data, sizeof(packet)-2) != packet.checksum) {
log_error("传感器数据校验失败");
return;
}
// 温度异常告警
if(packet.temperature > 85.0f) {
trigger_temperature_alert();
}
}
2.3 文件格式解析
c
#define MAX_PATH 260
void read_image_metadata(const char* filename) {
FILE* fp = fopen(filename, "rb");
if(!fp) return;
// 读取文件头
char header[4];
fread(header, 1, 4, fp);
// 判断文件类型
if(memcmp(header, "\x89PNG", 4) == 0) {
process_png(fp);
} else if(memcmp(header, "GIF8", 4) == 0) {
process_gif(fp);
} else {
printf("未知文件格式");
}
fclose(fp);
}
三、避坑指南:新手常见错误
3.1 忘记字符串终止符
错误代码:
c
char greeting[5];
strncpy(greeting, "Hello", 5); // 没有空间放'\0'
printf("%s", greeting); // 可能打印乱码
正确写法:
c
strncpy(greeting, "Hello", sizeof(greeting)-1);
greeting[sizeof(greeting)-1] = '\0';
3.2 混淆strcmp返回值
错误理解:
c
if(strcmp(a, b)) { // 错误:返回0表示相等
// 以为a和b相等时会执行这里
}
正确用法:
c
if(strcmp(a, b) == 0) { // 相等时进入分支
// ...
}
3.3 错误的内存操作大小
危险代码:
c
int src[5] = {1,2,3,4,5};
int dest[5];
memcpy(dest, src, 5); // 错误:应该用sizeof(int)*5
正确写法:
c
memcpy(dest, src, sizeof(src));
四、知识检测与练习
4.1 选择题
-
处理用户输入的用户名时,应该使用哪个函数最安全?(最佳选择:B. strncpy)
A. strcpy
B. strncpy
C. memcpy
D. strcat -
需要比较两个二进制文件内容是否完全相同时,应该使用:(最佳选择:B. memcmp)
A. strcmp
B. memcmp
C. strncmp
D. strstr
4.2 编程练习
实现一个安全版本的字符串拼接函数:
c
void safe_strcat(char* dest, size_t dest_size, const char* src) {
// 你的代码...
}
// 示例调用:
char buffer[10] = "Hello";
safe_strcat(buffer, sizeof(buffer), "World");
// buffer应为"HelloWor"
通过这种从基础到实战的学习路径,您将能够:
-
正确使用每个字符串和内存函数
-
避免常见的内存错误
-
编写安全可靠的业务代码
-
快速定位和修复相关BUG
记住:每个函数都是工具,就像木匠的锤子和锯子,只有正确使用才能造出精美的作品。动手实践是最好的学习方式,现在就开始您的编程之旅吧!