C++ printf族函数

2 篇文章 0 订阅

1、所有的printf族函数

有两大类:

#include<stdio.h>
int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
int dprintf(int fd, const char* format, ...);
int sprintf(char* str, const char* format, ...);
int snprintf(char* str, size_t size, const char* format, ...);
#include<stdarg.h>
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vdprintf(int fd, const char* format, va_list ap);
int vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);

从函数名来看,下面的函数的名字都比上面多一个字母 v
从参数类型来看,下面函数的最后一个参数都是va_list类型。
下面的函数在功能上与上面的函数一 一对应。

2、前驱知识

本小节从从最基础的 printf 入手,来讲解学习这些函数需要的前驱知识。

2.1 什么是: 格式化字符串format

这个其实我们每天都在用,只是不知道他叫这个名字。

int a = 1;
double b = 3.14;
printf("a = %d, b = %llf\n", a, b);

/*	printf函数的定义
int printf(const char* format, ...);
/*

结合上面printf函数的定义可知, "a = %d, b = %llf\n" 其实就是格式化字符串,即,参数format的内容。而ab合起来是可变参数参数,即,参数...的内容。

2.2 va_list是什么类型

va_list 是可变参数列表类型,它的含义和...一样,都是代表很多个参数。但它们的区别也很大。

准确来说 ...并不能算作一种类型,他只是在视觉上告诉我们这个位置可以传入可变参数。而在函数的实现中我们并不能直接使用...作为参数名来使用这些可变参数。那,我们想自己实现一个形如 my_printf(const char* format, ...) 的函数该怎么办呢??

va_list 类型就是用来解决这个问题的。在函数体中,我们使用va_list类型的一个参数就可以代表...中所有的可变参数。

好嘞,本小节标题的问题解决了。

3、可变参数列表(va_list)怎么用?

3.1 va_list族函数

#include<stdarg.h>
void va_start(va_list ap, last);	
type va_arg(va_list ap, type);
coid va_end(va_list ap);
void va_copy(va_list dest, va_list src);

3.2 这些函数怎么用

直接以一个实际应用中的例子来说明。现在,我们的程序中需要一个这样的函数:func(int a, double b, ...);

首先,在函数体中定义一个va_list类型的变量:va_list arg_list;

此时arg_list还未初始化,类似一个空指针,接下来要把这个空指针指定到需要的位置。va_start函数就用来完成这件事,使用va_list类型时,该函数必须第一个被调用:
va_start(arg_list, b);
调用该函数后,arg_list就能代表传入func所有可变参数。va_start函数中,参数last没有类型,就是一个参数名,他是fun中最后一个类型已知的参数的名字,本例中就是b

然后,arg_list就能正式投入使用了。我们既可以将他看作一个整体,传入printf函数的第二个参数,也可以把可变参数一个一个取出来用。
先来看前者:直接这样:printf(" 这里是格式化字符串 ", arg_list);
后者要复杂一些,需要使用va_arg函数。进行 va_arg(arg_list, int) 调用就是从开始位置返回一个int(可能不是int类型,根据实际情况确定类型),并把位置往前推1,下次调用该函数就返回后面的那个参数。这样访问需要提前知道可变参数的数量,类型以及顺序。

va_arg函数本身并不知道在哪里结束,所以用while(1){ var_arg(args,int);} 会无限循环直到内存访问越界程序coredump。 最好就在传参时传入一个专门用于标识结尾的参数 比如12345678 。当返回值等于标识数时,结束访问。

访问完毕后,必须使用va_end函数销毁刚刚初始化的arg_listva_end(arg_list);

3.3 总结

va_start:初始化(指定位置)
va_arg:遍历访问
va_end:销毁
va_copy:复制

4、再看printf族函数

#include<stdio.h>
int printf(const char* format, ...);				将输出写入标准输出 stdout
int fprintf(FILE* stream, const char* format, ...);	将输出写入给定的输出流
int dprintf(int fd, const char* format, ...);		将输出写入文件描述符
int sprintf(char* str, const char* format, ...);	将输出写入字符串
int snprintf(char* str, size_t size, const char* format, ...);	将定长输出写入字符串

使用va_list类型的函数功能与上面的一 一对应,只是在调用时,把上面的...变量用va_list变量替代。

#include<stdarg.h>
int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vdprintf(int fd, const char* format, va_list ap);
int vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);

5、asprintfvasprintf函数

查看man手册发现,这两个函数并没有和开头列出的10个函数放在一起,其定义如下:

#define _GNU_SOURCE
#include<stdio.h>

int asprintf(char** strp, const char* fmt, ...);
int vasprintf(char** strp, const cahr* fmt, va_list ap);

这两个函数的功能其实和sprintfvsprintf一样,只是上面的两个函数分配了一个足够大的字符串来保存输出,包括终止字符'\0',并通过第一个参数返回指向它的指针。最后该指针应该传递给free,以释放不再需要的存储空间。

使用实例代码:

void LogEvent::format(const char* fmt, va_list al)
{
	char* buf;
	int len = vasprintf(&buf, fmt, al);
	if (len != -1)
	{
		m_ss << std::string(buf, len);		m_ss 是一个 stringstream 类型的字符流对象
		free(buf);
	}
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值