格式化输入输出

printf 大伙太熟了这里就不细讲,给个表格自行查阅吧

符号 输出
%d int 十进制
%u unsinged 十进制
%l long
%lld long long
%f float
%e double 以指数形式
%s char*
%g 较短的输出小数。
p 指针
x 十六进制
o 八进制

str print format

sprintf()

1
int sprintf(char *str, const char *format, ...);

这玩意好用指出在于将数据整和,拼接到特定变量中, 或文件中(fprintf/fdprintf)。

为了安全起见防止数据溢出,建议使用snprintf() ,除非你很有把握

1
2
3
       int snprintf(char *str, size_t size, const char *format, ...);
//给定了size 放置溢出

by the way :

今天发现了一个坑,发现在赋值的时候当需要赋值超出类型限制的值时,需要进行强制转化,否则就会报错。编译器为了内存安全死活不让你通过。

1
2
3
  int a{0xFFFFFFFF};//error
int b{(int)4294967295};
// error: narrowing conversion of ‘4294967295’ from ‘unsigned int’ to ‘int’ [-Wnarrowing]

格式化输出

需要头文件

include

简单的使用凡事

1
2
3
4
5
6
7
8
9
10
11
12
void test(const char* format, ...) {
va_list ap;
va_start(ap, format);
vprintf(format, ap);
char buffer[4096] = "";
vsnprintf(buffer, sizeof(buffer), format, ap);
perror(buffer);

vsprintf(buffer, format, ap); //出现段错误

}

va_start 宏用来处理后续参数,出现段错误的原因也在这:

段错误:一半是对内存进行了非法的访问产生。了解这一点我们思路就可以逐渐打开了。

va_list 表示的是参数列表, 而format 这是第一个参数的值

va_start 的作用就是根据format 作为 锚点,通过指针偏移,取出后续参数。本质上,是拿着第一个参数的压栈的地址,偏移。va_start(ap,v);执行ap = (va_list)&v + _INTSIZEOF(v)。

ap+= sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型指针,这正是第一个可变参数在堆栈里的地址。

vprintf()执行一次就会调用ap偏移指针,取出参数。

可以发现va_list 中记录了四个参数

image-20230323122359209

每执行依次vprintf 参数都会发生改变,也就是偏移,偏着偏着就偏过头了。。

解决就是再调用之前初始化ap va_start(ap, format);

格式化输入

老生常谈scanf

不过需要注意其中还有一些默认的隐藏的东西行为

  • 空格是默认的分隔符,当然输入的时候遇到“%d,%s,%f”时,输入的时候就必须使用逗号分隔!

  • 不部分数据格式是可以自动正确分隔的。

  • 比如说%c%d,反过来%d%c,也没问题…吗?测试一下

  • int ret = scanf("%d%c", &a, &c);
    printf("ret = %d  %d %c\n", ret, a, c);
    if (isblank(c))
      std::cout << "yes!blank\n";
    if (iscntrl(c))
      std::cout << "yes!cntrl (int)c = " << (int)c << char(10);
    
    1
    2
    3

    输出结果嘛,就是将最后的确认 作为转义字符\n 传入c中了

    1324 ret = 2 1324 yes!cntrl (int)c = 10

格式化输入也有跟格式化输出相对应的几个函数

比如

格式化字符串输入

sscanf();

格式化字符串赋值

vsscanf()

vsnscanf()

格式化文件输入

vfscanf()