听说C语言很难?怎么不来看看我这篇(七)输入输出

1.写在前面

前面的博客,我已经介绍完了C语言的一些的基本的信息。包括变量、控制流、函数、指针、结构等等这些基本东西。我们学习Java的都知道,Java强大的地方有很多的框架,有很多API提供给我们使用。C是一门古老的语言,它有它的强大的API,就藏在对应的头文件中,这篇博客我们来介绍下C语言的标准输入和输出。

2.标准输入/输出

标准库实现了简单的文本输入/输出模式。文本流由一系列行组成,每一行的结尾是一个换行符。如果系统没有遵循这种模式,则标准库将通过一些措施使得适应这种模式。

最简单的输入机制是使用getchar函数从标准输入中一次读取一个字符:

int getchar(void)

getchar函数在每次被调用时返回下一个输入字符。若遇到文件结尾,则返回EOF。符号常量EOF在头文件<stdio.h>中定义,其值一般为-1,但程序中应该使用EOF来测试文件是否结束,这样才能保证程序同EOF的特定值无关。

函数

int putchar(int)

用于输出数据。putchar©将字符c送至标准输出上,在默认情况下,标准输出为屏幕显示。如果没有发生错误,则函数putchar将返回输出的字符;如果发生了错误,则返回EOF。同样,通常的情况下,也可以使用">输出文件名"的格式将输出重定向到某个文件中。

我们看如下的程序,具体的如下:

#include <stdio.h>
#include <ctype.h>
main() /* lower: convert input to lower case*/
{
  int c;
  while ((c = getchar()) != EOF)
    putchar(tolower(c));
  return 0;
}

上面的程序就是一个简单的文件的输入和输出的功能,通过从控制台输入数据,然后输出到控制台,同时这些数据已经是转小写了。其他的字母都是原样的输出。

3.格式化输出-printf函数

格式字符串包含两种类型的对象:普通字符和转换说明。在输出时,普通股字符将原样不动地复制到输出流中,而转换说明并不直接输出到输出流中,而是用于控制printf中参数的转换和打印,每个转换说明都由一个百分号字符(%)开始,并以一个转换字符结果。在字符%和转换字符中间可能依次包含下列组成的部分:

  • 负号,用于指定被转换的参数按照左对齐的形式输出。
  • 数,用于指定最小字段宽度。转换后的参数将打印不小于最小字段宽度的字段。
  • 小数点,用于将字段宽度和精度分开。
  • 数,用于指定精度,即指定字符串中要打印的最大字符数、浮点数小数点后的位数、整型最少输出的数字的数目
  • 字母h或l,字母h表不将整数作为short类型打印,字母l表示将整数作为long的类型打印。

printf函数基本的转换说明

字符参数类型:输出形式
d,iint类型:十进制数
oint类型:无符号八进制
x,Xint类型:无符号十六进制数
uint类型:无符号十进制数
cint类型:单个字符
schar *类型,顺序打印字符串中的字符,直到’\0’或已打印了由精度指定的字符数为止
fdouble类型:十进制小数
e,Edouble类型
g,Gdouble类型
pvoid *类型,指针
%不转换参数:打印一个百分号%

函数sprintf执行的转换和函数printf相同,但它将输出保存到一个字符串中

int sprintf(char *string, char *format, arg1, arg2, ...);

sprintf函数和printf函数一样,按照format格式格式化参数序列arg1、arg2、…,但它将输出的结果存放到string中,而不是输出到标准输出中。

4.变长参数表

这节我们主要看下对应的printf函数我们自己的实现,具体的如下:

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

/* minprintf: minimal printf with variable argument list */
void minprintf(char *fmt, ...) {
    va_list ap; /* points to each unnamed arg in turn */
    char *p, *sval;
    int ival;
    double dval;
    va_start(ap, fmt); /* make ap point to 1st unnamed arg */
    for (p = fmt; *p; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }
        switch (*++p) {
            case 'd':
                ival = va_arg(ap, int);
                printf("%d", ival);
                break;
            case 'f':
                dval = va_arg(ap, double);
                printf("%f", dval);
                break;
            case 's':
                for (sval = va_arg(ap, char *); *sval; sval++)
                    putchar(*sval);
                break;
            default:
                putchar(*p);
                break;
        }
    }
    va_end(ap); /* clean up when done */
}

其中,省略号表示参数表中参数的数量和类型是可变的。省略号只能出现在参数表的尾部。

va_list类型用于声明一个变量,该变量依次引用各参数。在函数minprintf中,我们将该变量称为ap,意思是参数指针。宏va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须被调用一次,参数表必须至少包含一个有名参数,va_start将最后一个有名参数作为起点。

每次调用va_arg,该函数都将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。最后,必须在函数返回之前调用va_end,以完成一些必要的清理工作。

5.格式化输入-scanf函数

scanf函数从标准输入中读取字符序列,按照format中的格式说明对字符序列进行解释,并把结果保存到其余的参数中。

当scanf函数扫描完其格式串,或者碰到某些输入无法与格式控制说明匹配的情况时,该函数将终止,同时,成功匹配并赋值的输入项的个数将作为函数值返回,所以,该函数的返回值可以用来确定已匹配的输入项的个数。如果到达文件的结尾,该函数将返回EOF。

另外还有一个输入函数sscanf,它用于从一个字符串(而不是标准输入)中读取字符序列:

int sscanf(char *string, char *format, arg1, arg2, ...)

它按照格式参数format中规定的格式扫描字符串string,并把结果分别保存到arg1、arg2这些参数中,这些参数必须是指针。具体的格式如下:

字符参数类型:输出形式
dint类型:十进制数
i整型:int *类型,可以是八进制或十六进制
oint类型:无符号八进制
uint类型:无符号十进制数
xint类型:无符号十六进制数
c字符:char *类型,将接下来的多个输入字符(默认为1个字符)存放到指定位置,该转换规范通常不跳过空白符。如果需要读入下一个非空白符,可以使用%1s
s字符串(不加引号)char *类型,指向一个足以存放该字符串(还包括尾部的字符’\0’)的字符数组,字符串的末尾将被添加一个结束符’\0’
e,f,g浮点数,它可以包括正负号 小数点 指数部分 float* 类型
%字符%:不进行任何赋值操作

6.文件访问

文件指针,它指向一个包含文件信息的结构,这些信息包括:缓冲区的位置、缓冲区中当前字符的位置、文件的读或写状态、是否出错或是否已经到达文件结尾。

打开一个文件操作如下:

fp = fopen(name, mode);

fopen的第一个参数是一个字符串,它包含文件名。第二个参数是访问模式,也是一个字符串,用于指定文件的使用方式。允许的模式包括:读(“r”)、写(“w”)以及追加(“a”)。某些系统还区分文本文件和二进制文件,对后者的访问需要在模式字符串中增加字符" b";

如果打开一个不存在的文件用于写或追加啊,改文件将会被创建。当以写方式打开一个已存在的文件时,该文件原来的内容将被覆盖。但是,如果以追加方式打开一个文件,则改文件原来的内容将保持不变。

我们看下如下的代码:

#include <stdio.h>

/* cat:  concatenate files, version 1 */
main(int argc, char *argv[]) {
    FILE *fp;
    void filecopy(FILE *, FILE *);
    if (argc == 1) /* no args; copy standard input */
        filecopy(stdin, stdout);
    else
        while (--argc > 0)
            if ((fp = fopen(*++argv, "r")) == NULL) {
                printf("cat: can't open %s\n, *argv");
                return 1;
            } else {
                filecopy(fp, stdout);
                fclose(fp);
            }
    return 0;
}

/* filecopy:  copy file ifp to file ofp */
void filecopy(FILE *ifp, FILE *ofp) {
    int c;
    while ((c = getc(ifp)) != EOF)
        putc(c, ofp);
}

文件指针stdin与stdout都是FILE * 类型的对象。但它们是常量,而非变量。因此不能对它们赋值。

7.错误处理-stderr和exit

cat程序的错误处理功能并不完善。问题在于,如果因为某种原因而造成其中的一个文件无法访问,相应的诊断信息要在该连接的输出的末尾才能打印出来。当输出到屏幕时,这种处理方法尚可接受,但如果输出到一个文件或通过管道输出到另一个程序时,就无法接受了。

于是我们修改了如下的程序,具体的如下:

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

void exit(int i);

/* cat:  concatenate files, version 2 */
main(int argc, char *argv[]) {
    FILE *fp;
    void filecopy(FILE *, FILE *);
    char *prog = argv[0];  /* program name for errors */
    if (argc == 1) /* no args; copy standard input */
        filecopy(stdin, stdout);
    else
        while (--argc > 0)
            if ((fp = fopen(*++argv, "r")) == NULL) {
                fprintf(stderr, "%s: can't open %s\n", prog, *argv);
                exit(1);
            } else {
                filecopy(fp, stdout);
                fclose(fp);
            }
    if (ferror(stdout)) {
        fprintf(stderr, "%s: error writing stdout\n", prog);
        exit(2);
    }
    exit(0);
}

该程序通过两种方式发出出错信息。首先,将fprintf函数产生的诊断信息输出到stderr上,因此诊断信息将会显示在屏幕上,而不是仅仅输出到管道或输出文件中。

其次,程序使用了标准库函数exit,当函数被调用时,它将终止调用程序的执行。

8.行输入和行输出

我们直接看对应的代码,具体的如下:

/* fgets:  get at most n chars from iop */
char *fgets(char *s, int n, FILE *iop)
{
  register int c;
  register char *cs;
  cs = s;
  while (--n > 0 && (c = getc(iop)) != EOF)
    if ((*cs++ = c) == '\n')
      break;
  *cs = '\0';
  return (c == EOF && cs == s) ? NULL : s;
}

/* fputs:  put string s on file iop */
int fputs(char *s, FILE *iop)
{
  int c;
  while (c = *s++)
    putc(c, iop);
  return ferror(iop) ? EOF : 0;
}

标准的输入函数fgets,它和getline函数类似。

9.其它函数

9.1字符串操作函数

如下几个函数在<string.h>中定义

  • strcat(s,t) 将t指向的字符串连接到s指向的字符串的末尾。
  • strncat(s,t,n) 将t指向的字符串中前n个字符连接到s指向的字符串的末尾。
  • strcmp(s,t) 根据s指向的字符串小于(s<t)、等于(s==t)或大于(s>t) t指向的字符串的不同情况,分别返回负整数、0或正整数。
  • strcnmp(s,t,n) 同strcmp相同,但只在前n个字符中比较。
  • strcpy(s,t) 将t指向的字符串复制到s指向的位置。
  • strncpy(s, t, n) 将t指向的字符串中前n个字符复制到s指向的位置。
  • strlen(s) 返回s指向的字符串的长度
  • strchr(s,c) 在s指向的字符串中查找c,若找到,则返回指向它第一次出现的位置的指针,否则返回NULL
  • strrchr(s,c) 在s指向的字符串中查找c,若找到,则返回指向它最后一次出现的位置的指针,否则返回NULL

9.2字符类别测试和转换函数

如下几个函数在<ctype.h>中定义

  • isapha© 若c是字母,则返回一个非0值,否则返回0
  • isupper© 若c是大写字母,则返回一个非0值,否则返回0
  • islower© 若c是小写字母,则返回一个非0值,否则返回0
  • isdigit© 若c是数组,则返回一个非0值,否则返回0
  • isalnum© 若isalpha©或isdigit©,则返回一个非0值,否则返回0
  • isspace© 若c是空格、横向制表符、换行符、回车符、换页符或纵向制表符 则返回一个非0值
  • toupper© 返回c的大写形式
  • tolower© 返回c的小写形式

9.3ungetc函数

函数的格式:

int ungetc(int c, FILE *fp)

该函数将字符c写回到文件fp中,如果执行成功,则返回c,否则返回EOF。每个文件只能接收一个写回字符。ungetc函数可以和任何一个输入函数一起使用。

9.4命令执行函数

函数system(char* s)执行包含在字符串s中的命令。然后继续执行当前程序。

9.5存储管理函数

函数malloccalloc用于动态地分配存储块。具体的如下

void *malloc(size_t n)

当分配成功时,它返回一个指针,设指针指向n字节长度的未初始化的存储空间,否则返回NULL。

void *calloc(size_t n, size_t size)

当分配成功时,它返回一个指针,该指针指向的空闲空间足以容纳由n个指定长度的对象组成的数组。否则返回NULL。该存储空间被初始化为0。

free§函数释放p指向的存储空间,其中,p是此前通过malloc或calloc函数得到的指针

9.6数学函数

如下几个函数在<math.h>中定义

  • sin(x) x的正弦函数,其中x用弧度表示
  • cos(x) x的余弦函数,其中x用弧度表示
  • atan2(y,x) y/x的反正切函数,其中,x和y用弧度表示
  • exp(x) 指数函数ex
  • log(x) x的自然对数
  • log10(x) x的常数对数
  • pow(x,y) 计算x的y次方的值
  • sqrt(x) x的平方根
  • fabs(x) x的绝对值

9.7随机数发生器函数

函数rand()生成介于0和RAND_MAX之间的伪随机整数序列。其中RAND_MAX是在头文件<stdlib.h>中定义的符号常量。下面是一种生成大于等于0但小于1的随机浮点数的方法:

#define frand() ((double) rand() / (RAND_MAx + 1.0))

10.写在最后

本篇博客主要介绍了C语言的标准的输入输出的功能,后面还有一章主要介绍UNIX的系统接口。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值