嵌入式面试题目(C/C++)

本文概述了C语言的一些关键概念,如switch语句参数类型、static的作用、引用与指针的区别、实时系统特性、全局和局部变量的区别、预编译、const和volatile关键字、指针数组与数字指针、内联函数优缺点、结构体与联合体、预处理作用、编译流程、#include的区别以及野指针的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C

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

switch 的参数不能为实型。
实型(Real type)是用来表示浮点数(即带有小数点的数值)的数据类型。C语言中的实型数据类型包括float、double和long double。
2. static 有什么用途?(请至少说明两种)
1)限制变量的作用域 
2)设置变量的存储域
static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长。
static修饰全局变量时,会改变全局变量的链接属性,从而使得全局变量的作用域变小(只能在本文件使用)。
static对函数的修饰与修饰全局变量十分相似,修饰函数时会改变函数的链接属性,从而使得函数的作用域变小(只能在本文件使用)。
3. 引用与指针有什么区别?
1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改变所指的对象。
2) 不存在指向空值的引用,但是存在指向空值的指针。
4. 描述实时系统的基本特性
在特定时间内完成特定的任务,实时性与可靠性
5. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

全局变量储存在静态数据库,局部变量在堆栈

6.什么是预编译
何时需要预编译:
1)总是使用不经常改动的大型代码体。
2)程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,
可以将所有包含文件预编译为一个预编译头。
7.
char * const p;
char const * p
const char *p
上述三个有什么区别?
char * const p; //常量指针,p 的值不可以修改

char const * p;//指向常量的指针,指向的常量值不可以改(不能用p更改)

const char *p; //同 char const *p

8.一个 32 位的机器,该机器的指针是多少位
指针是多少位只要看地址总线的位数就行了。80386 以后的机子都是 32 的数据总线。所以指针的位数 就是 4 个字节了。
9.写一个“标准”宏,这个宏输入两个参数并返回较小的一个。
#define Min(X, Y) ((X)>(Y)?(Y):(X)) 注意结尾没有;
10.嵌入式系统中经常要用到无限循环,你怎么用 C 编写死循环。
while(1){}或者 for(;;)
11.关键字 const 有什么含意?
表示常量不可以修改的变量。
12.关键字 volatile 有什么含意?并举出三个不同的例子?

volatile关键字分析,往往应用在三种场合

1)多线程编程共享全局变量的时候,该全局变量要加上volatile进行修饰,让编译器不要省略该变量的访问。

2)裸机编程的时候,某函数与中断服务函数共享全局变量的时候,该全局变量要加上volatile进行修饰,让编译器不要省略该变量的访问。

3)ARM定义寄存器的时候,寄存器是指向一个地址,要加上volatile进行修饰,让编译器不要优化而省略该变量的访问。

编译器不要优化该变量指的是防止编译器出现优化过度,导致代码运行失效。

13.指针数组和数字指针区别

数组指针 (*p)[n]

指针数组 *p[n]

指针数组:是数组——装着指针的数组。

数组指针:是指针——指向数组的指针

数组指针是一个指针变量,占有内存中一个指针的存储空间;

指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间     

14.内联函数(inline)的优点和缺点:空间换时间 

优点
1、内联函数,编译器将会根据上下文环境进一步深度优化代码,而这一点是普通函数无法达到的作用
2、省略了函数调用压入堆栈和弹出堆栈所用的时间
3、常用于高频使用的函数,可以提高时间

缺点
1)编译之后代码量会偏多,内联可以说是一种以空间换取时间的方法。牺牲一定的空间来加速运行时间。增加的代码量取决于函数调用次数和函数本身的大小
2)内联函数实现的更改往往并不可以保持链接兼容性,也就是说这样的更改需要用户重新编译他们的代码.

注意
1)inline实现其实就是对编译器的一种请求,因此编译器完全有权利取消一个函数的内联请求。一个好的编译器会根据函数定义体,自动取消不值得的内联,一般编译器会自动过滤选择一些短小而简单的函数来内联
2)使用内联函数的时候要注意:
(1)递归函数不能定义为内联函数
(2)内联函数一般适合于不存在while和switch等复杂的结构且只有1~5条语句的小函数上,否则编译系统将该函数视为普通函数。
(3)内联函数只能先定义后使用,否则编译系统也会把它认为是普通函数。
(4)对内联函数不能进行异常的接口声明。   

15. 结构体(struct)和联合体(union)的区别
        (1)结构体中的每个成员都占用独立的内存空间,而联合体中的所有成员共享同一块内存空间。这意味着,在联合体中只有一个成员变量能够同时存储值,而其他成员变量会被覆盖;

        (2)结构体的内存占用大小等于所有成员的内存大小之和,而联合体的内存占用大小等于其最大成员的内存大小,因此,联合体在内存占用上通常比结构体更节省;

        (3)结构体的成员可以同时被访问和修改,而联合体的成员只能同时访问和修改一个成员。这是因为联合体的成员共享同一块内存空间,修改其中一个成员会影响其他成员的值。

        (4)结构体的成员在内存中按照定义的顺序依次排列,而联合体的所有成员共享同一块内存空间,因此只有一个成员可以存储有效值;

        (5)结构体的初始化可以为每个成员分别赋值,而联合体的初始化只能对其中一个成员赋值。

16.预处理作用

而预处理主要做的就是:

- 头文件的展开(#include);
- 宏替换(#define);
- 条件编译的处理(#if #endif等);
- 去注释;
将.c文件变为预处理后的.i临时文件

17.描述c语言变成可执行文件过程:

18.include尖括号和双引号区别:

1.#include <xxx.h> 

系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。 

2.#include "xxx.h"

用户自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后在到C++安装目录(比如VC中可以指定和修改库文件查找路径,Unix和Linux中可以通过环境变量来设定)中查找,最后在系统文件中查找。 

这两种写法都是正确的写法,但是它们却是有区别的。我们知道C/C++已经有一些编写好的头文件(比如标准函数库等),它们存放在VC++的Include文件夹里。当我们使用#include <文件名>命令时,编译器就到这个文件夹里去找对应的文件。显然,用这种写法去包含一个我们自己编写的头文件(不在那个Include文件夹里)就会出错了。所以包含C++提供的头文件时,应该使用尖括号。

相反地,#include "文件名"命令则是先在当前文件所在的目录搜索是否有符合的文件,如果没有再到Include文件夹里去找对应的文件。因此,无论这个文件是C++提供的还是自己编写的,使用#include "文件名"命令一定是正确的。这也正是书中本节之前的程序一律使用#include "文件名"命令的原因。

19.什么是野指针:

        野指针就是指针指向的位置是未知的(随机的、不正确的、未明确限制的)。

野指针1:指针变量的值未被初始化 
        如果指针声明在全局变量区,那么未初始化的指针缺省为空。

        如果指针声明在栈区,那么该指针将随意指向一个地址空间。

野指针2: 指针所指向的地址空间已经被free或delete

        在堆上malloc或者new申请的地址空间,如果已经free或delete,那么此时堆上的内存已经被释放,但是指向该内存的指针如果没有人为的修改过,那么指针还会继续指向这段堆上已经被释放的内存

野指针3: 指针操作超越了作用域

超越了作用域,此时作用域里的内存已经被回收

解决办法:

1.初始化NULL;

2.申请内存后判空,malloc申请内存时判断是否为空

3.指针释放后置NULL;

C语言运算优先级,单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 > 条件运算符 > 赋值运算符 > 逗号运算符

1)算术运算符
                进行算术运算的符号,
                ++  -- :单目运算符
                        注意(++i)与(i++)是不同的。i++先输出后运算,++i先运算后输出
                        int i=1;
                        printf("%d",i++); // 1        printf("%d",++i); // 2
                        执行完此语句后i的值都是2;
                +  -   *    /   %  双目运算符
                /     得到的结果为商,如3/2 得到的结果为 1  .         5/2的结果为2
                % 得到的结果为余数   如6%4   得到的结果为 2
                当涉及到两个不同类型的数据,如  double 和 int ,需要向精准度高的转换。
    2)关系运算符
                用来判断两个数的大小关系
                <     >  >=   <= ==(等于)    !=(不等于)
                关系成立结果为1,不成立结果为0
                值得注意的是5>4>3 这种写法结果为0,步骤为,5>4结果为1,然后1>3结果为0
    3)逻辑运算符
                !逻辑非        单目运算符 “取反”
                &&  逻辑与     ||  逻辑或  双目运算符  具有(惰性运算!)
                逻辑真:(非0或者1)
                逻辑假:0
    4)位运算符
                按照bit位进行运算
                & 按位与                都是1则为1
                | 按位或                有1就为1
                ^ 按位异或             不同为1,相同为0
                ~ 按位取反            1变成0,0变成1
                <<按位左移        >>按位右移
                            左移    右边补0,舍弃超出的高字节
                            右移      丢弃右边的低字节,高字节,有符号补符号位,无符号(定义为unsigned)补0
        两个数交换,不涉及到第三个元素:
        {
            int a=3,b=4;
            a=a^b; 
            b=a^b;
            a=a^b;
         }
    5)赋值运算符:
                =  双目运算符,优先级只比逗号运算符高
                赋值运算符要求左边的操作数必须有左值(可写的地址)
                5=5 ERROR
                2+3 = 5 ERROR
                i++ = 5 ERROR    左边不能为表达式
                a += 5 += 6 ; ERROR             
    6)条件运算符
                expression  ? a :b
                如果表达式expression值为真,整个表达式的值就是a的值
                如果表达式expression值为假,整个表达式的值就是b的值 
    7)逗号运算符
                优先级最低,结合性从左至右 ,最后的值为最后一个的值
                a = (a = 6,b = 5);   //a = =5
                求值顺序: 
                先求表达式1的值,再表达式2的值 。。。。。最后
                求表达式n的值。
                整个逗号表达式的值就是表达式n的值。
    8)    指针表达式
                &取地址  *取值
    9)求字节运算符 sizeof 
                单目运算符 ,求一个对象或类型所占空间字节数。
                sizeof(x) 求x的类型,不要求x存在,但是要求x的类型是确定的
    10) 分量运算符: 
                求结构体中成员变量(分量)
                    .
                    ->        
    11) 下标运算符 
                [] 
                int a[10];    a[0]    a[1]
    12) 强制转换运算符 
                (类型)
                eg: 
                (unsigned char )255        
    

1、位(bit)
来自英文bit,音译为“比特”,表示二进制位。位是计算机内部数据储存的最小单位,11010100是一个8位二进制数。一个二进制位只可以表示0和1两种状态(21);两个二进制位可以表示00、01、10、11四种(22)状态;三位二进制数可表示八种状态(23)……。

2、字节(byte)
字节来自英文Byte,音译为“拜特”,习惯上用大写的“B”表示。
字节是计算机中数据处理的基本单位。计算机中以字节为单位存储和解释信息,规定一个字节由八个二进制位构成,即1个字节等于8个比特(1Byte=8bit)。八位二进制数最小为00000000,最大为11111111;通常1个字节可以存入一个ASCII码,2个字节可以存放一个汉字国标码。

3、
计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word)。一个字通常由一个或多个(一般是字节的整数位)字节构成。例如286微机的字由2个字节组成,它的字长为16;486微机的字由4个字节组成,它的字长为32位机。
计算机的字长决定了其CPU一次操作处理实际位数的多少,由此可见计算机的字长越大,其性能越优越。

另一种说法:


在计算机中,一串数码作为一个整体来处理或运算的,称为一个计算机字,简称宇。字通常分为若干个字节(每个字节一般是8位)。在存储器中,通常每个单元存储一个字,因此每个字都是可以寻址的。字的长度用位数来表示。
在计算机的运算器、控制器中,通常都是以字为单位进行传送的。宇出现在不问的地址其含义是不相同。例如,送往控制器去的字是指令,而送往运算器去的字就是一个数。
在计算机中作为一个整体被存取、传送、处理的二进制数字符串叫做一个字或单元,每个字中二进制位数的长度,称为字长。一个字由若干个字节组成,不同的计算机系统的字长是不同的,常见的有8位、16位、32位、64位等,字长越长,计算机一次处理的信息位就越多,精度就越高,字长是计算机性能的一个重要指标。目前主流微机都是32位机。
注意字与字长的区别,字是单位,而字长是指标,指标需要用单位去衡量。正象生活中重量与公斤的关系,公斤是单位,重量是指标,重量需要用公斤加以衡量。

字长

计算机的每个字所包含的位数称为字长。根据计算机的不同,字长有固定的和可变的两种。固定字长,即字长度不论什么情况都是固定不变的;可变字长,则在一定范围内,其长度是可变的。
计算的字长是指它一次可处理的二进创数字的数目。计算机处理数据的速率,自然和它一次能加工的位数以及进行运算的快慢有关。如果一台计算机的字长是另一台计算机的两倍,即使两台计算机的速度相同,在相同的时间内,前者能做的工作是后者的两倍。
一般地,大型计算机的字长为32―64位,小型计算机为12―32位,而微型计算机为4一16位。字长是衡量计算机性能的一个重要因素。

字节

字节是指一小组相邻的二进制数码。通常是8位作为一个字节。它是构成信息的一个小单位,并作为一个整体来参加操作,比字小,是构成字的单位。
在微型计算机中,通常用多少字节来表示存储器的存储容量。

字块

在信息处理中,一群字作为一个单元来处理的称为“字块”.也称“字组”。例如,储存于滋鼓的一个磁道上的字群就称为一个字块。在磁带上通常每120个字符就间隔一个字块际志,也称为一个字块。块与块之间一般留1.27―2.54厘米(1/2一1英寸)的间隔。在大容量存储中,信息都是以字块为单位而存入的,因此只有字块才是可选址的。目前,在高速绥冲技术中也引入了“字块”的概念。

另外:

比特即“bit”,还有一种称呼叫作“位”。

字节、字、位、比特,这四者之间的关系是:

1位=1比特
1字=2字节
1字节=8位
1字=16位

拓展内容:
二进制数系统中,每个0或1就是一个位(bit),位是数据存储的最小单位。其中8bit就称为一个字节(Byte)。计算机中的CPU位数指的是CPU一次能处理的最大位数。例如32位计算机的CPU一次最多能处理32位数据。

Bit,乃Binary digit(二进制数)位的缩写,是数学家John Wilder Tukey提议的术语(可能是1946年提出,但有资料称1943年就提出了)。这个术语第一次被正式使用,是在香农著名的《信息论》,即《通信的数学理论》(A Mathematical Theory of Communication)论文之第1页中。

位等价于比特(bit),是同一个单位。
比特(是最小的单位)bit

1字(word)= 2字节(byte)

1字节(byte) = 8位(bit)

并发是指一个处理器同时处理多个任务。 并行是指多个处理器或者是多核的处理器同时处理多个不同的任务

C/C++

1、new 和 malloc 的区别?

new 和 malloc 都是用于动态分配内存的方法,但是它们在以下方面存在一些区别:

内存分配位置:new 操作从自由存储区为对象动态分配内存空间,而 malloc 函数从堆上动态分配内存。自由存储区不仅可以是堆,还可以是静态存储区,这取决于 new 在哪里为对象分配内存。
返回类型安全性:new 操作符内存分配成功时,返回的是对象类型的指针,与对象类型严格匹配,无需进行类型转换,因此 new 是符合类型安全性的操作符。而 malloc 函数在内存分配成功后返回的是 void*,需要通过强制类型转换将 void* 指针转换成所需类型。
内存分配失败时的返回值:当 new 内存分配失败时,会抛出 bac_alloc 异常,不会返回 NULL;而 malloc 内存分配失败时,返回 NULL。
是否需要指定内存大小:使用new申请内存时,不需要指定内存块的大小,编译器会根据类型信息自行计算;而malloc则需要显式地指出所需内存块的大小。
总的来说,new和malloc在内存分配位置、返回类型安全性、内存分配失败时的返回值以及是否需要指定内存大小等方面存在差异。

在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区。


new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

自由存储区与堆是否是两块不同的内存区域?

划分自由存储区与堆的分界线就是new/delete与malloc/free。然而,尽管C++标准没有要求,但很多编译器的new/delete都是以malloc/free为基础来实现的。

那么借以malloc实现的new,所申请的内存是在堆上还是在自由存储区上?

从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还;而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。

基本上,所有的C++编译器默认使用堆来实现自由存储,也就是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,这时自由存储区就区别于堆了。我们所需要记住的就是:

  • 堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。
  • 堆与自由存储区并不等价。

那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

2、malloc的底层实现

(1)当开辟的空间小于 128K 时,调用 brk()函数,malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)

(2)当开辟的空间大于 128K 时,mmap()系统调用函数来在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟。

3、 在1G内存的计算机中能否malloc(1.2G)?为什么?

在1G内存的计算机中,无法使用malloc(1.2G)。

在C语言中,malloc()函数用于动态分配内存。当使用malloc()请求超过可用内存的大小时,会发生两种情况:

分配失败:如果请求的内存大小超过了操作系统可用的内存大小,malloc()将返回NULL,表示分配失败。在这种情况下,程序将无法继续执行。
分配成功但导致崩溃:如果请求的内存大小超过了可用的内存大小,但malloc()仍然返回了非NULL的指针,这通常是由于操作系统使用了内存映射技术或虚拟内存机制。这种情况下,程序可能会继续执行,但它可能会遇到未定义的行为,例如访问已分配的内存时崩溃或导致性能下降。
在1G内存的计算机中,由于可用内存只有1GB,因此无法使用malloc(1.2G),因为这将请求超过可用内存大小的内存大小。如果尝试这样做,malloc()将返回NULL,并且程序将无法继续执行。

4、指针与引用的相同和区别;如何相互转换?

引用时C++独有的特性,而指针则是C/C++都有的,它们有一些相似之处,但也有很多区别。

相同点:
它们都是用来间接访问内存的方式。
它们本身都占用内存(指针占用内存来存储所指向对象的地址,引用则是对象的别名,所以其实引用本身也是对象)。
它们都可以用来访问动态分配的内存(使用malloc()等函数)。
不同点:
指针是一个实体,它存储的是一个地址,指向内存的一个存储单元。而引用是原变量的一个别名,它存储的是原变量的值。
指针可以被重新赋值,指向不同的对象,而引用在声明后就不能改变其指向的对象。
指针可以被const修饰,而引用不能被const修饰。
sizeof运算符在操作引用时得到的是对象本身的大小,而操作指针时得到的是指针变量本身的大小。
相互转换:
指针转引用:将指针变量的值直接赋值给引用变量即可。
引用转指针:可以使用类型转换操作符(*)将引用转换为指针。
总的来说,指针和引用在功能和使用上有一些区别,需要根据具体情况选择使用哪种方式。

5、extern"C”的作用

extern "C"是一个C语言链接器的关键字,它有以下几个作用:

使得在C++中使用C编译方式成为可能,指明该函数使用C编译方式。

在某些情况下,使用extern "C"声明函数,可以使得C++编译器按照C语言的方式对函数进行编译和链接,从而能够正确地调用该函数。

extern "C"可以用于在C++中调用由C语言编写的库函数。因为C++与C语言的函数调用方式不同,使用extern "C"可以指明该函数使用C语言的方式进行链接,避免因为链接方式不同而导致的错误。

在使用动态链接库(DLL)时,使用extern "C"可以保证函数的导出和导入与C语言一致。

总之,extern "C"的作用是为了在C++中使用C语言的编译和链接方式,并且在一些特定情况下保证函数的正确导出和导入。

6、重写memcpy()函数需要注意哪些问题,(strcat strncat strcmp strcpy)那些函数会导致内存溢出?

重写 memcpy() 函数时需要注意以下问题:

源地址和目标地址的正确性:确保源地址和目标地址是有效的、合法的内存地址。在复制过程中,如果源地址无效或目标地址不足,会导致错误或未定义的行为。
复制的字节数:确保复制的字节数不超过目标地址的剩余空间,以避免内存溢出。在复制过程中,如果目标地址的剩余空间不足以容纳要复制的数据,会导致内存溢出。
目标地址的内存分配:如果目标地址是动态分配的内存区域(例如通过 malloc() 函数分配的内存),则在复制之前需要确保目标地址的内存已经分配,并且在复制完成后及时释放,以避免内存泄漏。
边界检查和错误处理:对于类似于 strcat()、strncat()、strcmp()、strcpy() 等函数,在处理字符串时需要注意边界检查,确保输入的字符串不会超过目标地址的限制。对于错误的输入,应该有适当的错误处理机制,例如报错或者返回错误码。
下面是关于 strcat()、strncat()、strcmp()、strcpy() 函数的一些注意事项:

strcat() 函数用于将一个字符串追加到另一个字符串的末尾。如果目标字符串的长度不足,或者目标字符串的剩余空间不足以容纳要追加的数据,就会导致内存溢出。
strncat() 函数类似于 strcat(),但是可以指定要复制的字节数。在使用 strncat() 时,需要确保目标字符串的剩余空间足够大,以避免内存溢出。
strcmp() 函数用于比较两个字符串是否相等。这个函数不会导致内存溢出,但是需要注意输入的字符串长度,如果输入的字符串过长可能会导致性能问题。
strcpy() 函数用于将一个字符串复制到另一个字符串中。在使用 strcpy() 时,需要确保目标字符串的长度足够大,以避免内存溢出。此外,还需要注意源字符串是否具有足够的空间,以避免访问越界的问题。
7、char 和 int 之间的转换

在C/C++中,可以使用以下方式将char和int之间进行转换:

将char转换为int:

使用强制类型转换,将char的ASCII码值转换为int。例如,对于char c = 'A';,可以使用int i = (int)c;来将c转换为int类型的i。
如果char类型的数据只包含ASCII码值,可以使用自然的隐式转换。例如,对于char c = 'A';,可以使用int i = c;来将c转换为int类型的i。
将int转换为char:

如果int类型的数据是ASCII码值,可以直接赋值给char类型的变量。例如,对于int i = 65;,可以使用char c = i;来将i转换为char类型的c。
如果int类型的数据不是ASCII

8、const 常量和 #define 的区别(编译阶段、安全性、内存占用等)

const 常量和#define 都是在C/C++中用于定义常量,但它们之间有一些重要的区别。

定义方式:
const 关键字用于定义常量,它是类型安全的,可以用于声明各种类型的常量,如整数、浮点数、字符等。而#define 是通过预处理器定义的,它可以定义任何类型的常量,包括字符串和其他复杂的表达式。

编译阶段:
const 是在编译阶段处理的,它在编译时被解析为常量表达式,并且可以直接内联到代码中。而#define 是由预处理器在编译预处理阶段处理的,它会被替换为相应的常量表达式。

类型安全:
const 关键字是类型安全的,它需要在声明时指定类型,并且在编译时进行类型检查。而#define 没有类型检查,它只是一个简单的文本替换,没有类型检查和语法检查。

内存占用:
const 常量是静态分配内存的,它们被存储在程序的数据区中,并且在编译时就已经确定了其值。而#define 只是一个符号,在编译预处理阶段被替换为相应的值,因此它不会分配内存,只是在编译时进行了一次文本替换。

可读性和安全性:
const 常量具有更好的可读性和安全性,它们可以提供更明确的语义,告诉读者该值是不能被修改的。而#define 只是一个预处理器指令,容易被误用或滥用,因此它的可读性和安全性相对较低。

综上所述,const 常量提供了更好的类型安全性和可读性,并且在编译时进行内存分配,同时也具有更好的性能。而#define 则更灵活,可以在编译预处理阶段进行一些复杂的操作,但它的类型安全性和可读性相对较低。在实际编程中,应根据具体情况选择使用 const 常量还是 #define。

9、变量的作用域(全局变量和局部变量)

变量的作用域是指变量在程序中可以使用的范围。在C/C++中,变量可以分为全局变量和局部变量两种。

全局变量(global variable):
全局变量是在函数外部定义的变量,它们的作用域是整个程序,可以从头文件一直使用到程序结束。全局变量通常在程序启动时初始化,并且可以被程序中的多个函数共同使用。
局部变量(local variable):
局部变量是在函数内部定义的变量,它们的作用域仅限于函数内部。当函数执行结束时,局部变量会被销毁,其内存空间也会被释放。
10、sizeof 与 strlen (字符串,数组)

sizeof() 是 C/C++ 中的运算符,用于获取变量或数据类型在内存中所占用的字节数。而 strlen() 是 C/C++ 中的函数,用于计算字符串的长度(不包括字符串结束符 '\0')。

对于字符串和数组,sizeof() 和 strlen() 的使用有以下区别:

字符串:

sizeof() 运算符用于获取整个字符串占用的字节数,包括字符串结束符 '\0'。例如,对于 char str[] = "hello";,sizeof(str) 的结果是 6(包括 '\0')。
strlen() 函数用于获取字符串的长度,即从字符串的起始地址开始,直到遇到结束符 '\0' 前,所经过的字节数。例如,对于 char str[] = "hello";,strlen(str) 的结果是 5。
数组:

sizeof() 运算符用于获取整个数组占用的字节数。例如,对于 int arr[] = {1, 2, 3};,sizeof(arr) 的结果是 sizeof(int) * 数组长度(即 sizeof(int) * 3)。
strlen() 函数通常不用于数组,因为数组没有结束符 '\0',无法计算其长度。
需要注意的是,sizeof() 运算符的结果是一个编译时确定的常量表达式,而 strlen() 函数需要在运行时逐个字符地计算字符串的长度。在使用时需要根据具体情况选择合适的函数,并注意避免越界访问和空指针异常等问题。

11、经典的sizeof(struct)和内存对齐(一字节对齐) 

在C语言中,sizeof()运算符用于获取变量或数据类型的大小。对于struct类型,sizeof()运算符将返回整个struct的大小,而不是每个成员变量的大小之和。这是因为C语言中的struct成员变量的对齐方式可能会导致struct的大小不是成员变量大小的整数倍。

内存对齐是为了提高内存访问的效率而引入的一种内存布局技术。在32位系统中最小的对齐单位是4字节,在64位系统中最小的对齐单位是8字节。根据对齐单位,编译器会在变量或结构体成员之间插入填充字节,使得它们的起始地址满足对齐要求。

对于一字节对齐,它通常用于一些特定的数据类型,例如char类型,它占用的空间就是1个字节。对于一些需要按照字节进行访问的数据类型,例如位域(bit-fields),也需要使用一字节对齐来保证数据的正确性。

在使用sizeof()运算符时,需要注意以下几点:

对于结构体或联合体类型,sizeof()返回的大小是整个结构体或联合体的占据的内存空间,而不是每个成员变量的大小之和。
在进行内存对齐时,需要考虑结构体成员变量的类型、大小和顺序,以及编译器和系统的对齐要求。合理地规划内存布局可以提高程序的性能和效率。
对于一些特殊的对齐要求,例如一字节对齐,需要使用特定的语法或技巧来实现,例如使用#pragma pack指令或者在结构体中使用特定的对齐修饰符(如__attribute__((packed))。
总之,sizeof()运算符和内存对齐是C语言中重要的概念,需要熟悉和掌握它们的使用方法和注意事项,以便更好地管理内存和提高程序的性能和效率。

推荐博客地址:http://t.csdn.cn/sAlrc

12、内存四区,什么变量分别存储在什么区域,堆上还是栈上。

在执行一个C/C++语言程序时,会将程序分配到的内存分为四个区域:栈区、堆区、全局区(静态区)和代码区。每个程序都有唯一的四个内存区域,我们需要熟悉和了解各个区域的特性,例如存储什么类型的数据,有谁去申请开辟,又有谁去管理释放。

栈区:栈区是用来存储函数调用和局部变量的一块内存区域,它是在程序执行时自动分配的,并且只在函数调用时使用,不会造成内存泄漏。在函数返回后,栈区会自动释放。
堆区:堆区是用来存储动态分配的内存的一块内存区域,它是由程序员分配和释放的。在程序执行时,通过malloc等函数从堆区分配一块内存,使用完后需要手动释放,否则会造成内存泄漏。
全局区(静态区):全局区是用来存储全局变量和静态变量的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。
代码区:代码区是用来存储程序代码的一块内存区域,它是在程序执行时就已经分配好并且一直存在直到程序结束时才被释放。
需要注意的是,这四个区域是按照顺序依次排列的,并且每个区域的存储数据类型和申请方式都有所不同,需要根据具体情况进行管理和使用。

<think>嗯,用户想准备C++相关的面试问题,我需要先整理一些常见的问题和答案。首先,基础问题肯定是必问的,比如面向对象的特性、指针和引用的区别、虚函数和纯虚函数这些。然后,内存管理部分也很重要,比如堆和栈的区别,new和malloc的不同,内存泄漏怎么处理。还有高级特性,像智能指针、多态的实现、虚函数表这些,都是常考点。 用户提供的引用里提到了析构函数为什么要声明为虚函数,这点需要详细解释,可能还要联系到基类指针指向派生类对象时的析构问题。另外,引用中还提到了垃圾回收机制,C++用的是析构函数,而Java是自动回收,这点对比可能也会出现在面试中。 接下来要考虑用户可能没有直接提到但相关的问题,比如const关键字的作用、static的用法、模板和STL相关的问题。还有设计模式,比如单例模式,虽然用户没提,但这也是常见问题。另外,C++11的新特性,比如智能指针、lambda表达式,也是面试热点。 要注意答案的结构清晰,每个问题分点回答,用代码示例帮助理解。比如解释虚函数时,可以给一个基类析构函数声明为虚函数的例子。智能指针部分,展示unique_ptr和shared_ptr的使用。 可能用户还需要了解如何优化C++程序,比如内存泄漏的检测工具,或者性能调优的方法,但根据引用内容,先集中在已有资料上,适当扩展。需要确保每个回答都正确,引用相关文献,比如引用中的析构函数部分要正确标注来源。 另外,用户可能需要问题及答案的深度,比如虚函数表的底层实现,虽然复杂,但可以简要说明机制,让回答更有深度。同时,要避免错误,比如虚函数表的实现是编译器相关的,但大致原理相同。 最后,生成的相关问题要覆盖不同难度,从基础到进阶,帮助用户全面准备。可能的问题包括多态的实现、智能指针类型、移动语义等,这些都是常见的后续问题。</think>以下是C++面试常见问题及详解(按知识点分类): ### 一、面向对象基础 1. **面向对象的三大特性** 封装(数据隐藏)、继承(代码复用)、多态(接口统一)[^3]。多态通过虚函数实现运行时绑定,例如: ```cpp class Animal { public: virtual void sound() { cout << "Animal sound"; } // 虚函数 }; ``` 2. **虚析构函数的作用** 基类析构函数声明为虚函数可确保正确释放派生类资源。若基类指针指向派生类对象,非虚析构会导致内存泄漏[^1][^3]: ```cpp class Base { public: virtual ~Base() {} // 必须声明为虚函数 }; ``` ### 二、内存管理 3. **堆与栈的区别** - 栈:自动分配/释放,大小固定(Windows默认1MB) - 堆:手动管理(new/delete),空间更大但可能碎片化 ```cpp int* p = new int(10); // 堆分配 delete p; // 必须显式释放[^2] ``` 4. **智能指针类型** - `unique_ptr`:独占所有权,不可复制 - `shared_ptr`:引用计数共享 - `weak_ptr`:解决循环引用问题 ### 三、高级特性 5. **虚函数表原理** 编译器为含虚函数的类创建虚函数表(vtable),每个对象包含指向vtable的指针(vptr),调用虚函数时通过vptr查找函数地址。 6. **const关键字的用法** - `const int* p`:指针指向的内容不可变 - `int* const p`:指针本身不可变 - `const成员函数`:承诺不修改对象状态 ### 四、关键机制 7. **引用与指针的区别** | | 引用 | 指针 | |----------|-------------|--------------| | 空值 | 必须初始化 | 可为nullptr | | 重定向 | 不可修改指向 | 可修改指向 | 8. **模板元编程示例** ```cpp template<typename T> T max(T a, T b) { // 函数模板 return a > b ? a : b; } ``` ### 五、设计模式 9. **单例模式实现** ```cpp class Singleton { private: static Singleton* instance; Singleton() {} // 私有构造函数 public: static Singleton* getInstance() { if (!instance) instance = new Singleton(); return instance; } }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值