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

C 語言指針進階全攻略:地址運算、字符指針、指針數組、多維數組與函數指針

     編輯:關於C語言

前言

指針是 C 語言的靈魂,也是區分初學者與資深工程師的分水嶺。本文從內存模型出發,系統講解指針算術、自定義內存分配器、字符串操作、指針數組排序、多維數組參數傳遞、函數指針等核心主題,並附帶可編譯運行的完整代碼示例。

一、指針算術運算與自定義內存分配器

1.1 指針算術核心規則

  • p + i 的地址增量 = i * sizeof(*p),自動按元素類型縮放
  • 同一數組內的指針可比較、相減(得 ptrdiff_t),跨數組無意義
  • 指針只能與整數加減、與 0 比較/賦值(NULL)

1.2 手寫 alloc/afree 內存池

原理:預分配大型靜態字符數組 alloc_buf,維護指針 allocp 指向下一個空閒位置,實現棧式(LIFO)分配釋放。

#define ALLOCSIZE 1000
static char alloc_buf[ALLOCSIZE];
static char *allocp = alloc_buf;

char *alloc(int n) {               // 申請 n 字節
    if (alloc_buf + ALLOCSIZE - allocp >= n) {
        allocp += n;
        return allocp - n;         // 返回本次分配起始地址
    }
    return NULL;                   // 空間不足
}

void afree(char *p) {              // 釋放,必須按 LIFO 順序
    if (p >= alloc_buf && p < alloc_buf + ALLOCSIZE)
        allocp = p;
}

關鍵點:static 限制文件內可見,隱藏實現細節;釋放順序必須與分配順序相反,否則 allocp 回退會導致內存洩漏或覆寫。

二、字符指針與字符串操作經典實現

2.1 字符串常量 vs 字符數組

定義方式內存佈局可修改性
char *p = "hello";指向只讀區常量區指針可改向,內容不可改
char arr[] = "hello";棧上分配,含結束符 \0內容可改,地址固定

2.2 指針版 strcpy/strcmp/strlen

// 指針版 strcpy:經典單行寫法
void strcpy_ptr(char *dst, const char *src) {
    while (*dst++ = *src++);  // 賦值後雙雙後移,遇 \0 停止
}

// 指針版 strcmp
int strcmp_ptr(const char *s, const char *t) {
    for (; *s == *t; s++, t++)
        if (*s == '\0') return 0;
    return *s - *t;
}

// 指針版 strlen
size_t strlen_ptr(const char *s) {
    const char *p = s;
    while (*p++) ;  // 移動到 \0 後一位
    return p - s - 1;
}

2.3 棧操作慣用語

  • 入棧:*p++ = val;(先存後增)
  • 出棧:val = *--p;(先減後取)

三、指針數組:高效處理可變長文本行

3.1 核心思想

不移動文本內容,僅操作指向行首的指針。排序時交換指針而非數據,消除大塊內存拷貝開銷。

3.2 完整程序:讀取、排序、輸出文本行

#define MAXLINES 100
#define MAXLEN   100
#define ALLOCSIZE 11000

char *lineptr[MAXLINES];  // 指針數組
static char allocbuf[ALLOCSIZE];
static char *allocp = allocbuf;

char *alloc(int n) { /* 同前 */ }
void afree(char *p) { /* 同前 */ }

int get_line(char *s, int lim) { /* 讀一行,返回長度 */ }

int readlines(char *lineptr[], int maxlines) {
    int len, nlines = 0;
    char line[MAXLEN], *p;
    while ((len = get_line(line, MAXLEN)) > 0) {
        if (nlines >= maxlines || (p = alloc(len + 1)) == NULL)
            return -1;
        line[len - 1] = '\0';  // 去換行符
        strcpy(p, line);
        lineptr[nlines++] = p;
    }
    return nlines;
}

void writelines(char *lineptr[], int nlines) {
    for (int i = 0; i < nlines; i++)
        printf("%s\n", lineptr[i]);
}

// 快速排序(指針數組版)
void qsort_ptr(char *v[], int left, int right) {
    if (left >= right) return;
    swap(v, left, (left + right) / 2);
    int last = left;
    for (int i = left + 1; i <= right; i++)
        if (strcmp(v[i], v[left]) < 0)
            swap(v, ++last, i);
    swap(v, left, last);
    qsort_ptr(v, left, last - 1);
    qsort_ptr(v, last + 1, right);
}

四、多維數組與指針參數傳遞

4.1 日期轉換案例:二維數組存儲月天數

static char day_table[2][13] = {
    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // 平年
    {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}  // 閏年
};

int leap(int year) {
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}

// 月日 -> 年內天數
int day_of_year(int year, int month, int day) {
    for (int i = 1; i < month; i++)
        day += day_table[leap(year)][i];
    return day;
}

// 年內天數 -> 月日(輸出參數用指針)
void date_of_year(int year, int yearday, int *pmonth, int *pday) {
    int lp = leap(year);
    int i = 1;
    while (yearday > day_table[lp][i])
        yearday -= day_table[lp][i++];
    *pmonth = i;
    *pday = yearday;
}

4.2 二維數組作函數參數的正確聲明

聲明形式含義正確性
void f(int a[2][13])帶行列數✅
void f(int a[][13])僅列數,行數退化為指針✅ 推薦
void f(int (*a)[13])指向「含 13 個 int 的數組」的指針✅ 本質等價
void f(int *a[13])13 個 int* 的數組❌ 錯誤
void f(int a[2][])缺列數,無法計算行偏移❌ 錯誤

本質:函數接收的是指向首行的指針,編譯器需要知道「一行有多少列」才能正確計算 a[i][j] 的地址偏移。

五、指針數組 vs 二維數組:內存佈局對比

// 指針數組:存 13 個指針,各指向字符串常量(長度不一)
static char *name_ptr[] = {"Illegal", "January", "February", ...};
// sizeof(name_ptr) = 13 * sizeof(char*) = 104 bytes (64位)

// 二維數組:連續存 13 行 × 20 列 字符
static char name_2d[][20] = {"Illegal", "January", "February", ...};
// sizeof(name_2d) = 13 * 20 = 260 bytes

結論:指針數組適合存儲長度不一的字符串,節省空間;二維數組適合固定寬度數據,訪問速度略快(連續內存、無間接引用)。

六、函數指針速覽

// 聲明:指向「接受 int, 返回 int」函數的指針
int (*func_ptr)(int);

// 賦值
int square(int x) { return x * x; }
func_ptr = square;

// 調用
int result = func_ptr(5);  // 25

// 回調示例
void apply(int *arr, int n, int (*op)(int)) {
    for (int i = 0; i < n; i++) arr[i] = op(arr[i]);
}

總結

  1. 指針算術:按元素大小縮放,同數組內才有意義
  2. 內存池:大數組 + 偏移指針 + LIFO 釋放,嵌入式常用
  3. 字符指針:區分常量區與棧區,掌握 *p++ 棧操作慣用語
  4. 指針數組:排序、存儲可變長字符串的利器
  5. 多維數組參數:必須提供列數,本質是指向數組的指針
  6. 函數指針:實現回調、策略模式的基礎

深度理解指針,即掌握了 C 語言操控內存的核心能力。

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