本文主要是介绍中高级软件工程师的c语言面试题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 问题1:解释 `volatile` 关键字的作用及其应用场景。
- 问题2:解释C语言中的内存对齐(Memory Alignment)以及为什么需要对齐。
- 问题3:解释C语言中的“严格别名规则”(Strict Aliasing Rule),以及如何避免相关问题。
- 问题4:解释并实现C语言中的浮点数比较。
- 问题5:实现一个线程安全的单例模式(Singleton)在C中。
- 问题6:解释C语言中的“堆栈溢出”(Stack Overflow)以及如何防止它。
- 问题7:解释 "memcpy", "strcpy", "strncpy", 和 "memmove",并实现 "strncpy" 和 "memmove"。
- 问题8:实现 memcompare 和 strcompare。
- 问题9:实现一个生产者-消费者模型,使用互斥锁和条件变量。
- 问题10:解释并实现快速排序算法(Quick Sort)。
- 问题11:实现一个LRU缓存机制。
- 问题12:解释C语言中的指针数组和数组指针的区别。
- 问题13:解释C语言中的函数指针及其用途,并写一个例子来说明。
- 问题14:解释C语言中的指针算术(Pointer Arithmetic)及其应用。
- 问题15:解释C语言中的变长数组(Variable Length Array, VLA),并实现一个示例。
- 问题16:解释并实现一个简单的内存池(Memory Pool)。
- 问题17:解释C语言中的预处理器指令和宏,并讨论它们的高级用法和潜在的陷阱。
- 问题18:解释C语言中的 `static` 关键字在函数内部和外部的不同作用。
- 问题19:解释C语言中的联合体(Union)及其应用场景。
- 问题20:解释C语言中的位域(Bit Fields),并实现一个示例。
问题1:解释 volatile
关键字的作用及其应用场景。
答案:
volatile
关键字告诉编译器,该变量可能会被程序外部(如硬件或其他线程)修改,因此编译器不应对该变量进行优化,应该每次都从内存中读取变量的值,而不是使用寄存器中的缓存值。
应用场景:
- 硬件寄存器: 对于硬件设备的寄存器映射,使用
volatile
确保每次读取寄存器时获取的是最新的值。 - 多线程编程: 在多线程环境中,使用
volatile
变量可以防止编译器对这些变量进行优化,确保线程间的可见性。 - 信号处理函数: 在信号处理函数中使用的变量应该声明为
volatile
,以防止编译器优化导致的问题。
问题2:解释C语言中的内存对齐(Memory Alignment)以及为什么需要对齐。
答案:
内存对齐是指数据在内存中的存储地址按照一定的规则进行排列,以提高内存访问的效率。大多数现代计算机体系结构要求数据以其大小的倍数对齐(如4字节的整数要以4字节对齐)。
需要对齐的原因:
- 性能原因: 许多处理器在内存对齐时可以更快地读取和写入数据。如果数据未对齐,处理器可能需要进行两次内存访问,影响性能。
- 硬件限制: 有些硬件体系结构不支持非对齐的内存访问,会导致程序崩溃或产生错误。
内存对齐示例:
#include <stdio.h>struct Example {char a; // 1字节int b; // 4字节short c; // 2字节
};int main() {struct Example e;printf("Size of Example: %lu\n", sizeof(e));printf("Offset of a: %lu\n", offsetof(struct Example, a));printf("Offset of b: %lu\n", offsetof(struct Example, b));printf("Offset of c: %lu\n", offsetof(struct Example, c));return 0;
}
问题3:解释C语言中的“严格别名规则”(Strict Aliasing Rule),以及如何避免相关问题。
答案:
严格别名规则指定了不同类型的指针不能互相转换访问,否则行为是未定义的。这是编译器优化的基础之一,违反这一规则可能导致不可预期的结果。
避免问题的方法:
- 使用
char*
类型进行字节级别的内存操作,因为它被视为可以指向任何类型的数据。 - 避免类型转换,尽量使用相同类型的指针进行操作。
- 如果必须进行类型转换,可以使用
union
,但要注意这种方式并不是在所有情况下都是安全的。
示例:
union Data {int i;float f;
};int main() {union Data data;data.i = 42;printf("As int: %d\n", data.i);printf("As float: %f\n", data.f);return 0;
}
问题4:解释并实现C语言中的浮点数比较。
答案:
在C语言中,直接比较两个浮点数是否相等是不可靠的,因为浮点数在表示小数时存在精度问题。因此,通常通过判断两个浮点数之差的绝对值是否小于一个很小的阈值(epsilon)来确定它们是否接近相等。
实现代码:
#include <stdio.h>
#include <math.h>#define EPSILON 1e-6int areAlmostEqual(float a, float b, float epsilon) {return fabs(a - b) < epsilon;
}int main() {float num1 = 0.1f + 0.2f;float num2 = 0.3f;if (areAlmostEqual(num1, num2, EPSILON)) {printf("num1 and num2 are approximately equal.\n");} else {printf("num1 and num2 are not equal.\n");}return 0;
}
问题5:实现一个线程安全的单例模式(Singleton)在C中。
答案:
实现一个线程安全的单例模式可以使用双重检查锁定(Double-Checked Locking)机制:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>typedef struct {int data;
} Singleton;Singleton *instance = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;Singleton *getInstance() {if (instance == NULL) {pthread_mutex_lock(&mutex);if (instance == NULL) {instance = (Singleton *)malloc(sizeof(Singleton));instance->data = 0;}pthread_mutex_unlock(&mutex);}return instance;
}int main() {Singleton *s1 = getInstance();Singleton *s2 = getInstance();printf("s1 data: %d\n", s1->data);printf("s2 data: %d\n", s2->data);return 0;
}
问题6:解释C语言中的“堆栈溢出”(Stack Overflow)以及如何防止它。
答案:
堆栈溢出发生在程序使用的堆栈空间超过系统为其分配的最大空间时。通常发生在递归调用过深或分配过大局部变量时。
防止方法:
- 避免过深的递归调用,使用迭代方式替代。
- 避免在堆栈上分配过大的局部变量,可以使用动态内存分配(如
malloc
)。 - 增加系统堆栈大小限制(具体方法依操作系统而不同)。
问题7:解释 “memcpy”, “strcpy”, “strncpy”, 和 “memmove”,并实现 “strncpy” 和 “memmove”。
解释:
memcpy
:用于从源地址复制一块内存内容到目标地址。不会处理内存重叠问题,假设源和目标区域是独立的。strcpy
:用于将源字符串复制到目标字符串,包括终止的空字符(\0
)。不进行边界检查,可能导致缓冲区溢出。strncpy
:用于将源字符串最多复制n个字符到目标字符串。如果源字符串长度小于n,目标字符串将用空字符填充。如果源字符串长度大于或等于n,不会自动添加终止的空字符(\0
)。memmove
:用于从源地址复制一块内存内容到目标地址,处理内存重叠问题,确保数据在重叠情况下也能正确复制。
实现 strncpy
和 memmove
:
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>// 实现strncpy
char *my_strncpy(char *dest, const char *src, size_t n) {size_t i;// 复制源字符串到目标字符串,最多n个字符for (i = 0; i < n && src[i] != '\0'; i++) {dest[i] = src[i];}// 如果源字符串长度小于n,用空字符填充for ( ; i < n; i++) {dest[i] = '\0';}return dest;
}// 实现memmove
void *my_memmove(void *dest, const void *src, size_t n) {uint8_t *d = (uint8_t *)dest;const uint8_t *s = (const uint8_t *)src;if (d == s) {return dest;}if (d < s) {// 如果目标地址在源地址之前,直接从前往后复制for (size_t i = 0; i < n; i++) {d[i] = s[i];}} else {// 如果目标地址在源地址之后,从后往前复制,避免重叠问题for (size_t i = n; i != 0; i--) {d[i - 1] = s[i - 1];}}return dest;
}int main(void) {// 测试my_strncpychar dest1[20];my_strncpy(dest1, "Hello, World!", 5);printf("my_strncpy result: %s\n", dest1);// 测试my_memmovechar dest2[20] = "Goodbye";my_memmove(dest2 + 4, dest2, 7);printf("my_memmove result:%s\n", dest2);return 0;
}
问题8:实现 memcompare 和 strcompare。
实现思想:
memcompare
:逐字节比较两个内存块的内容,直到找到不同的字节或比较完指定的字节数。strcompare
:逐字符比较两个字符串的内容,直到找到不同的字符或遇到字符串的结束符('\0'
)。
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>// 实现 memcompare
int memcompare(const void *ptr1, const void *ptr2, size_t num) {const uint8_t *p1 = (const uint8_t *)ptr1;const uint8_t *p2 = (const uint8_t *)ptr2;for (size_t i = 0; i < num; i++) {if (p1[i] != p2[i]) {return p1[i] - p2[i];}}return 0;
}// 实现 strcompare
int strcompare(const char *str1, const char *str2) {while (*str1 != '\0' && *str2 != '\0') {if (*str1 != *str2) {return (unsigned char)*str1 - (unsigned char)*str2;}str1++;str2++;}return (unsigned char)*str1 - (unsigned char)*str2;
}int main(void) {// 测试 memcomparechar mem1[] = {0, 1, 2, 3, 4};char mem2[] = {0, 1, 2, 4, 4};int result_memcompare = memcompare(mem1, mem2, sizeof(mem1));printf("memcompare result: %d\n", result_memcompare);// 测试 strcomparechar str1[] = "Hello";char str2[] = "HelLo";int result_strcompare = strcompare(str1, str2);printf("strcompare result: %d\n", result_strcompare);return 0;
}
问题9:实现一个生产者-消费者模型,使用互斥锁和条件变量。
实现思想:
- 互斥锁 用于保护缓冲区,确保同一时刻只有一个线程(生产者或消费者)访问缓冲区。
- 条件变量 用于同步生产者和消费者之间的等待和通知。当缓冲区满时,生产者等待
cond_produce
条件变量;当缓冲区为空时,消费者等待cond_consume
条件变量。 - 生产者 线程在生产数据时,首先获取互斥锁,如果缓冲区已满,则等待
cond_produce
条件变量,直到有空闲空间。然后将数据放入缓冲区,发信号通知cond_consume
消费者有新数据可供消费,最后释放互斥锁。 - 消费者 线程在消费数据时,首先获取互斥锁,如果缓冲区为空,则等待
cond_consume
条件变量,直到有新数据。然后从缓冲区取数据,发信号通知cond_produce
生产者有新空闲空间,最后释放互斥锁。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdint.h>
#include <unistd.h>#define BUFFER_SIZE 10static int buffer[BUFFER_SIZE];
static uint32_t count = 0;static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_produce = PTHREAD_COND_INITIALIZER;
static pthread_cond_t cond_consume = PTHREAD_COND_INITIALIZER;static void *producer(void *param) {(void)param; // 避免未使用参数的警告int32_t item;while (1) {item = rand() % 100;pthread_mutex_lock(&mutex);while (count == BUFFER_SIZE) {pthread_cond_wait(&cond_produce, &mutex);}buffer[count] = item;count++;printf("Produced: %d\n", item);pthread_cond_signal(&cond_consume);pthread_mutex_unlock(&mutex);sleep(1);}return NULL;
}static void *consumer(void *param) {(void)param; // 避免未使用参数的警告int32_t item;while (1) {pthread_mutex_lock(&mutex);while (count == 0) {pthread_cond_wait(&cond_consume, &mutex);}count--;item = buffer[count];printf("Consumed: %d\n", item);pthread_cond_signal(&cond_produce);pthread_mutex_unlock(&mutex);sleep(1);}return NULL;
}int main(void) {pthread_t prod, cons;pthread_create(&prod, NULL, producer, NULL);pthread_create(&cons, NULL, consumer, NULL);pthread_join(prod, NULL);pthread_join(cons, NULL);return 0;
}
问题10:解释并实现快速排序算法(Quick Sort)。
实现思想:
- 分治法:快速排序通过递归将数组分成较小的子数组进行排序。
- 选取基准:选择一个基准元素(通常是数组的最后一个元素)。
- 分区操作:将小于基准的元素移到基准左侧,大于基准的元素移到基准右侧。具体通过两个指针从数组两端向中间扫描交换元素实现。
- 递归排序:对基准元素左侧和右侧的子数组递归进行快速排序,直到子数组长度为1。
#include <stdio.h>
#include <stdint.h>static void swap(int32_t *a, int32_t *b) {int32_t temp = *a;*a = *b;*b = temp;
}static int32_t partition(int32_t arr[], int32_t low, int32_t high) {int32_t pivot = arr[high];int32_t i = low - 1;for (int32_t j = low; j < high; j++) {if (arr[j] < pivot) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]);return i + 1;
}static void quickSort(int32_t arr[], int32_t low, int32_t high) {if (low < high) {int32_t pi = partition(arr, low, high);quickSort(arr, low, pi - 1);quickSort(arr, pi + 1, high);}
}static void printArray(int32_t arr[], int32_t size) {for (int32_t i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main(void) {int32_t arr[] = {10, 7, 8, 9, 1, 5};int32_t n = sizeof(arr) / sizeof(arr[0]);quickSort(arr, 0, n - 1);printf("Sorted array: \n");printArray(arr, n);return 0;
}
问题11:实现一个LRU缓存机制。
实现思想:
- 双向链表:用于维护缓存的顺序,最近使用的元素移动到链表头,最久未使用的元素移到链表尾。
- 哈希表:用于快速查找缓存中的元素,键是缓存的键,值是双向链表中的节点。
- 缓存操作:
- 查询:从哈希表中查找元素,如果找到,则将该元素移动到链表头。
- 插入:如果缓存满,则移除链表尾的元素(即最久未使用的元素),然后将新元素插入链表头并更新哈希表。
- 删除:从链表中移除指定元素,并更新哈希表。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>typedef struct Node {int32_t key;int32_t value;struct Node *prev;struct Node *next;
} Node;typedef struct {int32_t capacity;int32_t size;Node *head;Node *tail;Node **hashTable;
} LRUCache;static Node *createNode(int32_t key, int32_t value) {Node *newNode = (Node *)malloc(sizeof(Node));if (newNode != NULL) {newNode->key = key;newNode->value = value;newNode->prev = NULL;newNode->next = NULL;}return newNode;
}static LRUCache *createCache(int32_t capacity) {LRUCache *cache = (LRUCache *)malloc(sizeof(LRUCache));if (cache != NULL) {cache->capacity = capacity;cache->size = 0;cache->head = createNode(0, 0);cache->tail = createNode(0, 0);if (cache->head != NULL && cache->tail != NULL) {cache->head->next = cache->tail;cache->tail->prev = cache->head;cache->hashTable = (Node **)calloc(capacity, sizeof(Node *));}}return cache;
}static void removeNode(Node *node) {if (node != NULL) {node->prev->next = node->next;node->next->prev = node->prev;}
}static void addToHead(LRUCache *cache, Node *node) {if (cache != NULL && node != NULL) {node->next = cache->head->next;node->prev = cache->head;cache->head->next->prev = node;cache->head->next = node;}
}static Node *getNode(LRUCache *cache, int32_t key) {return cache->hashTable[key % cache->capacity];
}static void putNode(LRUCache *cache, int32_t key, int32_t value) {if (cache != NULL) {Node *node = getNode(cache, key);if (node != NULL) {node->value = value;removeNode(node);addToHead(cache, node);} else {Node *newNode = createNode(key, value);if (newNode != NULL) {if (cache->size == cache->capacity) {Node *tail = cache->tail->prev;removeNode(tail);cache->hashTable[tail->key % cache->capacity] = NULL;free(tail);cache->size--;}addToHead(cache, newNode);cache->hashTable[key % cache->capacity] = newNode;cache->size++;}}}
}static int32_t get(LRUCache *cache, int32_t key) {Node *node = getNode(cache, key);if (node != NULL) {removeNode(node);addToHead(cache, node);return node->value;}return -1; // 如果key不存在,返回-1
}int main(void) {LRUCache *cache = createCache(2);putNode(cache, 1, 1);putNode(cache, 2, 2);printf("Get 1: %d\n", get(cache, 1)); // 返回 1putNode(cache, 3, 3); // 这个操作会使得key 2作废printf("Get 2: %d\n", get(cache, 2)); // 返回 -1 (未找到)putNode(cache, 4, 4); // 这个操作会使得key 1作废printf("Get 1: %d\n", get(cache, 1)); // 返回 -1 (未找到)printf("Get 3: %d\n", get(cache, 3)); // 返回 3printf("Get 4: %d\n", get(cache, 4)); // 返回 4return 0;
}
问题12:解释C语言中的指针数组和数组指针的区别。
答案:
-
指针数组(Array of Pointers): 一个数组,其中每个元素是一个指针。
int *arr[10]; // 这是一个包含10个int指针的数组
-
数组指针(Pointer to an Array): 一个指针,指向一个数组。
int (*p)[10]; // 这是一个指向包含10个int的数组的指针
问题13:解释C语言中的函数指针及其用途,并写一个例子来说明。
答案:
函数指针是指向函数的指针变量。它允许程序在运行时动态调用函数。
用途:
- 回调函数: 在库函数中使用回调函数进行定制操作。
- 函数表: 实现函数表以简化条件分支操作。
示例:
#include <stdio.h>void sayHello() {printf("Hello, world!\n");
}int main() {void (*funcPtr)() = sayHello;funcPtr(); // 调用函数指针执行 sayHello 函数return 0;
}
问题14:解释C语言中的指针算术(Pointer Arithmetic)及其应用。
答案:
指针算术允许在指针上进行加减运算。指针加减运算根据指针所指向的类型进行调整。
#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr;printf("Pointer arithmetic:\n");for (int i = 0; i < 5; i++) {printf("*(ptr + %d) = %d\n", i, *(ptr + i));}printf("Array indexing:\n");for (int i = 0; i < 5; i++) {printf("arr[%d] = %d\n", i, arr[i]);}return 0;
}
问题15:解释C语言中的变长数组(Variable Length Array, VLA),并实现一个示例。
答案:
变长数组(VLA)是C99标准引入的一种特性,允许数组长度在运行时确定。
#include <stdio.h>void printArray(int size) {int arr[size];for (int i = 0; i < size; i++) {arr[i] = i * i;}for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int n;printf("Enter the size of the array: ");scanf("%d", &n);printArray(n);return 0;
}
问题16:解释并实现一个简单的内存池(Memory Pool)。
答案:
内存池是一种预先分配一大块内存,然后在需要时从这块内存中划分出小块内存,提高内存分配和释放的效率。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>#define POOL_SIZE 1024Utypedef struct Block {uint32_t size;struct Block *next;
} Block;static Block *freeList = NULL;
static uint8_t memoryPool[POOL_SIZE];static void initMemoryPool(void) {freeList = (Block *)memoryPool;freeList->size = POOL_SIZE - sizeof(Block);freeList->next = NULL;
}static void *allocate(uint32_t size) {Block *current = freeList;Block *previous = NULL;while (current != NULL && current->size < size) {previous = current;current = current->next;}if (current == NULL) {return NULL; // No suitable block found}if (current->size > size + sizeof(Block)) {Block *newBlock = (Block *)((uint8_t *)current + sizeof(Block) + size);newBlock->size = current->size - size - sizeof(Block);newBlock->next = current->next;current->size = size;current->next = newBlock;}if (previous == NULL) {freeList = current->next;} else {previous->next = current->next;}return (uint8_t *)current + sizeof(Block);
}static void freeMemory(void *ptr) {if (ptr == NULL) {return;}Block *block = (Block *)((uint8_t *)ptr - sizeof(Block));block->next = freeList;freeList = block;
}int main(void) {initMemoryPool();void *p1 = allocate(100U);void *p2 = allocate(200U);printf("Allocated memory at %p and %p\n", p1, p2);freeMemory(p1);freeMemory(p2);void *p3 = allocate(150U);printf("Allocated memory at %p\n", p3);return 0;
}
当然可以,这里是一个关于C语言预处理器指令和宏的复杂面试问题。
问题17:解释C语言中的预处理器指令和宏,并讨论它们的高级用法和潜在的陷阱。
答案:
预处理器指令 是在编译过程中处理的指令,执行一些文本替换、文件包含和条件编译等操作。常见的预处理器指令包括 #define
, #include
, #if
, #ifdef
, #ifndef
等。
宏 是通过 #define
定义的预处理器指令,用于定义常量或函数样式的代码替换。
高级用法:
-
条件编译:根据不同的平台或条件编译不同的代码。
#ifdef _WIN32 #define PLATFORM "Windows" #elif defined(__linux__) #define PLATFORM "Linux" #else #define PLATFORM "Unknown" #endif
-
宏函数:定义一些常用的代码片段,减少代码重复。
#define SQUARE(x) ((x) * (x))
-
文件包含保护:防止头文件被多次包含,使用包含保护。
#ifndef MYHEADER_H #define MYHEADER_H // 文件内容 #endif
潜在陷阱:
-
宏替换陷阱:由于宏替换是简单的文本替换,可能会导致意外的行为。例如:
#define SQUARE(x) x * x int a = SQUARE(1 + 2); // 实际替换为 1 + 2 * 1 + 2
解决方法是使用括号包围宏参数和整个宏定义:
#define SQUARE(x) ((x) * (x))
-
多次求值:宏参数在宏替换中可能会被多次求值,导致副作用。
#define PRINT_AND_INCREMENT(x) printf("%d\n", (x)); (x)++; int a = 1; PRINT_AND_INCREMENT(a); // 打印1,a变成2 PRINT_AND_INCREMENT(a++); // 打印2,a变成4,而不是3
-
调试困难:宏替换发生在编译之前,错误信息可能难以追踪到宏定义,增加调试难度。
-
作用域问题:宏没有作用域,可能会影响全局命名空间,导致命名冲突。
问题18:解释C语言中的 static
关键字在函数内部和外部的不同作用。
答案:
- 在函数内部:
static
变量在函数内部声明时,表示该变量在函数调用之间保持其值不变。函数每次调用时不会重新初始化该变量。
- 在函数外部:
static
变量在函数外部声明时,表示该变量仅在声明它的文件内可见。其他文件不能访问该变量。
示例:
#include <stdio.h>void func() {static int x = 0; // 保持其值在函数调用之间不变x++;printf("x = %d\n", x);
}int main() {for (int i = 0; i < 5; i++) {func();}return 0;
}
输出:
x = 1
x = 2
x = 3
x = 4
x = 5
问题19:解释C语言中的联合体(Union)及其应用场景。
答案:
联合体(Union)是一种数据结构,它允许不同的数据类型共用同一段内存。联合体的所有成员共享同一块内存,存储空间大小等于其最大成员的大小。
应用场景:
- 内存节省: 联合体可以在不同类型的变量之间共享内存,节省内存空间。
- 数据解析: 联合体常用于解析不同格式的数据,例如网络协议数据包。
#include <stdio.h>union Data {int i;float f;char str[20];
};int main() {union Data data;data.i = 10;printf("data.i : %d\n", data.i);data.f = 220.5;printf("data.f : %f\n", data.f);strcpy(data.str, "C Programming");printf("data.str : %s\n", data.str);return 0;
}
问题20:解释C语言中的位域(Bit Fields),并实现一个示例。
答案:
位域(Bit Fields)是结构体的一部分,允许定义和存储比基本数据类型更小的位段。位域通常用于需要高效存储多个布尔值或小范围整数的情况。
#include <stdio.h>struct {unsigned int age : 3;
} Age;int main() {Age.age = 4;printf("Sizeof(Age) : %lu\n", sizeof(Age));printf("Age.age : %d\n", Age.age);Age.age = 7;printf("Age.age : %d\n", Age.age);Age.age = 8; // 超出范围,结果是不确定的printf("Age.age : %d\n", Age.age);return 0;
}
这些问题涵盖了C语言的高级主题,如多线程编程、内存管理、编译器优化规则等
这篇关于中高级软件工程师的c语言面试题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!