本文匯總了項目開發中高頻使用的字符串操作模型、預處理技巧、結構體/聯合/枚舉的深度用法,以及 Makefile 自動化構建基礎。所有代碼均經過實測,可直接應用於工程實踐。
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;
}
不修改原串,結果寫入輸出緩衝區——體現「指針輸入/輸出特性」的工程規範:
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;
}
void reverseStr(char *s) {
char *left = s, *right = s + strlen(s) - 1;
while (left < right) {
char tmp = *left; *left = *right; *right = tmp;
left++; right--;
}
}
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); // 復用去空格函數
}
按分隔符分割為二維數組,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) | 棧上一級指針 → 堆上指針數組 → 堆上數據 | 動態行數、動態列長 |
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;
}
}
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);
}
}
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);
}
int a[5] 分配 20 字節,a 是類型 int (*)[5] 的常量指針。a 的值(a = p 錯誤),但可定義指向數組的指針:int (*p)[5] = &a;。(*p)[i] 或 p[0][i]。int a[3][5] 在內存中線性佈局,a 類型為 int (*)[5]。a + 1 跨越 5 個 int(20 字節),即跳到下一行。a[i][j] 等價於 *(*(a+i)+j)。char *p = NULL;if (!p) return -1;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 | 內存操作 | 按字節操作,不關心類型 |
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;
}
#define SIZE 100
#define SQUARE(x) ((x)*(x)) // 括號防止運算符優先級錯誤
#define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
/* mylib.h */
#ifndef MYLIB_H
#define MYLIB_H
void foo(void);
#endif
/* 多文件包含同一頭文件僅展開一次 */
/* 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 */
static 限制作用域static int g_val:文件級私有全局變量。static void helper():文件級私有函數。extern 實現模塊封裝。# 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 清理產物。
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");
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);
}
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}
};
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字節)值傳遞或許更快
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); } // 寫入調用者內存,推薦
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)
/* 原則:成員偏移為自身大小倍數;結構體總大小為最大成員大小倍數 */
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(小端序)
enum Week { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };
enum Week today = Wed;
switch (today) { case Mon: puts("Monday"); break; ... }
char *p = "hello"; // p 指向只讀數據段
char a[] = "hello"; // a 在棧上,可修改
a[0] = 'H'; // OK
// p[0] = 'H'; // 崩潰:段錯誤
char buf[10];
scanf("%9s", buf); // 最多讀 9 字符,留 1 位給 \0
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;
}
char *pathCat(char *path, const char *name) {
char *p = path;
while(*p) p++;
if (*(p-1) != '/') *p++ = '/';
while(*p++ = *name++);
return path;
}
學習路線建議:先掌握字符串模型庫 → 理解二級指針三模型 → 熟練預處理與 Makefile → 深入結構體內存佈局 → 實戰中靈活組合。每個知譼點都配有可編譯代碼,建議動手調試,構建肌肉記憶。