C 是当今最流行的计算机语言之一,因为它的结构、高级抽象、与机器无关的特性等。
C 语言是为编写 UNIX 操作系统而开发的,因此它与 UNIX 密切相关,UNIX 是当今最流行的网络操作系统之一,也是互联网数据高速公路的核心。
C语言常见的面试题有哪些?在本文中,你将了解在较新、中级和高级级别可能会遇到的问题。
基本C语言常见面试题合集
1. 表达式 5["abxdef"] 的值是多少?
答案是“f”。
说明:提到的字符串“abxdef”是一个数组,表达式等于“abxdef”[5]。为什么inside-out表达式是等价的?因为 a[b] 等价于 *(a + b) 等价于 *(b + a) 等价于 b[a]。
2.什么是C中的内置函数?
C语言面试题解析:C 中最常用的内置函数是 sacnf()、printf()、strcpy、strlwr、strcmp、strlen、strcat 等等。
内置函数也称为库函数,由系统提供,通过帮助开发人员完成某些常用的预定义任务,使他们的生活变得轻松。例如,如果你需要将输出或程序打印到终端中,我们在 C 中使用 printf()。
3. 在 C 中,#line 是做什么用的?
在 C 中,#line 用作预处理器来重新设置代码中的行号,它以一个参数作为行号。这是相同的示例。
#include <stdio.h> /*line 1*/
/*line 2*/
int main(){ /*line 3*/
/*line 4*/
printf("Nello world\n"); /*line 5*/
//print current line /*line 6*/
printf("Line: %d\n",_LINE_); /*line 7*/
//reset the line number by 36 /*line 8*/
#line 36 /*reseting*/
//print current line /*line 36*/
printf("Line: %d\n",_LINE_); /*line 37*/
printf("Bye bye!!!\n"); /*line 39*/
/*line 40*/
return 0; /*line 41*/
} /*line 42*/
4. 如何将字符串转换为数字?
该函数将字符串作为需要转换为整数的输入。
int atoi(const char *string)
返回值:
- 成功转换后,它返回所需的整数值
- 如果字符串以字母数字字符开头或仅包含字母数字字符,则返回 0。
- 如果字符串以数字字符开头但后跟字母数字字符,则字符串将转换为整数,直到第一次出现字母数字字符。
5. 如何将数字转换为字符串?
该函数需要一个指向需要转换的char元素数组的指针,需要将格式字符串作为字符串写入缓冲区
int sprintf(char *str, const char *format, ...)
#include<stdio.h>
#include <math.h>
int main()
{
char str[80];
sprintf(str, "The value of PI = %f", M_PI);
puts(str);
return 0;
}
下面是运行上述代码后的输出:
输出:Pi 的值 = 3.141593
6.为什么C不支持函数重载?
编译 C 源代码后,目标代码中的符号名称需要完整无缺。如果我们在源代码中引入函数重载,我们还应该提供名称修改作为预防措施,以避免函数名称冲突。此外,由于 C 不是严格类型化的语言,许多东西(例如:数据类型)在 C 中可以相互转换。因此,重载解析的复杂性可能会在诸如 C 的语言中引入混淆。
编译 C 源代码时,符号名称将保持不变。如果引入函数重载,则应提供名称修改技术以防止名称冲突。因此,与 C++ 一样,你将在编译后的二进制文件中拥有机器生成的符号名称。
此外,C 没有严格的类型。在 C 中,很多东西都可以隐式地相互转换。重载解析规则的复杂性可能会在这种语言中引入混乱
7. global int 和 static int 声明有什么区别?
这之间的区别在于范围。真正的全局变量具有全局作用域,并且在你的程序中随处可见。
#include <stdio.h>
int my_global_var = 0;
int
main(void)
{
printf("%d\n", my_global_var);
return 0;
}
global_temp 是一个全局变量,对程序中的所有内容都可见,但要使其在其他模块中可见,你需要一个“extern int global_temp;”。”在其他源文件中,如果你有一个多文件项目。
静态变量具有局部作用域,但其变量并未分配在内存的堆栈段中。它的作用域可以小于全局范围,尽管 - 就像全局变量一样 - 它驻留在已编译二进制文件的 .bss 段中。
#include <stdio.h>
int
myfunc(int val)
{
static int my_static_var = 0;
my_static_var += val;
return my_static_var;
}
int
main(void)
{
int myval;
myval = myfunc(1);
printf("first call %d\n", myval);
myval = myfunc(10);
printf("second call %d\n", myval);
return 0;
}
8. const char* p 和 char const* p 的区别?
const char* p 是一个指向 const char 的指针。
char const* p 是一个指向 char const 的指针。
因为 const char 和 char const 是一样的,所以是一样的。
9. 为什么 n++ 执行得比 n+1 快?
n++ 是一元运算,它只需要一个变量。而 n = n + 1 是一种二元运算,它会增加开销以花费更多时间(也是二元运算:n += 1)。但是,在现代平台中,它取决于很少的事情,例如处理器架构、C 编译器、代码中的使用以及硬件问题等其他因素。
而在现代编译器中,即使你编写 n = n + 1,当它进入优化的二进制文件时,它也会被转换为 n++,并且效率相当。
10.宏比函数有什么优势?
高级复制粘贴上的宏,其定义到任何调用它的地方。因此,它节省了大量时间,因为在将控件传递给新函数时不会花费任何时间,并且控件始终与被调用函数一起使用。然而,一个缺点是编译后的二进制文件的大小很大,但一旦编译,程序运行速度相对更快。
中级C语言常见面试题合集
11.指定不同类型的决策控制语句?
程序中编写的所有语句都是从上到下依次执行的。控制语句用于根据条件将控制从程序的一个部分执行/转移到另一部分。
- If-else 语句。
- 正常的 if-else 语句。
- Else-if 语句
- 嵌套的 if-else 语句。
- switch语句。
12. C 中的 struct 和 union 有什么区别?
struct 是一组存储在内存块中的复杂数据结构,块中的每个成员都有一个单独的内存位置,以便可以立即访问它们,
而在联合中,所有成员变量都存储在内存中的相同位置因此,在为成员变量赋值时将更改所有其他成员的值。
/* struct & union definations*/
struct bar {
int a; // we can use a & b both simultaneously
char b;
} bar;
union foo {
int a; // we can't use both a and b simultaneously
char b;
} foo;
/* using struc and union variables*/
struct bar y;
y.a = 3; // OK to use
y.b = 'c'; // OK to use
union foo x;
x.a = 3; // OK
x.b = 'c'; // NOl this affects the value of x.a!
13. 什么是函数中的引用调用?
当我们的调用者函数绕过传递的实际参数的地址进行函数调用时,这称为引用调用。在 incall by reference 中,对形参执行的操作会影响实参的值,因为所有对存储在实参地址中的值执行的操作。
14. 什么是函数中的引用传递?
在传递引用中,被调用者接收地址并将参数的地址复制到形式参数中。Callee 函数使用地址来访问实际参数(进行一些操作)。如果被调用函数更改了在传递地址处寻址的值,则调用函数也将可以看到该值。
15. 什么是内存泄漏?如何避免?
当我们分配一个变量时,它会根据数据类型的大小占用我们的 RAM(堆或 RAM)空间,但是,如果程序员使用堆上可用的内存并忘记对其进行增量,则在某些时候所有内存ram 上的可用内存将被占用而没有剩余内存,这可能导致内存泄漏。
int main()
{
char * ptr = malloc(sizeof(int));
/* Do some work */
/*Not freeing the allocated memory*/
return 0;
}
为避免内存泄漏,你可以跟踪所有内存分配并向前考虑,你想要销毁(从某种意义上说)该内存并将删除放在那里。另一种方法是在 C 中使用 C++ 智能指针将其链接到 GNU 编译器。
16.什么是C中的动态内存分配?命名动态分配函数。
C语言面试题解析:C 是一种以对 DMA 中变量的内存分配进行低级控制而闻名的语言,有两个主要的标准库 malloc() 和 free。malloc() 函数采用单个输入参数,该参数告诉请求的内存大小。它返回一个指向分配内存的指针。如果分配失败,则返回 NULL。标准库函数的原型是这样的:
void *malloc(size_t size);
free() 函数获取 malloc() 返回的指针并取消分配内存。不返回成功或失败的指示。函数原型是这样的:
void free(void *pointer);
<stdlib.h> 头文件下定义了 C 提供的 4 个库函数,以方便 C 编程中的动态内存分配。他们是:
- malloc()
- calloc()
- free()
- realloc()
17.什么是typedef?
typedef 是一个 C 关键字,用于为 C 语言中的现有类型定义别名/同义词。在大多数情况下,我们使用 typedef 来简化现有的类型声明语法。或者为类型提供特定的描述性名称。
typedef <existing-type> <new-type-identifiers>;
typedef 为现有的复杂类型定义提供别名。使用 typedef,你可以简单地为任何类型创建别名。无论是简单的整数到复杂的函数指针还是结构体声明,typedef 都会缩短你的代码。
18. 为什么使用gets()通常是个坏主意?建议一个解决方法。
标准输入库gets() 读取用户输入,直到遇到换行符。但是,它不会检查用户提供的变量的大小是否低于数据类型的最大大小,因为这会使系统容易受到缓冲区溢出的影响,并且输入被写入不应该写入的内存中.
因此,我们使用 fgets() 在有限的输入范围内实现相同的目标
奖励:在 1999 年 ISO C 标准之前,它仍然是语言的官方部分,但在 2011 年标准中被正式删除。大多数 C 实现仍然支持它,但至少 GCC 会对使用它的任何代码发出警告。
19. C语言常见的面试题有哪些:#include "..." 和#include <...> 有什么区别?
实际上,区别在于预处理器搜索包含文件的位置。
对于#include <filename>,C 预处理器首先在系统目录的预定义列表中查找文件名,然后再查找用户指定的目录(我们可以使用 -I 选项将目录添加到上述预定义列表中)。
对于#include "filename",预处理器首先在与包含指令的文件相同的目录中搜索,然后遵循用于#include <filename> 表单的搜索路径。此方法通常用于包含程序员定义的头文件。
20. 什么是悬空指针?悬空指针与内存泄漏有何不同?
悬空指针指向一个已经被释放的内存。不再分配存储。尝试访问它可能会导致分段错误。以悬空指针结束的常见方法:
#include<stdio.h>
#include<string.h>
char *func()
{
char str[10];
strcpy(str,"Hello!");
return(str);
}
你正在返回一个作为局部变量的地址,当控制返回给调用函数时,该地址将超出范围。(未定义的行为)
*c = malloc(5izeof(int));
free(c);
*c = 3; //writing to freed location!
在上图中写入已释放的内存是悬空指针的一个示例,它使程序崩溃。
内存泄漏是指分配的内存未释放,这会导致程序使用 ram 中未定义的内存量,从而导致程序崩溃的每个其他正在运行的程序(或守护进程)都不可用。有各种工具,如 O 配置文件测试,可用于检测程序中的内存泄漏。
void function(){
char *leak = malloc (10); //leak assigned but not freed
}
21. C 中的“g”和“g”有什么区别?
在 C 中,双引号变量被标识为字符串,而单引号变量被标识为字符。另一个主要区别是字符串(双引号)变量以空终止符结尾,使其成为 2 个字符的数组。
高级C语言常见面试题合集
22. 你能告诉我如何检查链表是否是循环的吗?
单链表
循环链表
循环链表是链表的一种变体,其中最后一个节点指向第一个节点的信息部分。因此最后一个节点不指向空。
查找给定链表是否为循环的算法
一个很简单的判断链表是否循环的方法
- 遍历链表
- 检查节点是否指向头部。
- 如果是,那么它是圆形的。
让我们看看我们编写这个算法的代码片段。
Create a structure for a linked list
Declare
-Variable to store data of the node.
-Pointer variable of struct type to store the address of next node.
function of datatype tool isCircular(firstgode){
-Store the value of first node in temp variable and make it traverse all nodes.
-temp-firstgode
-tempenext node pointed by temp(temp->next)
-run until temp is at null or firstNode
if (temp at null)
not circular and returns false
if (temp points first node)
return true as its circular.
}
function of datatype node newNode(data){
-To insert new nodes and link each one of them to the previous node by storing the address of the new node to the previous one.
-Then make them point to NULL.
}
In int main function
-First insert nodes for circular linked list and check its nature by calling isCircular function.
-Since it is true through if statement it prints "yes..
-Second insert a normal linked list and check its nature by calling isCircular function. As its not circular it prints "no",
23. 每条程序语句末尾的分号 (;) 有什么用?
它主要与编译器如何读取(或解析)整个代码并将其分解为一组指令(或语句)有关,C 中的分号作为两组指令之间的边界。
24. 区分源代码和目标代码
源代码和目标代码的区别在于源代码是使用人类可读的编程语言编写的计算机指令的集合,而目标代码是机器语言中的语句序列,是编译器或汇编器转换后的输出源代码。
关于目标代码的最后一点是反映更改的方式。当修改源代码时,每次都需要编译源代码以反映目标代码中的变化。
25. 什么是头文件,它在 C 编程中有什么用途?
在 C 中,头文件的扩展名必须为 .h,其中包含函数定义、数据类型定义、宏等。头文件可用于使用 #include 指令将上述定义导入到源代码中。例如,如果你的源代码需要从用户那里获取输入做一些操作并在终端上打印输出,它应该包含 stdio.h 文件作为 #include <stdio.h>,我们可以使用 scanf 获取输入() 使用 printf() 进行一些操作和打印。
26.函数中何时使用“void”关键字
关键字“void”是一种数据类型,字面上不代表任何数据。最明显的用法是一个不返回任何内容的函数:
void PrintHello()
{
printf("Hello\n");
return; // the function does "return", but no value is returned
}
这里我们声明了一个函数,所有的函数都有一个返回类型。在这种情况下,我们说返回类型是“void”,这意味着“根本没有数据”被返回。
void 关键字的另一个用途是 void 指针。void 指针指向在变量定义时未定义数据类型的内存位置。即使你可以定义一个返回类型为 void* 或 void 指针的函数,意思是“在编译时我们不知道它会返回什么”让我们看一个例子。
void MyMemCopy(void* dst, const void* src, int numBytes)
{
char* dst_c = reinterpret_cast<char*>(dst);
const char* src_c = reinterpret_cast<const char*>(src);
for (int i = 0; i < numBytes; ++i)
dst_c[i] = src_c[i];
}
27.什么是动态数据结构?
动态数据结构 (DDS) 是指内存中数据的组织或集合,可以灵活地增加或缩小大小,使程序员能够准确控制使用多少内存。通过根据需要从堆分配或取消分配未使用的内存,动态数据结构的大小会发生变化。
动态数据结构在 C、C++ 和 Java 等编程语言中起着关键作用,因为它们为程序员提供了调整软件程序内存消耗的灵活性。
28. 不使用加法运算符将两个数相加
对于两个数字的总和,我们使用加法 (+) 运算符。在这些棘手的 C 程序中,我们将编写一个 C 程序,在不使用加法运算符的情况下将两个数字相加。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x, y;
printf("Enter two number: ");
scanf("%d %d",&x,&y);
// method 1
printf("%d\n", x-(-y));
// method 2
printf("%d\n", -(-x-y));
// method 3
printf("%d\n", abs(-x-y));
// method 4
printf("%d", x-(~y)-1);
return 0;
}
29. C语言常见的面试题有哪些:不使用减法运算符减去两个数
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x, y;
printf("Enter two number: ");
scanf("%d %d",&x,&y);
printf("%d", x+(~y)+1);
return 0;
}
本程序中使用了按位补码运算符。数字 ~y=-(y+1) 的按位补码。所以,表达式会变成 x+(-(y+1))+1=xy-1+1=xy
30. 不使用乘法运算符将整数乘以 2
#include<stdio.h>
int main()
{
int x;
printf("Enter a number: ");
scanf("%d",&x);
printf("%d", x<<1);
return 0;
}
左移运算符将所有位向左移动一定数量的指定位。表达式 x<<1 总是返回 x*2。请注意,移位运算符不适用于浮点值。
对于 x 乘以 4 的倍数,使用 x<<2。类似地,x<<3 乘以 x 8。对于 x 乘以 2^n 的倍数,使用 x<<n。
31.检查数字是偶数还是奇数,不使用任何算术或关系运算符
#include<stdio.h>
int main()
{
int x;
printf("Enter a number: ");
scanf("%d", &x);
(x&1)?printf("Odd"):printf("Even");
return 0;
}
按位与(&)运算符可用于快速检查数字是奇数还是偶数。
32. 反转链表。输入:1->2->3->4->5->NULL 输出:5->4->3->2->1->NULL
C语言面试题解析:假设我们有链表 1 → 2 → 3 → Ø,我们想把它改成 Ø ← 1 ← 2 ← 3。
在遍历链表时,将当前节点的下一个指针更改为指向其前一个元素。对先前节点的引用应该存储到一个临时变量中,如图所示,这样我们就不会丢失对交换节点的跟踪。在更改引用之前,你还需要另一个指针来存储下一个节点。同样,当我们完成后,返回反向列表的新头。
/* Function to reverse the linked list */
static void reverse(struct Node** head_ref)
{
struct Node* prev = NULL;
struct Node* current = *head_ref;
struct Node* next;
while (current != NULL)
{
// store next
next = current->next;
// reverse curr node pointer
current->next = prev;
// move pointer one position ahead
prev = current;
current = next;
}
*head_ref = prev;
}
33. 使用堆栈检查平衡括号
给定一个仅包含字符 '(', ')', '{', '}', '[' 和 ']' 的字符串 s,确定输入字符串是否有效。
输入字符串在以下情况下有效:
- 开括号必须被同类型的括号封闭。
- 左括号必须以正确的顺序关闭。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
以下是使用堆栈检查平衡括号的 C 程序的源代码,它已成功编译并在 Windows 系统上运行以产生所需的输出,如下所示:
int check(char exp[] )
{
int i;
char temp;
for(i=0;i<strlen(exp);i++)
{
if(exp[i]=='(' || exp[i]=='{' || exp[i]=='[')
push(exp[i]);
if(exp[i]==')' || exp[i]=='}' || exp[i]==']')
if(top==-1) /*stack empty*/
{
printf("Right parentheses are more than left parentheses\n");
return 0;
}
else
{
temp=pop();
if(!match(temp, exp[i]))
{
printf("Mismatched parentheses are : ");
printf("%c and %c\n",temp,exp[i]);
return 0;
}
}
}
if(top==-1) /*stack empty*/
{
printf("Balanced Parentheses\n");
return 1;
}
else
{
printf("Left parentheses more than right parentheses\n");
return 0;
}
}
34. 寻找第 n 个斐波那契数的程序
斐波那契数列的特点是前两个数之后的每个数都是前两个数之和。例如,考虑以下序列
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... .. 等等
其中 F{n} = F{n-1} + F{n-2} 具有基值 F(0) = 0 和 <code>F(1) = 1
下面是寻找斐波那契数列第 n 个成员的简单实现
// Function to find the nth Fibonacci number
int fib(int n)
{
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
int main()
{
int n = 8;
printf("nth Fibonacci number is %d", fib(8));
return 0;
}
35. C语言常见面试题合集:编写一个程序,找出两个单链表的交集开始的节点。
让我们以以下两个在节点 c1 处相交的链表为例。
解决方案 -
- 获取第一个列表中的节点数,令 count 为 c1。
- 获取第二个列表中的节点数,令 count 为 c2。
- 得到计数差 d = abs(c1 – c2)
- 现在从第一个节点到 d 个节点遍历更大的列表,以便从这里开始两个列表的节点数相等
- 然后我们可以并行遍历两个列表,直到遇到一个公共节点。(注意获取公共节点是通过比较节点的地址来完成的)
// Function to get the intersection point
// of the given linked lists
int getIntersectionNode(Node* head1, Node* head2)
{
Node *curr1 = head1, *curr2 = head2;
// While both the pointers are not equal
while (curr1 != curr2) {
// If the first pointer is null then
// set it to point to the head of
// the second linked list
if (curr1 == NULL) {
curr1 = head2;
}
// Else point it to the next node
else {
curr1 = curr1->next;
}
// If the second pointer is null then
// set it to point to the head of
// the first linked list
if (curr2 == NULL) {
curr2 = head1;
}
// Else point it to the next node
else {
curr2 = curr2->next;
}
}
// Return the intersection node
return curr1->data;
}
36. C语言常见的面试题有哪些:合并两个有序链表
合并两个排序的链表并将它们作为排序列表返回。该列表应通过将前两个列表的节点拼接在一起来制作。
NodePtr merge_sorted(NodePtr head1, NodePtr head2) {
// if both lists are empty then merged list is also empty
// if one of the lists is empty then other is the merged list
if (head1 == nullptr) {
return head2;
} else if (head2 == nullptr) {
return head1;
}
NodePtr mergedHead = nullptr;
if (head1->data <= head2->data) {
mergedHead = head1;
head1 = head1->next;
} else {
mergedHead = head2;
head2 = head2->next;
}
NodePtr mergedTail = mergedHead;
while (head1 != nullptr && head2 != nullptr) {
NodePtr temp = nullptr;
if (head1->data <= head2->data) {
temp = head1;
head1 = head1->next;
} else {
temp = head2;
head2 = head2->next;
}
mergedTail->next = temp;
mergedTail = temp;
}
if (head1 != nullptr) {
mergedTail->next = head1;
} else if (head2 != nullptr) {
mergedTail->next = head2;
}
return mergedHead;
}
C语言面试题解析:运行时复杂度线性,O(m + n) 其中 m 和 n 是两个链表的长度。
内存复杂度常数,O(1)
在合并的链表上维护一个头指针和一个尾指针。然后通过比较两个链表的第一个节点来选择合并链表的头部。对于两个列表中的所有后续节点,你选择较小的当前节点并将其链接到合并列表的尾部,并将该列表的当前指针向前移动一步。
当两个列表中都有一些剩余元素时,你会继续这样做。如果只有其中一个列表中仍有一些元素,则将此剩余列表链接到合并列表的尾部。
最初,合并的链表为 NULL。比较前两个节点的值,将值较小的节点作为合并链表的头节点。在本例中,它是来自 head1 的 4。
由于它是合并列表中的第一个也是唯一的节点,因此它也将是尾部。然后将 head1 向前移动一步。