首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> C 語言問答

C語言單向鏈結串列完整實作與常見錯誤修正

     編輯:C 語言問答

題目代碼核心問題

原代碼在 Listinsert_L 函數中存在三大錯誤:

  1. 結構體指標賦值錯誤:s->data.name = e.name 嘗試直接指派陣列,C 語言陣列不可作為左值。
  2. 遍歷指標 p 未初始化:while(p && j < i-1) 中 p 為野指標。
  3. 插入位置邏輯缺陷:未處理在第 1 位插入(需修改頭指標)與超出長度插入的情況。

正確完整實作

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define OK 1
#define ERROR 0
#define OVERFLOW -2

typedef int Status;

typedef struct {
    char name[20];
    int code;
    float score;
} ElemType;

typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;

// 建立帶頭節點的單向鏈結串列(頭插法,逆序)
void CreateList_L(LinkList *L, int n) {
    *L = (LinkList)malloc(sizeof(LNode));
    if (!*L) exit(OVERFLOW);
    (*L)->next = NULL;

    for (int i = 0; i < n; ++i) {
        LNode *p = (LNode*)malloc(sizeof(LNode));
        if (!p) exit(OVERFLOW);
        printf("請輸入第 %d 位學生 (學號 姓名 成績):", i + 1);
        scanf("%d %19s %f", &p->data.code, p->data.name, &p->data.score);
        
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

// 在第 i 位插入元素 (1-based,頭節點後為第 1 位)
Status ListInsert_L(LinkList *L, int i, ElemType e) {
    if (i < 1) return ERROR;

    LNode *p = *L;  // p 指向頭節點,第 0 位
    int j = 0;
    // 尋找第 i-1 位節點
    while (p && j < i - 1) {
        p = p->next;
        ++j;
    }
    if (!p) return ERROR;  // 位置超出長度

    LNode *s = (LNode*)malloc(sizeof(LNode));
    if (!s) exit(OVERFLOW);
    
    s->data.code = e.code;
    strcpy(s->data.name, e.name);  // 字串必須用 strcpy
    s->data.score = e.score;

    s->next = p->next;
    p->next = s;
    return OK;
}

// 遍歷輸出
void Output(LinkList L) {
    LNode *p = L->next;
    printf("\n學號\t姓名\t成績\n");
    while (p) {
        printf("%d\t%s\t%.2f\n", p->data.code, p->data.name, p->data.score);
        p = p->next;
    }
}

// 釋放整個鏈結串列
void DestroyList(LinkList *L) {
    LNode *p = *L;
    while (p) {
        LNode *tmp = p;
        p = p->next;
        free(tmp);
    }
    *L = NULL;
}

int main() {
    LinkList list = NULL;
    int n, pos;
    ElemType e;

    printf("請輸入學生數目:");
    scanf("%d", &n);
    printf("輸入格式:學號 姓名 成績\n");
    CreateList_L(&list, n);
    Output(list);

    printf("\n請輸入要插入的位置 (1 為第一筆):");
    scanf("%d", &pos);
    printf("請輸入學生資料 (學號 姓名 成績):");
    scanf("%d %19s %f", &e.code, e.name, &e.score);

    if (ListInsert_L(&list, pos, e) == OK) {
        printf("插入成功!\n");
        Output(list);
    } else {
        printf("插入失敗:位置無效\n");
    }

    DestroyList(&list);
    return 0;
}

關鍵修正點詳細解析

1. 字串複製:strcpy 而非直接指派

// 錯誤:陣列名不可作左值
s->data.name = e.name;

// 正確:逐字元複製,含結束符
strcpy(s->data.name, e.name);
// 或更安全:
strncpy(s->data.name, e.name, sizeof(s->data.name) - 1);
s->data.name[sizeof(s->data.name) - 1] = '\0';

2. 遍歷指標必須從頭節點開始

LNode *p = *L;  // 頭節點為第 0 位
int j = 0;
while (p && j < i - 1) { ... }

原代碼 LinkList p; 未初始化,直接進入 while(p && ...) 是未定義行為。

3. 位置語意統一:1-based,頭節點後為第 1 位

  • i=1:插在頭節點之後,即新的第一個資料節點
  • i=n+1:插在尾部
  • i>n+1:返回 ERROR

4. 記憶體配置失敗處理

LNode *s = malloc(sizeof(LNode));
if (!s) exit(OVERFLOW);  // 或返回 ERROR,視設計而定

5. 輸入緩衝區安全

scanf("%19s", p->data.name);  // 限制讀取長度,防緩衝區溢位

常見操作擴充

操作關鍵點
刪除第 i 位找第 i-1 位節點 p,tmp = p->next; p->next = tmp->next; free(tmp);
查找值遍歷比對,返回指標或位置
反轉串列三指標法:prev=NULL, curr=head, next 迴圈反轉 next 指向
合併有序串列歸併邏輯,建立新頭節點
檢測環快慢指標 (Floyd 判圈算法)

除錯建議

  1. 畫圖:每個操作前在紙上畫節點、指標、記憶體佈局。
  2. GDB 觀察:print *p、print p->next、watch p->data.code。
  3. Valgrind:valgrind --leak-check=full ./a.out 檢測記憶體洩漏、無效讀寫。
  4. 斷言:關鍵位置加 assert(p != NULL) 捕捉邏輯錯誤。

總結

單向鏈結串列是 C 語言指標操作的經典練習。核心功力在於:指標初始化、遍歷邊界、記憶體管理、字串複製、插入刪除的指標重接邏輯。掌握帶頭節點的統一處理模式,能大幅減少邊界條件判斷錯誤。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved