跳转到内容

C 编程/stdio.h/函数参考

来自维基教科书,开放的书籍,开放的世界


void clearerr( FILE *stream );

clearerr 函数重置给定流的错误标志和 EOF 指示器。当发生错误时,您可以使用 perror() 来确定实际发生的错误。

fclose 是 C 语言中属于 ANSI C 标准库的函数,包含在 stdio.h 文件中。它的目的是关闭流及其所有相关结构。通常流是一个打开的文件。fclose 有以下原型

int fclose(FILE *file_pointer)

它接受一个参数:指向要关闭的流的 FILE 结构的指针,例如::fclose(my_file_pointer) 此行调用 fclose 函数以关闭 my_file_pointer 指向的 FILE 流结构。

返回值是一个整数,具有以下含义

  • 0(零):流已成功关闭;
  • EOF:发生错误;

可以通过读取 errno 来检查错误。如果 fclose 尝试关闭当前未分配给文件的 文件指针,则其行为未定义 - 在许多情况下,会导致程序崩溃。

它通常是关闭系统调用的包装器。

示例用法

[编辑 | 编辑源代码]
 #include <stdio.h>

 int main(void) 
 {
   FILE *file_pointer;
   int i;
 
   file_pointer = fopen("myfile.txt", "r");
   fscanf(file_pointer, "%d", &i);
   printf("The integer is %d\n", i);
   fclose(file_pointer);
   
   return 0;
 }

上面的程序打开一个名为 myfile.txt 的文件,并扫描其中的整数。

feof 是 C 标准库函数,在 stdio.h 头文件中声明。[1] 它的主要目的是区分流操作已到达文件末尾的情况和EOF(“文件末尾”)错误代码作为通用错误指示器返回的情况,而实际上并未到达文件末尾。

函数原型

[编辑 | 编辑源代码]

函数声明如下

int feof(FILE *fp);

它接受一个参数:指向要检查的流的FILE 结构的指针。

返回值

[编辑 | 编辑源代码]

函数的返回值是一个整数。非零值表示已到达文件末尾;值为零表示尚未到达文件末尾。

示例代码

[编辑 | 编辑源代码]
 #include <stdio.h>
 
 int main()
 {
     FILE *fp = fopen("file.txt", "r");
     int c;
     c = getc(fp);
     while (c != EOF) {
         /* Echo the file to stdout */
         putchar(c);
         c = getc(fp);
     }
     if (feof(fp))
       puts("End of file was reached.");
     else if (ferror(fp))
       puts("There was an error reading from the stream.");
     else
       /*NOTREACHED*/
       puts("getc() failed in a non-conforming way.");
 
     fclose(fp);
     return 0;
 }

对该函数的一种常见误用是尝试使用feof“抢先”。但是,这不能正常工作,因为feof仅在读取函数失败后才为描述符设置。

int fgetc(FILE *stream)

C 标准库 stdio.h 中的 fgetc 函数用于从文件流中读取一个字符。如果成功,fgetc 将返回文件中当前位置处的字符。如果失败,fgetc 将返回错误代码。对于文本文件(与二进制文件相对),值 25 和 26(替换字符和转义字符)的字符可能会使输入流在之后无法使用,而是返回单个最大值。

错误代码

-1:参数错误:不是整数
-2:参数错误:超出范围
-3:文件未打开

fgetpos 是 C 标准库 I/O 部分中的一个函数。

int fgetpos(FILE *stream, fpos_t *pos);

fgetpos()将从 stdio 流中检索文件偏移量(以字节为单位)stream到类型存储fpos t由变量指向pos,应由调用者分配。此变量仅应用于后续调用fsetpos()。一个单独的函数,ftell()可以被用作替代,以检索文件偏移量的值作为一个长整数。

成功完成时返回值为 0,出错时返回值为 -1(在这种情况下errno将相应设置)。

fgets 是 C 编程语言中的一个函数,它从给定的文件流源中读取有限数量的字符到字符数组中。[2] fgets 代表 file get string。它包含在 C 标准库头文件 stdio.h 中。函数的原型如下

char* fgets(char *string, int length, FILE * stream);

该函数在找到换行符或到达文件末尾后结束读取,或者在读取 (length - 1) 个字符后结束读取。如果遇到换行符,它将作为最后一个字符包含在字符串中,紧接在空字符之前。长度参数包含空字符所需的空间,该空字符将附加到字符串的末尾。因此,要读取 N 个字符,长度规范必须指定为 N+1。如果至少读取了一个字符且未发生错误,则返回读取的字符串,否则返回 NULL 指针。

stream 参数指定要从其读取字符串的流。stdin 通常用于此,用于从标准输入读取。否则,使用fopen() 函数返回的 FILE * 值。

示例用法

[编辑 | 编辑源代码]

以下代码从控制台输入读取字符,并使用 puts 函数以每行 20 个字符的方式打印出来,直到出现 EOF 为止。

#include <stdio.h>

#define MAX_LEN 20

int main(void)
{
  char str_buf[MAX_LEN + 1]; // One extra byte needed
                             // for the null character

  while(fgets(str_buf, sizeof str_buf, stdin) != NULL)
	puts(str_buf);

  return 0;
}

在 POSIX 工具中的使用

[编辑 | 编辑源代码]

为了符合 POSIX 工具的行长要求,通常使用 LINE_MAX(通常在 limits.h 中找到)来定义字符缓冲区的大小。

fgets() 强制使用固定大小的缓冲区在需要处理任意长文本行的应用程序中很麻烦。

符合 POSIX.1-2008 标准的系统提供了一个名为 getline() 的函数(最初是 GNU 扩展[3]),它将读取整行,如果缓冲区不够长,则重新分配缓冲区。[4]

高级应用程序可以使用 mmap 来避免缓冲区限制。

在 glibc 和 musl 中实现的 fopen 将使用 open 系统调用。

使用 fopen 打开文件

[编辑 | 编辑源代码]

使用 fopen 打开文件,它返回一个与指定文件或其他设备关联的 I/O 流,可以从该文件或设备读取和写入。如果函数失败,它将返回一个空指针。

相关的 C 库函数 freopen 在首先关闭与其参数关联的任何打开的流之后执行相同的操作。

它们的定义如下

FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *fp);

fopen 函数本质上是 Unix 操作系统 open 系统调用的稍微更高级别的包装器。同样,fclose 通常是 Unix 系统调用 close 的薄包装器,而 C FILE 结构本身通常对应于 Unix 文件描述符。在 POSIX 环境中,fdopen 函数可用于从文件描述符初始化 FILE 结构;但是,文件描述符是纯粹的 Unix 概念,标准 C 中不存在。

fopenfreopenmode 参数必须是一个以以下序列之一开头的字符串

模式 描述 开始..
r rb 以读模式打开(文件必须存在) 开头
w wb 以写模式打开(如果文件不存在则创建文件)。删除内容并覆盖文件。 开头
a ab 以追加模式打开(如果文件不存在则创建文件) 结尾
r+ rb+ r+b 以读写模式打开(文件必须存在) 开头
w+ wb+ w+b 以读写模式打开。如果文件存在则删除内容并覆盖文件,否则创建一个空的新文件 开头
a+ ab+ a+b 以读写模式打开(如果文件存在则追加) 结尾

b”代表二进制。C 标准规定了两种类型的文件——文本文件和二进制文件——尽管操作系统不需要区分这两种类型。文本文件是一个由文本组成的文件,文本按行排列,并使用某种区分的换行符或序列(在 Unix 中,一个简单的换行符;在 Microsoft Windows 中,回车符后跟一个换行符)。当从文本文件中读取字节时,换行符序列通常映射为换行符,以便于处理。当写入文本文件时,简单的换行符在写入之前映射为特定于操作系统的换行符序列。二进制文件是一个文件,其中字节以“原始”方式读取,并以“原始”方式传递,没有任何类型的映射。

当以更新模式打开文件时('+'作为模式参数中的第二个或第三个字符),可以在关联的流上执行输入和输出。但是,写入之后不能紧跟着读取,除非在中间调用 fflush 或文件定位函数(fseekfsetposrewind),读取之后也不能紧跟着写入,除非在中间调用文件定位函数。[5]

写入和追加模式将尝试创建给定名称的文件,如果不存在这样的文件。如上所述,如果此操作失败,fopen 将返回 NULL

Glibc/musl 说明

[编辑 | 编辑源代码]

Musl 和 Glibc 支持e在 Linux 上的模式,它设置O_CLOEXEC在新的文件描述符上。O_CLOEXEC是 Linux 2.6 的特性,在 Linux 手册页项目 中有记载

为新的文件描述符启用 close-on-exec 标志。指定此标志允许程序避免额外的 fcntl(2) F_SETFD 操作来设置 FD_CLOEXEC 标志。此外,在某些多线程程序中使用此标志至关重要,因为使用单独的 fcntl(2) F_SETFD 操作来设置 FD_CLOEXEC 标志不足以避免一个线程打开文件描述符而另一个线程同时执行 fork(2) 加 execve(2) 时的竞争条件。

参见 http://danwalsh.livejournal.com/53603.html

参见 musl cgit 以了解 Musl 的实现。

fputs 是 C 编程语言中的一个函数,它将一个字符数组写入给定的文件流。fputs 代表 file put string。它包含在 C 标准库头文件 stdio.h 中。

函数 fputs 在遇到终止空字符 ('\0') 后结束。空字符不会复制到流中。函数的原型如下

int fputs ( const char * str, FILE * stream );

stream 参数指定要写入字符串的流。这里通常使用 stdout,用于写入标准输出。否则,将使用 fopen() 函数返回的 FILE * 值。

示例用法

[编辑 | 编辑源代码]

以下示例是使用 fputs 的“hello world”程序

#include <stdio.h>

int main()  {
    const char *buffer = "Hello world!";
    fputs (buffer, stdout);
    return 0;
    }

以下是询问用户姓名并将其写出的程序

#include <stdio.h>
int main() {
    char name[50];
    fputs("What is your name? ", stdout);
    fgets(name, sizeof(name), stdin);
    fputs(name, stdout);
    return 0;
    }

fread 是一个从文件读取缓冲的二进制输入的函数。[6] 它包含在标准 C 库中的 stdio.h 头文件中。

size_t fread (void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream)

fread 函数将 nmemb 个大小为 size 的数据项从名为 stream 的输入流复制到 ptr 指向的数组中。一个数据项是一个长度为 size 的字节序列(不一定是空字节终止的)。fread 在读取了 nmemb 个项目、到达文件末尾或发生错误时停止追加字节。返回时,fread 将设置流中的文件指针,指向已读取的最后一个字节的下一个字节。stream 的内容保持不变。fread 函数返回实际读取的项目数。如果 nmemb 为零,则不执行任何操作,函数将返回 0。

该函数可能出现以下错误代码

  • EAGAIN - 在不阻塞进程的情况下无法立即读取输入流,并且为与流相关的文件描述符设置了 O_NONBLOCK 标志。
  • EBADF - 不是用于读取的有效文件描述符。
  • EINTR - 读取操作在读取任何数据之前被信号终止。
  • EIO - 无法从控制终端读取。当进程位于后台进程组中,并且进程尝试从其控制终端读取失败时,就会发生这种情况,要么是因为进程组已成为孤儿,要么是因为进程正在忽略或阻塞 SIGTTIN 信号。
  • ENOMEM - 可用存储空间不足。
  • ENXIO - 尝试从不存在的设备或能力不足的设备读取。

fseek 是一个属于 ANSI C 标准库的 C 函数,包含在文件 stdio.h 中。它的目的是更改指定流的文件位置指示器。由于 fseek 在许多平台上使用 32 位值,因此它具有最大 2 GB 搜索的限制。fseeko64 将用于更大的偏移量。

int fseek(FILE *stream_pointer, long offset, int origin);

fseek 函数将与流关联的文件指针移动到从 origin 偏移字节的新位置。

参数含义

  • stream_pointer 是指向流 FILE 结构的指针,其位置指示器应更改;
  • offset 是一个 long 整数,指定应放置位置指示器的位置从 origin 的字节数;
  • origin 是一个整数,指定起始位置。它可以是
    • SEEK_SET:origin 是流的开头
    • SEEK_CUR:origin 是当前位置
    • SEEK_END:origin 是流的末尾

返回值

[编辑 | 编辑源代码]

返回值是一个 integer,表示

  • 0(零) :函数在流中成功执行
  • 非零 :发生错误
  • 在无法进行搜索的设备上,返回值未定义。

请注意,每个错误号都有不同的含义。可以通过检查 errno.h 来揭示含义。

#include <stdio.h>

int main(int argc, char **argv) {
  FILE *file_handle;
  long int file_length;
  
  file_handle = fopen("file.bin","rb");
  if(fseek(file_handle, 0, SEEK_END)) {
    puts("Error while seeking to end of file");
    return 1;
  }
  file_length = ftell(file_handle);
  if (file_length < 0) {
    puts("Error while reading file position");
    return 2;
  }
  
  printf("File length: %d bytes\n", file_length);
  fclose(file_handle);
  return 0;
}

此程序代码以只读模式打开名为 file.bin 的文件。文件长度通过搜索到末尾,然后读回文件指针的位置来确定。

fsetpos 将流定位在 *ptr 中由 fgetpos 记录的位置。fsetpos 在出错时返回非零值。它是文件定位函数之一。

int fsetpos(file *stream, const fpos_t *ptr)

函数 ftell 返回流中相对于第一个字节的当前偏移量。它返回流的当前文件位置,如果发生错误,则返回 -1。

 long ftell ( FILE * stream );

fwprintf是 C 标准库函数,如 wchar.h 中所定义。所需的标头是 whar.h 和 stdio.h。它是 fprintf 的宽字符版本。它的函数签名是

int fwprintf(FILE *fp, const wchar_t *format, argument list);

在 fwprintf 的签名中,fp 是要发送输出的流。format 是宽字符流,指定输出的格式。格式字符串负责您需要提供的其他参数。

fwprintf 将输出放在指定的输出流上。fwprintf 对流执行宽字符输出,该流不是字节导向的。fwprintf 是将参数列表中指定的所有参数根据格式中宽字符格式说明符进行转换、打印和格式化的函数。fwprintf() 函数在 format 指向的宽字符串的控制下将输出写入流。

fwprintf() 函数可以转换、打印和格式化括号中的参数,这些参数将转换格式宽字符字符串。这里格式由 编号参数说明(如 "%n$" 和 "*m$")或 非编号参数转换说明符(如 % 和 *)组成。但两者不能同时存在。

返回值

[编辑 | 编辑源代码]

fwprintf 返回使用的宽字符数,不包括 NULL 字符。如果发生错误,则返回负数。

freadfwrite 函数分别提供文件输入和输出操作。freadfwrite<stdio.h> 中声明。它通常包装 write 系统调用。

使用 fwrite 写入文件

[编辑 | 编辑源代码]

fwrite 定义为

int fwrite ( const void * array, size_t size, size_t count, FILE * stream );

fwrite 函数将一块数据写入流。它将向流中的当前位置写入 count 个元素的数组。对于每个元素,它将写入 size 个字节。流的位置指示器将根据成功写入的字节数前进。

该函数将返回成功写入的元素数。如果写入成功完成,则返回值将等于 count。如果出现写入错误,则返回值将小于 count

以下程序打开一个名为sample.txt的文件,向文件写入一个字符数组,然后关闭它。

 #include <stdio.h>
 
 int main(void)
 {
     FILE *file_ptr;
     int iCount;
     char arr[6] = "hello";
 
     file_ptr = fopen("sample.txt", "wb");
     iCount = fwrite(arr, 1, 5, file_ptr);
     fclose(file_ptr);
 
     return 0;
 }

getc 是字符输入函数之一。getc 从文件读取下一个字符,它接收指向文件的 file 指针。它是读取文件的 simplest 函数。

与 getchar 一样,getc() 可以实现为宏而不是函数。getc 等效于 fgetc。getc 从 fp 指向的流中返回下一个字符;它返回 EOF 表示文件结束或错误。

int getc( FILE * stream);

这里,参数 stream 是指向 FILE 对象的指针,该对象标识要执行操作的流。

/*getc 示例*/ <Source lang="c">

  1. include <stdio.h>

int main() { FILE *fp; int c; int n = 0; fp = fopen("myfile.txt", "r");

 if(fp == NULL)
    perror ("Error opening file");
 else
 {
    do  {
        c = getc(fp);
        if(c == '#')
        n++;
        }
    while(c != EOF);
 fclose(fp);
 printf ("File contains %d#.\n",n);
 }
return 0;
}

</syntaxhighlight>

上面的程序逐个字符地读取名为 myfile.txt 的文件,并使用 n 变量来统计文件中包含的 '#' 字符数量。

返回值

[编辑 | 编辑源代码]

读取的字符将作为 int 值返回。

如果到达文件末尾或发生读取错误,函数将返回 EOF 和相应的错误指示器。我们可以使用 ferror 或 feof 来确定是否发生了错误或到达了文件末尾。

[编辑 | 编辑源代码]

POSIX 错误函数 perror 用于 C 和 C++ 中,根据存储在 errno 中的错误状态,将错误消息打印到 stderr。它打印 str 和一个与全局变量 errno 对应的实现定义的错误消息。[7]

perror 的定义最初是在 System V 接口定义的第 1 版中发布的。

#include <stdio.h>
void perror(const char* prefix);
int fd = open("/etc/passwd", O_RDONLY);
if (fd == -1) {
    perror("open");
    exit(1); 
}

如果参数 prefix 为非 NULL,perror 首先会将 prefix 打印到标准错误,后面跟着一个冒号和一个空格。然后,它会将 strerror 的结果打印到标准错误,后面跟着一个换行符。例如,上面的示例可能会打印

open: Permission denied
printf 函数的示例。

Printf 函数(代表“print formatted”)是一类通常与某些类型的编程语言相关的函数。它们接受一个名为格式字符串的字符串参数,该参数指定了将任意数量的各种数据类型参数(s)呈现为字符串的方法。然后,默认情况下,此字符串将打印到标准输出流,但存在执行其他任务的变体。格式字符串中的字符通常按字面意义复制到函数的输出中,其他参数在由格式说明符标记的位置呈现为结果文本,格式说明符通常由%字符引入。

时间线

[编辑 | 编辑源代码]

许多编程语言都实现了printf函数,用于输出格式化的字符串。它起源于 C 编程语言,它具有类似于以下原型的原型

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

字符串常量format提供对输出的描述,带由“%”转义字符标记的占位符,用于指定函数应该产生的输出的相对位置和类型。返回值产生打印的字符数。

Fortran、COBOL

[编辑 | 编辑源代码]

Fortran 的可变参数PRINT语句引用了一个不可执行的FORMAT语句。

      PRINT 601, 123456, 1000.0, 3.1415, 250
  601 FORMAT (8H RED NUM I7,4H EXP,E8.1, ' REAL' F5.2,'; VALUE=',I4)

将打印以下内容(在前进到新行后,因为如果定向到打印设备,则由于前导空格字符)[8]

 RED NUM 123456 EXP 1.0E 03 REAL 3.14; VALUE= 250

COBOL 通过分层数据结构规范提供格式化

 01 out-rec.
   02 out-name   picture x(20).
   02 out-amount picture  $9,999.99.

...

    move me to out-name.
    move amount to out-amount.
    write out-rec.

1960 年代:BCPL、ALGOL 68、Multics PL/I

[编辑 | 编辑源代码]

C 的可变参数printf起源于 BCPL 的writef函数。

ALGOL 68 草案和最终报告具有函数infoutf,后来这些函数从原始语言中删除并替换为现在更熟悉的readf/getfprintf/putf

printf(($"Color "g", number1 "6d,", number2 "4zd,", hex "16r2d,", float "-d.2d,", unsigned value"-3d"."l$,
            "red", 123456, 89, BIN 255, 3.14, 250));

Multics 具有一个名为ioa_的标准函数,它具有各种各样的控制代码。它基于 Multics 的 BOS(引导操作系统)中的机器语言功能。

 call ioa_ ("Hello, ^a", "World!");

1970 年代:C、Lisp

[编辑 | 编辑源代码]
 printf("Color %s, number1 %d, number2 %05d, hex %x, float %5.2f, unsigned value %u.\n",
        "red", 123456, 89, 255, 3.14159, 250);

将打印以下行(包括换行符,\n)

Color red, number1 123456, number2 00089, hex ff, float  3.14, unsigned value 250.

printf函数返回打印的字符数,或者如果发生输出错误,则返回负值。

Common Lisp 具有format函数。

 (format t "Hello, ~a" "World!")

在标准输出流上打印"Hello, World!"。如果第一个参数为nil,则 format 会将其字符串返回给调用者。第一个参数也可以是任何输出流。format 于 1978 年在麻省理工学院的 ZetaLisp 中引入,基于 Multics 的ioa_,后来被采用到 Common Lisp 标准中。

1980 年代:Perl、Shell

[编辑 | 编辑源代码]

Perl 也具有printf函数。Common Lisp 具有一个 format 函数,它根据与printf相同的原则起作用,但使用不同的字符进行输出转换。GLib 库包含g_print,它是printf的实现。

一些 Unix 系统有一个printf程序,用于在 shell 脚本中使用。这可以在 echo 不可移植的情况下使用。例如

echo -n -e "$FOO\t$BAR"

可以以可移植的方式重写为

printf "%s\t%s" "$FOO" "$BAR"

1990 年代:PHP、Python

[编辑 | 编辑源代码]

1991:Python 的%运算符在内插元组内容时,类似于printf的语法。例如,此运算符可与print函数一起使用

print("%s\t%s" % (foo,bar))

Python 的 2.6 版本包含了str.format(),它比已过时的%更可取,后者可能会在未来版本的 Python 中消失

print("If you multiply five and six you get {0}.".format(5*6))

1995:PHP 也具有printf函数,其规范和用法与 C/C++ 中的相同。MATLAB 没有printf,但有其两个扩展sprintffprintf,它们使用相同的格式字符串。sprintf返回一个格式化的字符串,而不是产生可视化输出。

2000 年代:Java

[编辑 | 编辑源代码]

2004:从 1.5 版开始,Java 支持printf作为PrintStream[9]类的成员,赋予它printf和 fprintf 函数的功能。同时,通过添加format(String, Object... args)方法,将类似sprintf的功能添加到String类中。[10]

// Write "Hello, World!" to standard output (like printf)
System.out.printf("%s, %s", "Hello", "World!"); 
// create a String object with the value "Hello, World!" (like sprintf)
String myString = String.format("%s, %s", "Hello", "World!");

与大多数其他实现不同,Java 的printf实现会在遇到格式错误的格式字符串时抛出异常。

[编辑 | 编辑源代码]

ANSI C 标准规定了 printf 的多种变体,用于输出流不是默认流、参数列表形式不同、输出目标是内存而不是文件描述符等情况。 printf 函数本身通常只是一个包含默认值的包装器,它包装了这些变体之一。

int fprintf(FILE *stream, const char *format, ...)

fprintf 使 printf 输出能够写入任何文件。 程序员经常使用它来打印错误信息,通过写入标准错误设备,但它可以与使用 fopen(或 fdopen)函数打开的任何文件一起使用。

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

sprintf 打印到字符串(char 数组)而不是标准输出。 sprintf 的用户必须通过计算或通过保护页来确保结果字符串不会大于为 str 分配的内存。 无法确保这一点会导致缓冲区溢出。

在 PHP 等高级语言中,sprintf 函数没有 str 参数。 相反,它返回格式化的输出字符串。 PHP 中的原型如下

string sprintf (const string format, ...)

缓冲区安全性和 sprintf

[编辑 | 编辑源代码]

在 ISO C99 中,引入了 snprintf 作为 sprintf 的替代方案,可以帮助避免缓冲区溢出的风险。

int snprintf(char *str, size_t size, const char * restrict format, ...)

snprintf 保证不会向 str 中写入超过 size 字节的数据,因此使用它可以帮助避免缓冲区溢出的风险,如下面的代码片段所示

#define BUFFER_SIZE 50
char buf[BUFFER_SIZE];
int n;
n = snprintf(buf, BUFFER_SIZE, "Your name is %s.\n", username);
if (n < 0 || n >= BUFFER_SIZE)
   /* Handle error */

如果上面的示例中 username 导致结果长度超过 49 字节,该函数将通过截断最终字节来限制保存到 buf 中的字符串。 空终止符将始终写入第 50 个位置,因此结果始终以空终止。 此外,snprintf 的返回值指示该函数如果存在足够的空间会向字符串写入多少字节(不包括空字符)。 系统可以使用此信息在需要整个字符串时分配一个新的(更大的)缓冲区。

许多 snprintf 实现偏离了上述描述,特别是许多 Windows 库、2.0.6 之前的 glibc 版本和 Solaris。 最常见的错误是在截断时返回 -1 而不是所需的长度。 更麻烦的是,有些实现没有在截断时写入空终止符,或者返回 size-1(无法检测到截断)。 这些偏差使使用 snprintf 编写可移植的安全代码比应有的难度更大。

另一个安全的 sprintf 替代方案是 asprintf,它是一个 GNU 扩展。

int asprintf(char **ret, const char *format, ...)

asprintf 自动分配足够的空间来容纳最终字符串。 它将 *ret 设置为指向结果字符串的指针,如果发生错误则设置为未定义的值(glibc 值得注意的是,它是在错误时不会始终将 *ret 设置为 NULL 的唯一实现)。 使用 asprintf 的程序员有责任在使用后释放分配的内存。 虽然不是任何标准的一部分,但 asprintf 出现在多个操作系统的 C 库中(包括 OpenBSD、FreeBSD 和 NetBSD),以及 libiberty 库的其他平台上。

GLib 提供了另一个安全的替代方案:g_strdup_printf,它分配了足够的内存,但与 asprintf 不同,它将结果字符串作为返回值返回,而不是通过第一个参数。

C++ 中用于数字转换的 sprintf 替代方案

[编辑 | 编辑源代码]

C++ 中用于字符串格式化和其他类型转换为字符串的标准方法是 iostream。 与 printf 不同,iostream 标准库是类型安全的且可扩展的。

一个常见的编程任务是将数字类型转换为字符串(char 缓冲区)。 sprintf 家族虽然有用,但对于如此简单的任务来说可能过于复杂。 此外,许多使用它们的程序并非旨在处理当 区域设置 改变时输出的差异。

在 C/C++ 中开发了许多替代方法。

vprintf、vfprintf、vsprintf、vsnprintf 和 vasprintf

[编辑 | 编辑源代码]
#include <stdio.h>
/* va_list versions of above */
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, 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);
int vasprintf(char **ret, const char *format, va_list ap);

这些函数类似于上面没有 v 的函数,不同之处在于它们使用可变参数列表。 这些函数为程序员提供了创建自己的 printf 变体的能力。 例如,程序员可以编写一个函数

void fatal_error(const char *format, ...)

该函数将使用 va_start 宏从额外参数中获取 va_list 变量,使用 vfprintf 在标准错误设备上打印消息,使用 va_end 宏清理 va_list 变量,最后执行必要的任务来干净地关闭程序。

这些函数的另一个常见应用是编写一个自定义 printf,它打印到与文件不同的目标。 例如,一个图形库可能提供一个类似 printf 的函数,它包含 X 和 Y 坐标

int graphical_printf(int x, int y, const char *format, ...)

这将通过使用 vsnprintfvasprintf 将字符串临时保存到私有缓冲区来实现。

格式占位符

[编辑 | 编辑源代码]

格式化是通过格式字符串中的占位符进行的。 例如,如果一个程序想要打印出一个人的年龄,它可以先加上 "你的年龄是 " 的前缀来显示输出。 为了表示我们想要在该消息之后立即显示年龄的整数,我们可以使用格式字符串

"Your age is %d."

格式占位符的语法为

%[参数][标志][宽度][.精度][长度]类型
  • 参数 可以省略或可以为
字符 描述
n$ n 是使用此格式说明符显示的参数的编号,允许提供的参数使用不同的格式说明符或以不同的顺序多次输出。 这是一个 POSIX 扩展,不在 C99 中。 示例:printf("%2$d %2$#x; %1$d %1$#x",16,17) 生成

"17 0x11; 16 0x10"

  • 标志 可以是以下之一或多个(按任意顺序)
字符 描述
-
(减号)
左对齐此占位符的输出(默认是右对齐输出)。
+
(加号)
在正号数字类型之前添加一个加号。 正数 = '+',负数 = '-'。 (默认情况下,正数前面不添加任何内容)。
 
(空格)
在正号数字类型之前添加一个空格。 正数 = ' ',负数 = '-'。 如果存在 '+' 标志,则忽略此标志。 (默认情况下,正数前面不添加任何内容)。
0
(零)
当指定 宽度 选项时,在数字前面添加零。 (默认情况下,在数字前面添加空格)。

示例:printf("%2d", 3) 生成 " 3",而 printf("%02d", 3) 生成 "03"。

#
(井号)
备用形式。 对于 'g' 和 'G',不会删除尾随零。 对于 'f'、'F'、'e'、'E'、'g'、'G',输出始终包含一个小数点。 对于 'o'、'x'、'X' 或 '0',分别在非零数字之前添加 '0x'、'0X'。
  • 宽度 指定要输出的最小字符数,通常用于填充制表输出中的固定宽度字段,否则字段会更小,尽管它不会导致截断过大的字段。 宽度值中的前导零被解释为上面提到的零填充标志,负值被视为与上面提到的左对齐 "-" 标志结合使用的正值。
  • 精度通常指定输出的最大限制,具体取决于特定格式类型。对于浮点数类型,它指定输出应四舍五入的小数点右边的位数。对于字符串类型,它限制应输出的字符数,超过该限制后字符串将被截断。
  • 长度可以省略,也可以是以下任一值:
字符 描述
hh 对于整数类型,导致printf预期一个由char提升的int大小的整数参数。
h 对于整数类型,导致printf预期一个由short提升的int大小的整数参数。
l 对于整数类型,导致printf预期一个long大小的整数参数。
ll 对于整数类型,导致printf预期一个long long大小的整数参数。
L 对于浮点数类型,导致printf预期一个long double参数。
z 对于整数类型,导致printf预期一个size_t大小的整数参数。
j 对于整数类型,导致printf预期一个intmax_t大小的整数参数。
t 对于整数类型,导致printf预期一个ptrdiff_t大小的整数参数。

此外,在广泛使用 ISO C99 扩展之前,存在一些特定于平台的长度选项。

字符 描述
I 对于有符号整数类型,导致printf预期ptrdiff_t大小的整数参数;对于无符号整数类型,导致printf预期size_t大小的整数参数。在 Win32/Win64 平台上很常见。
I32 对于整数类型,导致printf预期一个 32 位(双字)整数参数。在 Win32/Win64 平台上很常见。
I64 对于整数类型,导致printf预期一个 64 位(四字)整数参数。在 Win32/Win64 平台上很常见。
q 对于整数类型,导致printf预期一个 64 位(四字)整数参数。在 BSD 平台上很常见。

ISO C99 包含inttypes.h头文件,其中包含许多用于平台无关printf编码的宏。示例宏包括

字符 描述
PRId32 通常等效于I32dWin32/Win64)或d
PRId64 通常等效于I64dWin32/Win64)、lld32 位平台)或ld64 位平台
PRIi32 通常等效于I32iWin32/Win64)或i
PRIi64 通常等效于I64iWin32/Win64)、lli32 位平台)或li64 位平台
PRIu32 通常等效于I32uWin32/Win64)或u
PRIu64 通常等效于I64uWin32/Win64)、llu32 位平台)或lu64 位平台
PRIx64 通常等效于I64xWin32/Win64)、llx32 位平台)或lx64 位平台
  • 类型可以是以下任一值:
字符 描述
di int 作为有符号十进制数。'%d' 和 '%i' 在输出时是同义词,但在用于scanf()进行输入时是不同的。
u 打印十进制unsigned int
fF double 以普通(定点)表示法。'f' 和 'F' 仅在打印无穷大数或 NaN 的字符串方式上有所不同('f' 为 'inf'、'infinity' 和 'nan','F' 为 'INF'、'INFINITY' 和 'NAN')。
eE double 值以标准形式([-]d.ddd e[+/-]ddd)表示。E 转换使用字母 E(而不是 e)来引入指数。指数始终包含至少两位数字;如果值为零,则指数为 00。在 Windows 中,指数默认包含三位数字,例如 1.5e002,但可以使用 Microsoft 特定的_set_output_format 函数进行更改。
gG double 以普通或指数表示法表示,以哪种方式更适合其大小而定。'g' 使用小写字母,'G' 使用大写字母。这种类型与定点表示法略有不同,因为不会包含小数点右边的无意义零。此外,整数不会包含小数点。
xX unsigned int 作为十六进制数。'x' 使用小写字母,'X' 使用大写字母。
o unsigned int 以八进制形式表示。
s 以 null 结尾的字符串。
c char(字符)。
p void *(指向 void 的指针)以实现定义的格式表示。
n 不打印任何内容,但将迄今为止成功写入的字符数写入一个整数指针参数。
% 一个字面百分号字符(此类型不接受任何标志、宽度、精度或长度)。

宽度和精度格式参数可以省略,也可以是嵌入在格式字符串中的固定数字,或者在格式字符串中用星号 "*" 表示时作为另一个函数参数传递。例如,printf("%*d", 5, 10) 将导致打印"   10",总宽度为 5 个字符,而printf("%.*s", 3, "abcdef") 将导致打印 "abc"。

如果转换规范的语法无效,则行为未定义,并可能导致程序终止。如果提供的函数参数过少,无法为模板字符串中的所有转换规范提供值,或者如果参数类型不正确,则结果也将未定义。多余的参数将被忽略。在许多情况下,未定义的行为会导致“格式字符串攻击”安全漏洞。

一些编译器,例如 GNU 编译器集合,将静态检查类似 printf 的函数的格式字符串并警告出现的问题(当使用标志-Wall-Wformat 时)。如果将非标准“format” __attribute__ 应用于函数,GCC 也会警告用户定义的类似 printf 的函数。

在表格输出中使用字段宽度与显式分隔符的风险

[edit | edit source]

仅使用字段宽度来提供制表,例如使用格式 "%8d%8d%8d" 来表示三个 8 字符列中的三个整数,不能保证如果数据中出现大数字,字段分隔符将被保留。字段分隔符的丢失很容易导致输出损坏。在鼓励将程序作为脚本中的构建块使用的系统中,这种损坏的数据通常会转发并损坏进一步的处理,无论原始程序员是否期望输出仅供人眼阅读。通过在所有表格输出格式中包含显式分隔符,即使是空格,也可以消除此类问题。只需将前面的危险示例更改为 " %7d %7d %7d" 就可以解决此问题,在数字变大之前格式相同,但随后由于显式包含的空格而明确防止它们在输出时合并。类似的策略也适用于字符串数据。

自定义格式占位符

[edit | edit source]

有一些printf类函数的实现允许扩展基于转义字符的迷你语言,从而允许程序员为非内置类型使用特定的格式化函数。最著名的一种是(现已弃用)的 glibc register_printf_function()。但是,由于它与静态格式字符串检查冲突,因此很少使用它。另一种是Vstr 自定义格式化程序,它允许添加多字符格式名称,并且可以与静态格式检查器一起使用。

一些应用程序(例如 Apache HTTP 服务器)包含自己的printf类函数,并将扩展嵌入其中。但是,这些函数往往与register_printf_function()存在相同的问题。

大多数具有printf类函数的非 C 语言通过仅使用 "%s" 格式并将对象转换为字符串表示来解决此功能的缺乏问题。C++ 提供了一个显著的例外,因为它继承了 C 的历史,因此具有printf函数,但它也具有完全不同的机制,而该机制是首选的。

putcstdio.h中的函数。它是打开文件后写入文件的最简单方法。它将字符写入流并前进位置指示器。它是输出函数。字符将写入流的当前位置,如内部位置指示器所示,然后位置指示器前进一个字符。

putc 等效于fputc,也需要一个流作为参数,但putc 可能被实现为宏,因此传递的参数不应是可能产生副作用的表达式。

有关没有流参数的类似函数,请参见putchar

int putc ( int character, FILE * stream );

参数

[edit | edit source]
字符
要写入的字符。字符以其 int 提升形式传递。
stream
指向 FILE 对象的指针,该对象标识要写入字符的流。

返回值

[edit | edit source]

如果没有错误,将返回与写入的字符相同的字符。如果发生错误,将返回 EOF 并且错误指示器将被设置。


示例

[edit | edit source]

/* putc 示例:字母写入器 */ <Source lang="c">

  1. include <stdio.h>

int main () {

 FILE *fp;
 char c;
 fp = fopen("alphabet.txt", "wt");
 for (c = 'A' ; c <= 'Z' ; c++)
   {
    putc (c , fp);
   }
 fclose (fp);
 return 0;

} </syntaxhighlight>

输出

[edit | edit source]

此示例程序创建一个名为 alphabet.txt 的文件,并将 ABCDEFGHIJKLMNOPQRSTUVWXYZ 写入其中。

putchar 是 C 编程语言中用于将单个字符写入标准输出流 stdout 的函数。[11] 它的原型如下

int putchar (int character)

要打印的字符作为参数输入到函数中,如果写入成功,则返回参数字符。否则,返回文件结尾。

putchar 函数在 C 标准库头文件 stdio.h 中定义。

示例用法

[编辑 | 编辑源代码]

以下程序使用 getchar 将字符读入数组,并在找到文件结尾字符后使用 putchar 函数将其打印出来。

 #include <stdio.h>

 int main(void)
 {
   char str[1000];
   int ch, i, n = 0;
  
   while ((ch = getchar()) != EOF && n < 1000)
     str[n++] = ch;
	   
   for (i = 0; i < n; ++i)
     putchar(str[i]);

   putchar('\n'); /* trailing '\n' needed in Standard C */
	
   return 0;
 }

程序将读取长度的最大值指定为 1000 个字符。它将在读取 1000 个字符或读取文件结尾指示符后停止读取,以先发生者为准。

puts 是一个用于输出字符串(加上一个换行符)的函数,例如

#include <stdio.h>
int main() {
    puts("welcome to WIKIPEDIA!!!");
}

输出(在 stdout 上)

welcome to WIKIPEDIA!!!

与 printf 的区别

1. puts 在提供的文本后打印一个换行符

2. puts 按原样打印字符串(它不会处理 % 代码)。

我们也可以将一个变量传递给 puts,例如

#include <stdio.h>
int main() {
    const char *str = "welcome to WIKIPEDIA!!!";
    puts(str);
}

输出

welcome to WIKIPEDIA!!!

puts 具有以下原型

int puts(const char *str)

它将打印 str 中的每个字节,直到遇到空字符,然后打印一个换行符。puts 返回写入的字节数(包括换行符),或 EOF(如果发生错误)。

要打印一个字符串而不处理 % 代码,或输出换行符,请尝试

    printf("%s", "welcome to WIKIPEDIA!!!");

remove 是 C 编程语言中用于删除特定文件的函数。它包含在 C 标准库头文件 stdio.h 中。

函数的原型如下

int remove ( const char * filename );

如果成功,函数返回零。如果失败,则返回非零值,并且 errno 变量设置为相应的错误代码。

示例用法

[编辑 | 编辑源代码]

以下程序演示了 remove 的常见用法

#include <stdio.h>

int main() 
{
    const char *filename = "a.txt";
    remove (filename);
    return 0;
}

要实现一个简单的删除工具

#include <stdio.h>

int main(char* argv[], int argc) 
{
    if (argc > 1) {
        return remove (argv[1]);
    }else{
        return -1;
    }
}

rename 是 C 编程语言中用于重命名特定文件的函数。[12]

函数的原型是

int rename(const char *oldname, const char *newname)

成功时返回零。如果失败,则返回其他整数,在这种情况下,errno 被设置为错误代码。

rename 函数在 C 的 stdio.h 库头文件和 C++ 的 cstdio 头文件中定义。它在 ANSI C 中定义。

在 POSIX 中,如果旧名称和新名称位于不同的挂载文件系统上,则 rename 将失败(返回 EXDEV)。[13] 如果 rename 调用成功,则保证它从当前主机(即另一个程序)的角度来看是原子的(即,另一个程序只会看到带有旧名称的文件或带有新名称的文件,而不是两者都有或两者都没有)。

Windows C 库版本的 rename 如果目标文件已存在则失败(POSIX 只有在用户没有删除目标文件的权限时才会失败)。虽然底层的 WIN32 调用(从 Win2K 开始)有一个选项可以复制 POSIX 原子行为,但库从未被修复为使用它。

rewind 是 C 编程语言中的一个函数,它在 stdio.h 库头文件中定义。该函数将文件位置指示器移动到指定流的开头,同时还清除与该流相关的错误和 EOF 标志。[14]

rewind 的作用与为传递的流调用 fseek(stream, 0L, SEEK_SET) 相同,只是 rewind 会导致错误指示器也被清除。

#include <stdio.h>
void rewind( FILE *stream );

scanf 是一个函数,它从给定的字符串流源读取具有指定格式的数据,起源于 C 编程语言,并且存在于许多其他编程语言中。

scanf 函数的原型是

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

该函数返回成功匹配的项目总数,该总数可能小于请求的总数。如果输入流耗尽或在任何项目匹配之前从输入流读取失败,则返回 EOF。

据可追溯的范围,“scanf”代表“scan format”,因为它扫描输入以查找有效标记并根据指定的格式解析它们。

scanf 函数在 C 中找到,它从标准输入(通常是命令行界面或类似类型的文本用户界面)读取数字和其他数据类型的输入。

以下显示了 C 中的代码,它从标准输入读取可变数量的无格式十进制整数,并在单独的行上打印出每个整数

#include <stdio.h>

int
main(void)
{
    int n;
    while (scanf("%d", & n))
        printf("%d\n", n);
    return 0;
}

在被上述程序处理后,一个杂乱无章的整数列表,例如

456 123 789     456 12
456 1
      2378

将整齐地显示为

456
123
789
456
12
456
1
2378

要打印出一个单词

#include <stdio.h>

int
main(void)
{
    char word[20];
    if(scanf("%19s", word) == 1)
        puts(word);
    return 0;
}

无论程序员希望程序读取什么数据类型,参数(如上面的 &n)都必须是指向内存的指针。否则,函数将无法正常执行,因为它将尝试覆盖错误的内存部分,而不是指向您试图获取输入的变量的内存位置。

由于 scanf 被指定为仅从标准输入读取,因此许多具有界面的编程语言,如 PHP,具有 sscanffscanf 等派生函数,而不是 scanf 本身。

派生函数

[编辑 | 编辑源代码]

根据输入的实际来源,程序员可以使用 scanf 的不同派生函数。两个常见的例子是 sscanffscanf

fscanf 派生函数从指定的流文件读取输入。原型如下

(C 或 C++)

int fscanf (FILE *file, const char *format, ...);

(PHP)

mixed fscanf (resource file, const string format [, mixed args...])

fscanf 派生函数的工作方式与原始 scanf 函数相同 - 读取的输入部分将不会再次读取,直到文件关闭并重新打开。

sscanf 派生函数从作为第一个参数传递的字符字符串读取输入。与 fscanf 的一个重要区别是,每次调用函数时,都会从头开始读取字符串。没有在成功读取操作后递增的“指针”。原型如下

(C 或 C++)

int sscanf (const char *str, const char *format, ...);

(PHP)

mixed sscanf (const string str, const string format [, mixed args...])

vscanf、vsscanf 和 vfscanf

[编辑 | 编辑源代码]
int vscanf (const char *format, va_list args);
int vsscanf (const char *source, const char *format, va_list args);
int vfscanf (FILE *file, const char *format, va_list args);

它们与没有 v 前缀的相同函数类似,只是它们从 va_list 获取参数。(参见 stdarg.h。)这些变体可用于可变参数函数。

格式字符串规范

[编辑 | 编辑源代码]

scanf 中的格式占位符与它的逆函数 printf 中的格式占位符大致相同。

格式字符串中很少有常量(即不是格式占位符的字符),主要是因为程序通常不是为了读取已知数据而设计的。例外情况是一个或多个空格字符,这些字符会丢弃输入中的所有空格字符。

以下是一些最常用的占位符:

  • %d : 以带符号十进制数的形式扫描整数。
  • %i : 以带符号数的形式扫描整数。类似于 %d,但在以 0x 开头时将数字解释为十六进制,以 0 开头时解释为八进制。例如,字符串 031 使用 %d 读取为 31,使用 %i 读取为 25。标志 h%hi 中表示转换为 shorthh 表示转换为 char
  • %u : 扫描十进制 unsigned int(注意,在 C99 标准中,输入值的减号是可选的,因此如果读取负[需要澄清] 数,不会出现错误,结果将是二进制补码。参见 strtoul()Template:Failed verification) 类似地,%hu 扫描 unsigned short%hhu 扫描 unsigned char
  • %f : 以普通(定点)表示法扫描浮点数。
  • %g, %G : 以普通或指数表示法扫描浮点数。%g 使用小写字母,%G 使用大写字母。
  • %x, %X : 以无符号十六进制数的形式扫描整数。
  • %o : 以八进制数的形式扫描整数。
  • %s : 扫描字符字符串。扫描在空格处终止。在字符串末尾存储一个空字符,这意味着提供的缓冲区必须至少比指定的输入长度长一个字符。
  • %c : 扫描一个字符(char)。不会添加空字符。
  • (space): 空格扫描空格字符。
  • %lf : 以双精度浮点数形式扫描。
  • %Lf : 以长双精度浮点数形式扫描。

以上可以在数字修饰符和 lL 修饰符(代表“long”)之间组合使用,这些修饰符位于百分号和字母之间。百分号和字母之间也可以有数字值,在 long 修饰符(如果有)之前,这些数字值指定要扫描的字符数。百分号后的可选星号 (*) 表示由该格式说明符读取的数据不存储在变量中。格式字符串后面的参数不应包含此丢弃的变量。

printf 中的 ff 修饰符在 scanf 中不存在,导致输入和输出模式之间的差异。llhh 修饰符在 C90 标准中不存在,但在 C99 标准中存在。[15]

格式字符串的示例如下:

"%7d%s %c%lf"

上面的格式字符串将前七个字符扫描为十进制整数,然后读取剩余字符为字符串,直到遇到空格、换行符或制表符,然后扫描后面的第一个非空格字符和一个双精度浮点数。

错误处理

[编辑 | 编辑源代码]

scanf 通常用于程序无法保证输入是否符合预期格式的情况。因此,健壮的程序必须检查 scanf 调用是否成功,并采取适当的措施。如果输入格式不正确,错误数据仍将保留在输入流中,必须读取并丢弃这些数据,然后才能读取新的输入。另一种避免这种情况的输入读取方法是使用 fgets,然后检查读取的字符串。例如,最后一步可以通过 sscanf 完成。

printf 一样,scanf 也容易受到格式字符串攻击。应格外小心,确保格式字符串包含字符串和数组大小的限制。在大多数情况下,来自用户的输入字符串大小是任意的;在 scanf 函数执行之前无法确定它。这意味着没有长度说明符的 %s 占位符的使用本质上是不安全的,并且可以用于缓冲区溢出。另一个潜在问题是允许动态格式字符串,例如存储在配置文件或其他用户控制文件中的格式字符串。在这种情况下,除非事先检查格式字符串并强制实施限制,否则无法指定字符串大小的允许输入长度。与之相关的是额外的或不匹配的格式占位符,这些占位符与实际的 vararg 列表不匹配。这些占位符可能会从堆栈中部分提取,根据 varargs 的特定实现,可能包含不希望的甚至不安全的指针。

/* 另一个仅在某些特定编译器上有效的用法是

scanf("请输入一个值 %d",&n);

这会打印引号中的字符串,并在指示的 % 号处停止接受输入。*/

setvbuf 是标准 C 中的一个函数,它允许程序员控制文件流的缓冲。它在 <stdio.h> 中声明;其函数原型为

int setvbuf(FILE *stream, char *buf, int mode, size_t size);

stream 参数是指向文件流的指针,相关缓冲操作将在此文件流上执行;buf 是长度为 size 的字符数组,或为 NULL 指针;mode 是所需的缓冲类型:_IOFBF,表示完全缓冲,_IOLBF 表示行缓冲,_IONBF 表示不缓冲。这三个宏在 <stdio.h> 中定义。setvbuf 在成功时返回零,在失败时返回非零值。

如果 buf 为 NULL 指针,系统将动态分配一个指定大小的缓冲区(size 个字符)。如果 mode_IONBF,流 I/O 将不会被缓冲,导致流上的每个后续 I/O 操作立即执行,并且 bufsize 参数将被忽略。

一个相关的函数 setbuf 也控制文件流的缓冲。与 setvbuf 不同,setbuf 仅接受两个参数。原型为

void setbuf(FILE *stream, char *buf);

setbuf 的行为等同于

(void)setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);

也就是说,如果 buf 不为 NULL,则使用给定的缓冲区将流设置为完全缓冲;否则,将流设置为不缓冲。如果向 setbuf 提供缓冲区,它必须至少 BUFSIZ 字节长。该函数始终成功。

下面的代码非常不稳定,可能无法在特定编译器上正常工作。它甚至可能发生缓冲区溢出。C99 规定,在写入流后不能调用 setvbuf,因此此代码调用未定义的行为。C99 脚注 230(非规范性)表示,在 main 结束时,应该关闭流,然后再释放 buf。

该程序的输出应为 Hello world 后跟一个换行符。

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

int main(void) {
    char buf[42];

    if(setvbuf(stdout, buf, _IOFBF, sizeof buf)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("He");
    /* The buffer contains "He"; nothing is written yet to stdout */
    fflush(stdout); /* "He" is actually written to stdout */

    if(setvbuf(stdout, NULL, _IONBF, 0)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("llo w"); /* "llo w" is written to stdout, there is no buffering */

    if(setvbuf(stdout, buf, _IOLBF, sizeof buf)) {
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }
    printf("orld"); /* The buffer now contains "orld"; nothing is written yet to stdout */
    putchar('\n'); /* stdout is line buffered; everything in the buffer is now written to stdout along with the newline */

    return EXIT_SUCCESS;
}

在计算中,tmpfile 是一个 ISO C/POSIX 函数,用于创建临时文件,临时文件是当打开文件的程序关闭或终止时不再存在的计算机文件。[16][17][18]

C

#include <stdio.h>

C++

#include <cstdio>

FILE* tmpfile(void);

函数 tmpfile 在成功时报告指向有效文件流的指针;在失败时,它返回 NULL[16]

错误条件

[编辑 | 编辑源代码]

除了返回 NULL 外,tmpfile 在失败时还会设置 errno。如果 tmpfile 失败,errno 的允许值如下:[16]

  • EINTR - 如果在执行 tmpfile 期间捕获到信号。
  • EMFILE - 如果已达到文件描述符的最大数量和/或文件流的最大数量(在进程中)。
  • ENFILE - 如果当前打开的文件数量已达到系统允许的最大数量。
  • ENOSPC - 如果文件系统中没有空间来创建临时文件。
  • EOVERFLOW - 如果文件是普通文件,并且文件的大小无法在 off_t 类型的对象中正确表示。
  • ENOMEM - 如果没有足够的内存来分配文件流。

注意事项

[编辑 | 编辑源代码]

tmpfile 函数容易受到多种安全漏洞的攻击;使用非可移植的 mkstemp(UNIX)或 tmpfile_s(MSVCRT)函数来代替,以避免这些问题。[19][20]

Microsoft C 运行时库中该函数的实现试图在当前驱动器的根目录中创建文件,通常会失败并报告“拒绝访问”。

Ungetc 是 C 标准库函数 ungetch 的一个版本。它在某种程度上是一个受限的函数。

int ungetc (int c, FILE *stream)

这里,c 是一个字符变量,stream 是一个文件指针,整个函数用整数数据类型表示。

如上所述,它是一个 ungetch 的受限版本,该函数将 c 值指定的字符压入堆栈。在压入时,字符 c 被转换为 unsigned char,然后压入输入 stream。如果对流应用 getc 函数,则可以返回压入的字符。

调用容量

[编辑 | 编辑源代码]

一次调用只允许压入一个字符。尝试连续压入字符可能正常工作,也可能不正常工作。在正常和常用的实践中,该函数被调用 4 次,因此连续压入了 4 个字符。这意味着该函数的处理过程完全取决于机器。因此,如果机器中的内存范围很大,则有可能进行无限次的压入操作。

返回值

[编辑 | 编辑源代码]

压入的字符将由流上的任何后续读取操作返回(按相反顺序),如果流的输入被缓冲,则意味着最后压入的字符将首先返回。当正常压入一个字符时,该函数将返回最后压入的字符。如果压入操作未正确完成,这意味着如果压入字符不成功(例如,如果文件未打开),则返回 EOF 字符作为错误。无法使用 ungetc 将 EOF 字符压入流。成功调用 ungetc 函数将清除流的 EOF 指示符。

要擦除从调用 ungetc 函数得到的压入字符,在从流中读取字符之前,需要调用 fseekfsetposfflush 函数。由于所有压入的字符都已读取,因此文件位置指示器与压入字符之前的状态相同。

Ungetcungetch 的受限版本

[编辑 | 编辑源代码]

如前所述,它是一个函数 ungetch 的受限版本,它具有相同的限制,例如,当读取操作紧随写入操作或反之亦然时。这就是为什么在 ungetc 和随后的 writeread 函数之间需要一个中间重新定位操作。

vwprintf是一个 C 标准库函数,如 wchar.h 中所定义。它的函数签名如下

int vwprintf(const wchar t *format, va_list args);

在函数 vwprintf 的签名中,format 是格式专门化,args 是指向参数的指针。

与其他函数的比较

[编辑 | 编辑源代码]

vwprintf 的功能与 swprintf 相同。这两个函数之间的区别在于 参数列表已被指向参数列表的指针替换。vwprintf 将宽字符输出到类似 stdout 的字符串,而 stdout 不应该是面向字节的。

vwprintf 的功能与 swprintf 相同。这两个函数之间的区别在于 参数列表已被指向参数列表的指针替换。vwprintf 将宽字符输出到类似 stdout 的字符串,而 stdout 不应该是面向字节的。此函数返回的字符数不包括空字符,如果发生输出错误,则返回任何负值。vwprintf() 等效于带有参数列表被 arg 替换的 wprintf,arg 可以通过 vastart 宏初始化。

返回值

[编辑 | 编辑源代码]

vwprintf 在成功时返回写入的字符数,但不包括 NULL 字符。但在失败时,它会返回错误,并设置 errno。

wprintf是一个 C 标准库函数,如 wchar.h 中所定义。它的函数签名如下

int wprintf(const wchar t *format,...);

wprintf() 将输出写入 stdout(标准输出流)。它使用可变参数列表。该函数以及 vprintf、vfprintf、vsprintf、vsnprintf 和 vasprintf 等函数为程序员提供了创建自己的 printf 变体的功能。

wprintf 函数在 C 中可以找到,它在格式字符串的控制下将输出写入流。格式字符串指定后续参数如何转换为输出。

wprintf 是 printf 格式的宽字符版本。格式是一个宽字符字符串,其中 wprintf 和 printf 在它们以 ANSI 模式打开时行为类似。

以下是用于理解 wprintf 工作原理的示例代码。

代码

#include<stdio.h>
#include<wchar.h>
int main(int argc,char *argv[])
{
      wchar_t *str = L"@TAJMAHAL@#$$$";
      wprintf(L"%ls\n", str);
      return EXIT_SUCCESS;
}

运行代码后,输出将如下所示

@TAJMAHAL@#$$$

限制
1. wprintf() 不是可移植函数。
2. wprintf() 不能与 printf() 混用。
3. wprintf 无法打印双精度值。

  for e.g. 2^56 is the double value which cannot be printed using wprintf().

wscanf

wscanf 是 C 编程语言中 C 标准库函数。
它转换 宽字符 输入。该函数由头文件 wchar.h 支持。wscanf 是 scanf 的宽字符版本。


语法
int wscanf(const wchar_t[21]*input 以格式化的形式)
它返回格式正确的输入数量。如果存在格式错误的宽字符输入,则计数可能为零或小于输入的数量。


示例 <Source lang="c">

  1. include<stdio.h>

int main() {

  int   j, result;
  float a;
  char  ch, string[128];
  wchar_t wch, wst[128];
  result = scanf( "%d %f %c %C %80s %80S", &j, &a, &ch, &wch, string, wst );
  printf( "The number of fields input is %d\n", result );
  printf( "The contents are: %d %f %c %C %s %S\n", j, a, ch, wch, string, wst);
  result = wscanf( L"%d %f %hc %lc %80S %80ls", &j, &a, &ch, &wch, string, wst );
  wprintf( L"The number of fields input is %d\n", result );
  wprintf( L"The contents are: %d %f %C %c %hs %s\n", j, a, ch, wch, string, wst);
  return 0;

}</syntaxhighlight>


现在,如果给定的输入如下所示:83 56.6 k m Scanf input
54 22.3 a f Wscanf input


那么输出将如下所示
输入字段数量为 6
内容为:83 56.599998 k m Scanf input
输入字段数量为 6
内容为:54 22.300003 a f Wscanf input
因此,scanf 返回格式正确或成功分配的字段数量。它不会返回格式错误的字段的计数,并且这些值被读取但未分配。如果在文件第一次读取字符时遇到文件结尾字符,则返回EOF

  1. ISO/IEC 9899:1999 规范 (PDF). 第 305 页,第 7.19.10.2 节。
  2. ISO/IEC 9899:1999 规范 (PDF). 第 296 页,第 7.19.7.2 节。
  3. http://www.gnu.org/software/libc/manual/html_node/Line-Input.html#index-getline-993
  4. http://pubs.opengroup.org/onlinepubs/9699919799/functions/getline.html
  5. http://www.opengroup.org/onlinepubs/009695399/functions/fopen.html
  6. ISO/IEC 9899:1999 规范 (PDF). 第 301 页,第 7.19.8.1 节。
  7. ISO/IEC 9899:1999 规范 (PDF). 第 305 页,第 7.19.10.2 节。
  8. "ASA 打印控制字符". 检索于 2010 年 2 月 12 日.
  9. "PrintStream (Java 2 Platform SE 5.0)". Sun Microsystems Inc. 1994. 检索于 2008-11-18.
  10. "String (Java 2 Platform SE 5.0)". Sun Microsystems Inc. 1994. 检索于 2008-11-18.
  11. ISO/IEC 9899:1999 规范 (PDF). 第 299 页,第 7.19.7.9 节。
  12. ISO/IEC 9899:1999 规范 (PDF). 第 268 页,第 7.19.4.2 节。
  13. rename: 重命名文件 - 系统接口参考,The Single UNIX® Specification, Issue 7 from The Open Group
  14. [1], The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008.
  15. C99 标准,第 7.19.6.2 节 “fscanf 函数” 第 11 段。
  16. a b c tmpfile 由 OpenGroup
  17. 临时文件 由 GNU C 库
  18. tmpfile 由 HMUG
  19. TMPNAM-TMPFILE 漏洞 由 Build Security In
  20. VOID FI039-C. 安全创建临时文件 由 CERT
  21. wchar_t
华夏公益教科书