小甲魚C語言《帶你學C帶你飛》學習筆記(2)
視頻鏈接https://www.bilibili.com/video/av27744141
由于篇幅限制,本筆記分兩部分:
小甲魚C語言《帶你學C帶你飛》學習筆記(1)-傳送門
?小甲魚C語言《帶你學C帶你飛》學習筆記(2)-本文
本筆記是本人根據(jù)小甲魚視頻講課內(nèi)容記錄,一些程序做了小修改。根據(jù)自身補充了一部分拓展內(nèi)容。如有錯誤,歡迎一起討論????????——正能量的康sir? ? ??
?如果喜歡我的文章,不妨點個關(guān)注、點個贊、投個幣,謝謝:)
P45單鏈表1
單鏈表
信息域 指針域
信息域存儲數(shù)據(jù),指針域指向下一個地址
最后一個指針域指向NULL
?
單鏈表插入元素(頭插法)
#include<stdio.h>
#include<stdlib.h>
struct Book {
???????? char title[128];
???????? char author[40];
???????? struct Book *next;
};
void getInput(struct Book *book){
???????? printf("請輸入書名:");
???????? scanf("%s",book->title);
???????? printf("請輸入作者:");
???????? scanf("%s",book->author);
}
void addBook(struct Book **library){//**library指向book結(jié)構(gòu)指針的指針
???????? struct Book *book,*temp;
???????? book=(struct Book *)malloc(sizeof(struct Book));
???????? if(book==NULL){
????????????????? printf("內(nèi)存分配失敗!\n");
????????????????? exit(1);//需要stdlib.h
???????? }
???????? getInput(book);
???????? if(*library!=NULL){//有書的情況,頭指針 *library指向新插入書位置
????????????????? temp=*library;//頭指針原來書的位置
????????????????? *library=book;//指向新插入書位置
????????????????? book->next=temp;//next指向下一個節(jié)點地址,即原來書的位置
???????? }
???????? else{//開始沒有書的情況 *library=NULL的情況下添加書
????????????????? *library=book;//第一個節(jié)點指針不是 NULL了是book節(jié)點的
????????????????? book->next=NULL; //next指向下一個節(jié)點地址,即NULL
???????? }
}
?
void printLibrary(struct Book *library){
???????? struct Book *book;
???????? int count = 1;
???????? book=library;
???????? while(book!=NULL){
????????????????? printf("-----------Book%d-----------\n",count);
????????????????? printf("書名:%s\n",book->title);
????????????????? printf("作者:%s\n",book->author);
????????????????? book=book->next;
????????????????? count++;
???????? }
}
void releseLibrary(struct Book *library){//釋放資源
???????? struct Book *temp;
???????? while(library!=NULL){
????????????????? temp=library->next;
????????????????? free(library);
????????????????? library=temp;???
???????? }
}
int main(){
???????? struct Book *library=NULL;
???????? int ch;
???????? while(1){
????????????????? do{
????????????????????????? printf("是否錄入書籍信息(Y/N):");
????????????????????????? ch=getchar();
????????????????? } while(ch!='Y'&&ch!='N');
????????????????? if(ch=='Y')
????????????????? {
????????????????????????? addBook(&library);
????????????????? }
????????????????? else
????????????????? {
????????????????????????? break ;
????????????????? }
???????? }
???????? do{???????????
????????????????? printf("是否打印書籍信息(Y/N):");
????????????????? ch=getchar();
???????? } while(ch!='Y'&&ch!='N');
???????? if(ch=='Y')
???????? {
????????????????? printLibrary(library);
???????? }
???????? releseLibrary(library);
???????? return 0;
}
P46單鏈表2
單鏈表插入元素(尾插法)
只需修改addBook函數(shù)的有書情況下的插法
???????? struct Book *temp;
。。。
???????? if(*library!=NULL){//有書的情況
????????????????? temp=*library;
????????????????? while(temp->next!=NULL){//定位 單鏈表尾部位置
????????????????????????? temp=temp->next;
????????????????? }
????????????????? //插入數(shù)據(jù)
????????????????? temp->next=book;
????????????????? book->next=NULL;
???????? }
優(yōu)化 定義一個指針始終指向尾部,提高效率
static struct Book *tail;
。。。
???????? if(*library!=NULL){//有書的情況
????????????????? tail->next=book;
????????????????? book->next=NULL;
???????? }
???????? else{//開始沒有書的情況 *library=NULL的情況下添加書
????????????????? *library=book;//第一個節(jié)點指針不是 NULL了是book節(jié)點的
????????????????? book->next=NULL; //next指向下一個節(jié)點地址,即NULL
???????? }
???????? tail=book;
?
搜索單鏈表
struct Book *searchBook(struct Book *library,char *target){
???????? struct Book *book;
???????? book=library;
???????? while(book!=NULL){
????????????????? if(!strcmp(book->title,target)||!strcmp(book->author,target)){//strcmp相等返回0 。需要string.h
????????????????????????? break;
????????????????? }
????????????????? book=book->next;
???????? }
???????? return book;
}
void printBook(struct Book *book){
???????? printf("書名:%s",book->title);
???????? printf("作者:%s",book->author);
}
int main(){
。。。
???????? char input[128];
???????? struct Book *book;
。。。
???????? printf("請輸入查找的書名或作者") ;
???????? scanf("%s",input);
???????? book=searchBook(library,input);
???????? if(book==NULL)
???????? {
????????????????? printf("很抱歉,未找到");
???????? }
???????? else{
????????????????? do{
????????????????????????? printf("已找到符合條件的圖書...");
????????????????????????? printBook(book);
????????????????? }while((book =searchBook(book->next,input))!=NULL);//多本圖書都匹配的話可以重復(fù)找
???????? }
。。。
}
P47單鏈表3
單鏈表插入節(jié)點(中間插入)
#include<stdio.h>
#include<stdlib.h>
struct Node{
???????? int value;
???????? struct Node *next;
};
void insertNode(struct Node **head,int value){
???????? struct Node *previous;//上一個
???????? struct Node *current;//當前
???????? struct Node *it;//new是關(guān)鍵字 就用it吧
???????? current= *head;
???????? previous=NULL;
???????? while(current!=NULL&¤t->value<value){
????????????????? previous=current;
????????????????? current=current->next;
???????? }
???????? it=(struct Node *)malloc(sizeof(struct Node));
???????? if(it==NULL){
????????????????? printf("內(nèi)存分配失敗!\n");
????????????????? exit(1);
???????? }
???????? it->value=value;
???????? it->next=current;
???????? if(previous==NULL){//空鏈表,current為NULL即*head為NULL的情況下,不執(zhí)行循環(huán)導(dǎo)致 previous為NULL
????????????????? *head=it;
???????? }
???????? else//不是空鏈表
???????? {
????????????????? previous->next= it;
???????? }
}
void printNode(struct Node *head){
???????? struct Node *current;
???????? current=head;
???????? while(current!=NULL){
????????????????? printf("%d ",current->value);
????????????????? current=current->next;
???????? }
???????? printf("\n");
}
int main(){
???????? struct Node *head =NULL;
???????? int input;
???????? while(1){
????????????????? printf("請輸入一個整數(shù)(輸入-1表示結(jié)束):");
????????????????? scanf("%d",&input);
????????????????? if(input==-1){
????????????????????????? break;
????????????????? }
????????????????? insertNode(&head,input);
????????????????? printNode(head);
???????? }?
}
?
單鏈表刪除節(jié)點
void deleteNode(struct Node **head,int value){
???????? struct Node *previous;
???????? struct Node *current;
???????? current = *head;
???????? previous=NULL;
???????? while(current!=NULL&¤t->value!=value){
????????????????? previous=current;
????????????????? current=current->next;
???????? }
???????? if(current==NULL){
????????????????? printf("找不到匹配的節(jié)點");
????????????????? return;
???????? }
???????? else{
????????????????? if(previous==NULL){
????????????????????????? *head=current->next;
????????????????? }
????????????????? else{
????????????????????????? previous->next=current->next;
????????????????? }
????????????????? free(current);
???????? }
}
main函數(shù)修改
???????? printf("開始測試刪除整數(shù)。。。\n");
???????? while(1){
????????????????? printf("請輸入一個整數(shù)(輸入-1表示結(jié)束):");
????????????????? scanf("%d",&input);
????????????????? if(input==-1){
????????????????????????? break;
????????????????? }
????????????????? deleteNode(&head,input);
????????????????? printNode(head);
???????? }?
P48內(nèi)存池
內(nèi)存碎片
?
時間上消耗 應(yīng)用層——內(nèi)核層——應(yīng)用層
?
解決方法 內(nèi)存池 讓程序額外維護的一個緩存區(qū)域
當申請內(nèi)存時檢查內(nèi)存池有沒有適合的垃圾內(nèi)存塊,重新使用
想申請內(nèi)存時
如果內(nèi)存池非空,則直接從里面獲取空間
如果內(nèi)存池為空,則重新申請內(nèi)存空間
想釋放內(nèi)存時
如果內(nèi)存池有空位(小于內(nèi)存池容量),那么將即將廢棄的內(nèi)存塊插入內(nèi)存池
如果內(nèi)存池沒有空位,那么就用free等釋放掉
?
P49基礎(chǔ)typedef
typedef基本功能 給數(shù)據(jù)類型(包括結(jié)構(gòu)體)起別名
typedef A B;//給A取別名B
typedef A B,C;//給A取別名B/C??梢匀《鄠€別名
?
define也可以 #define B A
區(qū)別
define是直接替換
typedef是對類型的封裝。真正的起別名??梢詫χ羔橆愋腿e名。
?
?
課外知識 fortran世界上第一門高級編程語言
?
給結(jié)構(gòu)體取別名
struct Date{
int year;
int month;
int day
};
typedef struct Date DATE;//如果需要,可以同時定義指針typedef struct Date DATE,*PDATE;
或者
typedef struct Date{
int year;
int month;
int day
} DATE;//如果需要,可以同時定義指針
?
P50進階typedef
使用typedef目的一般有兩個
給變量起容易記住且意義明確的別名;
簡化一些比較復(fù)雜的類型聲明。
?
一些比較復(fù)雜的聲明語句
int (*ptr)[3]數(shù)組指針 ptr是一個指針,指向三個整型元素的數(shù)組(數(shù)組起始地址)
哪個是數(shù)組指針,哪個是指針數(shù)組呢:
A)???? int *p1[10];
B)????? int (*p2)[10];
這里需要明白一個符號之間的優(yōu)先級問題。
“[]”的優(yōu)先級比“*”要高。p1 先與“[]”結(jié)合,構(gòu)成一個數(shù)組的定義,數(shù)組名為p1,int *修飾的是數(shù)組的內(nèi)容,即數(shù)組的每個元素。那現(xiàn)在我們清楚,這是一個數(shù)組,其包含10 個指向int 類型數(shù)據(jù)的指針,即指針數(shù)組。
至于p2 就更好理解了,在這里“()”的優(yōu)先級比“[]”高,“*”號和p2 構(gòu)成一個指針的定義,指針變量名為p2,int 修飾的是數(shù)組的內(nèi)容,即數(shù)組的每個元素。數(shù)組在這里并沒有名字,是個匿名數(shù)組。那現(xiàn)在我們清楚p2 是一個指針,它指向一個包含10 個int 類型數(shù)據(jù)的數(shù)組,即數(shù)組指針
#include<stdio.h>
typedef int (*PTR_TO_ARRAY)[3];
int main(){
???????? int array[3]={1,2,3};
???????? PTR_TO_ARRAY ptr_to_array=&array;
???????? int i;
???????? for(i=0;i<3;i++){
????????????????? printf("%d\n",(*ptr_to_array)[i]);
???????? }
???????? return 0;
}
?
int(*fun)(void);函數(shù)指針 指向一個參數(shù)為void,返回值為int的函數(shù)
#include<stdio.h>
typedef int (*PTR_TO_FUN)(void);
int fun(){
???????? return 520;
}
int main(){
???????? PTR_TO_FUN ptr_to_fun=&fun;//直接寫fun也可,函數(shù)名即是地址
???????? printf("%d\n",(*ptr_to_fun)());
???????? return 0;
}
?
int *(*array[3])(int);//*array[3]是指針數(shù)組,int *(*array[3])(int);是指針函數(shù),返回值int參數(shù)int
#include<stdio.h>
typedef int *(*PTR_TO_FUN)(int);
int *funA(int num){
????????????????????????????????????????????????????? printf("%d\t",num);
????????????????????????????????????????????????????? return #//返回無意義,只是測試
}
int *funB(int num){
????????????????????????????????????????????????????? printf("%d\t",num);
????????????????????????????????????????????????????? return #//返回無意義,只是測試
}
int *funC(int num){
????????????????????????????????????????????????????? printf("%d\t",num);
????????????????????????????????????????????????????? return #//返回無意義,只是測試
}
int main(){
????????????????????????????????????????????????????? PTR_TO_FUN array[3]={&funA,&funB,&funC};
????????????????????????????????????????????????????? int i;
????????????????????????????????????????????????????? for(i=0;i<3;i++){
???????????????????????????????????????????????????????????? printf("addr of num:%p\n",(*array[i])(i));
????????????????????????????????????????????????????? }
????????????????????????????????????????????????????? return 0;
}
?
void (*funA(int,void (*funB)(int)))(int);函數(shù)指針
P51共用體
union共用體名稱
{
???????? 共用體成員1;
???????? 共用體成員2;
???????? 共用體成員3;
???????? 。。。
};
?
共用體所有成員共享同一個內(nèi)存地址。地址長度足以容納最大成員的長度。也會受內(nèi)存對齊影響
#include<stdio.h>
#include<string.h>
typedef int *(*PTR_TO_FUN)(int);
union Test{
???????? int i;
???????? double pi;
???????? char str[10];
};
int main(){
???????? union Test test;
???????? test.i=520;
???????? test.pi=3.14;
???????? strcpy(test.str,"FishC.com");
???????? printf("addr of test.i:%p\n",&test.i);
???????? printf("addr of test.pi:%p\n",&test.pi);
???????? printf("addr of test.str:%p\n",&test.str); //輸出結(jié)果幾個地址%p相同
???????? printf("test.i:%d\n",test.i);
???????? printf("test.pi:%.2f\n",test.pi);
???????? printf("test.str:%s\n",test.str); //輸出結(jié)果只有str正確 ,因為前兩個被覆蓋了
???????? printf("size of int:%d\n",sizeof(int));
???????? printf("size of double:%d\n",sizeof(double));
???????? printf("size of test.str:%d\n",sizeof(test.str));
???????? printf("size of test:%d\n",sizeof(test));
???????? return 0;
}
?
定義共用體類型變量,和結(jié)構(gòu)體兩種方式基本相似。
另外,共用體的名字不是必須的
union {
???????? int a;
???????? char b;
} a,b,c;
?
初始化共用體
union data a={520};//初始化第一個成員
union data b=a;//使用一個共用體初始化另一個
union data c={.ch='c'};//C99新特性,指定初始化成員
?
拓展
共用體常用來節(jié)省內(nèi)存,特別是一些嵌入式編程,內(nèi)存是非常寶貴的!
共用體也常用于操作系統(tǒng)數(shù)據(jù)結(jié)構(gòu)或硬件數(shù)據(jù)結(jié)構(gòu)!
union 在操作系統(tǒng)底層的代碼中用的比較多,因為它在內(nèi)存共享布局上方便且直觀。所以網(wǎng)絡(luò)編程,協(xié)議分析,內(nèi)核代碼上有一些用到 union 都比較好懂,簡化了設(shè)計。
共用體(union)是一種數(shù)據(jù)格式,它能夠存儲不同類型的數(shù)據(jù),但是只能同時存儲其中的一種類型。
————————————————
版權(quán)聲明:本文為CSDN博主「狂奔的烏龜」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/xy010902100449/article/details/48292527
P52枚舉類型
一一列舉
如果一個變量只有幾種可能的值,那么就可以將其定義為枚舉(enumeration)類型
?
聲明枚舉類型
enum枚舉類型名稱 {枚舉值名稱,枚舉值名稱。。。};
定義枚舉變量
enum枚舉類型名稱 枚舉變量1,枚舉變量2;
#include<stdio.h>
#include<time.h>
int main(){
???????? enum Week {sun,mon,tue,wed,thu,fri,sat};//默認第一個是0,此時sun=0,mon=1。。。 。也可以指定值,后面的會依次自動加1。enum Week {sun=1,mon,tue,wed,thu,fri,sat};
???????? enum Week today;
???????? struct tm *p;//tm結(jié)構(gòu)體 包含了當?shù)貢r間和日期,其中成員變量int tm_wday 表示星期幾范圍0-6
???????? time_t t;
???????? time(&t);//time函數(shù)返回表示當前時間的time_t
???????? p=localtime(&t);//localtime函數(shù)將time_t類型的值轉(zhuǎn)化為具體的本地時間和日期
???????? today = (enum Week)p->tm_wday;
???????? switch(today){
????????????????? case mon:
????????????????? case tue:
????????????????? case wed:
????????????????? case thu:
????????????????? case fri:
????????????????????????? printf("工作日\n");break;
????????????????? case sat:
????????????????? case sun:
????????????????????????? printf("休息日\n");break;
????????????????? default:printf("Error\n");
???????? }
???????? return 0;
}
拓展
https://blog.csdn.net/L202132061/article/details/79383855
https://blog.csdn.net/L202132061/article/details/79383855
P53位域
單片機 集成電路芯片,把CPU、RAM、ROM、I/O等集成到一塊硅片上構(gòu)成小而完善的微型計算機系統(tǒng)。
節(jié)約空間
位域,或稱位段、位字段。
例如,對一個字節(jié)劃分為幾個部分并命名,這幾部分就是位域
使用位域的做法是 在結(jié)構(gòu)體定義時,在結(jié)構(gòu)體或成員后面使用冒號和數(shù)字來表示該成員所占的位byte數(shù)。
#include<stdio.h>
int main(){
???????? struct Test{
????????????????? unsigned int a:1;
????????????????? unsigned int b:1;
????????????????? unsigned int c:2;
???????? };
???????? struct Test test;
???????? test.a=0;
???????? test.b=1;
???????? test.c=2;
???????? printf("a=%d,b=%d,c=%d\n",test.a,test.b,test.c);
???????? printf("size of test=%d",sizeof(test));
???????? return 0;
}
//a=0,b=1,c=2
//size of test=4
//如果改為 unsigned int c:1;則c=0因為2二進制為10,不夠兩位,只能存后面的0
?
無名位域
位域成員可以沒有名稱,只要給出數(shù)據(jù)類型和寬度即可。
struct Test{
???????? unsigned int x:100;
???????? unsigned int :100//位域成員可以沒有名稱,只要給出數(shù)據(jù)類型和寬度即可。
}
?
?
P54位操作
c語言并沒有規(guī)定一個字節(jié)有幾位(一般是8位),只是規(guī)定“可尋址的數(shù)據(jù)存儲單位,其尺寸必須可以容納運行環(huán)境的基本字符集的任何成員”。一般是由編譯器規(guī)定在limits.h中
?
邏輯位運算符
只作用于整型數(shù)據(jù)
~按位取反
1變0,0變1
?
&按位與
不是邏輯與(&&)。同時為1才是1
?
^按位異或
兩個不同為1,相同為0
?
|按位或
不是邏輯或(||)。有1則1,全0才0
?
和賦值號=結(jié)合
除了按位取反都可以和賦值號結(jié)合
#include<stdio.h>
int main(){
???????? int mask = 0xFF;//0x表示是16進制? FF是15 15即0000 0000 0000 0000 1111 1111?
???????? int v1= 0xABCDEF; //10 11 12 13 14 15 即? ?1010 1011 1100 1101 1110 1111
???????? int v2= 0xABCDEF;
???????? int v3= 0xABCDEF;
???????? v1 &=mask;//即v1=v1&mask;下面兩句也是
???????? v2 |=mask;
???????? v3 ^=mask;
???????? printf("v1=0x%X\nv2=0x%X\nv3=0x%X\n",v1,v2,v3);
???????? return 0;
}
//v1=0xEF????????????????? 0000 0000 0000 0000 1110 1111????????
//v2=0xABCDFF???????? 1010 1011 1100 1101 1110 1111
//v3=0xABCD10???????? 1010 1011 1100 1101 0001 0000
P55移位和位操作的應(yīng)用
移位運算符:
左移位運算符<<
右移位運算符>>
?
左移位運算符<<
左邊操作數(shù)是即將被移位的數(shù)據(jù),右邊是要移動的位數(shù)。移出的數(shù)據(jù)扔掉,移動后空位用0填充
?
右移位運算符<<
左邊操作數(shù)是即將被移位的數(shù)據(jù),右邊是要移動的位數(shù)。移出的數(shù)據(jù)扔掉,移動后空位用0填充
?
和賦值號結(jié)合
#include<stdio.h>
int main(){
???????? int value=1;
???????? printf("-----左移---------\n");
???????? while(value<1024){
????????????????? value<<=1;//value=value<<1;
????????????????? printf("value=%d\n",value);
???????? }
???????? printf("-----右移---------\n");
???????? value=1024;
???????? while(value>1){
????????????????? value>>=1;
????????????????? printf("value=%d\n",value);
???????? }
???????? return 0;
}
?
一些未定義行為
移位運算符右操作數(shù)如果是負數(shù),或右操作數(shù)大于左操作數(shù)支持的最大寬度,那么表達式結(jié)果均屬于“未定義行為”。不同編譯器結(jié)果不同。
有符號和無符號也對移位運算符有不同的影響。有符號數(shù)移動后是否覆蓋符號位決定權(quán)還是在編譯器。
?
位運算符的應(yīng)用
掩碼
配電箱
只開主臥 掩碼10001000 相"&"結(jié)果10001000
打開位 開主臥 掩碼10001000 相"|"結(jié)果11001000
關(guān)閉位 關(guān)閉客廳 掩碼01000000 "~"掩碼10111111 相"&"結(jié)果10001000
轉(zhuǎn)置位 掩碼01001000 "^"結(jié)果10001000
P56打開和關(guān)閉文件
萬物皆文件Everything is a file
KISS原則Keep it simple,stupid.
?
文本文件和二進制文件
文本文件?? 由一些字符的序列組成的文件
二進制文件 通常指除了文本文件以外的
嚴格來說,文本文件也屬于二進制文件。打開方式要區(qū)分開主要是因為換行符。C語言換行符\n,unix系統(tǒng)\n,windows用\r\n,mac用\r。如果在windows系統(tǒng)以文本文件打開,讀會將\r\n自動轉(zhuǎn)換為\n,寫會\n轉(zhuǎn)換為\r\n,但是以二進制模式打開則不會做轉(zhuǎn)換。如果在unix系統(tǒng)二者是一樣的。
?
打開和關(guān)閉文件
讀 從文件中獲取數(shù)據(jù)
寫 將數(shù)據(jù)寫入到文件里
在完成讀寫操作后,必須將其關(guān)閉
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
??????? FILE *fp;
??????? int ch;
?
??????? if ((fp = fopen("hello.txt", "r")) == NULL)
??????? {
??????????????? printf("打開文件失??!\n");
??????????????? exit(EXIT_FAILURE);
??????? }
??????? while ((ch = getc(fp)) != EOF)//EOF通常是-1
??????? {
??????????????? putchar(ch);
??????? }
??????? fclose(fp);//關(guān)閉,釋放緩沖區(qū)
??????? return 0;
}
fopen用于打開文件并返回函數(shù)指針
打開成功,返回指向FILE結(jié)構(gòu)的文件指針;打開失敗,返回NULL并設(shè)置errno為指定的錯誤
fopen(路徑,模式)
路徑參數(shù)可以是相對路徑(./a.txt),也可以是絕對路徑(/home/user1/a.txt)。如果只給出文件名(a.txt)則表示該文件在當前文件夾。
模式參數(shù)
模式
描述
"r"
1. 以只讀的模式打開一個文本文件,從文件頭開始讀取
? 2. 該文本文件必須存在
"w"
1. 以只寫的模式打開一個文本文件,從文件頭開始寫入
? 2. 如果文件不存在則創(chuàng)建一個新的文件
? 3. 如果文件已存在則將文件的長度截斷為 0(重新寫入的內(nèi)容將覆蓋原有的所有內(nèi)容)
"a"
1. 以追加的模式打開一個文本文件,從文件末尾追加內(nèi)容
? 2. 如果文件不存在則創(chuàng)建一個新的文件
"r+"
1. 以讀和寫的模式打開一個文本文件,從文件頭開始讀取和寫入
? 2. 該文件必須存在
? 3. 該模式不會將文件的長度截斷為 0(只覆蓋重新寫入的內(nèi)容,原有的內(nèi)容保留)
"w+"
1. 以讀和寫的模式打開一個文本文件,從文件頭開始讀取和寫入
? 2. 如果文件不存在則創(chuàng)建一個新的文件
? 3. 如果文件已存在則將文件的長度截斷為 0(重新寫入的內(nèi)容將覆蓋原有的所有內(nèi)容)
"a+"
1. 以讀和追加的模式打開一個文本文件
? 2. 如果文件不存在則創(chuàng)建一個新的文件
? 3. 讀取是從文件頭開始,而寫入則是在文件末尾追加
"b"
1. 與上面 6 中模式均可結(jié)合("rb", ? "wb", "ab", "r+b", "w+b", ? "a+b")
? 2. 其描述的含義一樣,只不過操作的對象是二進制文件
?
P57讀寫文件1
順序讀寫和隨機讀寫
?
讀單個字符
fgetc、getc
#include <stdio.h>
? int fgetc( FILE *stream );
fgetc()函數(shù)返回來自stream(流)中的下一個字符
stream參數(shù)是FILE對象的指針,指定一個待讀取的文件流
返回值:
將讀取到的unsigned char類型轉(zhuǎn)換為int并返回
如果到達文件尾或者發(fā)生錯誤時返回EOF.
?
fgetc 函數(shù)和 getc 函數(shù)兩個的功能和描述基本上是一模一樣的,它們的區(qū)別主要在于實現(xiàn)上:fgetc 是一個函數(shù);而 getc 則是一個宏的實現(xiàn)。
一般來說宏產(chǎn)生較大的代碼,但是避免了函數(shù)調(diào)用的堆棧操作,所以速度會比較快。
由于 getc 是由宏實現(xiàn)的,對其參數(shù)可能有不止一次的調(diào)用,所以不能使用帶有副作用(side effects)的參數(shù)。所謂帶有副作用的參數(shù)就是指 getc(fp++) 這類型的參數(shù),因為參數(shù)在宏的實現(xiàn)中可能會被調(diào)用多次,所以你的想法是 fp++,而副作用下產(chǎn)生的結(jié)果可能是 fp++++++。
?
?
寫單個字符fputc,putc
#include <stdio.h>
int fputc(int c, FILE *stream);
c?????? 指定待寫入的字符
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待寫入的文件流
返回值:
如果函數(shù)沒有錯誤,返回值是寫入的字符;
如果函數(shù)發(fā)生錯誤,返回值是EOF
fputc 是一個函數(shù);而 putc 則是一個宏的實現(xiàn)
?
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
??????? FILE *fp1,*fp2;
??????? int ch;
??????? if ((fp1 = fopen("hello.txt", "r")) == NULL)
??????? {
??????????????? printf("打開文件失敗!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? if ((fp2 = fopen("fish.txt", "w")) == NULL)
??????? {
??????????????? printf("打開文件失??!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? while ((ch = getc(fp1)) != EOF)//EOF通常是-1
??????? {
??????????????? fputc(ch,fp2);
??????? }
??????? fclose(fp1);//關(guān)閉,釋放緩沖區(qū)
??????? fclose(fp2);//關(guān)閉,釋放緩沖區(qū)
??????? return 0;
}
?
讀寫整個字符串fgets、fputs
fgets 函數(shù)用于從指定文件中讀取字符串。
fgets 函數(shù)最多可以讀取 size - 1 個字符,因為結(jié)尾處會自動添加一個字符串結(jié)束符 '\0'。當讀取到換行符('\n')或文件結(jié)束符(EOF)時,表示結(jié)束讀取('\n' 會被作為一個合法的字符讀取,EOF不會)。
函數(shù)原型:
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
s?????? 字符型指針,指向用于存放讀取字符串的位置
size?? 指定讀取的字符數(shù)(包括最后自動添加的 '\0')
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待操作的數(shù)據(jù)流
返回值:
1. 如果函數(shù)調(diào)用成功,返回 s 參數(shù)指向的地址。
2. 如果在讀取字符的過程中遇到 EOF,則 eof 指示器被設(shè)置;如果還沒讀入任何字符就遇到這種 EOF,則 s 參數(shù)指向的位置保持原來的內(nèi)容(s不變),函數(shù)返回 NULL。
3. 如果在讀取的過程中發(fā)生錯誤,則 error 指示器被設(shè)置,函數(shù)返回 NULL,但 s 參數(shù)指向的內(nèi)容可能被改變。
?
fputs 函數(shù)用于將一個字符串寫入到指定的文件中,表示字符串結(jié)尾的 '\0' 不會被一并寫入。
#include <stdio.h>
int fputs(const char *s, FILE *stream);
s?????? 字符型指針,指向用于存放待寫入字符串的位置
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待操作的數(shù)據(jù)流
返回值:
如果函數(shù)調(diào)用成功,返回一個非 0 值;(此處錯誤。API文檔里是成功時返回非負值, 失敗時返回EOF)
如果函數(shù)調(diào)用失敗,返回EOF
?
feof 函數(shù)用于檢測文件的末尾指示器(end-of-file indicator)是否被設(shè)置。
#include <stdio.h>
int feof(FILE *stream);
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待檢測的文件流
返回值:
如果檢測到末尾指示器(end-of-file indicator)被設(shè)置,返回一個非 0 值;
如果檢測不到末尾指示器(end-of-file indicator)被設(shè)置,返回值為 0。
feof 函數(shù)僅檢測末尾指示器的值,它們并不會修改文件的位置指示器。
文件末尾指示器只能使用 clearerr 函數(shù)清除。
?
#include <stdio.h>
#include <stdlib.h>
#define MAX 1024
int main()
{
??????? FILE *fp;
??????? char buffer[MAX];
??????? if ((fp = fopen("hello.txt", "w")) == NULL)
??????? {
??????????????? printf("打開文件失??!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? fputs("hello 1111\n",fp);
??????? fputs("hello 2222\n",fp);
??????? fputs("hello 3333\n",fp);
??????? fclose(fp);//關(guān)閉,寫入文件,釋放緩沖區(qū) 。
????????????????? //需要fclose,不關(guān)的話,文件指示器指向文件末尾,影響后面操作 。而且還在緩沖區(qū),還沒寫入文件。
??????? if ((fp = fopen("hello.txt", "r")) == NULL)
??????? {
??????????????? printf("打開文件失敗!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? while(!feof(fp)) //feof檢測不到末尾,返回0 。所以這里是未到末尾 。
??????? {
??????? ?? fgets(buffer,MAX,fp);//每次最多讀取MAX-1個字符,因為結(jié)尾自動添加\0。 讀取到\n或EOF會結(jié)束這一行,\n也會被作為合法字符讀取,EOF不會 。
????????????????????????? printf("%s",buffer);
????????????????? }
???????? ??? return 0;
}
//hello 1111
//hello 2222
//hello 3333
//hello 3333
//出現(xiàn)問題 第四行打印第三行內(nèi)容,但文件hello中并沒有第四行 ,只有回車。
//原因 fgets如果還沒讀入任何字符就遇到 EOF,則 s 參數(shù)指向的位置保持原來的內(nèi)容(s不變),函數(shù)返回 NULL。
//解決? if(!fgets(buffer,MAX,fp)==NULL)printf("%s",buffer);
P58讀寫文件2
格式化讀寫文件
fscanf、fprintf
和scanf、printf相似,只不過是從文件讀取、輸出到文件
?
拓展 為什么scanf中用&取地址符,而printf不用。因為scanf本來就是一個函數(shù),用取地址后就能將接受的數(shù)據(jù)存在這個地址里,在scanf函數(shù)外也能用。指針在函數(shù)內(nèi)就是通過訪問所指向地址的值來進行改寫,并且能延續(xù)到函數(shù)外。
?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
??????? FILE *fp;
??????? struct tm *p;
??????? time_t t;
??????? time(&t);
??????? p=localtime(&t);
??????? if ((fp = fopen("date.txt", "w")) == NULL)
??????? {
??????????????? printf("打開文件失敗!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? fprintf(fp,"%d-%d-%d",1900+p->tm_year,1+p->tm_mon,p->tm_mday);
??????? fclose(fp);
??????? int year,month,day;
??????? if ((fp = fopen("date.txt", "r")) == NULL)
??????? {
??????????????? printf("打開文件失??!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
??????? fscanf(fp,"%d-%d-%d",&year,&month,&day);
??????? printf("%d-%d-%d",year,month,day);
??????? fclose(fp);
???????? ??? return 0;
}
?
以二進制方式讀寫文本文件
#include <stdio.h>
#include <stdlib.h>
int main()
{
??????? FILE *fp;
??????? if ((fp = fopen("text.txt", "wb")) == NULL)
??????? {
??????????????? printf("打開文件失??!\n");
??????????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??????? }
????????????????? fputc('5',fp);
????????????????? fputc('2',fp);
????????????????? fputc('0',fp);
????????????????? fputc('\n',fp);
??????? fclose(fp);
???????? ??? return 0;
}
?
二進制讀寫文件fread、fwrite
fread 函數(shù)用于從指定的文件中讀取指定尺寸的數(shù)據(jù)。
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr??? 指向存放數(shù)據(jù)的內(nèi)存塊指針,該內(nèi)存塊的尺寸最小應(yīng)該是 size * nmemb 個字節(jié)
size?? 指定要讀取的每個元素的尺寸,最終尺寸等于 size * nmemb
nmemb???? 指定要讀取的元素個數(shù),最終尺寸等于 size * nmemb
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待讀取的文件流
返回值:
1. 返回值是實際讀取到的元素個數(shù)(nmemb);
2. 如果返回值比 nmemb 參數(shù)的值小,表示可能讀取到文件末尾或者有錯誤發(fā)生(可以使用 feof 函數(shù)或 ferror 函數(shù)進一步判斷)。
?
fwrite 函數(shù)用于將指定尺寸的數(shù)據(jù)寫入到指定的文件中。
函數(shù)原型:
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr??? 指向存放數(shù)據(jù)的內(nèi)存塊指針,該內(nèi)存塊的尺寸最小應(yīng)該是 size * nmemb 個字節(jié)
size?? 指定要寫入的每個元素的尺寸,最終尺寸等于 size * nmemb
nmemb???? 指定要寫入的元素個數(shù),最終尺寸等于 size * nmemb
stream????? 該參數(shù)是一個 FILE 對象的指針,指定一個待寫入的文件流
返回值:
1. 返回值是實際寫入到文件中的元素個數(shù)(nmemb);
2. 如果返回值與 nmemb 參數(shù)的值不同,則有錯誤發(fā)生。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Date{
???????? int year;
???????? int month;
???????? int day;
};
struct Book{
???????? char name[40];
???????? char author[40];
???????? char publisher[40];
???????? struct Date date;
};
int main()
{
??? FILE *fp;
?? ?struct Book *book_for_write,*book_for_read;
??? book_for_write=(struct Book *)malloc(sizeof(struct Book));
??? book_for_read=(struct Book *)malloc(sizeof(struct Book));
??? if(book_for_write==NULL||book_for_read==NULL)
??? {
??????? printf("內(nèi)存分配失敗");
????????????????? exit(EXIT_FAILURE);
???????? }
???????? strcpy(book_for_write->name,"帶你學c帶你飛");
???????? strcpy(book_for_write->author,"小甲魚");
???????? strcpy(book_for_write->publisher,"清華大學出版社");
???????? book_for_write->date.year=2017;
???????? book_for_write->date.month=11;
???????? book_for_write->date.day=11;
???????? if ((fp = fopen("file.txt", "w")) == NULL)
??? {
??????????? printf("打開文件失?。n");
??????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??? }
???????? fwrite(book_for_write,sizeof(struct Book),1,fp);
???????? fclose(fp);
???????? if ((fp = fopen("file.txt", "r")) == NULL)
??? {
??????????? printf("打開文件失??!\n");
??????????? exit(EXIT_FAILURE);//exit() 需要stdlib.h
??? }
???????? fread(book_for_read,sizeof(struct Book),1,fp);
???????? printf("書名:%s\n",book_for_read->name);
???????? printf("作者:%s\n",book_for_read->author);
???????? printf("出版社:%s\n",book_for_read->publisher);
???????? printf("出版日期:%d-%d-%d\n",book_for_read->date.year,book_for_read->date.month,book_for_read->date.day);
???????? fclose(fp);?
???????? return 0;
}
P59隨機讀寫文件
ftell返回給定流 stream 的當前文件位置。
#include <stdio.h>
#include <stdlib.h>
int main()
{
??? FILE *fp;
??? if((fp=fopen("hello.txt","w"))==NULL){
??? ?printf("文件打開失敗!\n");
??? ?exit(EXIT_FAILURE);
???????? }
???????? printf("%ld\n",ftell(fp));
???????? fputc('F',fp);
???????? printf("%ld\n",ftell(fp));
???????? fputs("ishC\n",fp);
???????? printf("%ld\n",ftell(fp));
???????? fclose(fp);
???????? return 0;
}
//0開頭
//1
//7因為win系統(tǒng)換行 \r\n 所以1+6
?
rewind移動到文件頭
上面程序添加
rewind(fp);
fputs("Hello",fp);
會把原來的內(nèi)容覆蓋掉
?
fseek用于設(shè)置文件流的位置指示器
#include<stdio.h>
int fseek(FILE *stream,long int offset,int whence);
stream -- 這是指向 FILE 對象的指針,該 FILE 對象標識了流。
offset -- 這是相對 whence 的偏移量,以字節(jié)為單位。
whence -- 這是表示開始添加偏移 offset 的位置。它一般指定為下列常量之一:
SEEK_SET? 文件的開頭
SEEK_CUR 文件指針的當前位置
SEEK_END 文件的末尾
返回值
如果成功,則該函數(shù)返回零,否則返回非零值。
#include <stdio.h>
#include <stdlib.h>
#define N 4
struct Stu{
???????? char name[24];
???????? int num;
???????? float score;
}stu[N],sb;
int main()
{
??? FILE *fp;
??? int i;
??? if((fp=fopen("score.txt","w"))==NULL){
??? ?printf("打開文件失?。n");
??? ?exit(EXIT_FAILURE);
???????? }
???????? for(i=0;i<N;i++){
????????????????? printf("請開始錄入成績(格式:姓名 學號 成績)");
????????????????? scanf("%s %d %f",stu[i].name,&stu[i].num,&stu[i].score);
???????? }
???????? fwrite(stu,sizeof(struct Stu),N,fp);
???????? fclose(fp);
???????? if((fp=fopen("score.txt","rb"))==NULL){
????????????????? printf("打開文件失?。?#34;);
????????????????? exit(EXIT_FAILURE);
???????? }
???????? fseek(fp,sizeof(struct Stu),SEEK_SET);
???????? fread(&sb,sizeof(struct Stu),1,fp);
???????? printf("%s(%d)的成績是:%.2f\n",sb.name,sb.num,sb.score);
???????? fclose(fp);
???????? return 0;
}
?
可移植性問題
對于以二進制模式打開的文件,fseek在某些操作系統(tǒng)中可能不支持SEEK_END位置。
對于以文本模式打開的文件,feek函數(shù)的whence參數(shù)只能取SEEK_SET才是有意義的,并且傳遞給offset參數(shù)的值要么是0,要么是上一次對同一個文件調(diào)用ftell函數(shù)獲取的返回值。
?
P60標準流和錯誤處理
標準流
標準輸入stdin
標準輸出stdout
標準錯誤輸出stderr
?
#include <stdio.h>
#include <stdlib.h>
int main()
{
???????? FILE *fp;
???????? if((fp=fopen("壓根都不存在的文件.txt","r"))==NULL){
????????????????? fputs("打開文件失?。n",stderr);
????????????????? exit(EXIT_FAILURE);
???????? }
???????? fclose(fp);
???????? return 0;
}
?
錯誤處理
錯誤指示器ferror
使用clearerr函數(shù)可以人為的清除文件末尾指示器和錯誤指示器狀態(tài)
ferror函數(shù)只能檢測是否出錯,但無法獲取錯誤原因。不過,大多數(shù)系統(tǒng)函數(shù)在出現(xiàn)錯誤時會將錯誤原因就在errno中。
perror函數(shù)可以直觀的打印出錯誤原因。會自己加"冒號空格錯誤原因"。例如perror("錯誤原因是");輸出為"錯誤原因是: Bad file descriptor"
strerror函數(shù)直接返回錯誤碼對應(yīng)的錯誤消息。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include<string.h>
int main()
{
???????? FILE *fp;
???????? int ch;
???????? if((fp=fopen("hello.txt","r"))==NULL){
????????????????? fputs("打開文件失??!\n",stderr);
????????????????? exit(EXIT_FAILURE);
???????? }
???????? while(1){
????????????????? ch=fgetc(fp);
????????????????? if(feof(fp)){
????????????????????????? break;
????????????????? }
????????????????? putchar(ch);
???????? }
???????? fputc('c',fp);//因為只讀的,所以會失敗,觸發(fā)ferro
???????? if(ferror(fp)){
????????????????? fputs("出錯了,這條消息輸出到錯誤輸出流\n",stderr);
????????????????? printf("使用errno得到的錯誤代碼:%d\n",errno); //errno.h
????????????????? perror("使用perror得到的錯誤原因是"); //stdio.h
????????????????? //fputs("出錯了,,原因是%s\n",stderr);
????????????????? printf("使用strerror得到的錯誤原因是:%s\n",strerror(errno));//strerror需要<string.h>
???????? }
???????? clearerr(fp);
???????? if(feof(fp)||ferror(fp)){
????????????????? printf("123456");//不打印。末尾指示器和錯誤指示器被clear清除掉,不會觸發(fā)
???????? }???????
???????? fclose(fp);
???????? return 0;
}
輸出結(jié)果:
Hello
出錯了,這條消息輸出到錯誤輸出流
使用errno得到的錯誤代碼:9
使用perror得到的錯誤原因是: Bad file descriptor
使用strerror得到的錯誤原因是:Bad file descriptor
P61 IO緩沖區(qū)
IO緩沖區(qū)
#include<stdio.h>
#include<stdlib.h>
int main(){
???????? FILE *fp;
???????? if((fp=fopen("output.txt","w"))==NULL){
????????????????? perror("打開文件失敗,原因");
????????????????? exit(EXIT_FAILURE);
???????? }
???????? fputs("I Love FishC.com\n",fp);//寫在了緩沖區(qū)里,并沒有寫入文件
???????? getchar();//不輸入字符,這時看output是空的 。輸入后 ,下一步執(zhí)行fclose才寫入文件
???????? fclose(fp);
???????? return 0;
}
?
標準IO提供三種類型的緩沖模式
按塊緩存 也稱為全緩存,在填滿緩沖區(qū)后才進行實際的設(shè)備讀寫操作;
按行緩存 是指在接收到換行符\n之前,數(shù)據(jù)都是先緩存在緩沖區(qū)的;
不緩存 允許直接讀寫設(shè)備上的數(shù)據(jù)
?
setvbuf()定義流 stream 應(yīng)如何緩沖。
#include<stdio.h>
int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
參數(shù)
stream -- 這是指向 FILE 對象的指針,該 FILE 對象標識了一個打開的流。
buffer -- 這是分配給用戶的緩沖。如果設(shè)置為 NULL,該函數(shù)會自動分配一個指定大小的緩沖。
mode -- 這指定了文件緩沖的模式:
模式 描述
_IOFBF?????? 全緩沖:對于輸出,數(shù)據(jù)在緩沖填滿時被一次性寫入。對于輸入,緩沖會在請求輸入且緩沖為空時被填充。
_IOLBF?????? 行緩沖:對于輸出,數(shù)據(jù)在遇到換行符或者在緩沖填滿時被寫入,具體視情況而定。對于輸入,緩沖會在請求輸入且緩沖為空時被填充,直到遇到下一個換行符。
_IONBF????? 無緩沖:不使用緩沖。每個 I/O 操作都被即時寫入。buffer 和 size 參數(shù)被忽略。
size --這是緩沖的大小,以字節(jié)為單位。
返回值
如果成功,則該函數(shù)返回 0,否則返回非零值。
?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
???????? char buff[1024];
???????? memset(buff,'\0',sizeof(buff));//memset使用一個常量字節(jié)填充內(nèi)存空間。需要string.h
???????? setvbuf(stdout,buff,_IOFBF,1024);//修改為_IONBF 后會都輸出
???????? fprintf(stdout,"welcome to fishc.com");
???????? fflush(stdout);// 刷新緩沖區(qū)會立即輸出
???????? fprintf(stdout,"輸入任意字符后才會顯示該行字符\n");
???????? getchar();
???????? return 0;
}
? ? ??