引言
字节跳动作为国内顶级互联网公司,其技术笔试以难度高、考察全面著称。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 **p与int *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,改用strncpy或snprintf - 使用
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_r或strtok_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_start和va_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 高频题型分类
字符串处理(30%)
- 字符串分割、解析
- 正则表达式匹配
- 字符串转换
内存管理(25%)
- 内存分配与释放
- 内存泄漏检测
- 自定义内存池
链表操作(20%)
- 反转、环检测、合并
- 复杂链表操作
树与图(15%)
- 遍历、高度、镜像
- 最短路径、拓扑排序
位操作(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 备考建议
- 基础扎实:熟练掌握指针、内存管理、字符串操作
- 代码规范:注意边界检查、错误处理
- 复杂度分析:能够快速分析时间空间复杂度
- 调试能力:熟练使用调试工具
- 刷题策略:先易后难,注重质量而非数量
12.2 推荐练习资源
- LeetCode C语言题库
- 牛客网C语言专项练习
- 《C专家编程》
- 《C陷阱与缺陷》
12.3 笔试注意事项
- 仔细审题:理解题意,注意输入输出格式
- 边界条件:空输入、最大值、最小值
- 内存泄漏:确保所有malloc都有free
- 代码风格:缩进、命名规范
- 测试用例:多准备几个测试用例验证
通过系统学习和大量练习,相信你一定能在字节跳动的C语言笔试中取得好成绩!祝你成功!
