资讯详情

编程技能:格式化打印04,sprintf

📅 2026/9/16 21:55:03 | 华诺云谱 👁 阅读
编程技能:格式化打印04,sprintf
专栏导航本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏故划分为两个专栏导航。读者可以自行选择前往哪个专栏。一Win32 专栏导航上一篇编程技能格式化打印03printf回到目录下一篇编程技能格式化打印05格式控制符二MFC 专栏导航上一篇编程技能格式化打印03printf回到目录下一篇编程技能格式化打印05格式控制符本节前言在上一节我们讲解了 printf 的实现代码。本节我们来讲解与之相近的 sprintf 的实现代码。对于本节的讲解是基于 vsprintf 的知识的。如果你尚未学习过 vsprintf 的知识请参考下述链接所示的文章。编程技能格式化打印02vsprintf-CSDN博客确保你理解了 vsprintf 的大致逻辑之后接下来我们来学习 printf 的实现代码。在本节里面我会用到三个术语。他们分别是【栈指针】【格式字符串】和【参数列表】。这三个术语均可以从 vsprintf 文章链接中找到。如果你不懂这些术语请你先去上面的 vsprintf 文章链接中学习好 vsprintf 然后再进行本节的学习。我们开始本节的讲解。一. sprintf 函数的实现代码代码如下。extern int sprintf(char * str, const char *fmt, ...) { va_list args; int i; va_start(args, fmt); i vsprintf(str, fmt, args); va_end(args); return i; }上面的代码块中的代码有可能此时你还看不懂 。看不懂没有关系。在下面我会去讲解它们。此处你只需要浏览一下有一个粗略的印象就可以了。上面的这段代码改编自 Linux 0.12 内核。绝大部分的代码都与 0.12 内核一样。但是呢我也的确是作出了一点小小的改动。我们接着往下看。二. 调用 sprintf 函数的三个案例在本分节中我给出调用 sprintf 函数的三个案例代码。此处我暂时不需要你已经理解了下面的三个案例代码。因为讲解它们让你理解这三个案例代码是我们本节课的讲解任务。此处只要求你先浏览一下有一个大概的印象就可以了。在下面的讲解中我们可能随时会需要引用到这三个案例代码你需要随时来参阅这三个案例代码。建议你将它们临时复制到你本地的记事本中或者 notepad 里面。下面是案例1 的代码。static char buf01[300]; void func01(void) { int i, a, c; float b; a 90; b 3.14; c 1024; i sprintf(buf01, a %db %fc %x, a, b, c); return; }以上为案例1 的代码。下面是案例2 的代码。static char buf02[300]; void func02(void) { int i; i sprintf(buf02, 小雪的兜里有 %d 块钱, 100); return; }以上为案例 2 的代码。下面是案例3 的代码static char buf03[300]; void func03(void) { int i; i sprintf(buf03, I have a pen); return; }以上为案例3 的代码。再次强调一下这几个代码我们在下面的讲解中随时会引用。大家需要浏览一下这几个代码。一会儿你需要随时来查阅对照这几个案例代码。我们接着往下讲。三. sprintf 的函数头部sprintf 的函数头部如下。extern int sprintf(char * str, const char *fmt, ...);对于此函数的返回值我们先不去讲。我们来看看它的各个参数。一缓冲区第一个参数是字符指针 str 。这个字符指针是由调用 sprintf 的函数提供的。第二分节的案例1 代码里包含有下述两行代码。static char buf01[300];i sprintf(buf01, a %db %fc %x, a, b, c);在案例1 里面我们声明了一个大的字符数组里面包含 300 个元素。数组名为 buf01 。数组元素为 char 型所以数组名为字符指针。而在案例1 的调用 sprintf 函数的代码中传入的第一个参数为字符数组名 buf01 。在案例2 和案例3 里面也有类似的代码逻辑。也就是申请了一个大的字符数组。同时在调用 sprintf 函数的时候给 sprintf 传入的第一个参数是这个字符数组的数组名。一般地我们平时在申请数组的时候不会让数组包含着这么多的元素个数。然而在本节的第二分节的三个案例代码里面我们的确是分别申请了一个大的字符数组。申请这么大的数组干嘛一般来讲当我们选择申请一个尺寸很大的数组的时候我们是想要用它来临时地保存某些数据。这种临时地保存某些数据的尺寸很大的数组我们可以给它换一个名字缓冲区。在 Linux 0.12 内核里面为了实现 printf 函数它是申请了包含 1024 个 char 型元素的字符数组作为缓冲区来给 printf 函数使用。关于缓冲区的概念其实大家在基础的 C 语言学习中大家也听说过的。那就是键盘缓冲区。在 Linux 0.12 内核里面就包含有一个键盘缓冲区它也是用包含着 1024 个元素的大数组来作为键盘缓冲区的。buf常常用作【缓冲区】的含义它是 buffer 的简写。我们接着往下讲。二格式字符串在 sprintf 的函数头部里面第二个参数为【const char *fmt】。从这里可以看出本参数用来接收一个字符串并且由于 const 关键字的存在我们不可以通过字符指针 fmt 对这个接收的字符串进行任何改动而只是可以使用其值。在这里我将 fmt 接收的字符串称作格式字符串。所谓的格式字符串是说它里面可能会含有 %d%c%f%s 等等的格式控制符。在代码【printf(a %db %fc %x, a, b, c);】中【a %db %fc %x】是格式字符串。类似地在本文的的第二分节的案例1 代码里面在代码【i sprintf(buf01, a %db %fc %x, a, b, c);】中 【a %db %fc %x】也是格式字符串由sprintf 的第二个形参 fmt 来接收。在代码【printf(小雪的兜里有 %d 块钱, 100);】里面【小雪的兜里有 %d 块钱】是格式字符串。类似地在本文的的第二分节的案例2 代码里面在代码【i sprintf(buf02, 小雪的兜里有 %d 块钱, 100);】中 【小雪的兜里有 %d 块钱】也是格式字符串由sprintf 的第二个形参 fmt 来接收。在代码【printf(I have a pen);】里面【I have a pen】是格式字符串。类似地在本文的的第二分节的案例3 代码里面在代码【i sprintf(buf03, I have a pen);】中 【I have a pen】也是格式字符串由sprintf 的第二个形参 fmt 来接收。格式字符串里面可以包含有 %d 等等的格式控制符也可以不包含。到了这里sprintf 的第二个形参我们就讲完了。我们接着讲。三可变参数在 sprintf 函数头部里面在 fmt 形参的右边是【...】这个东西不是省略号。你也可以在你自己的代码里面包含这样的东西。不过呢使用的时候是三个英文句点不可以是中文的三个句点。在数量上必须是三个多一个不行少一个也不行。这个【...】有一个专有名字叫做可变参数。可变参数是说它的数目是可变的每一个参数的数据类型也是可变的没有固定的范式。在数目方面可变参数中可以包含有一个参数两个参数或者多个参数也可以不包含参数。在代码【printf(a %db %fc %x, a, b, c);】中【a, b, c】的部分便是可变参数部分。类似地在本文的的第二分节的案例1 代码里面在代码【i sprintf(buf01, a %db %fc %x, a, b, c);】中【a, b, c】的部分也是可变参数部分。在此代码示例中可变参数部分含有三个参数。在代码【printf(小雪的兜里有 %d 块钱, 100);】中【100】的部分为可变参数部分。类似地在本文的的第二分节的案例2 代码里面在代码【i sprintf(buf02, 小雪的兜里有 %d 块钱, 100);】中【100】的部分也是可变参数部分。在此代码示例中可变参数部分含有一个参数。在代码【printf(I have a pen);】里面可变参数部分无参数。类似地在本文的的第二分节的案例3 代码里面在代码【i sprintf(buf03, I have a pen);】中可变参数部分也是没有参数的。在大家平时写代码的时候应该不太会去使用可变参数的。不过这次我们要来学习 printfvsprintfsprintf 等等的打印函数那就需要来接触可变参数了。我尽力去讲希望大家能够学好可变参数。到了这里sprintf 的函数头部我们就先进行到这里。返回值后面会有讲解。四. va_list我们来看下图的红色框线部分的代码。图1图1 中的红色框线部分是变量声明。【int i;】这个简单声明了一个整型变量 i 。问题在于【va_list args;】这一部分需要去讲解一下的是 va_list 。va_list是【char *】的意思。请看下面的参考代码。typedef char *va_list;va_list是【char *】的别名。这样一来由 va_list 声明的 args 变量便是一个【char *】类型的变量。在这里我来讲一讲 va_list 的含义。va_list 中的 va是【variable argument】的意思翻译过来就是可变参数的意思。list是列表的含义。所以呢va_list整个的意思就是【可变参数列表】。用【可变参数列表】类型 va_list 声明的变量为 args它是 arguments 的简写就是【各个参数】的意思。我们接着往下看。五. va_start我们来看下图中的红色框线部分的代码。图2va_start直接翻译就是【可变参数开始】的意思。其实它是用来对可变参数变量 args 进行初始化的宏函数。va_start从它的使用方法上看似乎是一个函数。实际上它是一个宏。对于这种实际上为宏而外形为函数的东西我将其称作宏函数。别的地方咋叫我记不清了。反正我就管它叫宏函数。va_start它的宏代码具体是什么在这里我不展开。原因在于想要彻底理解其宏代码你需要具备汇编语言基础。在设计本专栏教程的时候我的一个基本的假定就是各位读者并不具备汇编语言基础。我希望在各位并不具备汇编语言基础的情况下也能够看懂本专栏教程。由于假定各位不懂汇编语言所以va_start 的宏代码我就不去细讲了。但是呢它的功能我还得来说一说的。va_start(args, fmt) 的意思是说将可变参数变量 args赋值为 fmt 右边的可变参数列表中第一个变量的栈指针。栈指针又是什么我们在讲解 vsprintf 的时候有去详细讲它。在讲解 vsprintf 的时候我们所给出的栈指针的大致含义如下。A 函数调用 B 函数的时候会将 B 函数所需要的参数也就是传递给 B 函数的实参压入栈中。完成了参数入栈的工作以后CPU 才会前往执行 B 函数的代码。那么A 函数所传递的某一个参数在栈中的位置就是这个实参的栈指针。在这里如果你需要进一步理解栈指针的含义那么你可以参考 vsprintf 的讲解。讲解 vsprintf 的文章链接如下所示。编程技能格式化打印02vsprintf-CSDN博客不过说实话我在讲解 vsprintf 的时候对栈指针的讲解做不到让你彻底地理解栈指针的含义。因为想要彻底地理解栈指针的概念你需要具备汇编语言基础。此处你只要能够模糊地大致地理解栈指针的概念就可以了。我们接着往下讲。为了辅助大家理解 va_start 的含义我们来举几个例子。在案例1 中的代码【i sprintf(buf01, a %db %fc %x, a, b, c);】中在调用了 sprintf 函数以后在 sprintf 函数内部代码【va_start(args, fmt);】的执行会将 args 赋值为格式字符串【a %db %fc %x】右边的可变参数列表中的第一个参数a 的栈指针。在案例2 中的代码【i sprintf(buf02, 小雪的兜里有 %d 块钱, 100);】里面在调用了 sprintf 函数以后在 sprintf 函数内部代码【va_start(args, fmt);】的执行会将 args 赋值为格式字符串【小雪的兜里有 %d 块钱】右边的可变参数列表中的第一个参数【100】的栈指针。在案例3 中的代码【i sprintf(buf03, I have a pen);】里面可变参数部分无参数。在这种情况下代码【va_start(args, fmt);】的执行也会令 args 指向一个东西不过在可变参数部分无参数的情况下args 究竟是指向啥我们就不必关心了。我们暂时只关心可变参数列表中至少含有一个参数的情形并且将可变参数列表中包含至少一个参数的情形视为典型。再次重复一下va_start(args, fmt) 的意思是说将可变参数变量 args赋值为 fmt 所指向的格式字符串右边的可变参数列表中的第一个变量的栈指针。正是在 va_start宏函数的作用之下可变参数列表中的第一个参数的栈指针被提取出来了。不知道你理解得是否费劲儿。反正此刻我是觉得讲得挺费劲儿的。接着来吧。六. 调用 vsprintf我们接着看下图的红色框线所示的代码。图3在图3 的红色框线部分我们调用了 vsprintf 函数传给它的三个参数分别是由上一级的调用函数传过来的字符缓冲区指针【str】格式字符串的指针【fmt】还有可变参数列表中第一个参数的栈指针【args】。vsprintf 函数的功能是根据 fmt 与 args对 fmt 所指向的格式字符串进行格式化转换转换结果放在字符缓冲区指针 str 所指向的字符数组里面。格式化转换的工作完成以后str 中的字符串的不含 NUL 结束符的有效字符长度会作为返回值予以返回。在图3 里面这个返回值由 int 型变量 i 来接收了。vsprintf 是如何进行格式化转换的请大家参考 vsprintf 文章链接链接如下。编程技能格式化打印02vsprintf-CSDN博客不过呢在这里虽说我不想细讲 vsprintf 的执行过程。不过呢我还是想要举几个例子。例如在执行本文第二分节的案例1 中的代码【i sprintf(buf01, a %db %fc %x, a, b, c);】以后sprintf 会在内部调用 vsprintf 。 vsprintf 会根据格式字符串【a %db %fc %x】与可变参数列表中第一个参数的栈指针对格式字符串进行格式化转换。对于案例1 而言格式化转换的结果为【a 90b 3.14c 400】这个转换结果会被存放在 str 缓冲区里面或者说是存放在案例1 中的 buf01 缓冲区里面。十进制 1024 的十六进制值为 0x400 。我在格式字符串里面没有给十六进制值加上十六进制前缀你可以自己添加啊。这里我偷个懒。再比如在执行本文第二分节的案例2 中的代码【i sprintf(buf02, 小雪的兜里有 %d 块钱, 100);】以后sprintf 会在内部调用 vsprintf 。 vsprintf 会根据格式字符串【小雪的兜里有 %d 块钱】与可变参数列表中第一个参数的栈指针对格式字符串进行格式化转换。格式化转换的结果为【小雪的兜里有 100 块钱】这个转换结果会被存放在 str 缓冲区里面或者说是存放在案例2 中的 buf02 缓冲区里面。再比如在执行本文第二分节的案例3 中的代码【i sprintf(buf03, I have a pen);】以后sprintf 会在内部调用 vsprintf 。 vsprintf 会根据格式字符串【I have a pen】与可变参数列表中第一个参数的栈指针对格式字符串进行格式化转换。在此例子代码中由于格式字符串里面不含有格式控制符可变参数列表里面也不含有参数因此vsprintf 的工作是直接把字符串【I have a pen】放在 str 缓冲区中或者说是存放在案例3 中的 buf03 缓冲区中而并不需要专门的格式化转换工作。我们接着往下看。七. va_end我们来看下图的红色框线部分所示的代码。图4va_end依然是一个宏函数。va_end(args)它的作用是将 args 置空。由于 args 是 va_list 类型的也就是【char *】类型所以呢【va_end(args);】的作用相当于执行代码【args NULL;】。就这么简单。在这里我将 va_end 的宏代码给展示一下。#define va_end(AP) (AP NULL)这个宏代码应该是不难吧如果不理解的话请查阅 C 语言基础知识教材。在 va_start宏函数里面初始化了 args 变量。在调用 vsprintf 的时候使用了 args 变量。最后呢在 va_end宏函数里面清理了 args 变量。我们接着往下看。八. 打印输出与返回请先看下图的红色框线所示的代码。图5图5 中的红色框线部分是返回语句【return i】。关于变量 i在本文的第六节中讲解 vsprintf 函数调用的时候我谈到了vsprintf 会将完成了格式化转换工作以后保存在 str 缓冲区中的字符串的不含有 NUL 结束符的有效字符长度作为返回值予以返回。这个返回值被赋值给 int 型变量 i 了。所以呢此时变量 i 里面所保存的是 str 缓冲区中的字符串的不含有 NUL结束符的有效字符长度。而在 sprintf 函数实现代码的末尾这个有效字符长度再一次作为返回值予以返回了。在我的学习经历中这个返回值还是有用的。至于究竟是如何来使用我们在以后的 Windows 编程教程中会见到的。Windows 编程我打算分为 Win32 与 MFC 两块来讲。在 MFC 专栏里面可能对 sprintf 用得不多。然而在 Win32 专栏里面我们用 sprintf 会多一些。当然了我们不是直接用 sprintf 而是用它的 Windows 版本wsprintf 函数。九. sprintf 函数总结我们对 sprintf 的函数功能进行一个小小的总结。我们还是先来看一下 sprintf 的函数头部。extern int sprintf(char * str, const char *fmt, ...);sprintf 函数的功能根据 fmt 所指向的格式字符串与可变参数列表中第一个参数的栈指针对格式字符串进行格式化转换工作。转换好的字符串会被存放在 str 缓冲区里面。函数的返回值为格式化转换工作完成以后存放在 str 缓冲区中的字符串的不含有 NUL 结束符的有效字符长度。对函数返回值部分我特意用橄榄色的粗体字来标识了。我还是再来重复一下。简单地来讲sprintf 函数的返回值为有效字符长度。到了这里本节的讲课任务就都完成了。结束语相比上一节讲解 printf 的时候来讲本节的写作算是轻松了一些因为大部分都是复制粘贴。然而也还有许多东西是需要去修改的。sprintf我认为是比较重要的函数。希望大家能够学习好它。其实到了这里主体任务vsprintfprintfsprintf这三大格式化打印函数我们就都讲完了。到了这里格式化打印部分可以说是完成了。不过我还想要补充一点东西。那就是对于格式控制符它还有一些个东西我想要去补充补充。也许这个补充是不必要的。不过我还是想要补充一下。下一节将会是格式化打印板块的最后一节。同时Windows 编程的预备知识部分也将在下一节画上句号。本节结束。专栏导航本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏故划分为两个专栏导航。读者可以自行选择前往哪个专栏。一Win32 专栏导航上一篇编程技能格式化打印03printf回到目录下一篇编程技能格式化打印05格式控制符二MFC 专栏导航上一篇编程技能格式化打印03printf回到目录下一篇编程技能格式化打印05格式控制符
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。