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

C語言進階:字符串處理、預處理器與結構體實戰

     編輯:關於C語言

C語言進階:字符串處理、預處理器與結構體實戰

本文匯總了項目開發中高頻使用的字符串操作模型、預處理技巧、結構體/聯合/枚舉的深度用法,以及 Makefile 自動化構建基礎。所有代碼均經過實測,可直接應用於工程實踐。

一、項目級字符串處理模型庫

1.1 子串計數:strstr 迭代模型

統計子串在主串中出現的次數,注意指針移動邏輯:

int getCount(const char *str, const char *sub, int *count) {
    if (!str || !sub || !count) return -1;
    int n = 0;
    const char *p = str;
    while ((p = strstr(p, sub))) {
        n++;
        p += strlen(sub);  // 跳過已匹配部分,避免重疊匹配
    }
    *count = n;
    return 0;
}

1.2 兩頭堵去空格:雙指針縮減區間

不修改原串,結果寫入輸出緩衝區——體現「指針輸入/輸出特性」的工程規範:

int trimSpace(const char *src, char *dst) {
    if (!src || !dst) return -1;
    int i = 0, j = strlen(src) - 1;
    while (isspace(src[i]) && src[i]) i++;
    while (j > 0 && isspace(src[j])) j--;
    int len = j - i + 1;
    if (len < 0) len = 0;
    strncpy(dst, src + i, len);
    dst[len] = '\0';
    return 0;
}

1.3 字符串反轉:首尾雙指針交換

void reverseStr(char *s) {
    char *left = s, *right = s + strlen(s) - 1;
    while (left < right) {
        char tmp = *left; *left = *right; *right = tmp;
        left++; right--;
    }
}

1.4 鍵值對提取:兩級 strstr 定位

從 "key=value" 格式中解析 value,工程中常見於配置文件解析:

int getKeyValue(const char *kv, const char *key, char *val) {
    const char *p = strstr(kv, key);
    if (!p) return -1;
    p += strlen(key);
    p = strstr(p, "=");
    if (!p) return -2;
    p++; // 跳過 '='
    return trimSpace(p, val); // 復用去空格函數
}

1.5 分割字符串:雙輔助指針「挖掘」模型

按分隔符分割為二維數組,pTmp 標記段起始,p 搜尋分隔符:

int splitString(const char *src, char delim, char out[][30], int *cnt) {
    const char *p = src, *start = src;
    int n = 0;
    while (*p) {
        p = strchr(p, delim);
        if (!p) break;
        int len = p - start;
        strncpy(out[n], start, len);
        out[n][len] = '\0';
        n++;
        p++; start = p; // 移至下一段
    }
    *cnt = n;
    return 0;
}

二、二級指針的三種內存模型

模型內存佈局典型場景
指針數組棧上數組,元素指向常量區/堆char *argv[]、關鍵字表
二維數組棧上連續塊固定長度字符串矩陣
手工二維(雙重 malloc)棧上一級指針 → 堆上指針數組 → 堆上數據動態行數、動態列長

2.1 指針數組排序:交換指針而非數據

void sortPtrArray(char **arr, int n) {
    for (int i = 0; i < n-1; i++)
        for (int j = i+1; j < n; j++)
            if (strcmp(arr[i], arr[j]) > 0) {
                char *tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
            }
}

2.2 二維數組排序:需逐字符拷貝

void sort2DArray(char arr[][30], int n) {
    char tmp[30];
    for (int i = 0; i < n-1; i++)
        for (int j = i+1; j < n; j++)
            if (strcmp(arr[i], arr[j]) > 0) {
                strcpy(tmp, arr[i]);
                strcpy(arr[i], arr[j]);
                strcpy(arr[j], tmp);
            }
}

2.3 動態分配模型:分級申請、逆序釋放

char **create2D(int rows, int cols) {
    char **a = malloc(rows * sizeof(char*));
    for (int i = 0; i < rows; i++)
        a[i] = malloc(cols * sizeof(char));
    return a;
}
void free2D(char **a, int rows) {
    for (int i = 0; i < rows; i++) free(a[i]);
    free(a);
}

三、數組本質深度解析

3.1 一維數組 = 常量指針

  • int a[5] 分配 20 字節,a 是類型 int (*)[5] 的常量指針。
  • 不可修改 a 的值(a = p 錯誤),但可定義指向數組的指針:int (*p)[5] = &a;。
  • 遍歷:(*p)[i] 或 p[0][i]。

3.2 二維數組 = 數組指針(指向低維數組的指針)

  • int a[3][5] 在內存中線性佈局,a 類型為 int (*)[5]。
  • a + 1 跨越 5 個 int(20 字節),即跳到下一行。
  • 訪問 a[i][j] 等價於 *(*(a+i)+j)。

四、野指針防範與內存管理鐵律

  1. 定義即初始化:char *p = NULL;
  2. malloc 後檢查:if (!p) return -1;
  3. free 後置 NULL:防止二次釋放崩潰。
  4. 封裝釋放函數:
void safeFree(void **pp) {
    if (pp && *pp) { free(*pp); *pp = NULL; }
}
// 使用:safeFree((void**)&p);

五、標準字符串庫函數實戰速查

函數用途注意點
strcpy/strncpy拷貝strncpy 不自動補 \0,需手動添加
strcat/strncat拼接目標空間需足夠大
strcmp/strncmp比較返回 0 表示相等
strlen長度不含 \0,O(n)
strchr/strrchr查字符返回指針或 NULL
strstr查子串返回首次出現地址
strtok分割破壞原串,非線程安全
sscanf/sprintf格式化轉換緩衝區溢出風險,優先用 snprintf
memset/memcpy內存操作按字節操作,不關心類型

5.1 字符串數組與命令行參數

int main(int argc, char *argv[]) {
    char *tigers[] = {"GuanYu","ZhangFei","ZhaoYun","MaChao","HuangZhong"};
    if (argc < 2) return 0;
    for (int i = 0; i < 5; i++)
        if (strcmp(argv[1], tigers[i]) == 0) {
            printf("%s 是五虎上將\n", argv[1]);
            return 0;
        }
    printf("%s 不是\n", argv[1]);
    return 0;
}

六、預處理器:宏、條件編譯與模塊化

6.1 宏變量與宏函數

#define SIZE 100
#define SQUARE(x) ((x)*(x))  // 括號防止運算符優先級錯誤
#define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

6.2 條件編譯防重復包含

/* mylib.h */
#ifndef MYLIB_H
#define MYLIB_H
void foo(void);
#endif

/* 多文件包含同一頭文件僅展開一次 */

6.3 多平台/多精度切換編譯

/* main.h */
#if defined(USE_SHORT)
  #include "short.h"
  #define TYPE short
#elif defined(USE_LONG)
  #include "long.h"
  #define TYPE long
#else
  #include "standard.h"
  #define TYPE int
#endif

/* 編譯命令:gcc -DUSE_LONG main.c long.c -o app */

6.4 static 限制作用域

  • static int g_val:文件級私有全局變量。
  • static void helper():文件級私有函數。
  • 配合頭文件聲明 extern 實現模塊封裝。

七、Makefile 自動化構建入門

# Makefile
CC = gcc
CFLAGS = -Wall -g
OBJS = main.o input.o output.o

app: $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f *.o app

.PHONY: clean

執行 make 自動增量編譯;make clean 清理產物。

八、結構體、聯合、枚舉綜合實戰

8.1 結構體聲明與初始化

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

struct Student s1 = {101, "Alice"};      // 順序初始化
struct Student s2 = {.name = "Bob", .id = 102}; // C99 指定初始化
struct Student s3; s3.id = 103; strcpy(s3.name, "Carol");

8.2 嵌套結構體:點與矩形

struct Point { int x, y; };
struct Rect  { struct Point lt, rb; };

double distance(struct Point p) {
    return sqrt(p.x*p.x + p.y*p.y);
}
int area(struct Rect r) {
    return abs(r.lt.x - r.rb.x) * abs(r.lt.y - r.rb.y);
}

8.3 結構體數組:月份天數表

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}
};

8.4 結構體傳參:值傳遞 vs 指針傳遞

void printVal(struct Student s) { printf("%d %s\n", s.id, s.name); } // 複製整個結構
void printPtr(const struct Student *p) { printf("%d %s\n", p->id, p->name); } // 僅傳地址,加 const 防誤改

// 大結構體必須用指針,小結構體(≤16字節)值傳遞或許更快

8.5 結構體作返回值 vs 輸出參數

struct Student inputVal(void) { struct Student s; scanf("%d%s",&s.id,s.name); return s; } // 返回副本
void inputPtr(struct Student *p) { scanf("%d%s",&p->id,p->name); } // 寫入調用者內存,推薦

8.6 內存對齊與補齊實測

struct A { char c; int i; };     // sizeof=8 (c佔1,補3, i佔4)
struct B { int i; char c; };     // sizeof=8 (i佔4, c佔1, 補3)
struct C { char c; double d; };  // sizeof=16 (c佔1,補7, d佔8)
/* 原則:成員偏移為自身大小倍數;結構體總大小為最大成員大小倍數 */

8.7 聯合:同一內存多重解釋

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

8.8 枚舉:可讀性增強的整型常量

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

九、字符串地址測試與基礎操作補充

9.1 字符串常量 vs 字符數組

char *p = "hello";   // p 指向只讀數據段
char a[] = "hello";  // a 在棧上,可修改
a[0] = 'H'; // OK
// p[0] = 'H'; // 崩潰:段錯誤

9.2 安全輸入:限制長度防溢出

char buf[10];
scanf("%9s", buf); // 最多讀 9 字符,留 1 位給 \0

9.3 手寫 strlen/strcat 理解指針運算

int myStrlen(const char *s) { int n=0; while(*s++) n++; return n; }
char *myStrcat(char *dest, const char *src) {
    char *d = dest;
    while(*d) d++;
    while(*d++ = *src++);
    return dest;
}

9.4 文件路徑拼接:自動補齊分隔符

char *pathCat(char *path, const char *name) {
    char *p = path;
    while(*p) p++;
    if (*(p-1) != '/') *p++ = '/';
    while(*p++ = *name++);
    return path;
}

學習路線建議:先掌握字符串模型庫 → 理解二級指針三模型 → 熟練預處理與 Makefile → 深入結構體內存佈局 → 實戰中靈活組合。每個知譼點都配有可編譯代碼,建議動手調試,構建肌肉記憶。

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