1. 程式人生 > >《C程式設計(第四版)》學習筆記

《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”(只