C/C++基础笔试面试题-嵌入式软件工程师-Part1

2022/7/11 14:21:23

本文主要是介绍C/C++基础笔试面试题-嵌入式软件工程师-Part1,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

//前言:本文仅用于作者自己学习记录,如有侵权请联系删除

1.const

关键字const用来告诉编译器一个一旦被初始化过的变量就不能再修改.

1、起作用的阶段
    编译运行时起作用-同时有进行类型检查-且定义的只读变量只备份一次。
    //类型检查:验证操作接收的是否为合适的类型数据以及赋值是否合乎类型要求。
2、cosnt的三种应用场景:
    ①针对指针--指定指针本身为const/指定指针所指的数据为 const/指定两者为const
    例:
        char * const p;//常量指针,p 的值不可以修改
        char const * p;//指向常量的指针,指向的常量值不可以改
        const char *p;//和 char const *p 相同
        const int* const p; //指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。
        //如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
​
    ②在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
    ③对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;
3、常引用
        常引用声明方式:const  类型标识符  &引用名 = 目标变量名;
        用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。还能提高传参效率。

 

2.define与typedef

宏定义,C语言中预处理命令一种。分为无参宏定义和带参宏定义。

无参宏定义的一般形式为:#define 宏名 字符串;

带参宏定义的一般形式为:#define 宏名(参数表) 字符串;

1、起作用阶段
    在编译的预处理阶段起作用--简单的字符串替换,没有类型检查--宏常量在内存中备份若干。
​
2、什么是预编译,什么时候需要预编译?
    ①总是使用不经常改动的大型代码体。 
    ②程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。
​
3、#ifndef 宏替换名 语句段 #endif
    如果未定义#ifndef后面的宏替换名,则对“语句段1”进行编译;如果定义#ifndef后面的宏替换名,则不执行语句段。--是为了防止头文件被重复包含
​
4、#include <XXX.h> 和 #include "xxx.h" 的区别
    前者是先去开发环境提供的库头文件去寻找xxx.h文件
    后者是先去自己在工程中定义的xxx.h文件,没找到再去库里面找。
​
例:
    ①声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
    #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
    
    ②已知一个数组table,用一个宏定义,求出数据的元素个数
    #define NTBL ( sizeof(table) / sizeof(table[0]) )//整个数组大小/单个元素大小
    
    ③写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个
    #define MIN(a,b) ((a)>=(b))?(a):(b)
    //注意:在调用时一定要注意这个宏定义的副作用,如下调用:**
    •       ((++*p)<=(x)?(++*p):(x)。
    •       p 指针就自加了两次,违背了 MIN 的本意。
    ④typedef与define
     typedef允许你从一个现有的类型中创建一个新类型.
     define是直接对出现的位置照搬引用,有时候会出现意想不到的结果
            #define dPS struct s * 
            typedef struct s * tPS; 
     这两条语句:dPS p1,p2;//struct s * p1, p2;定义p1为一个指向结构的指,p2为一个实际的结构
               tPS p3,p4;//第二个例子正确地定义了p3 和p4 两个指针

 

3.static

数据类型用来给变量创建永久存储空间.

静态变量在函数间调用时保持他们的值不变.

当用在一个类中时,所有要用到静态变量的时候这个类将把这个变量镜像过去.

1、在函数体内static变量的作用范围为该函数体--限制作用域
    该变量的内存只被分配一次,一个被声明为静态的变量在这一函数被调用过程中维持其值不变//内存永久分配,值不变
    
2、在模块内(但在函数体外)--改变了存储方式即改变了生存期
   一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。//模块内,函数体外,相当于全局变量
​
3、在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用--在内存中只有一份
    那就是,这个函数被限制在声明它的模块的本地范围内使用。//本地使用
    
    //static 在 C 中主要用于定义全局静态变量、 定义局部静态变量、 定义静态函数。
    //在 C++中新增了两种作用:**定义静态数据成员、 静态函数成员。
    因为 static 定义的变量分配在静态区,所以其定义的变量的默认值为 0,普通变量的默认值为随机数,在定义指针变量时要特别注意

 

内存分布

 

4.volatile

简洁的说就是:volatile关键词影响编译器编译的结果,用 volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错。(具体可B站搜索视频了解被优化的具体过程)

应用场景
    ①多线程的全局变量
    //buffer指向一个地址被A线程使用,B线程修改了buffer所指的地址,同时希望A线程使用新地址,设置volatile
    
    ②中断处理程序中访问的全局变量
    
    ③用于修饰硬件控制器(寄存器)
    //比如说需要从寄存器中读取插拔的状态,如果不加关键字的话,编译器可能会读取一个没有意义的值
   
    例:一个参数既可以是const还可以是volatile吗?解释为什么
    是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

 

5.sizeof与strlen的区别

1、sizeof是一个操作符,strlen 是库函数。

2、sizeof的参数可以是数据的类型,也可以是变量,而strlen 只能以结尾为‘\0‘的字符串作参数。

3、编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。

4、并且 sizeof计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。

 

6.简述 strcpysprintfmemcpy 的区别

三者主要有以下不同之处:

1、 操作对象不同
strcpy 的两个操作对象均为字符串, sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串, memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。

2、 执行效率不同
memcpy 最高, strcpy 次之, sprintf 的效率最低。

3、 实现功能不同
strcpy 主要实现字符串变量间的拷贝, sprintf 主要实现其他数据类型格式到字符串的转化, memcpy 主要是内存块间的拷贝。

//说明: strcpy、 sprintf 与 memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

 

7.extern有什么用?

1、关键字extern用来告知编译器变量在当前范围之外声明过了.
​
2、被extern语句描述过的变量将分派不到任何空间,因为他们在别的地方被定义过了.
​
3、Extern语句频繁的用于在多个文件之间的跨范围数据传递.

 

8.用变量a给出下面的定义

a) 一个整型数(An integer) 
    int a

b) 一个指向整型数的指针(A pointer to an integer) 
    int * a 

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

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

e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
    int *a[10]

f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers) 
    int (*a)[10]

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

h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer ) 
    int (*a[10])(int)

 

9.操作特定位置的内存

例:要对绝对地址0x100000赋值 
    //访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的
    int *p;
    p=0x100000;
    *p=xxx;

10.分别给出bool,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

bool型变量:  if(!var)
 
    int型变量:    if(var == 0)
 
    float型变量:  const float epsinon = 0.00001;
 
    if ((x >= - epsinon) && (x <= epsinon)
 
    指针变量:  if(var == null)

11.main函数返回值

int main()  
​
{  int x=3;  printf("%d", x);  return 1;   } 
​
问函数既然不会被其它函数调用,为什么要返回1?
​
答案:mian中,**c****标准认为****0****表示成功,非****0****表示错误**。具体的值是某中具体出错信息

12.避免野指针

“野指针”产生原因及解决办法如下:
(1) 指针变量声明时没有被初始化。 解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向 NULL。
    
(2) 指针 p 被 free 或者 delete 之后, 没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向 NULL。
    
(3) 指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。
    
(4) 注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。
//没有初始化---没有free/delete---范围超过

13.局部变量能否和全局变量重名

能,局部会屏蔽全局。要用全局变量,需要使用"::"//C++中
    
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量,

对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,

比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

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

可以,在不同的C文件中以static形式来声明同名全局变量。

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

15.从堆中动态分配内存可能遇到的问题

主要有三种类型:内存泄露、内存碎片和内存崩溃  
​
内存崩溃是内存使用最严重的结果
​
主要原因有:
    数组访问越界、写已经释放的内存、指针计算错误、访问堆栈地址越界等等。
    碎片收集的问题,变量的持行时间等等

 

16.堆栈溢出一般是因为什么

1、 函数调用层次太深。
    函数递归调用时,系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。
    再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。

2、 动态申请空间使用之后没有释放。
    由于C语言中没有垃圾资源自动回收机制,因此,需要程序主动释放已经不再使用的动态地址空间。
    申请的动态空间使用的是堆空间,动态空间使用不会造成堆溢出。

3、 数组访问越界。
    C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。

4、 指针非法访问。
    指针保存了一个非法的地址,通过这样的指针访问所指向的地址时会产生内存访问错误。

展伸:
    堆溢出:不断的new 一个对象,一直创建新的对象,

    栈溢出:死循环或者是递归太深,递归的原因,可能太大,也可能没有终止。
    
    通常「堆栈溢出」是指「调用堆栈(call stack)的溢出」。
    要通俗地解释调用堆栈可能比较困难,因为它涉及许多其他计算机架构的知识。
    而这个答案只是简单地解释堆栈这种数据结构的特点──先进后出/后进先出。
    溢出是指这个数据结构满溢,不能存放更多数据。其他的数据结构也会遇到这个情况。
    即使数据结构并非固定容量,而是可扩展的,在有限的内存空间下仍是有满溢的机会。
    
    另外,很多时候,「调用堆栈溢出」的出现是与递归(recursion)相关的。
    我们可以把一些递归的实现改为迭代(iteration),但有时还是必须有一个自定义的堆栈数据结构,
    例如对树的深度优先搜索(Depth-First Search, DFS)。自定义的堆栈也是有溢出的可能。

所以,虽然堆栈溢出常指调用堆栈溢出,但本质上也只是一种数据结构的满溢情况。没有回收垃圾资源。

17.不能做switch()的参数类型是

switch的参数不能为实型。
注:必须是整数型常量,包括char,short,int,long等,不能是浮点数。
int main()
{
	float a=3;
	switch(a)
	{
		case 3:
		printf(“a”);
	}
	return 0;
}
//error C2450: switch expression of type 'float' is illegal	

 



这篇关于C/C++基础笔试面试题-嵌入式软件工程师-Part1的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程