完整且详细的C语言教程,从入门到精通(5.3)--C语言常用函数(2)
·
你们好,我是一名嵌入式工程师。我正在持续更新《嵌入式工程师的C语言实战》系列教程,目前已更新7篇。如果你也在学习嵌入式,欢迎点个关注,我们一起从零到一。
文章目录
前言
strcpy、strlen、malloc、free、rand、memset、memcpy 这7个函数是C语言中字符串操作、内存管理和工具函数的核心成员。
一、strcpy —— 字符串复制
头文件
#include <string.h>
函数原型
char *strcpy(char *dest, const char *src);
功能
将源字符串 src(包括结尾的 \0)复制到目标字符串 dest 中。
参数
dest:目标字符串的指针(必须有足够的空间容纳src)。src:源字符串的指针(必须以\0结尾)。
返回值
返回 dest 的指针(便于链式调用)。
重要警告
strcpy 不检查目标缓冲区大小,如果 src 比 dest 的空间大,会导致缓冲区溢出(严重安全漏洞)。
示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20]; // 必须足够大
strcpy(dest, src);
printf("复制后: %s\n", dest); // 输出: Hello, World!
// 链式调用
char buffer[50];
strcpy(buffer, strcpy(dest, "C语言"));
printf("buffer: %s\n", buffer); // 输出: C语言
printf("dest: %s\n", dest); // 输出: C语言
return 0;
}
安全替代方案
建议使用 strncpy(指定最大复制长度):
char dest[10];
strncpy(dest, "Hello, World!", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保结尾有 \0
二、strlen —— 获取字符串长度
头文件
#include <string.h>
函数原型
size_t strlen(const char *s);
功能
计算字符串的长度(不包含结尾的 \0),即从起始地址到第一个 \0 之前的字符个数。
参数
s:以\0结尾的字符串。
返回值
返回字符串的长度,类型为 size_t(无符号整数)。
示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "Hello";
char str2[] = "中文测试"; // 中文字符在 UTF-8 下占多个字节
printf("'%s' 的长度: %zu\n", str1, strlen(str1)); // 输出 5
printf("'%s' 的长度: %zu\n", str2, strlen(str2)); // 输出 12 (UTF-8)
// sizeof vs strlen
char arr[20] = "Hello";
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 20 (整个数组大小)
printf("strlen(arr) = %zu\n", strlen(arr)); // 5 (字符串长度)
// 手动实现 strlen 的思路
const char *p = "Test";
size_t len = 0;
while (*p != '\0') {
len++;
p++;
}
printf("手动计算: %zu\n", len); // 输出 4
return 0;
}
重要提醒
strlen返回的是字符数,不是字节数(对多字节字符集需注意)。- 传入的字符串必须保证以
\0结尾,否则结果不可预知。
三、malloc —— 动态内存分配
头文件
#include <stdlib.h>
函数原型
void *malloc(size_t size);
功能
在**堆(heap)**上分配 size 个字节的连续内存空间,并返回指向该内存块的指针。
参数
size:要分配的字节数。
返回值
- 成功时返回指向已分配内存的指针(
void*,可强制转换为任意类型)。 - 失败时(内存不足)返回
NULL。
重要注意事项
- 分配的内存内容未初始化(包含垃圾值)。
- 必须检查返回值是否为
NULL。 - 使用完后必须调用
free释放,否则造成内存泄漏。 - 分配的大小要用
sizeof计算,避免硬编码。
示例
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr;
int n = 5;
// 分配 n 个 int 类型的内存
arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 使用内存(注意:此时值未初始化)
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
printf("%d ", arr[i]);
}
printf("\n");
// 释放内存
free(arr);
arr = NULL; // 避免野指针
return 0;
}
常见应用场景
- 在运行时才知道需要多大的数组。
- 创建链表、树等动态数据结构。
- 处理可变长度的数据(如从文件读取内容)。
四、free —— 释放动态内存
头文件
#include <stdlib.h>
函数原型
void free(void *ptr);
功能
释放之前由 malloc、calloc 或 realloc 分配的内存,将其归还给系统。
参数
ptr:指向要释放的内存块的指针(必须是之前动态分配的)。
返回值
无。
重要注意事项
- 只能释放动态分配的内存,不能释放栈上的变量。
- 不能重复释放(double free)同一块内存,会导致未定义行为。
- 释放后最好将指针置为
NULL,避免野指针。 - 释放
NULL是安全的(不会产生错误)。
示例
#include <stdio.h>
#include <stdlib.h>
int main() {
char *buffer;
// 分配 100 字节
buffer = (char*)malloc(100 * sizeof(char));
if (buffer == NULL) {
printf("分配失败\n");
return 1;
}
// 使用内存
sprintf(buffer, "动态分配的字符串");
printf("%s\n", buffer);
// 释放内存
free(buffer);
buffer = NULL; // 安全实践
// free(NULL) 是安全的
free(NULL); // 什么也不做
return 0;
}
五、rand —— 生成随机数
头文件
#include <stdlib.h>
函数原型
int rand(void);
功能
生成一个 0 到 RAND_MAX 之间的伪随机整数。RAND_MAX 通常定义为 32767。
参数
无。
返回值
返回一个伪随机整数。
重要注意事项
- 默认种子是 1,每次运行程序生成的随机数序列相同。
- 需要用
srand设置不同的种子(通常用当前时间)。 rand的随机质量有限,对安全性要求高的场景需用专用库。
示例
#include <stdio.h>
#include <stdlib.h>
#include <time.h> // 用于 time()
int main() {
// 设置随机种子(使用当前时间)
srand((unsigned int)time(NULL));
// 生成 10 个随机数
printf("10 个随机数:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", rand());
}
printf("\n");
// 生成 1~100 之间的随机数
printf("\n1~100 的随机数:\n");
for (int i = 0; i < 10; i++) {
int num = rand() % 100 + 1;
printf("%d ", num);
}
printf("\n");
// 生成 0.0~1.0 之间的随机浮点数
printf("\n0.0~1.0 随机浮点数:\n");
for (int i = 0; i < 5; i++) {
double f = (double)rand() / RAND_MAX;
printf("%.3f ", f);
}
printf("\n");
return 0;
}
生成特定范围随机数的公式
| 范围 | 公式 |
|---|---|
0 ~ N-1 | rand() % N |
1 ~ N | rand() % N + 1 |
min ~ max | rand() % (max - min + 1) + min |
0.0 ~ 1.0 | (double)rand() / RAND_MAX |
六、memset —— 设置内存块的值
头文件
#include <string.h>
函数原型
void *memset(void *s, int c, size_t n);
功能
将内存块 s 的前 n 个字节全部设置为 c(按字节填充)。
参数
s:指向要填充的内存块的指针。c:要填充的值(会被转换为unsigned char)。n:要填充的字节数。
返回值
返回 s 的指针。
常见用途
- 初始化数组或结构体为 0。
- 清空敏感数据(如密码)。
- 重置缓冲区。
示例
#include <stdio.h>
#include <string.h>
int main() {
// 1. 初始化数组为 0
int arr[10];
memset(arr, 0, sizeof(arr));
printf("arr[0] = %d\n", arr[0]); // 输出 0
// 2. 初始化数组为特定值(注意:按字节填充)
char buffer[20];
memset(buffer, 'A', 10); // 前10个字节设为 'A'
buffer[10] = '\0';
printf("buffer: %s\n", buffer); // 输出: AAAAAAAAAA
// 3. 初始化结构体
typedef struct {
int id;
char name[50];
float score;
} Student;
Student s;
memset(&s, 0, sizeof(s)); // 所有成员清零
printf("s.id = %d, s.score = %.2f\n", s.id, s.score);
// 4. 清空字符串
char str[100] = "Hello, World!";
printf("清空前: %s\n", str);
memset(str, 0, sizeof(str));
printf("清空后: %s (长度: %zu)\n", str, strlen(str)); // 长度 0
return 0;
}
警告
memset 按字节填充,对于 int 数组,memset(arr, 1, sizeof(arr)) 不会将每个元素设为 1,而是每个字节设为 1,结果每个 int 的值是 0x01010101(即 16843009)。只有填充 0 或 -1 时才适用于整数数组。
七、memcpy —— 内存复制
头文件
#include <string.h>
函数原型
void *memcpy(void *dest, const void *src, size_t n);
功能
将内存块 src 的前 n 个字节复制到 dest 中。
参数
dest:目标内存指针。src:源内存指针。n:要复制的字节数。
返回值
返回 dest 的指针。
重要注意事项
- 不检查
\0,直接按字节复制n个字节。 - 源和目标不能重叠(重叠请用
memmove)。 - 目标内存必须有足够的空间。
示例
#include <stdio.h>
#include <string.h>
int main() {
// 1. 复制数组
int src_arr[] = {1, 2, 3, 4, 5};
int dest_arr[5];
memcpy(dest_arr, src_arr, sizeof(src_arr));
printf("复制后的数组: ");
for (int i = 0; i < 5; i++) {
printf("%d ", dest_arr[i]);
}
printf("\n");
// 2. 复制字符串(包括 \0)
char src_str[] = "Hello, World!";
char dest_str[50];
memcpy(dest_str, src_str, strlen(src_str) + 1); // +1 复制 \0
printf("复制后的字符串: %s\n", dest_str);
// 3. 复制结构体
typedef struct {
int x;
int y;
} Point;
Point p1 = {10, 20};
Point p2;
memcpy(&p2, &p1, sizeof(Point));
printf("p2: (%d, %d)\n", p2.x, p2.y);
// 4. strcpy 与 memcpy 的区别
char text[] = "ABCDEFG";
char buf1[10], buf2[10];
strcpy(buf1, text); // 复制直到遇到 \0
memcpy(buf2, text, 3); // 只复制前3个字节(不自动加 \0)
buf2[3] = '\0'; // 手动添加结束符
printf("strcpy: %s\n", buf1);
printf("memcpy: %s\n", buf2); // 输出: ABC
return 0;
}
memcpy vs strcpy
| 对比项 | memcpy | strcpy |
|---|---|---|
| 复制方式 | 按字节数复制 | 复制到 \0 为止 |
是否检查 \0 | 否 | 是 |
| 能否复制二进制数据 | ✅ 可以 | ❌ 不行(遇 \0 停止) |
| 性能 | 通常更快 | 略慢(需扫描 \0) |
| 安全性 | 需手动控制长度 | 不检查目标大小(危险) |
综合示例:实战应用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main() {
// 1. 动态分配内存
int n = 10;
int *numbers = (int*)malloc(n * sizeof(int));
if (numbers == NULL) {
printf("内存分配失败\n");
return 1;
}
// 2. 初始化内存为 0
memset(numbers, 0, n * sizeof(int));
// 3. 生成随机数填充
srand((unsigned int)time(NULL));
for (int i = 0; i < n; i++) {
numbers[i] = rand() % 100 + 1; // 1~100
}
// 4. 复制数组
int *copy = (int*)malloc(n * sizeof(int));
if (copy == NULL) {
printf("内存分配失败\n");
free(numbers);
return 1;
}
memcpy(copy, numbers, n * sizeof(int));
// 5. 输出结果
printf("原始数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
printf("复制数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", copy[i]);
}
printf("\n");
// 6. 统计数组长度(类比 strlen)
char msg[] = "Dynamic memory management";
printf("消息: %s\n", msg);
printf("消息长度: %zu\n", strlen(msg));
// 7. 释放内存
free(numbers);
free(copy);
numbers = NULL;
copy = NULL;
return 0;
}
总结对比表
| 函数 | 头文件 | 核心功能 | 是否检查 \0 | 是否检查大小 | 返回值 |
|---|---|---|---|---|---|
strcpy | <string.h> | 复制字符串 | ✅ 是 | ❌ 否 | 目标指针 |
strlen | <string.h> | 计算字符串长度 | ✅ 是 | - | size_t |
malloc | <stdlib.h> | 分配内存 | - | - | void* |
free | <stdlib.h> | 释放内存 | - | - | 无 |
rand | <stdlib.h> | 生成随机数 | - | - | int |
memset | <string.h> | 按字节填充内存 | ❌ 否 | ✅ 需手动指定 | 目标指针 |
memcpy | <string.h> | 按字节复制内存 | ❌ 否 | ✅ 需手动指定 | 目标指针 |
最佳实践总结
- 字符串操作:优先使用
strncpy、strnlen等带n的安全版本。 - 动态内存:
malloc后必须检查NULL,使用后必须free,释放后置NULL。 - 随机数:程序开始时调用一次
srand(time(NULL)),避免重复种子。 - 内存操作:
memset用于清零,memcpy用于复制二进制数据,注意不要重叠。 - 内存泄漏检测:Linux 下可用
valgrind工具检测。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,主播是一个马上粉丝量超1个的嵌入式内容主播。谢谢!
更多推荐

所有评论(0)