实现简单的printf函数

C语言程序设计:简易版的printf函数实现 摘要:使用putchar、可变参函数实现简易版的printf 阅读详情
首先,要介绍一下printf实现的原理

printf函数原型如下:
int printf(const char* format,...);

返回值是int,返回输出的字符个数。

例如:
int main()
{
    int n;
    n=printf("hello world,%d\n",100);
    printf("返回值:%d\n",n);
    return 0;
}


测试结果:

hello world,100

返回值:16

测试结果是16,是因为100虽然是整型数,但是输出时计算返回值它是3个字符。



参数format是一个字符指针,指向printf里的第一个字符串。

参数...是不定参数。这是printf能够实现的核心。

接下来介绍一下不定参数是如何实现的。

int printf(const char* format,...);

函数的参数由右向左依次入栈,如下图:



比如我们printf实际输入的参数有4个,printf(char* format,arg1,arg2,arg3,arg4);

这些参数在内存中从低地址到高地址依次为format,arg1,arg2,arg3,arg4。

因为format是指针,所以所占的字节大小为一个int的大小。

所以如果我们找到format的储存地址,从format首地址开始,加上一个int的大小,此时地址刚好就是参数arg1的首地址,然后再加上sizeof(arg1),此时地址又刚好是arg2的首地址,这样我们就能依次找出参数所在地址。

具体实现时,我们只需要定义一个指针变量ap指向arg1参数的起始地址,同时分析format参数所指的字符串,从字符串第一个字符开始检查,如果遇到%则通过分析%后面的字符就能判断出变量的类型,此时输出ap地址上所指向的变量的值,同时ap指针向右移动该变量类型大小字节个单位,使ap指向下一个参数的储存地址,然后再次分析字符串,直到分析到字符串结尾结束。

通过上面的参数入栈方式我们可以得到如下结论:


如果想将栈中的参数读出来,我们只需要知道,栈顶元素的地址即第一个参数的地址即可。通过前面变参函数的分析,通过变参函数第一个参数可以知道传递的参数个数。

当然,每个参数都有自己的类型,还有的就是字节对齐了。在读取参数的时候,这些问题都必须考虑到。

实际上处理变参时,已经有封装好的宏处理这些所有问题

typedef   char  * va_list;                  //将char*别名为va_list;

        

#define   _INTSIZEOF(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))  

#define   va_start(ap,v)   (ap = (va_list)&v + _INTSIZEOF(v))

#define   va_arg(ap,t)     (*(t*)((ap += _INTSIZEOF(t)) -  _INTSIZEOF(t)))

#define   va_end(ap)      (ap = (va_list)0)




这些宏在不同的操作系统,有不同的实现,想使用的话,只需要包含头文件stdarg.h就可以了。

(1)va_start宏的作用 :

printf(const char* format,arg1,agr2,....)

实现ap指向第一个实际参数arg1的地址,实际参数指第一个参数format后的第一个参数arg1。即va_start(ap,format)。

(2)va_arg宏作用:

t指的是分析出来的实际参数的变量类型,首先ap向后移动sizeof(t)个单位,指向下一个实际参数的地址,同时返回(ap-sizeof(t))的地址,返回的地址跟刚开始时地址一样。实际上就是为了ap移动到下一个参数的地址,为了下一次输出。

(3)va_end宏的作用

将ap指针赋值为NULL,即0

看一下实现代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdarg.h>

void printch(const char ch)   //输出字符
{  
    putchar(ch);  
}  


void printint(const int dec)     //输出整型数
{  
    if(dec == 0)  
    {  
        return;  
    }  
    printint(dec / 10);  
    putchar((char)(dec % 10 + '0'));  
}  


void printstr(const char *ptr)        //输出字符串
{  
    while(*ptr)  
    {  
        putchar(*ptr);  
        ptr++;  
    }  
}  


void printfloat(const float flt)     //输出浮点数,小数点第5位四舍五入
{  
    int tmpint = (int)flt;  
    int tmpflt = (int)(100000 * (flt - tmpint));  
    if(tmpflt % 10 >= 5)  
    {  
        tmpflt = tmpflt / 10 + 1;  
    }  
    else  
    {  
        tmpflt = tmpflt / 10;  
    }  
    printint(tmpint);  
    putchar('.');  
    printint(tmpflt);  

}  


void my_printf(const char *format,...)  
{  
    va_list ap;  
    va_start(ap,format);     //将ap指向第一个实际参数的地址
    while(*format)  
    {  
        if(*format != '%')  
        {  
            putchar(*format);  
            format++;  
        }  
        else  
        {  
            format++;  
            switch(*format)  
            {  
                case 'c':  
                {  
                    char valch = va_arg(ap,int);  //记录当前实践参数所在地址
                    printch(valch);  
                    format++;  
                    break;  
                }  
                case 'd':  
                {  
                    int valint = va_arg(ap,int);  
                    printint(valint);  
                    format++;  
                    break;  
                }  
                case 's':  
                {  
                    char *valstr = va_arg(ap,char *);  
                    printstr(valstr);  
                    format++;  
                    break;  
                }  
                case 'f':  
                {  
                    float valflt = va_arg(ap,double);  
                    printfloat(valflt);  
                    format++;  
                    break;  
                }  
                default:  
                {  
                    printch(*format);  
                    format++;  
                }  
            }    
        }  
    }
    va_end(ap);         
}  


int main()  
{  
    char ch = 'A';  
    char *str = "hello world";  
    int dec = 1234;  
    float flt = 1234.45678;   
    my_printf("ch = %c,str = %s,dec = %d,flt = %f\n",ch,str,dec,flt);  
    return 0;  
} 

 运行结果:

ch = A,str = hello world,dec = 1234,flt = 1234.4568





实际上,实现时可以用一个更简单的函数,vprintf函数。

int vprintf(char *format, va_list param);

printf的功能就是用它来实现的,所不同的是,它用一个参数取代了变长参数表,且此参数是通过调用va_start宏进行初始化。其实vprintf也是经过封装的一个函数。
这样就省了我们调用宏对变参函数进行处理,只要开始调用一次va_start宏进行一次初始化即可。


代码如下:

#include<stdio.h>
#include<stdarg.h>


int my_printf(char *str,...)
{
    int n;    //记录返回值
    va_list list;
    va_start(list,str);
    n=vprintf(str,list);
    va_end(list);
    return n;
}

int main()
{

    my_printf("%s,%d","hello world",10);

}

运行结果:

hello world,10




学习笔记|printf函数的实现|不同操作系统中的换行|数的进制:2进制、10进制、16进制转换|STC32G单片机视频开发教程(冲哥)|第五集:C语言基础 阅读详情

相关推荐

printf 内部原理和实现 (你想知道的C语言 1.2)

printf

编程语言开发、框架原理、编程思想、最佳实践技巧经验分享。致力于各种编程技术的技巧经验干货分享,讲解开发避坑技巧,追踪开发前沿技术,实战展开解决难题。 程序员小迷助您在编程路上越走越好! 5691

【嵌入式C语言】可变参数 va_start、va_arg、va_end、va_list、stdarg.h 库详解

可变参数详解 printf( )是我们在编程中避不开的函数,之前我们研究了printf( )的实现原理,初步了解了printf( )函数在库中是借用putchar( )来进行实现输出的,但是还有一个问题我们上一篇文章没有解决,那就是printf( )中的可变参数是怎么实现的呢,如何去使用可变参数完成我们自己的输出函数呢? 让我们再来看一眼printf( )的长相,在stdio.h文件中他是这样声明的 int printf(const char *format, ...) printf( )中分为两种参数,

物联网从你我开始 4103

简单实现printf函数

使用可变参数列表简单实现printf函数 代码如下: my_print.h /* ****************** 头文件 ****************** */ #ifndef __PRINT_H__ #define __PRINT_H__ #define _CRT_SECURE_NO_WARNINGS #include #include #i

ali94s的博客 644

printf函数的实现方法

printf函数的实现

qq_39625728的博客 1635

对printf函数进行封装,实现一个简单清晰的日志打印

对printf函数进行封装,实现一个简单清晰的日志打印前言一、需要哪些打印信息?二、实现步骤1.需要的头文件2.函数的封装3.扩展结语 前言 提示:日常调试中很多情况下会用到调试打印,如何快速便捷的查看打印的信息是我们关注的。 一、需要哪些打印信息? 在调试的过程中哪些信息能快速的帮助我们定位到我们想要查看的位置,一般情况下有这么一些:时间,文件名,函数名,行号等。有了这些信息,我们便能够快速的定位到对应的位置,然后去修复对应的bug。 二、实现步骤 1.需要的头文件 头文件如下: #include

sinan1995的博客 3665

深入解析:如何基于开源p-net快速开发Profinet从站服务

开源p-net协议栈为工业设备开发者打开了通往Profinet世界的大门。随着工业互联网的发展,软协议栈方案将在边缘计算领域展现更大潜力。该文章完整实现了PROFINET从站的核心功能,可直接作为产品开发的基础框架。实际部署时需根据具体硬件平台调整网络驱动和实时性配置。

极地星辰 3879

Printf()函数简单实现

/************************************************************************/<br /> /* coder:huifeng00 <br /> /* 时间:2010-5-23<br /> /* 功能:简单实现自己的printf()函数,识别%d,%s,%c<br /> /*********************************

My_Notebook 850

从零构建机械臂规划栈:Pinocchio、OMPL与Ruckig的工程化融合实践

本文详细介绍了如何从零构建机械臂规划栈,通过融合Pinocchio、OMPL和Ruckig三大核心组件,实现高性能的机械臂运动规划与轨迹生成。文章深入探讨了技术选型、工程架构设计及实战优化技巧,特别针对逆解、路径规划和轨迹平滑等关键环节提供了实用解决方案,适用于工业场景中的高实时性需求。

weixin_29072299的博客 433

自己实现的简单Printf函数

#include #include void PrintStr(const char *pStr) { while(*pStr != '\0')// 1.*pStr != '\0' { putchar(*pStr); pStr++; } } void MyPrintf(const char *pFormat, ...) { va_list args; va_s

自由理想的足迹 1428

华为数通---配置风暴控制案例

风暴控制可以通过阻塞报文或关闭端口来阻断广播、未知组播和未知单播报文的流量。

2301_77081516的博客 1624

用Pixel5玩转AOSP:从零开始定制Android13系统(含GMS集成方案)

本文提供了一份详尽的Pixel 5 AOSP定制指南,涵盖从环境搭建、源码下载、系统编译到刷机的完整流程。重点解决了原生AOSP缺失Google移动服务(GMS)的难题,分享了从官方镜像提取并集成GMS组件的实战方案,助你打造独一无二的Android 13系统。

sam99的博客 526

用C语言的可变参函数实现一个简单的printf函数

文章出处:http://www.cnblogs.com/phinecos/archive/2007/08/24/868524.html #include "stdio.h" #include "stdlib.h" #include void myprintf(char* fmt, ) //一个简单的类似于printf的实现,//参数必须都是int 类型 { //

韦编二绝 3409

Obsidian的Git插件设置配置全流程 -- 简单的电脑端多平台同步方案及常见问题

Git同步obsidian的保姆级教程和常见问题

weixin_44370439的博客 1万+

简单模拟实现printf函数

简单模拟实现printf函数

triorwy的博客 388

Zephyr RTOS详解

Zephyr 是一个开源的实时操作系统(RTOS),专为资源受限的嵌入式设备设计,支持多种架构(如 ARM Cortex-M、RISC-V、x86 等)。

weixin_43484713的博客 4103

使用Qt Creator自带的Qt Designer构造UI界面流程总结

在本文中,我们将总结使用Qt Creator自带的Qt Designer构造UI界面的流程。在对话框中,您可以选择一个控件的信号,然后将其连接到另一个控件的槽。例如,您可以将按钮的点击信号连接到一个槽函数,以响应按钮的点击事件。右键单击窗口设计器中的空白区域,选择适当的布局管理器,如水平布局、垂直布局或网格布局。在Qt Designer中,您可以使用各种预定义的UI控件来设计应用程序的界面。现在,您可以在应用程序的源代码中使用生成的UI界面。在您的源文件中,包含相应的头文件,然后创建一个UI界面的实例。

cl6789的博客 1392

简单的printf函数实现

源码如下:       #include#include#include "myprintf.h"long int mypow(int a,int n){ long int result=1; int i=0; for(;i  result=result*a;  return result;}int float2str(char *s,float a,int len1,int len2)//f

xioahw的专栏 1010

Open3D 使用CPD算法对点云配准【2025最新版】

Coherent Point Drift相干点漂移算法实现点云配准(python代码)博客长期更新,本文最近更新时间为:2025年2月2日。

点云侠的博客 8万+

操作系统开发中printf函数的简单实现

操作系统开发中printf函数的简单实现

weixin_39410618的博客 571

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

用串口实现简单的printf函数

       在VC6中,打开printf函数的源代码,发现这个函数的代码量无比巨大,闲来无事,自己用C写了一个简单的printf函数,只实现了VC库中printf函数的部分简单功能,贴出自己写的代码只为抛砖引玉,娱人娱己,仅此而已!     printf.h  头文件#ifndef _prinrf_h#define _printf_h#include "uart.h"typedef  char ...

简单博客 7738

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

单片机开发简单实现printf函数

1、编译器不支持printf函数条件下,可自己实现printf函数; 2、实现方式如下: #include "usart.h" typedef char *va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(char) - 1) & ~(sizeof(char) - 1) ) #define va_start(a...

xqw19891201的博客 773

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

printf函数的简单模拟实现

#include #include #include using namespace std;void printlog(const char * str, ...){ int vint; char *vstr; char vch; char num[10]; char buff[256]; memset(buff, 0, sizeof(buff)); va_l

姥姥教我学编程 963

【C语言】printf函数的简单实现(可变参数列表)

stdarg宏: 可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件中,它是标准库的一部分。这个头文件声明一个类型va_list 和三个宏va_start、va_arg和va_end。我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数。   声明一个va_list 类型的变量arg,它用于访问参数列表的未确定部分。这个变量是调用va_start来初始化

liuxiaoqian_的专栏 2158
上一篇: wait和waitpid函数总结
下一篇: linux 进程的虚拟内存
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值