我又回來啦!距離上一篇博文發出不到4個小時,這篇博文又啟動了,希望順順利利,快逃離掛科區!😢
如果沒有看過上節的可以鏈接跳轉噢~C語言復習(上),這篇會接著上一篇寫噢~
如果需要源檔案,可以私信我噢!

4. 函式
函式是具有一定功能的一個模塊,所謂函式名就是給該功能起了一個名字,
注意:函式就是功能,每一個函式用來實作一個特定的功能,函式的名字應反映出它代表的功能,這樣代碼的可讀性會大大提升
記得在上一篇中有這么一句話,“一個C程式可由一個主函式和若干個其他函式構成,” C語言是一門完全面向程序的語言,在程式設計中要善于利用函式,以減少重復代碼的撰寫,盡量的減少代碼冗余,這樣也能提高代碼的可維護性,也更便于實作模塊化的程式設計,
4.1 定義一個函式
通過了上面的解釋,相信已經加深了對函式的理解以及對函式的作用有了進一步的認識,
定義一個函式需要包括以下幾個內容:
- 指定函式的名字,以便以后的函式呼叫
- 指定函式的型別,即函式的回傳值的型別
- 指定函式的引數的名字和型別,以便在呼叫函式的時候傳遞引數
- 書寫函式的功能,這是函式的核心內容
4.1.1 定義一個無參函式
當函式不需要接受用戶傳遞的資料時,不需要帶引數
回傳值型別 函式名() {
功能
}
下面我們來定義一個計算1加到100的函式
int sum()
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
}
通過將計算的結果保存到sum中,最后通過return陳述句回傳給函式的呼叫者,回傳值的型別是int
4.1.2 無回傳值函式
有些函式不需要回傳值,只需要執行代碼即可,我們可以通過void來指定回傳值的型別
void hello() }{
printf("hello world");
}
這個函式沒有回傳值,它的功能就是輸出hello world,型別void表示空型別或者無型別,大多數意味著無return陳述句
4.1.3 定義有參函式
當函式需要接收用戶傳遞的資料,那么定義時就需要帶上引數
int max(int x, int y) {
int z;
z = x > y ? x : y;
return z;
}
上面的函式功能是求兩個數中較大的那個數,在主函式呼叫時,將資料傳遞給形參x,y,在函式體內判斷兩個書中較大的數,并通過return陳述句回傳值回傳給函式的呼叫者
注意:
- 引數的資料說明可以省略,默認值是
int型別 - 函式名稱需要遵循識別符號命名規范
- 函式需要先定義后使用,如果函式寫在了
main函式后面,需要將函式的宣告寫到main中函式呼叫之前 - 在C語言中不允許函式的嵌套定義
4.2 呼叫函式
呼叫函式的方式非常簡單,以呼叫上面計算兩數中最大值為例
c = max(a, b);
這樣我們就能實作了函式的呼叫,將 a,b 傳給max函式,函式執行完畢后回傳值z的值賦值給c,這樣c就得到了a,b中較大數的值
下面我們撰寫一個程式來練練手
輸入兩個整數,要求輸出其中值較大者,使用函式來實作
首先我們先撰寫max函式,用來回傳兩個數中的較大者
int max(int x, int y) {
int z;
z = x > y ? x : y;
return z;
}
接下來我們撰寫主函式
int main()
{
int a, b, c;
printf("請輸入兩個數\n");
scanf("%d,%d", &a, &b);
c = max(a, b);
printf("max is %d", c);
return 0;
}
在主程式中接收兩個用戶輸入的資料a,b,通過函式回傳大的數,實作功能
注意:程式從上向下執行,當碰到函式名后,把值傳給呼叫函式,當程式得到了回傳值或呼叫函式結束,再繼續往下執行,
4.3 形參和實參的區別
在上一部分中,我們復習了如何定義和呼叫函式,
如果函式是一個加工廠的話,那么函式的引數就是工廠的原材料,回傳值就是經過加工的產品,
重要
-
形參只有在函式被呼叫時才會分配記憶體,呼叫結束后,會立刻釋放記憶體,因此形參變數只有在函式內部有效,不能在函式外部使用,
-
實參和形參在數量上、型別上、順序上必須嚴格一致,否則會發生“型別不匹配”的錯誤,如果會自動型別轉換,或者進行了強制型別轉換,那么實參型別也可以與形參型別不同,
-
函式呼叫中發生的資料傳遞是單向的,只能把實參的值傳遞給形參,而不能把形參的值反向地傳遞給實參,也就是改變形參不會影響實參的值,
注意:傳數值,形參的變化不會改變實參的變化;傳地址,形參的變化就有可能改變實參所對應的值
4.4 函式的嵌套呼叫
函式不能嵌套定義,但可以嵌套呼叫,也就是在一個函式的定義或呼叫程序中允許出現對另外一個函式的呼叫,這部分的內容過于簡單就不過多闡述,就是在一個函式里呼叫另一個函式,無限套娃
4.5 函式的遞回呼叫
在呼叫一個函式的程序中又出現直接或間接地呼叫該函式本身,稱為遞回呼叫,這也是C語言的特點之一,遞回函式在解決許多數學問題上起了至關重要的作用,比如計算一個數的階乘、生成斐波那契數列,等等,
注意:遞回必須要有一個結束的條件,否則可能會無限的呼叫死回圈
下面通過遞回來輸出斐波那契數列的第n項
#include <stdio.h>
int Fib(int n)
{
if (n == 0)
{
return 1;
}
else if (n == 1)
{
return 1;
}
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n, ret;
printf("請輸入n:");
scanf("%d", &n);
ret = Fib(n);
printf("%d\n", ret);
return 0;
}

重要陳述句return Fib(n - 1) + Fib(n - 2);通過return再次呼叫這個函式,直至到達結束的條件,
4.6 全域變數和區域變數
4.6.1 區域變數
定義在函式內部的變數稱為區域變數,它的作用域僅限于函式內部, 離開該函式后就是無效的,再使用就會報錯,
int name(int a)
{
int b, c;
return a + b + c;
}
int main()
{
int x, y;
return 0;
}
- 在 main 函式中定義的變數也是區域變數,只能在函式中使用,main 函式也是一個函式,與其他函式平等地位
- 實參給形參傳值的程序也就是給區域變數賦值的程序
- 可以在不同的函式中使用相同的變數名,它們表示不同的資料,分配不同的記憶體,互不干擾,(我偷偷的把它理解為js中的塊級作用域)
4.6.2 全域變數
宣告在函式外部的變數稱為全域變數,它的作用域是整個作用域,也就是整個檔案
4.6.3 練習題
輸入長方體的長寬高求它的體積以及三個面的面積,(撰寫一個函式實作)
#include <stdio.h>
int s1, s2, s3; //面積
int volume(int a, int b, int c)
{
int v; //體積
v = a * b * c;
s1 = a * b;
s2 = b * c;
s3 = a * c;
return v;
}
int main()
{
int v, length, width, height;
printf("輸入長寬高: ");
scanf("%d %d %d", &length, &width, &height);
v = volume(length, width, height);
printf("v=%d, s1=%d, s2=%d, s3=%d\n", v, s1, s2, s3);
return 0;
}
采用了三個全域變數,來記錄三個面的面積,這樣通過main函式可以直接通過訪問全域變數來獲取到對應面積的值,通過回傳值來得到體積v
注意:建議在不必要的情況下不要使用全域變數(這個在其他語言中也是同樣的)
原因:
- 全域變數在程式的全部執行程序中都要占用存盤單元,而不是僅在需要時才開辟單元
- 它使函式的通用性降低了,不利于模塊化編程
- 降低了代碼的可讀性
4.7 內部函式和外部函式
- 不能被其他源檔案呼叫的函式稱謂內部函式 ,內部函式由
static關鍵字來定義,因此也叫靜態函式,如static int max() - 能被其他源檔案呼叫的函式稱謂外部函式 ,外部函式由extern關鍵字來定義,形式為
extern int max() - 在沒有指定函式的作用范圍時,系統會默認為外部函式
// hello.c檔案
void hello()
{
printf("hello world");
}
//main.c檔案
#include<stdio.h>
#include "hello.c"
int main() {
hello();
return 0;
}
4.8 練習題
第一題
在 C 語言中,有關函式的說法,以下正確的是 ,
A. 函式可嵌套定義,也可嵌套呼叫 B. 函式可嵌套定義,但不可嵌套呼叫
C. 函式不可嵌套定義,但可嵌套呼叫 D. 函式不可嵌套定義,也不可嵌套呼叫
答案:C
第二題
有一個函式原型如下所示,則該函式的回傳型別為( ) ,
abc(float x,float y)
A. void B. double C. int D. float
答案:C 默認int
第三題
C語言中,以下敘述中錯誤的是( ),
A) 主函式中定義的變數是全域變數
B) 同一程式中,全域變數和區域變數可以同名
C) 全域變數的作用域從定義處開始到本源程式檔案結束
D) 區域變數的作用域被限定在其所定義的區域范圍中
答案:A 函式同級

5. 指標
每一個變數都有一個記憶體位置,可使用 & 取地址符來訪問它的記憶體地址,它表示了在記憶體中的一個地址,🍟
指標也就是記憶體地址,指標變數是用來存放記憶體地址的變數,就像其他變數或常量一樣,必須在使用指標存盤其他變數地址之前,對其進行宣告,
格式為:型別 *變數名
int *ip; /* 一個整型的指標 */
double *dp; /* 一個 double 型的指標 */
float *fp; /* 一個浮點型的指標 */
char *ch; /* 一個字符型的指標 */
不同資料型別的指標之間唯一的不同是,指標所指向的變數或常量的資料型別不同,
形象的說:一個房間的門口掛了一個房間號1304,這個1304就是房間的地址,或者說1304指向這個房間,因此把地址形象化的稱為指標,
指標變數可指向任意一種資料型別,但不管指向的資料占用多少位元組,一個指標變數占用四個位元組,
5.1 指標變數
存放地址的變數是指標變數,它用來指向另一個物件
注意:在定義指標變數時,必須確定指標型別,例如:int變數的指標需要用int型別指標來存盤,
區分指標變數和指標,指標是一個地址,而指標變數是存放地址的變數
5.1.1 使用指標變數
通過指標變數訪問整型變數
#include <stdio.h>
int main()
{
int a = 100;
int *pointer;
pointer = &a;
printf("*pointer的值是%d", *pointer);
return 0;
}
先定義一個整型變數,再定義一個指標變數,指向這個整型變數,通過訪問指標變數可以找到它所指向的變數,從而得到變數的值
5.1.2 定義指標變數
定義指標變數需要在變數名前面加星號*,例如
int *try;
*表示這是一個指標變數,int表示該指標變數所指向的資料的資料型別是int型別
int a = 100;
int *p = &a;
在定義指標變數p時同時對它進行初始化,將變數a的地址賦予它,此時p就指向了a
注意:
- 指標變數
p需要接收的是一個地址,因此需要使用&取地址符,來獲取a的地址 - 定義指標變數時必須帶
*號,給指標變數賦值時不能帶*號 - 指標變數
p的型別是int *而不是int噢,它們是完全不同的,考試避坑噢~
5.1.3 參考指標變數
- 給指標變數賦值
p = &a; //a的地址賦值給指標變數p
指標變數 p 的值是變數 a 的地址,p 指向 a
- 參考指標變數指向的變數
p = &c;
printf("%d",*p);
printf("%d",*p);的意思是:一整數形式輸出指標變數 p 指向的變數的值
*p = 1;
表示將整數1賦值給 p 所指向的變數,即c = 1
- 參考指標變數的值
printf("%o",p);
作用是以八進制形式輸出指標變數 p 的值,如果 p 指向了 a ,輸出的就是 a 的地址
*和&:這兩個符號的作用是相對的,可以理解為&是取變數的記憶體地址,*號是取地址上的變數值,也就是解引的作用
int a;
*&a = a;
*&a可以理解為*(&a),&a表示取變數 a 的地址,*(&a)表示取這個地址上的資料
5.2 指標變數作為函式引數
在前面的函式部分中,我們有說到
“傳數值,形參的變化不會改變實參的變化;傳地址,形參的變化就有可能改變實參所對應的值”
指標變數作為函式引數就是傳地址的情況,這能幫助我們解決一些問題,一個典型的例子就是交換兩個變數的值
當我們想要交換兩個變數時,我們可以宣告一個swap交換函式,交換兩個變數的值,但是,采用常規的值傳遞的方式是行不通的
#include <stdio.h>
void swap(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(){
int a = 1, b = 2;
swap(a, b);
printf("a = %d, b = %d", a, b);
return 0;
}
因為函式內部的a,b都是區域變數,它們占用著不同的記憶體,改變swap函式中的a,b值,不會影響到main函式中a,b的值
采用用指標變數作為函式引數,就可以解決這個問題,因為引數的傳遞是記憶體地址,外部函式,直接通過修改記憶體地址上的值,來完成操作
#include <stdio.h>
void swap(int *p1, int *p2){
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main(){
int a = 1, b = 2;
swap(&a, &b);
printf("a = %d, b = %d", a, b);
return 0;
}
5.3 通過陣列參考指標
5.3.1 陣列元素的指標
🎉所謂陣列元素的指標就是陣列元素的地址
可以用一個指標變數指向一個陣列元素,例如
int a[3] = {1, 2, 3};
int *p;
p = &a[0]
參考陣列元素可以采用下標法a[1],也可以采用指標法,指標法占用記憶體更少,運行速度更快
在陣列部分,我們知道陣列名稱代表了首元素的地址,因此我們可以直接寫p = a,來指向陣列的第一個元素
5.3.2 在參考陣列元素時的運算
在指標已經指向一個陣列元素的情況下,可以進行下列運算
p + 1:指向同一陣列中的下一個元素
p - 1:指向同一陣列的上一個元素
注意:p + 1不是簡單的數值上的加一,而是加上一個陣列元素所占的位元組數,如float型別陣列一個元素占4個位元組,p的值就加4,也就指向了下一個元素
如果p的初值指向的是陣列的第一個元素a[0]那么可想而知p + i就能訪問到陣列元素a[i]的地址,例如p + 9的值是&a[9],同樣的我們獲取到了了陣列元素的地址,就可以采用*號來得到它的值,如*(p + i) = a[i]
5.3.3 練習題
🎉通過指標變數輸出整型陣列a的10個元素
#include <stdio.h>
int main()
{
int *p, i, a[10];
p = a; //a的地址
printf("請輸入10個整數:\n");
for (i = 0; i < 10; i++)
{
scanf("%d", p++);
}
p = a;//由于指標p在遍歷的程序改變了,因此要重新賦值
for (i = 0; i < 10; i++, p++)
{
printf("%d ", *p);
}
return 0;
}
在遍歷時,采用了p++,在每一次遍歷結束后,將指標p指向陣列的下一位
5.4 指標陣列和陣列指標
指標陣列:它是一個陣列,陣列的元素都是指標,陣列占多少個位元組由陣列本身決定,也叫做存放指標的陣列
陣列指標:它是一個指標,它指向一個陣列,在32 位系統下永遠是占4 個位元組,也叫做指向陣列的指標
5.5 通過指標參考字串
在前面我們也有提到過“c語言中沒有字串變數”,但是可以通過字符陣列和字符指標的方式存盤字串
我們先從一個簡單的題目入手
通過字符指標變數輸出一個字串
#include <stdio.h>
int main()
{
//定義一個字符指標來存盤字串
char *string = "i am ljc";
// 輸出
printf("%s", string);
return 0;
}
在上面的代碼中成功的輸出了i am ljc,我們可以知道,在輸出string時,字符指標最開始指在了字串的第一個字符,又因為字串在記憶體中占據的是連續的記憶體空間,在輸出控制符%s下,系統會自動的輸出字串的第一個字符,然后讓字符指標指向下一個字符,直至遇到\0結束,
字符指標變數和字符陣列的比較
- 字符陣列由若干個元素組成,每個元素中放一個字符,而字符指標變數中存放的是地址
- 賦值方式不同,可以對字符指標變數賦值,而不能對陣列名賦值
- 存盤單元不同,編譯時字符陣列分配若干存盤單元,以存放各元素的值,而對字符指標變數,只占4各位元組(不同編譯器可能不同)
- 指標變數的值是可以改變的,而字符陣列名代表一個固定的值,不能改變
5.6 指標作為函式回傳值
當函式的回傳值是一個指標時,把這個函式稱為指標函式
#include <stdio.h>
#include <string.h>
char *longStr(char *str1, char *str2){
if(strlen(str1) >= strlen(str2)){
return str1;
}else{
return str2;
}
}
上面的代碼定義了一個回傳長度較長字串的函式
在使用指標函式時要注意,函式運行結束后會銷毀在它內部定義的所有區域資料,函式回傳的指標不要指向這些資料
5.7 練習題
🎋第一題
有定義:int x,*p;,能使指標變數p指向變數x的陳述句是( ),
A)
*p=&x;B)p=&x;C)*p=x;D)p=*&x;
答案:B
🎄第二題
若有陳述句int *p, a=10; p=&a; 下面均代表地址的一組選項是(),
A. a, p, *&aB. &*a, &a, *pC.
*&p, *p,&aD.&a, &*p, p
答案:D
🎍第三題
若已定義char s[10];則在下面運算式中不表示s[1]地址的是(),
A. s+1 B. s++ C. &s[0]+1 D. &s[1]
答案:B
🧶第四題
若定義:int a=511, *b=&a;則printf("%d\n", *b);的輸出結果為:
A. 無確定值 B. a的地址 C. 512 D. 511
答案:D
🔋第五題
下面程式中輸出幾個
*A. 9 B. 5 C. 6 D.7
#include <stdio.h>
int main()
{
char *s = "\ta\018bc";
for (; *s != '\0'; s++)
{
printf("*");
}
}
答案:C \0是轉義字符表示后面是個8進制數
| 定 義 | 含 義 |
|---|---|
| int *p; | p 可以指向 int 型別的資料 |
| int **p; | p 為二級指標,指向 int *型別的資料 |
| int *p[n]; | p 為指標陣列,[ ] 的優先級高于 *,所以應該理解為 int *(p[n]); |
| int (*p)[n]; | p 為二維陣列指標 |
| int *p(); | p 是一個函式,它的回傳值型別為 int * |
| int (*p)(); | p 是一個函式指標,指向原型為 int function() 的函式 |

6. 結構體
結構體從本質上來講是一種自定義的資料型別,但是這種資料型別比較復雜,它是由 int、char、float 等多種基本型別組成的
從前端js的角度去思考,我會把結構體形象為js中的物件
這部分沒有寫鏈表的內容,在我之前的博文中有寫到了用js實作鏈表的完整操作思路,實際上思路都一致,只是語法不同,就不過多闡述,有興趣的可以看之前的博文:一文帶你拿下前端必備資料結構 – 鏈表 !!
6.1 定義和使用結構體
6.1.1 建立結構體型別
可以使用結構體struct來存放一組不同的資料型別
下面采用結構體來存盤一個成員的個人資訊
struct people
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
};
people為結構體的名字,里面包含了4個成員,結構體成員的定義方式與變數和陣列的定義方式相同,只是不能初始化,
特別注意:結構體后的花括號需要打分號
6.1.2 定義結構體變數
在上面我們定義了一個結構體型別,我們可以用它來定義變數
struct people s1, s2;
這樣我們就定義了兩個變數s1,s2,它們的型別都是people型別,它們都有4個成員組成,形象的說:定義出來的people就相當于一個模板,該型別的變數都會有它的特性
struct people
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
} s1, s2;
也可以直接在結構體最后定義變數
結構體的各個成員中在記憶體中是連續存盤的,和陣列相似,但是由于結構體中的資料型別復雜,各個成員間存在著間隙,因此存在著結構體記憶體對齊的問題!
6.1.3 讀寫結構體成員的值
使用點號.獲取單個成員,也可以給成員賦值,.號叫做成員訪問運算子!
學前端的現在可以舒一口氣了,這個和物件太像了,其實學習一門編程語言當你學到了它的思想后,學其他的語言都會很輕松的,所以各位一定要先學踏過門檻~沖沖沖
通過這樣的方式可以獲取成員的值,也可以賦值
#include <stdio.h>
int main()
{
struct people
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
};
struct people my;
my.name = "LJC";
my.num = 1023;
my.age = 19;
my.score = 100;
printf("姓名:%s,年齡:%d,學號:%d,分數:%.1f", my.name, my.age, my.num, my.score);
return 0;
}
運行結果:姓名:LJC,年齡:19,學號:1023,分數:100.0
這樣我們就實作了對結構體變數的賦值,
🎃 結構體是一種自定義的資料型別,是創建變數的模板,不占用記憶體空間,只有在創建結構體變數的時候才會占用記憶體空間!!!
6.2 結構體陣列
結構體陣列指陣列中的每個元素都是結構體,這樣就非常的方便了,我們可以把一個班級的學生放在一個結構體陣列里,這樣一個班級學生都系結上了結構體中的成員
struct stu{
char *name; //姓名
int num; //學號
int age; //年齡
int score; //成績
}class[45];
定義了一個有45個學生的班級
讀寫的方式和結構體變數相同,在結構體陣列定義的同時也可以進行初始化,也可以不給出陣列的長度,如:
struct stu{
char *name; //姓名
int num; //學號
int age; //年齡
int score; //成績
}class[] = {
{"ljc",3213,19,100},
{"ljc",3223,19,99}
};
結構體陣列的使用也很方便,訪問賦值即可
class[0].score = 99;
6.3 結構體指標
結構體指標就是指向結構體的指標,一個結構體變數的起始地址就是這個結構體變數的指標,
定義結構體指標的一般形式為struct 結構體名 *變數名
struct stu{
char *name; //姓名
int num; //學號
int age; //年齡
int score; //成績
} my = {"ljc", 1023, 19, 100};// 定義了一個結構體變數my
struct stu *pmy = &my; // 定義結構體指標pmy,賦予my的地址,讓它指向my
也可以在定義結構體的同時定義結構體指標:(多見幾種形式考試才不會生疏噢~)
struct stu{
char *name; //姓名
int num; //學號
int age; //年齡
int score; //成績
} my = {"ljc", 1023, 19, 100}, *pmy = &my;
🎨 特別注意:結構體的變數名和陣列名不同,陣列名在運算式中會被轉換為陣列指標指向陣列的第一個元素,而結構體變數名不會因此需要帶上&取地址符噢~
6.3.1 獲取結構體成員
有兩種獲取的方法,一種是采用成員訪問運算子,另一種是采用->運算子,稱為箭頭,可以通過結構體指標直接取得結構體成員pmy -> num
示例:通過結構體指標來輸出結構體成員的值
#include <stdio.h>
int main()
{
struct people
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
} my = {"LJC", 1023, 19, 100}, *pmy = &my;
printf("姓名:%s,年齡:%d,學號:%d,分數:%.1f", (*pmy).name, (*pmy).num, pmy->age, pmy->score);
return 0;
}
特別注意:在使用第一種方法的時候,一定要有括號,因為.的優先級高于*,如果去掉意義就不一樣了
6.3.2 結構體陣列指標使用
結構體陣列指標的使用和結構體指標基本一致,其中定義的結構體指標ps是指向struct stu結構體資料型別的指標變數,在 for 回圈中給指標賦初值,指向陣列的第一個元素,輸出結束后,指標指向陣列的下一位即ps++
#include <stdio.h>
struct stu
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
} student[] = {
{"LJC", 1023, 19, 100},
{"LIN", 1021, 19, 110},
{"JC", 1020, 19, 120},
},
*ps;//定義結構體陣列指標
int main()
{
//求陣列的長度
int len = sizeof(student) / sizeof(struct stu);
printf("Name\tNum\tAge\tScore\t\n");
for (ps = student; ps < student + len; ps++)
{
printf("%s\t%d\t%d\t%.1f\n", ps->name, ps->num, ps->age, ps->score);
}
return 0;
}
輸出結果

6.3.3 結構體指標做函式引數
通過示例來復習吧:計算學生成績的平均分
注意:通過傳入結構體指標,這樣可以減少記憶體的占用,在傳入結構體陣列時,會將它所占用的記憶體單元的內容全部作為引數傳遞給形參,在函式呼叫的程序中,形參也需要占用記憶體,這樣對于記憶體的開銷就會很大
#include <stdio.h>
struct stu
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
} student[] = {
{"LJC", 1023, 19, 100},
{"LIN", 1021, 19, 110},
{"JC", 1020, 19, 120},
};
void average(struct stu *ps, int len)
{
int i;
float average, sum = 0;
for (i = 0; i < len; i++)
{
sum += (ps + i)->score;
}
printf("sum = %.1f\n平均分=%.2f", sum, sum / len);
}
int main()
{
//求陣列長度
int len = sizeof(student) / sizeof(struct stu);
average(student, len);
return 0;
}
輸出結果

6.4 共用體型別
共用體是一種特殊的資料型別,不同于結構體的是它允許在相同的記憶體位置存盤不同的資料型別,因此我們可以從名字上來淺析,它們共用同一塊記憶體空間
6.4.1 定義和使用共用體型別
定義的方式和使用的方式和結構體基本一致,定義結構體采用struct,定義共用體采用union關鍵字,定義格式為:
union 共用體名 {
成員串列
};
看是不是基本一樣呀,相信你應該都忘記了吧~
結構體和共用體的區別在于:結構體的各個成員會占用不同的記憶體,互相之間沒有影響;而共用體的所有成員占用同一段記憶體,相互之間會有影響,
共用體的記憶體大小取決于成員中占據記憶體最大的記憶體,例如定義以下共用體:
union data{
int a;
char b;
double c;
};
占用記憶體最大的是double占用了8個位元組,所以共用體所占的位元組數就是8,它們該共用體在記憶體中的排列方式如圖:

大致是這個意思,畫的有點丑
下面我們通過一個示例來演示共用體成員在記憶體的分布
#include<stdio.h>
//定義共用體
union data {
int i;
char ch;
};
int main() {
union data a; //定義共用體變數
printf("%d,%d\n",sizeof(a),sizeof(union data)); //輸出 8 8
a.i = 0x62;
printf("%X,%c\n",a.i,a.ch);
a.ch = '9';
printf("%X,%c\n",a.i,a.ch);
}
輸出結果

在代碼的第10行我們給共用體成員i賦值0x62,會被存入記憶體中的第一個位元組中,當給ch賦值為'9'時,字符9的ascII碼為57,對應的16進制就是0x39,因此第一個位元組中會被改寫成39,這樣成員i的值也被改變了

6.5 列舉型別
在高級程式設計中是這樣解釋的:所謂列舉,就是指把可能的指一一列舉出來,變數的值只限于列舉出來的值的范圍
列舉型別的定義
宣告列舉型別采用enum開頭,格式如下:
enum typeName {valueName1,valueName2,...}
示例:列出一個星期
enum Weekdday {sun, mon, tue, wed, thu, fri, sat};
這樣我們就宣告了一個列舉型別enum Weekday
這樣我們就可以定義這個列舉型別的變數enum Weekday workday,這樣就定義了一個列舉變數,
注意:列舉變數和其他變數不同,它們的值只限于花括號中的指定值之一
每一個列舉元素都代表一個整數,默認是0,1,2,3....,在上面的定義中sun = 0 mon = 1 依次下去,我們也可以手動設定每一個列舉元素的值
enum Weekday {sun = 7,mon = 1,tue, wed, thu, fri, sat} workday;
指定了sun的值為7,mon為1,依次遞增,則sat為6
幾點注意事項:
- 列舉串列中的識別符號的作用范圍是全域的,不能定義相同的變數名
- 列舉元素又叫列舉常量,不能賦值,例如
sun = 7是不合法的,只能在定義列舉型別時設定好 - 列舉元素不再是變數,不能使用
&來獲取它們的地址
6.6 typeof 宣告新型別名
typeof關鍵字,可以用來為型別取一個新的名字,例如我可以給int型別取名為a之后我要定義新的int型別時,就可以使用a來宣告
typeof int a;
a num;
當然實際中我們并不會這樣用,因為這樣的新名字別人根本看不懂,假設在一個程式中,需要用一個變數來計數,我們可以這樣做:
typeof int Count; // 指定Count代表int
Count i; // 用count定義變數i
這樣可以一目了然的知道它是用于計數的
注意:用typeof宣告的新型別名,只是一個別名,原型別名仍然可以正常使用
typeof用于處理復雜的型別會非常的好用,例如結構體,共用體這些
typeof struct stu
{
char *name; //姓名
int num; //學號
int age; //年齡
float score; //成績
} Student;
像上面的代碼中就定義了一個結構體,并且使用typeof取了別名student,因此在我們需要定義一個 stu 結構體變數時,我們只需
Student ljc;
這樣我們就定義了一個結構體變數,上面的代碼語意就非常的清晰,學生ljc,代碼讀起來寫起來都會比較方便
下面我們看多幾個來熟悉以下
命名一個新的型別名代替陣列
typeof int Num[100];
Num a; //定義了一個整型陣列a,相當于 int a[100]
代替指標型別
typeof char *String;
String p,s[10];// p為字符指標變數,s為字符指標陣列
代替指向函式的指標型別
typeof int (*Pointer)();
Pointer p1,p2; //p1,p2為Pointer型別指標變數
總結:按定義變數的方式,把變數名換成新的型別名,并且在最前面加typeof,就宣告了新型別名代表原型別
一步步來
// 1.按照定義變數的方式寫
int a[100];
//2. 把變數名換成新型別名
int Num[100];
//3. 在前面加typeof
typeof int Num[100];
//over
6.7 練習題
🎀第一題
#include <stdio.h>
int main()
{
struct sk
{
int a;
float b;
} data, *p;
p = &data;
}
以上代碼塊對data中的成員a的正確參考是:
A)
(*p).data.aB)(*p).aC)p->data.aD)p.data.a
答案:B
🩱第二題
#include<stdio.h>
struct name1{
char str;
short x;
int num;
}A;
int main()
{
int size = sizeof(A);
printf("%d\n",size);
return 0;
}
上面的代碼輸出結果是多少____
答案:8 考點:結構體記憶體對齊
🚩第三題
和上一題不一樣噢,將第5行和第六行位置調換
#include <stdio.h>
struct name1
{
char str;
int num;
short x;
} A;
int main()
{
int size = sizeof(A);
printf("%d\n", size);
return 0;
}
輸出多少____
答案:12

7. 檔案的輸入輸出
關于檔案就不做過多的闡述,直接來寫相關的操作
7.1 打開和關閉檔案
7.1.1 打開檔案
用fopen來實作打開檔案,呼叫方式為:fopen(檔案名,使用檔案方式);
例如:fopen("a1","r)表示打開檔案a1使用讀入的方式,
注意:fopen()函式會獲取檔案資訊,包括檔案名、檔案狀態、當前讀寫位置等,將這些資訊保存到一個 FILE 結構體變數中,然后將該變數的地址回傳,
FILE是<stdio.h>頭檔案當中的一個結構體,所以可以直接使用
FILE *fp = fopen("demo.txt", "r");
判斷是否打開成功
如果打開失敗會回傳一個空指標,因此可以通過回傳的指標來判斷檔案是否打開成功
#include <stdio.h>
int main()
{
FILE *fp = fopen("demo.txt", "r");
if (fp == NULL)
{
printf("打開失敗");
}
else
{
printf("打開成功");
}
}
fopen函式的打開方式
控制讀寫權限的字符
| 打開方式 | 說明 |
|---|---|
| “r” | **以“只讀”方式打開檔案,**如果檔案不存在,出錯, |
| “w” | **以“寫入”方式打開檔案,**如果檔案不存在,那么創建一個新檔案 |
| “a” | **以“追加”方式打開檔案,**如果檔案不存在,出錯, |
| “r+” | 以“讀寫”方式打開檔案,如果檔案不存在,出錯, |
| “w+” | 以“寫入/更新”方式打開檔案,如果檔案不存在,建新檔案, |
| “a+” | 以“追加/更新”方式打開檔案,如果檔案不存在,出錯, |
控制讀寫方式的字符
| 打開方式 | 說明 |
|---|---|
| “t” | 文本檔案,如果不寫,默認為"t", |
| “b” | 二進制檔案, |
讀寫權限和讀寫方式可以組合使用,讀寫方式要放在讀寫權限中間或尾部,例如:
- 將讀寫方式放在讀寫權限的末尾:“rb”、“wt”、“ab”、“r+b”、“w+t”、“a+t”
- 將讀寫方式放在讀寫權限的中間:“rb+”、“wt+”、“ab+”
從上面的字符中,我們可以知道檔案打開方式由6個字符組成,它們的含義分別是:
- r:讀
- w:寫
- a:追加
- t:文本檔案
- b:二進制檔案
- +:讀和寫
7.1.2 關閉檔案
在檔案使用完畢之后,需要使用fclose函式把檔案關閉,用法fclose(檔案指標)
#include <stdio.h>
int main()
{
FILE *fp = fopen("demo.txt", "r");
if (fp == NULL)
{
printf("打開失敗");
}
else
{
printf("打開成功");
}
// ... 操作檔案
// 關閉檔案
fclose(fp);
return 0;
}
7.2 字符讀寫檔案
字符讀寫檔案主要用到了2個函式fgetc 和 fputc,用法非常簡單,當成api使用即可
7.2.1 fgetc讀取檔案
下面通過代碼來實作讀取當前目錄下的demo.txt檔案
#include <stdio.h>
int main()
{
FILE *fp = fopen("demo.txt", "rt");
char ch;
//如果檔案不存在,給出提示并退出
if (fp == NULL)
{
printf("打開失敗");
}
//每次讀取一個位元組,直到讀取完畢
while ((ch = fgetc(fp)) != EOF)
{
putchar(ch);
}
putchar('\n'); //輸出換行符
fclose(fp);
return 0;
}
輸出結果

這正是我在demo.txt檔案中寫的內容,讀取成功
重點:上面代碼實作的關鍵是 while回圈的條件(ch = fgetc(fp)) != EOF ,fgetc每次從位置指標所在的位置讀取一個字符,保存到變數當中,然后指標后移一位,當到達檔案末尾時,就無法讀取字符了,于是回傳EOF,over
這里也可以使用
feof函式來判斷是否到達檔案結尾
7.2.2 fputc寫入檔案
寫入檔案采用fputc來實作,呼叫方式fputc(ch,fp)把字符ch寫到檔案指標變數fp所指向的檔案中
#include <stdio.h>
int main()
{
FILE *fp;
char ch;
//判斷檔案是否成功打開
if ((fp = fopen("demo.txt", "wt+")) == NULL)
{
puts("打開失敗");
}
printf("輸入:\n");
//每次從輸入中讀取一個字符并寫入檔案
while ((ch = getchar()) != '\n')
{
fputc(ch, fp);//輸出到檔案內
}
fclose(fp);
return 0;
}
7.3 字串讀寫檔案
在上面的字符讀取檔案方式中,一次只能讀取一個字符,寫也只能寫一個,真的是超級慢,因此我們往往不會采用字符讀取的方式
7.3.1 fgets讀取檔案
fgets() 函式用來從指定的檔案中讀取一個字串,并保存到字符陣列中,使用方法:
char *fgets (char *str, int n, FILE *fp);
str 為字符陣列,n 為要讀取的字符數目,fp 為檔案指標,
注意:由于字串的結尾會自動添加\0,所以如果希望讀取100個字符,n應當取101
注意:在n-1個字符之前如果出現了換行,或者到了檔案的末尾,則讀取結束,因此只能讀取一行資料
示例:使用fgets讀取檔案
#include <stdio.h>
#include <stdlib.h>
#define N 100 // 宏定義N = 100
int main()
{
FILE *fp = fopen("demo.txt", "rt");
char str[N + 1];
if (fp == NULL)
{
printf("打開失敗");
}
while (fgets(str, N, fp) != NULL)
{
printf("%s", str);
}
fclose(fp);
return 0;
}
輸出結果

注意:fgets當遇到換行符時,也會一并讀取,因此格式回和原檔案相同,gets會忽略換行符
7.3.2 fputs寫入檔案
用法:int fputs(char *str,FILE *fp)
示例:向demo.txt檔案中追加一句話
#include<stdio.h>
#include<string.h>
int main() {
FILE *fp = fopen("demo.txt","at+");
char attend[100];
char str[102] = {0};
if(fp == NULL) {
printf("打開失敗");
}
printf("輸入一句話\n");
gets(attend);
strcat(str,"\n");
strcat(str,attend);
fputs(str,fp);
fclose(fp);
return 0;
}
輸入輸出結果

讀寫檔案部分就寫這么多了,還有幾種方式沒有寫,可以自己了解一下
結語
C語言復習的全部內容就在這里了,博主也是肝了幾天才整理完,關于內容上有什么問題或者錯誤歡迎留言指出,好好學習,天天向上,希望看到這里的你能取得好的成績!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/287654.html
標籤:其他
下一篇:左神講演算法——二分法及其拓展
