天天看點

C語言——基礎查漏補缺(一):超長文幫你理清一些概念一、基礎知識二、C語言基本要素三、指針四、使用者自建資料類型五、對檔案的操作

相關文章:

C語言——基礎查漏補缺(二):《C程式設計試題彙編》應試概念總結

C語言——基礎查漏補缺(三):譚浩強紅書刷題筆記大雜燴

C語言——基礎查漏補缺(四):利用《挑戰程式設計競賽(第二版)》前兩章學習經典算法

github部落格同步釋出

C語言是部落客最先學的語言,但接觸機器學習以來,主要程式設計語言變成了python,為了防止對C語言的淡忘,利用譚浩強C語言系列進行查漏補缺,進行易忘概念總結。

一、基礎知識

1、一個C程式的執行是從本程式的main函數開始,到main函數結束;

2、C語言本身沒有輸入輸出語句(隻有輸入輸出函數);

3、C語言規定,在源程式中main函數的位置可以任意;

4、一個C語言程式是由:

{ 函 數 組 成 ( ✓ ) 一 個 主 程 序 和 若 幹 子 程 序 組 成 ( × ) 若 幹 過 程 組 成 ( × ) 若 幹 子 程 序 組 成 ( × ) \small\left\{ \begin{aligned} 函數組成(\checkmark) \\ 一個主程式和若幹子程式組成(\times) \\ 若幹過程組成(\times)\\ 若幹子程式組成(\times) \end{aligned} \right. ⎩⎪⎪⎪⎨⎪⎪⎪⎧​函數組成(✓)一個主程式和若幹子程式組成(×)若幹過程組成(×)若幹子程式組成(×)​

5、C語言的源程式必須通過編譯和連結後,才能被計算機執行;

6、在C語言源程式中,一個變量代表記憶體中的一個存儲單元;

7、在Turbo C環境中用RUN指令運作一個C程式時,所運作程式的字尾是

.exe

8、C語言源程式檔案的字尾是

.c

,經過編譯後,生成檔案的字尾是

.obj

,經過連接配接後,生成檔案的字尾是

.exe

9、C語言源程式的基本機關是函數;

10、一個C語言源程式是由若幹函數組成,其中至少應含有一個主函數(main);

11、C和C++的不同:C++是以C語言為基礎的面向對象程式設計語言,對于C89而言,因為C++标準包容了C89的所有内容,是以實作了其全部特性,但對于C99而言,其部分特性未包含到C++中。C語言源檔案的擴充名是

.c

,C++的是

.cpp

12、原碼、反碼和補碼:原碼是在真值的基礎上,最高位用1表示正号,用0表示負号;正數的反碼、補碼和原碼相同;負數的反碼是原碼除負号位外取反,補碼是在反碼基礎上末位加1;

13、C編譯器對資料的三種記憶體配置設定方式(書上說對資料區的記憶體配置設定需要認真研究):

(1)在靜态資料區中配置設定:靜态資料區中的變量在C程式編譯的時候就配置設定完成并初始化,這些變量稱為靜态型變量,其生命周期貫穿程式的整個運作周期。外部變量、static變量都在靜态資料區中配置設定存儲單元(這裡内容有個小小的bug,如果我将程式進行編譯後遲遲不去運作,那是不是被配置設定的記憶體空間就一直被這個程式的靜态型變量占用?我認為這裡應該是做了個假設:“編譯完就立刻裝入記憶體運作”,否則靜态型變量應當也是程式要被運作時才配置設定記憶體,隻不過是編譯時就先确定“其一定會被配置設定記憶體(同時就确定了所需記憶體大小)”且将程式裝入記憶體時就為之配置設定記憶體,配置設定完成才進行程式執行的下一步);

(2)在動态資料區的堆棧上配置設定:C程式在函數内或者語句塊内聲明的變量是内部變量,它就在堆棧上配置設定存儲空間。這部分存儲空間中的變量随時可能消失,調用函數時,這部分變量就在堆棧上配置設定存儲空間,當函數調用結束時,這部分存儲空間會被自動釋放,這些變量就不存在了;

(3)在動态資料區的堆上配置設定:需要程式員自己負責變量的配置設定和釋放。需要使用C語言的指針來管理、操作這部分記憶體。

二、C語言基本要素

1、

system("PAUSE")

的功能是産生一個DOS系統功能調用,等待鍵盤輸入一個字元。在輸入前螢幕暫停在目前位置,友善觀察結果。此函數不是必須的,但若用之,需加

#include<stdlib.h>

2、

return 0

的功能是退出main函數,傳回作業系統;

3、使用者自定義函數可以在main函數之後定義,但必須在main函數之前聲明;

4、

printf()

scanf()

stdio.h

中,都是标準庫函數;

5、開發過程:編輯、編譯(利用編譯器,得到計算機可識别處理的二進制代碼)、連接配接(将多個目标代碼或者庫函數代碼連接配接起來)、裝載運作(将連接配接後的程式裝載到計算機記憶體,運作并得到結果);

8、每個C程式都必須且隻能有一個main函數;

9、C99标準中,main函數必須傳回int型,即

void main(void)

是錯誤的,另外,要用

int main(void)

明确聲明main函數沒有參數;C89可以傳回void型,預設傳回int型;

10、包含在函數名後面一對大括号中的部分就是函數體,C89要求變量的定義語句應該放在函數體中語句塊的開頭,C99則無這個要求(一般為了滿足C89的要求,會将變量定義語句放在語句塊開頭);

變量、常量和資料類型

11、用const定義的常量稱為常變量,是不可改變的,可以通過初始化(定義時指派)或者外部硬體因素修改其值,但不能通過指派語句;

12、轉義字元:

轉義字元 字元值
\’
\" "
\? ?
\\ \
\a 警告(産生聲音或視覺信号)
\b 倒退(backspace),目前位置後退一個字元
\f 換頁(form feed),目前位置移到下一頁開頭
\n 換行
\r 回車(carriage return),目前位置移到本行開頭
\t 水準制表符,目前位置移到下一個tab位置
\v 垂直制表符,目前位置移到下一個垂直制表對齊點
\o、\oo、\ooo,o代表一位八進制數 與該八進制碼對應的ascii字元
\xh[h…],h代表一位十六進制數 與該十六進制碼對應的ascii字元

13、用

""

将若幹字元括起來,但不能用

'

,這個隻能包含一個字元;

14、用

#define

定義的常量為符号常量,習慣上用大寫表示,預編譯後,符号常量就不存在了,比如

#define Pi 3.1415926

,預編譯後,Pi被全部置換成3.1415926;

15、常量在程式運作期間,值不能改變,變量的值可以改變。變量必須先定義,後使用;

16、常變量是有名字的不變量,常量是沒有名字的不變量(有名字便于在程式中被引用)。常變量具有變量的基本屬性:有類型、占存儲單元,隻是不允許改變;

17、合法辨別符:隻能由字母、數字、下劃線組成,且第一個字元必須為字母或者下劃線(而不能是數字);

18、整型資料:C标準要求

sizeof(short)≤sizeof(int)≤sizeof(long)≤sizeof(long long)

,通常是把long定為32位,把short定為16位,而int可以是16位或者32位。書上沒提long long通常多少位,但表格中long long有8位元組,即64位;

19、隻有整型(包括字元型)可以加signed和unsigned修飾符,實行資料不能加。對無符号整型資料用“%u”格式輸出,這個代表輸出無符号十進制數的格式;

20、給無符号整型變量指派負數的案例:

unsigned short x = -1;
printf("%d\n", x);
           

輸出結果是65535,因為系統會将-1轉換成補碼形式,由于是short型(16位),即系統用十六個1表示-1的補碼。但由于x是unsigned型,是以輸出時會認為這是非負數,非負數的原碼和補碼一緻,故輸出65535;

21、由于字元是按照其代碼(整數)形式存儲的,是以C99單獨把字元型資料作為整型資料的一種。幾種重要字元的ascii代碼:

字元 ASCII代碼
A 65
a 97
1 49
’ '(空格字元) 32
% 37
\n 10

大寫字母的ASCII碼加32為對應的小寫字母

22、字元’1’和整數1的差別:前者隻是形狀為’1’的符号,在記憶體中占一個位元組以ascii碼(49)存儲,後者占兩個位元組,以二進制補碼方式存儲。⚠️注意’1’+‘1’不等于’2’;

23、用char定義的變量是字元變量,用%d列印出來是其ascii碼的值,用%c列印出來是這個字元本身;

24、字元型資料可以是signed或者unsigned的,不加這個符号時,預設是signed char還是unsigned char需要由編譯器決定。将負數指派給有符号字元型資料是合法的,但它不代表一個字元,而是作為一位元組整型變量存儲負整數;

25、總結一下浮點數取值範圍,浮點數值為:

尾 數 × 底 數 指 數 尾數\times底數^{指數} 尾數×底數指數

對于float:1bit(符号位)+8bits(指數位)+23bits(尾數位);

對于double:1bit(符号位)+11bits(指數位)+52bits(尾數位)。

(補充概念:規範化的指數形式指的是小數點前為0,小數點後第一位不為0)

尾數最大近似為1,浮點數的範圍主要就和指數部分有關,float的指數部分範圍是-127~128,底數為2。128代表浮點數範圍兩頭最大和最小是多少,-127代表浮點數範圍中最接近0的點應該是多少,是以float的兩頭範圍大緻就是 − 2 128 -2^{128} −2128 ~ 2 128 2^{128} 2128,即 − 3.4 × 1 0 38 -3.4\times10^{38} −3.4×1038 ~ 3.4 × 1 0 38 3.4\times10^{38} 3.4×1038,同理計算出最靠近0的部分就是 ( − ) 1.2 × 1 0 − 38 (-)1.2\times10^{-38} (−)1.2×10−38,double型的計算過程略,譚浩強教材中有一張表:

類型 位元組數 有效數字 數值範圍(絕對值)
float 4 6 0以及 1.2 × 1 0 − 38 1.2\times10^{-38} 1.2×10−38~ 3.4 × 1 0 38 3.4\times10^{38} 3.4×1038
double 8 15 0以及 2.3 × 1 0 − 308 2.3\times10^{-308} 2.3×10−308~ 1.7 × 1 0 308 1.7\times10^{308} 1.7×10308
long double(Visual C++ 6.0編譯器) 8 15 0以及 2.3 × 1 0 − 308 2.3\times10^{-308} 2.3×10−308~ 1.7 × 1 0 308 1.7\times10^{308} 1.7×10308
long double(Turbo C編譯器) 16 19 0以及 3.4 × 1 0 − 4932 3.4\times10^{-4932} 3.4×10−4932~ 1.1 × 1 0 4932 1.1\times10^{4932} 1.1×104932

對于float的有效數字,由于float的尾數有23位,即 2 23 = 8388608 2^{23} = 8388608 223=8388608,共七位,意味着最多能有7位有效數字,但絕對能保證的為6位,是以有效數字定為6位,這個也代表精度。其他類型的有效數字推算略,推算不是重點,将上圖各個類型的大緻範圍記住,友善程式設計就足夠了;

26、C程式中的實型常量都是雙精度浮點型常量;

算數運算和資料存儲

27、整除運算符

/

:多數C編譯系統采取“向零取整”,

5/3 = 1

-5/3 = -1

28、求餘運算符

%

:參加運算的運算對象為整數,結果也是整數。除了

%

以外的其他運算符的操作數都可以是任何算數類型;

29、運算符的優先級和結合性:優先級相同的情況下,算術運算符的結合方向都是先左後右(左結合性);

30、不同類型資料間混合運算:

int、float、double兩兩之間進行運算,先将非double型(包括float)的轉化為double型,再進行運算,運算結果為double型;

char型可以直接和int型進行運算(不需要類型轉換);

char型和float或者double型進行運算,char型和float型會先被轉化為double型;

31、強制類型轉換:(類型名)(表達式),比如

b = (double)a

就是将a的值強制轉化為double型賦給b,但a的類型和值都沒有發生變化;

⚠️由于

%

要求兩側數字都是整型的,令f為浮點型資料,則

f%3

不合法,可寫成

(int)f%3

32、指派過程的類型轉換

将浮點型(包括單精度和雙精度)賦給整型時,先舍棄浮點型資料的小數部分然後再賦予整型變量:

int i; i = 3.56;

運作結果是i為3;

将整型資料賦給浮點型資料時,數值不變,以浮點數形式存儲到變量中;

将double型賦給float時,先将雙精度轉換為單精度,隻取6~7位有效數字,存儲到float變量的4個位元組中;

⚠️雙精度範圍比單精度大,不要将單精度無法存儲的大數指派給單精度變量;

字元型資料給整型資料指派,将前者的ASCII賦給後者;

多位元組的整型資料賦給少位元組的整型資料,比如占4位元組的int賦給2位元組的short,或者1位元組的char,将其低位元組原封不動地送到被指派的變量,此過程叫“截斷”;

33、⚠️關于補碼的注意事項:

以下兩個橫線之間的内容參考了其他部落格,覺得其對+0和-0和-32768的補碼來源很有參考價值:

正0和負0的原碼:0000000000000000和1000000000000000,補碼表現中,兩者都是0000000000000000,也就是正0和負0在補碼系統中的編碼是一樣的,

而任何一個原碼都不可能在轉成補碼時變成1000000000000000(這個是-0的原碼),

是以,人為規定1000000000000000這個-0的原碼來表示-32768的補碼。是以,補碼系統中,範圍是-32768~32767(原碼系統中則是-32767~32767)。

此處參考原文:https://blog.csdn.net/chen_tongsheng/article/details/79011390

-127的補碼是10000000的道理和上面一樣,隻要是第一位是1,剩下全0的補碼,都可視為用-0的原碼充當的補碼。

再來一張圖輔助了解原碼、補碼和反碼,留意其中對-8補碼來源的解釋(-0和+0的補碼相同,是以用-0的原碼來表示-8的補碼):

C語言——基礎查漏補缺(一):超長文幫你理清一些概念一、基礎知識二、C語言基本要素三、指針四、使用者自建資料類型五、對檔案的操作

(在此友情提醒,譚浩強的C第四版圖3.16(b)可能有錯,應該是第一位1,後面15個0)

34、

int a, b, c = 5

隻會對c指派,若要對三者都指派5,可寫成:

int a=5, b=5, c=5

輸入輸出函數

35、printf:

(1)如果整數的範圍在0~127的範圍内,那麼用

%c

占位符可以輸出ASCII碼相對應的字元,如果超過這個範圍,則考慮最後一個位元組的字元進行輸出。比如以下兩種方式均輸出字元y(ASCII碼為121或者01111001):

方式一

short a = 121;
printf("%c", a);
           

方式二

int a = 377;
printf("%c", a);
           

(2)

-m.nf%

代表輸出資料占總列數(含小數點)為m,小數點後占的列數為n,左對齊;

(3)

%e

printf("%e", 123.456);
           

如上,如果不指定寬度,很多編譯系統中的輸出結果是:

1.234560e+002

,意思是 1.234560 × 1 0 2 1.234560\times10^2 1.234560×102。也可以利用

%m.ne%

的形式指定寬度:

printf("%13.2e", 123.456);
           

輸出結果是:

()1.23e+002

,“()”代表四個空格,這樣總長度是13;

(4)

%i

作用同%d,按照十進制整數資料的實際長度輸出,一般用%d而不是這個;

(5)

%o

以八進制整數形式輸出:

int a = -1;		//假設系統規定int占4位元組
printf("%o\n", a);
           

得到結果是

37777777777

,因為-1在記憶體單元中是32個1(補碼存儲),八進制1位是二進制3位,是以有11位八進制數;

(6)

%u

無符号整型資料;

(7)

%g

:輸出浮點數,系統自動選f格式或者e格式,選擇長度較短的格式,不輸出無意義的0;

36、scanf:

參數是格式和變量位址表列。

格式字元:

格式字元 說明
d, i 有符号十進制數
u 無符号十進制數
o 無符号八進制數
x, X 無符号十六進制數
c 單個字元
s 輸入字元串,将字元串送到一個字元數組中,以非空白字元開始,以第一個空白字元結束,并以’\0’作為最後一個字元
f 用來輸入實數,可用小數形式或指數形式(雙精度要用lf)
e, E, g, G 同f

格式附加字元:

字元 說明
l 輸入長資料,可用%ld,%lo,%lx,%lu,%lf(雙精度),%le
h 輸入短資料,可用%hde,%ho,%hx
域寬 正整數,指定輸入資料所占寬度(列數)
* 表示本輸入項在讀入後不賦給相應變量

37、putchar(),輸出一個字元,參數可以是字元的ASCII碼;

38、getchar(),輸出一個字元。

⚠️鍵盤輸入資訊時,輸入的字元先暫存在鍵盤的緩沖器中,隻有按了enter鍵後才将字元送入計算機中,按照先後順序分别賦給相應的變量。

示例:

char a, b, c;
a = getchar();
b = getchar();
c = getchar();
putchar(a);
putchar(b);
putchar(c);
putchar("\n");
           

如果輸入"BOY",再按回車,那麼三個字元進入計算機,然後分别指派給a,b,c,這樣在經過putchar()依次輸出:

BOY

如果輸入B、回車、O、回車,那麼運作情況:

B
O
B
O
           

上述運作情況是這樣的:輸入B,B進入鍵盤緩沖區,輸入回車,B和回車進入計算機,分别賦給a和b,然後再按O和回車,O賦給c,回車賦給下一個getchar();輸出的時候就依次輸出a、b、c,分别是B、回車、O,然後再用putchar("\n")輸出一個回車。注意程式運作之後,計算機中仍有個回車,如果再使用一個getchar(),這個回車就會被其接收。

關系運算符和邏輯運算符

39、關系運算符和關系表達式:

大于、大于等于、小于、小于等于四種優先級高于等于和不等于;

a為3,b為2,c為1,則:

a>b

值為真,表達式值為1;

(a>b) == c

值為真,表達式值為1(注意a>b成立,是以其值為1,等于c的值,是以表達式為真);

f = a > b > c

,f為0,因為>号是左結合的,先算a>b,為真,即值為1,然後算1>c,為假,值為0;

40、邏輯運算符優先級由高到低是非與或;

總的運算符優先級由高到低:

非、算數運算符、關系運算符、與或、指派運算符;

⚠️表達式是從左向右掃描的,在掃描的過程中結合優先級進行判斷。下清單達式結果是0:

5 > 3 && 8 < 4 - !0

分支結構要大量用到這一塊的内容,順便補充常見的邏輯判斷:

閏年是能被4整除但不能被100整除的數,或者是能被400整除的數;

分支和循環

41、兩種分支語句

if不再贅述,主要複習使用較少的switch,以下是示例:

#include<stdio.h>
int main(){
	char grade;
	scanf("%c", &grade);
	printf("Your score:");
	switch(grade){
		case 'A':
			printf("85~100\n");
			break;
		case 'B':
			printf("70~84\n");
			break;
		……
		default:
			printf("enter data error!\n");
	}
	return 0;
}
           

運作結果:

A
Your score:85~100
           

default可以沒有,且其在switch中的出現順序不一定非要最後一個;

switch執行順序是先計算grade的值,然後依次和各個case之後的值進行比較,找到比對的則直接執行之,注意如果case中沒有加break語句,則執行結束後順序跳轉到下一個case進行值的比較。如果沒有case比對,則執行default;

42、三種循環結構

for、while和do while,最後一種可以無條件循環一次,不是很常用。

一個重要例子:求100~200之間的所有素數

#include<stdio.h>
#include<math.h>
int main(){
	int n, k, i, m = 0;
	for(n=101; n<=200; n+=2){
		k = sqrt(n);				//用來求平方根,要求參數是雙精度。執行時将整數n自動轉化為雙精度,
									//計算出來的函數值也是雙精度的,賦給整型k後自動省略小數部分。
		for(i=2; i<=k; i++)
			if(n%i==0) break;
		if(i>=k+1){				//表明n未曾被整除過
			printf("%d ", n);
			m += 1;
		}
		if(m%10==0) printf("\n");		//每行隻輸十個數字
	}
	printf("\n");
	return 0;
}
           

數組

43、數組的長度必須依賴于常量而不是變量,有種情況除外,那就是在被調用函數中定義的數組,其長度可以是變量或者非常量表達式:

void func(int n)
{
	int a[n];
}
           

這是合法的,此情況稱為“可變長數組”。但注意在執行函數的時候,n的值是不變的,數組長度是固定的,且如果指定數組為靜态(static)存儲方式,則不能用“可變長數組”:

void func(int n)
{
	static int a[n];	//不合法
}
           

44、⭐️字元數組

由于字元型資料是按照整數形式(ASCII)存放的,是以可以用整型數組存放它的資料:

int c[10];
c[0] = 'a';
           

但是這樣做浪費存儲空間。

如果字元個數小于數組長度,比如:

char c[10] = {'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm'}
           

那麼數組的狀态是這樣的:

c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] c[8] c[9]
c 空格 p r o g r a m \0

其餘的位置會自動補上“\0”,這個是空字元,程式設計時可以用這個判斷數組末尾。

如果初值個數和數組長度相同,則不會在末尾補上這個空字元,而實際上,多數情況我們需要用這個“\0”來判斷字元的有效長度,是以說定義字元數組時先估計字元串的長度,然後保證字元數組長度大于字元串的長度(當然是否需要“\0”還是使用者來決定)。

上述有加“\0”和不加“\0”的情況,但下述情況必然會加“\0”(用字元串來初始化):

char c[] = {"deep dark fantasy"}
char c[] = "deep dark fantasy"		//這兩行等價
           

注意數組c的長度就不是17,而是18,因為要算上自動加上的“\0”。

但如果這樣定義,就不會加“\0”:

char c[] = {'d', 'e', 'e', 'p', ' ', ……,'y'}
           

記住,隻有用字元串初始化字元數組和(不管以什麼形式初始化)數組長度超過字元串長的情況,才有“\0”。

使用

%s

輸出字元串的時候,遇到第一個“\0”時,輸出就結束。

利用一個scanf()輸入多個字元串,需要以空格分離(多少個空格都一樣):

char str1[5], str2[5], str3[5];
scanf("%s%s%s", str1, str2, str3);
           

輸入

how are you?

,三個字元數組的狀态如下:

h o w \0 \0
a r e \0 \0
y o u ? \0

⚠️如果scanf輸入項如果是字元數組名,就不要再加位址符,因為數組名就是數組的起始位址。

string.h中的字元處理函數:

(1)

puts()

:将一個字元串(以“\0”結尾)輸出到終端,輸出的字元串可以包含轉義字元,會将字元串結束标志的“\0”自動轉換成“\n”。

(2)

gets()

:從标準輸入(鍵盤)讀取一行資料,遇到換行符就傳回,并将換行符替換成“\0”,鍵盤輸入

computer回車

,gets接收到的是9個字元,最後一個是空字元。這個函數比較危險,它不會檢查數組能否裝得下輸入行,是以C99不建議使用此函數。

(3)

strcat(str1, str2)

:将str2接到str1後面,并将結果存儲到str1中。将str1中的“\0”取消,隻保留str2中的“\0”。

(4)

strcpy(str1, str2)

char str1[10], str2[] = "China";
strcpy(str1, str2);
           

将str2中的前5個字元和其後的一個“\0”複制到str1中,取代其中前六個字元,但其後四個字元還是str1中原有的字元。

還有個擴充函數:

strncpy(str1, str2, 2);	//将str2前2個字元複制到str1中,此處指定的複制個數不得超過str1原有的字元(不含“\0”)
           

(5)

strcmp(str1, str2)

:字元串比較函數,從左至右依次比較ASCII碼,直到遇到不相等或遇到“\0”的情況。若字元串相等,則傳回0;若str1>str2,則傳回正數,反之負數。(字元串大小比較指的是ASCII碼的比較)

(6)

strlen()

char str[10] = "China";
printf("%d\n", strlen(str));
           

輸出結果是5,不包括最後的“\0”。

(7)大小寫轉換:

轉換為小寫:

strlwr()

轉換為大寫:

strupr()

函數注意事項和變量作用域、生存周期

45、形式參數和實際參數:調用函數的過程中,實參的資料會傳遞給形參;

46、函數的傳回值是通過函數中的return語句獲得的;

47、對被調用函數的聲明:在一個函數中調用另一個使用者自定義函數之前,應當先對被調用函數進行聲明。聲明的作用是将函數名、函數參數個數和參數類型等資訊通知編譯系統,以便在遇到函數調用的時候,編譯系統能夠正确識别函數并檢查調用是否合法。

在函數聲明中,形參可以簡寫,隻寫形參的類型而不寫名稱;

如果已經在某函數之前對被調用函數進行了“外部聲明”,則不需要在函數内部再進行聲明。寫在所有函數前面的外部聲明在整個檔案範圍内有效;

如果函數在被調用之前進行了定義,則不需要在被調用之前進行聲明;

48、數組作為函數參數:

(1)數組元素作實參時,向形參傳遞的是數組元素的值;

(2)用數組名作函數實參時,向形參(數組名或指針變量)傳遞的是數組首元素的位址;(數組名本身代表數組首元素的位址,C語言編譯系統不檢查形參數組大小,形參數組隻獲得實參數組首元素位址,形參數組可以不指定大小:

float average(float array[])

隻用方括号表示這是數組,但不指定大小);因為傳遞的是數組首位址,是以形參數組值發生變化時,會使得實參數組也發生變化;

(3)多元數組名作為函數參數:可以省略第一維大小說明,而不能省略第二維大小說明:

int array[][10];	//是對的
int array[3][];	//是錯的
int array[][];		//是錯的
           

原因:二維數組是由多個一維數組組成的,在記憶體中,數組是按行存放的,是以在定義二維數組時,必須指定列數(即一行中包含幾個元素)。實參和形參必須是由相同長度的一維數組組成的,第二維相同的情況下,第一維可以不同,是以必須指定第二維大小;

49、局部變量和全局變量:

(1)在一個函數内部,可以在複合語句中定義變量,這些變量隻在本複合語句中有效(複合語句就是用大括号包裹的幾條語句);(這種複合語句稱為“分程式”或“程式塊”);

(2)函數(或程式塊)内定義的變量是局部變量,函數外定義的變量是全局變量。前者的範圍是自定義起到本程式塊結束,後者的範圍是自定義起到本源檔案的結束;一個習慣是将全局變量名的第一個字母用大寫表示;

為什麼在不必要時盡量别用全局變量:

全局變量在程式全部執行過程中都占用存儲單元,而不是僅在需要時才開辟單元;

降低函數通用性;降低程式清晰性;

50、變量存儲方式和生存期:

(1)使用者使用的存儲空間分為:程式區、靜态存儲區、動态存儲區,全局變量存放在靜态存儲區中,函數形參、函數中定義的未用static聲明的變量(自動變量)、函數調用時的現場保護和傳回位址都存放在動态存儲區中;

(2)四種存儲類别:自動的(auto)、靜态的(static)、寄存器的(register)、外部的(extern),根據變量存儲類别,可以知道變量的作用域和生存期;

也可将存儲類别進一步劃入局部和全局存儲類别:

三種局部變量存儲類别:

1)自動變量:

int f(int a)
{
	auto int b, c = 3;
	……
}
           

調用該函數時,系統會給這類變量配置設定存儲空間,在函數調用結束時就自動釋放這些存儲空間,auto可以省略,不寫之則隐含指定為“自動存儲類别”;

2)靜态局部變量:有時希望函數中的局部變量值在函數調用結束後不消失而是繼續保留原值,即占用的記憶體單元不釋放,下一次再調用該函數的時候,該變量已有值就是上一次函數調用結束時的值。

{
	……
	static int a;	//靜态局部變量
	……
}
           

如非必要,不要使用靜态局部變量,因為它多占記憶體,降低程式可讀性,調用次數過多時,無法濃情靜态局部變量目前值是多少;

3)寄存器變量:

register int f;	//f為寄存器變量
           

寄存器的存取速度遠高于記憶體的,是以對于使用頻繁的變量可以使用這種方法提高速度;

全局變量的存儲類别:

1)用extern擴充外部變量作用域:

如果外部變量不在檔案的開頭處定義,則有效範圍隻是定義位置到檔案結束,用extern作“外部變量聲明”,就可以從“聲明”處起,合法使用該外部變量:

int main()
{
	extern int A, B, C;	//使得外部變量A,B,C能合法地從此處使用到檔案結束
	……
}

int A, B, C;

……
           

extern int A, B, C;

亦可以寫成

extern A, B, C;

,因為這不是真正的變量定義;

2)用extern将一個檔案定義的外部變量作用域擴充到另一個檔案:

file1.c:

int A;
……
           

file2.c:

extern A;
……
           

這樣,file2也可以使用file1的外部變量A了;

3)将外部變量作用域限制在本檔案中:

如果file1的主人不希望變量被file2拿去用,那麼就可以:

file1.c:

static int A;
……
           

這是如果file2.c:

extern A;
……
           

就會出錯。

⚠️不要以為對外部變量加static聲明後才采取靜态存儲方式,而不加static的是采取動态存儲。

真相是:對局部變量加static,則将其配置設定在靜态存儲區,該變量在其作用域結束後也暫不釋放,而是堅挺到程式執行結束;對全局變量用static聲明,則将該變量作用域限于本檔案子產品;

這一塊内容要多加小心,auto和register聲明不是很常見,重點掌握extern的用法,以及static對局部和全局變量的聲明作用;

小結

對于資料,其有資料類型和存儲類别兩種屬性,而存儲類型對于全局和局部變量又可能有不同的功效(比如static聲明全局或者局部變量功能不同)。下表彙總了不同類别的變量的可見性(能被引用)和生存期(存在)

函數内能被引用 函數記憶體在 函數外能被引用 函數外存在
自動變量和寄存器變量 ✓ \checkmark ✓ ✓ \checkmark ✓ × \times × × \times ×
靜态局部變量 ✓ \checkmark ✓ ✓ \checkmark ✓ × \times × ✓ \checkmark ✓
靜态外部變量 ✓ \checkmark ✓ ✓ \checkmark ✓ ✓ \checkmark ✓(僅限本檔案) ✓ \checkmark ✓
外部變量 ✓ \checkmark ✓ ✓ \checkmark ✓ ✓ \checkmark ✓ ✓ \checkmark ✓

50、聲明和定義

聲明包括定義,但聲明并非都是定義。

定義性聲明(簡稱定義)舉例:

int a;
           

引用性聲明舉例:

extern A;
           

⚠️外部變量的定義隻能有一次,它的位置在所有函數之外(外部和局部變量的差別就在于其定義位置在“外面”還是“裡面”)。

結論

在函數内對變量的聲明(extern除外)都是定義,在函數内對其他函數的聲明不是函數的定義。

51、内部函數和外部函數

内部函數示例:

static int func(int a, int b);
           

内部函數隻能被本檔案中其他函數所調用,又稱靜态函數(名稱來源與它是用static定義的)。

⚠️通常把隻能由本檔案使用的函數、外部變量放在檔案的開頭,前面都加上static使之局部化,其他檔案都不能引用,這就提高了程式的可靠性。記住,加了static的東東隻能在本檔案使用。

外部函數示例:

extern int func(int a, int b);
           

這樣,函數就能被其他檔案所使用,如果省略extern,則預設為外部函數。

以上部分所述C語言基本要素,構成了C語言的基本身軀,接下來總結指針,為C語言注入“靈魂”。

我偏愛用C++寫面向過程的程式,因為它不僅包含了C語言的功能,還有一個不錯的指針替代品——引用(自從學了引用,我幾乎不用指針,除了接收數組的形參和malloc數組定義),除此之外還可以視情況添加面向對象的程式設計元素。指針被譽為C語言的靈魂,如果你強制我隻用C寫程式,那我的程式一定不是十尾,而是外道魔像。

三、指針

指針基本概念

1、指針變量

使用示例:

int *p1, *p2;
int a = 1;
int b = 2;
p1 = &a;
p2 = &b;
printf("p1:", * p1);
printf("p2:", * p2);
           

以上是指針變量的定義和指針指向内容的引用。

指針變量用作函數參數:

void swap(int * p1, int * p2){
	int temp;
	temp = * p1;
	* p1 = * p2;
	* p2 = temp;
}
           

以上代碼實作指針指向的兩個存儲單元内容互換,但寫成以下這樣就有問題了:

void swap(int * p1, int * p2){
	int * temp;
	* temp = * p1;
	* p1 = * p2;
	* p2 = temp;
}
           

⚠️由于未給temp指派,是以temp無确定的值,是以temp指向的單元是未知的,此時對* temp指派,就相當于改變未知單元的值,萬一改變的是重要單元,那麼可能會破壞系統正常工作狀況。

如果執行以下語句,則互換作用無法達成:

void swap(int x, int y){
	int temp;
	temp = x;
	x = y;
	y = temp;
}
           

x和y作為形參,兩者互換,但不會影響到實參互換。

⚠️C語言中實參變量和形參變量之間的資料傳遞是單向傳遞,是以即便是通過指針的方式實作自定義swap函數的功能,也是将指針指向的存儲空間中的量進行改變,而不是将指針本身的值(指針指向存儲空間的位址)進行改變。

⚠️設有

int * p

定義的整型指針變量p,則形如

p = xxx

是改變指針變量的值,即指針變量指向的位址;形如

* p = xxx

是改變指針指向的位址中的内容,而指針的指向不變。

看一個重要概念:(此處大量參考百度知道使用者CodeBlove的内容,詳情見參考連結)

⚠️⚠️⚠️二維數組和指針的指針間的關系

二維數組名即數組位址,指向首行位址,不是指針的指針。

設有二維數組

int a[10][10]

,假設a等價于指針的指針,則設

int **p=a

; 那麼要對行遞增執行p++時,由于int **是指指向一個 int *類型的指針,其資料寬度是4位元組,内置寬度,是以p将指向下一個整數元素位址,也就是p遞增了4位元組,而不是10個int型資料的寬度,這就錯位了。

是以a[10][10]的位址類型不是簡單的指針的指針,應該是

int (*)[10]

,而不是int **。

int (*p)[10]=a

,其含義為,p是一個指向含10個int類型元素的一維數組或行的指針。

指針和數組

2、通過指針引用數組

指針存儲位址,而數組名就是數組首元素的位址。

示例:

int a[10];
……	//對a指派
for(int i=0; i<10; i++){
	printf("%d", *(a+i));	//但注意每次都要重新計算位址
}
           

上述代碼可以依次輸出數組的十個元素,a代表數組首元素位址,a+i代表第(i+1)個元素的位址,當進行a+i操作的時候,系統會自動根據a的基類型将i轉換為相應的數字,比如此處a的基類型是int,則使用VC++ 6.0的系統自動将+i轉換為+4。

*(a+i)引用數組元素的方法叫指針法,a[i]引用數組元素的方法叫下标法;

⚠️以上代碼算不上真正的通過指針引用數組元素,在使用a[i]時,C編譯器會将a[i]轉化為*(a+i),再計算元素位址,即使用a[i]和*(a+i)效率是相同的。

以下是利用指針的方式來輸出數組中的全部元素:

for(p=a; p<(a+10);p++){
	printf("%d", * p);
}
           

用指針變量直接指向元素,不必每次都重新計算位址,将指針在變量上進行移動(p++)也是較為迅速的操作。

調用自定義函數,傳遞參數的時候,系統是将形參數組名當作指針變量來處理的,以下兩種寫法是等價的:

func(int a[], int n);
func(int * a, int n);
           

3、通過指針引用多元數組

設有二維數組a[3][4],則:

符号 意義
a 指向第0行,即第0行首位址
a+i 指向第i行,即第i行首位址
a[0],*(a+0),*a 指向第0行第0個元素
a[1]+2,*(a +1)+2,&a[1][2] 指向第1行第2個元素
*(a[1]+2),a[1][2] a[1][2]
* (*(a+1)+2) a[1][2]

了解上表,隻需将二維數組想象成多行組成,每一行是一個一維數組。

⚠️&a[i]或a+i指向行,a[i]或*(a+i)指向列

指向二維數組的指針變量的兩種方式:

(1)使用int *型指針變量:

int a[3][4] = {……}
int * p;
for(p=a[10]; p<a[0]+12; p++){
	printf("%4d", * p);
}
           

(2)使用int(*)[4]型指針變量

int a[3][4] = {……}
int (* p)[4]
p = a;
printf("%d", * (*(p+i)+j));
           

(這種寫法真是要多麻煩有多麻煩,反正部落客是從來不用的)

小結

引用數組元素的三種方式中,a[i]和*(a+i)的方式效率一樣,都比較費時,因為每次都要重新計算位址,定義一個指針變量指向數組元素,通過指針變量自加操作周遊數組元素則較為高效;

⚠️要區分數組名a和指針p的差別,後者是指針變量,但前者a是指針常量,是以a++操作是行不通的。

指針和字元串

4、通過指針引用字元串

首先差別以下兩種方式:

//方式一
char * string;
string = "deep dark fantasy";

//方式二
char * string;
*string = "deep dark fantasy";
           

不要認為以上兩種方式是等價的,前者是将字元串的首位址賦給指針變量string,後者是将指針變量指向的位址的内容變成那個字元串;

再來看看字元串的輸出方式:

printf("%s\n", string);
           

%s是輸出字元串時所用的格式符,再輸出項中給出字元指針變量名string,則系統會輸出string指向的字元串的第一個字元,然後自動使string加1,使之指向下一個字元,再輸出該字元……直到遇到\0為止。用字元數組名或字元指針變量名都可以輸出一個字元串,但是對于數值型數組,不能企圖用數組名輸出它的全部元素。

⚠️"deep dark fantasy"是字元串常量,C語言對字元串常量是按照字元數組處理的,在記憶體中開辟了一個字元數組來存放該字元串常量,但這個字元數組是沒有名字的,是以不能通過數組名來引用,隻能通過指針變量來引用。

5、字元指針變量和字元數組名的比較

首先,調用函數時,函數的形參和實參都可以是字元數組名或字元指針變量中的一種。

接下來,講講兩者在其他方面的使用異同:

字元數組名和字元指針變量儲存的都是字元串第一個字元的位址,但是隻能對後者指派操作:

char * a;
a = "I am umbrellalalalala";
           

這樣是沒問題的,但下面的方法是錯的:

char str[14];
str = "I am umbrellalalalala";	//數組名是位址,是常量,不能被指派
str[] = "I am umbrellalalalala";	//這樣指派也是非法的
//如果是對單個數組元素指派則沒問題:str[0] = "I"
           

⚠️定義了字元指針變量(或者其他指針變量)的時候,應當立刻給它指派,否則它将指向一個未知的位址,如果給那個位址輸入資料,同時那個位址碰巧是系統區之類的地方,則會出問題:

char * a;	//定義字元指針變量a,此時a指向的位址未知
//下面這個語句改變的不是a的指向,而是a指向的位址的内容!
scanf("%s", a);	//鍵盤輸入一個字元串,字元串被存儲到a指向的記憶體單元開始的一段連續記憶體單元中,
							//VC++ 6.0編譯這種語句時會給出警告
           

是以應當在定義了指針變量後,及時指定其指向。

看一個有趣的字元指針變量用法開開眼界:

int x = 666;
char * format;
format = "%d\n";
printf(format, x);	//輸出666,換行
           

指針和函數

接下來總結部落客從來沒用過的函數指針。

6、函數指針定義和基本使用:

如果在程式中定義了一個函數,在編譯時,編譯系統為函數代碼配置設定一段存儲空間,這段存儲空間的起始位址(又稱入口位址)就是這個函數的指針。

使用指針變量通路使用者自定義的函數:

#include<stdio.h>
int main(){
	int max(int, int);	//使用者自定義函數的聲明
	int (*p)(int, int);	//定義指向函數的指針變量p
	p = max;			//指針指向函數(将函數名“賦給”指針變量)
	……
	//接下來使用自定義函數
	c = (*p)(a, b);
	……
}

int max(int x, int y)	//max函數的定義,略
……
           

注意

(*p)

代表max函數,括号不能省略,因為*要和p先結合。如果寫成了

int * p(int, int)

,這個是範圍整型指針的p函數。

要先将函數入口位址賦給指針,才能用指針使用函數,注意位址指派操作不能寫成:

p = max(a, b)

這樣就變成了将函數所得到的函數值賦給p。

7、用指向函數的指針作函數參數

直接看用法:

void func(int (* x1)(int), int (* x2)(int, int)){
	……
	a = (* x1)(1);
	b = (* x2)(2, 3);
	……
}
           

如果func經常需要調用不同的函數,也就是說func的參數經常要改變,那麼這種情況下的函數指針的使用就非常友善了,隻需要每次調用func時給出不同的函數名作為實參就行了。

指針數組和多重指針

8、區分指針數組和“數組指針”

下面定義一個指針數組:

int *p[4];
           

下面定義一個指向一維數組的指針變量:

int (* p)[4];
           

9、指向指針的指針

簡單看一個例子:

#include<stdio.h>
int main(){
	char *name[] = {"deep", "dark", "fantasy"};	//定義一個指針數組,每個數組元素指向一個字元串
	char **p;
	int i;
	for(i=0; i<5; i++){
		p = name + i;
		printf("%s\n", *p);
	}
	return 0;
}
           

運作結果:

deep
dark
fantasy
           

完♂美

依次類推,還可以定義

int ***

型,

int ****

型指針,但不常見也沒必要。

10、動态配置設定記憶體

這裡譚浩強的書上介紹了calloc、realloc、malloc、free函數,這裡隻介紹malloc和free,例子:

int *p, i, n;
scanf("%d", &n);	//可以在運作時指定數組大小
p = (int *)malloc(n * sizeof(int));
for(i=0; i<n; i++){
	scanf("%d", p+i);
}
……
free(p);	//使用完這個數組,将其指向的動态配置設定的空間釋放
           

這個代碼段實作程式運作時再指定數組大小,并向數組中輸入元素,使用完釋放動态配置設定的空間。

11、void指針類型

void * p;
p = &a;	//相當于執行p = (void *)&a
           

執行後p得到a的純位址,但不指向a,不能通過*p輸出a的值。

12、指針的其他概念

(1)指針和指針變量

指針就是位址本身,指針變量是存儲位址的變量。

(2)重點區分

int (* p)[4]

int * p[4]

前者是指向包含四個元素的一維數組的指針變量;後者是指針數組,包含四個指向整型資料的指針元素。

四、使用者自建資料類型

結構體及其使用

1、結構體定義和基本使用

struct Date	//定義一個叫Date的結構體
{
	int month;
	int day;
	int year;
};

struct Student
{
	int num;
	char name[20];
	char sex;
	int age;
	struct Date birthday;	//結構體可以“嵌套使用”,定一個 Date型的結構體變量名曰birthday,
							//作為Student結構體的成員
	char addr[30];
};

struct Student student1, student2;	//利用定義好的結構體定義兩個結構體變量

struct Philosophy{	//定義結構體的同時定義結構體變量,可同時給結構體變量初始化,也可以不初始化
	char name[20];
	char addr[20];
	char profession[20];
} Van = {"Van", "next door", "artist"}, Billy;		//注意分号的位置

//使用以上定義的結構體變量
printf("%s\n%s\n%s\n", Van.name, Van.addr, Van.profession};
           

C99标準允許對結構體變量的某一個成員進行初始化:

//定義一個Student結構體變量b,隻指定b的name
struct Student b = {.name = "xiaoming"};	//name之前有成員運算符“.”
           

如果未進行初始化,那麼數值型成員會被系統初始化為0,字元型成員會被系統初始化為’\0’,指針型成員會被初始化為NULL。

注意以下操作,由于

.

運算符優先級最高,是以:

b.age++
           

是對b的成員age進行自加操作。

同類的結構體變量可以互相指派:

struct Student c, d;
d = {……};	//初始化内容略
c = d;	//這樣c的成員值和d的一樣
           

2、結構體數組

struct Person{
	char name[20];
	int count;
}leader[3] = {"Li", 0, "Zhang", 0, "Sun", 0};
           

結構體數組名為leader,大小為3,上述初始化方式直接在大括号中方6個值,每個結構體變量有兩個成員,這種方式恰能按序給三個數組元素指派。

3、結構體指針

指向一個結構體變量的指針:

struct Student e;	//繼續使用1中定義的結構體變量
struct Student *p;
p = &e;
           

這樣操作後,

e.name

(*p).name

以及

p->name

(常用)是一樣的。

指向結構體數組的指針:

struct Student stu[3] = {{stu[1]的成員值}, {stu[2]的成員值}, {stu[3]的成員值}};
for(struct Student * p = stu; p<stu+3; p++)
	printf (p目前指向的結構體變量);
           

上述僞代碼,p在循環中,依次指向結構體變量stu[1]、stu[2]、stu[3]。

還可以用結構體變量和結構體變量指針做函數參數,此處略。

從來沒用過的共用體

說實話,這部分内容過去也就在各種考試前看過,實際使用頻率為0……但既然都寫了這麼多了,姑且就将這部分内容總結下:

4、共用體的三種聲明和定義的形式:

(1)聲明共用體的同時定義變量:

union Data{
	int i;
	char ch;
	float f;
}a, b, c;
           

(2)先聲明共用體,再定義變量:

union Data{
	int i;
	char ch;
	float f;
}
union Data a, b, c;
           

(3)聲明共用體的同時定義變量,但不說明共用體的名稱:

union{
	int i;
	char ch;
	float f;
}a, b, c
           

5、共用體和結構體的差別:

結構體變量所占記憶體長度是各成員占的記憶體長度隻和,每個成員分别占有其自己的記憶體單元。而共用體變量所占記憶體長度等于最長的成員的長度。

也就是說,共用體變量對應的同一個記憶體段可以用來存放幾種不同類型的成員,但在每一瞬時隻能存放其中一個成員。因為在每一個瞬時,存儲單元隻能有唯一的内容。例子:

union Data{
	int i;
	char ch;
	float f;
}a;
a.i = 97;
           

表示将整數97存到共用體變量中,顯然此時a中隻有i有使用者想要的值,但也不是不可以強行輸出所有成員:

//注意97在記憶體中是01100001
printf("%d", a.i);	//輸出的是97
printf("%c", a.ch);	//輸出的是字母'a',因為'a'的ASCII碼是97
printf("%f", a.f);	//輸出0.000000
           

按照浮點數形式輸出為0的解釋:

浮點數占32位,1位符号位+8位指數位+23位尾數位:

00000000,00000000,00000000,01100001

對于float型而言,指數位全是0,而實際上計算出來的指數要加127再作為指數位進行存儲 。

補充内容:

float型的2.0的存儲方式,2.0寫作二進制的科學計數法為:

1.0 × 2 1 1.0\times2^1 1.0×21

符号位為0(正),指數位為 127 + 1 = 128 = ( 10000000 ) 2 127+1=128=(10000000)_2 127+1=128=(10000000)2​,尾數位是0(隻取1.0的小數部位)

是以在記憶體中,以32位float型存儲為:

0,10000000,00000000000000000000000	//1符号、8指數、23尾數
           

是以說,int型的97在記憶體中的二進制序列,以float型的眼光看待,就是0(個人認為理由是指數位全零是非法的,或者指數位全零是特指float型的0)。

判斷對錯:

union Data{
	int i;
	char ch;
	float f;
}

union Data a = {1, 'a', 1.5};		//錯誤,不能初始化多個成員,它們占用同一段存儲單元
union Data a = {15};	//正确,對第一個成員初始化
union Data a = {.ch='j'}	//正确,C99允許用這種方式對特定的成員初始化
           

⚠️共用體變量中起作用的成員是最後一次被指派的成員。

C99允許同類型的共用體變量互相指派:

b = a;	//a和b都是同類型的共用體,合法
           

但是不允許對共用體變量指派,也不可以企圖引用共用體變量名以得到一個值賦給非共用體變量。

枚舉類型

使用頻率類似于switch語句,簡單總結如下:

6、枚舉類型介紹,直接看代碼:

enum Weekday{sun, mon, tue, wed, thu, fri, sat};   //enum用來聲明枚舉類型,大括号中的稱為枚舉元素
enum Weekday workday;	//聲明枚舉變量

//辨識正誤:
workday = mon;	//正确,mon是指定的枚舉常量之一
workday = monday;		//錯誤,monday不是指定的枚舉常量之一
mon = 1;		//錯誤,不能以這種形式對枚舉元素指派
           

枚舉元素的引用和指派:每一個枚舉元素都代表一個整數,比如上面定義的Weekday中,sun為0,mon為1……是以指派語句

workday = mon;

相當于

workday = 1

。可以将枚舉常量當作整數輸出:

printf("%d", workday);
           

輸出的是1。依此類推,枚舉常量還可以用作條件分支中整數之間的比較。

雖然枚舉元素的值不能單獨指定數值,但是可以在定義枚舉類型的時候顯示指定:

enum Weekday{sun=7, mon=1, tue, wed =3, thu, fri, sat}workday, weekend
           

typedef的使用方法

7、

個人認為typedef主要用于增強程式可讀性的場合,部落客參與算法競賽期間沒有用過,但寫遊戲這種設計大規模程式設計工作的任務中少不了它。

typedef int Integer;
typedef float Real;

//上面兩行執行後,下面兩行等價
int i;
Integer i;
//下面兩行也等價
float f;
Real f;
           

代碼講得很清楚了,typedef就是給類型名重新起個名字使用,達到自己想要的可讀性目标。

也可以用這個來簡化複雜類型表示方法:

int * (* (*)[10])(void)		//指向包含10個元素的一維數組的指針,數組元素的類型為函數指針,
							//函數沒有參數,函數的傳回值是int指針
           

是不是巨雞兒(文明你我他)麻煩?這時候用typedef來簡化十分友善,不過譚浩強系列書上沒給這種的具體表示,估計實際中用不到,等我們能用到時再說,咱不深究這個問題。

再看一個例子,用typedef簡化結構體(這可能是typedef最常用的地方,特别是在《資料結構》這門課中):

typedef struct{
	int month;
	int day;
	int year;
}Date;

Date birthday;		//定義結構體變量,不要寫成struct Date birthday
Date *p;			//定義結構體指針變量p,指向此結構體類型資料
           

簡化數組:

typedef int Num[100];
Num a;	//定義整型數組a,a中有100個元素
           

簡化指針:

type char * String;
String p, s[10];		//p為字元指針變量,s為字元指針數組
           

簡化數組指針:

typedef int ( * Pointer)();		//Pointer是指向函數的指針類型,函數傳回整型值
Pointer p;			//p為Pointer類型的指針變量
           

typedef記憶技巧:

如果去掉typedef,剩下的部分應當是一個完整的變量或别的什麼的定義:

typedef int Num[100];		//Num是指定的名稱
//去掉typedef後:
int Num[100];		//Num是被定義的變量
           

五、對檔案的操作

未完待續

一直沒更此部分,原因是操作太少,不是很難,用的不多,自行學習。

繼續閱讀