天天看點

C語言難點分析整理

from: http://anony3721.blog.163.com/blog/static/511974200681675623419/

這篇文章主要是介紹一些在複習C語言的過程中筆者個人認為比較重點的地方,較好的掌握這些重點會使對C的運用更加得心應手。此外會包括一些細節、易錯的地方。涉及的主要内容包括:變量的作用域和存儲類别、函數、數組、字元串、指針、檔案、連結清單等。一些最基本的概念在此就不多作解釋了,僅希望能有隻言片語給同是C語言初學者的學習和上機過程提供一點點的幫助。

變量作用域和存儲類别:

了解了基本的變量類型後,我們要進一步了解它的存儲類别和變量作用域問題。

變量類别 子類别
局部變量 靜态變量(離開函數,變量值仍保留)
自動變量
寄存器變量
全局變量 靜态變量(隻能在本檔案中用)
非靜态變量(允許其他檔案使用)

換一個角度

變量類别 子類别
靜态存儲變量 靜态局部變量(函數)
靜态全局變量(本檔案)
非靜态全局/外部變量(其他檔案引用)
動态存儲變量 自動變量
寄存器變量
形式參數

extern型的存儲變量在處理多檔案問題時常能用到,在一個檔案中定義extern型的變量即說明這個變量用的是其他檔案的。順便說一下,筆者在做課設時遇到out of memory的錯誤,于是改成做多檔案,再把它include進來(注意自己寫的*.h要用“”不用<>),能起到一定的效用。static 型的在讀程式寫結果的試題中是個考點。多數時候整個程式會出現多個定義的變量在不同的函數中,考查在不同位置同一變量的值是多少。主要是遵循一個原則,隻要本函數内沒有定義的變量就用全局變量(而不是main裡的),全局變量和局部變量重名時局部變量起作用,當然還要注意靜态與自動變量的差別。

函數:

對于函數最基本的了解是從那個叫main的單詞開始的,一開始總會覺得把語句一并寫在main裡不是挺好的麼,為什麼偏擇出去。其實這是因為對函數還不夠熟練,否則函數的運用會給我們程式設計帶來極大的便利。我們要知道函數的傳回值類型,參數的類型,以及調用函數時的形式。事先的函數說明也能起到一個提醒的好作用。所謂形參和實參,即在調用函數時寫在括号裡的就是實參,函數本身用的就是形參,在畫流程圖時用平行四邊形表示傳參。

函數的另一個應用例子就是遞歸了,筆者開始比較頭疼的問題,反應總是比較遲鈍,按照老師的方法,把遞歸的過程耐心準确的逐級畫出來,學習的效果還是比較好的,會覺得這種遞歸的運用是挺巧的,事實上,著名的八皇後、漢諾塔等問題都用到了遞歸。

例子:

long fun(int n)

{

long s;

if(n==1||n==2) s=2;

   else s=n-fun(n-1);

return s;

}

main()

{

printf("%ld",fun(4));

}

數組:

分為一維數組和多元數組,其存儲方式畫為表格的話就會一目了然,其實就是把相同類型的變量有序的放在一起。是以,在處理比較多的資料時(這也是大多數的情況)數組的應用範圍是非常廣的。

具體的實際應用不便舉例,而且絕大多數是與指針相結合的,筆者個人認為學習數組在更大程度上是為學習指針做一個鋪墊。作為基礎的基礎要明白幾種基本操作:即數組指派、列印、排序(冒泡排序法和選擇排序法)、查找。這些都不可避免的用到循環,如果覺得反應不過來,可以先一點點的把循環展開,就會越來越熟悉,以後自己編寫一個功能的時候就會先找出内在規律,較好的運用了。另外數組做參數時,一維的[]裡可以是空的,二維的第一個[]裡可以是空的但是第二個[]中必須規定大小。

冒泡法排序函數:

void bubble(int a[],int n)

{

int i,j,k;

for(i=1,i<n;i++)

   for(j=0;j<n-i;j++)

   if(a[j]>a[j+1])

    {

    k=a[j];

       a[j]=a[j+1];

       a[j+1]=k;

       }

}

選擇法排序函數:

void sort(int a[],int n)

{

int i,j,k,t;

for(i=0,i<n-1;i++)

   {

   k=i;

   for(j=i+1;j<n;j++)

      if(a[k]<a[j]) k=j;

      if(k!=i)

         {

         t=a[i];

         a[i]=a[k];

         a[k]=t;

         }

   }

}

折半查找函數(原數組有序):

void search(int a[],int n,int x)

{

int left=0,right=n-1,mid,flag=0;

while((flag==0)&&(left<=right))

   {

   mid=(left+right)/2;

   if(x==a[mid])

      {

      printf("%d%d",x,mid);

      flag =1;

      }

      else if(x<a[mid]) right=mid-1;

                   else left=mid+1;

   }

}

相關常用的算法還有判斷回文,求階乘,Fibanacci數列,任意進制轉換,楊輝三角形計算等等。

字元串:

字元串其實就是一個數組(指針),在scanf的輸入列中是不需要在前面加“&”符号的,因為字元數組名本身即代表位址。值得注意的是字元串末尾的‘\0’,如果沒有的話,字元串很有可能會不正常的列印。另外就是字元串的定義和指派問題了,筆者有一次的比較綜合的上機作業就是字元串列印老是亂碼,上上下下找了一圈問題,最後發現是因為

char *name;

而不是

char name[10];

前者沒有說明指向哪兒,更沒有确定大小,導緻了亂碼的錯誤,印象挺深刻的。

另外,字元串的指派也是需要注意的,如果是用字元指針的話,既可以定義的時候賦初值,即

char *a="Abcdefg";

也可以在指派語句中指派,即

char *a;

a="Abcdefg";

但如果是用字元數組的話,就隻能在定義時整體賦初值,即char a[5]={"abcd"};而不能在指派語句中整體指派。

常用字元串函數清單如下,要會自己實作:

函數作用 函數調用形式 備注
字元串拷貝函數 strcpy(char*,char *) 後者拷貝到前者
字元串追加函數 strcat(char*,char *) 後者追加到前者後,傳回前者,是以前者空間要足夠大
字元串比較函數 strcmp(char*,char *) 前者等于、小于、大于後者時,傳回0、正值、負值。注意,不是比較長度,是比較字元ASCII碼的大小,可用于按姓名字母排序等。
字元串長度 strlen(char *) 傳回字元串的長度,不包括'\0'.轉義字元算一個字元。
字元串型->整型 atoi(char *)
整型->字元串型 itoa(int,char *,int) 做課設時挺有用的
sprintf(char *,格式化輸入) 賦給字元串,而不列印出來。課設時用也比較友善

注:對字元串是不允許做==或!=的運算的,隻能用字元串比較函數

指針:

指針可以說是C語言中最關鍵的地方了,其實這個“指針”的名字對于這個概念的了解是十分形象的。首先要知道,指針變量的值(即指針變量中存放的值)是指針(即位址)。指針變量定義形式中:基本類型 *指針變量名 中的“*”代表的是這是一個指向該基本類型的指針變量,而不是内容的意思。在以後使用的時候,如*ptr=a時,“*”才表示ptr所指向的位址裡放的内容是a。

指針比較典型又簡單的一應用例子是兩數互換,看下面的程式,

swap(int c,int d)

{

int t;

t=c;

c=d;

d=t;

}

main()

{

int a=2,b=3;

swap(a,b);

printf(“%d,%d”,a,b);

}

這是不能實作a和b的數值互換的,實際上隻是形參在這個函數中換來換去,對實參沒什麼影響。現在,用指針類型的資料做為參數的話,更改如下:

swap(#3333FF *p1,int *p2)

{

int t;

t=*p1;

*p1=*p2;

*p2=t;

}

main()

{

int a=2,b=3;

int *ptr1,*ptr2;

ptr1=&a;

ptr2=&b;

swap(prt1,ptr2);

printf(“%d,%d”,a,b);

}

這樣在swap中就把p1,p2 的内容給換了,即把a,b的值互換了。

指針可以執行增、減運算,結合++運算符的法則,我們可以看到:

*++s 取指針變量加1以後的内容
*s++ 取指針變量所指内容後s再加1
(*s)++ 指針變量指的内容加1

指針和數組實際上幾乎是一樣的,數組名可以看成是一個常量指針,一維數組中ptr=&b[0]則下面的表示法是等價的:

a[3]等價于*(a+3)

ptr[3]等價于*(ptr+3)

下面看一個用指針來自己實作atoi(字元串型->整型)函數:

int atoi(char *s)

{

int sign=1,m=0;

if(*s=='+'||*s=='-')

sign=(*s++=='+')?1:-1;

while(*s!='\0')

   {

   m=m*10+(*s-'0');

   s++;

   }

return m*sign;

}

指向多元數組的指針變量也是一個比較廣泛的運用。例如數組a[3][4],a代表的實際是整個二維數組的首位址,即第0行的首位址,也就是一個指針變量。而a+1就不是簡單的在數值上加上1了,它代表的不是a[0][1],而是第1行的首位址,&a[1][0]。

指針變量常用的用途還有把指針作為參數傳遞給其他函數,即指向函數的指針。

看下面的幾行代碼:

void Input(ST *);

void Output(ST *);

void Bubble(ST *);

void Find(ST *);

void Failure(ST *);

void (*process[5])(ST *)={Input,Output,Bubble,Find,Failure};

printf("\nChoose:\n?");

scanf("%d",&choice);

if(choice>=0&&choice<=4)

(*process[choice])(a);

{

char *name;

int chinese;

int maths;

int phy;

int total;

}ST;

main()

{

ST a[N];

FILE *fp;

void (*process[3])(ST *)={Output,Bubble,Find};

int choice,i=0;

Show();

printf("\nChoose:\n?");

scanf("%d",&choice);

while(choice>=0&&choice<=2)

   {

   fp=fopen("aa.dat","rb");

   for(i=0;i<N;i++)

      fread(&a[i],sizeof(ST),1,fp);

   fclose(fp);

   (*process[choice])(a);

   printf("\n");

   Show();

   printf("\n?");

   scanf("%d",&choice);

   }

}

void Show()

{

printf("\n****Choices:****\n0.Display the data form\n1.Bubble it according to the total score\n2.Search\n3.Quit!\n");

}

void Output(ST *a)

{

int i,t=0;

printf("Name Chinese Maths Physics Total\n");

for(i=0;i<N;i++)

   {

   t=a[i].chinese+a[i].maths+a[i].phy;

   a[i].total=t;

   printf("%4s%8d%8d%8d%8d\n",a[i].name,a[i].chinese,a[i].maths,a[i].phy,a[i].total);

   }

}

void Bubble(ST *a)

{

int i,pass;

ST m;

for(pass=0;pass<N-1;pass++)

   for(i=0;i<N-1;i++)

      if(a[i].total<a[i+1].total)

         {

         m=a[i];

         a[i]=a[i+1];

         a[i+1]=m;

         }

Output(a);

}

void Find(ST *a)

{

int i,t=1;

char m[20];

printf("\nEnter the name you want:");

scanf("%s",m);

for(i=0;i<N;i++)

   if(!strcmp(m,a[i].name))

   {

   printf("\nThe result is:\n%s, Chinese:%d, Maths:%d,     Physics:%d,Total:%d\n",m,a[i].chinese,a[i].maths,a[i].phy,a[i].total);

   t=0;

   }

if(t)

   printf("\nThe name is not in the list!\n");

}

連結清單:

連結清單是C語言中另外一個難點。牽扯到結點、動态配置設定空間等等。用結構作為連結清單的結點是非常适合的,例如:

struct node

{

int data;

struct node *next;

};

其中next是指向自身所在結構類型的指針,這樣就可以把一個個結點相連,構成連結清單。

連結清單結構的一大優勢就是動态配置設定存儲,不會像數組一樣必須在定義時确定大小,造成不必要的浪費。用malloc和free函數即可實作開辟和釋放存儲單元。其中,malloc的參數多用sizeof運算符計算得到。

連結清單的基本操作有:正、反向建立連結清單;輸對外連結表;删除連結清單中結點;在連結清單中插入結點等等,都是要熟練掌握的,初學者通過畫圖的方式能比較形象地了解建立、插入等實作的過程。

typedef struct node

{

char data;

struct node *next;

}NODE;

正向建立連結清單:

NODE *create()

{

char ch='a';

NODE *p,*h=NULL,*q=NULL;

while(ch<'z')

   {

   p=(NODE *)malloc(sizeof(NODE));

   p->data=ch;

   if(h==NULL) h=p;

      else q->next=p;

   ch++;

   q=p;

   }

q->next=NULL;

return h;

}

逆向建立:

NODE *create()

{

char ch='a';

NODE *p,*h=NULL;

while(ch<='z')

   {

   p=(NODE *)malloc(sizeof(NODE));

   p->data=ch;

   p->next=h;

   h=p;

   ch++;

   }

return h;

}

用遞歸實作連結清單逆序輸出:

void output(NODE *h)

{

if(h!=NULL)

   {

   output(h->next);

   printf("%c",h->data);

   }

}

插入結點(已有升序的連結清單):

NODE *insert(NODE *h,int x)

{

NODE *new,*front,*current=h;

while(current!=NULL&&(current->data<x))

   {

   front=current;

   current=current->next;

   }

new=(NODE *)malloc(sizeof(NODE));

new->data=x;

new->next=current;

if(current==h)

   h=new;

else front->next=new;

return h;

}

删除結點:

NODE *delete(NODE *h,int x)

{

NODE *q,*p=h;

while(p!=NULL&&(p->data!=x))

   {

   q=p;

   p=p->next;

   }

if(p->data==x)

   {

   if(p==h)

   h=h->next;

      else q->next=p->next;

   free(p);

   }

return h;

}

經常有連結清單相關的程式填空題,做這樣的題要注意看下面提到的變量是否定義了,用到的變量是否賦初值了,是否有給配置設定空間的沒有配置設定空間,最後看看傳回值是否正确。