1.為什么要用到文件?怎樣數據才能持久化?
保存在內存中的數不安全(一次斷電,忘記保存,不用了還給系統)
持久化:保存在硬盤上(放在文件中)
什么是文件?文件分為程序文件和數據文件
程序文件是什么呢?
在windows環境下。.c源程序文件 / .obj目標文件?/ .exe執行文件,這些就是程序文件
數據文件
文件的內容不一定是程序,而是程序運行時讀寫的數據,比如程序運行需要從中讀取數據的文件,或者輸出內容的文件。
如圖:
?
編輯.c源程序文件(test.c)可以對A文件(數據文件)進行讀寫操作進行
2.文件名
一個文件有唯一的文件標識,以便于用戶的識別和引用
文件名包含三部分:文件路徑 + 文件主干名 + 文件后綴
例:??c:\code\heyerous\test.txt
3.文件的打開 or 關閉
->1. 文件指針
緩沖文件系統中,關鍵的概念是“文件類型指針”,簡稱:文件指針
每個被使用的文件都在內存中開辟了一個相應的文件信息區,用來存放文件的相關信息(如:文件的名字,文件的狀態,文件當前的位置等)。這些信息是保存在一個結構體變量中的,該結構體類型是由系統聲明的,取名:FILE
如圖:
?
注:??每打開一個文件就會產生一個文件信息區,每一個文件信息區都需要一個文件指針來指向它。不同的C編譯器的FILE類型包含的內容不完全相同,但大同小異
一般都是通過FILE的指針來維護這個FILE結構的變量,這樣更方便
創建一個FILE指針的變量:
FILE* pf? ? ? ?//文件指針變量
->2. 文件的打開和關閉
文件在讀寫之前先打開文件,在使用結束之后應該關閉文件
ANSIC(標準C)規定使用fopen函數(打開成功放回文件地址,失敗則返回NULL)來打開文件,fclose函數(打開成功返回0,打開失敗返回EOF(-1))來關閉文件
fopen和fclose的使用示例:
#include <stdio.h>int main()
{//打開文件FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "r");//判斷文件是否打開成功if(pf == NULL){perror("fopen");return 1;}//讀寫文件//....//關閉文件fclose(pf);pf = NULL;
}
運行結果:
?
打開成功說明該路徑存在這個文件
?
上面所示代碼是用的絕對路徑打開的文件 C:\code\heyerous\test.txt
絕對路徑
文件的準確位置
?
相對路徑
相對路徑的理解,可以認為是程序和文件的相對位置,比如說和當前.c文件在同文件夾中,或者文件在.c文件的上級路徑中?(以.c文件為例)
?
?
就可以看到該文本文件(test.txt)了
將文件放在上級目錄程序依舊可以運行
除了這兩個操作以外還其他的操作:
如:“../test.txt”? 上級目錄? ?“./text.txt”? 當前目錄? "././test,txt" 上一級目錄的上一級目錄
注:? 創建文件的時候看看后綴名(擴展名)
4.怎么讀寫?
1.文件的順序讀寫
fputc
->1.??fputc函數? ? //向文件中寫一個字符
int fputc(int charactor, FILE* stream);
(1). FILE* stream 需要寫入的文件的指針
(2).?int charactor 需要向文件中寫的字符
->2.??fputc函數的使用
puts按順序向文件中寫內容
#include <stdio.h>int main()
{//用"w"會將我們上次寫入的內容全部覆蓋掉FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "w"); if (NULL == pf){perror("fopen");return 1;}//方法1/*fputc('z', pf);fputc('y', pf);fputc('x', pf);fputc('6', pf);*///方法2:使用for循環for (char ch = 'A'; ch <= 'Z'; ch++){fputc(ch, pf);}fclose(pf);pf = NULL;return 0;
}
運行結果:
?
向文件寫一個字符,不是字符串!!!
fgetc
->1. fgetc函數? ??//向文件中讀一個字符
int fgetc(FILE* stream);
(1). 按順序向文件讀字符
(2). FILE* stream? ?需要讀文件的指針
->2. fgetc函數的使用
#include <stdio.h>int main()
{FILE* pf = fopen("C:\\code\\heyerous\\test.txt", "r"); if (NULL == pf){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c", ch);ch = fgetc(pf);printf("%c", ch);ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
fputs
->1. fputs函數? ?//向文件中寫入一行內容
int fputs( const char *string, FILE *stream );
(1).?const char *string? 需要寫入文件的內容
(2). FILE *stream 目標文件
->2. fputs函數的使用??
這里我們換用相對路徑
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "w");if(NULL == pf){perror("fopen");return 1;}fputs("hello world", pf);fclose(pf);pf ==NULL;return 0;
}
運行結果:
?
fgets
->1.?fgets函數 //向文件讀一行內容
char *fgets( char *string, int n, FILE *stream );
(1). char *string? 將讀到的內容放到另一個空間中
(2). int n? 個數限制
(3). FILE *stream 需要讀的文件的指針
->2. fgets函數的使用
#include <stdio.h>
int main()
{FILE* pf = fopen("test.txt", "r");if (NULL == pf){perror("fopen");return 1;}char buf[25] = { 0 };fgets(buf, 12, pf);printf(buf);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
注:? fgets讀文件時會留一個空間存放\0,所以我們想要將hello world全部都讀出來得限制12個個數(包括\0)
如果buf空間比文件中一行大,即:fgets只讀一行的內容
1.buf > num, 直接讀到末尾結束,第二行不管,只管第一行得內容
2.buf < num,只讀num數量的字符
fprintf
->1.?fprintf函數?? //將格式化的內容寫到文件中??
int fprintf(FILE* stream, const char*format, ...);
(1). FILE* stream? 需要寫入的文件的指針
(2).?const char*format? 數據流:格式化字符串等各種信息
(3). ...? 可變參數列表可接收多個參數
->2. fprintf函數的使用
#include <stdio.h>typedef struct S
{char name[20];int age;float score;
}S;int main()
{FILE* pf = fopen("test.txt", "w");if(FILE == NULL){perror("fopen");reutrn 1;}s stu = {"zhangsan", 20, 89.5}; fprintf(pf, "%s %d %f", stu.name, stu.age, stu.score);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
?fscanf
->1.?fscanf函數?? //將格式化的內容寫到文件中??
int fscanf(FILE* stream, const char*format, ...);
(1). FILE* stream? 需要寫入的文件的指針
(2).?const char*format? 數據流:格式化字符串等各種信息
(3). ...? 可變參數列表可接收多個參數
->2. fprintf函數的使用
#include <stdio.h>typedef struct S
{char name[20];int age;float score;
}S;int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen");return 1;}S stu = { 0 };fscanf(pf, "%s %d %f", stu.name, &(stu.age), &(stu.score));printf("%s %d %f", stu.name, stu.age, stu.score);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
以上函數操作時寫入文件都看得懂,因為是文本信息(適用于所有輸入輸出流)
我們將數據傳給輸出設備如果這些設備的讀寫的都不相同,程序要使用的話都需要了解,那對一個程序來說太復雜了,所以為了解決這個問題C語言引入了流(stream)
?
將數據輸出到流上,然后通過流輸出到設備上
任何一個C語言程序運行的時候:默認打開3個流
1.stdin - 標準輸出(鍵盤)
2.stdout - 標準輸出(屏幕)
3.stderr - 標準錯誤(屏幕)
這三個流的類型都是FILE*
例1:
int ch = fgets(stdin)
fputc(ch, stdout)
從鍵盤上讀一個字符然后寫到屏幕上
?
例2:
#include <stdio.h>typedef struct stu
{char name[20];int age;float score;
}stu;int main()
{stu p = { 0 };fscanf(stdin, "%s %d %f", p.name, &(p.age), &(p.score));fprintf(stdout, "%s %d %f", p.name, p.age, p.score);return 0;
}
運行結果:
?
fwrite
->1.?fwrite函數? //以二進制的方式向文件中寫入
size_t fwrite(const void *ptr, size_t size, size_t count, FILE* stream)
(1). const void *ptr? 需要寫入數據的地址
(2). size_t size? ?每個元素的大小
(3). size_t count? ?需要寫入文件的元素個數
(4). FILE *stream? ?該文件的指針
->2. fwrite函數的使用
#include <stdio.h>typedef struct stu
{char name[20];int age;float score;
}stu;int main()
{FILE *pf = fopen("test.txt", "wb"); //wb以二進制的方式寫if(NULL ==pf){perror("fopen");return 1;}stu p = {"zhangsan", 20, 85.5};fwrite(&p, sizeof(stu), 1, pf);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
從運行結果來看,確實是以二進制的形式寫進去的
?fread
->1.?fread函數? //以二進制的方式讀文件
size_t fwrite(void *ptr, size_t size, size_t count, FILE* stream)
(1). void *ptr? 存放向文件讀到的數據
(2). size_t size? ?每個元素的大小
(3). size_t count? ?需要寫入文件的元素個數
(4). FILE *stream? ?該文件的指針
->2. fread函數的使用
#include <stdio.h>typedef struct stu
{char name[20];int age;float score;
}stu;int main()
{FILE *pf = fopen("test.txt", "rb"); //以二進制的形式讀if(NULL == pf){perror("fopen");return 1;}stu s = { 0 };fread(&s, sizeof(stu), 1, pf);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
可以看到fread將文件的內容放入了結構體變量s上
sprintf
->1.?sprintf函數? //把格式化的數據,以字符串的格式打印存放在一個字符串中
int sprintf(const char* s, const char* format, ...);
(1). const char* s??存放數據的地址
(2). const* format? ?數據流:格式化字符串等各種信息
(3). ...? ?可變參數列表,可以放多個參數
->2.?sprintf函數的使用
#incldue <stdio.h>typedef struct tmp
{char name[20];int age;float score;
}tmp;int main()
{tmp s = {"zhangsan", 20, 86.5};char buf[30] = { 0 };sprintf(buf, "%s%d%f", s.name, s.age, s.score);printf(buf);return 0;
}
運行結果:
?
?sscanf
->1.?sscanf函數? //從一個字符串中,還原出一個格式化的數據
int sscanf(const char* s, const char* format, ...);
(1). const char* s??存放數據的地址
(2). const* format? ?數據流:格式化字符串等各種信息
(3). ...? ?可變參數列表,可以放多個參數
->2.?sscanf函數的使用
#include <stdio.h>typedef struct tmp
{char name[20];int age;float score;
}tmp;int main()
{tmp s = { 0 };char buf[30] = { "zhangsan 20 86.5" };sscanf(buf, "%s%d%f", s.name, &(s.age), &(s.score));printf("%s %d %f", s.name, s.age, s.score);return 0;
}
運行結果:
?
scanf - 從鍵盤(stdin)上讀取格式化的數據
printf - 把數據輸出到屏幕(stdout)上
fprintf - 針對所有輸入流的格式化的輸入函數:stdin,打開的文件
fscanf - 針對所有的輸出流的格式化的輸出函數:stdout,打開的文件
5.文件的隨機讀寫
fseek
->1.?fseek函數? //調整光標位置
int fseek(FILE* stream, long int offset, int orgin);
(1). FILE* stream??目標文件
(2). long int offset? ?偏移量(往哪偏移)
(3). int orgin??從哪開始調整
???SEEK_SET? ? 文件開始的位置
SEEK_CUR? ?光標現指向的位置
SEEK_END? ?文件末尾位置
->2.?fseek函數的使用
?例:
文本文件中的內容
?
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "r");if (NULL == pf){perror("fopen");return 1;}int ch = fgetc(pf);ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);//此時光標在字符c的后面d的前面,如果繼續往下讀肯定回到d,但是我們調整一下去讀bfseek(pf, 1, SEEK_SET);// 或者fseek(pf, -2, SEEK_SUR);//這時往下讀就會讀到bch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
ftell
->1.?ftell函數? //返回文件指針相對于起始位置的偏移量
long int fseek(FILE* stream);
(1). FILE* stream??目標文件
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "r");if (NULL == pf){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);ch = fgetc(pf);printf("%c\n", ch);int pos = ftell(pf);printf("%d", pos);fclose(pf);pf = NULL;return 0;
}
運行結果:
->2.?rewind函數的使用
#include <stdio.h>int main()
{FILE *pf = fopen("test.txt", "r");if(NULL == pf){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);rewind(pf);ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
rewind
->1.?rewind函數? //使指針回到初始位置
void rewind( FILE *stream );
(1). FILE *stream? 需要調整文件的指針
->2.?rewind函數的使用
#include <stdio.h>int main()
{FILE *pf = fopen("test.txt", "r");if(NULL == pf){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);rewind(pf);ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
運行結果:
?
6.文本文件和二進制文件
數據文件被稱為文本文件或者二進制文件
數據在內存中以二進制的形式存儲,如果不加轉換的輸出到外存(文件),就是二進制文件
在外存上以ASCII碼的形式儲存,則需要在存儲前轉換。以ASCII字符的形式存儲的文件就是文本文件
測試代碼:
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "wb");if (NULL == pf){perror("fopen");return 1;}int a = 10000;fwrite(&a, 4, 1, pf);fclose(pf);pf = NULL;return 0;
}
運行結果:
可以看到是二進制文件
我們來看看它是否是如我們所說的二進制一樣
?
然后添加這個文件
添加進來之后右擊點擊這個文件,選擇打開方式,點擊二進制編輯器
可以看到是以十六進制的方式寫的,因為這樣更加方便表達展現,而且字節存儲方式是小端
#include <stdio.h>int main()
{FILE* pf = fopen("test.txt", "w");if (NULL == pf){perror("fopen");return 1;}int a = 10000;fprintf(pf, "%d", a);fclose(pf);pf = NULL;return 0;
}
運行結果:
文本文件
7.文件讀取結束的判定
feof 和 ferror
int ferror( FILE *stream );
int feof( FILE *stream );
(1). FILE *stream - 需要判斷的文件
feof - 文件正常讀取遇到了結束標志而結束就返回真
ferror - 返回真,說明文件在讀取過程中出錯了而結束
用這兩個函數的前提是文件已經讀取結束了才可以去使用這兩個函數
用于文件讀取結束的時候,判斷是讀取失敗,還是到文件末尾結束?
1.文本文件讀取是否結束,判斷返回值是否為EOF(fgetc),NULL(fgets)
例如:
- fgetc判斷是否為EOF
- fgets判斷是否為NULL
文本文件的例子:
#include <stdio.h>
#define EXIT_FAILURE 1int main()
{FILE *pf = fopen("test,txt", "r");if(NULL == pf){perror("file opening failed");return EXIT_FAILURE;}int ch;while((ch = fgetc(pf)) != EOF) //fgetc的返回值是int類型{putchar(ch);}//判斷是什么原因結束的//判斷文件是否是讀取時遇到錯誤結束if(ferror(pf))puts("I/O error when reading"); //I/O讀取文件else if(feof(pf))puts("End of file reached successfully");fclose(pf);pf = NULL;return 0;
}
2.二進制文件的讀取結束判斷,判斷返回值是否小于實際要讀的個數
例如:
- fread 判斷返回值是否小于實際要讀的個數
二進制文件的例子:
#include <stdio.h>enum //沒有枚舉名作用和define差不多,文本替換
{size = 5,
};int main()
{FILE* pf = fopen("test.txt", "wb");if (NULL == pf){perror("FILE fopening failed");return 1;}int a[size] = { 1, 2, 3, 4, 5, };fwrite(a, sizeof * a, size, pf);fclose(pf);pf = NULL;return 0;
}
//以上代碼是將數組a中的容寫進文件test.txt
#include <stdio.h>enum //沒有枚舉名作用和define差不多,文本替換
{size = 5,
};int main()
{FILE* pf = fopen("test.txt", "rb");int a[size] = { 0 };size_t ret_code = fread(a, sizeof *a, size, pf);if (ret_code == size){perror("FILE fopening failed");return 1;}else if(feof(pf)){puts("Error reading test.txt: unexpected end of file\n");}else if(ferror(pf)){puts("Error reading test.txt");}fclose(pf);pf = NULL;return 0;
}
8.文件緩沖區
我們將數據沖數據區輸出/輸入到磁盤(文件)的時候不是直接輸入/輸出過去而是先經過緩沖區,通過緩沖區進入磁盤的,但是我們放到緩沖區的數據不會直接進入到磁盤,要怎么才能將數據放到磁盤了
將數據放入磁盤的條件:
(1). 緩沖區放滿的時候,緩沖區會自動將數據放入磁盤
(2). 主動刷新緩沖區的時候,緩沖區會將數放入磁盤
(3). 使用fclose關閉文件時,也會主動刷新緩沖區的將數據寫到硬盤中然后關閉文件
?注:
因為有緩沖區的存在,C語言在操作文件的時候。需要做刷新緩沖區或者在文件操作結束的時候關閉文件? ?很重要!!!(保存信息)