目录
一、IO知识
1.IO种类
标准IO:库函数
文件IO:系统调用
2.系统调用
概念:从用户空间到内核空间的过程(内核给用户留了接口,系统调用就是用户调用接口函数的过程,最后由内核把数据写到硬件中)
用户空间每个进程是独立的
内核空间每个进程是共享的(内核空间物理映射映射到同一块空间)
可移植性差,没缓冲区,效率低(由于没有缓冲区,写入一次数据就要进行一次系统调 用,频繁的系统调用是很浪费时间的)
因为每个系统都有自己的函数接口,函数接口不同,就会导致不同系统上的程序运行不了
3.库函数
系统调用+缓冲区,可移植性强,效率高(不用频繁的)
先缓存数据,刷新以后写入硬盘
4.常见的标准IO和文件IO的函数接口
标准IO:printf scanf fopen fwrite fclose fgets fputs fgetc fputc....
文件IO:open read write close
二、标准IO
1.FILE指针
FILE是_IO_FILE的别名
那么_IO_FILE是啥呢?
_IO_FILE是一个结构体,当使用 fopen 函数打开一个文件的时候,就会产生一个FILE结构体,该结构体中保存着所有和当前文件相关的信息,后面再对文件进行IO操作的时候,使用的就是这个结构体。每个进程维护自己的FILE指针,即使多个进程打开了同一个文件,他们的FILE指针也不一样。
每个FILE指针里都有文件描述符。
每一个正在运行的程序都有三个已经打开且可以直接使用的FILE指针:
stdin stdout stderr(程序本身就是文件.c文件)
2.man手册使用说明
命令 man 1 ls
系统调用 man 2 open
库函数 man 3 sleep
3.fopen函数
函数原型:FILE *fopen(const char *pathname, const char *mode);
功能:以不同模式打开此路径下的文件
参数:带路径的文件名 模式
r:读方式打开,光标在文件开头。没有文件报错
r+:读写方式打开,光标在文件开头。没有文件报错
w:写方式打开,如果没有则创建,如果文件存在则清空。光标在文件开头
w+:读写方式打开,如果没有则创建,如果文件存在则清空,光标在文件开头
a:追加写方式打开,如果没有则创建,如果文件存在光标在文件末尾
a+:读写方式打开,读光标在文件开头,写光标在文件末尾
返回值:成功:文件指针(FILE指针)
失败: NULL 重置错误码
4.fclose函数
函数原型:int fclose(FILE *stream);
功能:关闭FILE指针指向的文件
参数:fopen产生的文件的FILE指针
返回值:成功:0
失败:EOF 重置错误码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(int argc, const char *argv[])
{
FILE *fp=NULL;
fp=fopen("test.c","r");//以读的方式打开文件
//FILE *fp=fopen("test.c","w");//以写的方式打开文件
f(NULL == fp){
printf("fopen error\n");
return -1;
}
fclose(fp);
printf("1111\n"); //有显示
fclose(stdin);
fclose(stdout);
fclose(stderr);
printf("2222\n"); //没有显示了 因为 stdout 标准输出已经被关闭了
return 0;
}
5.内核中错误码的问题
当用户调用系统接口调用失败的时候,内核会给调用者返回错误码,
调用者需要在自己程序中包含#include <errno.h>头文件,在这个头
文件中有一个errno的变量,这个变量就可以接到错误的数值。用户
就可以根据这个数据判断出来程序是因为什么原因失败的。
重置的是errno,调用函数出错时,会用宏定义里的错误信息重置errno,这样就可以知道出什么错误了
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
fp=fopen("test.c","r");//以读的方式打开一个不存在的文件
f(NULL == fp){
printf("error:%d",errno);
return -1;
}
return 0;
}
5.2将错误码转换成错误信息的描述的函数
函数原型:char *strerror(int errnum);
功能:将错误码转换成错误信息的描述
参数:errnum:错误码
返回值:错误信息描述的字符串
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
fp=fopen("test.c","r");//以读的方式打开一个不存在的文件
f(NULL == fp){
printf("error:%d",strerror(errno));
return -1;
}
return 0;
}
5.3 perror直接打印错误信息(重置错误码才可以用)
函数原型:void perror(const char *s);
功能: 重置错误码之后 可以使用perror直接输出错误信息
出错后 应该立即输出错误信息 否则后面可能就被覆盖了
参数: s:用户附加的错误信息
返回值:void
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
fp=fopen("test.c","r");//以读的方式打开一个不存在的文件
f(NULL == fp){
perror("fopen error");//perror会自动加冒号且自动换行
//printf("fopen error : %s\n", strerror(errno));相当于这一行
return -1;
}
return 0;
}
自定义头文件
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERRLOG(msg) do{\
printf("%s %s %d\n", __FILE__, __func__, __LINE__);\
perror(msg);\
exit(-1);\
}while(0)
#endif
需要将头文件放到 /usr/include 下 sudo mv head.h /usr/include
6.fgetc/fputc
fgetc
函数原型:int fgetc(FILE *stream);
功能:从文件获取一个字符
参数:文件的FILE指针
返回值:成功 读取的字符
失败或者文件结束 -1(EOF)
fputc
函数原型:int fputc(int c, FILE *stream);
功能:向文件中写入一个字符
参数:c:要写入的字符
stream:文件指针
返回值:成功 写入的字符
失败或者文件结束 -1(EOF)
练习:
使用 fgetc 统计文件有多少行
要求 ./a.out 文件名
int count=0;
FILE *fp=NULL;
fp=fopen(argv[1],"r");
if(NULL==fp){
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
perror("fopen error");
exit(-1);
}
char c=0;
while((c=(fgetc(fp)))!=EOF){
if(c=='\n'){
count++;
}
}
//不接不行
/*while(fgetc(fp)!=EOF){
if(fgetc(fp)=='\n'){//这一步的fgetc(fp)和上一步的已经不一样了,光标又向后移了一个
count++;
}
}*/
printf("LINE=%d\n",count);
fclose(fp);
练习:
使用 fgetc/fputc 实现文件拷贝的功能
要求 ./a.out src_file dest_file
FILE *fp1=fopen(argv[1],"r");
if(NULL==fp1){
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
perror("fopen error");
exit(-1);
}
FILE *fp2=fopen(argv[2],"w");
if(NULL==fp2){
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
perror("fopen error");
exit(-1);
}
char c=0;
while((c=(fgetc(fp1)))!=EOF){
fputc(c,fp2);
}
fclose(fp1);
fclose(fp2);
7.fgets/fputs
fgets
函数原型:char *fgets(char *s, int size, FILE *stream);
功能:从文件获取一个字符串
最多读取size-1个字符放到s指向的缓冲区中
缓冲区满之前遇到换行符或者EOF停止获取,并在获取的字符串后面加一个'\0'
或者缓冲区满结束,读到size-1个字符结束,后面有一个'\0'
参数: s:缓冲区首地址
size:缓冲区大小
stream:文件的FILE指针
返回值:成功 s
失败或者文件结束 NULL
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc,const char * argv[])
{
//在终端获取字符串
char buff[6]={0};
fgets(buff,sizeof(buff),stdin);
//清理终端每次输入的\n,数组从零开始计数,所以在长度-1的位置换
buff[strlen(buff)-1]='\0';
printf("[%s]",buff);
//在文件获取字符串
FILE *fp=("test.c","r");
fgets(buff,sizeof(buff),fp);//一行没读完,就到size了,就只能读size-1个
//如果遇到\n了,就从上一次的size开始读,读到\n为止
printf("[%s]",buff);
return 0;
}
文件内容:
第一次读到hello+space(空格),第二次读到world
fputs
函数原型:int fputs(const char *s, FILE *stream);
功能:向文件中写入一个字符串
参数:s:要写入的字符串的首地址
stream:文件指针
返回值:成功 大于等于0的数
失败 EOF
int main(int argc,const char * argv[])
{
FILE *fp = fopen("hello.txt", "w");
if(NULL == fp){
perror("fopen error");
exit(-1);
}
//将内容写入文件
fputs("HELLO", fp);
char buff[32] = "aabbccdd";
fputs(buff, fp);
//将内容写入终端
fputs("HELLO", stdout);
fputs(buff, stderr);
fclose(fp);
return 0;
}
练习:
1.使用 fgets 判断文件有多少行;
int main(int argc, const char *argv[])
{
FILE *fp=fopen(argv[1],"r");
char buff[8]={0};
int count=0;
//char *p=NULL;
//while(fgets(buff,sizeof(buff),fp)!=NULL)
while(NULL != fgets(buff, sizeof(buff), fp)){
if(strlen(buff)<(sizeof(buff)-1)){//fgets读到八个,但是第八个是'\0',strlen不计入长度,所以要判断strlen(buff)是否小于8-1
count++;
printf("11111\n");
}
else if(strlen(buff)==(sizeof(buff)-1)){
if(strcmp(&(buff[strlen(buff)-1]),"\n")){
count++;
printf("2222\n");
}
}
printf("3333\n");
}
printf("%d\n",count);
fclose(fp);
return 0;
}
简单思路:
只要'\0'前一个字符是'\n'就让行号自加1。
int main(int argc, const char *argv[])
{
FILE *fp=fopen(argv[1],"r");
char buff[8]={0};
int count=0;
//char *p=NULL;
//while(fgets(buff,sizeof(buff),fp)!=NULL)
while(NULL != fgets(buff, sizeof(buff), fp)){
if(buff[strlen(buff)-1])){
count++;
}
printf("%d\n",count);
fclose(fp);
return 0;
}
2.使用 fgets/fputs 实现文件的拷贝
int main(int argc,const char * argv[])
{
if(3 != argc){
printf("Usage : %s src_file dest_file\n", argv[0]);
exit(-1);
}
FILE *fp_src = fopen(argv[1], "r");
if(NULL == fp_src)
ERRLOG("fopen error");
FILE *fp_dest = fopen(argv[2], "w");
if(NULL == fp_dest)
ERRLOG("fopen error");
char buff[64] = {0};
while(NULL != fgets(buff, sizeof(buff), fp_src)){
fputs(buff, fp_dest);
}
fclose(fp_src);
fclose(fp_dest);
return 0;
}
8.标准IO的缓冲区
8.1 缓冲区的类型
行缓冲:1024 和终端相关的stdin stdout printf
全缓冲:4096 和文件相关的
无缓冲:stderr 缓冲区大小是 0
8.2 缓冲区的大小
(1)行缓冲
nt main(int argc,const char * argv[])
{
printf("hello\n");//需要先使用一下 才能看到现象
printf("stdout buff size = [%ld]\n",
stdout->_IO_buf_end - stdout->_IO_buf_base);//1024
int a = 0;
scanf("%d", &a);
printf("stdin buff size = [%ld]\n",
stdin->_IO_buf_end - stdin->_IO_buf_base);//1024
perror("test:");
printf("stderr buff size = [%ld]\n",
stderr->_IO_buf_end - stderr->_IO_buf_base);//0
return 0;
}
(2)全缓冲
FILE *fp=fopen("t.c","w");
fputs("hello",fp);//需要使用一下,用fputs
printf("全缓冲:%ld\n",fp->_IO_buf_end-fp->_IO_buf_base);
fclose(fp);
(3)无缓冲
标准出错,测试出来的结果是1,但是向标准出错中写1字节,
这个字节的数据会立马写到终端上,所以认为标准出错没有缓冲
fputs("test stderr buffer size\n", stderr);
printf("stderr size = %ld\n", stderr->_IO_buf_end - stderr->_IO_buf_base);
(4)全缓冲
文件相关缓冲区,大小是4K
FILE* fp;
if ((fp = fopen("./hello.txt", "w+")) == NULL)
PRINT_ERR("fopen error");
fputc('t',fp);
printf("fp size = %ld\n", fp->_IO_buf_end - fp->_IO_buf_base);
fclose(fp);
8.3缓冲区刷新的时机
行缓冲
1.程序结束
//程序结束 会刷新 printf("hello");
2."\n"
//遇到换行符 \n 会刷新 printf("hello\n"); while(1);
3.输入输出切换
//输入(scanf)和输出(printf)切换时会刷新 printf("hello"); int a = 0; scanf("%d", &a);
4.fflush
//手动调用fflush函数刷新 printf("hello"); fflush(stdout); while(1);
5.关闭文件
//关闭文件时也会刷新 printf("hello"); fclose(stdout); while(1);
6.缓冲区满
//缓冲区满的时候会刷新 int i = 0; for(i = 0; i < 1025; i++){ fputc('H', stdout); } while(1);
全缓冲
1.程序结束
//程序结束会刷新 ctrl+c 结束 不会刷新 fputs("hello\n", fp);
2.关闭文件
//关闭文件会刷新 fputs("hello\n", fp); fclose(fp); while(1);
3.fflush
//手动调用fflush函数刷新 fputs("hello\n", fp); fflush(fp); while(1);
4.输入输出切换
//输入输出切换会刷新 fputs("hello\n", fp); char buff[10]; fgets(buff, 10, fp); while(1);
5.缓冲区满
int i = 0; for(i = 0; i < 4097; i++){ fputc('H', fp); } while(1);
几种等价写法
//几种等价写法
printf("hi\n");
fprintf(stdout,"hi");
int num = 10;
printf("num = %d\n", num);
fprintf(stdout, "num = %d\n", num);
fprintf(stderr, "num = %d\n", num);
//向文件输出格式化的字符串:
FILE *fp = fopen("filename", "a");
fprintf(fp, "num = %d\n", num);
9.获取系统时间
函数原型:time_t time(time_t *tloc);
功能:获取 1970-01-01 00:00:00 距今的秒数
参数和返回值都是获取的结果
两种方式均可
time_t tm=0; //time(&tm); tm=time(NULL); printf("time:%ld\n",tm);
获取实际时间
函数原型:struct tm *localtime(const time_t *timep);
功能:将time() 获取的时间转换为现在实际时间
参数:time() 获取的秒数
返回值:时间结构体首地址
重置错误码
struct tm {
int tm_sec; /* 秒 (0-60) */
int tm_min; /* 分钟 (0-59) */
int tm_hour; /* 小时 (0-23) */
int tm_mday; /* 一个月中的第几天 (1-31) */
int tm_mon; /* 月 (0-11) */ +1
int tm_year; /* 年 - 1900 */ 使用时得 + 1900
int tm_wday; /* 周几 (0-6, Sunday = 0) */
int tm_yday; /* 一年中的第几天 (0-365, 1 Jan = 0) */
nt tm_isdst; /* 夏令时 */
}
int main(int argc, char const *argv[]) { time_t tm=0; //time(&tm); tm=time(NULL); printf("time:%ld\n",tm); //有返回值,用指针接 struct tm *t=localtime(tm); printf("%d年%d月%d日 %2d:%2d:%2d",t->tm_year+1900,t->tm_mon+1, t->tm_mday+1,t->tm_hour,t->tm_min,t->tm_sec); return 0; }
作业:
把系统时间写入文件中
要求:支持断点续写
用sleep没问题
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
//把系统时间写入文件中
//要求:支持断点续写
/*if(argc!=2){
printf("Usage : %s src_file dest_file\n", argv[0]);
exit(-1);
}*/
FILE *fp=fopen("DateLOG.txt","a+");
if(NULL == fp){
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
perror("open error");
exit(-1);
}
time_t tmold=0;
time_t tnew=0;
struct tm *t=NULL;
char buff[10]={0};
char c=0;
int count=0;
while(NULL!=fgets(buff,sizeof(buff),fp)){
if(strlen(buff)<(sizeof(buff)-1)){//fgets读到八个,但是第八个是'\0',strlen不计入长度,所以要判断strlen(buff)是否小于8-1
count++;
}
else if(strlen(buff)==(sizeof(buff)-1)){
if(strcmp(&(buff[strlen(buff)-1]),"\n")){//如果相等strcmp返回值为0,那么不进if语句,如果没遇到\n,则跳过
continue;
}
}
}
/*while((c=fgetc(fp))!=EOF){
if(c=='\n'){
count++;
}
}/**/
while(1){
//sleep(1);
//count++;没想等之前一直在自增
//printf("1:%d\n",count);
if(-1==(tnew=time(&tnew))){
perror("time error");
}
if(tnew!=tmold){
t=localtime((&tnew));
if(t==NULL){
perror("localtime error");
}
count++;//时间不一样的时候++
//printf("3:%d\n",count);
fprintf(fp,"%d.%d年%d月%d日 %02d:%0d:%02d\n",count,t->tm_year+1900,t->tm_mon+1,
t->tm_mday+1,t->tm_hour,t->tm_min,t->tm_sec);
fflush(fp);//忘刷新文件了,一直在缓冲区
tmold=tnew;
}
/*struct tm *t=localtime((&tnew));
if(tnew==NULL){
perror("time error");
}
fprintf(fp,"%d.%d年%d月%d日 %02d:%0d:%02d\n",count,t->tm_year+1900,t->tm_mon+1,
t->tm_mday+1,t->tm_hour,t->tm_min,t->tm_sec);
fflush(fp);//忘刷新文件了,一直在缓冲区
sleep(1);*/
}
fclose(fp);
}
格式化控制参数函数
#include <stdarg.h>
void va_start(va_list ap, last);
功能:根据最后一个成员构造ap
type va_arg(va_list ap, type);
功能:根据ap,向后取type类型的数据
void va_end(va_list ap);
功能:销毁ap成员#include <head.h> #include <stdarg.h> int add(int n, ...) { int sum = 0; va_list ap; va_start(ap, n); for (int i = 0; i < n; i++) { sum += va_arg(ap, int); } va_end(ap); return sum; } int main(int argc, const char* argv[]) { printf("sum = %d\n", add(2, 100, 200)); printf("sum = %d\n", add(4, 100, 200, 300, 400)); return 0; }
10.sprintf/snprintf
sprintf
函数原型:int sprintf(char *str, const char *format, ...);
头文件:#include <stdio.h>
功能:格式化的字符串写到str指向的地址中
参数:str:字符串首地址
format:格式
...:可变参
返回值:成功 格式化的字符串的长度
失败 返回一个负数
例:
基础用法
char str[10]={0}; sprintf(str,"hello"); printf("%s\n",str); return 0;
还可以写入数字
char str[10]={0}; int num=10; sprintf(str,"hello"); sprintf(str,"%d",num); printf("%s\n",str); return 0;
hello被新写入的num覆盖了,如果不想覆盖,需要手动计算位置,如下
char str[10]={0}; int num=10; sprintf(str,"hello"); sprintf(str+5,"%d",num); printf("%s\n",str); return 0;
sprintf写入的内容不限数据类型,什么类型都可以写,如下
char buff[128] = {0}; int num = 10; sprintf(buff, "num = %d\n", num); printf("buff=[%s]\n", buff);
char buff[128]={0}; time_t tm=0; time(&tm); struct tm *t=localtime(&tm); sprintf(buff, "%d年%d月%d日 %02d:%02d:%02d", t->tm_year+1900,t->tm_mon+1,t->tm_mday+1, t->tm_hour,t->tm_min,t->tm_sec); printf("%s",buff);
//注意 sprintf不会对数组的大小做检查(会报警告,运行出错)
//有可能出现越界访问的问题
char str[10]={0}; int num=10; sprintf(str, "hello worldasdfasdf%d", num); printf("%s\n",str);
编译虽然会报警告,警告不是错,但是可以运行,会出现越界访问的问题,错误未知
snprintf
函数原型:int snprintf(char *str, size_t size, const char *format, ...);
头文件:#include <stdio.h>
功能:格式化的字符串写到str指向的地址中
参数:str:字符串首地址
size:最多只能将 size-1 个字符写入str指向的地址 最后一个位置是'\0'
format:格式
...:可变参
返回值:成功 格式化的字符串的长度
失败 返回一个负数
例:
都传sizeof(buff)个
int main(int argc,const char * argv[])
{
char buff[128] = {0};
int num = 10;
snprintf(buff, sizeof(buff), "num = %d\n", num);
printf("buff=[%s]\n", buff);
memset(buff, 0, 128);
time_t tm = time(NULL);
struct tm *p = localtime(&tm);
snprintf(buff, sizeof(buff), "%04d-%02d-%02d %02d:%02d:%02d\n",
p->tm_year+1900, p->tm_mon+1, p->tm_mday,
p->tm_hour, p->tm_min, p->tm_sec);
printf("%s\n", buff);
//注意 snprintf会对数组的大小做检查
//不会出现越界访问的问题
char str[8] = {0};
snprintf(str, sizeof(str),"hello worldasdfasdf%d", num);
printf("%s\n", str);//hello w只能到w,w后还有'\0'
return 0;
}
11.fread/fwrite
fread
函数原型:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
头文件:#include <stdio.h>
功能:在文件中读取 nmemb 个 size 大小的项目到ptr指向的地址中
参数:ptr:用来保存读取内容的空间的首地址
size:每个项目的大小
nmemb:项目数
stream:文件指针
返回值:成功 返回实际读取的项目数
失败或者文件结束 小于项目数的数或者0
feof(fp) 返回值为真 说明是文件结束了
ferror(fp) 返回值为真 说明是发生错误了
一般写的时候让size=1,返回值变成实际想获取的字节数
例:
FILE *fp=fopen("test.c","r");
char buff[128]={0};
fread(buff,1,sizeof(buff),fp);
printf("%s\n",buff);
fwrite
函数原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
头文件:#include <stdio.h>
功能:向文件中写入 nmemb 个 size 大小的项目 数据来自ptr指向的地址
参数:ptr:要写入的数据的首地址
size:每个项目的大小
nmemb:项目数
stream:文件指针
返回值:成功 返回实际写入的项目数
失败 小于项目数的数
例:
不要写sizeoff(buff),这样写数据后面的'\0'也会被算进去,'\0'无法以字符串显示,会造成文件与我想要写入的不一样
size是多少 写入文件的内容就是多少,数据有几个字节 就写几个字节
FILE *fp=fopen("t.c","w");
char buff[8]="hello";
//fwrite(buff,1,sizeof(buff),fp);
fwrite(buff,1,5,fp);
int ret=fread(buff,1,sizeof(buff),fp);
if(ret!=0){
if(feof(fp)){
fprintf(stderr, "end of file\n");
}
}
printf("%s\n",buff);
return 0;
文件读写实例:
向文件中写入指定字符串,并读取出来
int main(int argc, char const *argv[])
{
FILE *fp=fopen("t.txt","w");
char buff[10]="hello";
//fwrite(buff,1,sizeof(buff),fp);
fwrite(buff,1,5,fp);
//写完以后需要关闭文件,后面要以读的方式打开,否则会报错
fclose(fp);
FILE *fp2=fopen("t.txt","r");
memset(buff,0,10);//buff里有东西,读的时候需清空
int ret=fread(buff,1,sizeof(buff),fp2);
if(ret<sizeof(buff)){
if(feof(fp2)){
fprintf(stderr,"文件结束\n");
}
if(ferror(fp2)){
fprintf(stderr,"文件出错\n");
exit(-1);
}
}
printf("buff = [%s]\n", buff);
fclose(fp2);
return 0;
}
向文件中写入整数
int main(int argc, char const *argv[])
{
FILE *fp=fopen("t.txt","w");
int num=10;
fwrite(&num,1,4,fp);
fclose(fp);
FILE *fpr=fopen("t.txt","r");
int readnum=0;
fread(&readnum,1,sizeof(num),fpr);
fclose(fpr);
printf("%d\n",readnum);
return 0;
}
可以读出来,但是文件里是以字符显示的,所以文件里的内容显示的是对应的ASCII码
10在内存中以二进制的形式存储,因为是小端存储,所以是0x0A 0x00 0x00 0x00
0x0A对应的ASCII码正好是回车键,所以文件里是空了一行,然后3个'\0'
向文件中写入结构体
int main(int argc, char const *argv[])
{
FILE *fp=fopen("t.txt","w");
struct student st={"zhangsan",20,80,"male"};
fwrite(&st,1,sizeof(st),fp);
fclose(fp);
struct student st2;
FILE *fpr=fopen("t.txt","r");
fread(&st2,1,sizeof(st),fpr);
printf("%s %d %d %s\n",st2.name,st2.age,st2.score,st2.sex);//按结构体读没问题
printf("%d\n",sizeof(st));//52
/*可以看到后面全是'\0',以字符串的形式输出遇到'\0'就停止了,所以只能读到zhangsan
char buff[128]={0};
int ret=fread(buff,1,sizeof(st),fpr);
printf("%s\n",buff);*/
fclose(fpr);
//写入字符串里,可以读出来
/*sprintf(buff,"%s %d %d %s",st.name,st.age,st.score,st.sex);
printf("%s\n",buff);*/
return 0;
}
注:1.可以看到male已经是最后一个元素了,但后面仍有东西,因为涉及到结构体对齐的问题,所以后面还有
2.为社么不能放到数组里输出
练习:
使用fread和fwrite实现文件的拷贝
./a.out src_file dest_file
feof(fp) ferror(fp)
int main(int argc, char const *argv[])
{
FILE *fp1=fopen(argv[1],"r");
FILE *fp2=fopen(argv[2],"w");
char buff[10]={0};
int ret=0;
while((ret=fread(buff,1,sizeof(buff),fp1))>=sizeof(buff)){
fwrite(buff,1,ret,fp2);
}
//这种方法需要在if判断一下,最后一次读的在缓冲区里,需要通过判断将缓冲区里的写入文件
if(feof(fp1)){
fwrite(buff,1,ret,fp2);
}
if(ferror(fp2)){
fprintf(stderr,"文件出错\n");
exit(-1);
}
/*while(!feof(fp1) && !ferror(fp1)){
ret = fread(buff, 1, sizeof(buff), fp1);
fwrite(buff, 1, ret, fp2);
}*/
fclose(fp1);
fclose(fp2);
return 0;
}
12.fseek/rewind/ftell
fseek
函数原型:int fseek(FILE *stream, long offset, int whence);
功能:更改光标的位置
参数:stream:文件指针
offset:偏移量
>0 向后偏移
0 不偏移
<0 向前偏移
whence:相对位置
SEEK_SET 相对于文件开头
SEEK_CUR 相对于当前位置
SEEK_END 相对于文件结尾
返回值:成功 0
失败 -1 重置错误码
rewind
void rewind(FILE *stream);
功能:将光标重新定位到文件开头
参数:文件指针
用法等价于: fseek(fp, 0, SEEK_SET);
ftell
long ftell(FILE *stream);
功能:返回文件光标距离文件开头的偏移量
参数:文件指针
返回值: 成功 当前的偏移量
失败 -1 重置错误码
fseek 和 rewind使用实例
fseek对a方式的写光标修改无效
// 如果文件是以a+的方式打开,
// 可以使用fseek修改读光标位置,但是对写光标位置无效,写总是在结尾
#include <head.h>
struct Test{
int id;
char name[32];
char sex;
};
int main(int argc,const char * argv[])
{
FILE *fp = fopen("hello.txt", "w+");
if(NULL == fp)
ERRLOG("fopen error");
struct Test t1 = {.id = 1001, .name = "zhangsan", .sex = 'w'};
fwrite(&t1, 1, sizeof(t1), fp);
struct Test t2;
memset(&t2, 0, sizeof(t2));
//如果写完数据后 不重置光标位置 直接读 是读不到的
if(-1 == fseek(fp, 0, SEEK_SET))
ERRLOG("fseek error");
//rewind(fp); //这种写法 也可以将光标重置到文件开头
fread(&t2, 1, sizeof(t2), fp);
printf("%d-%s-%c\n", t2.id, t2.name, t2.sex);
//读取文件中间部分的内容
fseek(fp, 4, SEEK_SET);
fseek(fp, 32, SEEK_CUR);
char read_sex = 0;
fread(&read_sex, 1, 1, fp);
printf("read_sex = %c\n", read_sex);
fseek(fp, -4, SEEK_END);
read_sex = 0;
fread(&read_sex, 1, 1, fp);
printf("read_sex = %c\n", read_sex);
fclose(fp);
return 0;
}
ftell使用实例
可以用来求文件大小
int main(int argc,const char * argv[])
{
FILE *fp=fopen("test.c","r");
fseek(fp,0,SEEK_END);
printf("%ld\n",ftell(fp));
return 0;
}
13.标准IO操作图片文件
BMP格式图片头说明:
/*FILE *fp=fopen("test.c","r");
fseek(fp,0,SEEK_END);
printf("%ld\n",ftell(fp));*/
FILE *fp=fopen("ddm.bmp","r+");
int boffbits=0;
int width=0;
/*fseek(fp,10,SEEK_SET);
fread(&boffbits,1,4,fp);
printf("%d\n",boffbits);//138
fseek(fp,18,SEEK_SET);
fread(&width,1,4,fp);
printf("%d\n",width);//1574*/
fseek(fp,138,SEEK_SET);
struct color c={0,0,0,0};//通过改变中成员的值,找到对应的颜色
for(int i=0;i<30;i++){
for(int j;j<1574;j++){
fwrite(&c,1,sizeof(c),fp);
}
}
fclose(fp);
作业:
给叮当猫的鼻子换个颜色。换成方的就行。鼻子坐标:
左上角 "597 320"
右下角 "695 420"
(换成方的就行 不需要换成圆的)。
//作业
FILE *fp =fopen("ddm.bmp","r+");
struct color c={255,255,0,0};
fseek(fp,1574*320*4+597*4,SEEK_SET);
for(int i=0;i<100;i++){//100行
for(int j=0;j<100;j++){
fwrite(&c,1,sizeof(c),fp);//100列
}
fseek(fp,-100,SEEK_CUR);//还需将光标偏移回去
}
fclose(fp);
三、文件IO
1.概念
1.1什么是文件IO
文件IO就是系统调用,
无缓冲机制,效率低,可移植性差
1.2什么是文件描述符
文件描述符是 open 函数打开文件时的返回值,叫fd,这个fd就代表打开的文件。
以后对文件的读写操作就是对该fd的操作。fd是一个非负整数,在一个进程中,
文件描述符的范围是[0,1023] ,使用 ulimit -a 可以查看, open files 后面的值就是
每一个运行起来的进程都会有三个默认打开的文件描述符,
分别是 0(stdin) 1(stdout) 2(stderr),我们再打开文件,fd一般都是从3开始的
这个值可以修改 ulimit -n 2048
FILE指针 | 标准 | 文件描述符 |
stdin(stdin->filno) | 标准输入 | 0 |
stdout(stdout->filno) | 标准输出 | 1 |
stderr(stdinerror->filno) | 标准出错 | 2 |
2.文件操作
2.1open
头文件:#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>函数原型:int open(const char *pathname, int flags, mode_t mode);
功能:打开文件
参数:pathname:带路径的文件名
flags:标志位
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 如果文件不存在 会新建普通文件
O_APPEND 追加写5
O_EXCL 需要和O_CREAT结合使用 如果文件不存在会创建文件,如果文件 存在会报错,错误码:EEXIST
O_TRUNC 如果文件存在,会被清空
mode:如果第二个参数有 O_CREAT 则必须指定 mode
如果第二个参数没有 O_CREAT 则不用指定 mode
mode 是文件的权限 使用 3位八进制来表示
例如:0666 0664 0444
注意:指定的mode不完全是新建文件的权限
还要考虑 umask umask一般为 0002
实际权限 = (mode & ~umask)
也就是说 --》传参 0666
110110110 111111101 & ----------- 110110100 --》
最终实际的权限是 0664 --》rw-rw-r--
返回值:成功 返回文件描述符 返回的是当前进程中未被打开的编号最小的
失败 -1 重置错误码
标准IO | 文件IO | 功能 |
r | O_RDONLY | 以只读方式打开文件,如果文件不存在会报错 |
r+ | O_RDWR | 以读写的方式打开文件,如果文件不存在会报错 |
w | O_CREAT | O_WRONLY | O_TRUNC,0666 | 以只写的方式打开文件,如果文件不存在会创建文件,如果文件存在,会清空再写 |
w+ | O_CREAT | O_RDWR | O_TRUNC,0666 | 以读写的方式打开文件,如果文件不存在会创建文件,如果文件存在,会清空再写 |
a | O_CREAT |O_WRONLY| O_APPEND,0666 | 以追加写的方式打开文件,如果不存在会新建文件 |
a+ | O_CREAT | O_APPEND | O_RDWR ,0666 | 以读和追加写的方式打开文件,如果不村子会新建 |
int main(int argc,const char * argv[])
{
int fd;
fd = open("hello.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
if(-1 == fd){
ERRLOG("open error");
}
printf("fd = %d\n", fd);
return 0;
}
int main(int argc,const char * argv[])
{
int fd;
//如果文件不存在 就新建 如果存在 就报错
fd = open("hello.txt", O_WRONLY|O_CREAT|O_EXCL, 0666);
if(-1 == fd){
if(EEXIST == errno){
fd = open("hello.txt", O_WRONLY);
printf("1111\n");
}else{
ERRLOG("open error");
}
}
printf("fd = %d\n", fd);
printf("stdin_no = %d\n", stdin->_fileno);//0
printf("stdout_no = %d\n", stdout->_fileno);//1
printf("stderr_no = %d\n", stderr->_fileno);//2
return 0;
}
2.2close
#include <unistd.h>
函数原型:int close(int fd);
功能:关闭open打开的文件
参数:fd:文件描述符
返回值:成功 0
失败 -1,重置错误码
int main(int argc,const char * argv[])
{
int fd = 0;
if(-1 == (fd = open("hello.txt", O_WRONLY|O_CREAT, 0666)))
ERRLOG("open error");
printf("fd = %d\n", fd);//3
printf("stdin_no = %d\n", stdin->_fileno);//0
printf("stdout_no = %d\n", stdout->_fileno);//1
printf("stderr_no = %d\n", stderr->_fileno);//2
if(-1 == close(fd))
ERRLOG("close error");
printf("111\n");
//close(1);//如果把 1 号文件描述符关了 那么就没法向终端输出内容了
printf("222\n");
close(0);
close(2);
if(-1 == (fd = open("hello.txt", O_WRONLY|O_CREAT, 0666)))
ERRLOG("open error");
printf("fd = %d\n", fd);//0
return 0;
}
文件描述符的重定向
#include <head.h>
int main(int argc,const char * argv[])
{
// stdout->_fileno=1-->终端
printf("hello open/read/write/close...\n"); //会在终端显示
close(1); //关闭标准输出
printf("%s:%s:%d\n",__FILE__,__func__,__LINE__); //不会在终端显示
int fd;
if((fd = open("./hello.txt",O_WRONLY|O_TRUNC|O_CREAT,0666))==-1)
PRINT_ERR("open error");
// 打开文件得到的fd=1,最小未使用原则
// stdout->_fileno=1-->文件
printf("fd = %d\n",fd); //文件描述符重定向
return 0;
}
2.3read
#include <unistd.h>
函数原型:ssize_t read(int fd, void *buf, size_t count);
功能:从文件读取内容到buf中
参数:fd:文件描述符
buf:存放读取内容的缓冲区首地址
count:想要读取的个数
返回值:成功 读取的字节数(0表示文件结束),且光标移动到字节数位置
失败 -1,重置错误码
2.4write
#include <unistd.h>
函数原型:ssize_t write(int fd, const void *buf, size_t count);
功能:将buf数据写到文件中
参数:fd:文件描述符
buf:存放写入内容的缓冲区首地址
count:想要写入的个数
返回值:成功 写入的字节数(0表示没有写入内容)
失败 -1,重置错误码
int main(int argc,const char * argv[])
{
int fd = 0;
int ret = 0;
if(-1 == (fd = open("hello.txt", O_RDWR|O_CREAT|O_TRUNC, 0666)))
ERRLOG("open error");
//写入字符串
char buff[32] = "hello world";
if(-1 == write(fd, buff, strlen(buff)))//字符串可以使用 strlen 其他内容最好自己控制大小
ERRLOG("write error");
memset(buff, 0, 32);
close(fd);//重新将光标定位到文件开头
if(-1 == (fd = open("hello.txt", O_RDONLY)))
ERRLOG("open error");
//文件中只有11个字节 第一次想读32个字节 但是实际只读到11个字节
if(-1 == (ret = read(fd, buff, sizeof(buff))))
ERRLOG("read error");
printf("ret = %d buff = [%s]\n", ret, buff);//11 hello world
//第二次再读的时候 文件中已经没有内容了 read会返回 0
memset(buff, 0, 32);
if(-1 == (ret = read(fd, buff, sizeof(buff))))
ERRLOG("read error");
printf("ret = %d buff = [%s]\n", ret, buff);//11 hello world
close(fd);
if(-1 == (fd = open("hello.txt", O_RDWR|O_CREAT|O_TRUNC, 0666)))
ERRLOG("open error");
//写入整数
int num = 1314;
if(-1 == write(fd, &num, 4))
ERRLOG("write error");
close(fd);//重新将光标定位到文件开头
if(-1 == (fd = open("hello.txt", O_RDONLY)))
ERRLOG("open error");
int ret_num = 0;
if(-1 == (ret = read(fd, &ret_num, 4)))
ERRLOG("read error");
printf("ret = %d ret_num = %d\n", ret, ret_num);//4 1314
close(fd);
return 0;
}
思考:
hello.txt里写的是1234 ,num是多少?
答案:
数字在文件里也是以字符串形式存储的,那“1234”在计算中是以ASCII码存储的,又是小端存储,数据低位在地址地位,就是“4321” 00110100 00110011 00110010 00110001
练习:
使用read/write实现文件的拷贝。
./a.out src_file dest_file
int fd1=open(argv[1],O_RDONLY);
if(fd1==-1){
perror("open error");
}
int fd2=open(argv[2],O_CREAT|O_WRONLY|O_TRUNC,0664);
if(fd2==-1){
perror("open error");
}
int ret=0;
char buff[32]={0};
//while(0<(ret=read(fd1,buff,32))){
while(ret=read(fd1,buff,32)){
write(fd2,buff,ret);
}
close(fd1);
close(fd2);
2.5lseek
#include <sys/types.h>
#include <unistd.h>
函数原型:off_t lseek(int fd, off_t offset, int whence);
功能:修改光标的位置
参数:fd:文件描述符
offset:偏移量
>0 向后偏移
0 不偏移
<0 向前偏移
whence:相对位置
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件结尾
返回值:成功 返回当前光标相对于文件开头的偏移量
失败 -1,重置错误码
例:
int main(int argc,const char * argv[])
{
int fd = open("hello.txt", O_WRONLY);
if(-1 == fd)
ERRLOG("open error");
int ret = lseek(fd, 2, SEEK_SET);
printf("ret = %d\n", ret);//2
write(fd, "H", 1);
//相当与将文件扩大100字节 但是如果没有写操作不会扩大
ret = lseek(fd, 100, SEEK_END);
printf("ret = %d\n", ret);//105
write(fd, "Q", 1);
return 0;
}
2.目录操作
2.1stat
头文件:#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
功能:将文件的属性信息放到保存文件属性的结构体的首地址里
参数:带路径的文件名
自己创建的用来存放文件信息的struct stat*类型的指针
struct stat {
dev_t st_dev; /*文件所在磁盘设备号*/
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */文件权限 :st_mode & 0777
文件类型:
The following mask values are defined for the file type:
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* 文件的设备号()字符或块 */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */struct timespec st_atim; /* 最后一次被访问的时间 */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* 最后一次改变状态的时间*/#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
自己用if判断
if((st_mode & S_IFMT) == S_IFREG){
普通文件 }
或者用宏函数都行
if(S_ISREG(st_mode)){
普通文件 }
struct stat *stabuf=NULL;
返回值:成功:0
失败:-1,重置错误码
注意: stat不能获取软连接文件的信息,如果想获取,可以使用lstat
struct stat结构体(man 2 stat)
文件的权限(man 7 inode)
st_mode & 0777,与出的结果就是三位八进制的数
文件类型
例:
int main(int argc, char const *argv[])
{if(2 != argc){
printf("Usage : %s pathname\n", argv[0]);
exit(-1);
}
//struct stat *p=NULL;不需要指针
struct stat p;//创建存放文件信息的结构体
//stat(argv[1],&p);//传参的时候传结构体的地址
if(stat(argv[1],&p)==-1){
perror("stat error");
}
printf("%ld\n",p.st_dev);
printf("%ld\n",p.st_ino);
if((p.st_mode & __S_IFMT)){
printf("普通文件\n");
}
if(S_ISDIR(p.st_mode)){
printf("目录文件\n");
}
printf("%d\n",p.st_mode);
printf("%d\n",(p.st_mode & 0777));
printf("%d\n",(p.st_nlink & 0777));
printf("st_uid = %d\n", p.st_uid);
printf("st_gid = %d\n", p.st_gid);
printf("st_rdev = %ld\n", p.st_rdev);
printf("st_size = %ld\n", p.st_size);
printf("st_blksize = %ld\n", p.st_blksize);
printf("st_blocks = %ld\n", p.st_blocks);
struct tm *t=localtime(&(p.st_atime));
printf("%04d-%02d-%02d %02d:%02d:%02d\n",
t->tm_year+1900, t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
return 0;
}
2.2getpwuid和getgrgid
getpwuid
头文件:#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);//struct passwd *getpwuid(p.st_uid);
//就是上一个例子里的p.st_uid
功能:根据uid获取用户的信息
参数:uid号
返回值:成功:用户信息的结构体指针
失败:NULL 重置错误码
getgrgid
头文件:#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
功能:获取组信息
参数:gid
返回值:成功:组信息结构体指针
失败:MULL
例
int main(int argc, char const *argv[])
{
if(2 != argc){
printf("Usage : %s pathname\n", argv[0]);
exit(-1);
}
struct stat p;
if((stat(argv[1],&p))==-1){
perror("stat error");
}
//虽然getpwuid的参数有了是p.st_uid,但还是需要创建struct passwd结构体来接getpwuid(p.st_uid)的值
//注意,这两个函数的返回值不是常数,是指针类型,所以要定义指针类型的变量来接
struct passwd *puser;
if((puser=getpwuid(p.st_uid))==NULL){
perror("getpwuid error");
}
//printf("%s ",(&(p.st_uid))->pw_name);
printf("%s %s\n",puser->pw_name,puser->pw_passwd);
struct group *g;
if((g=getgrgid(p.st_gid))==NULL){
perror("getgrgid error");
}
printf("%s %s\n",g->gr_name,g->gr_passwd);
return 0;
}
练习:
使用 stat 配合 getpwsuid 和 getgrgid 实现 ls -l 查看单个文件详细信息的功能:
要求 ./a.out filename 显示如下信息
-rw-rw-r-- 1 yangfs yangfs 1433 4月 10 10:37 filename
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#define U_R 0400
#define U_W 0200
#define U_X 0100
#define G_R 0040
#define G_W 0020
#define G_X 0010
#define O_R 0004
#define O_W 0002
#define O_X 0001
char FILEtype(struct stat st){
if(S_ISREG(st.st_mode)){
return '-';
}else if(S_ISDIR(st.st_mode)){
return 'd';
}else if(S_ISCHR(st.st_mode)){
return 'c';
}else if(S_ISBLK(st.st_mode)){
return 'b';
}else if(S_ISFIFO(st.st_mode)){
return 'p';
}else if(S_ISLNK(st.st_mode)){
return 'l';
}else if(S_ISSOCK(st.st_mode)){
return 's';
}
}
int main(int argc, char const *argv[])
{
if(argc!=2){
printf("Usage :%s <filename>\n",argv[0]);
exit(-1);
}
struct stat st;
if(-1==stat(argv[1],&st)){
perror("stat error");
}
char type='-';
char u_r='-';
char u_w='-';
char u_x='-';
char g_r='-';
char g_w='-';
char g_x='-';
char o_r='-';
char o_w='-';
char o_x='-';
//文件类型
if(S_ISREG(st.st_mode)){
type='-';
}
if(S_ISCHR(st.st_mode)){
type='c';
}
if(S_ISFIFO(st.st_mode)){
type='p';
}
if(S_ISBLK(st.st_mode)){
type='b';
}
if(S_ISDIR(st.st_mode)){
type='d';
}
if(S_ISSOCK(st.st_mode)){
type='s';
}
if(S_ISLNK(st.st_mode)){
type='l';
}
//文件权限 st_mode & 0777
if(st.st_mode & U_R){
u_r='r';
}
if((st.st_mode & U_W) != 0){
u_w='w';
}
if((st.st_mode & U_W) != 0){
u_x='x';
}
if(st.st_mode & G_R){
g_r='r';
}
if(st.st_mode & G_W){
g_w='w';
}
if(st.st_mode & G_X){
g_x='x';
}
if(st.st_mode & O_R){
o_r='r';
}
if(st.st_mode & O_W){
o_w='w';
}
if(st.st_mode & O_X){
o_x='x';
}
struct passwd *pw=getpwuid(st.st_uid);
struct group *g=getgrgid(st.st_gid);
struct tm *t=localtime(&(st.st_atime));
printf("%c%c%c%c%c%c%c%c%c%c %ld %s %s %6ld %02d月 %2d %02d:%02d %s\n",type,u_r,u_w,u_x,g_r,g_w,g_x,o_r,o_w,o_x,
st.st_nlink,g->gr_name,pw->pw_name,
st.st_size,t->tm_mon+1,t->tm_mday+1,t->tm_hour,t->tm_min,argv[1]);
return 0;
}
2.2opendir
头文件:#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开一个目录文件
参数:目录文件的路径和名字
返回值:成功:目录流指针 DIR *
失败:NULL 重置错误码
2.3readdir
头文件:#include <dirent.h>
struct dirent *readdir(DIR *dirp);
功能:读取目录文件下的内容
参数:目录指针
返回值:成功:目录信息结构体指针
struct dirent
{
ino_t d_ino;/* 读到的文件的inode号 */
off_t d_off;/* 不是给应用程序用的 */
unsigned short int d_reclen;/* 这个结构体的大小 不是文件的大小 */
unsigned char d_type;/* 文件的类型 */
DT_BLK This is a block device. 块设备
DT_CHR This is a character device.字符设备文件
DT_DIR This is a directory.目录文件
DT_FIFO This is a named pipe (FIFO).管道文件
DT_LNK This is a symbolic link.链接文件
DT_REG This is a regular file.普通文件
DT_SOCK This is a UNIX domain socket.套接字文件
char d_name[256]; /*文件名*/
};
失败:NULL 重置错误码
文件类型:
2.4closedir
头文件:
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
功能:关闭opendir打开的目录文件
参数:目录流指针 DIR *
返回值:成功:0
失败:-1,重置错误码
例:
int main(int argc, char const *argv[])
{
if(argc!=2){
printf("Usage : %s pathname\n", argv[0]);
exit(-1);
}
DIR *dir=opendir("argv[1]");
//if (!opendir("argv[1]")){
if (!dir){
perror("opendir error");
}
struct dirent *p=readdir(dir);
/*printf("filename = %s ", p->d_name);
printf("%ld",p->d_ino);
printf("%d",(p->d_type & DT_REG));*/
while(NULL !=p){
printf("filename = %s ", p->d_name);
printf(" inode = %ld ", p->d_ino);
switch(p->d_type){
case DT_BLK:
printf(" 块设备文件 ");
break;
case DT_CHR:
printf(" 字符设备文件 ");
break;
case DT_DIR:
printf(" 目录文件 ");
break;
case DT_FIFO:
printf(" 管道文件 ");
break;
case DT_LNK:
printf(" 链接文件 ");
break;
case DT_REG:
printf(" 普通文件 ");
break;
case DT_SOCK:
printf("套接字文件");
break;
}
printf("\n");
}
return 0;
}
VScode里有警告,但编译运行没问题,以编译器为准
练习:
结合目录操作:输入一个目录名和一个文件名,判断该目录下是否存在该文件
./a.out dir_name file_name
if(argc!=3){
printf("Usage :%s dirname filename\n",argv[0]);
}
DIR *dir=NULL;
if(NULL==(dir=opendir(argv[1]))){
perror("opendir error");
}
struct dirent *d;
while(NULL!=(d=readdir(dir))){
if(!strcmp(d->d_name,argv[2])){
printf("文件存在\n");
//break;这里不用break,用return 0
return 0;
}
}
printf("文件不存在\n");
closedir(dir);
用return 0而不是break,避免以下问题
作业:实现 ls -l 功能;
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#define U_R 0400
#define U_W 0200
#define U_X 0100
#define G_R 0040
#define G_W 0020
#define G_X 0010
#define O_R 0004
#define O_W 0002
#define O_X 0001
char FILEtype(struct stat st){
if(S_ISREG(st.st_mode)){
return '-';
}else if(S_ISDIR(st.st_mode)){
return 'd';
}else if(S_ISCHR(st.st_mode)){
return 'c';
}else if(S_ISBLK(st.st_mode)){
return 'b';
}else if(S_ISFIFO(st.st_mode)){
return 'p';
}else if(S_ISLNK(st.st_mode)){
return 'l';
}else if(S_ISSOCK(st.st_mode)){
return 's';
}
}
int main(int argc, char const *argv[])
{
if(argc !=2){
printf("Usage : %s pathname\n", argv[0]);
exit(-1);
}
DIR *dir=opendir(argv[1]);
if (NULL==dir){
perror("opendir error");
}
struct dirent *p_dir=NULL;
struct stat st;
char c[256]={0};
char file_type='-';
char u_r='-';
char u_w='-';
char u_x='-';
char g_r='-';
char g_w='-';
char g_x='-';
char o_r='-';
char o_w='-';
char o_x='-';
struct passwd *puser=NULL;
struct group *g=NULL;
struct tm *t=NULL;
char buff[256]={0};
char a[]=".";
while((p_dir=readdir(dir))!=NULL){
//stat(buff,&st);
snprintf(buff,strlen(argv[1])+strlen(p_dir->d_name)+2,"%s/%s",argv[1],p_dir->d_name);
stat(buff,&st);
file_type=FILEtype(st);
if((st.st_mode & U_R)!=0){
u_r='r';
}
if((st.st_mode & U_W) != 0)
u_w = 'w';
if((st.st_mode & U_X) != 0)
u_x = 'x';
if((st.st_mode & G_R) != 0)
g_r = 'r';
if((st.st_mode & G_W) != 0)
g_w = 'w';
if((st.st_mode & G_X) != 0)
g_x = 'x';
if((st.st_mode & O_R) != 0)
o_r = 'r';
if((st.st_mode & O_W) != 0)
o_w = 'w';
if((st.st_mode & O_X) != 0)
o_x = 'x';
puser=getpwuid(st.st_uid);
g=getgrgid(st.st_gid);
t=localtime(&(st.st_atime));
if((strncmp(p_dir->d_name,a,1))==0){
continue;
}else{
printf("%c%c%c%c%c%c%c%c%c%c %ld %s %s %6ld %d月 %02d %02d:%02d %s\n",
file_type,u_r,u_w,u_x,g_r,g_w,g_x,o_r,o_w,o_x,st.st_nlink,
puser->pw_name,g->gr_name,st.st_size,t->tm_mon+1,t->tm_mday,
t->tm_hour,t->tm_min,p_dir->d_name);
}
}
closedir(dir);
return 0;
}
四、库的制作
1.概念
库是一个二进制文件,是将xx.c 文件编译生成二进制的文件,库分为 静态库 和 动态库
windows:
静态库 xxx.lib
动态库 xxx.dll
linux:
静态库 libxxx.a
动态库 libxxx.so
2.静态库
静态库是 lib库名.a 格式的文件,如果在编译的时候使用了静态库
会将静态库中的函数对应的机器指令编译到可执行程序中,可执行程序的体积相对来说会较大
执行效率相对较高。静态库的内容更新后,需要重新编译生成新的可执行文件,
更新操做相对麻烦。
2.1制作静态库
gcc -c xx1.c -o xx1.o //只编译 不链接
gcc -c xx2.c -o xx2.o //只编译 不链接
ar -cr libhqyj.a xx1.o xx2.o //ar是制作静态库的命令 hqyj是库名前缀lib和后缀.a 都是格式要求
2.2静态库的使用
构建场景:
t.h
#ifndef __HQYJ_H__ #define __HQYJ_H__ int my_add(int, int); #endif
t.c
int my_add(int x, int y){ return x+y; }
main.c
#include <stdio.h> #include "hqyj.h" int main(int argc, const char *argv[]) { int a = 10; int b = 20; printf("%d\n", my_add(a, b));//30 return 0; }
直接编译不认识,需要指定头文件的路径、库的路径和库
可以利用tree查看当前目录的文件情况,如果没有tree,sudo apt-get install tree安装一下
3.动态库
静态库是以 lib库名.so 格式的文件,如果在编译的时候使用了动态库
不会将动态库中的函数对应的机器指令编译到可执行程序中,
只是将库中的函数符号表放在了可执行文件中,所以可执行程序的体积相对来说会较小,
执行效率相对较低,
因为每次执行到调用库函数的时候都需要去对应的库里面查找对应的机器指令。
动态库的内容更新后,不需要重新编译生成新的可执行文件,更新操做相对简单。
3.1制作动态库
gcc -c -fPIC xx.c -o xx.o 将.c只编译不链接生成 .o f小写 PIC大写 -fPIC忽略相对位置
gcc -shared xx.o -o libhqyj.so 将.o编译生成动态库 hqyj是库名
或者
gcc -fPIC -shared xx.c -o libhqyj.so 直接将 .c 编译生成动态库
3.2动态库的使用
-I 指定头文件的路径
-L 指定库文件的路径
-l (小写的l)指定要连接的库
编译
基于动态库的可执行程序的执行过程
指定库的路径
方式1:修改环境变量 LD_LIBRARY_PATH
一般不用后两种
方式2:将自己的 libhqyj.so 库放在系统库的路径下 (/user/lib)----需要 root 权限
方式3:通过修改系统库路径的配置文件来解决 ----需要 root 权限
sudo vi /etc/ld.so.conf.d/libc.conf
将自己的库文件的绝对路径添加到libc.conf中
sudo ldconfig 立即生效,否则需要重启终端才能生效