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

C 語言核心程式設計:字串處理、預處理器與結構體深度解析

     編輯:C 語言入門

第一部分:字串處理實戰模式

1.1 專案級字串核心模型

在實際專案開發中,字串處理往往封裝為標準化介面,強調參數檢查、記憶體安全與指標輸入/輸出特性的區分。

子串計數模型 (strstr 迴圈)

使用 do-while 或 while 配合 strstr 尋找子串,找到後移動指標繼續搜尋,並嚴格檢查邊界條件。

int getCout(char *str, char *substr, int *count) {
    if (!str || !substr || !count) return -1; // 參數合法性檢查
    int ncout = 0;
    char *p = str;
    do {
        p = strstr(p, substr);
        if (!p) break;
        ncout++;
        p += strlen(substr); // 移至下一個搜尋起始位置
    } while (*p != '\0');
    *count = ncout; // 透過指標回傳結果 (輸出型參數)
    return 0;
}

兩頭堵模型 (去除首尾空格)

雙指標 i (頭) 與 j (尾) 向中間收斂,嚴禁修改輸入緩衝區,應將結果複製至輸出緩衝區。

int trimSpace_ok(char *mybuf, char *outbuf) {
    int i = 0, j = strlen(mybuf) - 1;
    while (isspace(mybuf[i]) && mybuf[i]) i++;
    while (isspace(mybuf[j]) && j > 0) j--;
    int count = j - i + 1;
    if (count < 0) count = 0;
    memcpy(outbuf, mybuf + i, count);
    outbuf[count] = '\0';
    return 0;
}

字串反轉模型 (雙指標交換)

void reverse(char *p) {
    char *p1 = p, *p2 = p + strlen(p) - 1;
    while (p1 < p2) {
        char c = *p1; *p1 = *p2; *p2 = c;
        ++p1; --p2;
    }
}

鍵值對解析模型 (兩個輔助指標挖掘)

適用於 key=value 格式解析,結合 strstr 定位關鍵字與分隔符,再提取值並去除空格。

1.2 指標作函數參數:輸入特性 vs 輸出特性

  • 輸入特性 (In): 主調函數分配記憶體,被調函數唯讀使用。const char * 最佳實踐。
  • 輸出特性 (Out): 被調函數分配記憶體 (或填充主調提供的緩衝區),主調負責釋放。通常為二級指標 char ** 或緩衝區指標 char *outbuf。
  • 輸入輸出特性 (In/Out): 主調分配,被調修改 (如 strcat、指標推進解析)。

1.3 字串基礎操作與常用函數實現

自定義 strlen、strcpy、strcat

理解指標遞增與字元複製的核心邏輯:

// strlen
int mystrlen(char *s) { int n=0; while(*s++) n++; return n; }

// strcpy (標準返回目的地址)
char* mystrcpy(char *dst, const char *src) {
    char *ret = dst;
    while((*dst++ = *src++));
    return ret;
}

// strcat
char* mystrcat(char *dst, const char *src) {
    char *ret = dst;
    while(*dst) dst++; // 找到結尾
    while((*dst++ = *src++));
    return ret;
}

路徑與檔名拼接實例

自動補齊路徑分隔符 /。

1.4 標準庫函數綜合應用

strcmp, sizeof vs strlen, strcat, strtok (注意破壞原字串且非執行緒安全), strstr, sscanf/sprintf 格式化轉換, memset 初始化。

1.5 字串陣列與命令列參數

指標陣列 char *argv[] 儲存命令列參數,argc 為個數。環境變數同樣為 char **env,以 NULL 結尾。

int main(int argc, char *argv[], char **env) {
    for (int i=0; i<argc; i++) printf("argv[%d]=%s\n", i, argv[i]);
    for (int i=0; env[i]; i++) printf("env[%d]=%s\n", i, env[i]);
    return 0;
}

第二部分:預處理器與模組化編譯

2.1 #include 檔案包含機制

#include <stdio.h> 搜尋系統目錄;#include "my.h" 先搜尋當前目錄,再搜尋系統目錄。可透過 gcc -I 指定搜尋路徑。

2.2 巨集定義與巨集函數

  • 物件巨集: #define SIZE 100、#define PI 3.14。
  • 函數巨集: #define MAX(a,b) ((a)>(b)?(a):(b))。參數必須加括號,避免運算子優先級問題。多行使用反斜線 \ 續行。
  • 字串化 # 與連接 ##: #define STR(x) #x、#define CONCAT(a,b) a##b。

2.3 條件編譯與標頭檔防重複包含

標準護衛宏模式:

#ifndef MY_HEADER_H
#define MY_HEADER_H
// 宣告內容
#endif

進階:根據編譯選項 (-DSHORT) 選擇不同型別實作:

#if defined(SHORT)
#include "short.h"
#elif defined(LONG)
#include "long.h"
#else
#include "standard.h"
#endif

2.4 static 關鍵字:內部連結與封裝

  • 修飾函數/全域變數: 限制連結範圍於當前編譯單元 (.c),外部不可見,實現資訊隱藏。
  • 修飾區域變數: 延長生命週期至程式結束,儲存於靜態區,僅初始化一次。

2.5 多檔案專案與 Makefile 自動化建置

將功能拆分為 .h (介面) + .c (實作)。Makefile 定義相依關係與編譯指令,實現增量編譯。

# 簡易 Makefile 範例
main: main.o input.o output.o
    gcc main.o input.o output.o -o main

main.o: main.c input.h output.h
    gcc -c main.c
input.o: input.c input.h
    gcc -c input.c
output.o: output.c output.h
    gcc -c output.c

clean:
    rm -f *.o main

第三部分:結構體、聯合與列舉

3.1 結構體宣告、初始化與巢狀結構

struct Student { int id; char name[20]; };

// 宣告同時定義變數
struct Student stu1 = {101, "Alice"};

// 指定成員初始化 (C99)
struct Student stu2 = {.name = "Bob", .id = 102};

// 巢狀結構:矩形由兩點構成
struct Point { int x, y; };
struct Rect { struct Point lt, rb; };
struct Rect r = {{0,0}, {10,20}};
// 面積計算
int area = abs(r.lt.x - r.rb.x) * abs(r.lt.y - r.rb.y);

3.2 結構體陣列:月份資料表

struct Month { char name[10]; int days; };
struct Month year[12] = {
    {"January", 31}, {"February", 28}, {"March", 31},
    {"April", 30}, {"May", 31}, {"June", 30},
    {"July", 31}, {"August", 31}, {"September", 30},
    {"October", 31}, {"November", 30}, {"December", 31}
};

3.3 結構體作函數參數:值傳遞 vs 位址傳遞

  • 值傳遞 void f(struct S s): 複製整個結構體,開銷大,函數內修改不影響原物件。
  • 位址傳遞 void f(struct S *p): 傳遞指標 (4/8 位元組),效率高,可修改原物件。成員存取使用 p->member。

最佳實踐: 唯讀傳遞大型結構體用 const struct S *;需修改或節省開銷用 struct S *。

3.4 結構體作返回值與輸出型參數

C 語言支援直接返回結構體 (複製返回),但較大結構體建議使用「輸出型參數」模式:

// 返回結構體 (適用於小型結構)
struct Student input1() { struct Student s; scanf(...); return s; }

// 輸出型參數 (推薦用於大型結構或需錯誤碼返回)
int input2(struct Student *out) {
    if (!out) return -1;
    scanf("%d %s", &out->id, out->name);
    return 0;
}

3.5 記憶體對齊與補齊

結構體大小非單純成員大小之和。編譯器遵循:

  1. 對齊: 每個成員偏移量必須是其型別大小 (或編譯器設定的對齊數) 的倍數。
  2. 補齊: 結構體總大小必須是最大成員大小 (通常上限 4 或 8) 的倍數。
struct { char c; int i; } // 大小通常為 8 位元組 (非 5)
/* 記憶體佈局:
c | padding(3) | i(4) | = 8 bytes
*/

可透過 #pragma pack(n) 或屬性 __attribute__((packed)) 壓縮對齊,但可能影響存取效能或觸發硬體異常。

3.6 聯合體:共享記憶體的多型視圖

所有成員共用同一塊記憶體,大小等於最大成員。用於型別轉換、節省記憶體、協定解析。

union Data { int i; float f; char c[4]; };
union Data d;
d.i = 0x41424344;
printf("%s\n", d.c); // 輸出 "DCBA" (小端序)

3.7 列舉型別:具名常數集合

本質為 int 常數,增強程式碼可讀性與型別安全 (編譯器可檢查 switch 覆蓋率)。

enum Week { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };
enum Week today = Wed;
switch(today) { case Mon: ... break; ... }

總結

字串處理重在記憶體邊界檢查與指標特性區分;預處理器提供強大的條件編譯與巨集抽象能力,配合 Makefile 實現工程化建置;結構體/聯合/列舉構成了 C 語言自定義資料型別的基石,深入理解記憶體佈局 (對齊/補齊) 為高效系統程式設計鋪路。

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