在牛客网做C语言题目时,往往第一步卡脖子的地方就是输入问题,比如:
按行读取数值,以0结束;
按一次性输入读取字符串(中间包含空格)
等等问题,发现基础很重要,干脆整理下,以便后面参考。
Makefile
每个C源文件都编译成对应的.o文件和目标程序
# File paths
SRC_DIR := .
BUILD_DIR := .
OBJ_DIR := $(BUILD_DIR)
# Compilation flags
CC := gcc
LD := gcc
CFLAGS := -Wall
# Files to be compiled
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
BUILD := $(OBJS:$(OBJ_DIR)/%.o=$(BUILD_DIR)/%)
# Don't remove *.o files automatically
.SECONDARY: $(OBJS)
all: $(BUILD)
# Compile each *.c file as *.o files
# @mkdir -p $(OBJ_DIR)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
@echo + CC $<
@$(CC) $(CFLAGS) -c -o $@ $<
# Link each *.o file as executable files
# @mkdir -p $(BUILD_DIR)
$(BUILD_DIR)/%: $(OBJ_DIR)/%.o
@echo + LD $@
@$(LD) $(CFLAGS) -o $@ $<
.PHONY: all clean
clean:
rm -rvf $(OBJS) $(BUILD)
一、输入和输出
1.1 perror()、exit()函数
man手册:perror()用于输出系统调用的错误信息
头文件:#include <stdio.h>
用法:void perror(const char *s)
返回值: 无
NAME
perror - print a system error message
SYNOPSIS
#include <stdio.h>
void perror(const char *s);
#include <errno.h>
const char * const sys_errlist[];
int sys_nerr;
int errno; /* Not really declared this way; see errno(3) */
exit()函数用于退出程序:
头文件:#include <stdlib.h>
用法:void exit(int status)
,其返回给父进程的值是status & 0xFF
返回值:无
比如下面这个代码,打开一个不存在的文件“test.txt",系统error
变量会记录错误信息:“No such file or director”。
//cat test_perror.c:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = NULL;
fp = fopen("test.txt", "r");
if(!fp)
{
perror("This Error Message");
exit(-1);
}
fclose(fp);
fp = NULL;
return 0;
}
执行结果:
jrg@hygon-RG-CS6020ES:test$ ./test_perror
This Error Message: No such file or directory
jrg@hygon-RG-CS6020ES:test$ echo $?
255
jrg@hygon-RG-CS6020ES:test$
1.2 流
stream分为两种:
stream |
---|
文本流 |
二进制流 |
I/O函数以基本的三种形式处理数据:
下表列出了用于各种I/O的函数或函数家族( 斜体 表示函数家族,它指一组函数中每个都执行相同的基本任务,形式有所不同)
数据类型 | 输入 | 输出 | 描述 |
---|---|---|---|
单个字符 | getchar | putchar | 单个字符I/O |
文本行 | gets scanf | puts printf | 未格式化I/O 格式化I/O |
二进制数据 | fread | fwrite | 二进制I/O |
为何会需要I/O函数家族呢,比如 scanf 家族,因为需要满足不同的需求,主要有三个不同需求:
- 只用于stdin或stdout
- 随作为参数的流stream使用
- 使用内存中的字符串而不是流stream
家族名 | 目的 | 可用于所有的流stream | 只用于stdin和stdout | 内存中的字符串 |
---|---|---|---|---|
getchar | 单个字符输入 | fgetc, getc | getchar | 对指针使用下标引用或间接访问操作从内存获得一个字符 |
putchar | 单个字符输出 | fputc, putc | putchar | 对指针使用下标引用或间接访问操作向内存写入一个字符 |
gets | 文本行输入 | fgets | gets | 使用strcpy从内存读取文本行 |
puts | 文本行输出 | fputs | puts | 使用strcpy向内存写入文本行 |
scanf | 格式化输入 | fscanf | scanf | sscanf |
printf | 格式化输出 | fprintf | printf | sprintf |
1.2.1 fopen打开流
fopen家族,打开一个文件
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *pathname, const char *mode, FILE *stream);
试图先关闭stream流,再重新打开pathname指向的文件,一般用于重定向stdin/stdou/stderr等;
mode:
模式 | 含义 |
---|---|
r | O_RDONLY |
w | O_WRONLY | O_CREATE | O_TRUNC(若文件存在,则长度被截为0,属性不变) |
a | O_WRONLY | O_CREATE | O_APPEND |
r+ | O_RDWR |
w+ | O_RDWR | O_CREATE | O_TRUNC |
a+ | O_RDWR | O_CREATE | O_APPEND |
1.2.2 fclose关闭流
头文件: #include<stdio.h>
用法: int fclose(FILE *stream)
返回值:成功返回0,否则返回EOF并置errno
fclose()按理也需要检查执行成功与否的,因为可能在调用fclose之前,steam已经被改掉了或者破坏了,会导致fclose失败
1.3 字符I/O
1.3.1 输入
fetc, getc, getchar, ungetc
NAME
fgetc, getc, getchar, ungetc - input of characters and strings
SYNOPSIS
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
int ungetc(int c, FILE *stream);
getchar从标准输入读取单个字符,一个一个字符读取
#include <stdio.h>
int main(int argc, char **argv)
{
char ch;
while((ch = getchar()) != ' ')
{
printf("get ch: %c, 0x%02X\n", ch, ch);
}
return 0;
}
输出:
jrg@hygon-RG-CS6020ES:test$ ./test_input
1 #输入1加换行,输出了1以及换行符,也就是说getchar读取了换行符
get ch: 1, 0x31
get ch:
, 0x0A
#只输入换行符
get ch:
, 0x0A
a #输入字母a以及换行符
get ch: a, 0x61
get ch:
, 0x0A
#输入了一个空格,程序结束
jrg@hygon-RG-CS6020ES:test$
1.3.2 输出
NAME
fputc, putc, putchar - output of characters and strings
SYNOPSIS
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
将上面的程序稍微修改:
jrg@hygon-RG-CS6020ES:test$ cat test_input.c
#include <stdio.h>
int main(int argc, char **argv)
{
char ch;
while((ch = getchar()) != ' ')
{
putchar(ch); ##加了一行putchar,将字符输出到标准输出
printf("get ch: %c, 0x%02X\n", ch, ch);
}
return 0;
}
jrg@hygon-RG-CS6020ES:test$ ./test_input
abcd ##输入abcd以及换行,getchar按字符单个读取
aget ch: a, 0x61 ##putchar会输出字符
bget ch: b, 0x62
cget ch: c, 0x63
dget ch: d, 0x64
##此处getchar读取到\n,然后putchar又输出\n,所以此处换行了
get ch:
, 0x0A
##输入空格(' '),程序结束
jrg@hygon-RG-CS6020ES:test$
1.4 未格式化的行I/O
gets和puts常用语操作字符串
1.4.1 gets输入
SYNOPSIS
#include <stdio.h>
char *gets(char *s);
char *fgets(char *s, int size, FILE *stream);
gets() reads a line from stdin into the buffer pointed to by s until either a terminating newline or EOF, which it replaces with a null byte (’\0’).
gets读取以换行符或者EOF为结束标志。
1.4.2 puts输出
SYNOPSIS
#include <stdio.h>
int fputs(const char *s, FILE *stream);
int puts(const char *s);
jrg@hygon-RG-CS6020ES:test$ cat test_gets.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char s[200] = {0};
if(gets(s)) ##按行读取
{
puts(s); ##输出读取的内容
}
else
{
perror("gets failed");
exit(-1);
}
return 0;
}
jrg@hygon-RG-CS6020ES:test$
jrg@hygon-RG-CS6020ES:test$ ./test_gets
asdfasd asdfasdfsdfs 12 43 asd 234
asdfasd asdfasdfsdfs 12 43 asd 234
jrg@hygon-RG-CS6020ES:test$
1.5 格式化的行I/O
1.5.1 输入scanf
scanf格式码:scanf接收读取值的参数是地址
代码 | 参数 | 含义 |
---|---|---|
c | char * | 读取和存储单个字符 |
i d | int * | 可选的有符号整数被转换,d把输入的解释为十进制;i根据它的第1个字符决定值的基数 |
u o x | unsigned * | u: 十进制数 o: 八进制数 x: 十六进制数,X和x相同 |
e f g | float * | 浮点值,E和G分别与e和g相同 |
s | char * | 非空白字符串,字符串后面会自动加上NUL结束符’\0’ |
p | void * | 输入预期为一串字符 |
1.5.2 输出printf
代码 | 参数 | 含义 |
---|---|---|
c | int | 参数被裁减为unsigned char 类型并作为字符打印 |
d i | int | 参数作为十进制整数打印 |
u o x,X | unsigned int | u: 十进制 o:八进制 x:十六进制小写abcdef, X:十六进制大写ABCDEF |
e E | double | 参数根据指数形式打印,比如,6.023000e23用e打印,6.023000E23用E打印,小数点后面的位数有精度字段决定,缺失值是6 |
f | double | 参照浮点格式打印,缺省精度6 |
s | char * | 打印字符串 |
p | void * | 指针 |