循環語句
1循環類型
- 完成任務
1. while循環:
- 當給定條件爲真時,重複語句或語句組。它會在執行循環主體之前測試條件(while循環的特點是先判斷條件表達式,後執行循環體語句)
1.while語句可簡單地記爲: 只要當循環條件表達式爲真(即給定的條件成立),就執行循環體語句。
2.“語句”就是循環體。循環體可以是一個簡單的語句,可以是複合語句(用花括號括起來的若干語句)。
3.執行循環體的次數是由循環條件控制的,這個循環條件就是上面一般形式中的“表達式”,它也稱爲循環條件表達式。當此表達式的值爲“真” (以非0值表示)時,就執行循環體語句;爲“假” (以0表示)時,就不執行循環體語句。
- C 語言中 while 循環的語法:
while(condition)
{
statement(s);
}
在這裏,statement(s) 可以是一個單獨的語句,也可以是幾個語句組成的代碼塊。
condition 可以是任意的表達式,當爲任意非零值時都爲 true。當條件爲 true 時執行循環。 當條件爲 false 時,退出循環,程序流將繼續執行緊接着循環的下一條語句
#include<stdio.h>
int main()
{
int i=1,sum=0; //定義變量i的初值爲1,sum的初值爲0
while(i<=100) //當i>100,條件表達式i<=100的值爲假,不執行循環體
{ //循環體開始
sum=sum+i; //第1次累加後,sum的值爲1
i++; //加完後,i的值加1,爲下次累加做準備
} //循環體結束
printf("sum=%d\n",sum); //輸出1+2+3…+100的累加和
return 0;
}
(1) 循環體如果包含一個以上的語句,應該用花括號括起來,作爲複合語句出現。如果不加花括號,則while語句的範圍只到while後面第一個分號處。
(2) 不要忽略給i和sum賦初值,否則它們的值是不可預測的,結果顯然不正確。
(3) 在循環體中應有使循環趨向於結束的語句。如本例中的“i++;”語句。如果無此語句,則i的值始終不改變,循環永遠不結束。
2. for循環:
- 多次執行一個語句序列,簡化管理循環變量的代碼
- C 語言中 for 循環的語法:
for ( init; condition; increment )
{
statement(s);
}
1.init 會首先被執行,且只會執行一次。這一步允許您聲明並初始化任何循環控制變量。您也可以不在這裏寫任何語句,只要有一個分號出現即可。
2.接下來,會判斷 condition。如果爲真,則執行循環主體。如果爲假,則不執行循環主體,且控制流會跳轉到緊接着 for 循環的下一條語句。
3.在執行完 for 循環主體後,控制流會跳回上面的 increment 語句。該語句允許您更新循環控制變量。該語句可以留空,只要在條件後有一個分號出現即可。
4.條件再次被判斷。如果爲真,則執行循環,這個過程會不斷重複(循環主體,然後增加步值,再然後重新判斷條件)。在條件變爲假時,for 循環終止。
#include<stdio.h>
int main()
{
int i=1,sum=0;
for(i=1;i<=100;i++)
sum=sum+i; //實現迭代
printf("%d\n",sum);
return 0;
}
- for無限循環:
當條件表達式不存在時,它被假設爲真。您也可以設置一個初始值和增量表達式,但是一般情況下,C 程序員偏向於使用 for(;😉 結構來表示一個無限循環。
3.do…while循環
-
除了它是在循環主體結尾測試條件外,其他與 while 語句類似在 C 語言中,do…while 循環是在循環的尾部檢查它的條件(do…while語句的特點是,先無條件地執行循環體,然後判斷循環條件是否成立)。
-
do…while 循環的語法:
do
{
statement(s);
}while( condition );
1 條件表達式出現在循環的尾部,所以循環中的 statement(s) 會在條件被測試之前至少執行一次。
2如果條件爲真,控制流會跳轉回上面的 do,然後重新執行循環中的 statement(s)。這個過程會不斷重複,直到給定條件變爲假爲止。
#include <stdio.h>
int main()
{
int i=1,sum=0;
do
{
sum=sum+i;
i++;
}while(i<=100);
printf("sum=%d\n",sum);
return 0;
}
- 嵌套循環
您可以在 while、for 或 do…while 循環內使用一個或多個循環
循環控制語句
可以改變你代碼的執行順序。通過它你可以實現代碼的跳轉。
1.break 語句:
- 終止循環或 switch 語句,程序流將繼續執行緊接着循環或 switch 的下一條語句。
- break 語句有以下兩種用法:
1.當 break 語句出現在一個循環內時,循環會立即終止,且程序流將繼續執行緊接着循環的下一條語句。
2.它可用於終止 switch 語句中的一個 case
如果是嵌套循環(即一個循環內嵌套另一個循環),break 語句會停止執行最內層的循環,然後開始執行該塊之後的下一行代碼。
2.continue 語句:
- 告訴一個循環體立刻停止本次循環迭代,重新開始下次循環迭代。
1.對於 for 循環,continue 語句執行後自增語句仍然會執行。
2.對於 while 和 do…while 循環,continue 語句重新執行條件判斷語句。 - continue 語句的語法:在語句後面加:continue ;
3.goto 語句:
- 將控制轉移到被標記的語句。但是不建議在程序中使用 goto 語句。
在任何編程語言中,都不建議使用 goto 語句。因爲它使得程序的控制流難以跟蹤,使程序難以理解和難以修改。任何使用 goto語句的程序可以改寫成不需要使用 goto 語句的寫法。
- goto語句的語法:
goto label;
..
.
label: statement;
IO緩衝區
- 完成任務
1. 標準IO三種緩存模式
***1、按塊緩存;2、按行緩存;3、不緩存***
1. 按塊緩存也稱全緩存,即在填滿緩衝區後才進行實際的設備讀寫操作;
2. 按行緩存是指在接收到換行符('\n')之前,數據都是先緩存在緩衝區的;
3. 不緩存也就是允許你直接讀寫設備上的數據。
2.標準文件
標準文件 | 文件指針 | 設備 |
---|---|---|
標準輸入 | stdin | 鍵盤 |
標準輸出 | stdout | 屏幕 |
標準錯誤 | stderr | 您的屏幕 |
文件指針是訪問文件的方式;
C 語言中的 I/O 通常使用 printf() 和 scanf() 兩個函數;
scanf() 函數用於從標準輸入(鍵盤)讀取並格式化;
printf() 函數發送格式化輸出到標準輸出(屏幕)。
3.getchar() & putchar() 函數
1. int getchar(void)函數:
getchar有一個int型的返回值,當程序調用getchar時,程序就等着用戶按鍵.用戶輸入的字符被存放在鍵盤緩衝區中,直到用戶按回車爲止(回車字符也放在緩衝區中)。當用戶鍵入回車之後,getchar纔開始從stdio流中每次讀入一個字符,getchar函數的返回值是用戶輸入的第一個字符的ASCII碼並把它返回爲一個整數。我們可以在循環內使用這個方法,以便從屏幕上讀取多個字符。
2. int putchar(int c)函數
把字符輸出到屏幕上,並返回相同的字符。這個函數在同一個時間內只會輸出一個單一的字符。同樣我們可以在循環內使用這個方法,以便在屏幕上輸出多個字符。
4.gets() & puts() 函數
char *gets(char *s) 函數從 stdin 讀取一行到 s 所指向的緩衝區,直到一個終止符或 EOF。
int puts(const char *s) 函數把字符串 s 和一個尾隨的換行符寫入到 stdout。
5.scanf() 和 printf() 函數
int scanf(const char *format, …) 函數從標準輸入流 stdin 讀取輸入,並根據提供的 format 來瀏覽輸入。
int printf(const char *format, …) 函數把輸出寫入到標準輸出流 stdout ,並根據提供的格式產生輸出。
format 可以是一個簡單的常量字符串,但是您可以分別指定 %s、%d、%c、%f 等來輸出或讀取字符串、整數、字符或浮點數。還有許多其他可用的格式選項,可以根據需要使用。
scanf() 期待輸入的格式與您給出的 %s 和 %d 相同,這意味着您必須提供有效的輸入,比如 “string integer”,如果您提供的是 “string string” 或 “integer integer”,它會被認爲是錯誤的輸入。另外,在讀取字符串時,只要遇到一個空格,scanf() 就會停止讀取,所以 “this is test” 對 scanf() 來說是三個字符
結構體
- 完成任務
C語言允許用戶自己建立由不同類型數據組成的組合型的數據結構,它稱爲結構體(structre)。
1.定義結構體格式:
struct 結構體名
{
成員表列
};
結構體類型的名字是由一個關鍵字struct和結構體名組合而成的。結構體名由用戶指定,又稱“結構體標記”(structure tag) 。
花括號內是該結構體所包括的子項,稱爲結構體的成員(member)。對各成員都應進行類型聲明,即類型名 成員名
“成員表列”(member list)也稱爲“域表”(field list),每一個成員是結構體中的一個域。成員名命名規則與變量名相同。
2.定義結構體類型變量
結構體變量所佔的內存長度是各成員佔的內存長度之和,每一個成員分別佔有其自己的內存單元
1.先聲明結構體類型在定義變量名
struct Student
{ int num; //學號爲整型
char name[20]; //姓名爲字符串
char sex; //性別爲字符型
int age; //年齡爲整型
float score; //成績爲實型
char addr[30]; //地址爲字符串
}; //注意最後有一
struct student student1,student2;
定義了student1和student2爲struct student類型的變量,它們具有struct student類型的結構
2.在聲明類型的同時定義變量
struct Student
{ int num; //學號爲整型
char name[20]; //姓名爲字符串
char sex; //性別爲字符型
int age; //年齡爲整型
float score; //成績爲實型
char addr[30]; //地址爲字符串
}student1,student2;
3.直接定義結構體類型變量
sttuct
{
成員表
} 變量名列表;
- 結構體類型與結構體變量是不同的概念,不要混淆。只能對變量賦值、存取或運算,而不能對一個類型賦值、存取或運算。在編譯時,對類型是不分配空間的,只對變量分配空間。
- 結構體類型中的成員名可以與程序中的變量名相同,但二者不代表同一對象。
- 對結構體變量中的成員(即“域”),可以單獨使用,它的作用與地位相當於普通變量
3.結構體變量的初始化和引用
- 在定義結構體變量時可以對它的成員初始化。初始化列表是用花括號括起來的一些常量,這些常量依次賦給結構體變量中的各成員。
- 可以引用結構體變量中成員的值,引用方式爲結構體變量名.成員名 對結構體變量初始化,不是對結構體類型初始化
a.num=10010;
//已定義a爲student類型的結構體變量,則a.num表示student1變量中的num成員
“.”是成員運算符,它在所有的運算符中優先級最高,因此可以把a.num作爲一個整體來看待,相當於一個變量。
不能以輸出結構體變量名來輸出結構體變量所有成員的值。只能對結構體變量中的各個成員分別進行輸入和輸出。
- 如果結構體中某一成員又屬於另一個結構體類型,則要用若干個成員運算符,一級一級地找到最低的一級的成員。只能對最低級的成員進行賦值或存取以及運算。
a.num (結構體變量a中的成員num)
a.birthday.month (結構體變量a中的成員birthday中的成員month)
- 對結構體變量的成員可以像普通變量一樣進行各種運算(根據其類型決定可以進行的運算)
b.score=a.score; (賦值運算)
sum=a.score+b.score;(加法運算)
a.age++; (自加運算
- 同類的結構體變量可以互相賦值。
b=a; (假設a和b已定義爲同類型的結構體變量)
- 可以引用結構體變量成員的地址,也可以引用結構體變量的地址
scanf(″%d″,&a.num); (輸入student1.num的值)
printf(″%o″,&a); (輸出結構體變量student1的起始地址)
但不能用以下語句整體讀入結構體變量:scanf(″%d,%s,%c,%d,%f,%s\n″,&student);
#include <stdio.h>
int main()
{ struct Student //聲明結構體類型struct Student
{ int num;
char name[20];
float score;
}student1,student2; //定義兩個結構體變量student1,student2
scanf("%d%s%f",&student1.num,student1.name,&student1.score); //輸入學生1的數據
scanf("%d%s%f",&student2.num,student2.name,&student2.score); //輸入學生2的數據
printf("The higher score is:\n");
if(student1.score>student2.score)
printf("%d %s %6.2f\n",student1.num,student1.name,student1.score);
else if(student1.score<student2.score)
printf("%d %s %6.2f\n",student2.num,student2.name,student2.score);
else
{ printf("%d %s %6.2f\n",student1.num,student1.name,student1.score);
printf("%d %s %6.2f\n",student2.num,student2.name,student2.score);
}
return 0;
}
4.使用結構體數組
1.定義結構體數組一般形式是:
- struct 結構體名 {成員表列} 數組名[數組長度];
struct Person
{ char name[20];
int count;
} leader[3];
- 先聲明一個結構體類型,然後再用此類型定義結構體數組
結構體類型數組名[數組長度];
struct Person
{ char name[20];
int count;
}
struct Person leader[3]; //leader是結構體數組名
2.對結構體數組初始化:
對結構體數組初始化的形式是在定義數組的後面加上:
={初值表列};
struct Person leader[3]= {"Li",0,"Zhang",0,"Fan",0};
#include <stdio.h>
struct Student //聲明結構體類型struct Student
{ int num;
char name[20];
float score;
};
int main()
{ struct Student stu[5]={{10101,"Zhang",78},{10103,"Wang",98.5},{10106,"Li",86},
{10108,"Ling",73.5},{10110,"Fan",100}}; //定義結構體數組並初始化
struct Student temp; //定義結構體變量temp,用作交換時的臨時變量
int i,j,k,n=5;
printf("The order is:\n");
for(i=0;i<n-1;i++)
{ k=i;
for(j=i+1;j<n;j++)
if(stu[j].score>stu[k].score) //進行成績的比較
k=j;
temp=stu[k]; stu[k]=stu[i]; stu[i]=temp; //stu[k]和stu[i]元素互換
}
for(i=0;i<n;i++)
printf("%6d %8s %6.2f\n",stu[i].num,stu[i].name,stu[i].score);
printf("\n");
return 0;
}
5.結構體指針
一個結構體變量的起始地址就是這個結構體變量的指針。如果把一個結構體變量的起始地址存放在一個指針變量中,那麼,這個指針變量就指向該結構體變量。
1.指向結構體變量的指針:
1.結構體指針變量說明的一般形式爲:
struct 結構名 *結構指針變量名
2.賦值是吧結構變量的首地址賦予該指針變量,不能把結構名賦予該指針變量。
3.訪問的一般形式:
- ( * 結構指針變量).成員名
- 結構指針變量->成員名
如果p指向一個結構體變量stu,以下3種用法等價:
① stu.成員名
stu.num
② (*p).成員名
(*p).num
③ p->成員名
p->num
2.指向結構體數組的指針:
#include <stdio.h>
struct Student //聲明結構體類型struct Student
{ int num;
char name[20];
char sex;
int age;
};
struct Student stu[3]={{10101,"Li Lin",'M',18},{10102,"Zhang Fang",'M',19},{10104,"Wang Min",'F',20}};
//定義結構體數組並初始化
int main()
{ struct Student *p; //定義指向struct Student結構體變量的指針變量
printf(" No. Name sex age\n");
for (p=stu;p<stu+3;p++)
printf("%5d %-20s %2c %4d\n",p->num, p->name, p->sex, p->age); //輸出結果
return 0;
}
3.用結構體變量和結構體變量的指針作函數參數
1.把一個結構體變量的值傳遞給另一個函數,有3種方法:
(1) 用結構體變量的成員作參數。
例如,用stu[1].num或stu[2].name作函數實參,將實參值傳給形參。用法和用普通變量作實參是一樣的,屬於“值傳遞”方式。應當注意實參與形參的類型保持一致。
(2) 用結構體變量作實參。
用結構體變量作實參時,採取的也是“值傳遞”的方式,將結構體變量所佔的內存單元的內容全部按順序傳遞給形參,形參也必須是同類型的結構體變量。在函數調用期間形參也要佔用內存單元。這種傳遞方式在空間和時間上開銷較大,如果結構體的規模很大時,開銷是很可觀的。此外,由於採用值傳遞方式,如果在執行被調用函數期間改變了形參(也是結構體變量)的值,該值不能返回主調函數,這往往造成使用上的不便。因此一般較少用這種方法。
(3) 用指向結構體變量(或數組元素)的指針作實參,將結構體變量(或數組元素)的地址傳給形參。
6. 用指針處理鏈表
1.鏈表是一種重要的數據結構,是動態地進行存儲分配的一種結構:
鏈表有一個“頭指針”變量,它存放一個地址,該地址指向一個結構體變量。
鏈表中每一個結構體變量稱爲“結點”,每個結點都應包括兩個部分:
① 用戶需要用的實際數據;
② 下一個結點的地址。
頭指針指向第1個元素,第1個元素又指向第2個元素……直到最後一個元素,該元素不再指向其他元素,它稱爲“表尾”,它的地址部分放一個“NULL”(表示“空地址”),鏈表到此結束。
2.可以用結構體變量建立鏈表
一個結構體變量包含若干成員,這些成員可以是數值類型、字符類型、數組類型,也可以是指針類型。用指針類型成員來存放下一個結點的地址。
struct Student
{ int num;
float score;
struct Student *next; //next是指針變量,指向結構體變量
};
成員num和score用來存放結點中的有用數據(用戶需要用到的數據)。
next是指針類型的成員,它指向struct Student類型數據(即next所在的結構體類型)。
3.建立簡單的靜態鏈表:
#include <stdio.h>
struct Student //聲明結構體類型struct Student
{
int num;
float score;
struct Student* next;
};
int main()
{
struct Student a, b, c, * head, * p; //定義3個結構體變量a,b,c作爲鏈表的結點
a.num = 10101; a.score = 89.5; //對結點a的num和score成員賦值
b.num = 10103; b.score = 90; //對結點b的num和score成員賦值
c.num = 10107; c.score = 85; //對結點c的num和score成員賦值
head = &a; //將結點a的起始地址賦給頭指針head
a.next = &b; //將結點b的起始地址賦給a結點的next成員
b.next = &c; //將結點c的起始地址賦給a結點的next成員
c.next = NULL; //c結點的next成員不存放其他結點地址
p = head; //使p指向a結點
do
{
printf("%ld %5.1f\n", p->num, p->score); //輸出p指向的結點的數據
p = p->next; //使p指向下一結點
} while (p != NULL); //輸出完c結點後p的值爲NULL,循環終止
return 0;
}
4.建立動態鏈表:
所謂建立動態鏈表是指在程序執行過程中從無到有地建立起一個鏈表,即一個一個地開闢結點和輸入各結點數據,並建立起前後相鏈的關係。
建立一個有3名學生數據的單向動態鏈表 |
---|
開闢一個新結點,並使 p1,p2指向它 |
讀入一個學生數據給p1所指的結點 |
head=NULL,n=0 |
當讀入的p1->num不是零 |
n=n+1 |
n等於1? |
1.真 head=p1(把p1所指的結點作爲第一個結點) |
2.假 p2->next=p1(把p1所指的結點連接到表尾) |
p2=p1(p2移到表尾) |
再開闢一個新結點,使p1指向它 |
讀入一個學生數據給p1所指結點 |
表尾結點的指針變量置NULL |
5.輸出鏈表
- List item
概覽
1.C語言中的數據類型分爲兩類: ①系統已經建立好的標準數據類型(如int,char,float,double等),編程者不必自己建立,可以直接用它們去定義變量。②是用戶根據需要在一定的框架範圍內自己建立的類型,先要向系統作出聲明,然後才能用它們定義變量。
2. 聲明結構體類型時,系統並不對其分配存儲空間,只有在用結構體類型定義變量時纔對變量分配存儲空間。結構體類型常用於事務管理領域,把屬於同一個對象的若干屬性放在同一個結構體變量中,便於處理。
3.同類結構體變量可以互相賦值,但不能用結構體變量名對結構體變量進行整體輸入和輸出。可以對結構體變量中的成員進行賦值、比較、輸入和輸出等操作。引用結構體變量中的成員的方式有: ① 結構體變量.成員名,如student1.age。
4.結構體變量的指針就是結構體變量的起始地址,可以定義指向結構體變量的指針變量,這個變量的值是結構體變量的起始地址。指向結構體變量的指針變量常用於函數參數和鏈表中(用來指向下一個結點)。
5.把結構體變量和指向結構體變量的指針結合起來,可以建立動態數據結構(如鏈表)。開闢動態內存空間要用malloc和calloc函數,函數的返回值是所開闢的空間的起始地址。利用所開闢的空間作爲鏈表的一個結點,這個結點是一個結構體變量,其成員由兩部分組成: 一部分是實際的有用數據,另一部分是一個指向結構體類數據的指針變量,利用它指向下一個結點。
共用體
共用體是一種特殊的數據類型,允許在相同的內存位置存儲不同的數據類型。可以定義一個帶有多成員的共用體,但是任何時候只能有一個成員帶有值。共用體提供了一種使用相同的內存位置的有效方式。
共用體變量所佔的內存長度等於最長的成員的長度。
1.定義共用體
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
2.訪問共用體成員
爲了訪問共用體的成員,我們使用訪問運算符(.)。成員訪問運算符是共用體變量名稱和我們要訪問的共用體成員之間的一個句號
只有先定義了共用體變量才能引用它,而且不能引用共用體變量,而且只能引用共用體變量的成員。
a.i
a.ch
a.f
位域
- 完成任務
1.位域聲明
struct
{
type [member_name] : width ;
};
2.位域中變量元素的描述:
元素 | 描述 |
---|---|
type | 只能爲 int(整型),unsigned int(無符號整型),signed int(有符號整型) 三種類型,決定了如何解釋位域的值 |
member_name | 位域的名稱 |
width | 位域中位的數量。寬度必須小於或等於指定類型的位寬度 |
枚舉類型
- 完成任務
枚舉是 C 語言中的一種基本數據類型,它可以讓數據更簡潔,更易讀。
1.枚舉類型定義
enum 枚舉類型名稱 {枚舉變量1,枚舉變量2,...};
第一個枚舉成員的默認值爲整型的 0後續枚舉成員的值在前一個成員上加 1。
可以在定義枚舉類型時改變枚舉元素的值:
enum Week {SUN, MON=3, TUE, WED, THU, FRI, SAT};
提示:SUN=0,MON=3, TUE=4, WED=5, THU=6, FRI=7, SAT=8};
2.枚舉變量的定義
1. 先定義枚舉類型,再定義枚舉變量:
enum DAY
{
SUN, MON=1, TUE, WED, THU, FRI, SAT
};
enum DAY day;
2. 先定義枚舉類型,再定義枚舉變量:
enum DAY
{
SUN, MON=1, TUE, WED, THU, FRI, SAT
}day;
3. 省略枚舉名稱,直接定義枚舉變量:
enum
{
SUN, MON=1, TUE, WED, THU, FRI, SAT
}day;
3.將整數轉換爲枚舉
#include <stdio.h>
#include <stdlib.h>
int main()
{
enum DAY
{
SUN, MON, TUE, WED, THU, FRI, SAT
} Weekend;
int a = 1;
enum DAY Weekday;
Weekday = (enum DAY) a; //類型轉換
printf("Weekday:%d,\n", Weekday);
return 0;
}
4.注意事項
枚舉元素作爲常量,它們是有值的,並且值是可以重複的;
枚舉值可以用來作判斷比較;
一個整數不能直接賦給一個枚舉變量;
在外部,可以對枚舉變量進行賦值,但需要進行類型轉換;
未區分範圍的枚舉常數可以隱式轉換爲int,但是int不可以隱式轉換爲枚舉值;
定義的枚舉的元素後,在程序運行過程就不能改變;
typedef
C 語言提供了 typedef 關鍵字,您可以使用它來爲類型取一個新的名字。
1.格式
typedef unsigned char BYTE;
在這個類型定義之後,標識符 BYTE 可作爲類型 unsigned char 的縮寫
2.typedef 和 #define
- typedef 僅限於爲類型定義符號名稱,#define 不僅可以爲類型定義別名,也能爲數值定義別名,比如您可以定義 1 爲 ONE。
- typedef 是由編譯器執行解釋的,#define 語句是由預編譯器進行處理的。
文件操作
- 完成任務
1.什麼是文件
文件有不同的類型,在進行C語言程序設計中,主要用到兩種文件:
(1) 程序文件。包括源程序文件(後綴爲.c)、目標文件(後綴爲.obj)、可執行文件(後綴爲.exe)等。這種文件是用來存放程序的。
(2) 數據文件。文件的內容不是程序,而是程序運行時讀寫的數據,如在程序運行過程中輸出到磁盤(或其他外部設備)的數據,或供程序運行時讀入內存的數據。如一批學生的成績數據、貨物交易的數據等。
文件(file)一般指存儲在外部介質上數據的集合。操作系統是以文件爲單位對數據進行管理的。
在C語言中,文件並不由記錄組成,數據由一連串的字符(字節)組成,中間沒有分隔符,對文件的存取是以字符(字節)爲單位的,允許對文件存取一個字符。輸入輸出的數據流的開始和結束僅受程序控制而不受物理符號(如Enter鍵)控制,這就增加了處理的靈活性。這種文件稱爲流式文件。
2.文件緩衝區
-
緩衝文件系統:
系統可以自動的在內存區爲每一個正在使用的文件開闢一個緩衝區。用緩衝文件系統進行的輸入輸出又稱爲高級磁盤輸入輸出。 -
非緩衝文件系統:
系統不自動開闢確定大小的緩衝區,而由程序爲每個文件設定緩衝區。用非緩衝文件系統進行的輸入輸出又稱爲低級輸入輸出系統。
3.文件類型指針
1.來存放文件的有關信息(如文件的名字、文件狀態及文件當前位置等)。這些信息是保存在一個結構體變量中的。該結構體類型是由系統聲明的,取名爲file.
2.聲明file結構體類型的信息包含在頭文件stdio.h中。在程序中可以直接用file類型名定義變量。
3.一般不對file類型變量命名,也就是不通過變量的名字來引用這些變量,而是設置一個指向FILE類型變量的指針變量,然後通過它來引用這些file類型變量。
1.定義格式:
file *fp
//定義一個指向FILE類型數據的指針變量
- 可以使fp指向某一個文件的文件信息區(是一個結構體變量),通過該文件信息區中的信息就能夠訪問該文件。也就是說,通過文件指針變量能夠找到與它關聯的文件。
- 如果有n個文件,應設n個指針變量,分別指向n個file類型變量,以實現對n個文件的訪問。爲方便起見,通常將這種指向文件信息區的指針變量簡稱爲指向文件的指針變量。
指向文件的指針變量並不是指向外部介質上的數據文件的開頭;
而是指向內存中的文件信息區的開頭。
4.文件位置標記
爲了對讀寫進行控制,系統爲每個文件設置了一個讀寫位置標記(簡稱文件位置標記或文件標記),用來指示當前的讀寫位置(即接下來要讀寫的下一個字符的位置)。
5.打開與關閉文件
- 所謂“打開”是指爲文件建立相應的信息區(用來存放有關文件的信息)和文件緩衝區(用來暫時存放輸入輸出的數據)。
- 所謂“關閉”是指撤銷文件信息區和文件緩衝區,使文件指針變量不再指向該文件,顯然就無法進行對文件的讀寫了。
在編寫程序時,在打開文件的同時,一般都指定一個指針變量指向該文件,也就是建立起指針變量與文件之間的聯繫,這樣,就可以通過該指針變量對文件進行讀寫了。
- 用fopen函數打開數據文件:
- 3.1 格式:
fopen(文件名,使用文件方式);
file*fp;//定義文件型指針變量
fp=fopen("a1","r");//使指針變量指向打開的文件的信息區
在打開一個文件時,通知編譯系統以下3個信息:
① 需要打開的文件名,也就是準備訪問的文件的名字
② 使用文件的方式(“讀”還是“寫”等)
③ 讓哪一個指針變量指向被打開的文件
- 3.2 訪問文件訪問的方式:
文件訪問方式 | 含義 | 如果指定的文件不存在 |
---|---|---|
“r”(只讀) | 爲了輸入數據,打開一個已存在的文本文件 | 出錯 |
“w”(只寫) | 爲了輸出數據,打開一個文本文件 | 建立新文件 |
“a”(追加) | 向文本文件尾添加數據 | 出錯 |
“rb”(只讀) | 爲了輸入數據,打開一個二進制文件 | 出錯 |
“wb”(只寫) | 爲了輸出數據,打開一個二進制文件 | 建立新文件 |
“ab”(追加) | 向二進制文件尾 添加數據 | 出錯 |
'r+"(讀寫) | 爲了讀和寫,打開一個文本文件 | 出錯 |
“w”(讀寫) | 爲了讀和寫,建立一個文本文件 | 建立新文件 |
“a+”(讀寫) | 爲了讀和寫,打開一個文本文件 | 出錯 |
“rb+”(讀寫) | 爲了讀和寫,打開一個二進制文件 | 出錯 |
“wb+”(讀寫) | 爲了讀和寫,建立一個新的二進制文件 | 建立新文件 |
“ab+”(讀寫) | 爲讀和寫打開一個二進制文件 | 出錯 |
(1)最基本的是“r”“w”“a”這3種方式。在其後加“b”表示是二進制文件,“+”表示既可讀又可寫。
(2) 如果不能實現“打開”的任務,fopen函數將會帶回一個出錯信息。出錯的原因可能是: 用“r”方式打開一個並不存在的文件;磁盤出故障;磁盤已滿無法建立新文件等。此時fopen函數將帶回一個空指針值NULL(NULL在stdio.h文件中已被定義爲0)。
- 用fclose函數關閉文件:
-
4.1 格式:
fclose(文件指針);在向文件寫數據時,是先將數據輸出到緩衝區,待緩衝區充滿後才正式輸出給文件。如果當數據未充滿緩衝區而程序結束運行,就會將緩衝區中的數據丟失。用fclose函數關閉文件,可以避免這個問題,它先把緩衝區中的數據輸出到磁盤文件,然後才釋放文件指針變量。
fclose函數也帶回一個值,當順利地執行了關閉操作,則返回值爲0,否則返回EOF(即-1)。
6.文件的順序讀寫
- 6.1向文件讀寫一個字符:
函數名 | 調用形式 | 功能 | 返回值 |
---|---|---|---|
fgetc | fgect(fp) | 從fp指向的文件讀入一個字符 | 讀成功,帶回所讀的字符,失敗則返回文件結束標誌EOF(即-1) |
fputc | fputc(ch,fp) | 把字符ch寫到文件指針變量fp所指向的文件中 | 輸出成功,返回值就是輸出的字符;輸出失敗,則返回EOF(即-1) |
#include <stdio.h>
#include <stdlib.h>
int main()
{ FILE *fp; //定義文件指針fp
char ch,filename[10]; //定義字符數組
scanf("%s",filename); //輸入文件名放入字符數組中
if((fp=fopen(filename,"w"))==NULL) //打開輸出文件並使fp指向此文件
{ printf("cannot open file\n"); //如果打開出錯就輸出“打不開”的信息
exit(0); //終止程序
}
ch=getchar(); //接收執行scanf語句時最後輸入的回車符
while(ch!='!') //當輸入′!′時結束循環
{ fputc(ch,fp); //向磁盤文件輸出一個字符
putchar(ch); //將輸出的字符顯示在屏幕上
ch=getchar(); //再接收從鍵盤輸入的一個字符
}
fclose(fp); //關閉文件
putchar(10); //向屏幕輸出一個換行符
return 0;
}
- 6.2向文件讀寫一個字符串:
函數名 | 調用形式 | 功能 | 返回值 |
---|---|---|---|
fgetc | fgect(str,n,fp) | 從fp指向的文件讀入一個長度爲(n-1)的字符串,然後在最後加一個’\0’,存放到字符數組str中。若在讀完n-1個字符之前遇到’\n’或文件結束符EOF,結束輸入 | 讀成功,返回地址str,失敗則返回NULL |
fputc | fputc(str,fp) | 把字符串str寫到文件指針變量fp所指向的文件中 | 輸出成功,返回0;否則返回非0值 |
fgets和fputs這兩個函數的功能類似於gets和puts函數,只是gets和puts以終端爲讀寫對象,而fgets和fputs函數以指定的文件作爲讀寫對象。
7.對文件進行格式化讀寫
可以對文件進行格式化輸入輸出,這時就要用fprintf函數和fscanf函數,從函數名可以看到,它們只是在printf和scanf的前面加了一個字母f。它們的作用與printf函數和scanf函數相仿,都是格式化讀寫函數。只有一點不同:fprintf和fscanf函數的讀寫對象不是終端而是文件。
- 它們的一般調用方式爲:
fprintf(文件指針, 格式字符串, 輸出表列);
fscanf(文件指針, 格式字符串, 輸出表列);
fprintf (fp,″%d,%6.2f″,i,t);
//將整型變量i和實型變量t的值按%d和%6.2f的格式輸出到fp指向的文件中
fscanf (fp,″%d,%f″,&i,&t);
//磁盤文件上如果有字符“3,4.5”,則將磁盤文件中的數據3讀入內存並送給變量i,讀入4.5並送給變量t
8.按二進制方式對文件進行讀寫
用fprintf和fscanf函數對磁盤文件讀寫,使用方便,比較直觀,容易理解,但由於在輸入時要將ASCII碼轉換爲二進制形式,在輸出時又要將二進制形式轉換成字符,需要多花費時間。因此,在內存與磁盤頻繁交換數據的情況下,最好不用fprintf和fscanf函數,而用fread和fwrite函數,以二進制方式進行讀寫。在輸出時按數據在內存中的存放形式原封不動地複製到磁盤文件,在輸入時把磁盤文件中指定區域的數據原樣讀入到內存。
struct Student stu[10];
//定義結構體數組stu,存放了10個學生的數據(包括學號、姓名、性別、成績等),數組每個元素長度爲36個字節
fwrite(stu,36,10,fp1);
//從stu數組首元素的地址開始,以36個字節爲一個單位,向文件指針fp1所指向的文件中寫入10個學生的數據
fread(stu,36,10,fp1);
//從fp1指向的文件中的當前位置開始,複製10×36個字節,存放到stu數組中
9.文件的隨機讀取
對流式文件可以進行順序讀寫,也可以進行隨機讀寫,關鍵在於控制文件的位置標記。如果位置標記是按字節位置順序移動的,就是順序讀寫;如果能將位置標記按需要移動到任意位置,就可以實現隨機讀寫。所謂隨機讀寫,是指讀寫完上一個字符(字節)後,並不一定要讀寫其後續的字符(字節),而可以讀寫文件中任意位置上所需要的字符(字節),即對文件讀寫數據的順序和數據在文件中的物理順序一般是不一致的。可以向文件的任何位置寫入數據,從文件的任何位置讀取數據
- 文件位置標記的定位:
- 用rewind函數使文件位置標記指向文件開頭:rewind(文件指針);
rewind函數的作用是使文件位置標記重新返回文件的開頭,此函數沒有返回值。
- 用fseek函數移動文件位置標記:fseek(文件類型指針, 位移量, 起始點);
“起始點”:用0,1或2代替,0代表“文件開始位置”,1爲“當前位置”,2爲“文件末尾位置”
“位移量”:指以“起始點”爲基點,向前移動的字節數(長整型)
fseed函數中的“起始點”的表示方法:
起始點 | 名字 | 用數字代表 |
---|---|---|
文件開始 | SEEK_SET | 0 |
文件當前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
一般用於二進制文件,因爲文本文件要進行字符轉換,計算位置時往往會發生混亂
fseek (fp,100L,0); //將文件位置標記向前移到離文件開頭100個字節處
fseek (fp,50L,1); //將文件位置標記向前移到離當前位置50個字節處
fseek (fp,-10L,2); //將文件位置標記從文件末尾處向後退10個字節
- 用ftell函數測定文件位置標記的當前位置:ftell(文件類型指針);
ftell函數的作用是得到流式文件中文件位置標記的當前位置。
用相對於文件開頭的位移量來表示。
如果ftell函數返回值爲-1L,表示出錯。
i=ftell(fp); //變量i存放文件當前位置
if(i==-1L) printf(″error\n″); //如果調用函數時出錯,輸出″error″
10.常用的文件操作函數
分類 | 函數名 | 功能 |
---|---|---|
打開文件 | fopen() | 打開文件 |
關閉文件 | fclose() | 關閉文件 |
文件定位 | fseek() | 改變文件位置標記的位置 |
文件定位 | rewind() | 使文件位置標記重新置於文件開頭 |
文件定位 | ftell() | 得到文件位置標記的當前值 |
文件讀寫 | fgetc(), getc() | 從指定文件取得一個字符 |
文件讀寫 | fputc(),putc() | 把字符輸出到指定文件 |
文件讀寫 | fgets() | 從指定文件讀取字符串 |
文件讀寫 | fputs() | 把字符串輸出到指定文件 |
文件讀寫 | fread() | 從指定文件中讀取數據塊 |
文件讀寫 | fwrite() | 把數據塊寫到指定文件 |
文件讀寫 | fscanf() | 從指定文件按格式輸入數據 |
文件讀寫 | fprintf() | 按指定格式將數據寫到指定文件中 |
文件狀態 | feof() | 若到文件末尾,函數值爲“真”(非0 |
文件狀態 | ferror() | 若對文件操作出錯,函數值爲“真”(非0) |
文件狀態 | clearerr() | 使ferror和feof函數值置零 |
位運算
- 完成任務
位運算是按指二進制進行的運算,因爲在系統軟件中,常要處理二進制的問題;C語言提供的位運算功能與其他高級語言相比,具有很大的優越性。
1.運算符和位運算
運算符 | 含義 | 運算符 | 含義 |
---|---|---|---|
& | 按位與 | ~ | 取反 |
| | 按位或 | << | 右移 |
^ | 按位異或 | >> | 右移 |
位運算符中除~以外,均是二元運算符,兩側各有一個運算量;
運算量只能是整型或字符型的數據,不能爲實型數據;
如果參加&運算的是負數,則要以補碼的形式表示爲二進制然後參與運算;
(65—90是:A-Z),(97~122是:a-z)
a=<<2,將a左移兩位,右邊補零;高位左移溢出捨棄;
左移一位相當於乘以2,右移一位相當於除以2;
注:由於本人水平有限,如有理解或描述錯誤,還請各位大佬批評指正。