C语言开发工程师 笔试题
考试时间: 120分钟 总分: 100分

第一部分:选择题 (每题3分,共30分)
-
sizeof和strlen,以下说法正确的是? A.sizeof是一个函数,strlen是一个运算符。 B.sizeof计算的是类型或变量所占的内存字节数,strlen计算的是字符串的长度(不包括结尾的\0)。 C. 对于一个空字符串char str[] = "";,sizeof(str)的结果是0。 D.sizeof的结果在编译时确定,而strlen的结果在运行时确定。 -
在32位系统上,
sizeof("Hello, World!")的值是? A. 11 B. 12 C. 13 D. 取决于编译器 -
以下关于
const的描述,错误的是? A.const int *p;表示p指向的内容不能被修改,但p本身可以指向其他地址。 B.int * const p;表示p本身是常量,不能指向其他地址,但指向的内容可以被修改。 C.const int * const p;表示p和它指向的内容都不能被修改。 D.const修饰的变量必须在声明时初始化。 -
以下代码的输出是什么?
(图片来源网络,侵删)#include <stdio.h> int main() { int a = 5; int b = 10; int *p = &a; int *q = &b; p = q; *p = 20; printf("%d, %d\n", a, b); return 0; }A. 5, 10 B. 5, 20 C. 20, 10 D. 20, 20
-
对于结构体
struct S { char c; int i; };,在大多数32位系统上,sizeof(struct S)的值最可能是? A. 5 B. 7 C. 8 D. 12 -
以下哪个宏定义是安全的(不会产生副作用)? A.
#define MAX(a, b) ((a) > (b) ? (a) : (b))B.#define SQUARE(x) (x * x)C.#define ADD_TO(a, b) (a += b)D.#define STRCPY(d, s) strcpy(d, s) -
以下哪个不是C语言的关键字? A.
externB.typedefC.sizeofD.function
(图片来源网络,侵删) -
在C语言中,以下哪个函数可以用来动态分配一块连续的内存空间? A.
malloc()B.free()C.calloc()D.realloc()E. 以上都是 -
以下代码的输出是什么?
#include <stdio.h> int main() { int i = 0; for (; i < 5; ++i) { if (i == 2) continue; printf("%d ", i); } return 0; }A. 0 1 2 3 4 B. 0 1 3 4 C. 0 1 2 4 D. 0 1 2 3
-
volatile关键字的主要作用是? A. 优化代码,提高运行效率。 B. 声明一个变量是易变的,防止编译器进行不必要的优化。 C. 声明一个变量是只读的。 D. 确保变量在多线程环境下的原子性访问。
第二部分:填空题 (每空2分,共20分)
- 在C语言中,
FILE *是一个指向__类型的指针,用于文件操作。 - 函数指针
int (*func_ptr)(int, char*);表示一个指向__的指针,该函数返回一个int,并接受一个int和一个char*作为参数。 enum Color { RED, GREEN, BLUE };中,RED的默认值是__。- 在Linux环境下,使用
fork()函数创建子进程后,子进程的fork()返回值是__,父进程的fork()返回值是__。 - 为了避免头文件被重复包含,通常使用__宏和__宏进行保护。
strcat函数的作用是__,它要求目标字符串必须有足够的空间以容纳连接后的结果,否则会导致__。static关键字修饰局部变量时,会使该变量的生命周期__(延长/缩短)为整个程序的生命周期。
第三部分:简答题 (每题10分,共20分)
- 简述C语言中
struct和union的区别。 - 简述C语言中
malloc和calloc的主要区别。
第四部分:编程题 (共30分)
1:实现一个简单的字符串反转函数 (10分)*
请编写一个函数 `void reverse_string(char str),将输入的字符串str原地反转,要求不能使用任何库函数(如strlen),并确保字符串以\0 2:找出数组中的最大值和次大值 (15分)** 请编写一个函数void find_two_largest(int arr[], int size, int max, int second_max),该函数能从整数数组arr中找出最大值和次大值,并通过指针参数max和second_max返回结果,要求算法的时间复杂度为 O(n)。 3:实现一个单链表的逆序 (5分)** 请用C语言定义一个单链表节点,并编写一个函数struct ListNode reverse_list(struct ListNode head)`,将一个单链表原地逆序。
参考答案与解析
第一部分:选择题
-
B
- 解析:
sizeof是一个运算符,而不是函数,它在编译时计算大小。strlen是一个库函数,它在运行时遍历字符串直到遇到\0来计算长度,选项A错误。sizeof(str)计算的是数组str所占的总空间,包括结尾的\0,所以结果是1,选项C错误。sizeof的结果在编译时确定,但strlen的结果在运行时确定,选项D正确,但B选项的描述更全面和准确,因此B是最佳答案。
- 解析:
-
C
- 解析:
sizeof计算的是字符串字面量所占的内存空间。"Hello, World!"包含11个可见字符和1个字符串结束符\0,总共12个字节,在32位或64位系统上,一个char占1个字节,所以结果是12。
- 解析:
-
D
- 解析:
const修饰的变量可以在声明时初始化,也可以之后通过指针等方式间接修改(const修饰的不是指针本身)。const int a;之后a = 10;是错误的,但int b = 5; const int *p = &b; *p = 10;也是错误的,但全局const变量如果不初始化,编译器会默认初始化为0,所以D的说法过于绝对,是错误的,A、B、C都是对const正确的用法描述。
- 解析:
-
D
- 解析:
p最初指向a。p = q;使得p也指向了b的地址。*p = 20;就是修改p所指向的内容,即修改b的值为20。a的值没有改变,仍然是5。printf打印的是a和b,所以输出是5, 20。注意: 原题有误,*p = 20;修改的是b,所以正确输出应为5, 20,但选项中没有这个答案,最接近的D是笔误,或者原题意图是*q = 20;,我们按标准逻辑分析,正确答案应为 B. 5, 20,如果选项D是5, 20,则选D,这里我们假设题目有笔误,B是正确答案。
- 解析:
-
C
- 解析: 这考察的是内存对齐,为了提高内存访问效率,编译器会进行内存对齐。
char通常对齐到1字节,int通常对齐到4字节,结构体struct S中,char c占1字节,为了使int i能从4字节的边界开始,编译器会在c后面填充3个字节,i占4字节,所以总共是1 + 3 + 4 = 8字节。
- 解析: 这考察的是内存对齐,为了提高内存访问效率,编译器会进行内存对齐。
-
A
- 解析: A选项是安全的,因为它使用了括号,避免了
MAX(a++, b)这样的调用导致a被多次自增,B选项不安全,SQUARE(a+1)会变成a+1 * a+1,结果是a + a + 1,C选项有副作用,调用ADD_TO(a, b)会永久改变a的值,D选项没有做类型检查,不安全。
- 解析: A选项是安全的,因为它使用了括号,避免了
-
D
- 解析:
extern,typedef,sizeof都是C语言的关键字。function不是C语言的关键字。
- 解析:
-
E
- 解析:
malloc分配指定大小的内存。calloc分配指定数量和大小的内存,并初始化为0。realloc重新调整已分配内存的大小。free释放内存,它们都是用于动态内存管理的函数。
- 解析:
-
B
- 解析:
for循环从i=0开始,当i=2时,continue语句会跳过本次循环的剩余部分(即printf),直接进入下一次循环。i=2不会被打印。
- 解析:
-
B
- 解析:
volatile告诉编译器,这个变量的值可能会在编译器未察觉的情况下被改变(硬件、多线程或信号处理程序),编译器不会对其进行优化,每次使用都会从内存中重新读取,以保证数据的“新鲜”。
- 解析:
第二部分:填空题
- FILE (或
struct _IO_FILE) - 一个函数 (或
一个返回int,参数为int和char*的函数) - 0 (枚举成员的默认值从0开始递增)
- 0 (子进程的返回值), 子进程的PID (父进程的返回值)
#ifndef,#define(或#pragma once)- 将源字符串连接到目标字符串的末尾, 缓冲区溢出 (Buffer Overflow)
- 延长
第三部分:简答题
-
struct和union的区别:- 内存占用:
struct是结构体,它的各个成员在内存中是依次存放的,总大小是其所有成员大小之和(考虑对齐)。union是联合体,它的所有成员共享同一块内存空间,总大小是其最大成员的大小。 - 数据访问:
struct的所有成员可以同时被访问和赋值,互不影响。union在任意时刻,只有一个成员是有效的,对其中一个成员的赋值会覆盖其他成员的值。 - 用途:
struct用于将不同类型的数据组合成一个整体,用于描述一个复杂的对象。union用于在不同的数据类型之间“复用”内存,例如需要存储多种类型数据中的一种,或者需要查看同一块内存的不同数据表示(如网络字节序转换)。
- 内存占用:
-
malloc和calloc的区别:- 参数不同:
malloc只有一个参数,即需要分配的内存字节数。calloc有两个参数,分别是元素的个数和每个元素的大小。 - 内存初始化:
malloc分配的内存是未初始化的,里面是随机的垃圾值。calloc分配的内存会被初始化为全0。 - 实现(:
calloc内部通常会先调用malloc分配内存,然后再使用memset或类似函数将这块内存清零。
- 参数不同:
第四部分:编程题
1:实现一个简单的字符串反转函数**
#include <stdio.h>
void reverse_string(char *str) {
if (str == NULL) {
return;
}
char *start = str;
char *end = str;
// 1. 找到字符串的末尾 (不包括 '\0')
while (*end != '\0') {
end++;
}
end--; // end现在指向最后一个字符
// 2. 交换头尾字符,并向中间移动
while (start < end) {
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
int main() {
char str[] = "Hello, World!";
printf("Original: %s\n", str);
reverse_string(str);
printf("Reversed: %s\n", str);
return 0;
}
2:找出数组中的最大值和次大值**
#include <stdio.h>
#include <limits.h> // 为了 INT_MIN
void find_two_largest(int arr[], int size, int *max, int *second_max) {
if (size < 2) {
// 数组元素不足,无法找到两个最大值
*max = *second_max = INT_MIN; // 或设置错误码
return;
}
// 初始化 max 和 second_max
if (arr[0] > arr[1]) {
*max = arr[0];
*second_max = arr[1];
} else {
*max = arr[1];
*second_max = arr[0];
}
// 从第三个元素开始遍历
for (int i = 2; i < size; i++) {
if (arr[i] > *max) {
// 当前元素比最大值还大,更新次大值为原最大值,最大值为当前元素
*second_max = *max;
*max = arr[i];
} else if (arr[i] > *second_max && arr[i] != *max) {
// 当前元素介于最大值和次大值之间,更新次大值
*second_max = arr[i];
}
}
}
int main() {
int arr[] = {12, 35, 1, 10, 34, 1};
int size = sizeof(arr) / sizeof(arr[0]);
int max, second_max;
find_two_largest(arr, size, &max, &second_max);
printf("The largest element is %d\n", max);
printf("The second largest element is %d\n", second_max);
return 0;
}
3:实现一个单链表的逆序**
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* reverse_list(struct ListNode *head) {
struct ListNode *prev = NULL;
struct ListNode *current = head;
struct ListNode *next = NULL;
while (current != NULL) {
// 保存下一个节点
next = current->next;
// 将当前节点的next指向前一个节点
current->next = prev;
// prev 和 current 向后移动
prev = current;
current = next;
}
// 当循环结束时,prev指向新的头节点
return prev;
}
// 辅助函数:创建链表
struct ListNode* create_list(int arr[], int size) {
if (size == 0) return NULL;
struct ListNode *head = (struct ListNode*)malloc(sizeof(struct ListNode));
head->val = arr[0];
struct ListNode *current = head;
for (int i = 1; i < size; i++) {
current->next = (struct ListNode*)malloc(sizeof(struct ListNode));
current = current->next;
current->val = arr[i];
current->next = NULL;
}
return head;
}
// 辅助函数:打印链表
void print_list(struct ListNode *head) {
struct ListNode *current = head;
while (current != NULL) {
printf("%d -> ", current->val);
current = current->next;
}
printf("NULL\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);
struct ListNode *head = create_list(arr, size);
printf("Original list: ");
print_list(head);
head = reverse_list(head);
printf("Reversed list: ");
print_list(head);
return 0;
}
