C语言面试题(基本概念)

2,236 阅读16分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

1、关键字static的作用是什么?

  • 延长变量的声明周期
  • 限定作用域 (修饰的变量或者函数只能在当前文件中使用)

2、“引用”与指针的区别是什么?

  1. 指针是一个实体;而引用仅是个别名
  2. 指针是一个变量的拷贝,存储的是地址;引用储存的是值
  3. 指针是间接访问变量,需要有分配自己的内存空间;引用是直接访问变量,不用分配自己的内存空间
  4. 引用使用时无需使用*;指针则需要
  5. 引用只能在定义时被初始化一次,之后不可变;指针则可变
  6. 引用不能为空;指针可以
  7. sizeof引用得到的是所指向变流量(或对象)的大小;而sizeof指针得到的是指针本身的大小

3、.h头文件中的ifndef/define/endif 的作用?

  1. ifndef:是先判断一下有没有这个宏定义,防止被重复定义。如果有,则不重新定义,没有,则定义一下。
  2. define:是宏定义。
  3. endif:用来结束if

4、#include<file.h> 与#include "file. h"的区别?

  1. #include <file.h>:编译器先从标准库路径开始搜索,如果没再搜索资源库目录,最后搜索当前工作目录
  2. #include "file.h"先搜索当前工作目录,如果没有,再去搜索库,库没有再搜索资源库

5、描述实时系统的基本特性

实时系统是指在系统工作时,能在特定的时间内完成特定的任务,其各种资源可以根据需要进行动态的分配,因此其处理事务的能力强,速度快。

  1. 高精度计时系统

    ​ 计时精度是影响实时性的一个重要因素。在实时应用系统中,经常需要精确确定实时地操作某个设备或执行某个任务,或精确的计算一个时间函数。这些不仅依赖于一些硬件提供的时钟精度,也依赖于实时操作系统实现的高精度计时功能。

  2. 多级中断机制

    ​ 一个实时应用系统通常需要处理多种外部信息或事件,但处理的紧迫程度有轻重缓急之分。有的必须立即作出反应,有的则可以延后处理。因此,需要建立多级中断嵌套处理机制,以确保对紧迫程度较高的实时事件进行及时响应和处理。

  3. 实时调度机制

    ​ 实时操作系统不仅要及时响应实时事件中断,同时也要及时调度运行实时任务。但是,处理机调度并不能随心所欲的进行,因为涉及到两个进程之间的切换,只能在确保“安全切换”的时间点上进行,实时调度机制包括两个方面,一是在调度策略和算法上保证优先调度实时任务;二是建立更多“安全切换”时间点,保证及时调度实时任务

6、全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

1.生存周期不同

  1. 普通局部变量:执行到普通局部变量定义语句➡{}结束
  2. static局部变量:从编译阶段➡整个程序结束。
  3. 普通全局变量:从编译阶段➡整个程序结束。
  4. static全局变量:从编译阶段➡整个程序结束。

2.作用范围不同

  1. 普通局部变量:作用域属于某个{}
  2. static局部变量:作用域属于某个{}
  3. 普通全局变量:作用域属于全局范围,不同文件使用需要声明。
  4. static全局变量:作用域属于该文件范围。

3.内存所在区域不同

  1. 普通局部变量:栈区(多次调用这个局部变量在栈上的位置都不一定相同);动态分配(malloc)的内容在堆区
  2. 普通全局变量:静态区; 已经初始化的全局变量在.data数据段 未经初始化的全局变量.bss段(未使用bss段中的内容全部都是0)
  3. static修饰的已经初始化的变量在.data数据段(只初始化一次) static修饰的未初始化的变量在.bss段

7、什么是平衡二叉树?

平衡二叉树是特殊的二叉排序树,树中所有结点为根的树的左右子树高度之差的绝对值不超过1

8、堆栈溢出一般是由什么原因导致的?

  1. 递归调用层次太深,导致堆栈容纳这些调用的返回地址
  2. 动态申请空间使用后没有释放,剩余对空间减少,可能会造成溢出
  3. 堆栈尺寸设置过小,数组访问越界
  4. 指针非法访问

9、冒泡排序算法的时间复杂度是什么?

时间复杂度就代表的是算法执行所需要的时间,一般用O()来表示。 例如:for(i=0;i<n;i++);它的时间复杂度就是O(n),所以由此可得:冒泡排序的时间复杂度为O(n^2)

10、什么函数不能声明为虚函数?

常见的不能声明为虚函数的有:

  1. 普通函数(非成员函数)
  2. 静态成员函数
  3. 内联成员函数
  4. 构造函数
  5. 友元函数

==析构函数可以是虚函数,而且通常声明为虚函数。==

(1)普通函数不能声明为虚函数。普通函数(非成员函数)只能被重载(overload),不能被
重写(override),声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。
(2) 构造函数不能声明为虚函数。构造函数一般用来初始化对象,只有在一个对象生成之
后,才能发挥多态作用。如果将构造函数声明为虚函数,则表现为在对象还没有生成的时候来
定义它的多态,这两点是不统一的。另外,构造函数不能被继承,因而不能声明为虚函数。
(3) 静态成员函数不能声明为虚函数。静态成员函数对于每个类来说只有一份代码,所有的
对象都共享这份代码,它不归某个对象所有,所以也没有动态绑定的必要性。
(4) 内联(inline)成员函数不能声明为虚函数。内联函数就是为了在代码中直接展开,减
少函数调用开销的代价。虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能
统一的。另外,内联函数在编译时被展开,虚函数在运行时才能动态的绑定函数。
(5) 友元函数不能声明为虚函数。友元函数不属于类的成员函数,不能被继承。

11、队列和栈有什么区别?

队列是队尾入队,队头出队,先进先出(FIFO);队列是基于地址指针进行遍历,无需开辟
空间

栈是栈顶进出,后进先出(LIFO);栈遍历数据的同时需要为数据开辟临时空间,保持数据
在遍历前的一致性

12、不能做switch()的参数类型

支持`byte`,`char`,`short`,`int`,`long`,`bool`,整数类型和枚举类型
不支持`float`,`double`,`string`

13、局部变量能否和全局变量重名?

C++中:能,局部变量会屏蔽全局变量。要用全局变量时,需要使用 “::”(域解析符)

14、如何引用一个已经定义过的全局变量?

可以用引用头文件的方式,也可以用extern关键字,

  1. 如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变量写错了,那么在编译期间会报错
  2. 如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错

15、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

可以,在不同的C文件中以static形式来声明同名全局变量。前提是只能有一个C文件中对此
变量赋初值,链接才不会出错 

16、语句for( ; 1 ; )有什么问题?它是什么意思?

死循环。我们知道在for语句中,初始化条件、循环条件、迭代语句都可以是空,而且在
C/C++中非零是ture,循环一直进行。【并且在这个for(;1;)语句中,1int类型,不
能自动转化为boolean,所以编译报错。】

17、do... while和while...do有什么区别?

一、跳出循环不同
1do-whiledo-while不可以通过break在循环过程中跳出。
2while-dowhile-do可以通过break在循环过程中跳出。
二、执行次数不同
1do-whiledo-while至少会执行一次循环体。
2while-dowhile-do可能会出现一次都不执行循环体的情况。
三、优先操作不同
1do-while:优先执行循环体,再判断执行条件是否符合要求。
2while-do:优先判断执行条件是否符合要求,再执行循环体。

18、 static 函数与普通函数有什么区别?

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

19、简述程序的内存分配

1.在栈上分配:局部变量、形参、函数的执行
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动
被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

2.从堆上分配:
即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在
何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆
上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不
同大小的堆空间将会产生堆内碎块。

3.从静态存储区域分配:.data/.bss/.text/.ro四段

.data:已经初始化的全局变量、static修饰的已经初始化的变量
.bss:未经初始化的全局变量,static修饰的未初始化的变量
.text:程序的文本
.ro:只读数据,const变量和字符串常量。

内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在速度快,不容易出
错,因为有系统会善后。

blog.csdn.net/freee12/art…

20、解释堆和栈的区别

1、堆栈空间分配区别

栈(操作系统):由操作系统(编译器)自动分配释放 ,存放函数的参数值,局部变量的值
等。其操作方式类似于数据结构中的栈。

堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,
分配方式倒是类似于链表。

2、堆栈缓存方式区别

栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。

堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对
象就能被回收)。所以调用这些对象的速度要相对来得低一些。

3、堆栈数据结构区别

堆(数据结构):堆可以被看成是一棵树,如:堆排序。

栈(数据结构):一种先进后出的数据结构。

21、什么是预编译,何时需要预编译?

预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作。
主要处理#开头的指令。如拷贝#include包含的文件代码、替换#define定义的宏、条件编#if等。

何时需要预编译:
	总是使用不经常改动的大型代码体。
	程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在
        这种情况下,可以将所有包含文件预编译为一个预编译头。

22、关键字const是什么含意?

const在谁后面就是修饰谁,在最前面的话,就后移一位
1.给读代码的人传达参数的应用目的信息
2.通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3.使编译器保护那些不希望被改变的参数,减少bug的出现

23、关键字volatile 有什么含意并给出三个不同的例子。

volatile修饰的变量防止编译器优化,volatile修饰的变量每次都从内存中获取新的值,而
不从(cache)缓存中获取缓存的值
			
    1.编译器优化
        int a;
        int b;
        a = 1;
        b = 500;     ====>这里编译器直接认为a=2;  
        a = 2;

    2.从内存中获取最新值的例子
        例如有一个a的变量,代表的是触摸屏是否按下的状态
        如果a = 1表示按下触摸屏
        如果a = 0表示没有按下触摸屏

        程序在执行的时候a的值被缓存到cache之后,此时
        按下了触摸屏,a的值改变为1,但是缓冲中的数据是0
        此时cpu拿到的数据就是错误的。a的变量就需要在前边
        加上volatile

注意:volatile使用场景
    1.片内外设寄存器的地址前都要加volatile
    2.多线程同时访问的变量需要将volatile
    3.在中断处理函数中访问非自动类型的变量,这个变
      量需要加volatile

24、三种基本的数据模型

数据模型(Data Model)是数据特征的抽象,它从抽象层次上描述了系统的静态特征、动态
行为和约束条件,为数据库系统的信息表示与操作提供一个抽象的框架。数据模型所描述的内
容有三部分,分别是数据结构、数据操作和数据约束。

层次模型:将数据组织成一对多关系的结构,用树形结构表示实体及实体间的联系。

网状模型:用连接指令或指针来确定数据间的网状连接关系,是具有多对多类型的数据组织方
式。

关系模型:以记录组或数据表的形式组织数据,以便于利用各种实体与属性之间的关系进行存
储和变换,不分层也无指针,是建立空间数据和属性数据之间关系的一种非常有效的数据组织
方法。

25、结构与联合有和区别?

1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一
个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放
地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构
的不同成员赋值是互不影响的。

26、描述内存分配方式以及它们的区别?

1.在栈上分配:局部变量、形参、函数的执行
在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动
被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

2.从堆上分配:
即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在
何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆
上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不
同大小的堆空间将会产生堆内碎块。

3.从静态存储区域分配:.data/.bss/.text/.ro四段

.data:已经初始化的全局变量、static修饰的已经初始化的变量
.bss:未经初始化的全局变量,static修饰的未初始化的变量
.text:程序的文本
.ro:只读数据,const变量和字符串常量。

内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在速度快,不容易出
错,因为有系统会善后。

27、请说出const 与#define 相比, 有何优点?

宏常量有数据类型,而const常量没有数据类型
有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试

编译器可以对const进行类型安全检查。而对#define只进行字符替换,没有类型安全检查,
并且在字符替换可能会产生意料不到的错误。

28、简述指针与数组的区别?

1.存储方式:数组只能在静态存储区或栈上被创建,而指针可以随时随地的指向任意类型的内
存块。
2.内存容量:用运算符sizeof可以计算出数组的容量(字节数)。sizeof(p),p为指针,得
到的是一个指针变量的字节数,而不是指针指向内存的容量。
3.内容上的差别:char a[]="hello"数组指向每一个数组元素;而char *p="world",p指
向的是字符串的首地址。

29、分别写出BOOL, int, float, 指针类型的变量a与“零”的比较语句

BOOL:
    if(a)  or  if(!a);
int:
    if(0 == a);
float:
    const float EXP = 0.000001;
    if(a<EXP && a>-EXP);
pointer:
    if(a != NULL)  or  if(a == NULL);

30、如何判断段程序是由C编译程序还是由 C++编译程序编译的?

如果编译器在编译cpp文件,那么 _cplusplus就会被定义,如果是一个C文件被编译,那么 _STDC_就会被定义,_STDC_是预定义宏,当它被定义后,编译器将按照ANSIC标准来编译C语言程序。 

31、论述含参数的宏与函数的优缺点

1.函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符
替换。
2.函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在
展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
3.对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;
而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定
的字符即可。宏定义时,字符串可以是任何类型的数据。
4.调用函数只可得到一个返回值,而用宏可以设法得到几个结果。
5.使用宏次数多时,宏展开后源程序长,因为每展开一次都使程序增长,而函数调用不使源程
序变长。
6.宏替换不占运行时间。而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
一般来说,用宏来代表简短的表达式比较合适。

32、用两个栈实现一个队列的功能?要求给出算法和思路!

两个栈s1,s2
 
始终维护s1作为存储空间,以s2作为临时缓冲区。
 
入队时,将元素压入s1。
出队时,将s1的元素逐个“倒入”(弹出并压入)s2,将s2的顶元素弹出作为出队元素,之后
再将s2剩下的元素逐个“倒回”s1

33、嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

while(1) { }

for(;;) { }中间没条件就无限循环。

区别:
	for(;;) {   } for 里面为空,编译执行之后没有判断的语句,
	而 while(1)始终都会有执行判断 1 = true,所以在单片机这种低速的、内存资
        源不多的环境,for(;;)是更好的选择。

34、位操作(Bit manipulation)

位或 |
位与 &
逻辑与 &&
逻辑或 ||
位取反 ~(可以连着使用 !!)
逻辑非 !(~~)
位异或 ^
左移和右移 >> <<

寄存器很常见的就是操作个别位

35、访问固定的内存位置(Accessing fixed memory locations)

访问某特定的内存位置。在某工程中,设置一绝对地址为0x67a9的整型变量的值为0xaa66。
编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针
是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;

36、什么是中断(Interrupts)

中断是由硬件或软件所发送的一种信号。中断允许让设备表明它们需要CPU。一旦CPU接收了
中断请求,CPU就会暂时停止执行正在运行的程序,并且调用一个称为中断处理器或中断服务
程序。

中断服务程序或中断处理器可以在中断向量表中找到,而这个中断向量表位于内存中的固定地
址中。中断被CPU处理后,就会恢复执行之前被中断的程序。
硬中断:非屏蔽中断、可屏蔽中断

1>非屏蔽中断通常被用于关键性硬件发生的错误,如内存错误,风扇故障,温度传感器故障
等。
2>可屏蔽中断是可以被CPU忽略或延迟处理的。
软中断

是在CPU执行指令(也就是说在进程正在运行的时候)的时候产生的,因为在执行指令时,
CPU(确切的说应是在CPU中的运算器)自身会产生一个异常(此处的异常也可理解为软中
断)。
1>硬中断是可屏蔽的,软中断不可屏蔽
2>软中断是执行中断指令产生的,而硬中断是由外设引发的
3>硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控
制器
4>硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上
半部
5>软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部

blog.51cto.com/noican/1355…

blog.csdn.net/zhangskd/ar…

37、动态内存分配(Dynamic memory allocation)

动态内存是相对静态内存而言的。所谓动态和静态就是指内存的分配方式。动态内存是指在堆
上分配的内存,而静态内存是指在栈上分配的内存
mallocfree 一定要成对存在,一一对应,只有动态创建的内存才能用 free 把它释放
掉,静态内存是不能用free释放的。静态内存只能由系统释放

38、请简述typedef的意义

typedef声明,简称typedef,为现有类型创建一个新的名字,或称为类型别名,在结构体定
义,还有一些数组等地方都大量的用到。

它有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法 。使用typedef可编写出更
加美观和可读的代码。所谓美观,意指typedef能隐藏笨拙的语法构造以及平台相关的数据类
型,从而增强可移植性以及未来的可维护性

blog.csdn.net/trochiluses…

39、用变量a给出下面的定义

a)一个整型数( An integer)

b)一个指向整形数的的指针(A pointer to an integer)

c) 一个指向指针的的指针,它指向的指针是指向一个整形数(A pointer to a pointer to an integer)

d)一个有10个整型数的数组 (An array of 10 integers)

e)一个有10个指针的数组,该指针指向一个整形数的(An array of 10 pointers to integers)

f)一个指向有10个整形数数组的指针(A pointer to an array of 10 integers)

g) 有一个整型的返回值函数

h)一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)

i)一个有10个指针的数组,该指针指向一个函数, 该函数有一个整型参数并返回一个整型数	(An array of ten poiners to functions that take an integer argument and return an integer)

a) int a;
b) int *a;
c) int **a;
d) int a[10];
e) int *a[10];
f) int (*a)[10];
g) int a(void);
h) int (*a)(int);
i) int (*a[10])(int);

40、写一个“标准”宏

#define MIN(x,y) ((x) < (y) ? (x) : (y))

41、A.c和B.c两个c文件中使用了两个相同名字的static 变量,编译的时候会不会有问题?这两个static 变量会保存到哪里(栈还是堆或者其他的) ?

静态存储区
编译器在编译的时候,对他们的命名是不同的

42、一个单向链表,不知道头节点,一个指针指向其中的一个节点, 问如何刪除这个指针指向的节点?

将这个节点复制成下一个节点的值,然后删除下一个节点