目录
1.1 概述
1.2 函数原型
1.3 返回值
1.4 示例
1.5 输出结果
1.6 常用格式说明符
1.7 注意事项
2. snprintf 函数
2.1 概述
2.2 函数原型
2.3 返回值
2.4 示例
2.5 输出结果
2.6 使用场景
2.7 注意事项
3. vsnprintf 函数
3.1 概述
3.2 函数原型
3.3 返回值
3.4 使用场景
3.5 示例
3.6 输出结果
3.7 注意事项
4. 如何选择使用哪一个函数
4.1 简单总结
5. 实际应用示例:构建并发送 AT 指令
5.1 使用 snprintf 构建指令
5.2 使用 vsnprintf 在可变参数函数中构建指令
5.3 输出结果
5.4 解释
6. 总结与实践
理解 printf
、sprintf、snprintf
和 vsnprintf
这几个函数对于有效地处理字符串输出和格式化非常重要,sprintf安全性低,容易发生溢出风险,具有安全隐患,所以这个函数学习意义不是很大,了解即可,接下来介绍其他三个函数的用途、区别以及如何在实际编程中使用,包括代码示例和解释。
1. printf
函数
printf
是 C 语言中最常用的输出函数,用于将格式化的数据输出到标准输出(通常是终端或控制台)。
#include
int printf(const char *format, ...);
format
:一个格式字符串,指定了输出的格式和内容。...
:可变参数,根据格式字符串中的占位符提供相应的值。printf
返回成功输出的字符数。如果发生错误,则返回一个负值。
#include
int main(void) {
int num = 42;
double pi = 3.14159;
char *str = "Hello, World!";
// 简单输出
printf("整数: %d\n", num);
printf("浮点数: %.2f\n", pi); // 保留两位小数
printf("字符串: %s\n", str);
// 多个参数
printf("组合: 整数=%d, 浮点数=%.3f, 字符串=%s\n", num, pi, str);
return 0;
}
整数: 42
浮点数: 3.14
字符串: Hello, World!
组合: 整数=42, 浮点数=3.142, 字符串=Hello, World!
格式说明符 | 描述 |
---|---|
%d |
有符号十进制整数 |
%u |
无符号十进制整数 |
%f |
浮点数 |
%.2f |
浮点数,保留两位小数 |
%s |
字符串 |
%c |
单个字符 |
%x |
无符号十六进制整数(小写) |
%X |
无符号十六进制整数(大写) |
%% |
输出一个 % 字符 |
printf
时,需要确保提供的参数类型与格式说明符匹配,否则可能导致未定义行为。snprintf
函数snprintf
类似于 printf
,但它将格式化后的输出存储在一个字符数组(缓冲区)中,而不是输出到标准输出。它还允许指定要写入缓冲区的最大字符数,从而防止缓冲区溢出。
#include
int snprintf(char *str, size_t size, const char *format, ...);
str
:指向字符数组的指针,用于存储格式化后的字符串。size
:str
的大小(以字节为单位),包括终止符 '\0'
。format
:格式字符串。...
:可变参数。snprintf
返回 将要写入的字符总数(不包括终止符 '\0'
)。#include
int main(void) {
char buffer[50];
int age = 30;
double salary = 75000.50;
// 使用 snprintf 格式化字符串
int written = snprintf(buffer, sizeof(buffer), "年龄: %d, 工资: %.2f", age, salary);
if (written < 0) {
printf("格式化字符串时出错。\n");
} else if ((size_t)written >= sizeof(buffer)) {
printf("输出被截断。\n");
} else {
printf("缓冲区内容: %s\n", buffer);
}
return 0;
}
缓冲区内容: 年龄: 30, 工资: 75000.50
snprintf
会自动在字符串末尾添加 '\0'
,前提是 size
大于 0。vsnprintf
函数vsnprintf
是 snprintf
的变体,用于处理可变参数列表(va_list
)。它通常与可变参数函数(如自定义的打印函数)一起使用。
#include
#include
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
str
:指向字符数组的指针,用于存储格式化后的字符串。size
:str
的大小(以字节为单位),包括终止符 '\0'
。format
:格式字符串。ap
:类型为 va_list
,表示可变参数列表。与 snprintf
相同,vsnprintf
返回将要写入的字符总数(不包括终止符 '\0'
)。如果返回值大于或等于 size
,则表示输出被截断。
vsnprintf
主要用于:
va_list
:当可变参数已被封装在 va_list
中时。假设我们要编写一个自定义的打印函数 my_printf
,它接受一个格式字符串和可变参数,并将格式化后的字符串存储在一个缓冲区中。
#include
#include
void my_printf(char *buffer, size_t size, const char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(buffer, size, format, args);
va_end(args);
}
int main(void) {
char buffer[100];
int id = 101;
char name[] = "Alice";
double score = 95.75;
my_printf(buffer, sizeof(buffer), "学生ID: %d, 姓名: %s, 成绩: %.1f", id, name, score);
printf("%s\n", buffer); // 输出: 学生ID: 101, 姓名: Alice, 成绩: 95.8
return 0;
}
学生ID: 101, 姓名: Alice, 成绩: 95.8
va_list
前调用 va_start
,使用后调用 va_end
。va_start
的第二个参数应为最后一个固定参数。函数 | 适用场景 | 备注 |
---|---|---|
printf |
直接将格式化字符串输出到标准输出(如终端、控制台)。 | 用于调试、日志输出等。 |
snprintf |
将格式化字符串输出到字符数组,避免缓冲区溢出。 | 用于构建字符串供后续使用(如发送到设备)。 |
vsnprintf |
在自定义可变参数函数中,将 va_list 格式化输出到字符数组。 |
用于高级用法,如编写自己的打印函数。 |
printf
,直接输出到控制台。snprintf
,将格式化后的字符串存储在缓冲区中。vsnprintf
,处理 va_list
。目前项目中使用到了AT指令,想通过将AT指令作为参数传入到API函数中,并且有的AT指令时带参数的,有的是不带参数的,这就导致API函数接收的参数个数是不确定的。
接下来构建一个 AT 指令并通过 API 函数发送,使用到了 snprintf
和 vsnprintf
函数。
snprintf
构建指令函数 send_at_command
,用于串口发送 AT 指令:
#include
#include
// 构建的发送函数
int send_at_command(const char *cmd);
// 使用 snprintf 构建并发送 AT 指令
int set_baud_rate(int baud_level) {
char cmd[50];
int written = snprintf(cmd, sizeof(cmd), "AT+IPR=%d\r\n", baud_level);
if (written < 0 || written >= sizeof(cmd)) {
// 处理错误:格式化失败或缓冲区不足
return -1;
}
return send_at_command(cmd);
}
vsnprintf
在可变参数函数中构建指令构建更通用的函数 api_send_at_command_param
,它可以接受格式化的指令:
#include
#include
// 构建的发送函数
int send_at_command(const char *cmd);
// 定义 API 返回状态
typedef enum {
API_OK = 0,
API_ERROR,
API_PARAM_ERROR
} API_Status;
// 使用 vsnprintf 在可变参数函数中构建并发送 AT 指令
API_Status api_send_at_command_param(const char *format, ...) {
char cmd[128];
va_list args;
va_start(args, format);
int written = vsnprintf(cmd, sizeof(cmd), format, args);
va_end(args);
if (written < 0 || written >= sizeof(cmd)) {
// 处理错误:格式化失败或缓冲区不足
return API_PARAM_ERROR;
}
if (send_at_command(cmd) != 0) {
// 发送失败
return API_ERROR;
}
return API_OK;
}
// 使用示例
int main(void) {
// 设置波特率为 9
if (api_send_at_command_param("AT+IPR=%d\r\n", 9) == API_OK) {
printf("波特率设置成功。\n");
} else {
printf("波特率设置失败。\n");
}
// 发送数据
if (api_send_at_command_param("AT+TXA=%u,%s\r\n", 12245, "Hello") == API_OK) {
printf("数据发送成功。\n");
} else {
printf("数据发送失败。\n");
}
return 0;
}
send_at_command
函数只是简单地通过串口打印发送的指令:
AT+IPR=9
波特率设置成功。
AT+TXA=12245,Hello
数据发送成功。
snprintf
:在 set_baud_rate
函数中,用于将整数 baud_level
插入到指令字符串中,构建完整的 AT 指令。vsnprintf
:在 api_send_at_command_param
函数中,用于处理可变参数,使函数能够接受任意数量和类型的参数,构建灵活的 AT 指令。选择合适的函数:
printf
进行简单的调试输出。snprintf
构建安全的、格式化的字符串,避免缓冲区溢出。vsnprintf
。始终检查返回值:
snprintf
和 vsnprintf
返回值可以帮助检测格式化过程中的错误或缓冲区溢出。确保缓冲区足够大:
避免格式化字符串漏洞:
printf
系列函数,防止潜在的安全漏洞。使用类型匹配:
封装与复用:
通过理解和正确使用 printf
、snprintf
和 vsnprintf
,可以更有效地处理 C 语言中的字符串输出和格式化任务,编写出更安全、可靠的代码。