1. 程式人生 > >記資料結構--入門和預備知識

記資料結構--入門和預備知識

資料結構(一)--入門和預備知識

1. 概述

資料結構定義:

我們如何把現實中大量而複雜的問題以特定的資料型別和特定的儲存結構儲存到主儲存器(記憶體)中,

以及在此基礎上為實現某個功能(如元素的CURD、排序等)而執行的相應操作,這個相應的操作也叫演算法。

資料結構 = 元素的儲存 + 元素的關係的儲存演算法 = 對資料儲存的操作

演算法:

演算法就是:解決問題的方法和步驟

衡量演算法有如下標準:

  1. 時間複雜度(程式要執行的次數,並非執行時間)
  2. 空間複雜度(演算法執行過程中大概要佔用的最大記憶體)
  3. 難易程度(可讀性)
  4. 健壯性

2. 資料結構的特點和地位

地位:

資料結構處於軟體中核心的地位。

如計算機記憶體中棧和堆的區別,不懂資料結構的人可能會認為記憶體就是分兩大部分,一塊叫棧,一塊叫堆,顯然這是非常膚淺且不正確的結論。

實際上如果一塊記憶體是以壓棧出棧的方式分配的記憶體,那麼這塊記憶體就叫棧記憶體,如果是以堆排序的方式分配的記憶體,那麼這塊記憶體就叫堆記憶體,其最根本的區別還是其記憶體分配演算法的不同。

例如,函式的呼叫方式也是通過壓棧出棧的方式來呼叫的,或者作業系統中多執行緒操作有佇列的概念,佇列用於保證多執行緒的操作順序,

這也是資料結構裡面的內容、或者計算機編譯原理裡面有語法樹的概念,這實際上就是資料結構裡面的

比如軟體工程、資料庫之類都有資料結構的影子。

特點:

資料結構修煉的是內功,並不能直接立竿見影的可以解決現實問題,但是有了這門內功會在其他方面的學習中對你大有益處。

3. 預備知識(C語言)

學習資料結構應該具備如下知識:

  • 指標
  • 結構體
  • 動態記憶體的分配和釋放
  • 跨函式使用記憶體

本小節主要介紹學習資料結構應該有的基礎,並對相關知識稍作講解。

指標

指標是 C語言 的靈魂,重要性不需多言。

指標定義

地址:

  地址是記憶體單元的編號

  其編號是從 0 開始的非負整數

  範圍: 0 -- 0xFFFFFFFF (2^32 - 1) 此指x86平臺,x64平臺下最大記憶體地址為 (2^64 - 1)

指標:

  指標就是地址,地址就是指標。

  指標變數是存放記憶體單元地址的變數,它內部儲存的值是對應的地址,地址就是記憶體單元的編號(如記憶體地址值:0xffc0)。

  指標的本質是一個操作受限的非負整數

   在計算機系統中,CPU 可以直接操作記憶體,關於 CPU 對記憶體的操作與控制原理可以簡單理解如下圖

地址線 : 確定操作哪個地址單元

控制線 : 控制該資料單元的讀寫屬性

資料線 : 傳輸 CPU 和記憶體之間的資料

指標的分類

  1. 基本型別的指標

int i = 10; // 定義一個 整形變數 i 初始值 10

int *p = i;  // 定義一個 整形的指標變數 p , 變數 p 指向 i 的地址    

int *p;      // 這兩行等價於上面一行
p = &i;`

1. p 存放了 i 的地址,我們就可以說“ p 指向了 i” ,但 p 和 i 是兩個不同的變數,修改一方不會影響另一個的值。
2. *p 等價於 i ,i 等價於 *p;兩者是一塊記憶體空間,裡面的值一變具變。`
  1. 指標和函式

    // 修改外部實參的值

    void func(int * p) { *p = 100; // 函式內修改外部變數的值 }

    // 修改外部實參的值,二級指標的值 void func2(int ** p) { *p = 100; // 函式內修改外部變數的值 ,這裡實際修改的是指標的內部的地址,這裡直接自己修改並不嚴謹也不安全,只是為了表達意思 }

    int main(void) { // 修改外部實參 int i = 10; func(&i); printf("i = %d",i);

    // 修改外部二級指標
    int *p = i; // 等價於 int *p; p = &i;
    func(&p);
    printf("i = %d",i);
    
    return 0;

    }

    // 通過函式呼叫,改變函式外部變數(實參)的值 `

  2. 指標和陣列

` 【指標】 和 【一維陣列】

int a[5] = {1,2,3,4,5 };
 
 a[3] == *(a + 3)  
 // 等價於 a[3] == *(3 + a) == 3[a];
 // 3[a] 這種寫法只是不常用,從原理上來說是正確的 a 等價於 a[0];
 // a 是陣列中第一個元素,每個元素佔用記憶體單位長度相同,
 // a[i] 中的 i 實際代表的是單位長度的倍數`


- 陣列名:<br>
  一維陣列名是個指標常量(它的值不可變) <br>
  它存放的是該一維陣列的第一個元素的地址(一維陣列名指向其第一個元素)
  
- 下標和指標的關係:
  (1)、 `a[i]` 等價於 `*(a + i)`
  (2)、假設指標變數的名字為 p, 
  則 `p + i` 的值為 `p + i * (p 所指向的變數所佔位元組數)`
  (3)、每個下標表示的是第 i+1 個元素,根據元素型別分配的位元組長度不同(int 型別4個位元組),每個位元組都有對應的記憶體地址編號,指標變數 p 儲存的是該元素首位元組的地址。

- 指標變數的運算: 
      指標變數不能相加、相乘、相除
      如果兩指標變數屬於同一陣列,則可以相減
      指標變數可以加減一個整數,前提是最終結果不能超過指標最大可訪問範圍
            


// 指標變數的運算
p + i 的值是 p + i*(所指向的變數所佔位元組數)
p - i 的值是 p - i*(所指向的變數所佔位元組數)
p++   等價於 p + 1
p--   等價於 p - 1


// 下面是一個通過函式修改陣列內部元素
void my_Array(int *a , int length)
{
    for(int i = 0; i < length; i++)
    {
        *a[i]++;  // 給每個元素加 1
    }
}

int main(void){

    int a[5] = {1,2,3,4,5};
    my_Array(a , 5); // 呼叫
}

`

結構體

為什麼會出現結構體

為了表示一些複雜的資料,而普通的基本資料無法滿足要求.

什麼叫結構體

結構體是使用者根據實際需要,自己定義的複合資料型別

// 如學生型別
struct Student{
   int age;
   char * name; // name 不同,賦值方法不同
   char name2[100]; // 這個只能 strcpy(s.name2, "zhangwangdsd"); 字串拷貝
   double height;
};

如何使用結構體

總結起來有兩種結構體的使用方式:直接使用 && 通過指標使用
struct Student ss = {12,"xiaoyou",1.73,"xiaozhang"};
struct Student *pst = &ss;

ss.name ;  這裡直接操作結構體本身
pst -> name ;   這裡通過指標地址操作,更加節省空間



struct Student{ // 自定義結構體
int age;
char * name;
double height;
char name2[100];

};

int main(void) {


struct Student s = {12,"xiaoyou",1.73,"xiaozhang"};


// 直接使用
printf(" age = %d \n name = %s \n height = %.2f \n",s.age,s.name,s.height);

s.age = 21;
s.name = "xiaozhu";
strcpy(s.name2, "zhangwangdsd");  // 字串拷貝
s.height = 1.70;
printf(" age = %d \n name = %s \n height = %.2f \n %s \n",s.age,s.name,s.height,s.name2);
// 以指標的方式使用
struct Student *pst = &ss;
pst -> name = "my new name";

printf(" name = %s\n",pst->name);
printf(" name = %s\n",(*pst).name);

// pst -> name 等價於 (*pst).name ,
// 而(*pst).name 又等價於 ss.name
// 所以 pst -> name 等價於 ss.name
return 0;
}

注意事項

結構體變數的型別為: struct Student

結構體變數不能加減乘除,但是能夠相互賦值

普通結構體變數和結構體指標變數作為函式傳參的問題

typedef struct Student{ // 結構體定義
int age;
char * name;
char name2[100];
double height;
}myStudent;

// 直接傳遞整個結構體資料,耗時 && 浪費記憶體空間
void func(struct Student st);
// 直接傳遞 只佔用 4 byte 的指標,省時效率也高 <推薦用法>
void func2(struct Student * pst);

int main(void){

myStudent ss = {12,"xiaoyou",1.73};
func(ss);
func2(&ss);
return 0;
}

void func(struct Student st){

printf("age = %d \n name = %s",st.age,st.name);
}

void func2(struct Student * pst){

printf("age = %d \n name = %s",(*pst).age,(*pst).name);
printf("age = %d \n name = %s",pst->age,pst->name);

}

### 動態記憶體分配和釋放

平時直接建立陣列的寫法都是靜態建立,建立完畢之後在整個程式的執行過程中,會固定佔用對應的記憶體,

不僅會造成記憶體空間浪費,還無法動態新增元素,所以侷限性很大,而程式中我們為了避免這種情況,應該使用動態的方式建立和銷燬陣列。


// 靜態建立陣列
int a[5] = {1,2,3,4,5};

動態構造一維陣列

動態構造一個 int 型的一維陣列。

int *p = (int *)malloc(int length);

1. void * malloc(size_t __size) 函式,只有一個 int 型別的形參,表示要求系統分配的位元組數

2. malloc 函式的功能是請求系統 length 個位元組的記憶體空間,如果請求完成則返回的是第一個位元組的地址,
如果請求不成功,則返回NULL
3. malloc 函式能且只能返回第一個位元組的地址,所以我們需要把沒有實際意義的第一個位元組地址(乾地址)轉化為一個有實際意義的地址,
所以 malloc 前面必須加(資料型別 *),表示把這個無意義的地址轉化為對應型別的地址

例項:
    int *p = (int *)malloc(50);
    表示將系統分配的 50 個位元組的第一個位元組的地址轉化為 int 型別的地址,準確的說是轉化為 4 個一組的地址的首地址,
    這樣 p 就指向了第一個四個位元組··· p+i 就指向了第 i+1 個四個位元組,p[0],p[i]也就分別是第一個,第i+1個元素。
    
    double *p = (double *)malloc(80);
    表示將系統分配的 80 個位元組的第一個位元組的地址轉化為 double 型別的地址,準確的說是轉化為 8 個一組的地址的首地址,
    這樣 p 就指向了第一個八個位元組··· p+i 就指向了第 i+1 個八個位元組,p[0],p[i]也就分別是第一個,第i+1個元素。
    
4. free(p);
釋放 p 所指向的記憶體,而不是釋放 p 本身所佔用的記憶體
    

程式碼示例如下:


void test2(void)
{
int len;
printf("請輸入你要動態建立的陣列長度:");
scanf("%d",&len);

int *pArr = (int *)malloc(len); // 動態建立陣列
*pArr = 4;      // 相當於 a[0] = 4;  這裡 pArr 就等於陣列首地址,等於陣列名
pArr[2] = 5;    // 相當於 a[2] = 5;

printf("pArr[0] = %d \npArr[2] = %d\n",pArr[0],pArr[2]);

free(pArr);     // 使用完畢,釋放對應的陣列空間 
}

跨函式使用記憶體

在函式內部分配的區域性變數,在函式呼叫完成之後就會被系統回收,其記憶體也會消失。但是程式中常常需要定義一塊記憶體,

當我們用完之後再會回收。如 OC 語言中物件。所以需要儲存住分配的記憶體,應該用動態分配記憶體,當用完之後再手動釋放。

這也是C語言的一個不足之處:記憶體需要我們手動建立和手動釋放,這也是 OC 語言在開發 iOS 程式時候,我們所講的MRC。

【蘋果也發現了這個不足,於 iOS 5 的時候推出了ARC 】

下面是一個跨函式使用記憶體的例子:

// 這個例子已經非常有面向物件的味道了

typedef struct Student{ // 自定義 student 結構體
int age;
char * name;
}myStudent;

myStudent * createStudent(void); // 建立 student 

void showStudent(myStudent *);   // 輸出 student

int main(void) {

myStudent *p = createStudent();  // 建立 student 
showStudent(p);                  // 輸出 student

return 0;
}

myStudent * createStudent(void)
{
myStudent *p = (myStudent *)malloc(sizeof(myStudent));
p->age = 20;
p->name = "xiaoyou";
return p;
}

void showStudent(myStudent *p)
{
printf("student.age = %d \nstudent.name = %s\n",p->age,p->name);
}

4. 小結

本文主要講解了資料結構的定義和簡介。 回顧了學習資料結構應該具備的一些 C語言 的基礎知識,如指標、結構體、和記憶體等。

後面會繼續開始對資料結構的講解。