在學習數據結構的時候,會經常使用到結構體。今天分享的內容是結構體與指針,因爲結構體和指針本身的內容並不是太多,所以今天的內容還包括了鏈表的實現。希望可以通過這篇博客,讓大家熟悉結構體與指針,以及鏈表的實現。
一、結構體指針
1.1、結構體的簡單介紹
在實際應用過程中,一組單一數據類型的數組很難滿足我們的需求,這個時候就需要藉助結構體。結構體是一種構造數據類型,它有許多不同數據類型的成員。假如我們需要整理一個班級學生的數據,具體要整理的數據如下:
- 00後的人數
- 女生的人生
- 平均分數
- 排名前五的學生的姓名
面對這樣一個數據,無法用基本數據類型的任何一個表示。但是我們知道,要整理上面的數據需要知道下面幾個信息:
- 生日
- 性別
- 分數
- 姓名
於是我們可以根據這些信息,創建一個結構體。結構體的創建格式如下:
struct 結構體名稱{
成員1
成員2
.....
}
定義一個結構體的關鍵詞是struct,那麼學生結構體的創建如下:
struct student{
char name[20];
char sex[10];
int year;
float score;
}
1.2、結構體變量和結構體數組
結構體和普通變量有很多類似的地方,我們可以將結構體理解爲一個數組,一個存儲不同數據類型的數組(只是作爲理解,結構體和數組的操作完全不同)。
(1)結構體變量
結構體變量的聲明和其它變量的聲明類似,大概樣式如下:
struct 結構體名稱 結構體變量;
那麼學生結構體變量聲明如下:
struct student stu1, stu2;
另外我們可以用typedef關鍵字來簡化結構體變量的聲明,具體操作如下:
//將類型struct student 定義爲 Student,以後看到了Student就可以用struct student替換
typedef struct student Student;
//那麼學生結構體的定義可以變成如下
Student stu1, stu2;
(2)初始化結構體變量
在結構體中,我們可以通過“.”來操作結構體的成員:
Student stu1;
stu1.name = "zack";
stu1.sex = "male";
stu1.year = 1999;
stu1.score = 89;
因爲結構體是構造數據類型,它所佔的字節數是由每個成員佔的字節數疊加的。
(3)結構體數組
結構體數組的定義和普通數組一致,我們可以循環初始化:
int i;
Student stu[3];
//循環輸入數據
for(i = 0; i < 3; i++){
printf("請輸入姓名:");
scanf("%s", stu[i].name);
printf("請輸入性別:");
scanf("%s", stu[i].sex);
printf("請輸入年齡:");
scanf("%d", &stu[i].age);
printf("請輸入分數");
scanf("%f", &stu[i].score);
}
//循環輸出結構體數組中的數據
for(i = 0; i < 3; i++){
printf("*************************************");
printf("姓名:%s\n", stu[i].name);
printf("性別:%s\n", stu[i].sex);
printf("年齡:%d\n", stu[i].age);
printf("分數:%f", stu[i].score);
}
大致和普通變量沒有區別,操作結構體的關鍵就是“.”。
1.3、結構體指針
結構體指針和其它變量的指針類似,而且用結構體指針操作結構體的用法也類似,唯一的不同就是在用結構體指針操作結構體成員。
(1)結構體指針的定義
結構體指針的定義和普通指針變量的定義是相似的,在未使用typedef時,結構體指針定義如下:
struct 結構體名稱 *結構體指針變量名稱;
//那麼學生結構體指針定義如下
struct student *p;
而使用typedef之後,定義如下:
Student *p;
(2)結構體指針初始化結構體
在此之前先講一下訪問結構體成員的三種方式,假定有下列結構體和結構體指針:
//聲明一個結構體變量和結構體指針變量
Student stu, *p;
那麼訪問成員的方式如下:
//通過“.”來訪問結構體成員,結構體變量訪問成員時,需要用“.”來訪問
stu.name
//通過“->”來訪問成員,結構體指針訪問成員時,需要用“->”來訪問
p->name
//先獲取結構體指針指向的內容,然後再訪問成員
*p->name
瞭解上面的東西后,就可以知道結構體指針初始化結構體了。
Student *p;
p->name = "zack";
p->sex = "male";
p->year = 1999;
p->score = 89;
然後我們做個簡單的練習,創建5個學生結構體,然後根據學生的年齡排名。爲了方便,我們重新創建一個結構體:
typedef struct{
char name[20];
int age;
}student;
這個結構體就兩個成員,我們可以使用冒泡排序:
//定義學生結構體數組,和臨時變量temp
student stu[5], temp;
int i, j;
//循環輸入5個學生的信息
for(i = 0; i < 5; i++){
printf("請輸入學生姓名:");
scanf("%s", stu[i].name);
printf("請輸入學生年齡:");
scanf("%d", &stu[i],age);
}
//使用排序算法排序
for(i = 0; i < 5; i++){
for(j = i; i < 5; j++){
if(stu[i].age > stu[j].age){
temp = stu[i]
stu[i] = stu[j];
stu[j] = temp;
}
}
}
可以看到,結構體在按成員排序時和普通變量排序是一致的。
二、鏈表
之前說過指針在結構體中用處巨大,而這個用處實際體現在鏈表的實現。在具體講解鏈表前,先了解一下線性表的概念。
2.1、線性表
因爲不是講解數據結構,所以對於線性表也是簡單介紹。
(1)線性表
其實我們已經遇到過各種各樣的線性表,最典型的就是數組了。雖然數組不等同於線性表,但是用數組理解線性表是個很好的選擇。那麼數組有什麼特點呢?
- 數據集合
- 有序
- 每兩個相鄰元素之間都有對應關係
- 有開頭有結尾
因爲數組和線性表還是有區別的,所以並不代表上面的就是線性表的特點。不過有一點非常重要,就是“每兩個相鄰元素之間都有對應關係”,這個對應關係簡單來說就是“一個前驅一個後繼”。這麼說有點不理解了,通俗講就是“前面有一個,後面有一個”。這裏的一個是指最多一個,也可以是零個。就好比珍珠項鍊,你任意以一個珍珠作爲參考,任何一個珍珠都是前面一個,後面一個。
(2)順序表和鏈表
上面簡單說了一下線性表,然後我們來講講線性表和鏈表的關係。先看下面兩張圖:
第一個是鐵鏈,第二個並不是麻花啊,它是一條繩子。我說這兩個都符合線性表的特點大家可能不是很理解,對於鐵鏈還可以接受,但是繩子就有點牽強了。所以我們假設,繩子上面串了珍珠。
在假設繩子串珍珠之後,會發現鐵鏈和繩子都符合“前一個後一個”的標準。我們也不深入研究了,現在我告訴大家這兩個都是線性表。但是它們有什麼區別呢?我們先看看鐵鏈的特點:
- 可以任意加長
- 在中間添加鏈子非常方便
然後我們看看繩子:
- 不能加長
- 在中間添加珍珠要移動後面所有珍珠
這麼一看鏈子要厲害許多,但是這只是對於鏈子和繩子來說。實際上鍊表和順序表並不完全和上面一樣。舉這個例子只是作爲直觀的理解,鏈表具體的特點,我將在鏈表的實現當中爲大家講解。
2.2、鏈表介紹
(1)鏈表的實習原理
鏈表實際上是由一個個節點組成的,每個節點都存儲兩個東西。一個數據、還有一個地址。數據的話就是我們要用到的東西,地址存儲的是下一個節點的地址。這樣我們就能通過某一個節點找到下一個節點,經此而已。
(2)用結構體模擬節點
結構體是一種構造數據類型,可以有許多類型不同的成員。然後我們根據節點的特點就可以用結構體模擬出一個節點了。
開始說節點存儲兩個數據,其實不準確。應該是兩種數據,一個是我們要用到的數據,還有一個是下一個節點的指針。存放數據的區域叫做數據域,存放地址的區域叫做指針域。我們先來定義一個最簡單的節點:
typedef struct num{
//數據域
int data;
//指針域
struct num *next;
}Node, LinkedList;
首先數據域沒有什麼疑問,就是一個普通的int數據。然後是指針域,因爲我們是要存儲下一個節點的地址,而節點又是一個num結構體,然後我們就可以根據上面學的結構體指針定義的方式定義一個結構體指針。
注意:在結構體中,內存大小必須是確定的。結構體的成員不允許是本身結構體變量,但是可以爲本身的結構體指針。以爲指針的大小是確定的,而如果成員中有本身的結構體變量系統將無法知道這個結構體應該分配多少內存。
結構體的具體實現將在下一篇爲大家分享。