《C程式設計(第四版)》學習筆記
參考教材:譚浩強的《C程式設計(第四版)》
序——對好書的定義:(20130106)
1、定位準確。要明確讀者物件,要有的放矢,不要不問物件,提筆就寫。
2、內容先進。要能反映計算機科學技術的新成果、新趨勢。
3、取捨合理。要做到“該有的有,不該有的沒有”,不要包羅永珍、貪多求全,不應把教材寫成手冊。
4、體系的當。要針對非計算機專業學生的特點,精心設計教材體系,不僅使教材體現科學性和先進性,還要注意循序漸進、降低臺階、分散難點,使學生易於理解。
5、風格鮮明。要用通俗易懂的方法和語言敘述複雜的概念。善於運用形象思維,深入淺出,引人入勝。
前言——作者對程式設計的看法:
1、為什麼要學習程式設計
計算機能高效處理各科學領域中可用邏輯分解成一定步驟的問題,程式則是操作計算機執行的主要手段,因此,對於專業人員,學好程式設計的必要性無需多言,而對於非專業人員,為了能更好的描述問題、同專業人員溝通,掌握程式設計的入門知識也是有其必要的。
2、為什麼選擇C語言
C雖然略顯古老,但相比C++,C易學,畢竟不是所有人需要用C++編寫大型程式的,再者,C更接近機器,讓我們瞭解機器工作原理,就像一個醫生不懂最基本的解剖學就在開處方。可參考《給計算機系學生的建議》
3、怎樣組織程式設計的教學?怎樣處理演算法和語言的關係
進行程式設計,要解決兩個問題:1)學習演算法(演算法是靈魂、語言是工具)。2)掌握用以實現演算法的程式語言。兩者缺一不可、相輔相成、隨著篇幅增加而提高難度。
第1章 程式設計和C語言
1.1 什麼事計算機程式
計算機並非“萬能”,它只能根據我們事先寫好的程式行動。
程式是一組計算機能識別和執行的指令。
計算機本質是程式機器。
1.2 什麼是計算機語言
人與人溝通通過人類的語言,人與計算機溝通通過計算機語言。
Machine Language經歷了幾個發展階段:
1)machine instrction(1&0構成的機器指令),缺點:難學、難寫、難記、難改、難用。
2)symbolic language(又稱assembler language由諸如ADD、SUB等助記符構成),缺點:在不同型號的計算機上沒有相容性,且仍然難學,只有專業人士能用。
3)high language(source program通過編譯轉換為object program,再通過連線轉為execution program)克服了跨計算機的相容性和難學。諸如:fortran&algol(適合數值計算)、basic&gbasic(適合初學者的小型會話語言)、cobol(適合商業管理)、pascal(適合教學的結構程式設計語言)、PL/1(大型通用語言)、LISP&PROLOG(人工智慧)、C(系統描述語言)、C++(支援OOP的大型語言)、Visual Basic(支援OOP的語言)、Java(適合網路)。
其中,High language是目前計算機行業所使用的主要語言,又經歷了以下階段:
1)非結構化(無嚴格規範、流程可隨意跳轉,程式碼難以閱讀&維護)。
2)結構化(解決了上述問題,但難以應付大型程式)。
3)面向物件(專門處理規模較大的問題)。
1.3 C語言的發展及其特點
1967年,C語言的祖先BCPL語言誕生(英國劍橋大學——Martin Richards推出),無資料型別。
1970年,B語言誕生(美國AT&T貝爾實驗室——Ken Thompson),過於簡單、功能有限。
1972~1973年,結合了BCPL和B語言的特點,C語言誕生(美國AT&T貝爾實驗室——D.M.Ritchie)。
1973年,Ken Thompson&Ken Thompson合作用C重寫了90%的UNIX(Version5)。
1978年,Brian W.Kernighan&Dennis.M.Ritchie以UNIX(Version7)為基礎合著了《The C Programming Language》。
1983年,ANSI成立委員會,制定了第一個C語言標準草案('83 ANSI C)。
1988年,Brian W.Kernighan&Dennis.M.Ritchie按照即將公佈的ANSI C重修《The C Programming Language》。
1989年,ANSI釋出了一個完整的C語言標準——ANSI X3.159-1989(ANSI C/C89)。
1990年,ISO接受C89作為國際標準ISO/IEC9899:1990。
1995年,ISO對C90做了一些修訂,命名為ISO/IEC 9899/AMD1:1995。
1999年,ISO在原基礎上,對C增加了一些功能,命名為ISO/IEC 9899/AMD1:1999。
2001年&2004年,ISO對C99增加了一些功能,記2001年的TC1和2004年的TC2。
本書以C99為依據。
C語言特點:
1)簡潔、緊湊、方便、靈活(一共37個關鍵字、C是一個很小的核心、不直接提供輸入&輸出語句和有關檔案操作的語句和動態記憶體管理的語句)。
2)運算子豐富。
3)資料型別豐富。
4)具有結構化的控制語句。
5)語法限制不太嚴格,程式設計自由度大。
6)允許直接訪問實體地址。
7)可移植性良好。
8)生成目的碼質量高,執行效率好。
1.4 最簡單的C語言程式
1.4.1 最簡單的C語言程式舉例
eg1.1要求在螢幕上輸出一行資訊“This is a program.”
#include <stdio.h>//這是編譯預處理命令
int main()//定義主函式
{//函式開始的標誌
printf("This is a C program.\n");//輸出所指定的一行資訊
printf("/*This is a multiple common*/\n");//註釋會被照常顯示
return 0;//函式執行完畢時返回函式值0
}//函式結束的標誌
每個C語言程式都必須有一個main函式,{}內是函式體。
stdio是“standard input&output”的縮寫,檔案字尾.h的意思是header file。
輸入輸出函式的相關資訊已事先放在stdio.h檔案中,#include指令把這些資訊調入供使用。
eg1.2求兩個整數之和
#include <stdio.h>
int main()
{
int a,b,sum;
a=123;
b=456;
sum=a+b;
printf("sum is %d\n",sum);//%d是指定的輸出格式,d表示用“十進位制整數”形式輸出
return 0;
}
eg1.3求兩個整數中的較大者
#include <stdio.h>
int main()
{
int max(int x,int y);//對被呼叫函式max的宣告
int a,b,c;
scanf("%d,%d",&a,&b);//接收2個整形輸入值
c=max(a,b);//呼叫max函式,將得到的值賦給c
printf("max=%d\n",c);
return 0;
}
int max(int x,int y)//定義max函式
{
int z;
if(x>y) z=x;
else z=y;
return (z);
}
程式的編譯是自上而下進行的,max函式的定義在main函式之後,因此,當編譯到第8行時,編譯系統無法知道max是什麼,所以,需要事先宣告。
&是”地址符“,&a的含義是”變數a的地址“,執行scanf函式,從鍵盤讀入兩個整數,送到變數a、b的地址處,賦值。
1.4.2 C語言程式的結構
1、一個程式由一個或多個源程式檔案組成。
1)預處理指令:編譯系統在對源程式進行“翻譯”前,先由一個“預處理程式”對預處理指令進行預處理,對於#include <stdio.h>就是將stdio檔案內容讀進來,放在#include處。
2)全域性宣告:定義函式之前的變數宣告,對整個源程式檔案範圍有效。
3)函式定義:指定函式完成我們想要的功能。
2、函式是C程式的主要組成部分:函式是C的基本單位,設計良好的程式中,每個函式用來實現一個或幾個特定的功能。
3、一個函式包括兩個部分
1)函式首部:int max(int x,int y)
2)函式體:{ /*宣告部分(定義變數or函式宣告) block 執行部分*/ }
4、程式總是從main函式開始執行的:無論它是在程式開始,還是結尾部分。
5、程式中對計算機的操作是有函式中的C語句完成的。
6、在每個資料宣告和語句的最後必須有一個分號。
7、C語言本身不提供輸入輸出語句:輸入輸出操作由庫函式scanf&printf等完成。
8、程式應當包含註釋。
1.5 執行C程式的步驟與方法
編寫源程式==》(.c原始檔)==》編譯==》(.obj目標檔案)==》連線處理==》(.exe可執行檔案)==》執行
1.6 程式設計的任務
問題分析==》設計演算法==》編寫程式==》對源程式進行編輯、編譯和連線==》執行程式,分析結果==》編寫程式文件
第2章 演算法——程式的靈魂
廣義角度看,一個程式包括:1、data struction(對資料的描述)。2、algorithm(對操作的描述)。即Data structon+Algorithm=Program。
2.1 什麼是演算法
不要認為只有“計算”的問題才有演算法,廣義地說,為解決一個問題而採取的方法和步驟就稱為“演算法”。
一個問題可以有多個演算法,為了有效解題,不僅要保證演算法正確,還要考慮演算法質量,選擇合適的演算法。
2.2 簡單的演算法舉例
求1-1/2+1/3-1/4+……+1/99-1/100。
解題思路——表面看,每一項都不一樣,但稍加分析,就可以看出:1)第1項的分子分母都是1。2)第2項的分母是2,以後每一項的分母都是前項分母加1。3)第2項前的運算子為減,第3項為加,隨後如此迴圈。
S1: sign=1
S2: sum=1
S3: deno=2
S4: sign=(-1)*sign
S5: term=sign*(1/deno)
S6: sum=sum+term
S7: deno=deno+1
S8: 若deno<=100返回S4;否則演算法結束。
#include <stdio.h>
int main()
{
int sign=1;
float term,sum=1.0;
int deno=2;
for(;deno<=100;deno++)
{
sign=(-1)*sign;
term=(sign*(1.0/deno));//分子使用1.0結果才是浮點型,若使用1結果是整型
sum+=term;
}
printf("sum is %19.18f\n",sum);
return 0;
}
2.3 演算法的特性
1、有窮性。2、確定性。3、有0個或多個輸入。4、有1個或多個輸出。5、有效性。
2.4 怎樣表示一個演算法
2.4.1用自然語言表示演算法
2.4.2用流程圖表示演算法
2.4.3三種基本結構和改進的流程圖
2.4.4用N-S流程圖表示演算法
2.4.5用偽程式碼表示演算法
2.4.6用計算機語言表示演算法
2.5 結構化程式設計方法
自頂向下,逐步細化,模組化設計(design),結構化編碼(coding)。
第3章 最簡單的C程式設計——順序程式設計(20130107)
3.1 順序程式設計舉例
編寫C語言程式需要具備的知識和能力:設計演算法,演算法需要完整、正確,採用結構化程式設計方法。
eg3.1溫度轉換,用華氏法表示的溫度64F,轉換為攝氏法表示的溫度17.8C。
#include <stdio.h>
int main()
{
float f,c;
f=64.0;
c=(5.0/9)*(f-32);
printf("f=%f\nc=%f\n",f,c);
return 0;
}
eg3.2計算存款利息,1000元本金,存一年,有3種方法可選:1)活期,年利率r1。2)定期,年利率r2。3)兩次半年定期,年利率r3。
#include <stdio.h>
int main()
{
float p0=1000,r1=0.0036,r2=0.0225,r3=0.0198,p1,p2,p3;
p1=p0*(1+r1);
p2=p0*(1+r2);
p3=p0*(1+r3/2)*(1+r3/2)
printf("p1=%f\np2=%f\np3=%f\n",p1,p2,p3);
return 0;
}
3.2 資料的表現形式及其運算
3.2.1 常量和變數
1、常量:1)整型常量-345。2)實型常量123.456 12.34e3。3)字元常量'A' '\n'。4)字串常量"String"。5)符號常量#define PI 3.1416。
2、變數:必須先定義,後使用,由變數名、變數值、儲存單元組成。
3、常變數:const int a=3;在變數存在期間其值不能改變。注意:符號常量是預處理指令,預處理編譯後符號常量都會被替換為其值,符號常量不佔儲存單元,而常變數佔用儲存單元,有變數值,只是該值不能改變。
4、識別符號(identifier):對變數、符號常量、函式、陣列、型別等命名的有效字元序列統稱為identifier。
3.2.2 資料型別
用計算機進行的計算不是抽象的理論值的計算,而是用工程的方法實現的計算,在許多情況下只能得到近似的結果。
基本型別(整型型別(int、short int、long int、long long int、char、bool)、浮點型別(float、double、float_complex、double_complex、long long_comlpex))
scalar type(arithmetic type(基本型別、列舉型別都是數值)、指標型別都是以數字來表示的)
aggregate type(陣列型別、結構體型別)
printf中,顯示無符號整型用%u、十進位制整型用%d、字元用%c。
3.2.3 整型資料
int(2or4)、shor(2)t、long(4)、long long(8)以上預設為signed,可以通過加上unsigned轉換為無符號整型。
signed用補碼錶示覆數(正數原始碼取反+1),由於第一位是符號位,可表示範圍比unsigned小一倍。
3.2.4 字元型資料
字元是以整數形式(字元的ASCII程式碼)存放在記憶體單元中的。
char(4)也可以用signed(-128~127)、unsigned(0~255)修飾,不同於整型,預設為unsigned。
3.2.5 浮點型資料
float(4)、double(8)、long double(16)沒有unsigned之分。
規範化指數形式:
+ | .314159 | 1
符號 | 小數部分 | 指數
3.2.6 怎樣確定常量的型別
常量的型別是系統自動給定的,整型預設用int,超過範圍自動轉換long int,以此類推。
浮點預設用double,若進行人工指定,比如1.23f,則用float。
3.2.7 運算子和表示式
1、基本的算術運算子(+、-、*、/、%)
2、自增、自減運算子(++、--)
3、算術表示式和運算子的優先順序與結合性(左結合性a+b+c,自左向右處理。右結合性a=b=c,自右向左處理。)
4、不同型別資料間的混合運算(int、float、double混合運算,前2者會被轉換為double進行運算。double、char混合運算,會把char的ASCII碼轉換為double進行運算。)
#include <stdio.h>
int main()
{
char c1,c2;
c1='A';
c2=c1+32;
printf("%c\n%c\n",c2,c2);
return 0;
}
5、強制型別轉換運算子((型別名)(表示式)(float)(x+y))
6、C運算子(算術運算子、關係運算符、邏輯運算子、位運算子、賦值運算子、條件運算子?:、逗號運算子、指標運算子*和&、求位元組數運算子sizeof、強制型別轉換運算子、成員運算子.->、下標運算子、其他)
3.3 C語句
3.3.1 C語句的作用和分類
1、控制語句。2、函式呼叫語句。3、表示式語句。4、空語句。5、複合語句。
3.3.2 最基本的語句——賦值語句
3.4 資料的輸入輸出
#include <stdio.h>
#include <math.h>
int main()
{
double a,b,c,disc,x1,x2,p,q;
scanf("%lf%lf%lf",&a,&b,&c);
disc=b*b-4*a*c;
p=-b/(2.0*a);
q=sqrt(disc)/(2.0*a);
x1=p+q;x2=p-q;
printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
return 0;
}
不把輸入輸出作為C語句的目的是使C語言編譯系統簡單精煉,因為將語句翻譯成二進位制的指令是在編譯階段完成的,沒有輸入輸出的語句就可以避免在編譯階段處理與硬體有關的問題,可以使編譯系統簡化,而且通用性強,可移植性好。
各種C編譯系統提供的系統函式庫是各軟體公司編制的,它包括了C語言建議的全部標準函式,還根據使用者的需要補充一些常用的函式,已對它們進行了編譯,成為.obj檔案。
C提供的輸入輸出格式較多,也比較繁瑣,初學時不易掌握,更不易記住。這裡只介紹幾種常用的,以後要用到一些特別的可以再即學即用。
printf和scanf都有格式宣告,其形式為“%”(附加字元)(格式字元),格式字元種類:P76
1、%d有符號十進位制整數。2、%5c一個字元,域寬為5,即前面有4個空格。3、%s一個字串。4、%3.2f實數,輸出3位數,其中小數佔2位,不給定預設為6位。5、%e指數形式。6、%u無符號十進位制整數。
字元輸入(getchar())輸出(putchar('x'))
#include <stdio.h>
int main()
{
putchar(getchar());
putchar(getchar());
return 0;
}
注意:在輸入Bo並按Enter後,這些字元才被送到計算機中,然後按得到字元的順序輸出2個字元Bo,如果輸入B並按回車,這樣會把回車也作為一個字元輸入。
第4章 選擇結構程式設計
if...else switch P85
第5章 迴圈結構程式設計
for do...while P114
第6章 利用陣列處理批量資料(20130108)
6.1 怎麼定義和引用一維陣列
1、定義的形式:型別符 陣列名[常量表達式] eg:int a[10];
C語言中不允許對陣列的大小作動態定義,即陣列的大小不依賴於程式執行過程中變數的值。但在被呼叫的函式(不包括main)中,陣列的長度可以是變數。
2、引用的形式:陣列名[下標] eg:a[6];
3、初始化:1)全初始化(int a[3]={0,1,3};)2)部分初始化(int a[3]={0,1};)3)全初始化為0(int a[3]={0};)4)可不制定長度(int a[]={0,1,3};)
eg:Fibonacci數列
#include <stdio.h>
int main()
{
int i;
int f[20]={1,1};
for(i=2;i<20;i++)
{
f[i]=f[i-2]+f[i-1];
}
for(i=0;i<20;i++)
{
if(i%5==0) printf("\n");
printf("%12d",f[i]);
}
return 0;
}
6.2 怎麼定義和引用二維陣列
eg:有一個3*4的矩陣,要求程式設計序求出其中值最大的那個元素的值,以及其所在的行號和列號。
#inlcude <stdio.h>
int main()
{
int i,j,row=0,colum=0,max;
int a[3][4]={{1,2,3,4},{9,8,7,6},{-10,-10,-5,2}};
max=a[0][0];
for(i=0;i<=2;i++)
{
for(j=0;j<=3;j++)
{
if(a[i][j]>max)
{
max=a[i][j];
row=i;
colum=j;
}
}
}
printf("max=%d\nrow=%d\ncolum=%d",max,row,colum);
return 0;
}
6.3 字元陣列
char c[]={"I am happy."};等價於char c[]={'I',' ','a','m',' ','h','a','p','p','y','\0'};,'\0'是由系統自動加上的,表示結束標誌,有了它,字元陣列的長度就顯得不那麼重要了。程式通過檢測'\0'的位置來判定字串是否結束。
通過char str[5];scanf("%s",str);輸入字元時,如果長度5的字元陣列,只輸入3個字元,空缺部分自動以'\0'補全,記憶體形式| H | o | w | \0 | \0 |。
char c[5];其中c是陣列名,代表字元陣列的起始地址。
執行過程:按字元陣列名c找到其陣列起始地址,然後逐個輸出其中的字元,直到遇'\0'為止。
字串處理函式<string.h>:
puts(str)將一個字串(以'\0'結束的字元序列)輸出到終端。
gets(str)從終端輸入一個字串到字元陣列。
strcat(str1,str2);STRing CATenate的縮寫,連線2個字串。
strcpy(str1,str2);將str2的內容複製到str1。strncpy(str1,str2,n);將str2前面n個字元複製到str1中。
strcmp(str1,str2);將兩個字串自左向右逐個字元相比(按ASCII碼值大小比較),直到出現不同的字元或遇到'\0'為止。比較結果:對等=0、str1>str2=正整數、str1<str2=負整數。
strlen(str);測試字串長度。
strlwr(str);字串轉換為小寫。
strupr(str);字串轉換為大寫。
再次強調:庫函式並非C語言本身的組成部分,而是C語言編譯系統為方便使用者使用而提供的公共函式。
eg:字元統計
#include <stdio.h>
int main()
{
char string[81];
int i,num=0,word=0;
char c;
gets(string);
for(i=0;(c=string[i])!='\0';i++)
{
if(c==' ')
{
word=0;
}
else if(word==0)
{
word=1;
num++;
}
}
printf("There are %d words in this line.\n",num);
return 0;
}
第7張 用函式實現模組化程式設計
7.1 為什麼要用函式
模組化程式設計的思路:把所有程式碼寫在一個主函式中會顯得難以閱讀&管理,此外,有時會多次用到某一功能,就需要多次重複編寫實現此功能的程式程式碼。這使程式冗長,不精煉。於是,有人自然想到採用“組裝”的辦法簡化設計的過程。
#include <stdio.h>
int main()
{
void print_star();
void print_message();
print_star();
print_message();
print_star();
return 0;
}
void print_star()//函式就是功能,每一個函式用來實現一個功能,函式的名字反映其代表的功能
{
printf("****************\n");
}
void print_message()
{
printf("How do you do!\n");
}
1、在設計一個較大程式時,往往把它分為若干個程式模組,每一個模組包括一個或多個函式,每個函式實現一個特定的功能。
2、一個模組對應一個源程式檔案,一個源程式檔案是一個編譯單位,在程式編譯時是以檔案為單位進行編譯的,而不是以函式為單位進行編譯的。
3、所有函式都是平行的,即在定義時是分別進行、相互獨立的。函式間不能巢狀,但可以相互呼叫。main函式只是被OS呼叫的。
7.2 怎麼定義函式
注意:通常在第1階段只設計最基本的模組,其他一些次要功能或錦上添花的功能則在以後需要時陸續不上,可以用類似void dummy(){}這樣的空函式佔位以示說明。
7.3 呼叫函式
注意:定義函式時,括號中的變數名稱為“形式引數”(或“虛擬引數”),在呼叫函式時,函式名後面括號中的引數稱為“實際引數”。實參向形參的資料傳遞是“值傳遞”,單向傳遞,只能由實參傳給形參,不能反向進行。實參&形參在記憶體中佔有不同的記憶體單元。
7.4 對被呼叫函式的宣告和函式原型
float add(float x,float y);稱為函式的宣告(declaration),其作用:編譯器是自上而下逐行進行的,為了保證在函式被呼叫時,形參和實參的個數、型別相同,就必須在被呼叫前進行宣告。
float add(float x,float y){...}第一行是函式原型(function prototype),其作用:為了便於對函式呼叫的合法性(包括一些基本資訊:函式名、函式值型別、引數個數、引數型別和引數順序)進行檢查。
7.5 函式的巢狀呼叫
eg:求4個整數中最大的一個
#include <stdio.h>
int main()
{
int max4(int a,int b,int c,int d);
int a,b,c,d,max;
printf("Please enter 4 integer numbers:");
scanf("%d%d%d%d",&a,&b,&c,&d);
max=max4(a,b,c,d);
printf("max=%d\n",max);
return 0;
}
int max4(int a,int b,int c,int d)
{
int max2(int a,int b);
int m;
m=max2(a,b);
m=max2(m,c);
m=max2(m,d);
return (m);
}
int max2(int a,int b)
{
if(a>b) return a;
else return b;
}
7.6 函式的遞迴呼叫
在呼叫一個函式時,直接或間接地呼叫函式本身稱為遞迴呼叫。
eg:有5個孩子,第1個比第2個大2歲,第2個比第3個大2歲……類推,第5個10歲,求第1個幾歲。
#include <stdio.h>
int main()
{
int age(int n);
printf("The fifth children's age: %d\n",age(5));
return 0;
}
int age(int n)
{
int c;
if(n==1)
c=10;
else
c=age(n-1)+2;
return (c);
}
經典eg:Hanoi塔,古代有一個梵塔,塔內有3個座A、B、C,開始時A座上有64個盤子,盤子大小不等,大的在下,小的在上。有一個老和尚想把這64個盤子從A座移到C座,但規定每次只允許移一個盤,且在移動過程中在3個座上都是始終保持大盤在下,小盤在上。
解題思路:
1、反向思維,現在63個盤被移到了B,第64個盤就能從A移到C,再把63個盤從B移到C就完成了,那麼,再看那63個盤如何被移動到B,62個盤移動到C,第63個盤就能從A移動到B,再把62個盤從C移動到B,以此類推。
2、步驟解析,由此可見移動過程遵循一定規律,將問題從64個盤簡化至3個盤,可知步驟A-C,A-B,C-B,A-C,B-A,B-C,A-C(3個盤子共需7步),推出移動n個盤子需經歷2^n-1步。而每次步驟需要完成的就是將1座上的n-1個盤子(藉助3座)移動到2座。
3、規律公式化,step1(n-2從1到3)、step2(n-1從1到2)、step3(n-2從3到2),每個盤移動皆是如此3步,只是對應不同層數的盤的1、2、3對應的A、B、C序號發生變化,最初1A、2B、3C,然後1A、2C、3B,再次1B、2C、3A。
#include <stdio.h>
int main()
{
void hanoi(int n,char one,char two,char three);
int m;
printf("input the number of diskes:");
scanf("%d",&m);
printf("The step to move %d diskes:\n",m);
hanoi(m,'A','B','C');
return 0;
}
void hanoi(int n,char one,char two,char three)
{
void move(char one,char three);
if(n==1) move(one,three);
else
{
hanoi(n-1,one,three,two);
move(one,three);
hanoi(n-1,two,one,three);
}
}
void move(char one,char three)
{
printf("move %c->%c\n",one,three);
}
7.7 陣列作為函式引數
注意:用陣列元素作實參時,向形參變數傳遞的是陣列元素的值,而用陣列名作函式實參時,向形參(陣列名或指標變數)傳遞的是陣列首元素的地址。
eg: 有一組一維陣列score,內放10個學生成績,求平均成績。
#include <stdio.h>
int main()
{
float average(float array[10]);
float score[10],aver;
int i;
printf("input 10 scores:\n");
for(i=0;i<10;i++)
{
scanf("%f",score[i]);
}
printf("\n");
aver=average(score);
printf("average score is %5.2f\n",aver);
return 0;
}
float average(float array[10])
{
int i;
float aver,sum;
sum=array[0];
for(i=1;i<10;i++)
{
sum=sum+array[i];
}
aver=sum/10;
return (aver);
}
eg:選擇法排序
void sort(int array[],int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(array[j]<array[k]) k=j;
}
t=array[k];array[k]=array[i];array[i]=t;
}
}
7.8 區域性變數和全域性變數
變數作用域(有效範圍):1、在函式的內部定義(區域性變數)。2、在函式內的複合語句(block)內定義(區域性變數)。3、在函式的外部定義(全域性變數)。
#include <stdio.h>
int a=3,b=5;//全域性變數
int main()
{
int a=8;//區域性變數
printf("a=%d, b=%d",a,b);//result:a=8, b=5
return 0;
}
7.9 變數的儲存方式和生存期
7.9.1 動態儲存方式與靜態儲存方式
變數生存期(存在的時間):1、靜態儲存方式。2、動態儲存方式。
要理解以上2個概念,就要先了解記憶體中的供使用者使用的儲存空間的情況:
程式區()
靜態儲存區(1、全域性變數。2、static區域性變數)
動態儲存區(1、形參。2、非static區域性變數。3、現場保護和返回地址等。)函式呼叫時開始分配,結束時釋放。
C語言中,每一個變數和函式都有兩個屬性:1、資料型別。2、資料的儲存類別(auto、static、register、extern)。
7.9.2 區域性變數的儲存類別
auto,自動變數,宣告變數時不指定類別則預設auto,由系統自動分配&釋放記憶體。
static,靜態區域性變數,函式呼叫結束,該變數記憶體不會自動釋放,當一個函式執行完畢,希望某些變數值保留,再下次呼叫時沿用,可以用static。
register,暫存器變數,當需要頻繁使用某一變數(比如一個迴圈執行1萬次),每次都要引用區域性變數,就會在存取上花費巨大,為提高效率,可將它存在暫存器中。
注意:現代計算機速度加快,編譯器優化越做越好,很多頻繁使用的變數,會由編譯器自動放入暫存器,因此,register的作用不太大。
7.9.3 全域性變數的儲存類別
如果外部變數不在檔案開頭定義,其有效的範圍就是從定義處到檔案結束。在定義處前的函式無法引用該外部變數。若要使用,必須使用extern。
eg:
#include <stdio.h>
int main()
{
extern int A;//extern關鍵字使外部變數A的作用域擴充套件至該位置
printf("%d\n",A);
return 0;
}
int A=10;
extern還可以跨檔案引用,eg:
//file1.c
#include <stdio.h>
int B=10;
int main()
{
printf("%d\n",B);//result:10
return 0;
}
//file2.c
#include <stdui.h>
extern B;
int main()
{
printf("%d\n",B);//result:10
return 0;
}
注意:系統是如何區別處理extern的?編譯時遇到extern的查詢順序:本檔案的外部變數==》其他檔案中的外部變數==》出錯處理。
當一個外部變數被定位為static時,它就只能作用於本檔案了。可以防止其他檔案通過extern誤用它。
7.10 關於變數的宣告和定義
一個函式一般由2部分組成:宣告部分(函式原型,第一行)和執行語句(功能的定義,花括號內)
int a;是一個定義性宣告(defining declaration),extern a;是一個引用性宣告(referencing declaration)。
區別:把建立儲存空間的宣告稱“定義”,而把不需要建立儲存空間的宣告稱“宣告”。
7.11 內部函式和外部函式
根據函式是否能被其它原始檔呼叫,區分為內部函式&外部函式。
7.11.1 內部函式
又稱靜態函式,形式如:static int fun(int a){...},函式作用域只侷限於所在檔案。
通常把內部函式和靜態外部變數放在檔案開頭處,前面冠以static使之區域性化,這就提高了程式的可靠性。
7.11.2 外部函式
extern關鍵字代表“該函式是在其它檔案中定義的外部函式”,形式如:extern int fun(int a){...}。
C語言規定,如果在定義函式時省略extern,則預設為外部函式。
eg:將從鍵盤輸入的字串從已存在字串中刪除
//file1.c
#include <stdio.h>
int main()
{
extern void enter_string(char str[]);
extern void delete_string(char str[],char ch);
extern void print_string(char str[]);
char c,str[80];
enter_string(str);
scanf("%c",&c);
delete_string(str,c);
print_string(str);
return 0;
}
//file2.c
void enter_string(char str[80])
{
gets(str);
}
//file3.c
void delete_string(char str[],char ch)
{
int i,j;
for(i=j=0;str[i]!='\0';i++)
{
if(str[i]!=ch) str[j++]=str[i];
str[j]='\0';
}
}
//file4.c
void print_string(char str[])
{
printf("%s\n",str);
}
第8章 善於利用指標(20130109)
8.1 指標是什麼
通過地址可以找到所需的變數單元,可以說,地址指向該變數單元。eg:2008是一個變數k的地址,我們形象化地稱其為“指標”。
注意:必須弄清2個概念:1、儲存單元的地址(Visual C++為int分配4個位元組,k的地址為2008~2011)。2、儲存單元的內容(k的內容為k=10;)。
程式經過編譯後會將變數名轉換為變數地址,對變數值得存取是通過地址進行的(scanf("%d",&k);)。
直接訪問(k=i+j;)從2000~2003取出i的值,再從2004~2007取出j的值,將它們相加後再將和送到k所佔的2008~2011單元中。
間接訪問(i_pointer=&i;)i的地址是2000~2003,值是5,通過地址賦值,i_pointer的地址是2012~2015,值是2000。
為了將值3送到i中,有兩種方式:1、(i=3;)。2、(*i_pointer=3;)。
一個變數的地址稱為該變數的“指標”,一個變數用於存放另一個變數的地址,則它稱為“指標變數(指標變數的值是地址)”。
8.2 指標變數
8.2.1 指標變數的例子
#include <stdio.h>
int main()
{
int a=100,b=10,c;
int *pointer_1,*pointer_2;//此處的*代表該變數是指標變數
pointer_1=&a;
pointer_2=&b;
printf("a=%d, b=%d\n"a,b);
printf("a=%x, b=%x\n"&a,&b);//輸出a,b變數的地址
while(1)
{
printf("*pointer_1=%d, *pointer_2=%d\n",*pointer_1,*pointer_2);//此處的*代表指標變數所指向的變數
printf("pointer_1=%x, pointer_2=%x\n",pointer_1,pointer_2);//輸出pointer的值,就是a,b變數的地址
scanf("%d",&c);
pointer=pointer+c;//嘗試改變pointer的值看看會有什麼變化
if(c==0) break;
}
return 0;
}
8.2.2 怎樣定義指標變數
(int *pointer;)int是在定義指標變數時必須指定的“基型別”。它指定指標變數可以指向的變數的型別。
指定“基型別”的意義在於:不同型別的變數所佔的儲存空間不同,因而必須知道從該地址取出的是一個字元還是整型,才能為其分配空間。
注意:1、在定義時指標變數名前的*代表該變數的型別為指標型,並非指標變數名為“*pointer”。2、必須指定“基型別”。
8.2.3 怎樣引用指標變數
1、給指標變數賦值(p=&a;)。2、引用指標變數指向的變數(printf("%d",*p);)。3、引用指標變數的值(printf("%o",p);)。注意:必須熟練掌握(&去地址運算子)(*指標運算子或稱“間接訪問”運算子)。
8.2.4 指標變數作為函式引數
eg:使用指標變數對兩數進行排序#include <stdio.h>
int main()
{
void swap(int *p1,int *p2);
int a,b;
printf("Please input 2 integer: ");
scanf("%d%d",&a,&b);
if(a<b)
{
int *p1=&a,*p2=&b;
swap(p1,p2);//變數只能進行值傳遞,指標變數可以進行地址傳遞
}
printf("max_a=%d, min_b=%d\n",a,b);
return 0;
}
void swap(int *p1,int *p2)
{
int temp;
temp=*p1;
*p1=*p2;//注意:p2是形參,且僅代表指向b指標,交換指標變數的值並不對b的值造成任何影響
*p2=temp;//指標運算子使temp直接對指標所指向的值進行賦值
}
8.3 通過指標引用陣列
8.3.1 通過指標引用陣列
陣列名代表陣列首元素的地址,(pointer=&a[0];或pointer=a;)效果相同,代表將陣列首元素地址賦值到指標變數pointer。8.3.2 在引用陣列元素時指標的運算
(pointer=a;)pointer+1指向同一陣列中的下一個元素,pointer-1指向同一陣列中的上一個元素。pointer+1實際上代表(pointer+1)*d,d是一個數組元素所佔的位元組數(比如Visual C++中float是4)。
8.3.3 通過指標引用陣列元素
引用陣列元素的兩種方法:1、下標法(a[i])。2、指標法(*(a+i))。eg:用指標變數對陣列進行賦值並遍歷
#include <stdio.h>
int main()
{
int i,a[5],*p=a;
printf("Please input 5 integer number:\n");
for(i=0;i<5;i++)
{
scanf("%d",p++);//scanf函式本身對地址指向的儲存單元進行賦值,因此不需要*p
}
p=a;//經過上述迴圈後p指向a[4]元素地址,因此需要重新指向a[0]
for(i=0;i<5;i++,p++)
{
printf("a[%d]=%d ",i,*p);
}
printf("\n");
return 0;
}
8.3.4 用陣列名作函式引數
通過指標的概念理解,就不難知道為什麼將arr[0]作為實參傳遞,函式結束後arr[0]不發生改變,而arr作為實參傳遞,函式結束後arr發生改變,前者是值傳遞,後者是首地址傳遞。
8.3.5 用指標引用多維陣列
假設有一個二維陣列a,那麼printf後會有如下結果:
1、a[0],*(a+0),*a表示0行0列元素地址。
2、a+1,&a[1]表示1行首地址。
3、a[1],*(a+1)表示1行0列元素a[1][0]的地址。
4、a[1]+2,*(a+1)+2,&a[1][2]代表1行2列元素a[1][2]的地址。
5、*(a[1]+2),*(*(a+1)+2),a[1][2]代表1行2列元素a[1][2]的值。
注意:二維陣列名是指向行的,因此a+1中的1代表一行中全部元素所佔的位元組數,一維陣列名師指向列元素的。不要把&a[i]簡單地理解為a[i]元素的實體地址,因為並不存在a[i]這樣一個實際的資料儲存單元。它只是一種地址的計算方法,能得到第i行的首地址。&a[i]和a[i]值相等,即它們代表同一地址,但它們所指向的物件是不同的,即指標的基型別是不同的。
eg:輸出二維陣列有關資料。
#include <stdio.h>
int main()
{
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
printf("%d, %d\n",a,*a);
printf("%d, %d\n",&a[0],&a[0][0]);
printf("%d, %d\n",a[1],a+1);
printf("%d, %d\n",&a[1][0],*(a+1)+0);
printf("%d, %d\n",a[2],*(a+2));//*(a+2)只是a[2]的另一種表現形式
printf("%d, %d\n",&a[2],a+2);
printf("%d, %d\n",a[1][0],*(*(a+1)+0));
printf("%d, %d\n",*a[2],*(*(a+2)+0));
int *p;
for(p=a[0];p<a[0]+12;p++)
{
if((p-a[0])%4==0) printf("\n");
printf("%4d",*p);
}
printf("\n");
return 0;
}
二維陣列中相對位置的計算:i*m+j(a[i][j],m為2維陣列的列數)
區別:p=a,p+1指向a[0][1]元素的地址,p=&a[0],p+1指向a[1]元素的地址。
注意:a[4]和(*p)[4]是一樣的,p指向一個多維陣列的首列地址,*p代表首列的一維陣列,(*p)[4]代表首列的一維陣列的第4個元素。
eg:一個班,3個學生,各學4門課,計算總平均分數以及第n個學生的成績。(指向陣列的指標做函式形參)
#include <stdio.h>
int main()
{
void average(float *p,int n);
void search(float (*p)[4],int n);
float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};
average(*score,12);
search(score,2);
return 0;
}
void average(float *p,int n)
{
float *p_end;
float sum=0,aver;
p_end=p+n-1;
for(;p<=p_end;p++)
{
sum=sum+(*p);
}
aver=sum/n;
printf("average=%5.2f\n",aver);
}
void search(float (*p)[4],int n)
{
int i;
printf("The score of No.%d are:\n",n);
for(i=0;i<4;i++)
{
printf("%5.2f ",*(*(p+n)+i));//a[n][i]
}
printf("\n");
}
void searchFail(float (*p)[4],int n)//查詢包含不及格的同學,並輸出他全科的分數
{
int i,j,flag;
for(j=0;j<n;j++)
{
flag=0;
for(i=0;i<4;i++)
{
if(*(*(p+j)+i)<60) flag=1;
}
if(flag==1)
{
printf("No.%d fails,his scores are:\n",j+1);
for(i=0;i<4;i++)
{
printf("%5.1f ",*(*(p+j)+i));
}
printf("\n");
}
}
}
8.4 通過指標引用字串
8.4.1 字串的引用方式
C語言中,字串是存放在字元陣列中的,陣列長度在初始化後不可修改(char s1[]="abc";)s1指向首地址。(char *ps1="abc";)ps1指向首地址。
#include <stdio.h>
int main()
{
char *s1="I love China! ";//用"I love China!"字串常量對*s1進行初始化,因為是常量,所以下邊迴圈內進行賦值操作會報錯
printf("%s\n",s1);
char s2[]="I love America!";
int i;
for(i=0;*(s2+i)!='\0';i++)
{
*(s1+i)=*(s2+i);//不能對常量進行賦值
}
printf("%s\n",s1);
return 0;
}
#include <stdio.h>
int main()
{
char a[]="I am a boy.",b[20],*p1,*p2;
p1=a;p2=b;//利用指標複製字元陣列
for(;p1!='\0';p1++,p2++)
{
*p1=*p2;
}
*p2='\0';
printf("string a is s%\n",a);
printf("string b is s%\n",b);
return 0;
}
注意:通過字元陣列名或字元指標變數可以輸出一個字串,但對一個數值型陣列,是不能企圖用陣列名輸出它的全部元素的。
8.4.2 字元指標作函式引數
eg:用字元型指標變數作實參
#include <stdio.h>
int main()
{
void copy_string(char from[],char to[]);
char a[]="I am a student.";
char b[]="You are a teacher.";
char *from,*to;
printf("String a is %s\nString b is %s\n",a,b);
printf("Now copy a to b...\n");
copy_string(from,to);
printf("String a is %s\nString b is %s\n",a,b);
return 0;
}
void copy_string(char from[],char to[])
{
int i=0;
while(from[i]!='\0')//改進版:while((*to=*from)!='\0')再次改進版:while((*to++=*from++)!='\0');也可以用for來寫
{
to[i]=from[i];i++//改進版:*to++;*from++;
}
to[i]='\0';
}
8.4.3 使用字元指標變數和字元陣列的比較
1、字元陣列由若干元素組成,每個元素中放一個字元,而字元指標變數中存放的是地址。
2、賦值方式:可以對字元指標變數賦值,但不能對陣列名賦值。
3、初始化的含義:(char *a="abc";)定義字元指標,並把字串的首地址賦給a。(char b[]="abc";)定義字元陣列,並把字串賦給陣列中各元素。
4、編譯時:為字元陣列分配若干儲存單元,以存放各元素的值。而對字元指標變數,只分配一個儲存單元。
5、指標變數的值可以改變,而陣列名代表一個固定的值,不能改變。
6、字元陣列中各個元素的值可改變,但字元指標變數指向的字串常量中的內容是不可被取代的。
7、char *format="a=%d, b=%d\n";printf(format,a,b);字元指標變數允許存放一句printf函式中的表示式。
8.5 指向函式的指標(20130110)
8.5.1 什麼是指向函式的指標
編譯系統為函式程式碼分配一段儲存空間,我們稱這段空間的起始地址(入口地址)為這個函式的指標。int (*p)(int,int);
8.5.2 用函式指標變數呼叫函式
#include <stdio.h>
int main()
{
int max(int,int);//等價於int max(int a,int b);
int (*p)(int,int);//定義函式指標變數
int a,b,c;
p=max;//指標指向max入口地址
c=(*p)(a,b);//等價於c=max(a,b);
return 0;
}
注意:int (*p)(int,int);中(*p)的括號代表*p是個整體,如果不加,int *p(int,int);就等於int *(p(int,int))變成了返回整型指標的函式p。
8.5.3 怎樣定義和使用指向函式的指標變數
形式:型別名 (*指標變數名)(函式引數列表);
函式指標不能進行算術運算!p+1,p-n是無意義的。
8.5.4 用指向函式的指標作函式引數
重要用途之一:可以把函式的地址作為引數傳遞到其他函式。
#include <stdio.h>
int main()
{
void fun(int,int,int (*p)(int,int));
int max(int,int);
int min(int,int);
int add(int,int);
int a=34,b=-21,n;
printf("Please choose 1max,2min or 3add: ");
scanf("%d",&n);
if(n==1) fun(a,b,max);
else if(n==2) fun(a,b,min);
else if(n==3) fun(a,b,add);
else printf("No this choose.\n");
return 0;
}
void fun(int x,int y,int (*p)(int,int))
{
int result;
result=(*p)(x,y);
printf("%d\n",result);
}
int max(int x,int y)
{
int z;
if(x>y) z=x;
else z=y;
printf("max=");
return (z);
}
int min(int x,int y)
{
int z;
if(y>x) z=x;
else z=y;
printf("min=");
return (z);
}
int add(int x,int y)
{
int z;
z=x+y;
printf("sum=");
return (z);
}
8.6 返回指標值的函式#include <stdio.h>
int main()
{
float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (*p)[4],int n);
float *p;
int i,k;
printf("enter the number of student: ");
scanf("%d",&k);
printf("The score of No.%d are:\n",k);
p=search(score,k);
for(i=0;i<4;i++)
{
printf("%5.2f\t",*(p+i));
}
printf("\n");
return 0;
}
float *search(float (*p)[4],int n)
{
float *pt;
pt=*(p+n);
return (pt);
}
8.7 指標陣列和多重指標
8.7.1 什麼是指標陣列
陣列內元素均為指標型別資料,稱為指標陣列
形式:型別名 *陣列名[陣列長度]。(int *p[4])這是一個指標陣列,(int (*p)[4])這是一個指向一維陣列的指標。
適用於:指向若干字串(比如圖書館書名有長有短,但二維字元陣列每列都是定長的,顯然浪費記憶體)。
#include <stdio.h>
#include <string.h>
int main()
{
void sort(char *name[],int n);
void print(char *name[],int n);
char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
int n=5;
print(name,n);
sort(name,n);
print(name,n);
return 0;
}
void sort(char *name[],int n)
{
char *temp;
int i,j,k;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(strcmp(name[k],name[j])>0) k=j;
}
if(k!=i)
{
temp=name[i];
name[i]=name[k];
name[k]=temp;
}
}
}
void print(char *name[],int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%s, ",name[i]);
}
printf("\n");
}
void print2(char *name[],int n)//另一種形式的print
{
int i=0;
char *p;
p=name[0];
while(i<n)
{
p=*(name+i++);//先求*(name+i)的值,即name[i](它是一個地址),然後使i加1
printf("%s, ",p);
}
printf("\n");
}
8.7.2 指向指標資料的指標
指向指標變數的指標變數,每一個元素是一個指標型的變數。(char *p[]是一個指標陣列,p+i代表p[i]的地址,p+i就是指向指標型資料的指標,可以設定一個指向指標的指標**p指向它)
#include <stdio.h>
int main()
{
int a[5]={1,3,5,7,9};
int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i;
p=num;
for(i=0;i<5;i++)
{
printf("%d",**p);//**p等價*(*p)
p++;
}
printf("\n");
return 0;
}
注意:指向指標的指標用的是“二級間址”的方法,理論上可以無限延伸,但實際應用中,很少有超過二級間址的,因為越多越混亂。
8.7.3 指標陣列作main函式的形參
main函式由系統呼叫,可以不給參(int main()),也可給參(int main(int argc,char *argv[]))。
這兩個引數通常是在命令列中輸入的,argc=argument count,argv=argument vector,argc、argv可以是其他任意的名字。
8.8 動態記憶體分配與指向它的指標變數
8.8.1 什麼是記憶體的動態分配
第7章介紹過全域性變數(靜態儲存區)&區域性變數(動態儲存區),這些儲存區稱stack。
除此之外,C語言還允許我們建立自由儲存區,以存放臨時的、不在宣告部分定義的資料,稱heap。
8.8.2 怎麼建立記憶體的動態分配
對記憶體動態分配是通過系統提供的庫函式實現的。
1、malloc
函式原型void *malloc(unsigned int size);,作用:在記憶體的動態儲存區分配一個長度為size的連續空間,返回值為連續空間的第一個位元組的地址。
malloc(100);//開闢100位元組的臨時分配域,函式值為其第1個位元組的地址
注意:指標的基型別為void,即不指向任何型別的資料,只提供一個地址。如果執行不成功,返回NULL。
2、calloc
函式原型void *calloc(unsigned n,unsigned size);,作用:在記憶體的動態區域分配n個長度為size的連續空間,足以儲存陣列,返回第一個位元組地址。
p=calloc(50,4);//開闢50*4個位元組的臨時分配域,把起始地址賦給指標變數p
3、free
函式原型void free(void *p);,作用:釋放指標變數p(應是最近一次呼叫calloc或malloc得到的函式返回值)所指向的動態空間。
free(p);//無返回值
4、realloc
函式原型void *realloc(void *p,unsigned int size);重新分配由malloc或calloc獲得的動態空間。
realloc(p,50);//將p所指向的已分配的動態空間改為50位元組
說明:以前C版本提供的malloc&calloc得到的是指向字元型資料的指標,原型為char *malloc(unsigned int size);,但實際上這些空間不一定用來存放字元。如果要存放整數,要進行型別強轉:pt=(int *)malloc(100);。
8.8.3 void指標型別
注意:“指向void型別”不等於“能指向任何型別”的資料,應理解為“指向空型別”或“不指定確定的型別”的資料。
int a=3,*p1=&a;
void *p2;
char *p3;
p2=(void *)p1;//將p1的值強轉為void *型別,賦值給p2
p3=(char *)p2;//參照同上
printf("%d, %c",*p2,*p2);//error
printf("%d, %c",*p3,*p3);//normal output
說明:當把void指標賦給不同基型別的指標變數時,編譯系統會自動進行轉換,不必使用者自己進行強制轉換。、
eg:建立動態陣列,輸入5個學生的成績,用函式檢查其中有無低於60分,輸出不合格的成績。
#include <stdio.h>
int main()
{
void check(int *);
int *p1,i;
p1=(int *)malloc(5*sizeof(int));
for(i=0;i<5;i++)
{
scanf("%d",p1+i);
}
check(p1);
return 0;
}
void check(int *p)
{
int i;
printf("They are fail: ");
for(i=0;i<5;i++)
{
if(p[i]<60) printf("%d ",p[i]);
}
printf("\n");
}
8.9 有關指標的小結
1、指標就是地址,但指標&指標變數不同。
2、指向意味著通過物件的地址。
3、一維陣列名arr代表首元素地址,*p=arr;陣列給指標p賦值首元素地址。
4、關於指標的歸納比較:
變數定義 | 型別表示 | 含義 |
int i | int | 定義整型變數i |
int *p | int * | 定義p為指向整型資料的指標變數 |
int a[5] | int [5] | 定義有5個元素的整型陣列 |
int *p[4] | int *[4] | 定義指標陣列,由4個指向整型資料的指標元素組成 |
int (*p)[4] | int (*)[4] | p為指向包含4個元素的一維陣列的指標變數 |
int fun() | int () | 返回int函式值的函式fun |
int *p() | int *() | p為返回一個(指向整型資料的)指標的函式 |
int (*p)() | int (*)() | p為指向(返回整型資料的)函式的指標 |
int **p | int ** | 同*(*p),p是一個指向整型資料的指標變數的指標變數 |
void *p | void * |
p是一個指標變數,基型別為void,即p是一個不指向具體物件的指標變數 |
5、指標運算,明白以下含義:p++、p--、p+i、p-i、p=&a、p=arr、p=&arr[i]、p=fun、p1=p2。
6、指標變數可以有空值,p=NULL(在stdio.h標頭檔案中#define NULL 0),它使p指向地址為0的單元。
注意:指標使用實在太靈活,對熟練的程式設計師可以編出高質、高效的程式碼,但對初學者要慎用,比如:未對p賦值的情況下,就向*p賦值會破壞其它單元內容。
第9章 使用者自己建立資料型別(20130111)
9.1 定義和使用結構體變數
9.1.1 自己建立結構體型別
C語言允許使用者自己建立由不同型別資料組成的組合型的資料結構,它稱為structre(其它高階語言可能稱它為record)。
struct Student//structre tag
{
int num;//member
char name[20];
char sex;
int age;
float score;
char addr[30];
}//structre = structre tag + member
9.1.2 定義結構體型別變數
1、先宣告,再定義。2、宣告時定義。3、宣告但不給定結構名直接定義。
//method1:先宣告,再定義
struct Student
{
//member
}
struct Student s1,s2;
//method2:宣告時定義
struct Student
{
//member
} s1,2;
//method3:宣告但不給定結構名,直接定義
struct
{
//member
} s1,s2;
9.1.3 結構體變數的初始化和引用
#include <stdio.h>
int main()
{
struct Student
{
int num;
char name[20];
int age;
char sex;
}s1={1001,"Li Ming",18,'M'};//定義時初始化
struct Student s2;
s2.name="Wang Wu";//定義後初始化
printf("No.%d name=%s\n",s1.num,s1.name);
return 0;
}
注意:1、結構體變數的成員允許是另一個結構體變數(class.student.name)。2、結構體變數的成員允許進行各種運算(student.age++;)。3、同類的結構體變數可以相互賦值(s1=s2;)。4、可以引用結構體變數成員的地址(scanf("%s",&student.name);)。
9.2 使用結構體陣列
eg:有n個學生的資訊,要求按成績高低進行排序輸出。
#include <stdio.h>
struct Student
{
int num;
char name[20];
float score;
};
int main()
{
struct Student stu[5]={{10101,"Zhang",78},{10103,"Wang",89.5},{10106,"Li",86},{10108,"Ling",73.5},{10110,"Sun",100}};
struct Student temp;
const int n=5;
int i,j,k;
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;
}
}
for(i=0;i<n-1;i++)
{
printf("%6d %8s % 6.2f\n",stu[i].num,stu[i].name,stu[i].score);
}
printf("\n");
return 0;
}
9.3 結構體指標
9.3.1 指向結構體變數的指標
#include <stdio.h>
struct Student
{
int num;
char name[20];
};
int main()
{
struct Student stu_1;
struct Student *p;
p=&stu_1;
stu_1.num=1001;
strcpy(stu_1.name,"Li Ming");
printf("Num: %d Name: %s\n",(*p).num,p->name);//為了方便、直觀,C語言允許把(*p).name表示成p->name
return 0;
}
注意:如果stu是一個結構體變數,並且struct Student *p=&stu;,以下3種用法等價:stu.name、(*p).name、p->name。
9.3.2 指向結構體陣列的指標
struct Student *p=stu;
for(int i=0;i<n;i++,p++)//用法類似指向陣列的指標,只是輸出格式變為structre
{
printf("Num: %d Name: %s Age: %d\n",p->num,p->name,p->age);
}
9.3.3 用結構體變數和結構體變數的指標作函式引數
無論是結構體變數本身還是它的成員作實參,都是進行“值傳遞”,這意味著一個大型、多條記錄的結構體變數在進行函式呼叫時記憶體開銷巨大,因此使用結構體變數的指標做實參更高效。
#include <stdio.h>
#define N 3
struct Student
{
int num;
char name[20];
float score[3];
}
int main()
{
void input(struct Student stu[]);
struct Student max(struct Student stu[]);
void print(struct Student stu[]);
struct Student stu[N],*p=stu;
input(p);//使用結構圖變數的指標作實參,能有效去除“值傳遞”時的(複製值的)開銷
print(max(p));//同上
return 0;
}
9.4 用指標處理連結串列
9.4.1 什麼是連結串列
一種重要的資料結構,可組成不定長單元陣列。
(head1249)-->(A1249,1356)-->(B1356,1475)-->(C1475,1021)-->(D1021,NULL)
連結串列有一個“頭指標”變數,它存放一個地址(指向下一個元素),每一個元素稱為“結點”,每個結點包括2個部分(1、該結點資料。2、下個結點的地址。),如此迴圈直到“表尾”的NULL。
用結構體建立連結串列非常合適。
9.4.2 建立簡單的靜態連結串列
#include <stdio.h>
struct Student
{
int num;
float score;
struct Student *next;
};
int main()
{
struct Student a,b,c,*head,*p;
a.num=10101;a.score=89.5;
b.num=10103;b.score=90;
c.num=10107;c.score=85;
head=&a;
a.next=&b;
b.next=&c;
c.next=NULL;
p=head;
do
{
printf("%1d %5.2f\n",p->num,p->score);
p=p->next;
}while(p!=NULL);
return 0;
}
9.4.3 建立動態連結串列
動態建立指在執行過程中從無到有,一個接一個地開闢結點和輸入各結點資料。
#include <stdio.h>
#include <stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{
int num;
float score;
struct Student *next;
};
int n;
struct Student * creat(void)
{
struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(struct Student *)malloc(LEN);
scanf("%1d,%f",&p1->num,&p1->score);
head=NULL;
while(p1->num!=0)
{
n=n+1;
if(n==1) head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student *)malloc(LEN);
scanf("%1d,%f",&p1->num,&p1->score);
}
p2->next=NULL;
return (head);
}
int main()
{
struct Student * pt;
pt=creat();
printf("\nnum: %1d\nscore: %5.1f\n",pt->num,pt->score);
return 0;
}
9.4.4 輸出連結串列
void print(struct Student *head)
{
struct Student *p;
p=head;
if(head!=NULL)
{
do
{
printf("%d, %5.1f,\n",p->num,p->score);
p=p->next;//每次輸出完本結點資料,將下一結點指標賦給自己
}while(p!=NULL);
}
}
說明:連結串列是一個比較深入的內容,初學者有一定難度,專業人員應該掌握。
9.5 共用體型別
9.5.1 什麼是共用體型別
使幾個不同型別的變數可以共享同一段記憶體的結構,稱為“共用體”型別的結構。
union Data
{
int i;//以下不同型別的變數都可以存放於Data這一型別中
char ch;
float f;
}a,b,c;
9.5.2 引用共用體變數的方式
printf("%c\n",a.ch);//正確的引用方式
printf("%c\n",a);//錯誤的引用方式
9.5.3 共用體型別資料的特點
1、共用體開闢的記憶體可用來存放幾種不同型別的成員,但同一時間內只能存放其中一種。
2、可以對共用體初始化,但只能初始化其中一種型別。
3、起作用的是最後一次被賦值的成員。
4、一個共用體內所有成員起始地址是同一個。
5、不能對共用體變數名進行賦值。
6、共用體型別可以出現在結構體型別定義中,也可以定義共用體陣列。
9.6 使用列舉型別
如果一個變數只有幾種可能,則可定義為Enumeration。
enum Weekday{sun,mon,tue,wed,thu,fri,sat};//宣告列舉型別
enum Weekday workday,weekend;//定義列舉變數
列舉預設第一個元素值為0,下一個元素依次+1遞增,可以在宣告時顯示指定其值,但之後不能再進行賦值改動。
enum EnumVar{x=1,y=3,z};//正確,z根據y的值自動+1
x=5;//錯誤
9.7 用typedef宣告新型別名
1、簡單的替代原有型別名(typedef int Integer;)。2、命名一個複雜的型別表示方法(typedef char* String;)。
第10章 對檔案的輸入輸出(20130112)
10.1 C檔案的有關基本知識
1、程式設計中,主要用到兩種檔案:程式檔案(*.c、*.obj等)、資料檔案(供程式執行時讀寫的資料,本章主要討論物件)。
2、為了簡化使用者對IO裝置的操作,使使用者不必區分各種IO裝置之間的區別,OS把各種裝置都統一作為檔案來處理。
3、data file一個重要概念,一般指儲存在外部介質上資料的集合。由執行環境(即OS)進行統一管理。
4、IO是資料傳送的過程(資訊從源到目的端的流動),我們形象地稱其為stream(IO的開始和結束僅受程式控制),即資料流。計算機流向data file(印表機、磁碟)稱output,data file流向計算機稱input。
5、C語言把data file看作是一個字元(或位元組)的序列,即由一個一個字元(或位元組)的資料順序組成。
6、data file的分類:有兩種,分別是ASCII檔案(又稱text file)、二進位制檔案(又稱image file),兩者都以binary形式儲存,因此,text file要作為ASCII輸出需要一個轉換過程(額外的開銷)!
7、緩衝檔案系統:從記憶體向磁碟輸出資料必須先送到記憶體的buffer,裝滿buffer後才一起送到磁碟去。從磁碟讀取資料,則一次將一批資料讀入buffer(充滿為止),然後再從buffer逐個送到程式資料區。
8、緩衝檔案系統中的關鍵概念“檔案型別指標”,每個被使用的檔案都在記憶體中開闢一個相應的檔案資訊區(用以儲存名字、狀態、當前位置等),這些資訊是儲存在一個由系統宣告,取名為FILE的structre中。
eg:C編譯環境提供的stdio.h標頭檔案中有以下的檔案型別宣告。
typedef struct
{
short level;//緩衝區“滿”或“空”的程度
unsigned flags;//檔案狀態標誌
char fd;//檔案描述符
unsigned char hold;//如緩衝區無內容不讀取字元
short bsize;//緩衝區的大小
unsigned char *buffer;//資料緩衝區的位置
unsigned char *curp;//指標當前的指向
unsigned istemp;//臨時檔案指示器
short token;//用於有效性檢查
}FILE;
9、檔案資訊是在開啟檔案時由系統根據檔案的情況自動放入的,使用者不必過問,一般不通過變數的名字來引用FILE型別變數,而是通過指標。
10.2 開啟與關閉檔案
1、開啟:fopen(檔名,使用檔案方式)
FILE *fp;//定義一個指向檔案的指標變數pt
fp=fopen("a1","r");//將fopen函式的返回值賦給指標變數fp
檔案使用方式 | 含義 | 如果指定檔案不存在 |
“r”(只讀) | 為了輸入資料,開啟一個已存在的text file | 出錯 |
“w”(只 |