程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C語言范例學習03-上,c語言范例03-

C語言范例學習03-上,c語言范例03-

編輯:關於C語言

C語言范例學習03-上,c語言范例03-


不好意思,這兩天要幫家裡做一些活兒。而且內容量與操作量也確實大幅提升了。所以寫得很慢。

不過,從今天開始。我寫的東西,許多都是之前沒怎麼學的了。所以速度會慢下來,同時寫得也會詳細許多。

第三章是數據結構,數據結構可以說是理論學習的重點。同時許多學校(包括我所就讀的大學)都開設了數據結構課程。但是講的東西大多太過理論性,主要講解概念與思想。

另外,數據結構可是計算機考研中專業課的重點科目哦。

這章數據結構包括結構體、鏈表、棧與隊列、串與廣義表、二叉樹、圖與圖的應用。

每一個都是重點,所以我會細細地過一遍。

3.1結構體

也許你的C語言老師不會講解鏈表、二叉樹等,但他一定會講解結構體的使用,並且讓你們作出代碼實現。

結構體是什麼?其實結構體就是一個制式容器。裡面包括了規定的各個變量。

比如說,我設定了

 1 struct student
 2 {
 3   char name;
 4   int age;
 5   float score;
 6 };

 

那麼在這個結構體所在的程序裡,student就是一個包含三個不同數據類型的變量。當然,你也可以認為student是一個新的數據類型,一個自己設立的數據類型。不過這個數據類型是一個特定的數組,因為其中有著許多不同的數據類型(數組只能是相同數據類型)。

創建結構體是要用到struct。其他用法將在接下來的例子中提到。

實例076

問題:從鍵盤中輸入姓名和電話號碼,以#結束,編程實現輸入姓名,查詢電話號碼的功能。

邏輯:首先一個主函數作為程序入口,實現程序運行框架。然後一個存儲函數readin實現數據的存儲,再一個查詢函數seach實現數據的查找。

主函數無非定義,初始化變量。調用存儲函數來存儲我們輸入的函數。再調用查詢函數來輸出所要的結果。

存儲函數定義,初始化變量。建立循環,判讀輸入是否為#,同時將數據存儲。

查詢函數定義,初始化變量。建立循環,判斷所要查找的數據與當前數據是否符合,判斷數據是否已經全部遍歷。

程序代碼:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #define MAX 101
 4 struct aa
 5 {
 6   char name[15];
 7   char tel[15];
 8 };
 9 int readin(struct aa *a)
10 
11 //注意這裡結構體aa的變量名為*a,是一個指針,因為這個結構體在三個函數中均被調用。所以只有使用指針才較為便利地可以實現函數間大量數據的調用。
12 
13 //其實,在使用指針為變量時,就已經建立了一個以a為名的aa型結構體數組了。
14 
15 {
16   int i=0,n=0;
17   while(1)
18 
19   //本人最為喜歡的循環體設置方法,包括for(;1;)的設立,循環體中再建立相關判斷條件。
20   {
21     scanf("%s",a[i].name);
22     if(!strcmp(a[i].name,"#"))
23 
24     //利用這樣的判斷語句判斷輸入是否為#,(其實可以在判斷語句中實現賦值與判斷的共同實現,雖然好看,但是確保自己邏輯正確。)
25 
26     //strcmp()比較兩個字符串是否相等,相等返還0,相等返還0,相等返還0.
27       break;
28     scanf("%s",a[i].tel);
29     i++;
30     n++;
31   }
32   return n;
33 
34   //返回輸入結構體的數量。(一組數據就是一個結構體)
35 }
36 void search(struct aa *b,char *x,int n)
37 
38 //x是一個指針,主函數中name是數組名,即一個指針。
39 {
40   int i;
41   i=0;
42   while(1)
43   {
44     if(!strcmp(b[i].name,x))
45     {
46       printf("name:%s            tel:%s\n",b[i].name,b[i].tel);
47       break;
48     }
49     else
50       i++;
51     n--;
52 
53     //其實這個n是用來計算是否所有結構體遍歷。但是我認為用i就行了。雖然i是在判斷語句裡面,但這個語句在沒找到目標是是一直執行的,如同在外面和n相同待遇
54 
55     //當判斷語句找到目標時,那就更不需要他了。因為程序結束了。
56 
57     if(n==0)
58     {
59       printf("No found!");
60       break;
61     }
62   }
63 }
64 main()
65 {
66   struct aa s[MAX];
67   int num;
68   char name[15];
69   num=readin(s);
70 
71   //這個語句有兩個用處,1.將readin函數返回給num;2.readin函數將數據存儲於數組s。
72   printf("input the name:");
73   scanf("%s",name);
74   search(s,name,num);
75 }

 

反思:結構體在日後的使用中,就好像int、char這些數據類型一般,很常用。同時,這個程序中,我們學到程序中函數的簡單架構。另外,我們完全可以將這個函數擴展化。比如小到平時存儲東西,查找東西。大到各個程序中的存儲,查找功能等。

3.2鏈表

一般來說,數據結構這門課講解的第一個數據結構一般就是鏈表。很多教材第一頁就是鏈表。

曾經有公司高層談論編程時,說:”學生大多缺乏實際編程經驗,我在招聘時經常讓應聘者做一個簡單的鏈表。結果很多人都不會。“

鏈表可以說是數據結構應用的入門,如果連這個都不會,最好別去考慮與編程有關的職業。

曾經看過一本書,它的開頭講述了一個故事。說有一個剛入行的程序員第一次處理網站數據,做了一個1500長度的數組和循環用來存儲臨時數據。然後不久那個網站就掛了。後來作者去就將他做得數組改成了一個1500的循環鏈表就完事兒了。

鏈表是動態分配存儲空間來存儲數據,避免了空間的浪費。(當年用數組存儲數據有多少人和我一樣糾結空間大小的舉個手)同時,鏈表的存儲空間可以不連續、不連續、不連續。這樣產生了許多便利,並且有效利用了存儲空間。

我會講解每一個例子。(我才不會說,之前看的時候,我就記得單向鏈表了。。。)

實例078    創建單向鏈表

問題:創建一個單向鏈表,實現數據的輸入、輸出。

邏輯:鏈表是由一個個節點組成的。每一個節點分為兩部分:數據域與指針域。數據域用來存儲數據,指針域用來存儲下一個節點位置(雙向鏈表就還有一個指向前一個節點的鏈表)。一般第0個節點是整個鏈表的頭結點,稱為”頭指針“,一般不存放數據,只存放指向第1個節點的指針。鏈表的最後一個節點的指針設為空(NULL),作為鏈表的結束標志。

程序代碼:

 1 #include<stdio.h>
 2 #include<malloc.h>
 3 //malloc是用來劃分存儲空間的函數。
 4 struct LNode
 5 {
 6   int data;
 7   struct LNode *next;
 8   //指向下一個的結構體。從而形成鏈表。
 9 };
10 struct LNode *create(int n)
11 {
12   int i;
13   struct LNode *head,*p1,*p2;
14   //head表示頭節點。p1,實現存儲空間的獲取,數據域賦值等。p2,作為與p1的中間指針,構成鏈表的替換連接。
15   int a;
16   head=NULL;
17   //頭節點為空。
18   printf("Input the integers:\n");
19   for(i=n;i>0;--i)
20   {
21     p1=(struct LNode*)malloc(sizeof(struct LNode));
22     //malloc(sizeof(struct LNode)表示劃分LNode大小的存儲空間。
23     //(struct LNode*)表示數據類型的轉換,將劃分來的存儲空間首地址轉化為LNode的變量名(指針類型)。
24     scanf("%d",&a);
25     p1->data=a;
26     //向p1的結構體中存儲數據。
27     if(head==NULL)
28     {
29       head=p1;
30       p2=p1;
31       //對頭節點的處理。
32     }
33     else
34     {
35       p2->next=p1;
36       //表明p2的下一個節點是p1。其中p2是上個循環中的p1。也就是說,p2只是一個中間變量temp。
37       p2=p1;
38       //印證了上個注釋中,p2的由來。
39       //具體的過程在變量名的說明中就已經體現了。
40     }
41   }
42   p2->next=NULL;
43   //最後一個節點的指針域為空,作為結束的標志。
44   return head;
45   //返回代表頭結點的結構體的變量名(指針)。
46 }
47 void main()
48 {
49   int n;
50   struct LNode *q;
51   printf("Input the count of the nodes you want to creat:");
52   scanf("%d",&n);
53   q=create(n);
54   //輸入,返回。
55   printf("The result is:\n");
56   while(q)
57   //q只有到了最後一個節點的next時,為NULL,才會跳出循環。一個很有用的小技巧。
58   {
59   printf("%d    ",q->data);
60   q=q->next;
61   //實現q的轉換,從而輸出所有數據。
62   }
63   getch();
64 }

 

如果對其中提到的malloc函數,以及相關的calloc函數,free函數感興趣的話,可以浏覽http://blog.csdn.net/shuaishuai80/article/details/6140979。

反思:學習該例有這麼幾個要點: 1.指針不要混淆,不要將結構體名的指針和結構體內next的指針混淆,雖然有時這兩者表達的是同一個東西;

                2.一定要完全理解p1=(struct LNode*)malloc(sizeof(struct LNode));這句代碼;(解釋在樣例中)

                3.正確理解p2->next=p1;與p2=p1;這兩句代碼的實現。(無法理解可帶入數據進行兩到三次循環,即可理解)。  

實例079    創建雙向鏈表

問題:創建一個雙向鏈表 ,並將這個鏈表中數據輸出到窗體上,輸入要查找的學生姓名,將查找的姓名從鏈表中刪除,並現實刪除後的鏈表。

邏輯:其實就邏輯而言是很簡單的。與之前的單向鏈表相同,不過比後繼結點的設置多了一個前驅節點的設置。另外查找,與一般的結構體數組查找,並沒有什麼區別。至於刪除嘛,就需要改動刪除節點的前驅節點和後繼結點的設置。具體操作,可以看代碼。

程序代碼:

  1 #include<stdio.h>
  2 typedef struct node
  3 {
  4   char name[20];
  5   struct *prior,*next;
  6   //設立節點的前驅節點和後繼結點。
  7 }stud;
  8 stud *creat(int n)
  9 {
 10   stud *p,*h,*s;
 11   int i;
 12   h=(stud*)malloc(sizeof(stud));
 13   //申請存儲空間
 14   h->name[0]='\0';
 15   //設置name為空
 16   h->prior=NULL;
 17   h->next=NULL;
 18   p=h;
 19   for(i=0;i<n;i++)
 20   {
 21     s=(stud*)malloc(sizeof(stud));
 22     p->next=s;
 23     printf("Input the %d student'sname:",i+1);
 24     scanf("%s",s->name);
 25     s->prior=p;
 26     s->next=NULL;
 27     p=s;
 28     //方法和單向鏈表沒什麼區別,區別只在於多了一個前驅節點的設置。
 29     //從一些方面來說,多了前驅節點更容易理解了。
 30   }
 31   p->next=NULL;
 32   //設置最後節點的後繼結點為空,作為結束標志。
 33   return(h);
 34 }
 35 stud *search(stud *h,char *x)
 36 {
 37   stud *p;
 38   char *y;
 39   p=h->next;
 40   while(p)
 41   {
 42     y=p->name;
 43     if(strcmp(y,x)==0)
 44     //判斷是否為尋找的目標。
 45       return(p);
 46       //返回目標地址。
 47     else
 48       p=p->next;
 49       //繼續下一個節點的檢測。
 50   }
 51   printf("cannot find data!\n");
 52   //沒有任何符合條件的返回。
 53 }
 54 void del(stud *p)
 55 {
 56   p->next->prior=p->prior;
 57   //令p的下一個節點的前驅節點與現在p的前驅節點一致(這樣在前驅節點中p就不存在了。並且鏈表沒有斷開。)
 58   p->prior->next=p->next;
 59   //令p的前一個節點的後繼結點與現在p的後繼結點一致(這樣在後街節點中p就不存在了。並且鏈表沒有斷開。)
 60   //切記:p->next是指p的後一個節點。p->prior同理。
 61   //無法理解的話,請在草稿紙上畫出鏈表圖,就清晰無比了。
 62   //這裡兩句代碼在有些運行環境中會報錯,可以自行更改語句或環境。
 63   free(p);
 64   //釋放p的存儲空間(鏈表上p已經不存在了。當然不能讓它占著空間了)
 65 }
 66 main()
 67 {
 68   int number;
 69   char sname[20];
 70   stud *head,*sp;
 71   puts("Please input the size of the list:");
 72   scanf("%d",&number);
 73   head=creat(number);
 74   sp=head->next;
 75   printf("\nNow the double list is:\n");
 76   while(sp)
 77   {
 78     printf("%s",&*(sp->name));
 79     sp=sp->next;
 80   }
 81   //之前的都與單向鏈表相同。
 82   printf("\nPlease input the name which you want to find:\n");
 83   scanf("%s",sname);
 84   sp=search(head,sname);
 85   //通過search函數,尋找到所要尋找的sname的地址sp。
 86   printf("the name you want to find is:\n",*&sp->name);
 87   del(sp);
 88   //將sp帶入到del()函數中,刪除。
 89   sp=head->next;
 90   //這句話只是為了下面的while循環做二次利用的。完全可以刪除這句話,為下面的循環單獨設置一個變量。
 91   printf("\nNow the double list is:\n");
 92   while(sp)
 93   {
 94     printf("%s",&*(sp->name));
 95     sp=sp->next;
 96   }
 97   printf("\n");
 98   puts("\nPress any key to quit...");
 99   getch();
100   //與單向鏈表相同的輸出原理。
101 }

 

 反思:雙向鏈表與單向鏈表並沒有什麼不同。要記得的是,雙向鏈表可是比單向鏈表多了一個prior的指針,無論是添加,刪除,移動,修改,都要記住。

實例080    循環鏈表

還記得開頭,我說的那個網站關於鏈表的故事嗎。這下說的就是循環鏈表。

其實到了這個時候也就沒有什麼說的了。循環鏈表就是將最後節點的後繼結點設置為頭結點。

循環鏈表與普通鏈表的操作基本一致,只是在算法中循環遍歷鏈表節點時判斷條件不再是p->next是否為空,而是是否等於鏈表的頭結點

程序代碼:(這裡就不演示。篇幅不應該留給不需要的東西。)

實例081    雙鏈表逆置

問題:創建一個雙向鏈表,將雙向鏈表的節點逆置,即將尾節點放到第一個節點的位置,倒數第二個節點放到第二個節點的位置,依此類推。

邏輯:雙向鏈表的創建、輸入、輸出,都和之前一樣。為了模塊化的操作,就單獨創建一個reverse函數,用來執行逆置操作。逆置操作就是指針的變換。

部分代碼:

 1 stud *reverse(stud *head)
 2 {
 3   stud *p,*r,*h;
 4   h=head->next;
 5 
 6   //設置h。
 7   if(h&&h->next)
 8   {
 9     p=h;
10     r=p->next;
11     p->next=NULL;
12     while(r)
13     {
14       p=r;
15       r=r->next;
16       p->next=h;
17       h->prior=p;
18       h=p;
19 
20       //中間變量p,變量r從鏈表的頭部向後遍歷,變量h從鏈表的尾部向前遍歷。變量p作為中間變量來轉移指針地址,完成鏈表逆置。
21     }
22     head->next=h;
23     h->prior=head;
24 
25     //事後,完成鏈表的補全。即鏈表的開頭和結尾。
26     return head;
27   }
28 }

 

反思:想要完成這些,自己一定不能混淆指針,指針的指針,指針的指向。這三個概念。

後面還有很多相關的知識,比如逆序輸出,約瑟夫環,鏈表的元素插入,節點插入,節點刪除,合並鏈表以及頭插入法建立鏈表等。但是,我認為,如果之前的鏈表都懂了。那麼後面的知識無非就是鏈表的基礎知識版應用。如果有需要,可以找我。

總結:其實鏈表在學習後,就會發現,之前指針學得好是多麼重要啊。尤其是指針的指針這一點。如果,指針學得好,那麼鏈表要學的就是鏈表的概念,鏈表的一些專用函數,以及常用的算法。那麼之後的一些應用就是水到渠成的事兒。(完全就是之前程序的鏈表版應用啦。)

由於時間關系,這次就先寫這麼多了。(其實這次寫的比之前多了很多,尤其線下還做了那麼多的程序調試。)

謝謝大家的鑒賞和評價。也希望能夠結識一些相關興趣的小伙伴。

另外,也許不久,我還會寫一些別的東西。

(剛剛博客園官方通知了我,我才知道有代碼插入這個東西。挺贊的。對於我這個有代碼行潔癖的,每次copy後敲Tab,也是蠻難過的。謝謝了。)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved