1. 程式人生 > >longj's coding workbench

longj's coding workbench

這周本人在程式設計實驗課上遇到了一道c++的連結串列構造題,對於我這種學過c初學c++不久的人來說花費的時間還蠻多的,收穫當然也不少,鑑於連結串列真是一個非常非常重要的知識點,故把程式碼與自己的思考總結記錄下來,方便以後檢視。

題目TA已經把main.cpp和list.hpp檔案寫好了,我們只需要把list.cpp這個實現檔案寫好就行。
main.cpp就不用寫了,只是TA自己的呼叫而已,關鍵是把標頭檔案和實現檔案寫好。

1、已經被TA規定好的標頭檔案:

#ifndef LIST
#define LIST

#include <string>
#include <iostream>
typedef struct node { int data; struct node* next; node(int data = 0, struct node* next = NULL) : data(data), next(next) {} } node; class list { private: node* head; int _size; public: list(); list(const list&); list& operator=(const list&); ~list(); // Capacity bool
empty(void) const; int size(void) const; public: // output // list: [1,2,3,4,5] // output: 1->2->3->4->5->NULL std::string toString(void) const; void insert(int position, const int& data); void erase(int position); void clear(void) { if (this->head != NULL) { node* p = this
->head; while (p != NULL) { node* temp = p; p = p->next; delete temp; } this->head = NULL; } this->_size = 0; } list& sort(void); }; #endif

2、各種實現

1、一般和預設建構函式,解構函式

#include "list.hpp"
#include <sstream>
#include <iostream>
#include <string>

void swap(int*, int*);
std::string change(int i);

list::list() {
    head = NULL;
    _size = 0;
}

list::~list() {
    clear();
}

2、等號運算子的過載

list& list::operator =(const list& l) {
    clear();
    if (l.head == NULL) {
        head = NULL;
        _size = 0;
    } else {
        node* ptr = l.head -> next;
        _size = 1;
        head = new node(l.head -> data);
        for (int i = 1; i < l._size; i++) {
            insert(i, ptr -> data);
            ptr = ptr -> next;
        }
        _size = l._size;
    }
    return *this;
}

3、拷貝建構函式

list::list(const list& another) {
  this->head = NULL;
  this->_size = 0;
  *(this) = another;
}

4、判斷是否為空

bool list::empty(void) const {
    if (head != NULL) return false;
    else return true;
}

5、遍歷連結串列

其中change函式為一個自己定義的把int型轉為string型的函式。
返回一個特定的string來表示連結串列的內容而已,對於連結串列的構建來說不是必須存在的。

std::string list::toString(void) const {
    std::string str = "";
    node *ptr = head;
    while (ptr != NULL) {
        str += change(ptr -> data);
        str += "->";
        ptr = ptr -> next;
    }
    str += "NULL";
    return str;
}

6、int轉string

std::string change(int i) {
    std::stringstream ss;
    ss << i;
    return ss.str();
}

7、獲得當前連結串列長度

int list::size(void) const {
    return _size;
}

8、往連結串列中插入一個元素

void list::insert(int position, const int& data) {
    if (position > _size) {
        return;
    } else {
        node *current = new node(data);
        if (position == 0) {
            current -> next = head;
            head = current;
            _size++;
        } else {
            node *ptr = head;
            while (--position) {
                ptr = ptr -> next;
            }
            current -> next = ptr -> next;
            ptr -> next = current;
            _size++;
        }
    }
}

9、從連結串列中清除一個元素

void list::erase(int position) {
    if (position < _size) {
        if (position != 0) {
            node *ptr = head;
            while (--position) {
                ptr = ptr -> next;
            }
            node *temp = ptr -> next;
            ptr -> next = temp -> next;
            delete temp;
            _size--;
        } else {
            node *temp = head;
            head = head -> next;
            delete temp;
            _size--;
        }
    } else {
        return;
    }
}

10、給連結串列中的元素重新排個序

list& list::sort(void) {
    int* a = new int[_size];
    node* ptr = head;
    int i = 0;
    while (ptr != NULL) {
        a[i++] = ptr -> data;
        ptr = ptr -> next;
    }
    for (int p = 0; p < _size - 1; p++) {
        for (int q = _size - 1; q > p; q--) {
            if (a[q - 1] > a[q]) swap(&a[q - 1], &a[q]);
        }
    }
    ptr = head;
    i = 0;
    while (ptr != NULL) {
         ptr -> data = a[i++];
         ptr = ptr -> next;
    }
    delete []a;
    return *this;
}

11、輔助交換的函式

void swap(int *a, int *b) {
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

3、小結

1、深拷貝

淺拷貝就是對於類按照成員變數逐步複製,當類裡面的成員變數沒有指標時,淺拷貝與深拷貝的效果是一樣的,但是當成員變數裡面存在指標時,淺拷貝就會出現不足。
舉個例子:
這裡寫圖片描述

這裡寫圖片描述

因此,淺拷貝容易出現記憶體垃圾的現象,當程式持續執行時,將會降低計算機記憶體的利用效率。當我們沒有在實現檔案裡自定義拷貝建構函式時,編譯器會自動幫我們加上一個是淺拷貝的拷貝建構函式。所以,如果我們想要提高記憶體利用率,使用深拷貝的話,就需要自己實現,一步一步地拷貝過來,就像上面的程式碼一樣。
這裡因為過載了“=”的運算,“=”運算中實現的就是深拷貝,故此處的拷貝建構函式就直接利用“=”,以簡化程式碼的長度。

需要指出的是,在等於號過載中,需要先把原來指向的內容給清空掉,即先呼叫一次clear()函式,否則會出現記憶體洩漏的情況。

2、記憶體洩漏

程式不再需要動態建立物件時,一定要記住釋放掉這些物件。否則這些空間會一直被佔用,無法分配給別的程式使用;如果指向這些空間的指標指向了別處,將無法回收這些記憶體空間,這就是我們所說的記憶體洩漏。

  1. 養成良好的程式設計習慣,在對原指標賦值的時候,時刻想著會不會出現記憶體塊丟失的問題。
  2. 可以維護一個容器,通過過載new和delete運算子來記錄堆記憶體的申請
    3.通過eden系統的提示,慢慢尋找記憶體錯誤的源頭(需要耐心和細心)(ps,Eden是我們學校的一個線上評測系統,其他的同學可能第三條不適用。。。但是可以用gdb來替代 :) )

3、這裡寫圖片描述

這個是什麼問題?
就是在寫拷貝建構函式的沒有一開始的兩行,即不先給head和_size以預設值,直接呼叫過載的運算子“=”
這是因為拷貝建構函式與“=”有一點點的不同,拷貝建構函式進行時,物件還沒有被創建出來,所以head為未定值,這樣進行clear()函式的話,會死在這裡面。而“=”過載時,等號左邊的物件已經被建立好了。。

4、為何過載等號時需要返回*this和引用?

改的是自己,即賦值運算子左邊的物件,如果不是返回*this和引用,將會返回一個臨時變數,雖然也可以實現,但是會加大記憶體的負荷,程式大時不推薦。

5、引用的小複習

double result;
double &re = result;
double *r = &result;

則*r和re等價,改(*r)的值等於改re的值

6、連結串列

解決連結串列問題時,一定要畫圖!!!!只有畫好圖了,指標之間的相互關係才能夠比較清晰地呈現出來,這樣寫的時候正確率就高多了。

7、小結中的小結

此文作為自己的一個學習心得筆記,歡迎大家參考,主要的是連結串列的元素插入和刪除的實現,拷貝建構函式和等於號運算子的過載的實現,其他的函式只是輔助迎合原題的設計而已,可以不用太過於關注。