一、标准输入、输出
1. 简单的输入\输出机制
- 从标准输入中一次读取一个字符:
int getchar(void)
- 将字符c送到标准输出中:
int putchar(int)
2. 输入重定向
- 如果程序prog中使用了getchar函数,那么
prog < infile
,将使得程序prog从输入文件infile中读取字符。 - 如果输入通过管道来自另一个程序,那么这种输入切换也是不可见的。如命令
otherprog | prog
将程序的otherprog的标准输出通过管道重定向到程序prog的标准输入中。
3. 输出重定向
- 如果程序prog中使用了putchar函数,那么
prog > file
, 将使得程序prog的标准输出重定向到file文件。 - 如果系统支持管道,那么
prog | anotherprog
将把程序的标准输出通过管道重定向到程序anotherprog的标准输入中。
二、格式化输出——printf函数
1. 完整形式
int printf(char* format, arg1, arg2, arg3,....);
- '-',负号。指定被转换的参数按左对齐的形式输出。
- 数(小数点之前)。指定最小字段宽度。字符长度不足,则填充空格。
- 小数点。将字段宽度和精度分开。
- 数(小数点之后)。指定字符串中要打印的最大字符数、浮点数小数点后的位数、整形最少输出的数字数目。
2. 基本的转换说明
字符 | 输出形式 |
---|---|
d,i | int,十进制数 |
o | int,无符号八进制数 |
x | int,无符号十六进制数 |
u | int,无符号十进制数 |
c | int类型,单个字符 |
s | char* 类型,直到'\0'或打印了精度指定的字符数 |
f | double类型,十进制小数,[-]m.dddddd ,d个数由精度指定 |
e,E | double类型,[-]m.ddddd E [+-] xxx ,d个数由精度指定 |
g,G | double类型,如果指数小于-4或大于等于精度,则使用%e、%E输出,否则用%f输出 |
p | void* 类型;指针 |
int main() {
double a = 100;
printf("%e\n", a);
return 0;
}
输出为:
1.000000e+002
3. 字符串输出的精度控制
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char* s = "hello, world"; //12 char
printf("-%s-\n",s); //用-指示宽度边界
printf("-%10s-\n",s);
printf("-%.10s-\n",s);
printf("-%-10s-\n",s); // - 左对齐
printf("-%15s-\n",s);
printf("-%-15s-\n",s);
printf("-%15.10s-\n",s);
printf("-%-15.10s-\n",s);
int max = 5;
printf("-%.*s-\n", max, s); //宽度或精度可通过星号*表示,其值可通过转换下一参数(必须为int类型)来计算。
return 0;
}
输出
-hello, world-
-hello, world-
-hello, wor-
-hello, world-
- hello, world-
-hello, world -
- hello, wor-
-hello, wor -
-hello-
4. sprintf函数
int sprintf(char* string, char* format, arg1, arg2, ...);
按照format格式格式化序列参数arg1,arg2,...,将输出结果存放到string中。string的空间需要大到足以容纳结果。
三、 变长参数表
1. 几个va_宏定义
- va_list :
va_list ap;
用于声明一个变量ap(arg pointer),依次引用各参数。 - va_start:
va_start(ap, namedArg);
将ap初始化为第一个无名参数的指针。使用ap之前,该宏必须被调用依次。参数表必须至少包含一个又名参数,va_start将最后一个有名参数作为起点。 - va_arg:
va_arg(ap, int)
该函数将返回一个参数,并将ap指向下一个参数。va_arg使用一个类型名来决定返回的对象类型、指针移动的步长。 - va_end:
va_end(ap);
最后在函数返回之前,需调用va_end来完成一些必要的清理工作。
2. 实现一个miniprintf
void minprintf(char* fmt, ...){
va_list ap;
char* p, *sval;
int ival;
double dval;
va_start(ap, fmt);
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);
}
3. 实现一个可变参数累加函数
long long int sum(int num, ...)
其中num为累加整数的数量。
#include <stdio.h>
#include <stdarg.h>
long long int sum(int num, ...){
long long int result = 0;
va_list ap;
va_start(ap, num);
while(num--){
result += va_arg(ap, int);
}
va_end(ap);
return result;
}
int main() {
printf("%lld", sum(3,2,3,4));
return 0;
}
输出为:
9
四、格式化输入——scanf函数
scanf
和sscanf
的所有参数都必须是指针。
1. scanf: 从标准输入中读取字符序列。
int scanf(char* format, ...)
-
其返回值可以用来确定已匹配的输入项的个数。
-
如果达到文件的末尾,返回EOF。
-
如果返回0, 表示下一个输入字符与格式串中的第一个格式说明不匹配。
2. sscanf: 从一个字符串而不是标准输入中读取字符序列。
int sscanf(char* string, char* format, arg1, arg2...)
按照格式参数format
中规定的格式扫描字符串string
,并把结果保存到arg1, arg2, ...
五、文件访问
1. 文件指针:
- 在读写一个文件之前,必须通过库函数fopen打开该文件。
fopen
利用文件名建立与操作系统必要的连接和通信,返回一个可用于文件读写操作的指针FILE*。它指向一个包含文件信息的结构,这些信息包括:缓冲区的位置、缓冲区当前字符的位置、文件的读写状态等、是否出错或是否已经达到文件末尾等。
FILE* fopen(char* name, char* mode);
- name是文件名,mode是访问模式,包括
读(r)
、写(w)
、追加(a)
。某些系统用b
来区分文本文件和二进制文件。
2. 对文件的读写方法
int getc(FILE* fp)
: 从文件指针fp指向的文件中中获取一个字符。
int putc(int c, FILE* fp)
: 向指针写入一个字符c
。
3. 启动c程序时,操作系统打开的文件
-
启动一个c语言程序时,操作系统负责打开三个文件,并将这三个文件的指针提供给该c程序。三个文件分别是标准输入、标准输出和标准错误,对应的指针为
stdin
、stdout
、stderr
,包含在<stdio.h>。 -
通常,
stdin
指向键盘、stdout
和stderr
指向显示器。 -
由前述可知,
stdin
、stdout
可以重定向到管道。
4. 文件的格式化输入、输出
可以使用fscanf()
、fprintf()
,比scanf()
和printf()
相比,多了一个文件指针参数FILE* fp
作为第一个参数。
- 格式化输入
int fscanf(FILE* fp, char* format, ...);
- 格式化输出
int fprintf(FILE* fp, char* format, ...);
- 实现简单的cat程序: 将多个文件的内容输出。
cat.c
#include <stdio.h>
void filecopy(FILE* ifp, FILE* ofp){
int c;
while((c = getc(ifp)) != EOF){
putc(c, ofp);
}
}
int main(int argc, char* argv[]) {
FILE* fp;
if(argc == 1){
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;
}
1.txt
hello
2.txt
world
编译运行:
gcc cat.c -o cat
./cat 1.txt 2.txt
输出:
hello
world
六、错误处理——stderr和exit
1. 刚刚实现的cat程序错误处理并不完善。
如果因为某种原因造成某个文件无法访问,相应的诊断信息要在输出末尾打印出来,输出到屏幕可以接受,但输出到另一个文件或重定向到另一个程序时,就无法接受了。为了更好地处理这种情况,另一个输出流stderr
以与stdin
和stdout
相同的方式分派给程序。即使对标准输出进行了重定向,写到stderr
的输出通常也会显示在屏幕上。
对cat程序进行修改,将错误信息写到标准错误文件中。
#include <stdio.h>
#include <stdlib.h>
void filecopy(FILE* ifp, FILE* ofp){
int c;
while((c = getc(ifp)) != EOF){
putc(c, ofp);
}
}
int main(int argc, char* argv[]) {
FILE* fp;
char* prog = argv[0];
if(argc == 1){
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);
}
2. fprintf()
函数将产生的诊断信息输出到stderr中
3. 使用exit()
函数终止调用程序的执行。
在主程序main中,语句return expr
相当于exit(expr)
。但是exit函数
的优点是可以在其他函数中调用。
4. ferror(FILE* fp)
函数
int ferror(FILE* fp)
如果流fp中出现错误,该函数将返回一个非0值。
5. feof(FILE* fp)
函数
如果指定的文件达到文件末尾,它将返回一个非0值。
七、行输入和行输出
1. fgets
char *fgets(char* line, int maxline, FILE* fp)
从fp指向的文件中读取下一个输入行,将它存在字符数组line中(以'\0'结尾),最多可读取maxline-1个字符。遇到文件结尾或发生错误,会返回NULL。
2. fputs
int fputs(char* line, FILE* fp)
将一个字符串line写入到fp指向的文件。发生错误时,返回EOF,否则返回一个非负值。
八、其它函数
1. 字符串
函数 | |
---|---|
strcat(s,t) | |
strcat(s, t, n) | |
srcmp(s, t) | |
strcmp(s, t, n) | |
strcpy(s, t) | |
strcpt(s, t, n) | |
strlen(s) | |
strchr(s, c) | 在字符串s中查找c,若找到返回第一次出现的位置的指针,否则返回null |
strrchr(s, c) | 在字符串s中查找c,若找到返回最后一次出现的位置的指针,否则返回null。(反向查找) |
2. 字符串测试和转换函数
函数 | |
---|---|
isalpha(c) | |
isupper(c) | |
islower(c) | |
isdigit(c) | |
isalnum(c) | |
isspace(c) | |
toupper(c) | |
tolower(c) |
3.ungetc函数
int ungetc(int c, FILE* fp)
将字符c写回到文件fp中。如果执行成功,返回c,否则返回EOF。
4.命令执行函数
system(char* s)
执行包含在字符串s中的命令,然后继续执行当前程序。比如system("date")
将执行程序date, 在标准输出中打印当前的日期和时间。又比如system("pause")
。
5.存储管理函数
函数malloc和calloc用于动态分配内存。
- malloc函数
void* malloc(size_t n)
, 分配成功时,返回一个指向n字节长度的未初始化的存储空间,否则返回NULL。
- calloc函数
void* calloc(size_t n, size_t size)
,分配成功时,该指针指向足以容纳由n个指定长度为size的对象组成的数组,否则返回NULL。
- 例子:
以下例子进行了类型转换。
int* ip;
ip = (int*)calloc(n, sizeof(int));
free(p)
函数释放p指向的空间,p必须是此前通过调用malloc和calloc函数得到的指针。
6. 数学函数
sin(x)
、cos(x)
、atan2(x)
、exp(x)
、log(x)
、 log10(x)
、pow(x, y)
、 sqrt(x)
、 fabs(x)
7.随机数发生器函数
rand()生成介于0和RAND_MAX之间伪随机整数序列。