详解结构体与链表
目錄:
1.定義使用結(jié)構(gòu)體變量
2.使用結(jié)構(gòu)體數(shù)組
3.結(jié)構(gòu)體指針
4.結(jié)構(gòu)體內(nèi)存對(duì)齊(重點(diǎn))
5.typedef的使用
6.動(dòng)態(tài)內(nèi)存的分配與釋放
7.鏈表的創(chuàng)立、增刪查改插
???? ?? 什么是鏈表?
???? ?? 靜態(tài)鏈表和動(dòng)態(tài)鏈表
???? ?? 鏈表的創(chuàng)立
???? ?? 鏈表的插入元素
???? ?? 鏈表的刪除元素
???? ?? 鏈表的查找元素
???? ?? 鏈表的更新元素
???? ?? 鏈表的添加元素
由于博主水平有限,所以博客中難免會(huì)出現(xiàn)錯(cuò)誤,如果發(fā)現(xiàn),可以加以指正,博主會(huì)在第一時(shí)間修改。此外,博客中采用部分C語(yǔ)言中文網(wǎng)上的內(nèi)容C語(yǔ)言中文網(wǎng)
1.定義和使用結(jié)構(gòu)體變量
1.1結(jié)構(gòu)體類型是做什么的?
思考,我們能不能使用一個(gè)變量存儲(chǔ)一個(gè)學(xué)生的學(xué)號(hào),姓名,性別,年齡,出生年月等個(gè)人的基本信息(也就是一個(gè)變量代表一個(gè)學(xué)生,一個(gè)變量能查找一個(gè)學(xué)生的所有信息),像我們學(xué)的int、float等基本數(shù)據(jù)類型是做不到的,你可能會(huì)想到數(shù)組,但是數(shù)組元素的數(shù)據(jù)類型是相同的,我們的姓名需要使用字符串來(lái)存儲(chǔ),年齡是需要int類型的來(lái)存儲(chǔ),所以數(shù)組也不行。于是C語(yǔ)言便允許用戶建立由不同類型數(shù)據(jù)組合的組合型數(shù)據(jù)結(jié)構(gòu)——結(jié)構(gòu)體類型
1.2結(jié)構(gòu)體類型的定義與使用
定義格式:
struct 結(jié)構(gòu)體名{ 成員列表 };//一定要注意這個(gè)分號(hào)注意兩個(gè)概念:
1.結(jié)構(gòu)體名:用于區(qū)別其他結(jié)構(gòu)體類型
2.結(jié)構(gòu)體類型名:struct + 結(jié)構(gòu)體名=結(jié)構(gòu)體類型名
比如我們要定義一個(gè)結(jié)構(gòu)體類型用于來(lái)存儲(chǔ)一個(gè)學(xué)生的信息:
struct Date{ //用于存儲(chǔ)學(xué)生的出生年月int month;int day;int year; }; struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }已經(jīng)定義結(jié)構(gòu)體類型(相當(dāng)于一個(gè)模板),我們有三種定義結(jié)構(gòu)體類型變量(相當(dāng)于一個(gè)學(xué)生的實(shí)體)的方法:
1.先聲明結(jié)構(gòu)體類型,再定義結(jié)構(gòu)體變量
#include<stdio.h> struct Date{ //用于存儲(chǔ)學(xué)生的出生年月int month;int day;int year; }; struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }; int main() {struct student student1;//定義結(jié)構(gòu)體變量student1 }2.在聲明類型的同時(shí)定義變量
struct 結(jié)構(gòu)體名{ 成員列表 }變量名列表;例子:
struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }student1,student2;//student1與student2是定義的變量3.不直接類型名直接定義結(jié)構(gòu)體類型變量
struct{ 成員列表 }變量名列表;注意:當(dāng)我們使用這種方法定義結(jié)構(gòu)體類型時(shí),變量只能在變量名列表處定義,不能再定義其他的變量,因?yàn)檫@種方式定義的結(jié)構(gòu)體類型沒(méi)有結(jié)構(gòu)體名
注意:結(jié)構(gòu)體類型在編譯時(shí)是不分配空間的,只對(duì)變量分配空間
1.3結(jié)構(gòu)體變量的使用
結(jié)構(gòu)體變量的使用:
結(jié)構(gòu)體變量名.成員名能引用結(jié)構(gòu)體變量里面的各個(gè)屬性
#include<stdio.h> struct Date{ //用于存儲(chǔ)學(xué)生的出生年月int month;int day;int year; }; struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }; int main() {struct student student1;//定義結(jié)構(gòu)體變量student1printf("請(qǐng)輸入學(xué)生的學(xué)號(hào):");scanf("%d",&student1.num);printf("請(qǐng)輸入學(xué)生的姓名:");scanf("%s",student1.name);printf("請(qǐng)輸入學(xué)生的性別(M/W):");getchar();//吸收回車scanf("%c",&student1.sex);printf("請(qǐng)輸入學(xué)生的年齡:");scanf("%d",&student1.age);printf("請(qǐng)輸入學(xué)生的地址:");scanf("%s",student1.addr);printf("請(qǐng)分別輸入學(xué)生出生年、月、日:");scanf("%d%d%d",&student1.birthday.year,&student1.birthday.month,&student1.birthday.day);printf("該學(xué)生的學(xué)號(hào)為:%d\n",student1.num);printf("該學(xué)生的姓名為:%s\n",student1.name);printf("該學(xué)生的性別為:%c\n",student1.sex);printf("該學(xué)生的年齡為:%d\n",student1.age);printf("該學(xué)生的地址為:%s\n",student1.addr);printf("該學(xué)生的出生年月為:%d %d %d",student1.birthday.year,student1.birthday.month,student1.birthday.day);} //輸入輸出: 請(qǐng)輸入學(xué)生的學(xué)號(hào):1001 請(qǐng)輸入學(xué)生的姓名:張三 請(qǐng)輸入學(xué)生的性別(M/W):M 請(qǐng)輸入學(xué)生的年齡:18 請(qǐng)輸入學(xué)生的地址:南陽(yáng) 請(qǐng)分別輸入學(xué)生出生年、月、日:2000 10 12 該學(xué)生的學(xué)號(hào)為:1001 該學(xué)生的姓名為:張三 該學(xué)生的性別為:M 該學(xué)生的年齡為:18 該學(xué)生的地址為:南陽(yáng) 該學(xué)生的出生年月為:2000 10 12 --------------------------------2.結(jié)構(gòu)體數(shù)組
2.1結(jié)構(gòu)體數(shù)組的定義
1.在定義結(jié)構(gòu)體類型的時(shí)候定義結(jié)構(gòu)體數(shù)組:
struct 結(jié)構(gòu)體名{ 成員列表 }數(shù)組名[數(shù)組長(zhǎng)度];2.使用結(jié)構(gòu)體類型名定義結(jié)構(gòu)體數(shù)組
結(jié)構(gòu)體類型名 數(shù)組名[數(shù)組長(zhǎng)度];2.2結(jié)構(gòu)體數(shù)組的初始化
其實(shí)和int等類型的數(shù)組初始化差不多,舉個(gè)栗子:
#include<stdio.h> struct student{ int num;char name[20]; }; int main() {struct student stu[2]={1001,"張三",1002,"李四"};//或者 struct student stu[2]={{1001,"張三"},{1002,"李四"}};for(int i=0;i<2;i++){printf("第%d個(gè)學(xué)生的學(xué)號(hào)是:%d\n",i+1,stu[i].num);printf("第%d個(gè)學(xué)生的姓名是:%s\n",i+1,stu[i].name);} } //輸出結(jié)果: 第1個(gè)學(xué)生的學(xué)號(hào)是:1001 第1個(gè)學(xué)生的姓名是:張三 第2個(gè)學(xué)生的學(xué)號(hào)是:1002 第2個(gè)學(xué)生的姓名是:李四--------------------------------3.結(jié)構(gòu)體指針
如果對(duì)指針操作不熟悉的話,可以參照這一篇博客:萬(wàn)字長(zhǎng)文搞定C語(yǔ)言指針
3.1指向結(jié)構(gòu)體變量的指針
定義結(jié)構(gòu)體指針:結(jié)構(gòu)體類型名+結(jié)構(gòu)體指針
使用結(jié)構(gòu)體指針:(*結(jié)構(gòu)體指針).屬性或者結(jié)構(gòu)體指針->屬性
例子:
#include<stdio.h> struct student{ int num;char name[20]; }; int main() {struct student *stu;struct student s;printf("請(qǐng)輸入學(xué)生的學(xué)號(hào):");scanf("%d",&s.num);printf("請(qǐng)輸入學(xué)生的姓名:");scanf("%s",s.name);stu=&s;printf("該學(xué)生的學(xué)號(hào)為:%d\n",stu->num);//等同于 printf("該學(xué)生的學(xué)號(hào)為:%d\n",(*stu).num);printf("該學(xué)生的姓名為:%s",stu->name); //等同于 printf("該學(xué)生的學(xué)號(hào)為:%s\n",(*stu).name); }3.2指向結(jié)構(gòu)體數(shù)組的指針
例子:
#include<stdio.h> struct student{ int num;char name[20]; }; int main() {struct student stu[2]={1001,"張三",1002,"李四"};struct student *p;int i=1;for(p=stu;p<stu+2;p++){printf("第%d個(gè)學(xué)生的學(xué)號(hào)是:%d\n",i,p->num);printf("第%d個(gè)學(xué)生的姓名是:%s\n",i,p->name);i++;} }3.3結(jié)構(gòu)體指針作為函數(shù)參數(shù)
結(jié)構(gòu)體變量名代表的是整個(gè)集合本身,作為函數(shù)參數(shù)時(shí)傳遞的整個(gè)集合,也就是所有成員,而不是像數(shù)組一樣被編譯器轉(zhuǎn)換成一個(gè)指針。如果結(jié)構(gòu)體成員較多,尤其是成員為數(shù)組時(shí),傳送的時(shí)間和空間開(kāi)銷會(huì)很大,影響程序的運(yùn)行效率。所以最好的辦法就是使用結(jié)構(gòu)體指針,這時(shí)由實(shí)參傳向形參的只是一個(gè)地址,非常快速。
計(jì)算全班學(xué)生的總成績(jī)、平均成績(jī)和以及 140 分以下的人數(shù)。
#include <stdio.h>struct stu{char *name; //姓名int num; //學(xué)號(hào)int age; //年齡char group; //所在小組float score; //成績(jī) }stus[] = {{"Li ping", 5, 18, 'C', 145.0},{"Zhang ping", 4, 19, 'A', 130.5},{"He fang", 1, 18, 'A', 148.5},{"Cheng ling", 2, 17, 'F', 139.0},{"Wang ming", 3, 17, 'B', 144.5} };void average(struct stu *ps, int len);int main(){int len = sizeof(stus) / sizeof(struct stu);average(stus, len);return 0; }void average(struct stu *ps, int len){int i, num_140 = 0;float average, sum = 0;for(i=0; i<len; i++){sum += (ps + i) -> score;if((ps + i)->score < 140) num_140++;}printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140); } //運(yùn)行結(jié)果: sum=707.50 average=141.50 num_140=24.結(jié)構(gòu)體內(nèi)存對(duì)齊(重點(diǎn))
我們定義一個(gè)結(jié)構(gòu)體變量,查看所占據(jù)的內(nèi)存字節(jié)數(shù):
#include<stdio.h> struct Date{ //用于存儲(chǔ)學(xué)生的出生年月int month;int day;int year; }; struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }; int main() {struct student stu1;printf("%d",sizeof(stu1)); } //輸出結(jié)果:76我們按照正常的思路,num為int類型占據(jù)4個(gè)字節(jié),name數(shù)組占據(jù)20個(gè)字節(jié),sex占據(jù)1個(gè)字節(jié),age占據(jù)4個(gè)字節(jié),birthday占據(jù)12個(gè)字節(jié),addr占據(jù)30個(gè)字節(jié),一共是71個(gè)字節(jié),為啥會(huì)是76個(gè)字節(jié)大小呢。這就牽扯到C語(yǔ)言的內(nèi)存對(duì)齊問(wèn)題。
結(jié)構(gòu)體對(duì)齊原則一:
結(jié)構(gòu)體中元素是按照定義順序一個(gè)一個(gè)放到內(nèi)存中去的,但并不是緊密排列的。從結(jié)構(gòu)體存儲(chǔ)的首地址開(kāi)始,每一個(gè)元素放置到內(nèi)存中時(shí),它都會(huì)認(rèn)為內(nèi)存是以它自己的大小來(lái)劃分的,因此元素放置的位置一定會(huì)在自己寬度的整數(shù)倍上開(kāi)始(以結(jié)構(gòu)體變量首地址為0計(jì)算)
比如上面例子(占據(jù)16個(gè)字節(jié)):首先系統(tǒng)會(huì)將字符型變量a存入第0個(gè)字節(jié)(相對(duì)地址,指內(nèi)存開(kāi)辟的首地址);然后在存放整形變量b時(shí),會(huì)以4個(gè)字節(jié)為單位進(jìn)行存儲(chǔ),由于第一個(gè)四字節(jié)模塊已有數(shù)據(jù),因此它會(huì)存入第二個(gè)四字節(jié)模塊,也就是存入到4~8字節(jié);同理,存放雙精度實(shí)型變量c時(shí),由于其寬度為8,其存放時(shí)會(huì)以8個(gè)字節(jié)為單位存儲(chǔ),也就是會(huì)找到第一個(gè)空的且是8的整數(shù)倍的位置開(kāi)始存儲(chǔ),此例中,此例中,由于頭一個(gè)8字節(jié)模塊已被占用,所以將c存入第二個(gè)8字節(jié)模塊。整體存儲(chǔ)示意圖如圖1所示。
原則二:
在經(jīng)過(guò)第一原則分析后,檢查計(jì)算出的存儲(chǔ)單元是否為所有元素中最寬的元素的長(zhǎng)度的整數(shù)倍,是,則結(jié)束;若不是,則補(bǔ)齊為它的整數(shù)倍
上面這個(gè)例子中:我們分析完后的存儲(chǔ)長(zhǎng)度為20字節(jié),不是最寬元素長(zhǎng)度8的整數(shù)倍,因此將它補(bǔ)齊到8的整數(shù)倍,也就是24。這樣就沒(méi)問(wèn)題了。其存儲(chǔ)示意圖如圖2所示。
當(dāng)成員變量里含有指針、數(shù)組或是其它結(jié)構(gòu)體變量
1.包含指針類型的情況。只要記住指針本身所占的存儲(chǔ)空間是8個(gè)字節(jié)就行了,而不必看它是指向什么類型的指針。
2.包含結(jié)構(gòu)體類型時(shí),檢查計(jì)算出的存儲(chǔ)單元是否為所有元素(也包括所含結(jié)構(gòu)體變量的元素)中最寬的元素的長(zhǎng)度的整數(shù)倍
3.包含數(shù)組類型,內(nèi)存對(duì)齊時(shí)只看基類型,如果數(shù)組基類型寬度最大,那么內(nèi)存補(bǔ)齊時(shí)要是這個(gè)基類型的整數(shù)倍
此時(shí)開(kāi)頭的問(wèn)題就能迎刃而解了:
#include<stdio.h> struct Date{ //用于存儲(chǔ)學(xué)生的出生年月int month;int day;int year; }; struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];char sex;int age;struct Date birthday;//結(jié)構(gòu)體嵌套char addr[30]; }; int main() {struct student stu1;printf("stu1首地址:%d\n",&stu1);printf("num首地址:%d\n",&stu1.num);printf("name首地址:%d\n",stu1.name);printf("sex首地址:%d\n",&stu1.sex);printf("age首地址:%d\n",&stu1.age);printf("birthday首地址:%d\n",&stu1.birthday);printf("addr首地址:%d\n",stu1.addr);printf("stu1占據(jù)總的字節(jié)數(shù)是:%d\n",sizeof(stu1)); } stu1首地址:6684112 num首地址:6684112 name首地址:6684116 sex首地址:6684136 age首地址:6684140 birthday首地址:6684144 addr首地址:6684156 stu1占據(jù)總的字節(jié)數(shù)是:76--------------------------------5.typedef的使用
C語(yǔ)言允許為一個(gè)數(shù)據(jù)類型起一個(gè)新的別名,就像給人起“綽號(hào)”一樣。
起別名的目的不是為了提高程序運(yùn)行效率,而是為了編碼方便。例如有一個(gè)結(jié)構(gòu)體的名字是 stu,要想定義一個(gè)結(jié)構(gòu)體變量就得這樣寫(xiě):
struct stu stu1;struct 看起來(lái)就是多余的,但不寫(xiě)又會(huì)報(bào)錯(cuò)。如果為 struct stu 起了一個(gè)別名 STU,書(shū)寫(xiě)起來(lái)就簡(jiǎn)單了:
STU stu1;這種寫(xiě)法更加簡(jiǎn)練,意義也非常明確,不管是在標(biāo)準(zhǔn)頭文件中還是以后的編程實(shí)踐中,都會(huì)大量使用這種別名。
使用關(guān)鍵字 typedef 可以為類型起一個(gè)新的別名。typedef 的用法一般為:
typedef oldName newName;oldName 是類型原來(lái)的名字,newName 是類型新的名字。例如:
typedef int INTEGER; INTEGER a, b; a = 1; b = 2;INTEGER a, b;等效于int a, b;。
typedef 還可以給數(shù)組、指針、結(jié)構(gòu)體等類型定義別名。先來(lái)看一個(gè)給數(shù)組類型定義別名的例子:
typedef char ARRAY20[20];表示 ARRAY20 是類型char [20]的別名。它是一個(gè)長(zhǎng)度為 20 的數(shù)組類型。接著可以用 ARRAY20 定義數(shù)組:
ARRAY20 a1, a2, s1, s2;它等價(jià)于:
char a1[20], a2[20], s1[20], s2[20];又如,為結(jié)構(gòu)體類型定義別名:
typedef struct stu{char name[20];int age;char sex; } STU;STU 是 struct stu 的別名,可以用 STU 定義結(jié)構(gòu)體變量:
STU body1,body2;它等價(jià)于:
struct stu body1, body2;再如,為指針類型定義別名:
typedef int (*PTR_TO_ARR)[4];表示 PTR_TO_ARR 是類型int * [4]的別名,它是一個(gè)二維數(shù)組指針類型。接著可以使用 PTR_TO_ARR 定義二維數(shù)組指針:
PTR_TO_ARR p1, p2;按照類似的寫(xiě)法,還可以為函數(shù)指針類型定義別名:
typedef int (*PTR_TO_FUNC)(int, int); PTR_TO_FUNC pfunc;對(duì)于給函數(shù)指針類型定義別名:
我們知道,單獨(dú)的int (*p)(int,int),p代表定義了一個(gè)函數(shù)指針(其返回值為int,兩個(gè)int形參數(shù)),
#include<stdio.h> int max(int a,int b) {return a>b?a:b; } int main() {int (*p)(int,int);p=max;int a=(*p)(20,30);printf("%d",a);} //輸出30如果變成typedef int (*p)(int,int)。p就變成定義函數(shù)指針變量的一個(gè)類型,就像int一樣,int a代表定義一個(gè)整形變量,p a,代表定義一個(gè)函數(shù)指針變量,p a還等同于int (*a)(int,int)
#include<stdio.h> int max(int a,int b) {return a>b?a:b; } int main() {typedef int (*p)(int,int);p a;a=max;int c=(*a)(20,30);printf("%d",c); } //輸出30同樣的對(duì)于typedef char (*PTR_TO_ARR)[30];
#include <stdio.h>typedef char (*PTR_TO_ARR)[30]; typedef int (*PTR_TO_FUNC)(int, int);int max(int a, int b){return a>b ? a : b; }char str[3][30] = {"http://c.biancheng.net","C語(yǔ)言中文網(wǎng)","C-Language" };int main(){PTR_TO_ARR parr = str;PTR_TO_FUNC pfunc = max;int i;printf("max: %d\n", (*pfunc)(10, 20));for(i=0; i<3; i++){printf("str[%d]: %s\n", i, *(parr+i));}return 0; }typedef 和 #define 的區(qū)別
typedef 在表現(xiàn)上有時(shí)候類似于 #define,但它和宏替換之間存在一個(gè)關(guān)鍵性的區(qū)別。正確思考這個(gè)問(wèn)題的方法就是把 typedef 看成一種徹底的“封裝”類型,聲明之后不能再往里面增加別的東西。#define是在預(yù)編譯時(shí)進(jìn)行簡(jiǎn)單的字符串替換,而typedef是在編譯階段完成的
經(jīng)過(guò)宏替換以后,第二行變?yōu)?#xff1a;
int *p1, p2;這使得 p1、p2 成為不同的類型:p1 是指向 int 類型的指針,p2 是 int 類型。
相反,在下面的代碼中:
typedef int * PTR_INT PTR_INT p1, p2;p1、p2 類型相同,它們都是指向 int 類型的指針。
6.動(dòng)態(tài)內(nèi)存分配與釋放
C語(yǔ)言內(nèi)存簡(jiǎn)介
| 棧區(qū) | 存放函數(shù)的參數(shù)值、局部變量等,由編譯器自動(dòng)分配和釋放,通常在函數(shù)執(zhí)行完后就釋放了,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧 |
| 堆區(qū) | 就是通過(guò)new、malloc、realloc分配的內(nèi)存塊,編譯器不會(huì)負(fù)責(zé)它們的釋放工作,需要用程序區(qū)釋放。分配方式類似于數(shù)據(jù)結(jié)構(gòu)中的鏈表。“內(nèi)存泄漏”通常說(shuō)的就是堆區(qū)。 |
| 靜態(tài)區(qū) | 全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后,由系統(tǒng)釋放。 |
| 常量區(qū) | 常量存儲(chǔ)在這里,不允許修改。 |
| 代碼區(qū) | 顧名思義,存放代碼 |
1.void *malloc(unsigned int size):作用是在動(dòng)態(tài)存儲(chǔ)區(qū)中分配一個(gè)長(zhǎng)度為size的連續(xù)空間,unsigned代表沒(méi)有符號(hào)位的整形數(shù)據(jù)(非負(fù)整數(shù)),返回所分配內(nèi)存區(qū)域第一個(gè)字節(jié)的地址.分配失敗返回NULL指針
2.void *calloc(unsigned n,unsigned size):作用是在動(dòng)態(tài)內(nèi)存空間中分配n個(gè)長(zhǎng)度為size的連續(xù)空間,分配失敗返回NULL指針
3.void free(void *p):釋放指針變量p所指向的動(dòng)態(tài)空間
4.void *realloc(void *p,unsigned int size):對(duì)已經(jīng)通過(guò)malloc函數(shù)calloc函數(shù)獲得了動(dòng)態(tài)空間,想改變其大小,用此函數(shù)重新分配
注意:void*類型的指針表示指向空類型或者不指向確定的類型的數(shù)據(jù)
以上函數(shù)得使用#include<stdlib.h>
使用舉例:
#include<stdio.h> #include<stdlib.h> int main() {int i=0;int *p=(int*)malloc(4);//(函數(shù)前的int*代表把分配的內(nèi)存轉(zhuǎn)換成int*類型)*p=3;printf("%d\n",*p);free(p); }7.鏈表的創(chuàng)立、增刪查改插
7.1什么是鏈表?
當(dāng)一個(gè)班級(jí)有50個(gè)人,那么我們需要定義數(shù)組長(zhǎng)度為50的結(jié)構(gòu)體數(shù)組來(lái)存儲(chǔ)這些學(xué)生的信息,如果新來(lái)了幾個(gè)學(xué)生,那么我們則需要再重新定義一個(gè)更大的數(shù)組,比如60個(gè)大小(你不能申請(qǐng)一個(gè)足夠大的數(shù)組,這樣做太浪費(fèi)內(nèi)存空間)。這時(shí)候就可以用我們的鏈表存儲(chǔ)。
鏈表是一種動(dòng)態(tài)的進(jìn)行存儲(chǔ)分配的一種結(jié)構(gòu)
看下面一段代碼:
struct student{int num;char name[20];struct student *next; }上面一段代碼中,結(jié)構(gòu)體類型是struct student,存儲(chǔ)著學(xué)生的基本信息,里邊還有一個(gè)struct student *next成員變量,他是一個(gè)指向struct student類型數(shù)據(jù)的指針,也就是說(shuō),他的存儲(chǔ)結(jié)構(gòu)可以如下所示:
這種能夠把數(shù)據(jù)之間進(jìn)行連接的數(shù)據(jù)結(jié)構(gòu)稱為鏈表,鏈表中的每一個(gè)元素稱為結(jié)點(diǎn),每個(gè)結(jié)點(diǎn)中存放指針的空間稱為指針域,存放其他信息的空間稱為數(shù)據(jù)域。
一般的鏈表有頭指針,頭結(jié)點(diǎn),尾結(jié)點(diǎn)指向NULL,如下:
我們可以把鏈表想象成一個(gè)火車,頭結(jié)點(diǎn)相當(dāng)于火車頭,火車頭負(fù)責(zé)連接第一節(jié)車廂,火車頭里邊不放乘客只是與第一個(gè)存放乘客的車廂建立聯(lián)系(相當(dāng)于數(shù)據(jù)域不賦值,指針域指向第一個(gè)結(jié)點(diǎn))
頭結(jié)點(diǎn):頭結(jié)點(diǎn)不是鏈表必須的部分,有了頭結(jié)點(diǎn),在對(duì)第一個(gè)結(jié)點(diǎn)前插入或者刪除第一個(gè)結(jié)點(diǎn)就和其余結(jié)點(diǎn)統(tǒng)一了
頭指針:頭指針是指鏈表指向第一個(gè)結(jié)點(diǎn)的指針,若鏈表有頭結(jié)點(diǎn),則是指向頭結(jié)點(diǎn)的指針。頭指針是鏈表的必要元素
實(shí)例:
#include<stdio.h> struct student{ //用于存儲(chǔ)學(xué)生的信息int num;char name[20];struct student *next; }; int main() {struct student stu2={1002,"李四",NULL};struct student stu1={1001,"張三",&stu2};struct student stu0;stu0.next=&stu1;struct student *head=&stu0;struct student *p=head->next;//此時(shí)p指向第一個(gè)結(jié)點(diǎn)stu1int i=1;while(p!=NULL)//遍歷學(xué)生信息{printf("第%d個(gè)學(xué)生的學(xué)號(hào)為:%d",i,p->num);printf("第%d個(gè)學(xué)生的姓名為:%s\n",i,p->name);i++;p=p->next;} } //輸出: 第1個(gè)學(xué)生的學(xué)號(hào)為:1001第1個(gè)學(xué)生的姓名為:張三 第2個(gè)學(xué)生的學(xué)號(hào)為:1002第2個(gè)學(xué)生的姓名為:李四--------------------------------上面程序?qū)?yīng)下圖:
7.2靜態(tài)鏈表,動(dòng)態(tài)鏈表
所有結(jié)點(diǎn)都是在程序中定義的,不是我們自己申請(qǐng)的內(nèi)存(由系統(tǒng)自動(dòng)分配內(nèi)存空間),用完后系統(tǒng)自動(dòng)釋放,這種鏈表稱為靜態(tài)鏈表。如7.1的例子就是如此,所謂動(dòng)態(tài)鏈表就是我們手動(dòng)開(kāi)辟內(nèi)存存放結(jié)點(diǎn),需要回收時(shí)我們手動(dòng)釋放的鏈表
7.3鏈表的創(chuàng)建
代碼實(shí)現(xiàn):
//聲明節(jié)點(diǎn)結(jié)構(gòu) typedef struct Link{int elem;//存儲(chǔ)整形元素struct Link *next;//指向直接后繼元素的指針 }link; //創(chuàng)建鏈表的函數(shù) link * initLink(){link * p=(link*)malloc(sizeof(link));//創(chuàng)建一個(gè)頭結(jié)點(diǎn)link * temp=p;//聲明一個(gè)指針指向頭結(jié)點(diǎn),用于遍歷鏈表//生成鏈表for (int i=1; i<5; i++) {//創(chuàng)建節(jié)點(diǎn)并初始化link *a=(link*)malloc(sizeof(link));a->elem=i;a->next=NULL;//建立新節(jié)點(diǎn)與直接前驅(qū)節(jié)點(diǎn)的邏輯關(guān)系temp->next=a;temp=temp->next;}return p; }從實(shí)現(xiàn)代碼中可以看到,該鏈表是一個(gè)具有頭節(jié)點(diǎn)的鏈表。由于頭節(jié)點(diǎn)本身不用于存儲(chǔ)數(shù)據(jù),因此在實(shí)現(xiàn)對(duì)鏈表中數(shù)據(jù)的"增刪查改"時(shí)要引起注意。
7.4鏈表的插入元素
根據(jù)添加位置不同,可分為以下 3 種情況:
雖然新元素的插入位置不固定,但是鏈表插入元素的思想是固定的,只需做以下兩步操作,即可將新元素插入到指定的位置:
例如,我們?cè)阪湵?{1,2,3,4} 的基礎(chǔ)上分別實(shí)現(xiàn)在頭部、中間部位、尾部插入新元素 5,其實(shí)現(xiàn)過(guò)程如圖 :
從圖中可以看出,雖然新元素的插入位置不同,但實(shí)現(xiàn)插入操作的方法是一致的,都是先執(zhí)行步驟 1 ,再執(zhí)行步驟 2。
注意:鏈表插入元素的操作必須是先步驟 1,再步驟 2;反之,若先執(zhí)行步驟 2,除非再添加一個(gè)指針,作為插入位置后續(xù)鏈表的頭指針,否則會(huì)導(dǎo)致插入位置后的這部分鏈表丟失,無(wú)法再實(shí)現(xiàn)步驟 1。
代碼實(shí)現(xiàn):
//p為原鏈表,elem表示新數(shù)據(jù)元素,add表示新元素要插入的位置 link * insertElem(link * p, int elem, int add) {link * temp = p;//創(chuàng)建臨時(shí)結(jié)點(diǎn)temp//首先找到要插入位置的上一個(gè)結(jié)點(diǎn)for (int i = 1; i < add; i++) {temp = temp->next;if (temp == NULL) {printf("插入位置無(wú)效\n");return p;}}//創(chuàng)建插入結(jié)點(diǎn)clink * c = (link*)malloc(sizeof(link));c->elem = elem;//向鏈表中插入結(jié)點(diǎn)c->next = temp->next;temp->next = c;return p; }回想這一句話:
頭結(jié)點(diǎn):頭結(jié)點(diǎn)不是鏈表必須的部分,有了頭結(jié)點(diǎn),在對(duì)第一個(gè)結(jié)點(diǎn)前插入或者刪除第一個(gè)結(jié)點(diǎn)就和其余結(jié)點(diǎn)統(tǒng)一了
由于有了頭結(jié)點(diǎn),所以插入的時(shí)候,在第一個(gè)結(jié)點(diǎn)前插入就和所有結(jié)點(diǎn)進(jìn)行了統(tǒng)一。否則????(你懂的)
7.5鏈表的刪除元素
從鏈表中刪除指定數(shù)據(jù)元素時(shí),實(shí)則就是將存有該數(shù)據(jù)元素的節(jié)點(diǎn)從鏈表中摘除,但作為一名合格的程序員,要對(duì)存儲(chǔ)空間負(fù)責(zé),對(duì)不再利用的存儲(chǔ)空間要及時(shí)釋放。因此,從鏈表中刪除數(shù)據(jù)元素需要進(jìn)行以下 2 步操作:
其中,從鏈表上摘除某節(jié)點(diǎn)的實(shí)現(xiàn)非常簡(jiǎn)單,只需找到該節(jié)點(diǎn)的直接前驅(qū)節(jié)點(diǎn) temp,執(zhí)行一行程序:
temp->next=temp->next->next;例如,從存有 {1,2,3,4} 的鏈表中刪除元素 3,則此代碼的執(zhí)行效果如圖 2 所示:
代碼實(shí)現(xiàn):
我們可以看到,從鏈表上摘下的節(jié)點(diǎn) del 最終通過(guò) free 函數(shù)進(jìn)行了手動(dòng)釋放。
同樣的因?yàn)橛辛祟^結(jié)點(diǎn),才會(huì)有對(duì)所有結(jié)點(diǎn)的刪除實(shí)現(xiàn)統(tǒng)一
7.6鏈表的查找元素
在鏈表中查找指定數(shù)據(jù)元素,最常用的方法是:從表頭依次遍歷表中節(jié)點(diǎn),用被查找元素與各節(jié)點(diǎn)數(shù)據(jù)域中存儲(chǔ)的數(shù)據(jù)元素進(jìn)行比對(duì),直至比對(duì)成功或遍歷至鏈表最末端的 NULL(比對(duì)失敗的標(biāo)志)。
//p為原鏈表,elem表示被查找元素、 int selectElem(link * p,int elem){ //新建一個(gè)指針t,初始化為頭指針 plink * t=p;int i=1;//由于頭節(jié)點(diǎn)的存在,因此while中的判斷為t->nextwhile (t->next) {t=t->next;if (t->elem==elem) {return i;}i++;}//程序執(zhí)行至此處,表示查找失敗return -1; }注意,遍歷有頭節(jié)點(diǎn)的鏈表時(shí),需避免頭節(jié)點(diǎn)對(duì)測(cè)試數(shù)據(jù)的影響,因此在遍歷鏈表時(shí),建立使用上面代碼中的遍歷方法,直接越過(guò)頭節(jié)點(diǎn)對(duì)鏈表進(jìn)行有效遍歷。
7.7鏈表更新元素
更新鏈表中的元素,只需通過(guò)遍歷找到存儲(chǔ)此元素的節(jié)點(diǎn),對(duì)節(jié)點(diǎn)中的數(shù)據(jù)域做更改操作即可。
//更新函數(shù),其中,add 表示更改結(jié)點(diǎn)在鏈表中的位置,newElem 為新的數(shù)據(jù)域的值 link *amendElem(link * p,int add,int newElem){link * temp=p;temp=temp->next;//在遍歷之前,temp指向首元結(jié)點(diǎn)//遍歷到待更新結(jié)點(diǎn)for (int i=1; i<add; i++) {temp=temp->next;}temp->elem=newElem;return p; }7.8鏈表的添加元素
頭插法:
link *(link * p){link * temp=p;//temp初始化為頭指針int count;printf("請(qǐng)輸入要插入的元素?cái)?shù):\n");scanf("%d",&count);for(int i=0;i<count;i++){link *a=(link*)malloc(sizeof(link));a->elem=i;//假設(shè)添加的元素等于本輪的ia->next=temp->next;temp->next=a;}return p; }尾插法:
link *(link * p){link * temp=p;//temp初始化為頭指針int count;printf("請(qǐng)輸入要插入的元素?cái)?shù):\n");scanf("%d",&count);while(temp->next!=NULL){temp=temp->next;}for(int i=0;i<count;i++){link *a=(link*)malloc(sizeof(link));a->elem=i;//假設(shè)添加的元素等于本輪的i temp->next=a;a->next=NULL;temp=a;}return p; }總結(jié)
- 上一篇: 汉明码的理解
- 下一篇: LInux命令行参数