引言

字节跳动作为国内顶级互联网公司,其技术笔试以难度高、考察全面著称。C语言作为系统编程和高性能计算的基础语言,在字节跳动的后端开发、基础架构、音视频等岗位的笔试中占据重要地位。本文将深入解析字节跳动C语言笔试中的高频考点,并提供实战技巧,帮助求职者高效备考。

一、指针与内存管理

1.1 指针基础与高级应用

指针是C语言的核心,也是字节跳动笔试的必考内容。考察形式包括指针运算、指针与数组、函数指针等。

高频考点:指针与数组的关系

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    
    printf("arr[2] = %d\n", *(ptr + 2));  // 输出3
    printf("arr[2] = %d\n", ptr[2]);      // 输出3,ptr[2]等价于*(ptr+2)
    
    // 指针运算
    printf("ptr++ = %d\n", *(ptr++));     // 输出1,然后ptr指向arr[1]
    printf("*(++ptr) = %d\n", *(++ptr));  // 输出3,ptr先自增再解引用
    
    return 0;
}

实战技巧:

  • 理解指针运算的优先级和结合性
  • 掌握指针与数组的等价关系:arr[i] 等价于 *(arr + i)
  • 注意指针自增自减的副作用

1.2 二级指针与指针数组

高频考点:二级指针作为函数参数

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

// 错误示例:无法修改外部指针
void allocate_memory_wrong(int *p) {
    p = (int*)malloc(sizeof(int) * 5);  // 只修改了局部副本
}

// 正确示例:使用二级指针
void allocate_memory_correct(int **p) {
    *p = (int*)malloc(sizeof(int) * 5);
    for (int i = 0; i < 5; i++) {
        (*p)[i] = i * 10;
    }
}

int main() {
    int *arr = NULL;
    
    // 错误调用
    allocate_memory_wrong(arr);
    printf("Wrong: arr = %p\n", arr);  // 输出NULL
    
    // 正确调用
    allocate_memory_correct(&arr);
    printf("Correct: arr[2] = %d\n", arr[2]);  // 输出20
    
    free(arr);
    return 0;
}

实战技巧:

  • 二级指针用于在函数中修改指针本身
  • 理解 int **pint *p[] 的区别
  • 注意内存释放:free(*p) 后需要将 *p 置为NULL

1.3 函数指针

高频考点:函数指针数组与回调函数

#include <stdio.h>

// 定义函数指针类型
typedef int (*operation)(int, int);

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b != 0 ? a / b : 0; }

// 使用函数指针作为参数
int calculate(int a, int b, operation op) {
    return op(a, b);
}

// 函数指针数组
operation ops[] = {add, sub, mul, div};

int main() {
    int a = 10, b = 5;
    
    // 直接调用
    printf("10 + 5 = %d\n", calculate(a, b, add));
    printf("10 - 5 = %d\n", calculate(a, b, sub));
    
    // 通过数组调用
    for (int i = 0; i < 4; i++) {
        printf("Result %d: %d\n", i, calculate(a, b, ops[i]));
    }
    
    return 0;
}

实战技巧:

  • 函数指针定义格式:返回类型 (*指针名)(参数列表)
  • 函数指针数组:返回类型 (*数组名[大小])(参数列表)
  • 函数指针常用于实现策略模式、回调机制

二、内存管理与内存泄漏

2.1 malloc/calloc/realloc 对比

高频考点:内存分配函数的正确使用

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

void test_memory_functions() {
    // malloc: 不初始化,内容随机
    int *arr1 = (int*)malloc(5 * sizeof(int));
    printf("malloc: ");
    for (int i = 0; i < 5; i++) printf("%d ", arr1[i]);  // 随机值
    printf("\n");
    
    // calloc: 初始化为0
    int *arr2 = (int*)calloc(5, sizeof(int));
    printf("calloc: ");
    for (int i = 0; i < 5; i++) printf("%d ", arr2[i]);  // 0
    printf("\n");
    
    // realloc: 调整已分配内存大小
    arr1 = (int*)realloc(arr1, 10 * sizeof(int));
    for (int i = 5; i < 10; i++) arr1[i] = i * 10;
    printf("realloc: ");
    for (int i = 0; i < 10; i++) printf("%d ", arr1[i]);
    printf("\n");
    
    free(arr1);
    free(arr2);
}

int main() {
    test_memory_functions();
    return 0;
}

实战技巧:

  • malloc 用于分配未初始化的内存
  • calloc 用于分配并清零内存
  • realloc 用于调整内存大小,可能移动内存块
  • 检查返回值:if (ptr == NULL) { /* 处理错误 */ }

2.2 内存泄漏检测与预防

高频考点:复杂场景下的内存泄漏

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

// 错误示例:内存泄漏
void leak_example() {
    int *p = (int*)malloc(sizeof(int) * 100);
    // ... 使用p
    // 忘记free(p)
}

// 错误示例:多次free
void double_free_example() {
    int *p = (int*)malloc(sizeof(int));
    free(p);
    free(p);  // 未定义行为
}

// 错误示例:free后继续使用
void use_after_free() {
    int *p = (int*)malloc(sizeof(int));
    *p = 10;
    free(p);
    printf("%d\n", *p);  // 未定义行为
}

// 正确示例:RAII模式(C语言模拟)
void safe_allocation() {
    int *p = (int*)malloc(sizeof(int) * 100);
    if (!p) {
        fprintf(stderr, "Memory allocation failed\n");
        return;
    }
    
    // 使用资源
    // ...
    
    // 确保释放
    free(p);
    p = NULL;  // 防止悬垂指针
}

// 复杂场景:链表操作
typedef struct Node {
    int data;
    struct Node *next;
} Node;

void free_list(Node *head) {
    Node *current = head;
    while (current != NULL) {
        Node *temp = current;
        current = current->next;
        free(temp);
    }
}

int main() {
    safe_allocation();
    return 0;
}

实战技巧:

  • 每次 malloc 必须有对应的 free
  • free 后立即置指针为 NULL
  • 在函数出口集中释放资源
  • 使用工具检测:Valgrind、AddressSanitizer

2.3 悬垂指针与野指针

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

void dangling_pointer() {
    int *p;
    {
        int x = 10;
        p = &x;  // p指向局部变量
    }  // x被销毁,p成为悬垂指针
    printf("%d\n", *p);  // 危险:可能输出垃圾值
}

void wild_pointer() {
    int *p;  // 未初始化
    *p = 10;  // 野指针,未定义行为
}

void correct_usage() {
    int *p = (int*)malloc(sizeof(int));
    if (p) {
        *p = 10;
        printf("%d\n", *p);
        free(p);
        p = NULL;  // 防止悬垂指针
    }
}

int main() {
    correct_usage();
    return 0;
}

三、字符串操作

3.1 字符串函数的安全使用

高频考点:strcpy vs strncpy vs snprintf

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

void unsafe_strcpy() {
    char dest[5];
    char src[] = "Hello World";
    strcpy(dest, src);  // 缓冲区溢出!
}

void safe_strncpy() {
    char dest[10];
    char src[] = "Hello World";
    strncpy(dest, src, sizeof(dest) - 1);
    dest[sizeof(dest) - 1] = '\0';  // 确保终止符
    printf("safe: %s\n", dest);
}

void safe_snprintf() {
    char dest[10];
    char src[] = "Hello World";
    snprintf(dest, sizeof(dest), "%s", src);
    printf("snprintf: %s\n", dest);
}

void safe_strcat() {
    char dest[20] = "Hello";
    char src[] = " World";
    strncat(dest, src, sizeof(dest) - strlen(dest) - 1);
    printf("strcat: %s\n", dest);
}

int main() {
    safe_strncpy();
    safe_snprintf();
    safe_strcat();
    return 0;
}

实战技巧:

  • 永远不要使用 strcpy,改用 strncpysnprintf
  • 使用 strncat 时注意目标缓冲区大小
  • 字符串操作后手动添加终止符 '\0'
  • 使用 sizeof 而不是硬编码大小

3.2 字符串解析与处理

高频考点:字符串分割、转换

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

// 安全的字符串分割(线程安全版本)
void tokenize_safe() {
    char input[] = "apple,banana,cherry,date";
    char *token;
    char *saveptr;  // strtok_r的上下文指针
    
    token = strtok_r(input, ",", &saveptr);
    while (token != NULL) {
        printf("Token: %s\n", token);
        token = strtok_r(NULL, ",", &saveptr);
    }
}

// 字符串转整数(带错误检查)
int safe_atoi(const char *str, int *result) {
    if (str == NULL || result == NULL) return -1;
    
    char *endptr;
    long val = strtol(str, &endptr, 10);
    
    // 检查转换是否成功
    if (endptr == str) return -1;  // 无数字
    if (*endptr != '\0') return -1;  // 后面有非数字字符
    if (val > INT_MAX || val < INT_MIN) return -1;  // 溢出
    
    *result = (int)val;
    return 0;
}

// 字符串查找与替换
void replace_substring() {
    char str[] = "The quick brown fox";
    char *pos = strstr(str, "brown");
    if (pos) {
        // 注意:这只是一个简单示例,实际替换需要处理内存
        memcpy(pos, "red   ", 6);
        printf("Replaced: %s\n", str);
    }
}

int main() {
    tokenize_safe();
    
    int result;
    if (safe_atoi("12345", &result) == 0) {
        printf("Converted: %d\n", result);
    }
    
    replace_substring();
    return 0;
}

实战技巧:

  • strtok 不是线程安全的,使用 strtok_rstrtok_s
  • 字符串转换函数必须检查返回值和错误条件
  • 使用 strstr 查找子串时注意空指针检查
  • 字符串操作前检查缓冲区大小

3.3 字符串与内存操作对比

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

void compare_operations() {
    char str1[20] = "Hello";
    char str2[20] = "World";
    
    // 字符串操作(遇到'\0'停止)
    strcat(str1, str2);  // str1 = "HelloWorld"
    printf("strcat: %s\n", str1);
    
    // 内存操作(按字节操作)
    char mem1[20] = "Hello";
    char mem2[20] = "World";
    memcpy(mem1 + 5, mem2, 5);  // 不考虑'\0'
    printf("memcpy: %s\n", mem1);  // 可能没有终止符
    
    // 安全的内存操作
    char safe1[20] = "Hello";
    char safe2[20] = "World";
    memcpy(safe1 + 5, safe2, 5);
    safe1[10] = '\0';  // 手动添加终止符
    printf("safe memcpy: %s\n", safe1);
}

int main() {
    compare_operations();
    return 0;
}

四、结构体与位操作

4.1 结构体内存对齐

高频考点:结构体大小计算

#include <stdio.h>

struct S1 {
    char a;      // 1字节
    int b;       // 4字节(需要3字节填充)
    char c;      // 1字节(需要3字节填充)
};  // 总大小:1+3+4+1+3 = 12字节

struct S2 {
    int b;       // 4字节
    char a;      // 1字节
    char c;      // 1字节
    // 2字节填充
};  // 总大小:4+1+1+2 = 8字节

struct S3 {
    char a;
    char b;
    char c;
    int d;
};  // 总大小:1+1+1+1(填充)+4 = 8字节

// 手动控制对齐
#pragma pack(push, 1)
struct S4 {
    char a;      // 1字节
    int b;       // 4字节
    char c;      // 1字节
};  // 总大小:1+4+1 = 6字节
#pragma pack(pop)

void print_size_info() {
    printf("S1 size: %zu\n", sizeof(struct S1));  // 12
    printf("S2 size: %zu\n", sizeof(struct S2));  // 8
    printf("S3 size: %zu\n", sizeof(struct S3));  // 8
    printf("S4 size: %zu\n", sizeof(struct S4));  // 6
    
    // 打印成员偏移
    printf("S1.a offset: %zu\n", offsetof(struct S1, a));  // 0
    printf("S1.b offset: %zu\n", offsetof(struct S1, b));  // 4
    printf("S1.c offset: %zu\n", offsetof(struct S1, c));  // 8
}

int main() {
    print_size_info();
    return 0;
}

实战技巧:

  • 结构体成员按声明顺序存储
  • 编译器会插入填充字节以满足对齐要求
  • 优化结构体内存:按大小降序排列成员
  • 使用 #pragma pack 控制对齐(注意移植性)

4.2 位操作与位域

高频考点:位运算实现

#include <stdio.h>

// 位操作函数
void bit_operations() {
    unsigned int x = 0b10101010;  // 170
    
    printf("Original: %u (0x%X)\n", x, x);
    
    // 设置位(置1)
    x |= (1 << 3);  // 设置第3位
    printf("Set bit 3: %u (0x%X)\n", x, x);
    
    // 清除位(置0)
    x &= ~(1 << 5);  // 清除第5位
    printf("Clear bit 5: %u (0x%X)\n", x, x);
    
    // 切换位(翻转)
    x ^= (1 << 7);  // 切换第7位
    printf("Toggle bit 7: %u (0x%X)\n", x, x);
    
    // 检查位
    if (x & (1 << 3)) {
        printf("Bit 3 is set\n");
    }
    
    // 提取位段
    unsigned int bitfield = (x >> 2) & 0x03;  // 提取第2-3位
    printf("Bitfield (bits 2-3): %u\n", bitfield);
}

// 位域示例
struct Flags {
    unsigned int is_valid : 1;
    unsigned int priority : 3;
    unsigned int type : 4;
    unsigned int reserved : 24;
};

void bitfield_example() {
    struct Flags f = {0};
    f.is_valid = 1;
    f.priority = 5;  // 二进制101
    f.type = 10;     // 二进制1010
    
    printf("Flags size: %zu bytes\n", sizeof(f));  // 4
    printf("is_valid: %u\n", f.is_valid);
    printf("priority: %u\n", f.priority);
    printf("type: %u\n", f.type);
}

// 实战:检查是否是2的幂
int is_power_of_two(unsigned int n) {
    return n && !(n & (n - 1));
}

// 实战:计算汉明重量(1的个数)
int count_bits(unsigned int n) {
    int count = 0;
    while (n) {
        n &= (n - 1);  // 清除最低位的1
        count++;
    }
    return count;
}

int main() {
    bit_operations();
    bitfield_example();
    
    printf("Is 16 power of two? %s\n", is_power_of_two(16) ? "Yes" : "No");
    printf("Bits in 0b10101010: %d\n", count_bits(0b10101010));
    
    return 0;
}

实战技巧:

  • 位运算优先级较低,建议加括号
  • 无符号整数避免符号位问题
  • 位域节省空间但移植性差
  • 常用技巧:x & (x-1) 清除最低位1

4.3 位图(Bitmap)实现

高频考点:大规模数据去重与排序

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

#define BITMAP_SIZE 10000000  // 1000万

typedef struct {
    unsigned char *bits;
    size_t size;
} Bitmap;

// 初始化位图
Bitmap* bitmap_create(size_t size) {
    Bitmap *bm = malloc(sizeof(Bitmap));
    if (!bm) return NULL;
    
    bm->size = (size + 7) / 8;  // 计算需要的字节数
    bm->bits = calloc(bm->size, 1);
    if (!bm->bits) {
        free(bm);
        return NULL;
    }
    return bm;
}

// 设置位
void bitmap_set(Bitmap *bm, size_t pos) {
    if (!bm || pos >= bm->size * 8) return;
    bm->bits[pos / 8] |= (1 << (pos % 8));
}

// 清除位
void bitmap_clear(Bitmap *bm, size_t pos) {
    if (!bm || pos >= bm->size * 8) return;
    bm->bits[pos / 8] &= ~(1 << (pos % 8));
}

// 检查位
int bitmap_test(Bitmap *bm, size_t pos) {
    if (!bm || pos >= bm->size * 8) return 0;
    return (bm->bits[pos / 8] >> (pos % 8)) & 1;
}

// 销毁位图
void bitmap_destroy(Bitmap *bm) {
    if (bm) {
        free(bm->bits);
        free(bm);
    }
}

// 实战:找出1亿个整数中缺失的最小正整数
int find_missing_positive(int *nums, size_t n) {
    // 假设数字范围在[1, n+1]
    Bitmap *bm = bitmap_create(n + 2);
    if (!bm) return -1;
    
    for (size_t i = 0; i < n; i++) {
        if (nums[i] > 0 && nums[i] <= n + 1) {
            bitmap_set(bm, nums[i]);
        }
    }
    
    int missing = 1;
    while (bitmap_test(bm, missing)) {
        missing++;
    }
    
    bitmap_destroy(bm);
    return missing;
}

int main() {
    // 测试位图
    Bitmap *bm = bitmap_create(100);
    bitmap_set(bm, 5);
    bitmap_set(bm, 50);
    printf("Bit 5: %d\n", bitmap_test(bm, 5));  // 1
    printf("Bit 6: %d\n", bitmap_test(bm, 6));  // 0
    bitmap_destroy(bm);
    
    // 测试找缺失数
    int nums[] = {3, 4, -1, 1};
    printf("Missing: %d\n", find_missing_positive(nums, 4));  // 2
    
    return 0;
}

五、高级数据结构

5.1 链表操作

高频考点:链表反转、环检测、合并

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

typedef struct ListNode {
    int val;
    struct ListNode *next;
} ListNode;

// 创建节点
ListNode* create_node(int val) {
    ListNode *node = malloc(sizeof(ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}

// 链表反转(迭代)
ListNode* reverse_list_iterative(ListNode *head) {
    ListNode *prev = NULL;
    ListNode *curr = head;
    ListNode *next = NULL;
    
    while (curr) {
        next = curr->next;  // 保存下一个节点
        curr->next = prev;  // 反转指针
        prev = curr;        // 移动prev
        curr = next;        // 移动curr
    }
    return prev;
}

// 链表反转(递归)
ListNode* reverse_list_recursive(ListNode *head) {
    if (!head || !head->next) return head;
    
    ListNode *new_head = reverse_list_recursive(head->next);
    head->next->next = head;
    head->next = NULL;
    return new_head;
}

// 检测环
int has_cycle(ListNode *head) {
    if (!head) return 0;
    
    ListNode *slow = head;
    ListNode *fast = head;
    
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) return 1;
    }
    return 0;
}

// 合并两个有序链表
ListNode* merge_two_lists(ListNode *l1, ListNode *l2) {
    ListNode dummy = {0, NULL};
    ListNode *tail = &dummy;
    
    while (l1 && l2) {
        if (l1->val <= l2->val) {
            tail->next = l1;
            l1 = l1->next;
        } else {
            tail->next = l2;
            l2 = l2->next;
        }
        tail = tail->next;
    }
    
    tail->next = (l1 ? l1 : l2);
    return dummy.next;
}

// 打印链表
void print_list(ListNode *head) {
    while (head) {
        printf("%d -> ", head->val);
        head = head->next;
    }
    printf("NULL\n");
}

// 释放链表
void free_list(ListNode *head) {
    while (head) {
        ListNode *temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    // 创建链表:1->2->3->4->5
    ListNode *head = create_node(1);
    head->next = create_node(2);
    head->next->next = create_node(3);
    head->next->next->next = create_node(4);
    head->next->next->next->next = create_node(5);
    
    printf("Original: ");
    print_list(head);
    
    // 反转
    ListNode *reversed = reverse_list_iterative(head);
    printf("Reversed: ");
    print_list(reversed);
    
    // 环检测
    ListNode *cycle_node = create_node(1);
    cycle_node->next = create_node(2);
    cycle_node->next->next = create_node(3);
    cycle_node->next->next->next = cycle_node;  // 创建环
    printf("Has cycle: %s\n", has_cycle(cycle_node) ? "Yes" : "No");
    
    // 合并链表
    ListNode *l1 = create_node(1);
    l1->next = create_node(3);
    l1->next->next = create_node(5);
    
    ListNode *l2 = create_node(2);
    l2->next = create_node(4);
    l2->next->next = create_node(6);
    
    ListNode *merged = merge_two_lists(l1, l2);
    printf("Merged: ");
    print_list(merged);
    
    // 释放内存
    free_list(reversed);
    free_list(cycle_node);
    free_list(merged);
    
    return 0;
}

实战技巧:

  • 链表操作注意空指针检查
  • 反转链表时保存next节点
  • 快慢指针检测环时,fast每次走2步,slow走1步
  • 合并链表使用dummy节点简化操作

5.2 二叉树

高频考点:遍历、高度、镜像

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

typedef struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *1right;
} TreeNode;

// 创建节点
TreeNode* create_tree_node(int val) {
    TreeNode *node = malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 二叉树高度
int tree_height(TreeNode *root) {
    if (!root) return 0;
    int left = tree_height(root->left);
    int right = tree_height(root->right);
    return 1 + (left > right ? left : right);
}

// 镜像二叉树
void mirror_tree(TreeNode *root) {
    if (!root) return;
    
    // 交换左右子树
    TreeNode *temp = root->left;
    root->left = root->right;
    root->right = temp;
    
    mirror_tree(root->left);
    mirror_tree(root->right);
}

// 前序遍历
void preorder(TreeNode *root) {
    if (!root) return;
    printf("%d ", root->val);
    preorder(root->left);
    preorder(root->right);
}

// 中序遍历
void inorder(TreeNode *root) {
    if (!root) return;
    inorder(root->left);
    printf("%d ", root->val);
    inorder(root->right);
}

// 后序遍历
void postorder(TreeNode *root) {
    if (!root) return;
    postorder(root->left);
    postorder(root->r1ight);
    printf("%d ", root->val);
}

// 层序遍历(使用队列)
#define QUEUE_SIZE 100

typedef struct {
    TreeNode **data;
    int front;
    int rear;
    int size;
} Queue;

Queue* queue_create(int size) {
    Queue *q = malloc(sizeof(Queue));
    q->data = malloc(sizeof(TreeNode*) * size);
    q->front = q->rear = 0;
    q->size = size;
    return q;
}

void queue_push(Queue *q, TreeNode *node) {
    if ((q->rear + 1) % q->size == q->front) return;  // 队满
    q->data[q->rear] = node;
    q->rear = (q->rear + 1) % q->size;
}

TreeNode* queue_pop(Queue *q) {
    if (q->front == q->rear) return NULL;  // 队空
    TreeNode *node = q->data[q->front];
    q->front = (q->front + 1) % q->size;
    return node;
}

int queue_empty(Queue *q) {
    return q->front == q->rear;
}

void levelorder(TreeNode *root) {
    if (!root) return;
    
    Queue *q = queue_create(QUEUE_SIZE);
    queue_push(q, root);
    
    while (!queue_empty(q)) {
        TreeNode *node = queue_pop(q);
        printf("%d ", node->val);
        
        if (node->left) queue_push(q, node->left);
        if (node->right) queue_push(q, node->right);
    }
    
    free(q->data);
    free(q);
}

// 释放树
void free_tree(TreeNode *root) {
    if (!root) return;
    free_tree(root->left);
    free_tree(root->right);
    free(root);
}

int main() {
    // 创建树:    1
    //            / \
    //           2   3
    //          / \
    //         4   5
    TreeNode *root = create_tree_node(1);
    root->left = create_tree_node(2);
    root->right = create_tree_node(3);
    root->left->left = create_tree_node(4);
    root->left->right = create_tree_node(5);
    
    printf("Height: %d\n", tree_height(root));  // 3
    
    printf("Preorder: ");
    preorder(root);
    printf("\n");
    
    printf("Inorder: ");
    inorder(root);
    printf("\n");
    
    printf("Postorder: ");
    postorder(root);
    printf("\n");
    
    printf("Levelorder: ");
    levelorder(root);
    printf("\n");
    
    // 镜像
    mirror_tree(root);
    printf("Mirror inorder: ");
    inorder(root);
    printf("\n");
    
    free_tree(root);
    return 0;
}

实战技巧:

  • 递归遍历注意基准情况
  • 层序遍历使用队列
  • 树操作注意内存释放
  • 理解前序、中序、后序遍历的区别

5.3 堆(优先队列)

高频考点:Top K问题、堆排序

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

// 最小堆
typedef struct {
    int *data;
    int size;
    int capacity;
} MinHeap;

MinHeap* heap_create(int capacity) {
    MinHeap *heap = malloc(sizeof(MinHeap));
    heap->data = malloc(sizeof(int) * (capacity + 1));
    heap->size = 0;
    heap->capacity = capacity;
    return heap;
}

void heap_swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void heapify_up(MinHeap *heap, int idx) {
    while (idx > 1) {
        int parent = idx / 2;
        if (heap->data[parent] > heap->data[idx]) {
            heap_swap(&heap->data[parent], &heap->data[idx]);
            idx = parent;
        } else {
            break;
        }
    }
}

void heapify_down(MinHeap *heap, int idx) {
    while (idx * 2 <= heap->size) {
        int child = idx * 2;
        if (child + 1 <= heap->size && heap->data[child + 1] < heap->data[child]) {
            child++;
        }
        if (heap->data[idx] > heap->data[child]) {
            heap_swap(&heap->data[idx], &heap->data[child]);
            idx = child;
        } else {
            break;
        }
    }
}

void heap_push(MinHeap *heap, int val) {
    if (heap->size >= heap->capacity) return;
    heap->data[++heap->size] = val;
    heapify_up(heap, heap->size);
}

int heap_pop(MinHeap *heap) {
    if (heap->size == 0) return -1;
    int min_val = heap->data[1];
    heap->data[1] = heap->data[heap->size--];
    heapify_down(heap, 1);
    return min_val;
}

int heap_top(MinHeap *heap) {
    return heap->size > 0 ? heap->data[1] : -1;
}

// Top K问题:找出数组中最大的K个数
void find_top_k(int *arr, int n, int k) {
    MinHeap *heap = heap_create(k);
    
    // 建立大小为K的最小堆
    for (int i = 0; i < k; i++) {
        heap_push(heap, arr[i]);
    }
    
    // 维护大小为K的堆
    for (int i = k; i < n; i++) {
        if (arr[i] > heap_top(heap)) {
            heap_pop(heap);
            heap_push(heap, arr[i]);
        }
    }
    
    printf("Top %d elements: ", k);
    while (heap->size > 0) {
        printf("%d ", heap_pop(heap));
    }
    printf("\n");
    
    free(heap->data);
    free(heap);
}

int main() {
    int arr[] = {3, 2, 1, 5, 6, 4};
    find_top_k(arr, 6, 3);  // Top 3: 4 5 6
    
    return 0;
}

六、函数与递归

6.1 递归优化:尾递归与迭代

高频考点:递归转迭代

#include <stdio.h>

// 斐波那契数列:递归(低效)
int fib_recursive(int n) {
    if (n <= 1) return n;
    return fib_recursive(n-1) + fib_recursive(n-2);
}

// 斐波那契数列:迭代(高效)
int fib_iterative(int n) {
    if (n <= 1) return n;
    int a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        int temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}

// 斐波那契数列:动态规划(记忆化)
int fib_dp(int n) {
    if (n <= 1) return n;
    int *dp = malloc((n + 1) * sizeof(int));
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
    }
    int result = dp[n];
    free(dp);
    return result;
}

// 尾递归版本
int fib_tail_recursive(int n, int a, int b) {
    if (n == 0) return a;
    if (n == 1) return b;
    return fib_tail_recursive(n - 1, b, a + b);
}

// 汉诺塔问题
void hanoi(int n, char from, char to, char aux) {
    if (n == 1) {
        printf("Move disk 1 from %c to %c\n", from, to);
        return;
    }
    hanoi(n - 1, from, aux, to);
    printf("Move disk %d from %c to %c\n", n, from, to);
    hanoi(n - 1, aux, to, from);
}

// 递归转迭代:汉诺塔
void hanoi_iterative(int n) {
    // 使用栈模拟递归
    // 实际实现较复杂,这里仅展示思路
    printf("Iterative Hanoi not implemented for brevity\n");
}

int main() {
    int n = 10;
    printf("Fib %d (recursive): %d\n", n, fib_recursive(n));
    printf("Fib %d (iterative): %d\n", n, fib_iterative(n));
    printf("Fib %d (DP): %d\n", n, fib_dp(n));
    printf("Fib %d (tail): %d\n", n, fib_tail_recursive(n, 0, 1));
    
    printf("\nHanoi(3):\n");
    hanoi(3, 'A', 'C', 'B');
    
    return 0;
}

实战技巧:

  • 递归注意栈溢出风险
  • 尾递归可被编译器优化为迭代
  • 动态规划避免重复计算
  • 递归转迭代使用栈数据结构

6.2 可变参数函数

高频考点:va_list、va_start、va_arg

#include <stdio.h>
#include <stdarg.h>

// 计算可变参数的平均值
double average(int count, ...) {
    va_list args;
    va_start(args, count);
    
    double sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(args, int);
    }
    
    va_end(args);
    return sum / count;
}

// 自定义printf(简化版)
void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);
    
    while (*format) {
        if (*format == '%') {
            format++;
            switch (*format) {
                case 'd': {
                    int val = va_arg(args, int);
                    printf("%d", val);
                    break;
                }
                case 's': {
                    char *val = va_arg(args, char*);
                    printf("%s", val);
                    break;
                case 'f': {
                    double val = va_arg(args, double);
                    printf("%f", val);
                    break;
                }
                default:
                    putchar(*format);
            }
        } else {
            putchar(*format);
        }
        format++;
    }
    
    va_end(args);
}

// 计算可变参数的最大值
int max(int count, ...) {
    if (count == 0) return 0;
    
    va_list args;
    va_start(args, count);
    
    int max_val = va_arg(args, int);
    for (int i = 1; i < count; i++) {
        int val = va_arg(args, int);
        if (val > max_val) max_val = val;
    }
    
    va_end(args);
    return max_val;
}

int main() {
    printf("Average: %.2f\n", average(5, 1, 2, 3, 4, 5));
    my_printf("Name: %s, Age: %d, Score: %f\n", "Alice", 25, 95.5);
    printf("Max: %d\n", max(5, 3, 7, 2, 9, 1));
    
    return 0;
}

实战技巧:

  • 必须使用 va_startva_end 配对
  • va_arg 需要指定类型
  • 可变参数函数需要至少一个固定参数
  • 注意类型匹配和参数顺序

七、文件操作与系统编程

7.1 文件操作

高频考点:文件读写、错误处理

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

void file_operations() {
    // 写入文件
    FILE *fp = fopen("test.txt", "w");
    if (!fp) {
        perror("fopen");
        return;
    }
    
    fprintf(fp, "Hello, ByteDance!\n");
    fprintf(fp, "C Language Test\n");
    fclose(fp);
    
    // 读取文件
    fp = fopen("test.txt", "r");
    if (!fp) {
        perror("fopen");
        return;
    }
    
    char buffer[100];
    while (fgets(buffer, sizeof(buffer), fp)) {
        printf("Read: %s", buffer);
    }
    fclose(fp);
    
    // 二进制读写
    int data[] = {1, 2, 3, 4, 5};
    fp = fopen("data.bin", "wb");
    fwrite(data, sizeof(int), 5, fp);
    fclose(fp);
    
    fp = fopen("data.bin", "rb");
    int read_data[5];
    fread(read_data, sizeof(int), 5, fp);
    fclose(fp);
    
    printf("Binary read: ");
    for (int i = 0; i < 5; i++) printf("%d ", read_data[i]);
    printf("\n");
}

// 文件复制(带错误处理)
int file_copy(const char *src, const char *dest) {
    FILE *fps = fopen(src, "rb");
    if (!fps) {
        perror("fopen src");
        return -1;
    }
    
    FILE *fpd = fopen(dest, "wb");
    if (!fpd) {
        perror("fopen dest");
        fclose(fps);
        return -1;
    }
    
    char buffer[4096];
    size_t bytes;
    while ((bytes = fread(buffer, 1, sizeof(buffer), fps)) > 0) {
        if (fwrite(buffer, 1, bytes, fpd) != bytes) {
            perror("fwrite");
            fclose(fps);
            fclose(fpd);
            return -1;
        }
    }
    
    fclose(fps);
    fclose(fpd);
    return 0;
}

int main() {
    file_operations();
    file_copy("test.txt", "test_copy.txt");
    return 0;
}

实战技巧:

  • 总是检查文件指针是否为NULL
  • 使用 perror 输出错误信息
  • 二进制模式用 “rb”/“wb”,文本模式用 “r”/“w”
  • 注意 fread/fwrite 的返回值

7.2 内存映射文件

高频考点:mmap使用

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

// 内存映射文件读写
void mmap_example() {
    const char *file = "mmap_test.txt";
    const char *content = "Hello mmap!";
    
    // 创建文件
    int fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open");
        return;
    }
    
    // 写入数据
    write(fd, content, strlen(content));
    close(fd);
    
    // 重新打开用于映射
    fd = open(file, O_RDWR);
    if (fd < 0) {
        perror("open");
        return;
    }
    
    // 获取文件大小
    struct stat sb;
    if (fstat(fd, &sb) < 0) {
        perror("fstat");
        close(fd);
        return;
    }
    
    // 内存映射
    char *mapped = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return;
    }
    
    // 读取和修改
    printf("Original: %s\n", mapped);
    memcpy(mapped, "Modified", 8);
    printf("Modified: %s\n", mapped);
    
    // 解除映射
    munmap(mapped, sb.st_size);
    close(fd);
}

int main() {
    mmap_example();
    return 0;
}

八、并发编程

8.1 线程基础

高频考点:线程创建、同步

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 线程函数
void* thread_function(void *arg) {
    int *num = (int*)arg;
    printf("Thread %d started\n", *num);
    sleep(1);
    printf("Thread %d finished\n", *num);
    return NULL;
}

// 线程创建
void thread_example() {
    pthread_t threads[3];
    int thread_nums[3] = {1, 2, 3};
    
    for (int i = 0; i < 3; i++) {
        if (pthread_create(&threads[i], NULL, thread_function, &thread_nums[i]) != 0) {
            perror("pthread_create");
            return;
        }
    }
    
    // 等待线程结束
    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }
    
    printf("All threads completed\n");
}

// 互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;

void* increment(void *arg) {
    for (int i = 0; i < 100000; i++) {
        pthread_mutex_lock(&mutex);
        shared_counter++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void mutex_example() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("Counter: %d (expected: 200000)\n", shared_counter);
}

// 条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;

void* producer(void *arg) {
    sleep(2);
    pthread_mutex_lock(&mutex);
    ready = 1;
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    printf("Producer: data ready\n");
    return NULL;
}

void* consumer(void *arg) {
    pthread_mutex_lock(&mutex);
    while (!ready) {
        pthread_cond_wait(&cond, &mutex);
    }
    pthread_mutex_unlock(&mutex);
    printf("Consumer: data consumed\n");
    return NULL;
}

void cond_example() {
    pthread_t prod, cons;
    
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_create(&prod, NULL, producer, NULL);
    
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
}

int main() {
    thread_example();
    mutex_example();
    cond_example();
    return 0;
}

实战技巧:

  • 线程函数返回类型必须是 void*
  • pthread_join 等待线程结束
  • 互斥锁保护共享资源
  • 条件变量用于线程间通信

8.2 原子操作

高频考点:__sync系列函数

#include <stdio.h>
#include <pthread.h>

// 原子操作计数器
int atomic_counter = 0;

void* atomic_increment(void *arg) {
    for (int i = 0; i < 100000; i++) {
        __sync_fetch_and_add(&atomic_counter, 1);
    }
    return NULL;
}

void atomic_example() {
    pthread_t t1, t2;
    
    pthread_create(&t1, NULL, atomic_increment, NULL);
    pthread_create(&t2, NULL, atomic_increment, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    
    printf("Atomic counter: %d\n", atomic_counter);
}

// 原子比较交换
int atomic_compare_swap(int *ptr, int oldval, int newval) {
    return __sync_bool_compare_and_swap(ptr, oldval, newval);
}

void cas_example() {
    int value = 10;
    int expected = 10;
    int newval = 20;
    
    if (atomic_compare_swap(&value, expected, newval)) {
        printf("CAS success: value = %d\n", value);
    }
    
    // 失败的情况
    if (!atomic_compare_swap(&value, expected, newval)) {
        printf("CAS failed: value = %d\n", value);
    }
}

int main() {
    atomic_example();
    cas_example();
    return 0;
}

九、算法与数据结构实战

9.1 排序算法

高频考点:快速排序、归并排序

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

// 快速排序
int partition(int *arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    
    int temp = arr[i + 1];
    arr[i + 1] = arr[high];
    arr[high] = temp;
    
    return i + 1;
}

void quick_sort(int *arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quick_sort(arr, low, pi - 1);
        quick_sort(arr, pi + 1, high);
    }
}

// 归并排序
void merge(int *arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;
    
    int *L = malloc(n1 * sizeof(int));
    int *R = malloc(n2 * sizeof(int));
    
    for (int i = 0; i < n1; i++) L[i] = arr[left + i];
    for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];
    
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k++] = L[i++];
        } else {
            arr[k++] = R[j++];
        }
    }
    
    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
    
    free(L);
    free(R);
}

void merge_sort(int *arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;
        merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }
}

// 堆排序
void heapify(int *arr, int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    
    if (left < n && arr[left] > arr[largest]) largest = left;
    if (right < n && arr[right] > arr[largest]) largest = right;
    
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

void heap_sort(int *arr, int n) {
    // 建堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }
    
    // 排序
    for (int i = n - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        heapify(arr, i, 0);
    }
}

void print_array(int *arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr1[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr1) / sizeof(arr1[0]);
    
    printf("Original: ");
    print_array(arr1, n);
    
    quick_sort(arr1, 0, n - 1);
    printf("Quick sort: ");
    print_array(arr1, n);
    
    int arr2[] = {12, 11, 13, 5, 6, 7};
    int n2 = sizeof(arr2) / sizeof(arr2[0]);
    
    merge_sort(arr2, 0, n2 - 1);
    printf("Merge sort: ");
    print_array(arr2, n2);
    
    int arr3[] = {12, 11, 13, 5, 6, 7};
    heap_sort(arr3, n2);
    printf("Heap sort: ");
    print_array(arr3, n2);
    
    return 0;
}

实战技巧:

  • 快速排序最坏情况O(n²),平均O(n log n)
  • 归并排序稳定,适合链表排序
  • 堆排序不稳定,但空间复杂度O(1)
  • 注意递归深度和栈溢出

9.2 查找算法

高频考点:二分查找、哈希表

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

// 二分查找
int binary_search(int *arr, int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

// 二分查找变体:查找第一个等于target的位置
int binary_search_first(int *arr, int n, int target) {
    int left = 0, right = n - 1;
    int result = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) {
            result = mid;
            right = mid - 1;  // 继续向左找
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return result;
}

// 哈希表(简单实现)
#define TABLE_SIZE 100

typedef struct HashNode {
    int key;
    int value;
    struct HashNode *next;
} HashNode;

typedef struct {
    HashNode **buckets;
    int size;
} HashTable;

HashTable* hash_create(int size) {
    HashTable *ht = malloc(sizeof(HashTable));
    ht->size = size;
    ht->buckets = calloc(size, sizeof(HashNode*));
    return ht;
}

int hash_function(int key, int size) {
    return abs(key) % size;
}

void hash_insert(HashTable *ht, int key, int value) {
    int index = hash_function(key, ht->size);
    HashNode *node = malloc(sizeof(HashNode));
    node->key = key;
    node->value = value;
    node->next = ht->buckets[index];
    ht->buckets[index] = node;
}

int hash_get(HashTable *ht, int key) {
    int index = hash_function(key, ht->size);
    HashNode *node = ht->buckets[index];
    while (node) {
        if (node->key == key) return node->value;
        node = node->next;
    }
    return -1;  // 未找到
}

void hash_free(HashTable *ht) {
    for (int i = 0; i < ht->size; i++) {
        HashNode *node = ht->buckets[i];
        while (node) {
            HashNode *temp = node;
            node = node->next;
            free(temp);
        }
    }
    free(ht->buckets);
    free(ht);
}

int main() {
    // 二分查找
    int arr[] = {1, 3, 5, 7, 9, 11, 13, 15};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("Binary search 7: %d\n", binary_search(arr, n, 7));
    printf("Binary search 8: %d\n", binary_search(arr, n, 8));
    
    // 哈希表
    HashTable *ht = hash_create(TABLE_SIZE);
    hash_insert(ht, 100, 1);
    hash_insert(ht, 200, 2);
    hash_insert(ht, 300, 3);
    
    printf("Hash get 100: %d\n", hash_get(ht, 100));
    printf("Hash get 200: %d\n", hash_get(ht, 200));
    printf("Hash get 400: %d\n", hash_get(ht, 400));
    
    hash_free(ht);
    return 0;
}

十、字节跳动笔试实战技巧

10.1 时间与空间复杂度分析

高频考点:复杂度计算与优化

// 示例:分析以下代码的时间复杂度
void example_analysis() {
    // O(n) - 线性时间
    for (int i = 0; i < n; i++) {
        // O(1) 操作
    }
    
    // O(n²) - 二次时间
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            // O(1) 操作
        }
    }
    
    // O(log n) - 对数时间
    int i = 1;
    while (i < n) {
        i *= 2;
    }
    
    // O(n log n) - 线性对数时间
    for (int i = 1; i < n; i *= 2) {
        for (int j = 0; j < n; j++) {
            // O(1) 操作
        }
    }
}

实战技巧:

  • 嵌套循环:复杂度相乘
  • 递归:分析递归树深度和分支数
  • 分治:O(n log n)
  • 动态规划:状态数 × 每个状态的计算时间

10.2 边界条件检查

高频考点:空指针、溢出、边界值

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

// 安全的整数加法
int safe_add(int a, int b, int *result) {
    if ((b > 0 && a > INT_MAX - b) ||
        (b < 0 && a < INT_MIN - b)) {
        return -1;  // 溢出
    }
    *result = a + b;
    return 0;
}

// 安全的整数乘法
int safe_multiply(int a, int b, int *result) {
    if (a == 0 || b == 0) {
        *result = 0;
        return 0;
    }
    
    if (a > 0) {
        if (b > 0) {
            if (a > INT_MAX / b) return -1;
        } else {
            if (b < INT_MIN / a) return -1;
        }
    } else {
        if (b > 0) {
            if (a < INT_MIN / b) return -1;
        } else {
            if (a < INT_MAX / b) return -1;
        }
    }
    
    *result = a * b;
    return 0;
}

// 边界测试
void boundary_test() {
    int result;
    
    // 加法溢出
    if (safe_add(INT_MAX, 1, &result) == -1) {
        printf("INT_MAX + 1 overflow\n");
    }
    
    // 乘法溢出
    if (safe_multiply(INT_MAX, 2, &result) == -1) {
        printf("INT_MAX * 2 overflow\n");
    }
    
    // 空指针检查
    int *p = NULL;
    if (p == NULL) {
        printf("Null pointer detected\n");
    }
    
    // 数组越界
    int arr[5];
    for (int i = 0; i <= 5; i++) {  // 错误:i<=5
        // arr[i] = i;  // 越界访问
    }
}

int main() {
    boundary_test();
    return 0;
}

10.3 调试技巧

高频考点:调试工具使用

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

// 调试宏
#define DEBUG 1

#if DEBUG
#define LOG(fmt, ...) printf("DEBUG: " fmt, ##__VA_ARGS__)
#define ASSERT(cond) \
    if (!(cond)) { \
        printf("ASSERT FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \
        abort(); \
    }
#else
#define LOG(fmt, ...)
#define ASSERT(cond)
#endif

// 调试示例
void debug_example() {
    int *p = malloc(sizeof(int));
    ASSERT(p != NULL);
    
    *p = 10;
    LOG("Value: %d\n", *p);
    
    // 模拟错误
    // ASSERT(1 == 2);
    
    free(p);
}

// 内存泄漏检测(简化版)
void* debug_malloc(size_t size, const char *file, int line) {
    void *ptr = malloc(size);
    printf("malloc: %p at %s:%d\n", ptr, file, line);
    return ptr;
}

void debug_free(void *ptr, const char *file, int line) {
    printf("free: %p at %s:%d\n", ptr, file, line);
    free(ptr);
}

#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)

int main() {
    debug_example();
    return 0;
}

十一、字节跳动笔试高频题型总结

11.1 高频题型分类

  1. 字符串处理(30%)

    • 字符串分割、解析
    • 正则表达式匹配
    • 字符串转换
  2. 内存管理(25%)

    • 内存分配与释放
    • 内存泄漏检测
    • 自定义内存池
  3. 链表操作(20%)

    • 反转、环检测、合并
    • 复杂链表操作
  4. 树与图(15%)

    • 遍历、高度、镜像
    • 最短路径、拓扑排序
  5. 位操作(10%)

    • 位图、位运算
    • 状态压缩

11.2 解题模板

链表反转模板:

ListNode* reverse(ListNode *head) {
    ListNode *prev = NULL, *curr = head, *next = NULL;
    while (curr) {
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

二分查找模板:

int binary_search(int *arr, int n, int target) {
    int left = 0, right = n - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

动态规划模板:

int dp[n+1];
dp[0] = base;
for (int i = 1; i <= n; i++) {
    dp[i] = dp[i-1];  // 状态转移
}
return dp[n];

十二、总结与建议

12.1 备考建议

  1. 基础扎实:熟练掌握指针、内存管理、字符串操作
  2. 代码规范:注意边界检查、错误处理
  3. 复杂度分析:能够快速分析时间空间复杂度
  4. 调试能力:熟练使用调试工具
  5. 刷题策略:先易后难,注重质量而非数量

12.2 推荐练习资源

  • LeetCode C语言题库
  • 牛客网C语言专项练习
  • 《C专家编程》
  • 《C陷阱与缺陷》

12.3 笔试注意事项

  1. 仔细审题:理解题意,注意输入输出格式
  2. 边界条件:空输入、最大值、最小值
  3. 内存泄漏:确保所有malloc都有free
  4. 代码风格:缩进、命名规范
  5. 测试用例:多准备几个测试用例验证

通过系统学习和大量练习,相信你一定能在字节跳动的C语言笔试中取得好成绩!祝你成功!