C语言中的printf、sprintf、snprintf、vsnprintf 函数

目录

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、snprintfvsnprintf 这几个函数对于有效地处理字符串输出和格式化非常重要,sprintf安全性低,容易发生溢出风险,具有安全隐患,所以这个函数学习意义不是很大,了解即可,接下来介绍其他三个函数的用途、区别以及如何在实际编程中使用,包括代码示例和解释。


1. printf 函数

1.1 概述

printf 是 C 语言中最常用的输出函数,用于将格式化的数据输出到标准输出(通常是终端或控制台)。

1.2 函数原型

#include <stdio.h>int printf(const char *format, ...);
  • format:一个格式字符串,指定了输出的格式和内容。
  • ...:可变参数,根据格式字符串中的占位符提供相应的值。

1.3 返回值

printf 返回成功输出的字符数。如果发生错误,则返回一个负值。

1.4 示例

#include <stdio.h>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;
}

1.5 输出结果

整数: 42
浮点数: 3.14
字符串: Hello, World!
组合: 整数=42, 浮点数=3.142, 字符串=Hello, World!

1.6 常用格式说明符

格式说明符描述
%d有符号十进制整数
%u无符号十进制整数
%f浮点数
%.2f浮点数,保留两位小数
%s字符串
%c单个字符
%x无符号十六进制整数(小写)
%X无符号十六进制整数(大写)
%%输出一个 % 字符

1.7 注意事项

  • 缓冲区溢出:使用 printf 时,需要确保提供的参数类型与格式说明符匹配,否则可能导致未定义行为。
  • 安全性:避免将用户输入直接作为格式字符串,防止格式化字符串漏洞。

2. snprintf 函数

2.1 概述

snprintf 类似于 printf,但它将格式化后的输出存储在一个字符数组(缓冲区)中,而不是输出到标准输出。它还允许指定要写入缓冲区的最大字符数,从而防止缓冲区溢出。

2.2 函数原型

#include <stdio.h>int snprintf(char *str, size_t size, const char *format, ...);
  • str:指向字符数组的指针,用于存储格式化后的字符串。
  • sizestr 的大小(以字节为单位),包括终止符 '\0'
  • format:格式字符串。
  • ...:可变参数。

2.3 返回值

  • 成功时snprintf 返回 将要写入的字符总数(不包括终止符 '\0')。
  • 如果返回值 >= size,表示输出被 截断,即目标缓冲区不足以容纳完整的格式化字符串。
  • 出错时,返回一个负值。

2.4 示例

#include <stdio.h>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;
}

2.5 输出结果

缓冲区内容: 年龄: 30, 工资: 75000.50

2.6 使用场景

  • 构建字符串:在需要将多个变量组合成一个字符串时非常有用。
  • 避免缓冲区溢出:通过指定缓冲区大小,防止写入超过数组边界的数据。

2.7 注意事项

  • 终止符snprintf 会自动在字符串末尾添加 '\0',前提是 size 大于 0。
  • 返回值检查:始终检查返回值,以确定输出是否被截断。
  • 缓冲区大小:确保缓冲区足够大以存储预期的字符串,尤其是在拼接多个变量时。

3. vsnprintf 函数

3.1 概述

vsnprintfsnprintf 的变体,用于处理可变参数列表(va_list)。它通常与可变参数函数(如自定义的打印函数)一起使用。

3.2 函数原型

#include <stdio.h>
#include <stdarg.h>int vsnprintf(char *str, size_t size, const char *format, va_list ap);
  • str:指向字符数组的指针,用于存储格式化后的字符串。
  • sizestr 的大小(以字节为单位),包括终止符 '\0'
  • format:格式字符串。
  • ap:类型为 va_list,表示可变参数列表。

3.3 返回值

snprintf 相同,vsnprintf 返回将要写入的字符总数(不包括终止符 '\0')。如果返回值大于或等于 size,则表示输出被截断。

3.4 使用场景

vsnprintf 主要用于:

  • 自定义可变参数函数:例如,编写自己的日志函数或格式化函数。
  • 处理 va_list:当可变参数已被封装在 va_list 中时。

3.5 示例

假设我们要编写一个自定义的打印函数 my_printf,它接受一个格式字符串和可变参数,并将格式化后的字符串存储在一个缓冲区中。

#include <stdio.h>
#include <stdarg.h>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.8return 0;
}

3.6 输出结果

学生ID: 101, 姓名: Alice, 成绩: 95.8

3.7 注意事项

  • 初始化和清理:始终在使用 va_list 前调用 va_start,使用后调用 va_end
  • 参数顺序va_start 的第二个参数应为最后一个固定参数。
  • 类型匹配:确保传入的参数类型与格式说明符匹配,否则会导致未定义行为。

4. 如何选择使用哪一个函数

函数适用场景备注
printf直接将格式化字符串输出到标准输出(如终端、控制台)。用于调试、日志输出等。
snprintf将格式化字符串输出到字符数组,避免缓冲区溢出。用于构建字符串供后续使用(如发送到设备)。
vsnprintf在自定义可变参数函数中,将 va_list 格式化输出到字符数组。用于高级用法,如编写自己的打印函数。

4.1 简单总结

  • 调试输出:使用 printf,直接输出到控制台。
  • 构建字符串:使用 snprintf,将格式化后的字符串存储在缓冲区中。
  • 可变参数处理:在可变参数函数中使用 vsnprintf,处理 va_list

5. 实际应用示例:构建并发送 AT 指令

目前项目中使用到了AT指令,想通过将AT指令作为参数传入到API函数中,并且有的AT指令时带参数的,有的是不带参数的,这就导致API函数接收的参数个数是不确定的。

接下来构建一个 AT 指令并通过 API 函数发送,使用到了 snprintfvsnprintf 函数。

5.1 使用 snprintf 构建指令

函数 send_at_command,用于串口发送 AT 指令:

#include <stdio.h>
#include <string.h>// 构建的发送函数
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);
}

5.2 使用 vsnprintf 在可变参数函数中构建指令

构建更通用的函数 api_send_at_command_param,它可以接受格式化的指令:

#include <stdio.h>
#include <stdarg.h>// 构建的发送函数
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) {// 设置波特率为 9if (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;
}

5.3 输出结果

 send_at_command 函数只是简单地通过串口打印发送的指令:

AT+IPR=9
波特率设置成功。
AT+TXA=12245,Hello
数据发送成功。

5.4 解释

  • snprintf:在 set_baud_rate 函数中,用于将整数 baud_level 插入到指令字符串中,构建完整的 AT 指令。
  • vsnprintf:在 api_send_at_command_param 函数中,用于处理可变参数,使函数能够接受任意数量和类型的参数,构建灵活的 AT 指令。

6. 总结与实践

  1. 选择合适的函数

    • 使用 printf 进行简单的调试输出。
    • 使用 snprintf 构建安全的、格式化的字符串,避免缓冲区溢出。
    • 在需要处理可变参数的自定义函数中使用 vsnprintf
  2. 始终检查返回值

    • snprintfvsnprintf 返回值可以帮助检测格式化过程中的错误或缓冲区溢出。
  3. 确保缓冲区足够大

    • 根据预期的最大输出长度选择合适的缓冲区大小,或动态分配缓冲区。
  4. 避免格式化字符串漏洞

    • 不要将未经验证的用户输入作为格式字符串传递给 printf 系列函数,防止潜在的安全漏洞。
  5. 使用类型匹配

    • 确保格式说明符与传入参数的类型匹配,避免未定义行为。
  6. 封装与复用

    • 封装常用的格式化操作到函数或宏中,提高代码复用性和可维护性。

通过理解和正确使用 printfsnprintfvsnprintf,可以更有效地处理 C 语言中的字符串输出和格式化任务,编写出更安全、可靠的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65935.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

以余弦序列谈频谱泄漏

廖老师说频谱泄漏是指有新的频率分量生成。一句话get到点上。 对于频谱泄露&#xff0c;信号为无限长序列&#xff0c;运算需要截取其中一部分&#xff08;截断&#xff09;&#xff0c;于是需要加窗函数&#xff0c;加了窗函数相当于时域相乘&#xff0c;于是相当于频域卷积&…

前端项目打包发布

webstorm下的vue项目 打包项目 在package.json中执行打包命令&#xff0c;运行结束后会生成一个 dist目录 发布项目 通过tomcat部署项目 注意&#xff1a;通过tomcat发布前端项目不需要重启tomcat&#xff0c;如果有文件更新需要重启tomcat 在虚拟机上进入tomcat的webapp…

R语言6种将字符转成数字的方法,写在新年来临之际

咱们临床研究中&#xff0c;拿到数据后首先要对数据进行清洗&#xff0c;把数据变成咱们想要的格式&#xff0c;才能进行下一步分析&#xff0c;其中数据中的字符转成数字是个重要的内容&#xff0c;因为字符中常含有特殊符号&#xff0c;不利于分析&#xff0c;转成数字后才能…

华为消费级QLC SSD来了

近日&#xff0c;有关消息显示&#xff0c;华为的消费级SSD产品线&#xff0c;eKitStor Xtreme 200E系列&#xff0c;在韩国一家在线零售商处首次公开销售&#xff0c;引起了业界的广泛关注。 尽管华为已经涉足服务器级别的SSD制造多年&#xff0c;但直到今年6月才正式推出面向…

【生活】冬天如何选口罩(医用口罩,N95, KN95还是KP95?带不带呼吸阀门?带不带活性炭?)

&#x1f4a1;总结一下就是&#xff1a; 日常防护的话&#xff0c;医用口罩就可以啦。要是想长时间佩戴N95&#xff08;KN95&#xff09;口罩的话也可以. 在高风险环境&#xff08;像医院、疫情防控期间&#xff09;&#xff0c;一定要选不带呼吸阀门的N95口罩KN95&#xff09…

Javascript算法——回溯算法(组合问题)

相关资料来自《代码随想录》&#xff0c;版权归原作者所有&#xff0c;只是学习记录 回溯 回溯模板 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点…

【门铃工作原理】2021-12-25

缘由关于#门铃工作原理#的问题&#xff0c;如何解决&#xff1f;-嵌入式-CSDN问答 4 RST&#xff08;复位&#xff09;当此引脚接高电平时定时器工作&#xff0c;当此引脚接地时芯片复位&#xff0c;输出低电平。 按钮按下给电容器充电并相当与短路了R1改变了频率&#xff0c;按…

2025年,测试技能支棱起来。

你是否曾为提升自己的测试技能而烦恼&#xff1f;在这个日新月异的技术时代&#xff0c;2025年已经悄然而至&#xff0c;软件测试行业的需求和挑战也在不断变化。那么&#xff0c;如何在这个竞争激烈的环境中脱颖而出&#xff0c;成为一名更加优秀的测试工程师呢&#xff1f; …

【AI创作】kimi API初体验

一、介绍 接口文档 https://platform.moonshot.cn/docs/guide/migrating-from-openai-to-kimi 收费详情 并发: 同一时间内我们最多处理的来自您的请求数RPM: request per minute 指一分钟内您最多向我们发起的请求数TPM: token per minute 指一分钟内您最多和我们交互的toke…

LLM2Vec: 解锁大语言模型的隐藏能力

LLM2Vec&#xff1a;重新定义大语言模型在自然语言处理中的应用 一种名为 ** LLM2Vec ** 的新方法正在改变我们对大语言模型&#xff08;LLMs&#xff09;在自然语言处理&#xff08;NLP&#xff09;中的使用方式。 研究人员提出了一种创新方法&#xff0c;将通常仅用于生成文…

人工智能安全与隐私——联邦遗忘学习(Federated Unlearning)

前言 在联邦学习&#xff08;Federated Learning, FL&#xff09;中&#xff0c;尽管用户不需要共享数据&#xff0c;但全局模型本身可以隐式地记住用户的本地数据。因此&#xff0c;有必要将目标用户的数据从FL的全局模型中有效去除&#xff0c;以降低隐私泄露的风险&#xf…

GAN对抗生成网络(一)——基本原理及数学推导

1 背景 GAN(Generative Adversarial Networks)对抗生成网络是一个很巧妙的模型&#xff0c;它可以用于文字、图像或视频的生成。 例如&#xff0c;以下就是GAN所生成的人脸图像。 2 算法思想 假如你是《古董局中局》的文物造假者&#xff08;Generator,生成器&#xff09;&a…

数字图像总复习

目录 一、第一章 二、第三章 三、第四章 四、第五章 五、第八章 六、第十章 作业一 作业二 一、第一章 1.图像文件格式由&#xff08;文件头&#xff09;及&#xff08;图像数据&#xff09;组成 2.常见的图像文件格式&#xff1a;&#xff08;JPEG&#xff09;、&…

使用Fn Connect之后,如何访问到其他程序页面?原来一直都可以!

前言 昨天小白讲过在飞牛上登录Fn Connect&#xff0c;就可以实现远程访问家里的NAS。 接着就有小伙伴咨询&#xff1a;如何远程访问到家里其他需要使用不同端口号才能访问到的软件&#xff0c;比如Jellyfin、Emby等。 这个小白在写文章的时候确实没有考虑到&#xff0c;因为…

(二)当人工智能是一个函数,函数形式怎么选择?ChatGPT的函数又是什么?

在上一篇文章中&#xff0c;我们通过二次函数的例子&#xff0c;讲解了如何训练人工智能。今天&#xff0c;让我们进一步探讨&#xff1a;面对不同的实际问题&#xff0c;应该如何选择合适的函数形式&#xff1f; 一、广告推荐系统中的函数选择 1. 业务目标 想象一下&#x…

利用3DGS中convert.py处理自采数据

前言 3DGS源码中convert.py提供对自采数据集的处理&#xff0c;需要预先安装Colmap和ImageMagick. ubuntu22.04安装colmap 点击进入NVIDIA官网&#xff0c;查看GPU的CMAKE_CUDA_ARCHITECTURES 1、克隆colmap源码&#xff0c;并进入colmap文件夹 git clone https://github.c…

硬件设计-关于ADS54J60的校准问题

目录 简介: 校准模分析: 交错的优势 交错挑战 S/2 fIN处产生杂散。失调不匹配杂散很容易识别,因为只有它位于fS/2处,并可轻松地进行补偿。增益、时序和带宽不匹配都会在输出频谱的fS/2 fIN 处产生杂散;因此,随之而来的问题是:如何确定它们各自的影响。图8以简单的…

什么是神经网络?神经网络的基本组成部分训练神经网络激活函数有哪些局限性和挑战

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c; 忍不住分享一下给大家。点击跳转到网站 学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把…

感恩相伴,蓝凌向新成长!一起拥抱数智2025

2024&#xff0c;数字中国&#xff0c;向新奔跑&#xff01;千行百业拥抱数字化、人工智能&#xff0c;蓝凌继续践行“让组织更智慧”的使命&#xff0c;与客户、伙伴等共创共赢&#xff0c;引领中国数智化办公创新发展。感恩相伴24载&#xff0c;让我们一起拥抱数智2025&#…

【算法】模拟退火算法学习记录

写这篇博客的原因是博主本人在看某篇文章的时候&#xff0c;发现自己只是知道SGD这个东西&#xff0c;但是到底是个啥不清楚&#xff0c;所以百度了一下&#xff0c;然后在通过博客学习的时候看到了退火两个字&#xff0c;想到了本科做数模比赛的时候涉猎过&#xff0c;就上bil…