笔试题最强合集

1、嵌入式系统和普通计算机有什么区别
1. 应用场景不同:嵌入式系统通常被嵌入到
某种设备中,如汽车、家电、医疗设备等,用
于控制、监测、处理等特定任务,而普通计算
机则主要用于一般计算、办公、游戏等应用。
2. 系统结构不同:嵌入式系统通常采用定制
化硬件和嵌入式操作系统,系统资源较为有限,
主要包括微处理器、存储器、外设等;普通计
算机则采用标准化硬件和操作系统,配置更加
灵活、丰富,能够处理更加复杂的应用任务。
3. 可扩展性不同:嵌入式系统的硬件和软件
都是为特定场景和任务而设计的,因此扩展性
相对较差,一旦确定了系统结构和功能,很难
进行改变。而普通计算机可以通过升级硬件或
软件来提升性能、增强功能,具有更高的可扩
展性。
4. 安全性要求不同:嵌入式系统往往需要保证
高度可靠和安全,一般采用特殊的设计和验证
方法来避免系统漏洞和故障。而普通计算机的
安全性要求相对较低,虽然也需要进行防病毒、
防火墙等安全措施,但一般不需要特殊的验证
方法。
2、能否解释下编译、汇编、链接的过程
1. 编译(Compile):编译是将高级语言(如
C/C++)源代码转换为汇编代码的过程。这个
过程由编译器完成。编译器会对源代码进行词
法分析、语法分析、语义分析、优化等操作,
生成对应的汇编代码。主要的作用是将源代码
转换为中间代码,减少程序员错误并提高执行
效率。
2. 汇编(Assemble):汇编是将汇编代码转
换为机器码的过程。这个过程由汇编器完成。
汇编器会将汇编代码转换为机器指令,生成目
标文件。目标文件里包含了机器指令、符号表、
重定位表等信息。主要的作用是将中间代码转
换为机器码,便于计算机直接执行。
3. 链接(Link):链接是将多个目标文件和共
享库文件合并成一个可执行文件或动态链接
库的过程。这个过程由链接器完成。链接器会
对目标文件进行符号解析和重定位,将所有文
件中引用的符号和定义的符号进行匹配,并生
成可执行文件或库文件。主要的作用是将多个
目标文件和库文件合并为一个整体,使程序能
够被操作系统装载运行。
3、如何处理多个中断 
多个中断(Multiple Interrupts)指在同一
时间内,处理器收到多个中断请求的情况。为
了处理多个中断,需要对中断进行优先级排序,
以确定哪些中断需要立即处理,哪些可以等待。
以下是处理多个中断的常见方法:
1. 中断优先级:将每个中断分配一个优先级,
当处理器同时收到多个中断请求时,会优先处
理优先级最高的中断。这种方法通常需要硬件
支持,使处理器能够判断中断的优先级。
2. 中断屏蔽:通过设置中断屏蔽寄存器,可
以屏蔽低优先级的中断,从而保证高优先级中
断的处理。这种方法可以有效地防止高优先级
中断被低优先级中断所干扰。
3. 中断处理程序:将所有中断请求存储在一
个队列中,按照优先级逐一处理。这种方法允
许多个中断同时处理,并且不需要硬件支持,
但是可能存在缓慢的响应时间。
总的来说,处理多个中断需要根据中断的优先
级和紧急程度来安排处理顺序,避免因中断处
理顺序不当而导致系统性能下降或出现异常
情况。
4、怎样保证嵌入式系统的实时性
保证嵌入式系统的实时性需要从以下几个方
面入手:
1. 硬件设计:硬件设计应该遵循简单、稳定、
高效的原则。可以采用硬件定时器、DMA、中
断控制器等硬件模块来处理实时任务,提高系
统的实时性。
2. 软件设计:软件设计应该遵循低耗能、高
效的原则。可以采用实时操作系统(Real-Time 
Operating System,RTOS)来管理任务,实现
任务优先级、调度算法、任务切换等功能,提
高系统的实时性。同时,在编写程序时要注意
使用轻量级数据类型和算法,减少开销和延迟。
3. 性能测试和优化:在系统设计完成后,需
要进行性能测试和优化,检测系统是否满足实
时性要求。可以通过模拟不同场景、压力测试
等方式,对系统性能进行评估,并对性能瓶颈
进行优化,提高系统的实时性。
5、嵌入式系统中如何进行调试 
嵌入式系统中调试的方法和普通计算机有所
不同,具体的调试方法和工具需要根据硬件和
软件的特性而定。以下是嵌入式系统中常见的
调试方法:
1. LED/Buzzer 调试:通过控制 LED 灯或蜂
鸣器发出声音来进行调试。这种方法可以快速
检查系统是否正常工作,但是只能提供很有限
的信息。
2. 串口调试:通过串口将系统输出信息输出
到终端或 PC 机上,从而对系统进行调试。这
种方法需要使用专门的调试工具或软件,比较
灵活,但是需要一定的硬件和软件支持。
3. JTAG 调试:JTAG(Joint Test Action Group)
是一种用于测试电路板和芯片的接口标准,可
以实现对芯片的调试、烧录和测试等功能。这
种方法需要使用专门的 JTAG 调试器,支持硬
件断点和监视变量等功能,比较全面。
4. 虚拟仿真调试:通过使用虚拟仿真软件(如
QEMU),可以在开发 PC 上模拟嵌入式系统,
进行调试和测试。虚拟仿真调试可以大大提高
调试效率,但是需要注意与实际硬件的差异。
6、请简述一下 I2C 总线协议
I2C(Inter-Integrated Circuit)总线协议
是一种串行数据传输协议,用于在集成电路之
间进行短距离、低速度的数据通信。I2C 总线
具有两根线:时钟线(SCL)和数据线(SDA)。
以下是 I2C 总线协议的主要特点和工作过程:
1. 特点:
- 多主从结构:I2C 总线协议支持多个主设备
(如微控制器)和多个从设备(如传感器、存
储器)共用一条总线。
- 硬件简单:I2C 总线只需要两根线即可完成
通信,在硬件实现上比较简单。
- 传输速率慢:I2C 总线的传输速率较慢,最
高速率只有 400Kbps。
2. 工作过程:
- 起始条件(Start Condition):主设备向
总线上发送一个低电平脉冲信号,表示开始一
个新的数据传输。
- 地址传输:主设备发送从设备的地址和读/
写标志位,通知从设备它需要进行读或写操作。
- 数据传输:主设备和从设备之间传输数据。
由主设备产生时钟信号和数据信号,从设备根
据时钟信号接收或发送数据。
- 停止条件(Stop Condition):主设备向总
线上发送一个高电平脉冲信号,表示数据传输
完成。
3. 应用:
- I2C 总线广泛应用于各种嵌入式系统中,如
智能手机、平板电脑、工业控制器等。
- 常见的 I2C 设备有温度传感器、光照传感
器、加速度传感器、液晶显示器等。
总之,I2C 总线协议是一种简单、灵活、可靠
的串行通信协议,适用于连接多个低速设备和
传感器的应用场景。
7、嵌入式系统中常用的微控制器都有哪些
1. AVR:是 Atmel 公司生产的一种低功耗、
高性能的 8 位微控制器。AVR 系列产品广泛
应用于消费电子、智能家居、工业控制等领域。
2. PIC:是 Microchip 公司生产的一种低成
本、易用性好的 8 位微控制器。PIC 系列产
品广泛应用于汽车、家电、医疗设备等领域。
3. STM32:是 ST 微电子公司生产的一种高性
能、低功耗的 32 位微控制器。STM32 系列产
品具有强大的处理能力和丰富的外设,被广泛
应用于消费电子、通讯设备、工业自动化等领
域。
4. MSP430:是德州仪器公司(TI)生产的一
种超低功耗、高性能的 16 位微控制器。
MSP430 系列产品广泛应用于便携式电子设备、
医疗器械、安防设备等领域。
5. ARM Cortex-M 系列:是英国 ARM 公司推出
的一种基于 ARM Cortex-M 内核架构的 32 位
微控制器。ARM Cortex-M 系列产品具有强大的
处理能力和丰富的外设,广泛应用于智能交通、
机器人、工业控制等领域。
8、请简述一下 SPI 总线协议
SPI(Serial Peripheral Interface)总线协
议是一种同步全双工的串行通信协议,用于在
集成电路之间进行数据传输。SPI 总线通常由
一个主设备和多个从设备组成,每个从设备都
有一个单独的片选信号。以下是 SPI 总线协
议的主要特点和工作过程:
1. 特点:
- 高速传输:SPI 总线具有高速传输的特点,
通信速率可达几十 MHz。
- 硬件简单:SPI 总线只需要少量线路即可完
成通信,可以通过硬件实现。
- 可靠性高:SPI 总线采用同步传输方式,数
据传输可靠。
2. 工作过程:
- 起始条件(Chip Select):主设备通过片
选信号(Chip Select)选择从设备,并发送
低电平信号,表示开始数据传输。
- 传输方式:主设备和从设备之间使用 MOSI 
和 MISO 两根线传输数据。主设备通过 MOSI 
发送数据,从设备通过 MISO 接收数据。
- 时钟信号:主设备通过 SCLK 时钟信号控制
数据传输速率,同时从设备也按照同样的时钟
频率来接收数据。
- 数据长度:SPI 总线没有固定数据长度的限
制,可以发送任意长度的数据块。
- 结束条件(Chip Deselect):主设备通过
片选信号(Chip Deselect)取消对从设备的
选择,结束数据传输。
3. 应用:
- SPI 总线广泛应用于各种嵌入式系统中,如
商业自动化、监控设备、网络通讯等领域。
- 常见的 SPI 设备有存储器、数字信号处理
器、传感器、显示器等。
总之,SPI 总线协议是一种高速、可靠、硬件
简单的串行通信协议,适用于连接多个设备和
传感器的应用场景。
9、嵌入式系统中的定时器有哪些应用场景 
1. 精确定时:在一些特殊场合需要对时间进
行精确控制,例如计时、计数、测量等。嵌入
式定时器可以精确地控制时间,提高操作准确
性和可靠性。
2. 调度程序:在实时应用中,如物联网、智
能家居等领域,需要对任务进行调度和管理。
定时器可以作为触发器,按照设定的时间间隔
来启动任务。
3. 脉冲生成:某些高精度机器或仪器需要定
期发送信号或者脉冲,以进行下一步操作或是
作为输入输出信号。嵌入式定时器可以通过配
置产生所需的周期性信号或脉冲,达到控制的
目的。
4. 延时:嵌入式系统中有许多需要延迟执行
的操作。嵌入式定时器可以提供硬件支持,方
便进行时间延迟操作。
总之,定时器是嵌入式系统的重要组成部分,
广泛应用于控制、测量、计时等领域。同时,
在人机交互设计中,也可以使用定时器来控制
用户界面的动画效果或者进行某些特定的提
示等操作。
10、如何进行单元测试和集成测试 
1. 单元测试:
- 步骤 1:编写测试用例。根据需求、功能模
块或代码的规范确定测试用例,包括输入数据、
期望输出等。
- 步骤 2:编写测试代码。编写测试代码实现
对所要测试的模块进行代码覆盖率达到 100%
的测试。可以使用自动化测试工具或者手动编
写测试用例。
- 步骤 3:运行测试。对编写的测试代码进行
运行,执行测试用例,得到测试结果。
- 步骤 4:分析结果并修复代码。分析测试结
果,根据错误类型修改代码。直到测试结果符
合预期为止。
- 步骤 5:重复以上步骤。不断优化测试用例
和测试代码的质量,直到达到测试设计和测试
覆盖率的目标。
2. 集成测试:
- 步骤 1:进行测试计划。制定集成测试的测
试计划,确定集成测试的目标、测试方法、测
试环境等。
- 步骤 2:进行测试方案设计。根据测试计划,
设计测试方案,制定测试策略和测试用例,明
确测试的步骤和要点。
- 步骤 3:执行测试方案。按照测试方案中的
步骤和用例,进行测试并记录测试结果。
- 步骤 4:分析测试结果。根据测试结果判断
软件系统是否符合要求,如果不符合则找出问
题原因,并制定相应的解决方案。
- 步骤 5:修正和改进。针对测试发现的问题,
修正代码和测试方案,不断优化测试质量和效
率,直到测试满足系统的要求。
总之,单元测试和集成测试是软件测试过程的
两个基本环节。进行单元测试和集成测试可以
有效地提高软件质量和可靠性。
11、嵌入式系统中如何进行软件更新 
1. 串口下载:使用串口连接计算机和嵌入式
设备,将新程序发送到设备中。这种方法速度
较慢,但是适用性广泛,基本上所有嵌入式系
统都具备串口工作能力。
2. USB 下载:通过 USB 接口实现从计算机向
嵌入式设备上传或下载数据。相较于串口下载,
USB 下载速度更快,同时具有良好的兼容性,
基本上所有现代化的嵌入式系统都支持 USB 
下载。
3. 网络下载:通过网络将升级程序传输到嵌
入式设备中。这种方法一般需要在设备中集成
网络接口,可以使用 TCP/IP 等应用层协议进
行数据传输,但必须保证网络稳定,否则会造
成数据传输的错误。
无论是哪种方式,软件更新需要先把新的程序
下载到设备中,然后再通过 bootloader 进行
升级。bootloader 是一个简单的操作系统,
只负责启动设备、加载内核并启动内核,一般
都会集成在芯片内部,提供了一些基本的功能,
如对 Flash 存储器的读、写等。
 
12、解释一下异步和同步通信的区别 
异步和同步通信是两种不同的通信方式,其主
要区别在于数据交互的时序性和数据处理的
方式。具体来说,异步通信是数据通过事件触
发或者回调函数的方式进行传递,而同步通信
则是通过阻塞或者轮询等方式进行传递。
1. 异步通信:当发送方发送数据时,接收方
并不会立即处理数据,而是通过回调函数或者
事件触发的方式告知发送方已经接收到数据,
并开始处理。这种方式的特点在于发送方和接
收方的操作是分离的,不需要等待接收方的响
应。异步通信常见的应用场景是客户端与服务
器之间的通信,例如 AJAX 技术。
2. 同步通信:当发送方发送数据时,会一直
等待接收方对数据进行处理并返回结果,只有
当接收方处理完毕后才会继续执行下一步操
作。这种方式的特点在于发送方和接收方是同
步的,必须等待每个请求的响应。同步通信常
见的应用场景是应用程序内部的模块之间的
通信,例如线程之间或进程之间的通信。
总之,异步和同步通信的区别在于数据的处理
方式和时序性。异步通信适用于数据处理方和
数据发送方相互独立的情况,而同步通信则适
用于需要等待响应结果的情况。在实际应用中,
根据具体的需求和场景来选择合适的通信方
式是非常重要的。
13、嵌入式系统中,如何选择合适的电源芯片 
在嵌入式系统中,选用合适的电源管理芯片是
非常重要的。一个好的电源管理方案不仅可以
提高系统的性能和稳定性,还可以延长系统的
寿命。以下是选用电源管理芯片时需要考虑的
一些因素:
1. 电源管理芯片功能:不同的电源管理芯片
具有不同的功能,如电源控制、电量监测、温
度保护、充放电保护等。在选择电源管理芯片
时,需要根据具体的需求和功能要求进行选择。
2. 电源管理芯片性能:电源管理芯片的性能
直接影响到系统的性能和稳定性。其中包括负
载能力、输出稳定性、效率、静态电流等指标。
在选择电源管理芯片时,要根据实际需求选择
性能合适的芯片。
3. 芯片的封装:电源管理芯片的封装形式有
多种,如 LGA、BGA、QFN 等。选择电源管理芯
片时,应根据实际应用环境和使用方式选择适
合自己的芯片封装。
4. 芯片的可靠性:电源管理芯片的可靠性对
系统的稳定性和使用寿命有着非常重要的影
响。在选择芯片时,应考虑其品牌、质量和可
靠性等方面的因素。
总之,在选择电源管理芯片时,需要根据具体
的需求、性能、封装和可靠性等因素进行综合
考虑,以确保系统的稳定性和性能。
14、请说明关键路径在嵌入式系统中的作用 
关键路径法 CPM(Critical Path Method)在
嵌入式系统中的作用类似于其他领域,即可用
于计算项目的最短工期和确定各个任务的执
行顺序。在嵌入式系统中,关键路径法可以帮
助开发人员更好地规划和管理项目进度,以确
保项目按时完成。
具体来说,在嵌入式系统开发中,一般需要同
时完成多项任务,这些任务往往存在依赖关系。
关键路径法可以通过建立任务之间的耦合关
系网络图,对任务的执行顺序进行评估和优化,
从而找到决定整个系统工期的关键路径,避免
任务间出现瓶颈和资源争用的情况。
另外,关键路径法还可帮助开发人员确定哪些
任务是必要的,哪些任务是可以延迟或者可以
简化的。这样可以有效地避免浪费时间和资源,
提高项目开发的效率和质量。
总之,关键路径法是嵌入式系统开发过程中一
个非常重要的工具,它可以帮助开发人员规划
和管理项目进度,优化任务执行顺序,避免任
务间的资源争用和瓶颈,从而提高整个项目的
效率和质量。
15、请简述 CAN 总线协议 
CAN 是 Controller Area Network(控制器局
域网)的缩写,是一种串行通信总线,常用于
工业控制、汽车电子等领域。CAN 总线协议是
指 CAN 总线上各个节点之间通信的规范和标准,
它定义了帧格式、标识符、数据传输方式等。
CAN 总线协议的主要特点如下:
1. 异步传输:CAN 总线上各个节点可以异步地
发送和接收消息,不需要进行时钟同步。这种
传输方式使得 CAN 总线在高速数据传输环境中
有很好的适应性。
2. 差分传输:CAN 总线使用差分传输,即将一
个信号与它的反相信号同时传输,可以有效地
抑制电磁干扰和噪声。
3. 抗干扰能力强:CAN 总线具有良好的抗干扰
和容错能力,在环境恶劣或者存在故障情况下,
仍然能够保持稳定的通讯。
4. 多主结构:在 CAN 总线中,各个节点等价,
没有主从之分,每个节点都可以发送和接收消
息。
5. 帧格式灵活:CAN 总线中的通讯帧格式非常
灵活,可以根据不同的应用需求进行设置。
总之,CAN 总线协议是工业控制和汽车电子等
领域中广泛使用的通讯协议,它具有异步传输、
差分传输、抗干扰、多主结构、帧格式灵活等
特点。在实际应用中,开发人员需要根据具体
的需求和应用场景选择合适的 CAN 总线协议进
行通讯。
16、怎样确保嵌入式系统的安全性 
嵌入式系统在工业控制、汽车电子等领域中得
到了广泛应用,通常需要处理机密性高、安全
要求严格的数据。因此,确保嵌入式系统的安
全性是非常重要的。以下是一些确保嵌入式系
统安全的措施:
1. 访问控制:嵌入式系统中的硬件和软件需
要实现访问控制机制,限制对系统的未经授权
的访问。
2. 加密保护:系统中的数据需要进行加密处
理,以防止敏感信息被窃取或者篡改。
3. 安全升级:及时修复系统中的漏洞和错误,
更新安全补丁,确保系统的可靠性和稳定性。
4. 防火墙:在嵌入式系统中设置防火墙,对
入侵者进行识别和阻拦,保护系统免受攻击。
5. 硬件保护:采用物理隔离或者加密技术等
手段,保护系统中涉及到的硬件设备,防止未
经授权的访问和攻击。
6. 用户教育:开发人员和维护人员需要深入
了解系统中的安全问题,并提高安全意识,避
免因人为因素导致的安全问题。
总之,确保嵌入式系统的安全性需要综合考虑
硬件、软件和人员等多个方面,采取有效的措
施保护用户数据不受损害。
17、嵌入式系统中如何进行软件设计与架构 
嵌入式系统的软件设计和架构是指在嵌入式
系统开发过程中,根据需求规格说明书确定软
件系统的整体结构、划分功能模块、确定每个
模块的实现算法以及编写具体的代码,形成软
件的设计方案和系统的架构。
通常,嵌入式系统中的软件设计与架构遵循以
下步骤:
1. 确定需求:在嵌入式系统开发前,首先需
要对需求进行全面的分析和规划。开发人员需
要明确系统的功能、性能、安全等方面的需求,
并将其转化为软件设计和系统架构的要求。
2. 制定设计方案:在明确系统需求后,开发
人员需要制定详细的软件设计方案。这包括确
定软件模块,确定模块之间的接口和交互方式,
选择适当的算法和数据结构,设计测试方案等。
3. 实现系统架构:根据软件设计方案,开发
人员需要设计嵌入式系统的架构,包括系统的
硬件、软件、网络、数据库等组成部分。开发
人员需要考虑系统的可扩展性、可靠性、安全
性等方面的因素。
4. 编写代码:在完成设计方案和系统架构后,
开发人员需要编写具体的代码实现。在编写代
码时,需要遵循软件开发规范和标准,并进行
测试和调试。
5. 系统集成和测试:在完成代码编写后,开
发人员需要对嵌入式系统进行集成和测试,包
括单元测试、集成测试、系统测试等阶段。
总之,嵌入式系统中的软件设计和架构是嵌入
式系统开发的重要环节,需要全面分析和规划
系统需求,制定详细的设计方案,设计系统架
构,编写代码并进行测试和调试。
18、嵌入式系统中常用的传感器有哪些 
嵌入式系统中常用的传感器种类繁多,不同的
传感器可以应用于不同的嵌入式系统中。以下
列举一些常见的嵌入式系统传感器:
1. 温度传感器:用于测量环境温度,根据不
同的应用场景可以选择不同类型的温度传感
器。
2. 湿度传感器:用于测量环境湿度,通常适
用于高精度、宽温度范围和高稳定性的应用。
3. 光敏传感器:用于检测光线强度和颜色,
通常适用于电子设备中的屏幕亮度和自动环
境光调节等方面的应用。
4. 加速度传感器:用于测量加速度、倾角等
物理量,具有广泛的应用领域,如安全监测、
运动检测等。
5. 磁场传感器:用于测量磁场强度、方向等
物理量,通常适用于导航、自动控制等应用。
6. 压力传感器:用于测量空气压力、流体压
力等物理量,通常应用于汽车、航空航天、医
疗、工业等领域。
以上仅是一些常见的嵌入式系统传感器,实际
应用中还有许多其他类型的传感器,例如气体
传感器、颜色传感器、声音传感器等等。不同
的传感器适用于不同的场景和环境,开发人员
需要根据实际需求选择合适的传感器。
19、请简述一下 UART 串口协议 
UART 串口通信协议是一种传输速度相对较慢
但应用广泛的异步串行通信协议。UART 通讯协
议中采用异步串行通信方式,将数据一位一位
地进行传输,同时在数据前后加上起始位和停
止位,以区分不同的数据帧。在 UART 通信协
议中,信号线上的状态为高电平时代表'1',
信号线上的状态为低电平时代表'0',这样就
可以将二进制数据通过串行通信方式传输。
UART 通讯协议中主要包含以下内容:
1. 数据格式:串口数据通信中需要定义每个
数据包的格式,常见的数据格式包括数据大小、
数据校验、起始位和停止位等字段。
2. 波特率和数据字长:UART 通信协议中使用
的波特率和数据字长需要与硬件设备相匹配,
通常情况下波特率越高,数据传输速度越快。
3. 传输模式:UART 通信协议中支持单向传输
和双向传输两种传输模式,根据具体应用场景
选择不同的传输模式。
4. 中断机制:UART 通信协议中可以设置中断
机制,当有数据到达时可以及时响应并进行处
理。
总之,UART 串口通信协议是一种常见的异步串
行通信协议,具有传输速度相对较慢但应用广
泛的特点。了解 UART 通讯协议的基本原理和
相关参数可以帮助开发人员更好地设计和开
发嵌入式系统中的串口通信功能。
20、嵌入式系统中时钟有什么作用 
嵌入式系统中的时钟具有非常重要的作用。时
钟可以提供精确的时间参考,使得设备能够按
照特定的时间表执行任务和操作,同时也为数
据传输、数据存储和处理等功能提供了必要的
时间基准。以下是时钟在嵌入式系统中的一些
常见应用:
1. 系统时钟:嵌入式系统中的系统时钟通常
由晶振、RTC(实时时钟)或其他外部时钟源
提供,用于控制整个系统的时序和频率,包括
CPU、总线控制器、定时器、UART 等外设。
2. 定时器:定时器是嵌入式系统中的一个重
要组件,可以通过设置定时器计数寄存器的值
来触发定时器中断,实现精确的时间控制和定
时任务的执行。
3. 时钟同步:嵌入式系统中的多个设备通常
需要进行时钟同步,以便各设备能够按照相同
的时间基准进行协同工作。时钟同步是复杂的
技术问题,通常需要采用专用的时钟同步协议,
如 IEEE 1588 和 NTP 等网络协议,或者基于 GPS
等外部时钟源进行同步。
4. 省电模式:一些嵌入式系统需要采用省电
模式,以延长电池寿命或减少功耗。时钟可以
帮助设备在一段时间内处于低功耗状态,并在
特定的时间点自动唤醒设备进行工作。
总之,时钟在嵌入式系统中应用广泛,不仅提
供了重要的时间参考,还支持各种时间控制和
数据处理功能。在嵌入式系统的设计和开发中,
需要根据实际需求选择合适的时钟源和时钟
同步方案,以确保系统的精确性和可靠性。
21、请说明 OSI 七层协议 
OSI 七层协议是开放式通信系统互联参考模型,
定义了网络通信体系结构的规范。每一层都有
特定的功能,从物理传输到应用程序,每层提
供服务和信息,向上一层提供服务,向下一层
请求服务。以下是 OSI 七层协议的具体说明:
1. 物理层(Physical Layer):定义物理设
备标准,如网线、光纤的接口类型等,在物理
媒介上传输比特流。
2. 数据链路层(Data Link Layer):将物理
层传送的数据可靠地传输到相邻节点,错误检
测与重发,分为逻辑链路控制和介质访问控制
两部分。
3. 网络层(Network Layer):处理分组到达
目的地址的路由选择和逻辑寻址。
4. 传输层(Transport Layer):为两台主机
提供端到端的传输服务,确保数据的可靠传输
和流量控制机制。
5. 会话层(Session Layer):管理不同计算
机之间的会话和进程,建立、管理和终止通信
会话,进行身份验证等。
6. 表示层(Presentation Layer):处理数
据表示和编码,在网络上传输数据时将数据转
换成一种通用的格式。
7. 应用层(Application Layer):面向用户,
提供各种网络服务,包括发送邮件、网页浏览
等。
总之,OSI 七层协议是一个广泛采用的网络通
信协议,每一层的特定功能都支持了网络通信
不同的方面,实现了数据从源端到目的地的可
靠传输。在互联网技术应用中,OSI 七层协议
被广泛使用,为网络通信提供了强大的规范和
标准化支持。
22、嵌入式系统中常用的通信接口有哪些 
1. SPI 接口: SPI ( Serial Peripheral 
Interface)串行外设接口,是一种基于全双
工传输方式的通信协议。SPI 接口使用四根线
来完成通信,包括时钟线、数据输入线、数据
输出线和片选线等。SPI 接口被广泛应用于
Flash 存储器、ADC/DAC 芯片等外设。
2. I2C 接口:I2C(Inter-Integrated Circuit)
总线是一种基于地址识别方式的串行通信协
议。使用 I2C 接口可以连接多个芯片,实现相
互之间的通信,在嵌入式系统中使用较为广泛,
例如连接温度传感器、EEPROM 等。
3. UART 接口:UART(Universal Asynchronous 
Receiver/Transmitter)串行通讯接口,是一
种异步串行通信协议。UART 接口只需要一对单
向传输数据线即可实现双向通信,常用于与 PC
机进行数据通信。
4. CAN 接口:CAN(Controller Area Network)
总线是一种数据通信标准,主要用于车载电子
和工业领域等环境下的数据通信。CAN 总线采
用差分信号传输,具有抗干扰能力强、传输速
率高等优点,是工业自动化领域中广泛使用的
通讯接口。
总之,以上这些通信接口在嵌入式系统中应用
非常广泛,可以满足不同场景的通信需求。在
选择通信接口时,需要根据实际需要选择具体
的接口类型,以满足嵌入式系统的通信需求。
23、如何解决嵌入式系统中的内存管理问题 
1. 合理设计内存分配方案:在嵌入式系统中,
内存资源通常非常有限,需要合理规划内存使
用,并按照需求分配不同类型的内存空间。可
采用动态内存分配算法、静态内存分配算法等
来实现合理的内存分配方案。
2. 优化代码结构和算法设计:在嵌入式系统
中,程序的运行效率也十分重要,因此需要优
化代码结构和算法设计,减少内存占用量,提
高程序效率,例如使用基于栈的数据结构,避
免频繁的内存分配和释放等。
3. 使用内存保护机制:内存保护机制可以有
效避免内存泄漏、越界访问等内存管理问题,
可以使用硬件或软件实现。其中,硬件实现可
使用 MMU(Memory Management Unit)等机制,
通过虚拟内存技术来保护内存;软件实现可使
用内存保护模块等工具,防止非法内存访问等
问题。
4. 应用开发框架:应用开发框架通常包括了
内存管理的相关 API 和函数库,可以有效简化
内存管理的过程。例如,使用 RTOS(实时操作
系统)来进行嵌入式系统开发,大多数 RTOS
都提供了内存管理 API 和工具,开发人员可以
更加便捷地管理内存。
总之,解决嵌入式系统中的内存管理问题需要
综合考虑内存分配、代码结构优化、内存保护
机制等方面的问题,选择合适的处理方案,并
且在开发过程中注意保证程序效率和稳定性。
24、嵌入式系统中存在哪些功耗优化技术 
嵌入式系统是一种功耗非常敏感的计算机系
统,因此在设计嵌入式系统时需要采用一些功
耗优化技术来降低功耗,延长电池寿命。常见
的功耗优化技术包括:
1. 低功耗 CPU(CPU Low-power):选择功耗
较低的 CPU,能够降低嵌入式系统整体的功耗。
例如,在选择通用处理器时,可以选择低功耗
的 ARM Cortex-M 系列芯片。
2. 休眠模式(Sleep Mode):使用休眠模式
可以将嵌入式系统中未被使用的部分关闭,并
且降低系统的硬件频率和电压,从而达到节能
的效果。在嵌入式系统中使用休眠模式可以节
省大量的功耗。
3. 内存优化(Memory Optimization):在嵌
入式系统中,内存访问速度也会影响系统功耗。
因此,可以通过采用快速内存、静态内存等方
式来优化内存使用效率,减少功耗。
4. 芯片级别的功耗优化:在设计芯片时,也
需要考虑功耗优化。例如,采用多层金属互连
技术可以降低电阻和电容的损失,减少功耗。
同时,采用功耗较小的逻辑电路和 ETC 结构可
以降低芯片功耗。
5. 高 效 能 源 管 理 ( Effective Energy 
Management):通过分析嵌入式系统中的功耗
瓶颈,设计高效的能源管理方案来控制功耗。
例如,使用智能充电技术、基于实时功耗监测
的能源管理系统等方式。
总之,嵌入式系统中的功耗优化技术需要从硬
件和软件两方面进行考虑,常见的功耗优化技
术包括低功耗 CPU、休眠模式、内存优化、芯
片级别的功耗优化以及高效能源管理等方面,
以达到延长电池寿命和节省功耗的目的。
25、嵌入式系统中的噪音问题应该如何解决 
1. 电路隔离:采用适当的隔离措施,例如使
用屏蔽罩、隔离接口等,将敏感电路与干扰源
隔开,减少外界噪声对电路的影响。
2. 优化布线:在设计布线时,应尽量避免长
距离的传输线路,减少传输线路对电路的干扰。
同时,对于高频干扰源,应该采用阻抗匹配等
技术来减少反射和传输线路上的波动。
3. 降噪滤波器:在信号输入端加入合适的滤
波器,例如 RC 滤波器、电感滤波器等,可有
效降低信号中的噪声,提高信号质量。
4. 选用低噪声元件:在选择器件时,应尽量
选用低噪声和抗干扰性能更好的元件,例如低
噪声放大器、抗干扰芯片等。
5. 增加地线:嵌入式系统中地线的设计也很
重要,应该经常增加地线,减少地线的阻抗和
光耦隔离等。
总之,嵌入式系统中的噪音问题需要综合考虑
硬件和软件两方面的因素,采取适当的隔离措
施、布线优化、滤波、选择低噪声元件以及增
加地线等措施,才能有效降低噪音的干扰,提
高嵌入式系统的可靠性和稳定性
26、请简述一下 USB 协议 
USB(Universal Serial Bus,通用串行总线)
是一种用于计算机和外部设备之间传输数据
的标准接口。USB 协议定义了数据传输的方式、
数据编码格式、设备接口等细节,同时还规定
了 USB 插口形状和电气规范。
USB 协议包括三个主要的组成部分:主机控制
器、USB 设备、USB 连接线。主机控制器负责
控制总线的操作;USB 设备接收和发送数据,
包括键盘、鼠标、打印机等;USB 连接线连接
主机控制器和 USB 设备。
USB 协议支持不同类型的传输模式,包括控制
传输、批量传输和中断传输。控制传输用于管
理和查询 USB 设备,批量传输用于传输大块数
据,中断传输则用于高速传输小块数据,例如
鼠标或键盘的输入数据。
USB 协议在各种计算机和嵌入式系统中都广泛
应用,由于其方便性和通用性,使得设备之间
的连接和交互变得更加简单和可靠。
27、嵌入式系统中如何进行电路板设计 
在嵌入式系统中,电路板设计是将电路原理图
转换为物理电路的过程,通常分为如下几个步
骤:
1. 原理图设计:根据系统需求,设计出嵌入
式系统所需要的电路原理图。原理图设计工具
有很多,例如 Altium Designer、Cadence 
Allegro 等。
2. PCB 布局设计:根据原理图设计,将各个元
器件放置在 PCB 板面上,并设计电路走线路线。
在进行 PCB 布局设计时,需要考虑尽量缩小板
面大小、简化走线、减少干扰等因素,保证设
计的可靠性和稳定性。
3. 电气规则检查(ERC):设计完成后需要通
过电气规则检查,对电路进行逐一检测,排除
错误,确保电路可用性。
4. 生成制造文件:生成针对 PCB 加工所需的
Gerber 文件,包括钻孔图、PCB 层图等制造文
件。
5. PCB 加工生产:将 Gerber 文件提供给 PCB
加工厂家进行 PCB 加工,根据需要还可以进行
丝印、防护油喷涂、焊锡等处理。
6. 测试调试:完成加工生产后,还需要对电
路板进行测试调试,确保系统正常工作。
总之,嵌入式系统中的电路板设计需要经过原
理图设计、PCB 布局设计、电气规则检查、制
造文件生成、PCB 加工生产和测试调试等多个
步骤,通过逐步审核和测试,确保设计与加工
质量、电路性能达到预期要求。
28、嵌入式系统中使用的开发环境有哪些 
1. Keil MDK:Keil MDK 是一种非常流行的嵌
入式开发工具,它包括了很多功能模块,如编
译器、调试器、仿真器、实时操作系统等,支
持多种芯片。
2. IAR Embedded Workbench:IAR Embedded 
Workbench 是一种集成化的开发环境,提供编
译器、调试器、跟踪器等各种工具,支持多种
平台和多种 MCU。
3. Eclipse:Eclipse 是一种免费的 IDE,可
以通过插件进行扩展和定制,支持多种嵌入式
编译器,提供了 C/C++、Java 等多种编程语言
的支持。
4. Code Composer Studio :Code Composer 
Studio 是德州仪器公司(TI)为其 DSP 和微控
制器产品设计的开发环境,支持多种编程语言,
包括 C 语言、C++、Assembly 语言等。
5. Visual Studio:Visual Studio 是微软公
司的开发集成化环境,可以通过插件或 SDK 支
持多种编程语言,包括 C/C++、C#等。同时,
可配合使用 Visual Studio Code 对代码进行
编辑和调试。
总之,不同的嵌入式系统开发环境有着各自的
特点和优势,开发者需要根据硬件平台和工作
需求选择合适的开发环境。
29、嵌入式系统中的 ADC 和 DAC 有什么作用 
ADC 和 DAC 是嵌入式系统中的两种重要的模数
转换器,用于数字信号和模拟信号的相互转换。
ADC 将模拟信号转换为数字信号,DAC 将数字
信号转换为模拟信号。它们在嵌入式系统中有
着很重要的作用。
ADC 主要用于采集外界传感器、电路等模拟信
号,并将其转换成数字信号,供处理器进行数
字信号的处理和分析。比如,温度传感器、光
线传感器、声音传感器等都需要使用 ADC 模块
来获取模拟信号。
DAC 则用于输出数字信号,并将其转换成模拟
信号供外部电路使用。比如,在音频输出电路
中,DAC 模块用于将数字音频信号转换成模拟
音频信号,以供音响系统播放声音。DAC 在驱
动 LED 显示器、步进电机、电流输出等方面也
具有广泛的应用。
在嵌入式系统中,ADC 和 DAC 的精度和速度都
是非常重要的参数。高精度和高速度的 ADC 和
DAC 可以提高系统的准确性和响应速度。同时,
还需要考虑 ADC 和 DAC 的功耗,以保证系统的
整体功耗水平。
总之,ADC 和 DAC 在嵌入式系统中是非常重要
的模数转换器,广泛应用于信号采集、数据处
理和控制输出等方面,对于保证嵌入式系统的
精度、速度和功耗水平具有重要意义。
30、请简述一下 BLE 协议 
BLE(Bluetooth Low Energy),也称作“蓝
牙低功耗”,是一种基于蓝牙技术的低成本、
短距离、低功耗、可互操作的无线通信技术。
BLE 协议是蓝牙 4.0 和 5.0 版本新增的一项特
性,广泛应用于智能家居、健康监测、运动健
身、物联网等领域。
BLE 协议相对于传统蓝牙协议,具有以下优点:
1. 低功耗:BLE 通过减小蓝牙信号传输的时间
来降低功耗,使得终端设备可以使用更小的电
池,从而延长电池寿命。
2. 短距离:BLE 适用于短距离通信,通常应用
于建筑物内、车内、人体周围等距离较短的场
景。
3. 快速连接:BLE 建立连接速度非常快,这对
于需要快速发送数据的应用场景非常重要。
4. 互操作性:BLE 协议可以与其他蓝牙设备无
缝衔接,使用起来非常方便。
BLE 协议支持两种数据传输方式:广播和连接。
广播适用于无需建立连接的数据传输场景,例
如广告推送、周边设备探索等。连接适用于建
立连接后进行数据传输的场景,例如传感器数
据采集、远程控制等。
总之,BLE 协议在智能家居、健康监测、运动
健身、物联网等众多领域有着广泛的应用,通
过低成本、低功耗、快速连接和互操作性等优
点,为无线通信提供了更加灵活和便捷的解决
方案。
31、嵌入式系统中采用的操作系统有哪些 
1. 实 时 操 作 系 统 ( Real-time Operating 
System,RTOS):实时操作系统是一种专门为
嵌入式系统设计的操作系统,其最主要的特点
是能够实时响应,满足实时任务的需求。实时
操作系统可以根据不同应用的需要进行定制
和优化,以提高系统性能和稳定性,同时还可
以方便地进行任务调度、内存管理等功能。
2. 轻量级操作系统(Lightweight Operating 
System,LOS):轻量级操作系统通常采用微
内核结构,具有体积小、速度快、可裁剪和可
定制等特点。轻量级操作系统适用于资源受限
的嵌入式系统,如物联网设备、传感器等。
3. 嵌入式 Linux 操作系统:嵌入式 Linux 操
作系统是基于 Linux 内核的一种操作系统,相
对于其他操作系统,它具有成熟的软件生态系
统和强大的开发工具链,使用起来更加方便,
同时也支持多种处理器架构。
4. 其他操作系统:还有其他一些适用于嵌入
式系统的操作系统,如 Windows Embedded、
VxWorks、Nucleus、FreeRTOS 等。
32、嵌入式系统中浮点运算问题应该如何解
决 
嵌入式系统中的 CPU 通常都不支持硬件浮点运
算,因此需要使用软件实现浮点数运算。不过,
软件实现的浮点运算会消耗较多的 CPU 和内存
资源,导致系统性能下降,因此需要采用一些
优化方法来解决。
以下是解决嵌入式系统中浮点运算问题的一
些方法:
1. 采用固定点运算代替浮点运算:将小数形
式的数字以整数形式存储,并使用程序进行相
应的处理,从而实现数值的计算。虽然这种方
法会增加代码量并增加设计难度,但可以显著
提高系统性能。
2. 选择适当的浮点库:选择使用适当的浮点
库,可以最大程度地提高性能和精度。可以根
据应用需求选择合适的浮点库,如 Microchip
的 FPU 库、TI 的 MathLIB 库等。
3. 优化浮点运算代码:通过对浮点运算代码
进行优化,如使用循环展开、减少除法运算等
方法,可以提高系统的效率。同时,还可以使
用编译器提供的优化选项进行优化,如使用
-O2 选项等。
4. 使用硬件浮点运算加速器:如果嵌入式系
统的 CPU 支持硬件浮点运算加速器,在特定的
计算任务中可以使用硬件浮点运算加速器来
提高运算速度。
总之,在嵌入式系统中实现浮点运算需要根据
具体的应用场景和系统资源情况进行选择,结
合以上方法可以有效地解决浮点运算问题。
33、在嵌入式系统中,如何解决数据储存问题 
在嵌入式系统中,数据存储通常使用闪存、
EEPROM 或者 SD 卡等非易失性存储器件。这些
存储器件都有一定的容量和读写次数限制,因
此需要一些特殊的技术来解决数据存储问题。
以下是在嵌入式系统中解决数据存储问题的
一些方法:
1. 数据压缩:使用数据压缩算法可以有效地
减小数据存储空间。在压缩过程中需要考虑压
缩速度和解压速度,以及压缩后的数据格式是
否易于处理。
2. 数据加密:对于一些敏感的数据,可以使
用数据加密算法来保护数据的安全。加密算法
需要考虑加密强度和加密速度,并且要保证解
密的可靠性和正确性。
3. 数据备份:对于一些重要的数据,需要进
行数据备份,以防止数据损坏或丢失。备份数
据可以存储到其他非易失性存储器件、云存储
等位置。
4. 文件系统:使用文件系统可以方便地管理
和操作数据,如读取、写入、删除等操作。文
件系统需要选择适合嵌入式系统的文件系统
类型,如 FAT16、FAT32 等。
5. 数据压平:对于一些结构化数据,可以将
其压平为一维数组进行存储,这样可以减小数
据存储空间。在处理时需要考虑压平的复杂度
和还原数据的难易程度。
总之,在嵌入式系统中解决数据存储问题需要
综合考虑数据的容量、读写速度、安全性等因
素,并选择适当的技术来处理。
34、请简述一下 TCP/IP 协议 
TCP/IP 协议是一组用于互联网通信的标准协
议,也被称为传输控制协议/因特网协议
(Transmission Control Protocol/Internet 
Protocol)。它由两个主要协议组成,即 TCP
和 IP。
TCP(Transmission Control Protocol)是一
种面向连接的、可靠的数据传输协议,它可以
保证数据能准确无误地送达目的地,并能按照
数据发送的顺序依次接收。TCP 协议还具有拥
塞控制和流量控制等重要功能,以保证网络传
输过程中的稳定性和高效性。
IP(Internet Protocol)是一种无连接的、
非可靠的协议,它负责将数据包从源地址传输
到目的地址。IP 协议不保证数据传输的可靠性
和正确性,但是它对网络拓扑结构不敏感,可
以适应复杂的网络环境。
TCP/IP 协 议 还 包 括 UDP ( User Datagram 
Protocol,用户数据报协议)和 ICMP(Internet 
Control Message Protocol,因特网控制报文
协议)等其他协议。UDP 是一种无连接协议,
它只提供数据传输的最基本功能,没有可靠性
和保证数据顺序的功能;ICMP 负责处理各种网
络异常情况,如网络拓扑变化、主机不可达等。
总之,TCP/IP 协议是互联网通信的标准协议,
包含了 TCP、IP、UDP 和 ICMP 等多个协议,它
们共同协作完成数据的传输、路由、控制和异
常处理等各种功能,为互联网的稳定性和高效
性提供了保障。
35、嵌入式系统中常用的调试工具有哪些 
1. 仿真器/调试器:仿真器/调试器是一种硬
件设备,它能够连接到嵌入式系统的 CPU 的调
试接口上,提供对系统的硬件和软件进行调试、
仿真和测试等功能,包括单步执行、查看寄存
器值、内存监视等。
2. 调试串口:调试串口通常是通过 UART 协议
连接到嵌入式系统的串口上,可以用来实时监
视和调试系统的运行状况,并且还可以通过串
口发送和接收数据。
3. JTAG 接口:JTAG 接口是一种标准的测试和
调试接口,适用于各种类型的数字电路,并且
可以通过 JTAG 接口在系统运行时读取 CPU 的
内部寄存器、内存和 I/O 端口等信息。它可以
提供系统级别的跟踪和分析,可以解决复杂的
硬件和软件问题。
4. 系统监视器:系统监视器是一种软件工具,
它可以在嵌入式系统运行时监视系统的运行
情况,包括内存使用率、CPU 利用率、任务调
度等信息,并且还可以记录和分析故障信息,
对系统进行性能优化。
总之,嵌入式系统的调试工具种类繁多,选择
适合的调试工具需要考虑系统的架构和特点、
调试需求和可用资源等因素,并综合使用多种
调试工具来解决复杂的问题。
36、嵌入式系统中如何进行驱动程序设计 
嵌入式系统中的驱动程序设计主要包括以下
几个步骤:
1. 确定需要驱动的硬件:首先需要明确需要
驱动的硬件设备,如 IO 口、串口、SPI 总线、
I2C 总线等。
2. 了解硬件设备的规格和接口:需要了解硬
件设备的相关规格和接口,以便正确地对其进
行驱动控制。
3. 编写设备驱动程序:编写设备驱动程序是
完成驱动程序设计的核心部分,其目的是编写
代码将软件和硬件相连接。驱动程序需要调用
底层的 HAL 库,通过读写寄存器来实现对硬件
设备的控制。
4. 进行测试和调试:驱动程序编写完成后,
需要进行测试和调试,以保证驱动程序能够正
常运行并与硬件设备进行可靠的通信。
在驱动程序设计过程中,还需要注意以下几点:
- 确保代码的可移植性:由于嵌入式系统的硬
件环境存在差异,需要编写具有可移植性的驱
动程序,以便适应不同的硬件平台。
- 确保代码的可重用性:驱动程序应该采用模
块化的设计方式,尽可能地实现代码的可重用
性,以减少代码冗余和提高开发效率。
- 确保代码的稳定性和安全性:驱动程序涉及
到对硬件设备的直接控制,必须保证代码的稳
定性和安全性,以避免出现系统崩溃、数据丢
失等严重问题。
总之,在嵌入式系统中进行驱动程序设计需要
深入了解硬件设备的规格和接口,采用模块化
的设计方式,编写具有可移植性、可重用性、
稳定性和安全性的代码,并进行测试和调试,
以确保驱动程序能够正常运行并与硬件设备
进行可靠的通信。
37、嵌入式系统中常见的错误类型有哪些 
1. 程序逻辑错误:程序逻辑错误是指程序设
计中的逻辑错误,比如算法错误、条件判断错
误等。
2. 内存错误:内存错误是指程序对内存进行
越界访问、空指针访问等操作,导致系统崩溃
或异常。
3. 硬件错误:硬件错误是指硬件设备出现故
障或者因为硬件的质量、使用环境等原因而引
起的系统异常或崩溃。
4. 编译链接错误:编译链接错误是指在编译
和链接过程中出现的错误,比如变量未定义、
函数符号未找到等。
5. 通信错误:通信错误是指系统进行通信时
发生的错误,比如数据传输错误、超时等。
6. 操作系统错误:操作系统错误是指操作系
统出现故障或者因为配置问题、权限问题等原
因而导致的系统异常或崩溃。
总之,嵌入式系统中常见的错误类型非常多,
除以上列出的外还有很多其他类型的错误。在
实际开发中需要深入了解系统的架构和特点,
采用合适的调试工具和技术来进行错误定位
和修复,并且在程序设计中尽可能考虑各种异
常情况,提高系统的容错能力和稳定性。
38、如何保护嵌入式系统的知识产权 
1. 注册软件著作权:嵌入式系统中的软件具
有独立的著作权,可以通过国家版权局进行注
册。注册软件著作权可以确保嵌入式系统中的
软件独立性和原创性,提高软件开发者的知识
产权保护能力。
2. 使用加密技术:嵌入式系统中的软件可能
会被黑客等不法分子攻击,为了保护系统的安
全性,可以使用加密技术对软件进行加密,防
止盗版或逆向工程等行为。
3. 建立知识产权保护意识:企业或组织应建
立知识产权保护意识,明确嵌入式系统中的知
识产权属于企业或个人,帮助员工了解知识产
权保护相关法律法规,加强管理和监督,避免
侵犯他人知识产权。
4. 签署保密协议:在开发、测试、维护等过
程中,可以要求相关人员签署保密协议,明确
保护嵌入式系统的知识产权的义务和责任,防
止机密信息被泄露。
总之,保护嵌入式系统的知识产权是企业或个
人应重视的问题。可以通过注册软件著作权、
使用加密技术、建立知识产权保护意识和签署
保密协议等措施来提高知识产权保护能力,确
保嵌入式系统中的知识产权得到切实的保护。
39、嵌入式系统中如何进行模块化设计 
在嵌入式系统中进行模块化设计需要遵循以
下几个原则:
1. 将功能划分为模块:需要将嵌入式系统中
的功能划分为不同的模块,每个模块负责完成
特定的功能。模块之间应该解耦且尽可能地独
立。
2. 模块之间使用标准接口通讯:各个模块之
间的通讯需要通过标准接口来进行,这样可以
提高系统的可移植性和扩展性。接口应该定义
清晰,保证模块之间的相互调用和协作。
3. 采用标准规范:在进行模块化设计时需要
遵循相关的标准规范,包括硬件接口规范、软
件接口规范等。这样有利于提高模块之间的兼
容性,降低开发成本和维护成本。
4. 模块独立测试:在设计每个模块时,需要
独立测试以确保其功能正确实现。测试应该涵
盖所有常见的情况,并进行异常测试来验证模
块的稳定性和鲁棒性。
总之,在嵌入式系统中进行模块化设计需要划
分功能模块、规范接口、遵循标准规范、独立
测试等一系列操作。此外,还需要根据具体的
应用场景和需求对模块进行细分和组合,以提
高系统的灵活性和可扩展性。
40、请简述一下 HTTP 协议 
HTTP 协 议 全 称 是 HyperText Transfer 
Protocol,它是一个基于 TCP/IP 协议的应用
层协议,主要用于在 Web 浏览器和服务器之间
传递信息。HTTP 协议规定了客户端和服务器之
间的通信格式和行为方式,是互联网上应用最
为广泛的协议之一。
HTTP 协议的通信过程一般由请求和响应两个
阶段组成。客户端通过发送 HTTP 请求到服务
器来获取相应的资源,而服务器则返回相应的
HTTP 响应数据。HTTP 协议的请求和响应数据
都由头部和正文两部分组成,其中头部包含了
请求或响应的信息,如请求方法、请求地址、
响应码等,正文则是具体的传输内容。
HTTP协议的优点在于可以通过URL标识任意互
联网资源,支持多种不同类型的数据格式,并
且易于扩展。不过,HTTP 协议也存在一些缺点,
其中一个就是安全性较差,在传输过程中的数
据容易被窃听和篡改。
目前,HTTP/2 和 HTTP/3 已成为 HTTP 协议的最
新版本。其中 HTTP/2 使用二进制格式代替了
HTTP/1.x 的文本格式,在传输效率和速度上较
HTTP/1.x 有显著提升;而 HTTP/3 则使用了更
加先进的 QUIC 协议,将原有的 TCP/IP 协议替
换为 UDP 协议,进一步提高了通信速度和性能。
41. 嵌入式系统中使用的编程语言有哪些 
嵌入式系统中使用的编程语言主要包括以下
几种:
1. C 语言:C 语言是一种通用的、高效的、面
向过程的编程语言,广泛应用于嵌入式系统的
开发中。由于其具有直接操作硬件的能力和良
好的移植性等特点,因此成为了嵌入式系统开
发中最为常用的语言。
2. C++语言:C++语言是在 C 语言基础上发展
而来的一种面向对象的编程语言,也广泛应用
于嵌入式系统的开发中。与 C 语言相比,C++
语言具有更强的抽象能力和更优秀的开发效
率,因此在某些应用场景下被优先选择。
3. 汇编语言:汇编语言是一种直接操作计算
机硬件的低级语言,嵌入式系统中需要对硬件
进行精细控制时,通常会使用汇编语言编写少
量的关键代码。
4. Python 语言:Python 语言是一种高级的、
面向对象的脚本语言,近年来也逐渐应用于嵌
入式系统中。Python 语言具有易学易用、库丰
富、适合快速开发等特点,适用于一些简单的、
非实时的应用场景。
5. Java 语言:Java 语言是一种面向对象的编
程语言,具有跨平台、安全性高等特点,也适
用于嵌入式系统开发。Java 通常在嵌入式计算
机的操作系统上运行,用于实现网络连接、UI
界面、远程管理等功能。
总之,嵌入式系统中使用的编程语言主要以 C
语言和 C++语言为主,汇编语言和脚本语言等
也被应用在某些特定应用场景中。根据应用需
求和资源限制,选择合适的编程语言来开发嵌
入式系统非常重要。
42. 嵌入式系统中如何进行数据传输和处理 
数据传输和处理是嵌入式系统中非常关键且
常见的问题,其处理方式通常受限于嵌入式系
统的硬件平台和数据规模等因素。下面介绍嵌
入式系统中常用的数据传输和处理方法。
1. 基于总线的数据传输:基于总线的数据传
输是嵌入式系统中常用的一种传输方式。常见
的总线包括 I2C、SPI、CAN 等。这些总线的特
点是通信速度较快,线路简单、可靠性高。在
传输数据时可以使用相关驱动程序进行读写
操作,从而实现数据在嵌入式系统内部的传输。
2. 串口数据传输:串口数据传输是嵌入式系
统中最为常见的一种传输方式。串口通常使用
RS232 或者 UART 协议进行通信,其特点是传输
速度较慢,但串口芯片内建有 FIFO 缓冲区,
可以直接通过中断方式传输数据,从而减少
CPU 占用率。
3. 数据处理算法:在嵌入式系统中,由于处
理器的性能比较有限,对于大量、复杂的数据
通常需要采用一些数据处理算法进行优化,例
如压缩算法、FFT 算法等。这些算法的特点是
可以将数据尽可能地压缩或进行特殊转换,从
而降低嵌入式系统处理的数据量。
4. DMA 传输:DMA(Direct Memory Access)
是一种用于实现高速数据传输的技术。使用
DMA 可以直接将数据从外设设备传输到内存中,
或者从内存中传输到外设设备中,无需 CPU 的
干预。因此,使用 DMA 可以提高数据传输的效
率,减少 CPU 的占用率。
总之,嵌入式系统中的数据传输和处理需要根
据具体应用场景和硬件设备来选择合适的方
法进行处理。同时,也需要优化传输效率、降
低占用率、减小存储空间等方面进行优化,以
实现高效可靠的数据传输和处理。
43. 嵌入式系统中的 PWM 有什么作用 
在嵌入式系统中, PWM ( Pulse Width 
Modulation)是一种常见的数字信号调制技术,
它通过改变正脉冲和负脉冲的占空比来控制
输出电平的电压值,从而达到控制电机速度、
亮度等效果。
具体来说,PWM 技术针对输出信号进行脉宽调
制,将其转换为具有不同占空比的一个周期性
的方波信号,占空比越大,输出电平就越高。
如此反复调制,可以让受控对象(如电机)输
出平均电平与占空比成正比的模拟电压信号,
从而达到对电机速度、亮度等进行控制的目的。
在嵌入式系统中,PWM 技术通常由芯片的定时
器或计数器来实现,并通过配置相关寄存器参
数进行设置。 PWM 技术具有高效、精确、低成
本等优点,被广泛应用于嵌入式系统中的电机
控制、LED 亮度控制、蜂鸣器声音控制等领域。
44. 请简述一下 UDP 协议 
UDP(User Datagram Protocol)是一种无连
接的、不可靠的传输层协议,主要用于在 IP
网络上提供简单的数据包交换服务。
UDP协议与TCP协议不同,没有建立连接过程,
只是直接将数据报文从源地址发送到目的地
址,具有开销小、传输速度快等优点。但 UDP
协议也没有错误检测和重传机制,因此在传输
过程中容易出现数据包的丢失和损坏。
通过 UDP 协议发送和接收数据的过程如下:
1. 应用程序通过 UDP 套接字向目标主机发送
数据报文。
2. 数据报文被封装成 UDP 数据报,其中包括
源端口号、目的端口号、长度等信息。
3. UDP 数据报被封装成 IP 数据报,在网络层
被传输到网络中。
4. 目标主机根据目的端口号识别接收到的
UDP 数据报,将其分离出来。
5. 应用程序从 UDP 数据报中提取数据,并进
行处理。
UDP 协议常用于实时性要求高、数据大小较小、
丢失一些数据不会影响应用的场景,例如音视
频传输、DNS 查询等。但由于 UDP 协议不提供
拥塞控制和流量控制,因此在网络环境不稳定
或者带宽受限的情况下,容易导致网络拥塞和
丢包现象。
总之,UDP 协议是一种轻量级的、适用于小数
据传输的传输层协议,具有开销小、传输速度
快等优点,但没有错误检测和重传机制,需要
开发者进行额外的处理来确保数据可靠性。
45. 嵌入式系统中的时序问题应该如何解决 
时序问题是嵌入式系统中常见的问题之一,由
于硬件资源非常有限,时钟频率较低,处理器
速度慢等原因,会出现信号延迟、时序不准确、
信号干扰等问题。以下是几种嵌入式系统中解
决时序问题的方法:
1. 时钟同步:时钟同步是嵌入式系统中解决
时序问题的基本方法。当使用多个时钟时,必
须在它们之间建立一定的关系,以便实现时序
的同步。时钟同步可以通过硬件电路上的锁相
环(PLL),实现时钟信号的提频和同步。
2. 时序分析:时序分析是指对于各种时序关
键路径进行分析,找出信号传输的瓶颈,从而
更好地优化时序。时序分析的核心是找出时序
敏感路径,以及当在一个路径上加上一个硬件
后,可以达到的最高的工作频率。
3. 延迟控制:通过添加延迟电路,使数据信
号与时钟信号保持同步,实现时序的精确控制。
通常采用延迟锁相环(DPLL)或者延迟线的方
式来实现,以达到精确控制时序的目的。
4. 引脚分配:在嵌入式系统中,相邻引脚之
间通常会产生互电干扰或者串扰等问题,这可
能导致信号的时序不准确。为了避免这些问题,
需要合理分配引脚,同时保持各个引脚之间的
距离足够大,以降低互电干扰的概率。
总之,嵌入式系统中的时序问题需要通过多种
手段进行综合优化,包括时钟同步、时序分析、
延迟控制、引脚分配等等。只有通过全面的优
化,才能确保嵌入式系统的可靠性和稳定性。
46. 嵌入式系统中如何进行功率管理 
嵌入式系统中的功率管理是指通过软件或硬
件的方式来控制系统的功耗,从而降低系统能
量的消耗。以下是一些常见的嵌入式系统中的
功率管理方法:
1. 电源管理:电源管理是一种有效的功率管
理方法,它通过选择合适的电源方案,在不影
响嵌入式系统性能的前提下,降低系统功耗。
例如,采用低功耗电源、降压升压技术、多电
平电源等方案,都可以降低嵌入式系统的功耗。
2. CPU 睡眠模式:在不需要 CPU 处理任何任务
时,将 CPU 置于睡眠状态,以减少系统能量的
消耗。这种方式通常需要通过设置相应的寄存
器,启动睡眠模式并设置唤醒条件,以实现节
能效果。
3. 动态电压调节:在嵌入式系统使用过程中,
处理器负载会不断变化,因此可以通过调节
CPU 电压,以达到功耗和性能的平衡。例如,
当负载较轻时,可以降低 CPU 电压,减少能量
消耗;当负载增加时,可以适当提高 CPU 电压,
保证性能表现。
4. 休眠模式:当嵌入式系统不需要运行时,
可以将整个系统置于休眠模式,以达到最大的
节能效果。在此模式下,除了一些必要的电路,
其他部分都被关闭,从而将能耗尽可能地降至
最低。
5. 关闭不必要的模块和外设:关闭不必要的
模块和外设也是一种有效的功率管理方式。例
如,在使用 Wi-Fi 传输数据时,可以关闭蓝牙、
GPS 等无线模块,以减少效能消耗。
总之,针对不同的嵌入式系统,可以采用以上
这些方式来降低功耗和提高系统能效。同时,
需要注意的是,要根据具体应用场景和硬件资
源来选择合适的方案,以确保系统性能和稳定
性。
47. 嵌入式系统中如何进行容错设计
1. 硬件容错:硬件容错是指通过硬件设计防
止或处理错误,以保证系统的可靠性。例如,
采用纠错编码、备份电源、电源监控电路等硬
件方案,可以有效降低嵌入式系统出错的概率。
2. 软件容错:在嵌入式系统中,一些高级软
件或操作系统可以通过软件容错技术来保证
系统的可靠性。例如,内存管理单元(MMU)
和异常处理器可以检测并处理软件错误、异常。
3. 备份机制:备份机制是指在嵌入式系统中
同时运行两个或多个相同的任务,在主程序发
生故障时,将转向执行备份程序。例如,采用
双机热备方案,将主程序和备份程序分别装在
两台计算机上,并通过网络连接实现数据同步,
从而实现高可用性和高可靠性。
4. 实时监控:实时监控是一种通过采集、监
测硬件和软件状态信息来检测错误并处理的
方法。例如,通过硬件监测器和故障检查程序
来监测硬件状态,并在出现故障时自动切换到
备份模块。
总之,嵌入式系统中的容错设计需要综合考虑
硬件、软件和备份机制等多个方面,以确保系
统的可靠性和稳定性。同时需要根据具体应用
场景和可承受风险程度来选择合适的容错设
计方案。
48. 嵌入式系统中的数字滤波器有哪些应用 
数字滤波器是一种对数字信号进行处理的滤
波器,可以在嵌入式系统中应用于多种场景。
以下是一些嵌入式系统中数字滤波器的应用:
1. 传感器数据滤波:在许多嵌入式系统中,
常常需要对传感器采集到的数据进行滤波处
理,去除噪声和干扰。例如,利用低通数字滤
波器可以实现降噪。
2. 音频信号处理:在音频信号处理中,数字
滤波器通常用于去除杂音和回声,增强音频质
量。例如,利用减少 DC 偏置和陷波滤波器可
以实现去噪。
3. 无线通信:数字滤波器也广泛应用于嵌入
式无线通信领域,例如蓝牙、Wi-Fi 和 LTE 等。
在这些场景下,数字滤波器通常用于预处理和
后处理,以提高通信的质量和效率。
4. 生物信号处理:数字滤波器也可以用于嵌
入式生物医学工程中,例如心电图(ECG)和
脑电图(EEG)信号处理。在这些应用中,数
字滤波器可以去除运动伪影和电力干扰等,提
高生物信号的质量和准确性。
总之,数字滤波器在嵌入式系统中有着广泛的
应用,可用于传感器数据滤波、音频信号处理、
无线通信和生物信号处理等多种场景。不同类
型的数字滤波器可以根据具体应用要求选择,
以满足不同的滤波需求。
49. 请简述一下 MQTT 协议 
MQTT 协议是一种轻量级的、基于发布-订阅模
式的通信协议,用于在物联网(IoT)和 M2M
(机器到机器)通信中进行消息传递。其全称
为 Message Queuing Telemetry Transport,
即消息队列遥测传输。
MQTT 协议的特点包括:
1. 轻量级:MQTT 协议采用二进制消息格式,
使用较小的协议头和消息体,以减少网络带宽
和存储消耗。
2. 发布-订阅模式:MQTT 采用发布-订阅模式,
即客户端可以发布消息到一个特定的主题,其
他客户端则可以订阅相同的主题并接收来自
该主题的消息。
3. 可靠性:MQTT 支持多种传输级别(QoS0、
QoS1 和 QoS2),以满足不同的可靠性要求。
4. 连接维护:MQTT 支持会话保持和心跳机制,
以保持长时间的连接,并在网络断开或者重新
连接时恢复之前的状态。
5. 安全性:MQTT 支持 SSL / TLS 协议加密传
输,以确保数据的安全性。
在 MQTT 协议中,客户端可以发布消息到一个
主题,而其他客户端只需订阅相同的主题即可
接收到该消息。同时,MQTT 协议支持三种消息
等级,即 QoS(Quality of Service,服务质
量)0、1 和 2,用于控制消息在网络中的可靠
性和延迟。例如,在 QoS 2 级别下,消息发送
者需要确保接收到每个消息,以实现高效而又
可靠的通信。
总之,MQTT 协议是一种轻量级、灵活的通信协
议,广泛应用于物联网和 M2M 通信领域中,具
有高效、安全、可靠等特点,为物联网设备实
现可靠、高效的通信提供了重要的支持。
50. 嵌入式系统中的 GPIO 有什么作用 
GPIO 是 General-purpose input/output 的缩
写,即通用输入输出引脚。在嵌入式系统中,
GPIO 被用于控制和读取外部设备(如传感器、
执行器等)的状态。以下是 GPIO 在嵌入式系
统中的主要作用:
1. 控制外部设备:GPIO 可以控制外部设备的
开关、电平、信号等状态。例如,通过 GPIO
驱动 LED 灯、LCD 显示屏、无线模块、电机、
继电器等。
2. 读取外部设备:GPIO 可以读取外部设备的
状态,例如读取按钮、开关、传感器等的状态,
以实现相应的控制和处理。
3. 实现中断处理:GPIO 还可以配置为中断输
入,一旦外部设备的状态发生变化,就会触发
中断,从而加快对外部设备的响应速度和操作
效率。
4. 实现多种接口标准:许多外部设备接口标
准,如 SPI、I2C、UART 等,都与 GPIO 紧密相
关。在这些接口中,GPIO 被用于控制通信时序、
发送和接收数据等。
总之,GPIO 在嵌入式系统中具有重要的作用,
可以实现与外部设备的连接、控制和交互。
GPIO 也是嵌入式系统中最基本、最常用的一个
硬件接口,因此熟悉和掌握 GPIO 的使用,对
于进行嵌入式系统设计和开发具有重要意义。
51. 说一下怎么通过 bootloader 让一段代码
从中间执行 
1. 编写两段代码,一段运行完成后进入
Bootloader 等待,另一段则由 Bootloader 加
载并执行。
2. 在第一段代码中通过某种方式将第二段代
码的镜像文件保存至 Flash 或其他存储设备中。
3. 当第一段代码运行完毕后,跳转到
Bootloader 程序,等待第二段代码的加载请求。
4. 用户通过某个操作触发 Bootloader 加载第
二段代码的请求,在 Bootloader 内部进行镜
像文件的加载,并将第二段代码的入口地址作
为参数传递给第一段代码。
5. 第一段代码接收到入口地址参数后,跳转
到第二段代码的入口地址处,从而实现让第二
段代码从中间开始运行。
需要注意的是,在实现这个方法时需要考虑多
个问题,例如如何将第二段代码的镜像文件保
存到 Flash 中,以及如何进行镜像文件的加载
和跳转。同时也需要对具体硬件平台和开发环
境进行适配,以确保代码能够正确运行。
总之,通过 Bootloader 实现让一段代码从中
间执行是一种常见的方法,可以在某些特殊场
景下起到重要的作用。它可以使我们更加灵活
地控制代码的运行顺序和流程,提高嵌入式系
统的可扩展性和可维护性。
52. 三极管的反向怎么实现 
三极管的反向可以通过正确定向电压和电流
进行控制实现。下面是三极管反向控制的详细
步骤:
1. 首先需要将三极管的发射极接地,基极接
入信号源或控制器,集电极接入负载。
2. 当控制信号为低电平(0V)时,基极与发
射极之间的二极管处于反向截止状态,此时三
极管处于关断状态,负载不受控制,无法输出
电流。因此,在这个状态下,三极管实现了反
向控制。
3. 当控制信号为高电平(正向电压)时,会
在基极与发射极之间形成正向电路,使得基极
电流增大,从而导致集电极电流也增大,从而
控制负载的电流输出。
总之,三极管反向控制是通过将三极管发射极
接地,并控制基极与发射极之间的二极管状态
实现的。在控制信号为低电平时,三极管处于
反向截止状态,可实现反向控制;而在控制信
号为高电平时,三极管处于正常导通状态,可
实现正向输出控制。
53. uboot 启动过程 
U-boot,也称为 Das U-Boot,是一款开放源代
码的嵌入式引导加载程序,用于在嵌入式系统
中引导操作系统内核。下面简要介绍 U-boot
的启动过程:
1. 上电初始化:当嵌入式系统上电后,处理
器会从 Flash 中读取 U-boot 镜像文件并将其
复制到 RAM 中。此时,U-boot 程序控制权被传
递给 U-boot 程序入口地址,开始执行。
2. 硬件初始化:在 U-boot 启动过程中,会对
硬件进行初始化,包括 CPU、内存、I/O 接口、
串口等设备的初始化。
3. 低级初始化:在硬件初始化完成后,U-boot
会进入低级初始化过程,包括栈指针和堆指针
的初始化、全局变量的初始化、串口和网络输
出的配置等。
4. 驱动初始化:在低级初始化完成后,U-boot
会进一步初始化系统的驱动程序,例如 Block、
NAND/NOR Flash、USB、Ethernet、视频驱动
等。
5. 加载启动镜像:U-boot 会在 Flash 或其他
存储设备中查找可以启动的镜像文件,并将其
加载到 RAM 中,然后控制权交给这个镜像文件
去启动操作系统内核。
54. kernel 启动过程 
Linux 内核是一个开源的操作系统内核,其启
动过程相对复杂。以下是 Linux 内核启动过程
的基本流程:
1. 加载 Bootloader:在计算机上电后,首先
由 BIOS 加载引导程序(例如 GRUB 或 LILO),
引导程序会读取内核映像文件,并将其从磁盘
等存储设备中加载到内存中。
2. 进入内核初始化阶段:一旦内核被加载到
RAM 中并解压缩后,CPU 将执行内核中的启动
代码,这是进入内核初始化阶段的开始。此时,
内核会对硬件进行初始化、创建进程、加载模
块等操作。
3. 创建 init 进程:在内核初始化完毕后,内
核会创建 init 进程,作为内核空间和用户空
间的分界点。init 进程是所有用户级别应用程
序的父进程,因此它是操作系统初始化的重要
部分。
4. 执行 rc.sysinit 脚本:一旦 init 进程被
创建,系统会执行 rc.sysinit 脚本,该脚本
会加载系统必需的模块、配置网络、设置时钟、
挂载文件系统等操作。
5. 启动 getty 进程:在 rc.sysinit 脚本执行
完毕后,系统会启动 getty 进程,该进程用于
启动终端会话,也就是用户登录系统之前的准
备工作。
6. 等待用户登录:此时,Linux 系统已经启动
完成,用户需要输入用户名和密码才能登录系
统。一旦用户通过用户名和密码登录系统,
init 进程会执行用户定义的脚本,例如 bashrc、
profile 等。
总之, Linux 内 核 启 动 过 程 包 括 加 载
Bootloader、内核初始化阶段、创建 init 进
程、执行 rc.sysinit 脚本、启动 getty 进程
和等待用户登录等步骤。在这个过程中,内核
会对硬件进行初始化、加载必需的模块、设置
文件系统等操作,以确保系统能够正常运行。
同时也为用户提供了一个良好的平台,使得用
户能够方便地使用和管理操作系统。
55. 如何系统突然掉电或者没网怎么去解决 
在嵌入式系统中,突然掉电或者无法连接网络
等问题是比较常见的情况。下面介绍一些解决
这些问题的方法:
1. 突然掉电
如果嵌入式系统遇到了突然掉电的情况,需要
进行以下操作:
- 对于数据存储在 RAM 中的数据,由于内存数
据在掉电时会丢失,可以通过使用非易失性存
储器(如 Flash)来保存关键数据,以保证不
丢失。在系统启动后可以读取 Flash 中的数据
进行恢复。
- 如果使用了日志记录技术,在系统重启后可
以通过读取日志文件来恢复数据。
- 对于一些需要持久化数据存储的场景,可以
使用电池或超级电容器等组件,提供能量缓存
和备份,以保证数据不丢失。
2. 没网
如果嵌入式系统无法连接网络,需要进行以下
操作:
- 检查网络连接:首先要检查网络连接是否正
常,例如电缆、网络设备、IP 地址、DNS 配置
等。
- 检查网络服务:如果网络连接正常,还需要
检查所需的网络服务是否可用,例如 DHCP 服
务器、DNS 服务器、NTP 服务器等。
- 检查网络驱动程序:如果网络服务正常,可
能需要检查与网络相关的驱动程序是否正确
安装和配置,例如网络适配器驱动程序、网卡
驱动程序等。
- 测试其他应用程序:如果以上操作都没有解
决问题,可以尝试使用其他应用程序测试网络,
例如 ping 命令、telnet 命令等,以确定问题
所在。
总之,在遇到突然掉电或者无法上网等问题时,
需要进行一系列的检查和排除,并采取相应的
解决方法。同时也需要针对具体的应用场景和
硬件平台进行适当的配置和优化,以确保系统
能够稳定运行.
56. 有没有学过模块化编程 
如果面试官问您有没有学过模块化编程,您可
以回答如下:
模块化编程是一种基于模块的软件开发方法,
将程序分割成多个独立的、可复用的部分,每
个部分都具有特定的功能和接口。这些模块之
间通过定义的接口进行相互通信和协作,从而
实现系统的构建和扩展。在模块化编程中,每
个模块都是独立的,可以被单独编写、测试、
调试,并且可以在不同的应用程序和系统之间
重用。
对于嵌入式系统来说,模块化编程也是一个非
常重要的概念。在嵌入式系统中,模块化编程
可以帮助我们将功能划分为小的可重用组件,
提高代码的可读性、可维护性和可扩展性。模
块化编程也能够降低代码的耦合度,隔离不同
模块之间的影响,从而提高系统的稳定性和安
全性。
在实践中,模块化编程可以使用各种编程语言
和框架实现。例如,C 语言中的函数、结构体
和头文件能够将代码划分为不同的模块;
Python 中使用包和模块来组织代码;而面向对
象语言如 Java 和 C#中,则使用类和接口来实
现模块化编程。
总而言之,模块化编程是软件开发中一种重要
的方法,能够提高代码质量、可维护性和可扩
展性,同时也是嵌入式系统开发中一个非常重
要的概念。
57. 摄像头应用怎么写的(V4L2 框架) 
V4L2(Video for Linux Two)框架是 Linux
内核中的一套视频设备驱动程序接口,可用于
访问摄像头、音频采集卡等设备。V4L2 框架提
供了一套标准的 API,允许用户空间应用程序
通过系统调用来操作和控制摄像头设备。
下面介绍如何使用 V4L2 框架在 Linux 平台上
编写摄像头应用程序:
1. 打开摄像头设备:使用 open 系统调用打开
摄像头设备文件,例如/dev/video0。
2. 获取并设置摄像头设备参数:使用 ioctl
系统调用,执行 VIDIOC_QUERYCAP 命令获取设
备信息;执行 VIDIOC_S_FMT 命令设置视频帧
格式、分辨率等参数。
3. 启动摄像头设备:使用 ioctl 系统调用,
执行 VIDIOC_STREAMON 命令启动摄像头设备。
4. 从摄像头设备读取视频帧数据:使用 read
系统调用从摄像头设备读取视频帧数据。
5. 处理视频帧数据:对于读取到的视频帧数
据,可以进行一系列的处理,例如图像处理、
视频编码、储存等。
6. 关闭摄像头设备:在应用程序退出前,使
用 ioctl 系统调用,执行 VIDIOC_STREAMOFF
命令关闭摄像头设备,并使用 close 系统调用
关闭设备文件。
 
58. 为什么线程开销比较小 
相比于进程,线程的开销确实要小很多。主要
原因如下:
1. 线程共享进程资源:线程是在进程中创建
的,因此多个线程可以共享同一个进程的内存
空间、文件句柄等资源,避免了进程间通信的
开销。
2. 线程切换开销小:线程的切换只需要保存
少量的上下文信息(如寄存器值等),而进程
的切换需要保留更多的信息(如页表、程序计
数器等),因此线程的切换开销较小。
3. 线程创建、撤销开销小:线程的创建和撤
销不需要像进程一样需要分配和释放大量的
资源,例如进程地址空间、文件句柄、IPC 机
制等。
4. 线程调度开销小:由于线程只存在于进程
中,因此线程调度开销比进程调度开销要小得
多。
59. 线程的互斥与同步 
线程的互斥和同步是多线程编程中非常重要
的概念。线程同步是指协调不同线程之间的操
作,以确保它们能够按照正确的顺序、在正确
的时间执行。线程互斥是指只有在一个线程完
成一项任务后,另一个线程才能开始执行该任
务。
线程同步:
1. 信号量(Semaphore):Semaphore 是一种锁
机制,可用于限制同时访问共享资源的线程数
量。Semaphore 维护一个计数器,表示可用的
资源数量,每个线程在使用资源前必须先获得
一个信号量,使用完毕后再释放信号量,从而
使其他线程也有机会访问资源。
2. 条件变量(Condition Variable):条件变
量是一种同步工具,通过等待和唤醒线程来实
现线程间同步。当某个条件不满足时,线程可
以使自己进入睡眠状态,并将 CPU 时间让给
其他线程。当其他线程改变了条件后,就可以
唤醒等待的线程以继续执行。
线程互斥:
1. 互斥锁(Mutex):互斥锁是一种同步工具,
用于避免多个线程同时访问共享资源,例如全
局变量、I/O 设备等。在任何时刻,只有一个
线程可以获得互斥锁,其余的线程必须等待锁
被释放后才能继续执行。
2. 读写锁(Reader-Writer Lock):读写锁是
一种同步机制,用于控制对共享资源的并发访
问。读写锁允许多个线程同时读取共享资源,
但只允许一个线程写入共享资源。这样,就能
够提高并发性,并减少对共享资源的访问冲突。
总之,在进行多线程编程时,需要合理使用线
程互斥和同步机制,以避免出现死锁、竞态条
件等问题,从而保证程序的正确性和可靠性。
60. 进程的信号量 
进程的信号量是一种进程间同步和互斥的机
制,用于协调多个进程之间对共享资源的访问。
信号量通常是一个整型变量,在并发操作中可
以实现对共享资源的访问控制。
进程的信号量主要包含以下两个操作:
1. P 操作:当一个进程开始执行一个临界区(即
运行访问共享资源的代码)时,需要执行 P 操
作,该操作会从信号量的值中减去 1,表示有
一个资源已经被分配给该进程,其他进程需要
等待。
2. V 操作:当进程完成一个临界区的代码执行
后,需要执行 V 操作,该操作会向信号量中添
加 1,表示资源可用,其他进程可以继续访问
共享资源。
在进程中使用信号量可以避免资源竞争和死
锁等问题,使得多个进程能够更好地协作和共
享资源。同时,由于信号量只是一个计数器,
因此对于操作系统内核来说,实现起来也比较
简单,不需要高度复杂的算法或数据结构。
然而,在使用信号量时,程序员需要注意一些
问题,如计数器的初始值、进程间的关系、信
号量的优先级等,这些问题可能会影响到程序
的正确性和性能。同时,还需要注意信号量的
使用范围,以避免过度使用信号量导致代码过
于复杂和混乱。
61. 停车场项目里用进程还是线程为什么 
停车场项目可以使用进程或线程实现,具体取
决于实际需求和设计方案。
如果停车场有多个入口和出口,并且每个入口
和出口之间需要进行协调和交互,因此可以使
用多进程的方式来实现。例如,每个进程可以
负责一个入口或出口,负责监控和管理对应的
车辆进出情况,这样可以有效地避免不同进程
之间资源的竞争和冲突。在使用多进程时,可
以使用 IPC 机制来实现进程间的通信和数据共
享。
如果停车场有大量车辆需要进出,并且需要支
持高并发的处理能力,可以使用多线程的方式
来实现。例如,每个线程可以负责一个车辆或
一个入口/出口,负责处理车辆进出的操作,
这样可以提高系统的吞吐量和响应速度。在使
用多线程时,需要注意线程的同步和互斥问题,
避免出现竞态条件和死锁等问题。
总而言之,停车场项目可以使用进程或线程来
实现,取决于实际需求和设计方案。如果需要
协调多个入口/出口之间的操作,或需要多个
独立的进程对停车场进行管理和监控,可以使
用多进程的方式来实现;如果需要高并发的处
理能力和快速响应用户操作,可以使用多线程
的方式来实现。
62. C++中开辟内存的指令 
在 C++中,开辟内存可以使用 new 操作符,语
法如下:
指针变量 = new 数据类型;
```
其中,指针变量是一个指向数据类型的指针,
new 操作符用于在堆上分配对应数据类型的内
存,并返回指向该内存的指针。例如,下面的
代码创建了一个指向 int 类型的指针
numPtr,并分配了一个 int 类型的内存:
```
int *numPtr = new int;
```
此时,numPtr 指向分配的 int 类型内存,可
以通过解引用操作符(*)来访问该内存中的值:
```
*numPtr = 10;
cout << *numPtr << endl; // 输出 10
```
需要注意的是,在使用 new 操作符分配内存
后,必须使用 delete 关键字来释放该内存,
以避免内存泄漏问题。
```
delete numPtr; // 释放内存
```
总之,在 C++中,new 操作符用于在堆上分配
内存,并返回指向该内存的指针;而 delete 
关键字用于释放该内存。在使用时,需要注意
内存管理和垃圾回收等问题,避免出现内存泄
漏、野指针等问题。
63. C++中继承是什么,私有继承是怎么样的 
在 C++中,继承是一种面向对象编程的重要概
念,它允许一个类派生出另一个新的类,并且
从基类中继承属性和方法。通过继承,我们可
以实现代码重用、扩展和多态性等。
在 C++中,继承有三种类型:公有继承、私有
继承和保护继承。其中,私有继承是指派生类
只能访问基类的公共和保护成员,而不能访问
基类的私有成员。
私有继承的语法格式如下:
```
class Derived: private Base
{
 // derived class definition
};
```
在上述代码中,private 关键字表示使用私有
继承方式,Derived 类是由 Base 类私有派生而
来的。这意味着 Derived 类将可以访问 Base
类的所有公共和保护成员,但不能直接访问其
私有成员。例如:
```
class Base {
public:
 int pub;
protected:
 int prot;
private:
 int priv;
};
class Derived: private Base {
public:
 void foo() {
 pub = 1; // 可以访问基类的公
有成员
 prot = 2; // 可以访问基类的保
护成员
 //priv = 3; // 错误,不能访问
基类的私有成员
 }
};
```
使用私有继承时,派生类将继承基类中的公共
和保护成员,并且这些成员在派生类中仍然是
公共或保护的。因此,在使用私有继承时,可
以通过派生类来访问基类的成员函数和成员
变量,但不能直接访问基类的私有成员。
总之,私有继承是 C++中的一种继承方式,它
允许派生类从基类中继承公共和保护成员,但
不能直接访问基类的私有成员。
64. C++中多态怎么体现的 
在 C++中,多态是指同一类型的对象在不同情
况下会表现出不同的行为。C++中实现多态性
的方式主要有两种:虚函数(virtual function)
和纯虚函数(pure virtual function)。
1. 虚函数
虚函数是在基类中使用 virtual 关键字声明
的成员函数,它在派生类中被重写后可以实现
多态性。对于一个函数被定义为虚函数,意味
着在程序运行时,将根据对象的实际类型来动
态调用该函数所属的类中的版本。例如:
```
class Animal {
public:
 virtual void speak() {
 cout << "I am an animal." << endl;
 }
};
class Cat: public Animal {
public:
 void speak() {
 cout << "I am a cat." << endl;
 }
};
class Dog: public Animal {
public:
 void speak() {
 cout << "I am a dog." << endl;
 }
};
```
在上述代码中,Animal 类中的 speak()函数被
声明为虚函数,在其派生类 Cat 和 Dog 中被重
新实现。当创建一个基类指针并将其指向 Cat
或 Dog 对象时,可以通过该指针调用 speak()
函数,并根据对象的实际类型来调用相应的类
中的版本:
```
Animal *animalPtr;
Cat cat;
Dog dog;
animalPtr = &cat;
animalPtr->speak(); // 输 出 "I am a 
cat."
animalPtr = &dog;
animalPtr->speak(); // 输 出 "I am a 
dog."
```
2. 纯虚函数
纯虚函数不需要在基类中实现,在派生类中必
须被重写,用于实现接口的规范定义。如果一
个基类中包含至少一个纯虚函数,则该类被称
为抽象类,不能创建该类的对象。例如:
```
class Shape {
public:
 virtual double area() = 0; // 纯虚
函数
};
class Circle: public Shape {
public:
 double area() {
 return 3.14 * radius * radius;
 }
private:
 double radius;
};
class Rectangle: public Shape {
public:
 double area() {
 return width * height;
 }
private:
 double width;
 double height;
};
```
在上述代码中,Shape 类是一个抽象类,并包
含一个纯虚函数 area(),必须在其派生类
Circle 和 Rectangle 中被重写实现。当创建一
个基类指针并将其指向 Circle 或 Rectangle
对象时,可以通过该指针调用 area()函数,并
根据对象的实际类型来调用相应的类中的版
本:
```
Shape *shapePtr;
Circle circle;
Rectangle rectangle;
shapePtr = &circle;
cout << shapePtr->area() << endl; // 
输出圆的面积
shapePtr = &rectangle;
cout << shapePtr->area() << endl; // 
输出矩形的面积
```
总之,在 C++中,多态性主要是通过虚函数和
纯虚函数来实现的。虚函数可以在派生类中被
重写实现,以实现对基类行为的改变;而纯虚
函数则定义了抽象接口规范,用于约束派生类
的实现。通过使用多态性,C++可以更好地支
持面向对象编程的需求,提高代码的可扩展性、
可复用性和可维护性。
65. C++虚函数是什么
在 C++中,虚函数是一种特殊的成员函数,它
在基类中被声明为虚函数后,在派生类中可以
根据需要进行重新定义(即覆盖),从而实现
多态性。
虚 函 数 的 声 明 方 式 是 在 函 数 定 义 前 加 上
virtual 关键字,例如:
```c++
class Animal {
public:
 virtual void speak() {
 cout << "I am an animal." << endl;
 }
};
class Cat : public Animal {
public:
 void speak() {
 cout << "I am a cat." << endl;
 }
};
```
在上面的例子中,Animal 类中的 speak()函数
被声明为虚函数,而 Cat 类中的 speak()函数
覆盖了 Animal 类中的 speak()函数。此时,当
使用基类指针指向派生类对象,并且调用该基
类指针的 speak()函数时,会自动根据实际指
向的对象类型来调用相应的派生类函数,例如:
```c++
Animal *animalPtr;
Cat cat;
animalPtr = &cat; // 把派生类对象地址赋
给基类指针
animalPtr->speak(); // 输 出 "I am a 
cat."
```
由于 speak()函数是虚函数,在运行时会根据
animalPtr 所指向的对象类型来决定调用哪个
版本的函数。这就是所谓的动态绑定(dynamic 
binding)或后期绑定(late binding)特性。
总之,在 C++中,通过使用虚函数,可以让基
类指针在调用函数时自动根据指向对象的实
际类型来调用相应的派生类函数,从而实现多
态性。虚函数是 C++中面向对象编程的重要概
念之一,广泛应用于类库、框架和各种复杂系
统的开发中。
66. 项目都是一样的,你觉得你的项目有什么
突出的 
面试官问到这个问题时,你需要结合你自己的
项目经历,来突出你的项目的独特之处。以下
是一些可能的回答方式:
1. 技术创新:如果在项目中使用了新的技术
或者解决了某个技术难题,可以着重强调这一
点。例如,“我们在项目中使用了最新的 AI 技
术,将深度学习应用于图像处理领域,取得了
显著的成果。”
2. 用户体验:如果你的项目能够提升用户体
验,可以强调这一点。例如,“我们的项目在
用户界面和交互设计方面下足功夫,努力为用
户打造一个简洁、易用、愉悦的产品。”
3. 团队协作:如果你的项目中涉及到多人协
作,且你的团队具有良好的协作精神和高效的
工作方式,可以强调这一点。例如,“我们的
团队十分团结合作,拥有高效的沟通、协调和
解决问题的能力,这使得我们能够快速地完成
项目目标。”
4. 成功案例:如果你的项目已经取得了显著
的成果或者成功应用于某个具体的场景,可以
强调这一点。例如,“我们的项目已被成功应
用于 XX 领域中,解决了一些困难和实际的问
题,取得了较大的成果。”
总之,你需要根据你自己的项目经历,找出你
的项目在技术、用户体验、团队协作和成功案
例等方面的独特之处,并且清晰地表达出来。
这不仅能够突显你的项目优势,也能够展现你
的项目实施能力和沟通表达能力。
67. LCD 原理,LCD 分屏 
液晶显示器(LCD)原理是利用液晶分子的光
学反应,将后面的光透过每个像素。液晶分子
是带电荷的棒状分子,在没有电场的情况下会
呈现扭曲状态。当有电场施加在它们上面时,
液晶分子就会变为一直状态,使得光线能够穿
透。
液晶分屏是指将屏幕分成若干个小区域,在每
个小区域内显示不同内容,以展示更多的信息
和提高界面的可读性。常见的液晶分屏方式有
以下几种:
1. 十字屏幕:将屏幕分成十字状,通常用于
显示飞行模拟器或其他模拟器。
2. 偏移屏幕:将屏幕分成两部分,左边部分
显示一个图像,右边部分显示另一个图像。
3. 全屏幕幕膜:多个屏幕通过拼接方式组合
成一个大屏幕,广泛应用于商业广告和展会展
示。
4. 瓷砖屏幕:将屏幕拼接成规则的瓷砖形状,
广泛应用于大型户外广告。
总之,液晶显示器的原理是通过液晶分子的光
学反应来控制后面的光线透过每个像素,从而
显示出图像和文字。而液晶分屏则是将屏幕分
成若干个小区域,在每个小区域内显示不同内
容,以展示更多的信息和提高界面的可读性。
68. FB/DRM 框架 
FB(Frame Buffer)和 DRM(Direct Rendering 
Manager)是 Linux 操作系统中用于图形显示
的两个重要框架。
1. Frame Buffer
FB 是一种简单的图形驱动框架,直接依赖于
Linux 内核,并提供了对低级显卡硬件的直接
访问,它可以将屏幕数据写入到帧缓存中,然
后直接在屏幕上显示出来。由于 FB 直接访问
硬件,因此需要在驱动程序中实现各种硬件的
读写操作,适用于一些老旧或低端的显卡设备。
2. Direct Rendering Manager
DRM 是现代 Linux 系统中使用的一种高级图
形框架,它更加灵活和通用,支持多个图形驱
动程序同时运行,以提供更高效、更流畅的图
形渲染和显示。DRM 框架和 FB 框架不同,它
不直接依赖于内核,而是使用内核模块来访问
GPU,并通过与用户空间的交互,实现了对显
卡硬件的控制和管理。DRM 框架还包括了
Direct Rendering Infrastructure(DRI)子
系统,它提供了 OpenGL 和其他图形库的高性
能渲染功能。
总的来说,FB 和 DRM 都是 Linux 内核中的
图形框架,FB 是一种简单的图形驱动框架,
适用于低端显卡设备,而 DRM 是现代 Linux 
系统中使用的一种高级图形框架,支持多个图
形驱动程序同时运行,提供更高效、更流畅的
图形渲染和显示。需要根据具体情况选择合适
的图形框架来实现图形渲染和显示的功能。
69. FreeRTOS 用来干嘛, 
FreeRTOS 是一款流行的开源实时操作系统
(RTOS),可以用于小型嵌入式系统,它针对
嵌入式系统的资源有限、功耗要求低、需要实
时响应和高可靠性等特点进行优化设计。
FreeRTOS 提供了许多实时操作系统通常都具
备的特性,如任务管理、信号量、互斥锁、消
息队列等,还提供了一些高级特性,如协程、
事件组、软件定时器、定时任务等。
在使用 FreeRTOS 开发嵌入式系统时,可以将
系统划分为几个独立的任务,并为每个任务分
配一个独立的处理器时间片,这样可以使不同
任务之间的执行更加协调和稳定。同时,
FreeRTOS 还提供了多种通信机制,如队列、邮
箱、信号量等,可以用于任务之间的数据传输
和同步,从而提高整个系统的并发性和可靠性。
总的来说,FreeRTOS 主要用于解决嵌入式系统
中任务调度、资源管理、任务间通信和实时响
应等问题,实现多个任务之间的协同工作和相
互独立,提高嵌入式系统的可靠性和性能。适
用于诸如微控制器、移动设备、家电、仪器仪
表、车载系统等领域的小型嵌入式系统。
 
70. 时间片 
时间片是指在多任务系统中,将 CPU 运行时
间分配给多个任务,并按照一定的时间间隔轮
流切换任务的机制。多任务系统中的每个任务
都被分配了一个固定的时间片,当系统运行时,
每个任务轮流获取 CPU 的运行时间,执行任
务中的代码,直到时间片用完或者任务自愿让
出 CPU。当某个任务的执行时间超过了它所被
分配的时间片,操作系统会强制中断该任务的
运行,并把 CPU 分配给下一个任务。
时间片的大小通常是由操作系统决定的,通常
情况下时间片的大小可以通过修改操作系统
的参数进行调整。较短的时间片能够更快地响
应用户请求和任务之间的切换,但也会增加系
统负担和上下文切换开销;较长的时间片可以
减少上下文切换次数和系统负担,但会导致任
务切换时间较长,影响系统的实时性。因此,
在实际应用中需要根据具体情况选择合适的
时间片大小。
时间片是多任务系统的重要机制,在多任务操
作系统中扮演着非常重要的角色,通过它可以
实现任务之间的平衡和调度,提高系统的并发
性和稳定性。
71. I2c 驱动 
I2C(Inter-Integrated Circuit)驱动是一
种用于串行通信的总线协议,主要用于连接微
处理器、感应器、存储设备等各种嵌入式设备,
提供了简单、快速、低成本、多设备互连的通
信方式。
I2C 驱动的主要功能包括如下几点:
1. 初始化:对硬件进行初始化设置,并配置
I2C 的时钟频率、传输速率、地址宽度等参数。
2. 写数据:将数据写入到 I2C 总线上,以便
外围设备接收并处理数据。
3. 读数据:从 I2C 总线上读取数据,以便外
围设备返回处理结果给主控制器。
4. 错误处理:在发生错误情况(如总线冲突)
时,进行相应的错误处理操作。
在 Linux 系统中,I2C 驱动通常使用标准的 I2C 
API 来访问 I2C 总线,包括 i2c_transfer()、
i2c_smbus_read_byte() 、
i2c_smbus_write_byte()等函数,用于读写数
据和控制I2C设备。开发人员可以通过这些API
来实现自己的 I2C 驱动程序,以便与各种嵌入
式设备进行通信。
总之,I2C 驱动是一种用于串行通信的总线协
议,通过初始化、写数据、读数据和错误处理
等功能,实现与各种嵌入式设备之间的通信。
在 Linux 系统中,I2C 驱动通常使用标准的 I2C 
API 来访问 I2C 总线,通过 API 来实现自己的
I2C 驱动程序,以便与各种嵌入式设备进行通
信。
72. 二极管降压三极管环境 
1. 二极管压降
二极管压降是指在正向偏置(二极管为导通状
态)时,电流通过两个 PN 结的二极管时所产
生的电压降,它是直接与二极管材料、温度和
电流等因素有关的物理特性。二极管压降通常
用于确定二极管在导通状态下的电压损失,以
保证电路中的稳定工作。
2. 三极管环境
三极管环境是指三极管在电路中的工作环境,
包括电源电压、负载电流、温度等因素,这些
因素会影响到三极管的工作状态和性能表现。
在设计电路中需要准确的考虑这些因素,以确
保三极管的工作稳定可靠。
在实际应用中,三极管环境可能会存在一些问
题。例如,如果电源电压过高,可能会导致电
路不稳定或烧坏器件;如果负载电流过大,可
能会超出三极管的额定工作范围,影响正常的
运行;而温度的变化也会对三极管的特性造成
影响,使工作参数产生一定的漂移或者失效。
总之,二极管压降和三极管环境都是电子学中
重要的概念。二极管压降的大小决定了二极管
的基本特性,而三极管环境则是三极管在电路
中正常工作的基础条件,需要综合考虑相关因
素来确保电路的稳定性和可靠性。
73. 设备树 
设备树(Device Tree)是一种用于描述硬件
设备的数据结构,通常是一种文本文件,它描
述了系统中所有硬件设备的信息和其在系统
中的连接方式。设备树可以帮助操作系统内核
识别和管理硬件设备,减少了操作系统内核与
设备之间的耦合度,提高了操作系统的移植性
和可维护性。
设备树通常由三部分组成:根节点(root node)、
节点(node)和属性(property)。根节点是
设备树的顶层节点,包括了整个系统中的所有
设备信息,而节点则描述了具体设备的信息,
包括设备名称、驱动名称、地址、中断、时钟
等;属性则记录了设备的属性信息,如设备类
型、配置寄存器、寄存器空间大小等。
设备树的使用在各种嵌入式系统中越来越流
行,因为它能够大量减少开发人员对特定硬件
的代码修改,简化了嵌入式系统的开发和维护。
在 Linux 系统中,设备树被广泛应用于各种嵌
入式设备的驱动开发,例如通过配置设备树可
以在运行 Linux 的平台上支持多种不同的硬件
设备。
总的来说,设备树是一种用于描述硬件设备的
数据结构,它通过节点和属性的方式描述硬件
设备信息,并支持在软件中配置硬件信息,从
而大大简化了嵌入式系统的开发和维护。
74. 平台总线 
平台总线(Platform Bus)是一种在操作系统
内核中用于管理硬件设备的通用架构,它将所
有硬件设备组织成一个层次结构,将硬件设备
和操作系统内核分开,同时提供了一套标准的
驱动程序接口,使得编写设备驱动程序更简单、
更可靠。
平台总线通常由如下几个组成部分:
1. 总线控制器(Bus Controller):负责管
理总线上所有设备的注册、控制、访问等功能,
是整个平台总线的核心模块。
2. 设备节点(Device Node):是平台总线中
的硬件设备节点,包括设备所需的信息(如设
备类型、硬件地址、中断号等)。
3. 驱动程序(Driver):驱动程序可以与设
备节点相对应,负责管理设备的状态和操作,
提供了一套标准的接口,用于与设备交互。
平台总线的主要优点是:
1. 可移植性:平台总线提供了一套标准的接
口和数据结构,可以在各种硬件平台上使用,
使得开发人员可以轻松地将应用程序迁移到
不同的硬件平台。
2. 灵活性:平台总线的层次结构使得硬件设
备可以很方便地加入或移除,同时也随时可以
扩展新的设备类型。
3. 维护性:平台总线的标准驱动程序接口使
得开发人员无需关心底层硬件细节,只需要与
平台总线交互即可,从而提高了代码的可重用
性和可维护性。
总的来说,平台总线是一种在操作系统内核中
用于管理硬件设备的通用架构,它将所有硬件
设备组织成一个层次结构,在这个架构下,设
备节点和驱动程序更容易被添加、删除、修改。
平台总线的使用可以大大简化硬件和软件的
交互,提高设备驱动程序的可靠性和移植性。
75. DMA 
DMA(Direct Memory Access,直接存储器访
问)是一种计算机 I/O 技术,允许外设(如硬
盘、网卡等)直接访问系统内存,而不需要通
过 CPU 进行数据传输,这能够显著提高数据传
输速度和减少 CPU 的负担。DMA 通常由 DMA 控
制器完成,它可以自动将数据从设备读取或写
入到内存,无需 CPU 的干预。
在实际应用中,DMA 的传输模式通常分为三种:
1. 单向传输模式:用于从外设读取数据或将
数据写入外设设备中。
2. 循环传输模式:用于反复重复读/写操作,
以实现某些特定功能。
3. 内存到内存传输模式:用于在内存之间进
行数据传输,而无需依赖 CPU。
DMA 技术在操作系统中得到了广泛应用,支持
各种固定和可编程的 DMA 控制器,并且大多数
现代操作系统都有相应的 DMA API,允许开发
人员方便地利用 DMA 进行设备驱动程序的开发,
以提高系统的性能。
总之,DMA 是一种计算机 I/O 技术,允许外设
设备直接访问系统内存,无需 CPU 进行介入,
可以显著提高数据传输速度和减轻 CPU 的负担。
在实际应用中,DMA 通常由 DMA 控制器完成,
它支持多种传输模式,并且大多数现代操作系
统都提供了相应的 DMA API,方便开发人员进
行设备驱动程序的开发和优化系统性能。
76. 103 和 407 升怎样通信的 
103 和 407 是两种常见的工业控制系统通信协
议,它们之间的通信通常是通过串口方式实现。
下面是 103 和 407 通信的基本步骤:
1. 确定通信参数:在进行 103 和 407 通信之
前,需要确认通信的参数,例如波特率、数据
位、校验位和停止位等。这些参数通常在系统
设计时已经确定,并且需要保证两端的通信参
数一致才能成功通信。
2. 发送命令:发送方根据设备地址和具体指
令组成命令帧,然后将命令帧通过串口发送给
接收方。
3. 接收命令:接收方通过串口接收命令帧,
并进行解析、处理等操作。如果命令无误,则
通过串口回应确认帧,否则回应错误帧。
4. 发送响应:发送方接收到确认帧或错误帧
后,需要根据帧类型解析响应信息,并发送响
应帧给接收方。
5. 接收响应:接收方接收到响应帧后,根据
帧类型解析响应信息,并根据需要进行相应的
操作,如更新数据、显示状态、触发报警等。
总之,103 和 407 通信通常是通过串口方式实
现的,需要确定通信参数并交换命令、响应帧
以实现数据交换。这种通信方式在工业自动化、
能源管理等领域得到广泛应用,是实现设备间
通信的重要手段。
77. ADC 是怎样获取模拟量的 
ADC (Analog-to-Digital Converter,模数转
换器) 是一种将连续的模拟信号转换为数字
信号的电路设备。它可以将来自电路中传感器
等模拟信号转换为数字信号并输出给微处理
器或其他数字化设备。具体来说,ADC 获取模
拟量的过程如下:
1. 采样:在连续时间内,将模拟信号在一定
时间间隔内取若干个点,并将每个采样点的模
拟信号值转换为数字信号。
2. 量化:采样得到的模拟信号会通过 ADC 进
行量化处理,将其转换为一系列离散的数字信
号值。量化通常涉及到两个参数:比特率和参
考电压。比特率指每个采样周期内取样的数量,
参考电压则是 ADC 使用的标准电压信号,用于
表示数字信号与模拟信号之间的关系。
3. 编码:将采样后的离散数字信号转换为二
进制编码,以便被微处理器或其他数字化设备
处理和存储。
4. 输出:ADC 将转换后的数字信号输出给微处
理器或其他数字化设备,供后续的处理和分析。
总之,ADC 是一种将连续的模拟信号转换为数
字信号的电路设备,它通过采样、量化、编码
和输出等步骤,将模拟信号转换为数字信号并
输出给微处理器或其他数字化设备。在实际应
用中,ADC 广泛应用于各种工业自动化、仪器
仪表、通信、医疗等领域中。
78. 怎么将显示的图像旋转,缩放 
将显示的图像旋转或缩放可以通过图像处理
技术实现,下面简单介绍一下常用的方法:
1. 旋转:图像旋转可以通过对图像的矩阵进
行变换实现。最常见的方法是使用旋转矩阵对
图像像素坐标进行变换,使得图像以某个角度
进行旋转。具体来说,可以将原图像中的每个
像素点坐标进行旋转,再在目标图像中通过插
值算法获取新的像素值。 常用的插值算法有
双线性插值和最近邻插值等。
2. 缩放:图像缩放可以通过对原始图像进行
拉伸或压缩来实现。图像缩放通常需要保持原
始图像的长宽比不变,否则图像会发生形变。
缩放图像的方法有两种,即最近邻插值法和双
线性插值法。最近邻插值法直接使用目标像素
点坐标周围最近的像素值作为目标像素点的
值。而双线性插值法则根据目标像素周围四个
像素点的加权平均值计算目标像素点的值。
总之,图像旋转和缩放是常用的图像处理技术,
可通过矩阵变换和插值算法实现。常用的插值
算法有最近邻插值法和双线性插值法,其中后
者具有更好的效果。这些技术在计算机视觉、
图形学、数字媒体等领域得到广泛应用。
79. 内核的启动流程 
内核的启动流程是操作系统启动过程中非常
重要的步骤,它是将操作系统加载到内存中并
开始执行的过程。内核启动的基本流程如下:
1. BIOS 自检:计算机加电后,首先运行的是
主板上的 BIOS 程序,BIOS 会对硬件进行一系
列自检操作,并根据设置选择合适的引导设备。
2. 引导管理程序:当 BIOS 确定稳定的引导设
备后,会加载引导管理程序(如 GRUB),并跳
转到该程序的入口点。
3. 加载内核镜像:引导管理程序从硬盘上读
取内核镜像,并将其加载到内存中。加载内核
需要使用到硬件设备驱动程序和文件系统,因
此需要依赖 BIOS 提供的工具和接口。
4. 启动内核:内核加载完成后,控制权被转
交 给 内 核 的 入 口 点 , 即 启 动 初 始 化 函 数
start_kernel()。内核在启动过程中会进行一
系列初始化操作,包括初始化中断向量表、建
立虚拟内存映射关系等。
5. 用户空间初始化:初始化函数
start_kernel()执行完毕后,会进入用户空间
初始化过程,这时系统才真正成为可交互状态。
用户空间初始化过程中会完成一些必要的配
置任务,例如设置网络、挂载文件系统、启动
登录服务等。
6.启动初始进程:最后,内核会在用户空间中
启动初始进程(init 进程),这个进程是所有
其他进程的祖先进程,它的主要任务是启动系
统服务,为其他应用程序提供支持。
总之,内核启动流程经过 BIOS 自检、引导管
理程序、加载内核镜像、启动内核、用户空间
初始化和启动初始进程等步骤,完成了将操作
系统从硬盘加载到内存,并成功启动的过程。
80. 你写过哪些驱动(鼠标 ,触摸屏,EEPROM) 
如果面试官问我写过哪些驱动,我可以根据自
己的实际情况和经验回答。例如:
1. 鼠标驱动:我曾经在一个项目中开发了基
于 USB 接口的鼠标驱动程序。这个驱动程序涉
及到 USB 协议栈、中断处理、定时器等方面的
知识,主要目标是为 Linux 系统提供对 USB 鼠
标的支持。
2. 触摸屏驱动:我也曾经在一个嵌入式系统
项目中开发过触摸屏驱动程序。这个驱动程序
涉及到触摸屏底层硬件与驱动的交互、输入事
件的处理、坐标转换算法等方面的知识,目标
是让嵌入式系统支持触摸屏输入。
3. EEPROM 驱动:此外,我还参与过一个物联
网设备项目,其中需要开发 EEPROM 驱动程序。
这个驱动程序主要负责 EEPROM 芯片的读写操
作,包括 I2C 通信协议的实现、寄存器地址映
射等方面的知识。目标是将 EEPROM 用于设备
参数存储、固件升级等功能上。
总之,我可以说我写过鼠标驱动、触摸屏驱动、
EEPROM 驱动等,这些驱动程序涉及到的技术和
知识点不尽相同,但都需要熟悉底层硬件与系
统间的交互原理以及相关的编程技巧。同时,
我也可以介绍更多我所掌握的驱动开发技术,
以便面试官更好地了解我在此方面的经验和
能力。
81. EEPRO 驱动是怎么实现的 
EEPROM 驱 动 程 序 涉 及 到 的 主 要 功 能 是 对
EEPROM 芯片进行读写操作,通常需要实现以下
几项功能:
1. 初始化操作:在 EEPROM 驱动程序中,需要
对硬件进行初始化,包括设置 I2C 通信协议、
寄存器地址映射以及其他的配置操作。
2. 读操作:EEPROM 驱动程序需要实现从
EEPROM 芯片中读取数据的功能。在开发过程中,
需要熟悉 I2C 通信的读取过程,并根据数据长
度和 EEPROM 芯片内部地址等参数来访问对应
的寄存器。
3. 写操作:EEPROM 驱动程序需要实现向
EEPROM 芯片中写入数据的功能。这个过程与读
操作类似,需要使用 I2C 通信协议来完成数据
传输,并根据参数设置对应的寄存器地址和其
他参数。
4. 销毁操作:在 EEPROM 驱动程序退出或被卸
载时,需要对相关资源进行释放,包括关闭 I2C
总线、解除中断绑定及释放其他相关资源等。
主要的实现方式如下:
1. 硬件层面:EEPROM 驱动程序需要通过硬件
与 EEPROM 芯片进行通信,最常用的接口是 I2C
接口。系统需要先初始化 I2C 总线相关的寄存
器及引脚等硬件信息,然后通过 I2C 接口与
EEPROM 进行通信。其中需要掌握 I2C 协议的工
作原理和寄存器配置等硬件相关知识。
2. 软件层面:在软件层面,EEPROM 驱动程序
一般由多个模块组成,如读取模块、写入模块、
初始化模块和销毁模块等组成。其中,读取模
块和写入模块需要掌握 I2C 协议的读写基本操
作,并根据要求设置对应的寄存器地址和参数。
而初始化模块和销毁模块通常是管理 I2C 总线
和接口资源的模块,需要完成初始化和释放的
操作。
总之,EEPROM 驱动程序的实现需要充分了解
I2C 协议、寄存器操作、中断处理和系统调用
等技术,能够实现 EEPROM 芯片的读写操作并
提供相应的系统调用接口。
82. I2C 的速率控制,和通信实现的硬件原理 
I2C(Inter-Integrated Circuit)总线是一
种广泛应用于嵌入式系统中的串行通信协议,
它可以连接多个设备进行数据交换。I2C 总线
的速率控制和通信实现的硬件原理如下:
1. 速率控制:I2C 总线的速率取决于主机和从
机的处理速度,以及连接的设备数量等因素。
一般情况下,I2C 总线的通信速率分为两种模
式,即标准模式和快速模式。标准模式下,I2C
总线的最大速率为 100kbps,快速模式下最大
速率为 400kbps。如果需要提高通信速率,还
可以使用高速模式,最大速率可达到 3.4Mbps。
速率控制通常由硬件实现,其核心就是通过控
制时钟频率控制数据传输速率。
2. 通信实现:I2C 总线进行通信时,需要两根
信号线:SDA 和 SCL。其中,SDA 线用于数据传
输,SCL 线用于同步时钟信号。I2C 总线是一
种主从式结构的总线,其中一个设备为主机,
其余设备都是从机。主机负责对从机进行数据
读写等操作,而从机只接收来自主机的请求并
响应。当主机向从机发送命令时,通过 SCL 线
控制时钟周期进行同步,并利用 SDA 线传输数
据。主机发出数据后,从机会发回一个应答信
号来确认是否接收数据。通信实现的硬件原理
即是通过 I2C 总线上的时钟和数据传输来完成
主机与从机之间的交互。
总之,I2C 总线的速率控制和通信实现的硬件
原理需要掌握 I2C 协议的基本知识、I2C 总线
的物理结构以及相关的信号线和设备工作原
理等。同时,还需要了解如何根据需求选择不
同的速率模式和实现方式,并且针对具体的应
用场景进行调优和优化。
83. I2C 的上拉电阻有什么用 
I2C 总线通信需要使用两根信号线:串行数据
线 SDA 和串行时钟线 SCL。其中,SDA 和 SCL
线都需要具备上拉电阻,以确保总线的正确工
作。
在 I2C 总线中,SDA 和 SCL 线被从机设备和主
机设备共享,如果没有上拉电阻,则可能出现
以下几种问题:
1. 总线信号电平不稳定问题:I2C 总线是一个
双向总线,其上拉电阻会将总线的信号电平拉
到高电平状态,以确保总线上的信号始终处于
静态状态。如果没有上拉电阻,则总线上的信
号电平就可能会出现漂移和抖动等问题。
2. 信号干扰问题:在进行 I2C 总线通信时,
主机或从机的输出信号需要在上拉电阻的作
用下才能被传输到总线上。如果没有上拉电阻,
则会导致信号相互干扰,从而使通信失败。
因此,I2C 总线中的上拉电阻起到了确保总线
信号电平稳定的作用,这样可以避免信号干扰
和通信错误的发生。在实际应用中,上拉电阻
的阻值一般取决于所连接的 I2C 总线设备的数
量和距离等因素,通常在 1-10KΩ之间。
84. EEPROM 页写的实现 
EEPROM 页写操作是指将 EEPROM 芯片中的一整
页数据进行写入,而不是单独写入一个字节或
几个字节。由于 EEPROM 芯片的物理结构是按
页划分的,因此进行页写操作可以提高写入数
据的效率,减少写操作所需的时间。EEPROM 页
写的具体实现步骤如下:
1. 选择页起始地址:在进行页写操作时,需
要先确定写入数据的页起始地址。一般情况下,
页的大小为 16 到 256 字节不等,因此需要根
据实际情况选择合适的页起始地址。
2. 向页地址发送写入命令:向 I2C 总线发送
写入命令,并且附加上页地址信息。此时,
EEPROM 芯片会开始接收后续的数据。
3. 写入页数据:根据所选的页大小,将需要
写入的数据按照页内存储方式依次写入到
EEPROM 芯片中。如果一页的数据没有写满,则
需要进行填充操作。
4. 等待写入完成:在写入数据的过程中,
EEPROM 芯片会进行相应的擦除和写入操作。在
这个过程中,I2C 总线会被锁定,直到数据写
入完成才会解锁。程序需要在写入数据之后等
待一段时间,直到 EEPROM 芯片完成数据写入
操作为止。
总之,EEPROM 页写操作能够提高写入数据的效
率,减少写入操作所需的时间。其实现过程需
要熟悉 I2C 总线的基本工作原理和 EEPROM 芯
片的页内存储结构,以及掌握 EEPROM 芯片的
写入命令和数据格式等技术。同时,在进行
EEPROM 页写操作时,还需要注意数据填充和写
入完成等细节问题,以确保数据写入的正确性
和稳定性。
85. Linux 内核的并发控制,阻塞,以及中断
下半部 
Linux 内核中的并发控制、阻塞以及中断下半
部(Bottom Half)是操作系统中非常重要的
概念和机制。下面是它们的具体说明:
1. 并发控制:在 Linux 内核中,有很多的内
核模块需要同时访问同一个共享资源,这就要
求对并发访问进行合理的控制。为了保证系统
的正确性和稳定性,在内核代码中使用了多种
并发控制方法,如自旋锁、读写锁、信号量等。
这些方法可以有效地控制内核模块的并发访
问,避免出现数据竞争等问题。
2. 阻塞:在许多情况下,对于内核模块的操
作需要等待某些事件的发生,例如设备驱动程
序等待设备完成响应。在这种情况下,内核模
块需要执行阻塞操作,即暂停当前进程,等待
事件的发生。一般情况下,阻塞操作会挂起进
程并将其放入等待队列,直到满足条件后再将
其唤醒。在内核中,有多种阻塞方式可供开发
人员使用,如休眠、忙等待、软中断等。
3. 中断下半部:当硬件设备发生中断时,内
核需要尽快地响应中断,完成中断处理并恢复
设备的工作。然而,在中断处理函数中执行过
多的操作可能会导致系统响应缓慢或者不可
预料的结果。因此,内核提供了一种称为中断
下半部(Bottom Half)的机制来处理中断操
作。中断下半部可以在中断处理函数之后被调
用,以完成一些与中断相关的操作。
总之,Linux 内核的并发控制、阻塞以及中断
下半部是保证系统正确性和稳定性的一些重
要机制。开发人员需要熟悉并掌握这些机制,
以便更好地进行内核编程和系统优化。
86. 进程和线程有什么区别
进程(Process)和线程(Thread)是操作系
统中两个重要的概念,它们都代表了正在运行
的程序在计算机中的状态。进程是系统分配资
源的基本单位,每个进程都有独立的内存空间、
数据栈以及代码区域;而线程是独立调度的基
本单位,一个进程中可以有多个线程,它们共
享了进程的资源,如内存空间、文件句柄等。
具体来说,进程是一个动态的概念,表示正在
执行的程序,包含了程序执行的上下文环境,
如寄存器、堆栈、内存映像和打开的文件等信
息。而线程是进程中的一个执行流,在同一进
程内的各个线程之间是可以共享进程的资源
的,线程也有自己的堆栈和程序计数器,但是
没有独立的地址空间,因此线程之间的通信相
对进程会更加方便。
此外,由于进程拥有独立的虚拟地址空间,所
以不同进程之间的通信需要通过进程间通信
(IPC)实现,而线程之间的通信则可以直接
读写进程中的共享变量等方式实现。
综上,进程和线程都是操作系统中非常重要的
概念,进程是系统资源分配的基本单位,而线
程是独立调度和执行的基本单位,为了更好地
利用计算机资源,程序员应该充分理解和利用
它们之间的区别与联系。
87. WIFI 的模式,WIFI 的引脚知道哪些 
WIFI 是一种无线网络技术,它广泛应用于移动
设备、家庭网络、企业网络等领域。根据使用
场景和需求的不同,WIFI 可以分为以下几种模
式:
1. 热点模式(Hotspot mode):热点模式是
指将 WIFI 设备设置为无线网络热点,其他设
备可以通过该热点连接到 Internet 或者其他
网络。例如,在手机中开启热点功能,其他手
机或电脑就可以通过该热点进行上网和数据
传输。
2. 客户端模式(Client mode):客户端模式
是指将 WIFI 设备连接到一个现有的 WIFI 网络,
以便设备可以访问 Internet 或者其他网络资
源。例如,在手机或电脑中连接到家庭或者企
业的 WIFI 网络。
3. 无线桥接模式(Wireless bridge mode):
无线桥接模式是指将两个或多个 WIFI 网络连
接在一起,以便实现互联和数据传输。例如,
在办公室或者公司中,需要将不同部门或楼层
的 WIFI 网络连接在一起,以实现统一管理和
数据交换。
4. 无 线 扩 展 器 模 式 ( Wireless extender 
mode):无线扩展器模式是指将 WIFI 信号进
行扩展,以便覆盖更广的范围。例如,在家庭
中,可以使用无线扩展器将 WIFI 信号扩展到
卧室和客厅等房间。
至于关于 WIFI 的引脚,一般来说是由 WIFI 模
块制造商根据自身设计决定的。通常,WIFI 模
块的引脚包括供电引脚、复位引脚、串行通信
接口引脚(如 SPI、I2C、UART 等)、GPIO 引
脚等。这些引脚对于 WIFI 模块的工作非常重
要,开发人员在使用 WIFI 模块时需要仔细阅
读相关的数据手册和规格说明,以确保正确连
接和使用。
88. FreeRTOS 的好处 
FreeRTOS 是一款适用于嵌入式系统的实时操
作系统(RTOS),它具有以下优点和好处:
1. 实时性强:FreeRTOS 的核心是一个实时内
核,能够对任务进行高效的调度和管理。它采
用先进的调度算法和优先级管理机制,能够实
现毫秒级别的实时响应。
2. 易于移植和使用:FreeRTOS 是一款开源的
软件,其代码结构简单、可移植性强。由于其
使用的库函数较少,因此可以方便地嵌入到不
同硬件平台和应用场景中。
3. 系统资源占用低:FreeRTOS 采用了多种节
约资源的策略,包括动态内存管理、任务栈大
小优化、调度器优化等,因而在系统资源占用
方面相对较低,符合嵌入式系统的要求。
4. 支持多任务处理:FreeRTOS 支持多任务处
理,能够并行运行多个任务,提高系统的工作
效率和响应速度。
5. 可靠性高:FreeRTOS 对任务进行了完善的
保护机制,保证了系统的安全性和稳定性。在
程序中出现错误时,FreeRTOS 能够及时发现并
进行异常处理,避免程序崩溃或导致其他系统
问题。
总之,FreeRTOS 是一款适用于嵌入式系统的实
时操作系统,具有实时性强、易于移植和使用、
系统资源占用低、支持多任务处理和可靠性高
等优点。这些特点使得 FreeRTOS 成为嵌入式
开发人员广泛关注和使用的 RTOS 之一。
89. 电磁炮实现原理 
电磁炮是一种利用电磁力推动物体达到高速
移动的装置。其实现原理可以简单描述为:
1. 利用电流产生磁场:在电磁炮中,通过经
过铜线的电流能够在导线周围生成磁场。
2. 利用电磁力推动炮弹:将炮弹置于电磁炮
管内部,当有足够大的瞬时电流通过炮管内的
线圈时,产生的瞬间电磁力会将炮弹加速推出。
3. 多级加速:为了得到更高的速度,使用多
段的线圈,这些线圈之间由恰当的时间间隔来
进行触发,在每个线圈上加速更多磁场能量,
使炮弹获得更高的速度。
4. 能量转换:最后,通过反向电流将磁场反
向并消失来停止电磁炮的加速过程,并将磁场
产生的能量转化为电流来存储或消散。
总之,电磁炮的实现原理是基于电磁感应和电
磁力的物理原理,通过电流产生磁场并利用磁
场产生的电磁力将炮弹推动并加速达到高速
移动的效果。它具有瞬间加速度大、速度高、
精度高等优点,被广泛应用于军事、科研和工
业领域。
90. TFT 屏幕获取坐标的原理 
TFT 液晶屏幕获取坐标的原理是通过触摸屏来
实现的。触摸屏一般由一层透明的触摸传感器
和一层透明导电膜组成,导电膜上均匀分布着
一系列的导电线。
当手指或其他物体接触到触摸屏表面时,由于
人体或其他物体一般带有电荷,会形成一个电
容,这个电容会改变导电膜上的电场分布,使
导电线上的电流发生变化。通过对导电膜上的
电流进行检测和处理,就可以确定触摸的位置。
具体来说,TFT 液晶屏幕一般采用四线或五线
式的触摸屏控制芯片,其中四条或五条线分别
对应触摸屏中 X 轴和 Y 轴的导电线。控制芯片
在扫描触摸屏导电膜上的电流时,会分别将 X
轴和 Y 轴的电压进行读取,并将读取到的数据
经过算法处理,得到触摸点的坐标值。这些坐
标值可以通过串口、I2C、SPI 等接口发送到主
控芯片,由主控芯片进一步处理和使用,实现
如触摸屏键盘、手写笔和手势识别等功能。
总之,TFT 屏幕获取坐标的原理是通过触摸屏
控制芯片对导电膜上的电流进行检测和处理,
确定触摸点的坐标。
91. uboot 启动流程 
U-Boot 是 一 种 开 放 源 码 的 引 导 加 载 程 序
(bootloader),它主要用于在嵌入式系统中
启动操作系统内核。U-Boot 启动流程一般分为
以下几个步骤:
1. 硬件初始化:U-Boot 首先进行硬件初始化,
包括 CPU、内存、外设等的初始化工作,以便
后续的代码能够正确运行。
2. 加载 U-Boot 镜像:U-Boot 启动时会从存储
介质(如闪存、SD 卡)读取 U-Boot 镜像并加
载到 RAM 中。
3. 启动执行 U-Boot 镜像:将读取到的 U-Boot
镜像复制到 RAM 中,然后跳转到 U-Boot 代码
的入口地址开始执行。执行 U-Boot 代码可以
进行更多初始化和配置工作。
4. 配置和检查环境变量:U-Boot 通常使用环
境变量来保存系统的配置信息,例如启动参数、
IP 地址、MAC 地址等。在启动过程中,U-Boot
会读取环境变量信息并进行检查和配置,以确
保系统的正确性。
5. 加载内核镜像:U-Boot 会根据环境变量中
定义的内核映像位置和参数,从存储介质中加
载操作系统内核映像到内存中。
6. 启动执行内核镜像:内核镜像加载完成后,
U-Boot 会将控制权交给内核,并开始执行内核
代码,从而启动整个系统。
总之,U-Boot 是启动 Linux 等操作系统的重要
组件之一,它通过硬件初始化、加载 U-Boot
镜像、配置环境变量、加载内核镜像和启动内
核镜像等步骤,实现了对嵌入式系统的管理和
控制。
92. 内核启动顺序 
内核启动顺序可以简单地概括为以下三个阶
段:
1. 体系结构初始化:内核在启动时首先进行
一系列的体系结构初始化工作,包括处理器芯
片、存储器、总线、中断控制器、时钟、串口、
以太网等硬件的初始化。这样,操作系统可以
使用这些硬件功能来进行后续的工作。
2. 启动加载程序(bootloader):bootloader
是一种引导加载程序,其作用是从存储介质
(硬盘、U 盘、CD 等)中读取内核映像文件,
并把内核载入内存并启动内核执行。常用的
bootloader 有 GRUB、U-Boot 等。
3. 内核初始化和启动:内核被加载入内存后,
开始执行启动代码,它会先进行各种初始化工
作,建立操作系统的基本环境和数据结构,然
后进行系统设置和设备驱动程序的初始化,最
终启动系统的第一个进程,通常是 init 进程。
此外,在内核初始化和启动阶段中还有以下几
个重要的工作:
- 内存管理的建立并初始化
- 虚拟文件系统挂载
- 系统调用初始化
- 进程初始化
总之,内核启动顺序包括了体系结构初始化、
启动加载程序和内核初始化和启动三个阶段。
内核初始化和启动阶段是整个过程中最为关
键的一步,需要进行各种初始化工作,建立操
作系统的基本环境和数据结构,从而使操作系
统进入到正常的运行状态。
93. 线程和进程哪个更费资源,为什么 
在操作系统中,线程(thread)和进程(process)
都是系统的执行单元,但它们之间存在一些区
别。通常情况下,每个进程都至少有一个线程,
而一个线程只能属于一个进程。相较于进程,
线程的创建、切换和销毁等操作开销要小很多。
因此,从资源使用的角度来看,线程比进程更
节省资源。
具体来说,与进程相比,线程所需的内存空间
更少,上下文切换效率更高,且同一进程内的
多个线程可以共享该进程所拥有的资源,包括
代码段、堆和文件描述符等。
另一方面,进程所需要的资源相对较多,包括
地址空间、文件描述符、信号处理器、用户 ID
和组 ID 等。在创建进程时,需要为其分配独
立的内存空间、打开标准输入输出等文件描述
符,并且还需要进行一些额外的初始化工作,
这些操作会占用相对较多的系统资源。
综上所述,线程比进程更节省资源,不仅因为
线程所需的内存空间更小,而且其创建、切换
和销毁等操作成本更低。但是,在某些情况下,
进程比线程更加可靠和安全,因此在选择使用
线程或进程时,需要根据具体的应用场景来进
行权衡和选择。
94. C++多态,虚函数 
在 C++中,多态(polymorphism)是一种重要
的特性,它允许通过基类的指针或引用来调用
派生类的成员函数,实现对不同的子类进行统
一的处理。而实现多态的关键就是使用虚函数
(virtual function)。
虚函数是动态绑定的一种方式,即在程序运行
时动态确定调用哪个函数。在 C++中,如果将
一个成员函数声明为虚函数,则编译器会将该
函数与类的虚函数表(vtable)关联起来,当
调用该函数时,会根据对象的类型动态地选择
正确的虚函数进行调用。
使用虚函数可以实现多态性,即同名函数在不
同的派生类中的具体实现可能不同。例如:
```c++
class Animal {
public:
 virtual void eat() {
 std::cout << "I'm eating..." << 
std::endl;
 }
};
class Dog : public Animal {
public:
 void eat() override {
 std::cout << "Dog is eating..." << 
std::endl;
 }
};
class Cat : public Animal {
public:
 void eat() override {
 std::cout << "Cat is eating..." << 
std::endl;
 }
};
int main() {
 Animal* animal = new Animal();
 Animal* dog = new Dog();
 Animal* cat = new Cat();
 animal->eat(); // I'm eating...
 dog->eat(); // Dog is eating...
 cat->eat(); // Cat is eating...
 
 return 0;
}
```
在上述代码中,Animal 类的 eat 函数被声明为
虚函数,派生类 Dog 和 Cat 分别重写了该函数。
在 main 函数中,创建了 Animal、Dog 和 Cat
的实例,并调用它们的 eat 函数,由于 eat 函
数是虚函数,因此会根据不同的对象的类型动
态地选择正确的虚函数进行调用。
总之,C++的多态性是通过虚函数来实现的。
使用虚函数,可以在基类中定义一个通用的接
口,让派生类来具体实现该接口,从而实现对
不同的子类进行统一的处理。
95. 分屏显示怎么实现 
分屏显示一般是在计算机的图形界面中实现
的,具体可以通过以下几种方式来实现:
1. 操作系统原生支持:一些操作系统(如
Windows、macOS)提供了分屏功能,用户可以
通过按键或手势来访问。对于 Windows 系统,
用户可以使用 Win+左/右箭头键来将窗口分别
置于屏幕的左右半部分,从而实现分屏显示。
2. 使用应用程序:一些应用程序(如终端模
拟器、编辑器等)自带分屏功能,用户可以在
一个应用程序窗口中同时显示多个子窗口。例
如,Vim 编辑器支持使用 split、vsplit 命令
来在 Vim 窗口中分割屏幕,并显示多个文件。
3. 使用第三方工具:一些第三方工具(如 i3wm、
tmux 等)提供了更加高级的分屏功能,用户可
以灵活地设置和管理窗口布局。例如,i3wm 是
一款动态平铺式窗口管理器,可以自动调整窗
口大小和位置,从而实现快速的分屏。
总之,实现分屏显示可以通过操作系统原生支
持、使用应用程序或使用第三方工具等方式来
实现。具体选择哪种方式要根据具体应用场景
和需求来进行权衡和选择。
96. ADC 引脚设置怎么转换数据 
ADC(Analog-to-Digital Converter,模数转
换器)是一种电路组件,用于将模拟信号转换
成数字信号。在嵌入式系统中,开发人员可以
通过给 ADC 引脚设置来实现模拟信号的采集和
转换。下面简要介绍一下如何进行 ADC 引脚设
置和如何将模拟信号转换为数字信号:
ADC 引脚设置:
1. 首先,需要确认芯片 Datasheet 中 ADC 引
脚的具体信息,例如引脚名称、编号和工作电
压等。
2. 根据具体的硬件平台(例如 Arduino、树莓
派等)和编程语言(例如 C、Python 等),编
写相应的程序代码,以设置 ADC 引脚。
3. 在代码中,需要使用相应的函数库或驱动
程序,例如 WiringPi、RPi.GPIO 等,来控制
ADC 引脚的状态和操作。例如,在树莓派上使
用 Python,可以通过 RPi.GPIO 库的`setup()`
函数来设置引脚的输入输出模式和电平状态。
将模拟信号转换为数字信号:
1. 首先,需要使用 ADC 将模拟信号转换为数
字信号。在硬件电路中,需要将模拟信号输入
到 ADC 引脚中,然后通过 ADC 进行采样和量化,
从而输出数字信号。在程序代码中,需要使用
相应的函数库或驱动程序,例如 WiringPi、
RPi.GPIO 等,来读取 ADC 输出的数字信号。
2. 然后,需要对数字信号进行相关的处理,
例如用算法进行滤波、校准和标定等。在程序
代码中,需要编写相应的处理算法,以实现对
数字信号的处理。
3. 最后,将处理后的数字信号传输到其他设
备或进行存储和分析处理。在程序代码中,可
以使用相应的通信协议(例如 UART、SPI、I2C
等)或数据存储方式(例如文件系统、数据库
等)来实现数据的传输和存储。
以上是 ADC 引脚设置和如何将模拟信号转换为
数字信号的基本步骤,具体实现方法还要根据
具体的硬件平台和编程语言来进行选择和配
置。
97. 描述一下平台总线和 Input 子系统 
平台总线(platform bus)是一个用于描述 SOC 
上的片上资源的虚拟总线。它并不是一个实际
的总线,而是一个驱动管理和注册机制。平台
总线在 Linux 操作系统的内核中被广泛使用。
相比于其他的总线如 PCI 和 USB 等,平台总
线主要用于描述 SOC 上的片上资源,例如
I2C 控制器、SPI 控制器、GPIO 控制器等,
并且这些控制器的访问是通过 CPU 的总线直
接取址来实现的。平台总线的引入减少了设备
的重复编写,提高了代码的可重用性和可维护
性。
input 子系统是 Linux 内核中的一个子系统,
主要用于处理输入设备,例如键盘、鼠标、触
摸屏等。input 子系统与平台总线之间的联系
是,一些输入设备可以是基于平台总线的,因
此需要在 input 子系统中进行注册和管理。
在 input 子系统中,每个输入设备都对应一
个 input_device 结构体,其中包含了输入设
备的名称、类型、物理地址等信息,并可以注
册对应的事件处理函数。每次输入设备的状态
变 化 ( 例 如 按 键 按 下 或 松 开 ) 都 会 通 过
input_event 结构体的形式通知到 input 子
系 统 , 然 后 再 通 过 应 用 程 序 调 用
input_device 对应的事件处理函数来处理输
入事件。
综上所述,平台总线主要用于描述 SOC 上的
片上资源,而 input 子系统则用于处理输入
设备。两者之间并没有直接的联系,但是在一
些场景下可能需要进行配合和协同工作。
98. 多进程之间,多线程之间的通信方式有哪
些?你最常用的是哪个 
多进程之间通信的方式有共享内存、消息队列、
管道、Socket 等。其中,共享内存和管道是较
为常用的方式。共享内存是指在多个进程之间
共享同一块物理内存区域,并利用同步机制来
保证数据的一致性,因此可以实现高效的数据
传递。而管道则是 Unix 中最古老的进程间通
信方式,它只能用于有血缘关系的进程之间信
息传递,但其实现简单,适用于一些小规模任
务。
多线程之间通信的方式有锁、条件变量、信号
量、队列等。其中,锁和条件变量是最基本的
线程同步手段,用于保护共享资源的访问,从
而避免数据竞争问题。信号量则是一种更为通
用的同步工具,除了能够控制共享资源的访问
外,还可以实现进程之间的同步和通信。队列
则是一种较为高级的线程通信机制,提供了生
产者-消费者模型的支持,它可以实现线程之
间的解耦和并行处理。
对于我最常用的通信方式,如果是在多进程场
景下,我会选择共享内存或管道进行通信,具
体根据任务需求来选择。如果是在多线程场景
下,我比较常用的是锁和条件变量,这也是最
基本的线程同步手段.
99. 项目 PWM 输出 占空比是多少,影响 PWM 输
出的因素 
对于具体的项目 PWM 输出占空比,需要根据实
际需求进行确定,一般在 0% 到 100% 范围内,
例如在[1]中给出了一个例子,要求四路 PWM
的占空比可调,在这种情况下,占空比可以根
据具体的需求进行设置。
影响 PWM 输出的因素有以下几个:
1. 周期时间及占空比: PWM 周期时间和占空
比直接决定了输出波形的形态和幅度。周期时
间越短,输出的高频率波形越多,幅度相对就
会小;占空比越大,输出波形的高电平时间就
越长,从而使输出电压平均值越高。
2. PWM 模块参数的选择:不同的 MCU 厂家引脚
车复用功能不同,所以在选择时要根据硬件设
计需求来选择合适的 MCU 型号。同样地,GPIO
端口的数目、输出的最大功率等都影响着 PWM
的使用效果。
3. 外围电路的设计:通过外围电路,可以实
现 PWM 信号的级联、放大或滤波等。例如,可
以通过 RC 滤波器或低通滤波器来控制 PWM 的
占空比。此外,在某些应用场景下,还需要引
入额外的保护电路,例如过压保护、欠压保护
等。
4. 环境温度和供电电压:这些因素会直接影
响到 PWM 模块的工作状态和输出效果。在高温
环境下,晶体管的导通电阻会增加,从而影响
PWM 输出的精度;同时,在电源电压变化较大
时,可能会导致 PWM 波形出现失真或者振荡。
综上所述,影响 PWM 输出的因素很多,其中最
重要的是周期时间和占空比。此外还要注意选
择合适的 MCU 型号、外围电路设计以及环境温
度和供电电压。
100. 温湿度传感器(DHT11)通过什么传输
数据 
温湿度传感器用于测量室内环境的温度和湿
度,并通过 RS485 接口、干节点输出接口、
4-20mA 模拟输出等多种型号产品进行数据传
输。而对于 DHT11 温湿度传感器,经过查询
得知它属于数字输出型传感器。DHT11 通过一
个 8 位单片机将测量到的温度和湿度数据以序
列化的方式输出至单个数据引脚,即可通过该
引脚进行数据传输。这个传输信号可以被单片
机、Arduino 等其他微控制器检测、读取并实
时处理。
因此,DHT11 温湿度传感器通过单个数据引脚
进行数字信号传输,不需要像 RS485 接口、
干节点输出接口、4-20mA 模拟输出等那样的
多种接口进行数据传输。
101.预处理器(Preprocessor) 
1 . 用预处理指令#define 声明一个常数,用
以表明 1 年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 
365)UL
我在这想看到几件事情:
(1) #define 语法的基本知识(例如:不能
以分号结束,括号的使用,等等)
(2)懂得预处理器将为你计算常数表达式的
值,因此,直接写出你是如何计算一年中有多
少秒而不是计算出实际的值,是更清晰而没有
代价的。
(3)意识到这个表达式将使一个 16 位机的整
型数溢出-因此要用到长整型符号 L,告诉编译
器这个常数是的长整型数。
(4) 如果你在你的表达式中用到 UL(表示无
符号长整型),那么你有了一个好的起点。记
住,第一印象很重要。
2 . 写一个"标准"宏 MIN ,这个宏输入两个参
数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 
这个测试是为下面的目的而设的:
(1) 标识#define 在宏中应用的基本知识。
这是很重要的。因为在 嵌入(inline)操作符
变为标准 C 的一部分之前,宏是方便产生嵌入
代码的唯一方法,对于嵌入式系统来说,为了
能达到要求的性能,嵌入代码经常是必须的方
法。
(2)三重条件操作符的知识。这个操作符存
在 C 语言中的原因是它使得编译器能产生比
if-then-else 更优化的代码,了解这个用法是
很重要的。
(3) 懂得在宏中小心地把参数用括号括起来
(4) 我也用这个问题开始讨论宏的副作用,
例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
3. 预处理器标识#error 的目的是什么?
如果你不知道答案,请看参考文献 1。这问题
对区分一个正常的伙计和一个书呆子是很有
用的。只有书呆子才会读 C 语言课本的附录去
找出象这种问题的答案。当然如果你不是在找
一个书呆子,那么应试者最好希望自己不要知
道答案。
#error 命令是 C/C++语言的预处理命令之一,
当预处理器预处理到#error 命令时将停止编
译并输出用户自定义的错误消息。
102.死循环(Infinite loops) 
4. 嵌入式系统中经常要用到无限循环,你怎
么样用 C 编写死循环呢?
这个问题用几个解决方案。我首选的方案是: 
 
while(1) 

 

一些程序员更喜欢如下方案:
for(;;)
{
}
这个实现方式让我为难,因为这个语法没有确
切表达到底怎么回事。如果一个应试者给出这
个作为方案,我将用这个作为一个机会去探究
他们这样做的基本原理。如果他们的基本答案
是:"我被教着这样做,但从没有想到过为什
么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一
个汇编语言程序员(这也许是好事)或者他是
一个想进入新领域的 BASIC/FORTRAN 程序员。
103.数据声明(Data declarations) 
5. 用变量 a 给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an 
integer)
c)一个指向指针的的指针,它指向的指针是指
向一个整型数( A pointer to a pointer to 
an intege)r 
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) 一个指向函数的指针,该函数有一个整型
参数并返回一个整型数(A pointer to a 
function that takes an integer as an 
argument and returns an integer)
h) 一个有 10 个指针的数组,该指针指向一个
函数,该函数有一个整型参数并返回一个整型
数( An array of ten pointers to functions 
that take an integer argument and return 
an integer )
答案是:
a) int a; // An integer 
b) int *a; // A pointer to an integer 
c) int **a; // A pointer to a pointer to 
an integer 
d) int a[10]; // An array of 10 integers 
e) int *a[10]; // An array of 10 pointers 
to integers 
f) int (*a)[10]; // A pointer to an array 
of 10 integers 
g) int (*a)(int); // A pointer to a 
function a that takes an integer argument 
and returns an integer 
h) int (*a[10])(int); // An array of 10 
pointers to functions that take an integer 
argument and return an integer 
104.Static 
6. 关键字 static 的作用是什么?
这个简单的问题很少有人能回答完全。在 C 语
言中,关键字 static 有三个明显的作用:
(1)在函数体,一个被声明为静态的变量在
这一函数被调用过程中维持其值不变。
(2)在模块内(但在函数体外),一个被声
明为静态的变量可以被模块内所有函数访问,
但不能被模块外其它函数访问。它是一个本地
的全局变量。
(3)在模块内,一个被声明为静态的函数只
可被这一模块内的其它函数调用。那就是,这
个函数被限制在声明它的模块的本地范围内
使用。
大多数应试者能正确回答第一部分,一部分能
正确回答第二部分,同是很少的人能懂得第三
部分。这是一个应试者的严重的缺点,因为他
显然不懂得本地化数据和代码范围的好处和
重要性。
 
105.Const 
7.关键字 const 有什么含意?
我只要一听到被面试者说:"const 意味着常数
",我就知道我正在和一个业余者打交道。去
年 Dan Saks 已经在他的文章里完全概括了
const 的所有用法,因此 ESP(译者:Embedded 
Systems Programming)的每一位读者应该非常
熟悉 const 能做什么和不能做什么.如果你从
没有读到那篇文章,只要能说出 const 意味着
"只读"就可以了。尽管这个答案不是完全的答
案,但我接受它作为一个正确的答案。(如果
你想知道更详细的答案,仔细读一下 Saks 的
文章吧。)
如果应试者能正确回答这个问题,我将问他一
个附加的问题:
下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前两个的作用是一样,a 是一个常整型数。
第三个意味着 a 是一个指向常整型数的指针
(也就是,整型数是不可修改的,但指针可以)。
第四个意思 a 是一个指向整型数的常指针(也
就是说,指针指向的整型数是可以修改的,但
指针是不可修改的)。
最后一个意味着 a 是一个指向常整型数的常指
针(也就是说,指针指向的整型数是不可修改
的,同时指针也是不可修改的)。
如果应试者能正确回答这些问题,那么他就给
我留下了一个好印象。顺带提一句,也许你可
能会问,即使不用关键字 const,也还是能很
容易写出功能正确的程序,那么我为什么还要
如此看重关键字 const 呢?我也如下的几下理
由:
(1)关键字 const 的作用是为给读你代码的
人传达非常有用的信息,实际上,声明一个参
数为常量是为了告诉了用户这个参数的应用
目的。如果你曾花很多时间清理其它人留下的
垃圾,你就会很快学会感谢这点多余的信息。
(当然,懂得用 const 的程序员很少会留下的
垃圾让别人来清理的。)
(2)通过给优化器一些附加的信息,使用关
键字 const 也许能产生更紧凑的代码。
(3) 合理地使用关键字 const 可以使编译器
很自然地保护那些不希望被改变的参数,防止
其被无意的代码修改。简而言之,这样可以减
少 bug 的出现。
106.Volatile 
8. 关键字 volatile 有什么含意?并给出三个
不同的例子。
一个定义为 volatile 的变量是说这变量可能
会被意想不到地改变,这样,编译器就不会去
假设这个变量的值了。精确地说就是,优化器
在用到这个变量时必须每次都小心地重新读
取这个变量的值,而不是使用保存在寄存器里
的备份。下面是 volatile 变量的几个例子:
(1)并行设备的硬件寄存器(如:状态寄存
器)
(2) 一个中断服务子程序中会访问到的非自
动变量(Non-automatic variables)
(3) 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认
为这是区分 C 程序员和嵌入式系统程序员的最
基本的问题。搞嵌入式的家伙们经常同硬件、
中断、RTOS 等等打交道,所有这些都要求用到
volatile 变量。不懂得 volatile 的内容将会
带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀
疑是否会是这样),我将稍微深究一下,看一
下这家伙是不是直正懂得 volatile 完全的重
要性。
1)一个参数既可以是const还可以是volatile
吗?解释为什么。
2)一个指针可以是 volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
(1)是的。一个例子是只读的状态寄存器。
它是 volatile 因为它可能被意想不到地改变。
它是 const 因为程序不应该试图去修改它。
(2); 是的。尽管这并不很常见。一个例子
是当一个中服务子程序修该一个指向一个
buffer 的指针时。
(3) 这段代码有点变态。这段代码的目的是
用来返指针*ptr 指向值的平方,但是,由于
*ptr 指向一个 volatile 型参数,编译器将产
生类似下面的代码:
int square(volatile int *ptr) 
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr 的值可能被意想不到地该变,因此 a
和 b 可能是不同的。结果,这段代码可能返不
是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr) 
{
int a;
a = *ptr;
return a * a;
}
107.位操作(Bit manipulation) 
9. 嵌入式系统总是要用户对变量或寄存器进
行位操作。给定一个整型变量 a,写两段代码,
第一个设置 a 的 bit 3,第二个清除 a 的 bit 3。
在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
(1)不知道如何下手。该被面者从没做过任
何嵌入式系统的工作。
(2) 用 bit fields。Bit fields 是被扔到 C
语言死角的东西,它保证你的代码在不同编译
器之间是不可移植的,同时也保证了的你的代
码是不可重用的。我最近不幸看到 Infineon
为其较复杂的通信芯片写的驱动程序,它用到
了 bit fields 因此完全对我无用,因为我的
编译器用其它的方式来实现 bit fields 的。
从道德讲:永远不要让一个非嵌入式的家伙粘
实际硬件的边。
(3) 用 #defines 和 bit masks 操作。这
是一个有极高可移植性的方法,是应该被用到
的方法。最佳的解决方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void) 
{
a |= BIT3;
}
void clear_bit3(void) 
{
a &= ~BIT3;
}
一些人喜欢为设置和清除值而定义一个掩码
同时定义一些说明常数,这也是可以接受的。
我希望看到几个要点:说明常数、|=和&=~操
作。 
访问固定的内存位置(Accessing fixed 
memory locations) 
10. 嵌入式系统经常具有要求程序员去访问
某特定的内存位置的特点。在某工程中,要求
设置一绝对地址为 0x67a9 的整型变量的值为
0xaa66。编译器是一个纯粹的 ANSI 编译器。
写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地
址把一个整型数强制转换(typecast)为一指
针是合法的。这一问题的实现方式随着个人风
格不同而不同。典型的类似代码如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is: 
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二种方案,但我建议你
在面试时使用第一种方案。
108.中断(Interrupts) 
11. 中断是嵌入式系统中重要的组成部分,这
导致了很多编译开发商提供一种扩展—让标
准 C 支持中断。具代表事实是,产生了一个新
的关键字 __interrupt。下面的代码就使用了
__interrupt 关键字去定义了一个中断服务子
程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double 
radius) 
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
这个函数有太多的错误了,以至让人不知从何
说起了:
(1)ISR 不能返回一个值。如果你不懂这个,
那么你不会被雇用的。
(2) ISR 不能传递参数。如果你没有看到这
一点,你被雇用的机会等同第一项。
(3) 在许多的处理器/编译器中,浮点一般
都是不可重入的。有些处理器/编译器需要让
额处的寄存器入栈,有些处理器/编译器就是
不允许在 ISR 中做浮点运算。此外,ISR 应该
是短而有效率的,在 ISR 中做浮点运算是不明
智的。
(4) 与第三点一脉相承,printf()经常有重
入和性能上的问题。如果你丢掉了第三和第四
点,我不会太为难你的。不用说,如果你能得
到后两点,那么你的被雇用前景越来越光明了。
109.代码例子(Code examples) 
12 . 下面的代码输出是什么,为什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得 C 语言中的整数自动
转换原则,我发现有些开发者懂得极少这些东
西。不管如何,这无符号整型问题的答案是输
出是 ">6"。原因是当表达式中存在有符号类
型和无符号类型时所有的操作数都自动转换
为无符号类型。因此-20 变成了一个非常大的
正整数,所以该表达式计算出的结果大于 6。
这一点对于应当频繁用到无符号数据类型的
嵌入式系统来说是丰常重要的。如果你答错了
这个问题,你也就到了得不到这份工作的边缘。
13. 评价下面的代码片断:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF; 
/*1's complement of zero */
对于一个 int 型不是 16 位的处理器为说,上
面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
这一问题真正能揭露出应试者是否懂得处理
器字长的重要性。在我的经验里,好的嵌入式
程序员非常准确地明白硬件的细节和它的局
限,然而 PC 机程序往往把硬件作为一个无法
避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或
者信心满满志在必得。如果显然应试者不是很
好,那么这个测试就在这里结束了。但如果显
然应试者做得不错,那么我就扔出下面的追加
问题,这些问题是比较难的,我想仅仅非常优
秀的应试者能做得不错。提出这些问题,我希
望更多看到应试者应付问题的方法,而不是答
案。不管如何,你就当是这个娱乐吧...
110.动态内存分配(Dynamic memory 
allocation) 
14. 尽管不像非嵌入式计算机那么常见,嵌入
式系统还是有从堆(heap)中动态分配内存的
过程的。那么嵌入式系统中,动态分配内存可
能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收
集的问题,变量的持行时间等等。这个主题已
经在 ESP 杂志中被广泛地讨论过了(主要是
P.J. Plauger, 他的解释远远超过我这里能提
到的任何解释),所有回过头看一下这些杂志
吧!让应试者进入一种虚假的安全感觉后,我
拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL) 
puts("Got a null pointer");
else
puts("Got a valid pointer");
这是一个有趣的问题。最近在我的一个同事不
经意把 0 值传给了函数 malloc,得到了一个合
法的指针之后,我才想到这个问题。这就是上
面的代码,该代码的输出是"Got a valid 
pointer"。我用这个来开始讨论这样的一问题,
看看被面试者是否想到库例程这样做是正确。
得到正确的答案固然重要,但解决问题的方法
和你做决定的基本原理更重要些。
Typedef 
15 Typedef 在 C 语言中频繁用以声明一个已
经存在的数据类型的同义字。也可以用预处理
器做类似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上两种情况的意图都是要定义 dPS 和 tPS 
作为一个指向结构 s 指针。哪种方法更好呢?
(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问
题(正当的原因)是应当被恭喜的。答案是:
typedef 更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一个扩展为
struct s * p1, p2;
.
上面的代码定义 p1 为一个指向结构的指,p2
为一个实际的结构,这也许不是你想要的。第
二个例子正确地定义了 p3 和 p4 两个指针。
111.晦涩的语法 
16 . C 语言同意一些令人震惊的结构,下面的
结构是合法的吗,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。
不管你相不相信,上面的例子是完全合乎语法
的。问题是编译器如何处理它?水平不高的编
译作者实际上会争论这个问题,根据最处理原
则,编译器应当能处理尽可能所有合法的用法。
因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后 a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。
如果你不知道答案,我也不把这个当作问题。
我发现这个问题的最大好处是这是一个关于
代码编写风格,代码的可读性,代码的可修改
性的好的话题。
嵌入式软件开发工程师笔试面试题总结 
1.关键字 volatile 有什么含义?给出三个不
同的例子
关键字 volatile 是防止变量被编译器优化。
被 volatile 修饰的变量,编译器不会去假设
该变量的值,当优化器每次用到该变量的值时,
都会去变量的原始地址去读取这个变量的值,
而不是使用保存在寄存器中的备份值。 
例子:(1)并行设备的硬件寄存器。
(2)一个中断服务子程序中的非自动
变量。
(3)多线程应用中被几个线程任务共
享的变量。
2.一个参数既可以是const还可以是volatile
吗?解释为什么?
答:可以。一个例子是只读的状态寄存器。它
是 volatile 因为它可能被意想不到地改变。
它是 const 因为程序不应该试图去修改它。
3.一个指针可以是 volatile 吗?解释为什
么?
答:可以。尽管这并不常见。一个例子是当一
个中断服务子程序修改一个指向 buffer 的指
针时。
4.关键字 static 的作用是什么?
答:在 C 语言中:
(1)修饰全局变量时,该全局变量只能 在本
文件内使用。
(2)修饰局部变量时,该变量生命周期延长
到程序结束。如果该局部变量没有被初始化,
其值默认为 0,若已被初始化,则只能初始化
一次。
(3)修饰函数时,该函数只能在本文件中使
用。
在 C++中:
(1)被 static 修饰的成员变量在本质上是全
局变量,所以需要在类的外部进行定义。
(2)被 static 修饰的成员函数没有 this 指
针,可以通过类名::函数名进行调用
5.static 全局变量与普通的全局变量有什么
区别?static 局部变量和普通局部变量有什
么区别?static 函数与普通函数有什么区
别?
static 全局变量与普通的全局变量有什么
区别:static 修饰的全局变量只初使化一次,
且不能在其他文件单元中被引用;
static 局部变量和普通局部变量有什么区
别:static 修饰的局部变量生命周期从程序开
始到程序结束,且只被初始化一次,下一次依
据上一次结果值;
static 函数与普通函数有什么区别:
static 函数在内存中只有一份,普通函数在每
个被调用中维持一份拷贝。
6.关键字 const 有什么含意?
(1)可以定义 const 常量
(2)const 可以修饰函数的参数、返回值,
甚至函数的定义体。被 const 修饰的东西都受
到强制保护,可以预防意外的变动,能提高程
序的健壮性。
7.定义一个返回值是指向函数的指针,且有一
个指向函数的指针做参数的函数。
typedef int(*P)();
P fun( int (*p)() );
8.找出下面一段 ISR 的问题。
_interrupt double cpmpute_area(double 
radius)
{
Double area = PI * radius * radius;
Printf(“\nArea = %f”,area);
Return area;
}
答:(1)ISR 不能有参数。
(2)ISR 不能有返回值。
(3)ISR 应该短且有效率,在 ISR 中做浮点
运算不明智。
9.评价下面的代码片断:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
对于一个 int 型不是 16 位的处理器为说,上
面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
10.typedef 与#define 的区别。
#define 在预编译的时候做简单的字符替换处
理。
typedef 是在编译的时候进行的处理,并不是
做简单的字符替换,而是同定义一个变量一样
声明一个数据类型,然后用它去定义这种数据
类型的变量。
11.Union 与 struct 的区别。(故而常用 struct)
1.在存储多个成员信息时,编译器会自动给
struct 每个成员分配存储空间,struct 可以
存储多个成员信息,而 Union 每个成员会用同
一个存储空间,只能存储最后一个成员的信息。
2.都是由多个不同的数据类型成员组成,但在
任何同一时刻,Union 只存放了一个被先选中
的成员,而结构体的所有成员都存在。
3.对于 Union 的不同成员赋值,将会对其他成
员重写,原来成员的值就不存在了,而对于
struct 的不同成员赋值 是互不影响的。
12.引用和指针有什么区别?
1、引用必须初始化,指针不必;
2、引用处画化后不能改变,指针可以被改变;
3、不存在指向空值的引用,但存在指向空值
的指针;
13.请说出 const 与#define 相比,有何优点?
(故而常用 const)
答:1、const 定义的是只读变量,#define 为
宏替换
2、const 不会改变变量的存储位置,
#define 定义的宏存储在代码段
3、const 常量有数据类型,而宏常量没
有数据类型。
4、编译器可以对前者进行类型安全检查。
而对后者只进行字符替换,没有类型安全检查,
并且在字符替换可能会产生意料不到的错误。
14.堆与栈的区别。
Heap 是堆,Stack 是栈。
(1)栈的空间由操作系统自动分配和回收,而
堆上的空间由程序员申请和释放。
(2)栈的空间大小较小,而堆的空间较大。
(3)栈的地址空间往低地址方向生长,而堆向
高地址方向生长。
(4)栈的存取效率更高。程序在编译期间对变
量和函数的内存分配都在栈上,
且程序运行过程中对函数调用中参数的内存
分配也是在栈上。
15.Linux 环境下段错误出现的原因及调试方

出现原因:(1)访问不存在的内存地址。
(2)访问系统保护的内存地址。
(3)访问只读的内存地址。
(4)栈溢出。
段错误调试方法:(1)使用 printf 在关键处打
印输出信息。
(2)使用 gcc 与 gdb,编译时使
用 gcc -g 命令,然后使用 gdb 进
行调试。
(3)使用 core 和 gdb 进行调试
(4)使用 objdump 进行调试。
(5)使用 catchsegv
16.OSI 七层模型与 TCP/IP 四层模型?
答:OSI 七层模型:物理层、数据链路层、
网络层、传输层、会话层、表示层、应用层。
TCP/IP 四层模型:网络接口层(物理层、
数据链路层)、网际层、传输层、表示层 
(会话层、表示层、应用层)。
17.OSI 七层的对应各网络协议?
答:物理层:IEEE 802.3(以太网协议)、
RJ45
数据链路层:HDLC 、VLAN 、MAC (网
桥,交换机)、ARP(属于 TCP/IP 协议族)(在
TCP/IP 四层中属于网络层)
网络层: IP 、ICMP(互联网控制信息
协议)、 (ARP 、 RARP) 工作内容在数据
链路层
传输层: TCP、UDP
会话层: NFS(网络文件系统协议) 、
SQL、RPC(远程调用协议)
表示层: JPEG、MPEG
应用层:FTP (文本传输协议)、DNS 、
Telnet(远程登录协议) 、 SMTP(简单邮件
传输协议) 、 HTTP(超远文本传输协议) 、
NFS
18.网络四类地址的区间?
网络地址分为网络位和主机位。
A 类地址:1.0.0.0 ---
127.255.255.255(127.0.0.1 为回环地
址)(ping 通本地回环地址说明本机协议没问
题)
B 类地址:128.0.0.0 --- 191.255.255.255
C 类地址:192.0.0.0 --- 223.255.255.255
D 类地址:224.0.0.0 ---
239.255.255.255(广播地址)
19.简述 TCP/UDP 服务器端创建流程与客户端
创建流程。
TCP 服务器端创建流程:创建通信用文件
描述符
(socket)-->设
置端口号和 IP
地址(为绑定做
准备)-->绑定
(bind)-->监听
(listen)-->接
受请求,建立连
接(accept)-->
发送与接收消息
(send/recv)-->
关闭文件
(close)
TCP 客户端创建流程:创建通信用文件描
述符(socket)-->
设置端口号和 IP
地址-->发起连
接请求
(connect)-->接
受与发送消息
(send/recv)-->
关闭文件
(close)
UDP 服务器端创建流程:创建通信用文件
描述符(socket)-->设置端口号和 IP 地址(为
绑定 做准备)-->绑
定(bind)-->接受和发送消息(sendto && 
recvfrom)
-->关闭文件(close)
UDP 客户端创建流程:创建通信用文件描
述符(socket)-->设置端口号和 IP 地址-->接
受与 发送消息
(sendto && recvfrom)-->关闭文件
20.简述三次握手与四次挥手。
在 TCP/IP 协议中,TCP 协议提供可靠的连
接服务,采用三次握手建立一个连接。
三次握手的过程:
第一次握手:建立连接时,客户端发送 SYN(SYN 
= j)包到服务器,并进入 SYN_SEND 状态,等
待服务器的确认;
第二次握手:服务器收到 SYN 包,必须确认客
户的 SYN(ACK = j+1),同时自己也发送一个
SYN 包(SYN=k),即 SYN+ACK 包,此时服
务器进入 SYN_RECV 状态;
第三次握手:客户端收到服务器的 SYN+ACK 包,
向服务器发送确认包 ACK(ACK=k+1),此包发
送完毕,客户端和服务器进入
ESTABLISHED 状态,完成三次握手。
四次挥手的过程(客户端或服务器均可主
动发起挥手动作):
(1)客户端 A 发送一个 FIN,用来关闭客户
A 到服务器 B 的数据传送。
(2)服务器 B 收到这个 FIN,它发回一个
ACK,确认序号为收到的序号加 1。
(和 SYN 一样,一个 FIN 将占用一个序
号)。
(3)服务器 B 关闭与客户端 A 的连接,发
送一个 FIN 给客户端 A。
(4)客户端 A 发回 ACK 报文确认,并将确
认序号设置为收到序号加 1.
21.TCP 与 UDP 的区别?
(1)TCP 是面向连接的协议,UDP 是面向无
连接的协议。
(2)TCP 对系统资源要求较多,UDP 对系统
资源要求较少。
(3)TCP 是数据流模式,UDP 是数据报模式。
(4)TCP 保证数据顺序及数据的正确性,UDP
可能会丢包。
22.如果内存已经泄露,该如何检测?
(1)匹配 malloc 和 free 在个数上是否匹配。
(grep -r “malloc”*| wc -l;grep -r “free”
* |wc -l)
(2)使用 Visual Leak Detector 检测工具。
23.TCP 与 UDP 报头各占几个字节?
TCP 报头由 10 个必须字段(源端口号 16 位、
目标端口号 16 位、序列号 32 位、确认号 32
位、报文长度 4 位、保留位与控制位各 6 位、
窗口位 16 位、校验和 16 位、紧急指针 16 位)
和一个可选字段,至少 20 个字节构成。
UDP 由(源端口号、目标端口号、数据报长
度、校验值)四部分组成,每个域各占两个字
节,故 UDP 报头为 8 个字节。
24.定义宏时需要注意什么?
(1) 在定义#define 命令时,注意<宏名>和
<字符串>之间用空格分开,而不是用等号连接。
(2) 使用#define 定义的标识符不是变量,
它只用作宏替换,因此不占有内存。
(3) 习惯上用大写字母表示<宏名>。 
(4) 定义宏函数时不能有数据类型。
25.面向对象的三大特征
面向对象的三大特征是封装性、继承性和多态
性:
 封装性:将客观事物抽象成类,每个类对
自身的数据和方法实行(protection、private、
protected、public)。
 继承性:广义的继承有三种实现形式:实
现继承(使用基类的属性和方法而无需额外编
码的能力)、可视继承(子窗体使用父窗体的外
观和实现代码)、接口继承(仅使用属性和方法,
实现滞后到子类实现)。
 多态性:是将父类对象设置成为和一个或
更多它的子对象相等的技术。用子类对象给父
类对象赋值之后,父类对象就可以根据当前赋
值给它的子对象的特性以不同的方式运作。
 说明:面向对象的三个特征是实现面向对
象技术的关键,每一个特征的相关技术都非常
的复杂,程序员应该多看、多练。
26.如何创建守护进程?
只要保证进程与控制终端完全脱离,此进程
就会成为一个守护进程 
1、创建子进程,父进程退出 ---- 形式上做
到与终端无关
2、在子进程中创建新会话 --- 与终端完全脱
离,使该子进程成为新会话组的组长
3、更改当前工作目录为"/"或“/tmp”---- 增
强可移植性,防止其工作目录被删掉
4、重设文件权限掩码 ---增强守护进程创建
文件的灵活性
5、关闭文件描述符 --- 关闭父进程打开的
文件描述符
27.嵌入式系统经常具有要求程序员去访问某
特定的内存位置的特点。在某工程中,要求设
置一绝对地址为 0x67a9 的整型变量的值为
0xaa66。编译器是一个纯粹的 ANSI C 编译器。
写代码去完成这一任务。
答:int *ptr;
ptr = (int *)(0x67a9);
*ptr = 0xaa66;
28.请问以下代码有什么问题:
int main()
{
char a;
char *str=&a;
strcpy(str,"hello");
printf(str);
return 0;
}
答案:没有为 str 分配内存空间,将会发生异
常问题出在将一个字符串复制进一个字符变
量指针所指地址。虽然可以正确输出结果,但
因为越界进行内在读写而导致程序崩溃。
29.char* s="AAA"; printf("%s",s); 
s[0]='B'; printf("%s",s); 有什么错?
答案:"AAA"是字符串常量。s 是指针,指向这
个字符串常量,所以声明 s 的时候就有问题。
cosnt char* s="AAA"; 然后又因为是常量,
所以对是 s[0]的赋值操作是不合法的。
30.进程与线程的区别
线程与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进
程作为资源拥有的资本单位。
(2)并发性:不仅进程之间可以并发执行,同一
进程的多个线程之间也可以并发的执行。
(3)拥有资源:进程是拥有资源的独立单位,
线程不拥有系统资源,但可以访问隶属于进程
的资源.
(4)系统开销:在创建或撤消进程时,由于系
统都要为之分配和回收资源,导致系统的开销
明显大于创建或撤消线程时的开销
31.参数传递有几种方式;
解析:传值,传指针或者引用
32.操作系统中进程调度策略有哪几种?
解析:这个是操作系统的知识,FCFS(先来先服
务),优先级,时间片轮转,多级反馈
33.发生死锁的必然条件
互斥条件、请求与保持、不可剥夺、循环
等待。
34.C++语言中使用“extern C”有什么作用。
告诉编译器这段代码是用 C 语言编写的,因为
C 语言不支持函数重载,而 C++支持函数重载,
编译后的函数名不同。目的是实现C语言与C++
语言的混合编程。 
35.new delete 与 malloc free 的区别。
(1) malloc free 是库函数,需要加载头
文件,new delete 是运算符,不需要加载头文
件。
(2)释放数组内存的时候,free(数组首地
址),delete[]数组首地址。
(3)new 申请内存的时候已经确定了数据类
型,会有类型检查;malloc 申请内存的时候并
没有数据类型的检查,需要强制类型的转化。
(4)malloc 申请内存的入参是申请内存的
字节数,new 只需要指明数据类型和元素个数。
(5)new 一个对象的时候会自动调用该对象
的构造函数,delete 一个指针的时候,会自动
调用该对象的析构函数.而 malloc free 没有
这个功能。
36.想让程序跳转到绝对地址 0x100000 处执行,
该如何做?
(*(void(*)(void) )0x100000)();
( ( void(*)(void) ) 0x100000)();
37.内存泄露和内存溢出的区别
内存溢出就是要求分配的内存大小超出了定
义时所预设的内存大小,系统不能满足需求,
于是产生溢出。
内存泄漏是指向系统申请分配内存进行使用
(new),可是使用完了以后却不归还(delete),
结果你申请到的那块内存你自己也不能再访
问(也许你把它的地址给弄丢了),而系统也
不能再次将它分配给需要的程序。
124 道嵌入式工程师面试题以及答案总结 
一、单项选择题
1、如下哪一个命令可以帮助你知道 shell 命
令的用法( A )
A. 
man B.pwd
C. 
help D. more
2、Linux 分区类型默认的是:( B )
A. vfat B. 
ext2/ext3 C. 
swap D. dos
3、在大多数 Linux 发行版本中,以下哪个属
于块设备 ( B )
A. 串行口 B. 硬
盘 C. 虚拟终端 D. 
打印机
4、下面哪个命令行可用来马上重新启动正在
运行的 Linux 系统?( D )
A. restart 
--delay=0 B. reboot -w
C. halt -p D.
shutdown -r now
5、在 Linux 系统,默认的 shell 是什么
( A )
A.bash B.ash C.csh
D.gnush
6、下面哪条命令可用来确保文件“myfile”
存在( B )
A. cp myfile /dev/null B. 
touch myfile
C. create myfile D. 
mkfile myfile
7、 LILO 的配置文件是:( B )
A. 
/etc/conf B. 
/etc/lilo.conf 
C. /proc/kcore D. 
/usr/local/
8、用“useradd jerry”命令添加一个用户,
这个用户的主目录是什么( A )
A./home/jerry B
./bin/jerry
C./var/jerry
D./etc/jerry
9、Linux 文件权限一共 10 位长度,分成四段,
第三段表示的内容是( D )
A.文件类
型 B.文件
所有者的权限
C.文件所有者所在组的权限 D.其他
用户的权限
10、某文件的组外成员的权限为只读;所有者
有全部权限;组内的权限为读与写,则该文件
的权限为( D )
A.467 B.674
C.476 D
.764
11、不是 shell 具有的功能和特点的是( A )
A.管

B.输入输出重定

C.执行后台进
程 D.
处理程序命令
12、如何从当前系统中卸载一个已装载的文件
系统( A )
A. 
umount
B. dismount
C. mount –
u D. 从
/etc/fstab 中删除这个文件系统项
13、你用 vi 编辑器编写了一个脚本文件
shell.sh,你想将改文件名称修改为
shell2.sh,下列命令( B )可以实现。
A. cp shell.sh shell2.sh B. 
mv shell.shshell2.sh 
C. ls shell.sh >shell2.sh D. ll 
shell.sh>shell2.sh
14、在/home/stud1/wang 目录下有一文件 file,
使用 ( D )可实现在后台执行命令,此命令
将 file 文件中的内容输出到 file.copy 文件
中。
A. cat 
file >file.copy B
. cat file file.copy
C. &cat 
file file.copy D.
&cat file >file.copy 
15、字符设备文件类型的标志是 ( B )
A. p B. c C. 
s D. l
16、删除文件命令为( D )
A. mkdir B. 
rmdir C.mv
D. rm
17、( B )命令可更改一个文件的权限设置?
A. attrib B. 
chmod C. 
change D. file
18、用命令 ls -al 显示出文件 ff 的描述如下
所示,由此可知文件 ff 的类型为( A )。
-rwxr-xr-- 1 root root 599 Cec 10 17:12 ff
A. 普通文件 B. 硬链
接 C. 目
录 D. 符号链接
19、系统中有用户 user1 和 user2,同属于
users 组。在 user1 用户目录下有一文件 file1,
它拥有 644 的权限,如果 user2 用户想修改
user1用户目录下的file1文件,应拥有( B )
权限。
A. 744 B. 
664 C. 
646 D. 746
20、在指令系统的各种寻址方式中,获取操作
数最快的方式是( 1 -B );若操作数的
地址包含在指令中,则属于( 2-A )
方式。
(1) A、直接寻址 B、立即
寻址 C、寄存器寻
址 D、间接寻

(2) A、直接寻址 B、立即
寻址 C、寄存器寻
址 D、间接寻

21、在 CPU 和物理内存之间进行地址转换时,
( B )将地址从虚拟(逻辑)地址空间映
射到物理地址空间。
A、TCB B、MMU C、
CACHE D、DMA
22、Linux 将存储设备和输入/输出设备均看做
文件来操作,(C)不是以文件的形式出现。
A. 目

B. 软链接
C. i 节点

D. 网络适配器
23、关于文件系统的安装和卸载,下面描述正
确的是(A)。
A. 如果光盘未经卸载,光驱是打不开的
B. 安装文件系统的安装点只能是/mnt 下
C. 不管光驱中是否有光盘,系统都可以安装
CD-ROM 设备
D. mount /dev/fd0 /floppy 此命令中目录
/floppy 是自动生成的
24、为了查看 Linux 启动信息,可以用(B)
命令
A. cat 
/etc/lilo.conf
B.dmesg
C. 
cat/proc/cpuinfo
D.lilo
25、用下列(A)命令查看 Linux 使用了多少
内存
A.cat 
/proc/meminfo
B. cat /bin/meminfo 
C.vi 
/proc/meminfo
D.vi /user/local/meminfo
26、下列(D)设备是字符设备。
A. hdc B. 
fd0 C. 
hda1 D. tty1
27、下列说法正确的是(D)
A. ln -s a.txt b.txt,作用是制
作文件 b.txt 的符号链接,其名称为 a.txt
B. df 命令可以查看当前目录占用磁盘空间的
大小
C. comm 命令打印两个文本文件中的相同的内

D. rm 命令可以用来删除目录
28、有如下的命令:$dd if=f1 of=f2。其中
if=f1 表示(A)
A. 以 f1 作为源文件,代替标准输入
B. 以 f1 作为目标文件,代替标准输出
C. 当条件满足 f1 的时候,执行真正的拷贝
D. 拷贝的过程中,不转化文件
29、为了查找出当前用户运行的所有进程的信
息,我们可以使用(B)命令:
A. ps -a B. ps -u C. ls 
-a D. ls –l
30、为保证在启动服务器时自动启动 DHCP 进
程,应对( B )文件进行编辑。
A、
/etc/rc.d/rc.inet2
B、/etc/rc.d/rc.inet1
C、
/etc/dhcpd.conf
D、/etc/rc.d/rc.S
31、( D )设备是字符设备。
A、hdc B、fd0 C、
hda1 D、tty1
32、文件 exer1 的访问权限为 rw-r--r--,现
要增加所有用户的执行权限和同组用户的写
权限,下列命令正确的是( A )。
A 、chmod a+x g+w exer1 B 、
chmod 765 exer1 
C 、chmod o+x 
exer1 D 、chmod 
g+w exer1
33、删除当前目录 abc 以及下面的所有子目录
和文件,并不要求提示任何确认信息的命令是
(B)
A. del abc*.* B. rm –rf 
abc C. rmdirabc D. rm –r 
abc *.* 
34、如果忘记了 ls 命令的用法,可以采用( C )
命令获得帮助
a. ?ls b.help ls c.man 
ls d.get ls
35、在安装开始前,用光盘启动系统,想要进
入字符界面安装,需要输入的命令是( C )
a.linux doc b.linux c.linux 
text d.linux note
36、要给文件 file1 加上其他人可执行属性的
命令是( C )
a.chmod a+x b.chown a+x c.chmod 
o+x d.chown o+x
37、怎样新建一个新文件:( A )
a.touch hello.c b.mk 
hello.c c.rm 
hello.c d.new hello.c
38、在 bash 命令中,当用( B )参数时,表
示 bash 是交互的。
A、-c B、-i C、-
s D、-d
39、重定向的符号“>"表示:( C )
A、输出追加 B、输入追加 C、输出
重定向,原来的文件会被改写 D、管道
40、linux 系统能够直接读取的分区类型是
( D )
a.ntfs b.fat16 c.fat3
2 d.ext3
41、下列提法中,属于 ifconfig 命令作用范
围的是( B )。
A、编译源程
序 B、
配置网卡的 IP 地址
C、配置系统内
核 D、加载
网卡到内核中
42、下列对 shell 变量 FRUIT 操作,正确的是
( C )
A、为变量赋值:$FRUIT=apple B、
显示变量的值:fruit=apple 
C、显示变量的值:echo $FRUIT D、
判断变量是否有值:[ -f “$FRUIT” ]
43、一般可以用( C )实现自动编译。
A、gcc B、gdb * C、
make D、 vi
44、处理机主要由处理器、存储器和总线组成,
总线包括( D )。
A、数据总线、串行总线、逻辑总线、物理总
线
B、并行总线、地址总线、逻辑总线、物理总
线
C、并行总线、串行总线、全双工总线
D、数据总线、地址总线、控制总线
45、假设当前目录下有文件 Makefile,下面是
其内容:
pr1: prog.o subr.o
gcc –o pr1 prog.o subr.o
prog.o: prog.c prog.h
gcc –c –l prog.o prog.c
subr.o: subr.c
gcc –c –o subr.o subr.c
clear:
rm –f pr1*.o
现在执行命令 make clear,实际执行的命令是
( A ):
A. rm –f pr1*.o 
B. gcc –c –l prog.o prog.c 
C. gcc –c –o subr.o subr.c 
D. 都执行
46、Linux 将存储设备和输入/输出设备均看做
文件来操作,下列选项(C)不是以文件的形
式出现。
A. 目

B. 软链接
C. i 节点

D. 网络适配器
47、有如下的命令:$dd if=f1 of=f2。其中
if=f1 表示( A )
A. 以 f1 作为源文件,代替标准输入
B. 以 f1 作为目标文件,代替标准输出
C. 当条件满足 f1 的时候,执行真正的拷贝
D. 拷贝的过程中,不转化文件
48. 文件之间可以建立两种链接关系:软链接
和硬链接,硬链接的特点是(C)
A. 等同于文件复制操作
B. 类似于文件复制,但新的链接文件并不占
用文件磁盘存储空间
C. 删除源文件,将使其他链接文件失效
D. 可以对目录文件名建立硬链接
49. 下面哪一个选项不是 linux 系统的进程类
型(D)
A. 交互进程
B. 批处理进程
C. 守护进程
D. 就绪进程
50、下面( B )特性不符合嵌入式操
作系统特点。
A、实时性 B、不可
定制
C、微型化 D、易
移植
51、下面关于 C 语言程序的描述,正确的是
( C )。
A、总是从第一个定义的函数开始执

B、要调用的函数必须在 main()函数中定

C、总是从 main()函数开始执行
D、main()函数必须放在程序的开始
52、在 FTP 协议中,控制连接是由( B )
主动建立的。
A、服务器端 B、客户

C、操作系统 D、服务提
供商
53、以下叙述中,不符合 RISC 指令系统特点
的( B )。
A、指令长度固定,指令种类

B、寻址方式种类丰富,指令功能尽量增

C、设置大量通用寄存器,访问存储器指令简

D、选取使用频率较高的一些简单指令
54、当我们与某远程网络连接不上时,就需要
跟踪路由查看,以便了解在网络的什么位置出
现了问题,满足该目的的命令是( C )。
A、
ping
B、ifconfig
C、
traceroute
D、netstat
55. 下列哪种文件系统的写入是 LINUX 所不能
完全支持的:D
A. FAT B. UFS C. 
JFS D. NTFS
56. LINUX 支持网络文件系统 NFS,下列哪个命
令实现了将位于 192.168.1.4 机器上的
/opt/sirnfs 目录挂载到本机/mnt/sirnfs 下:
A
A.mount -t nfs 
192.168.1.4:/opt/sirnfs/mnt/sirnfs
B.mount -t nfs 
/mnt/sirnfs192.168.1.4:/opt/sirnfs
C.mount nfs –t 
192.168.1.4:/opt/sirnfs/mnt/sirnfs
D.mount nfs –t 
/mnt/sirnfs192.168.1.4:/opt/sirnfs
57、同 CISC 相比,下面哪一项不属于 RISC 处
理器的特征_ D
A、采用固定长度的指令格式,指令规整、简
单、基本寻址方式有 2~3 种。
B、减少指令数和寻址方式,使控制部件简化,
加快执行速度。
C、数据处理指令只对寄存器进行操作,只有
加载/存储指令可以访问存储器,以提高指令
的执行效率,同时简化处理器的设计。
D、RISC 处理器都采用哈佛结构
58、在下列 ARM 处理器的各种模式中,__D___
模式有自己独立的 R8-R14 寄存器。
A、系统模式(System)、
B、终止模式(Abort)
C、中断模式(IRQ)
D、快中断模式(FIQ)
59、按照 ARM 过程调用标准(APCS),栈指针
使用_B___寄存器,
A、R0 B、R13 C、
R14 D、R15
60、在 ARM 体系结构中,_C___寄存器作为连
接寄存器,当进入子程序时或者处理器响应异
常的时候,用来保存 PC 的返回值;_C___寄存
器作为处理器的程序计数器指针。
A、R0,R14 B、R13,R15 C、R14,
R15 D、R14,R0
61、在 ARM 体系结构中,要从主动用户模式
(User)切换到超级用户模式(Supervisor),
应采用何种方法?C
A、直接修改 CPU 状态寄存器(CPSR)对应的
模式
B、先修改程序状态备份寄存器(SPSR)到对
应的模式,再更新 CPU 状态
C、使用软件中断指令(SWI)
D、让处理器执行未定义指令
62、下面关于 MMU 和 Linux 描述错误的是:C
A、MMU 是内存管理单元 Memory Management 
Unit 的缩写
B、uClinux 可以运行在有 MMU 的处理器上
C、Linux 内核功能强大,内存管理功能丰富,
即使在没有 MMU 的处理器上,也可以通过软件
实现地址映射。
D、Linux 系统正是利用 MMU,才能使得各个进
程有独立的寻址空间
63、DNS 域名系统主要负责主机名和
( A )之间的解析。
A、IP 地址 B、
MAC 地址
C、网络地
址 D、主机别

64、在 vi 编辑器中的命令模式下,重复上一
次对编辑的文本进行的操作,可使用
( C )命令。
A、上箭头 B、下箭头 C、
<.> D、<*>
65、进程有三种状态:( C )。
A 、准备态、执行态和退出
态 B 、精确态、模糊态
和随机态
C 、运行态、就绪态和等待
态 D 、手工态、自动态
和自由态
66、下列变量名中有效的 shell 变量名是
( C )。
A、-1-time B、
_2$3
C、bo_chuang_1 D、
2009file
67、文件系统的主要功能是( A )。
A、实现对文件的按名存
取 B、
实现虚拟存储
C、 提高外存的读写速
度 D、
用于保存系统文档
68、在 ARM Linux 体系中,用来处理外设中断
的异常模式是__C____
A、软件中断(SWI) B、
未定义的指令异常
C、中断请求(IRQ) D、
快速中断请求(FIQ)
69、在 Linux 系统中,驱动程序注册中断处理
程序的函数是_B____
A、
trap_init
B、request_irq
C、
enable_irq
D、register_irq
70、在 ARM Linux 系统中,中断处理程序进入
C 代码以后,ARM 的处于__A__工作模式
A、超级用户(SVC) B、
中断(IRQ)
C、快速中断(IRQ) D、
和进入中断之前的状态有关系
71、在 ARM 体系构建的嵌入式系统中,由电平
模式触发的中断,其对应的中断标准应该在何
时被清除?A
A、当中断处理程序结束以后,才可以清除
B、进入相应的中断处理程序,即可以清除
C、产生 IRQ 中断的时候,处理器自动清除
D、任何时候都可以清除
72、在操作系统中,Spooling 技术是用一类物
理设备模拟另一类物理设备的技术,实现这种
技术的功能模块称做( B )。
A、可林斯系统 B、斯普林系统
C、图灵机系统 D、 虚拟存储系统
73、通过修改下面文件哪个文件 ,可以设定
开机时候自动安装的文件系统(C )
A. 
/etc/mta
B. /etc/fastboot 
C. 
/etc/fstab
D. /etc/inetd.conf
74、下面关于 Shell 的说法,不正确的是:(D)
A. 操作系统的外壳
B. 用户与 Linux 内核之间的接口程序
C. 一个命令语言解释器
D. 一种和 C 类似的程序语言
75、init 可执行文件通常存放在( C )目录
中。
A./etc
B./boot
C./sbin
D./root
76、假设 root 用户执行“init 0”命令,系
统将会( B )。
A.暂停 B.关
机 C.重新启
动 D.初始化
77、嵌入式系统应用软件一般在宿主机上开发,
在目标机上运行,因此需要一个
( B )环境。
A、交互操作系统 B、
交叉编译
C、交互平
台 D、
分布式计算
78、已知有变量 data1 定义如下:C
union data
{ int i;
char ch;
float f;
} data1; 
则变量 data1 所占的内存存储空间可表示为。
A、
sizeof(int)
B、sizeof(char)
C、
sizeof(float)
D、
sizeof(int)+sizeof(char)+sizeof(float)
79、软件开发模型给出了软件开发活动各阶段
之间的关系,( D )不是软件开发模
型。
A、瀑布模型 B、螺旋模

C、原型模型 D、程序模

80、实时操作系统(RTOS)内核与应用程序之
间的接口称为( C )。
A、输入/输出接口 B、文件系

C、API D、图
形用户接口
81、在操作系统中,除赋初值外,对信号量仅
能操作的两种原语是( C )。
A、存操作、取操作 B、读操
作、写操作
C、P 操作、V 操作 D、输入
操作、输出操作
82、在下列 ARM 处理器的各种模式中,只有
__A___模式不可以自由地改变处理器的工作
模式。
A、用户模式(User) B、系统模式(System)
C、终止模式(Abort) D、中断模式
(IRQ)
83、32 位体系结构的 ARM 处理器有_B___种不
同的处理器工作模式,和__B__个主要用来标
识 CPU 的工作状态和程序的运行状态的状态寄
存器。
A、7、7 B、7、6 C、
6、6 D、6、7
84、已知 Linux 系统中的唯一一块硬盘是第一
个 IDE 接口的 master 设备,该硬盘按顺序有 3
个主分区和一个扩展分区,这个扩展分区又划
分了 3 个逻辑分区,则该硬盘上的第二个逻辑
分区在 Linux 中的设备名称是( D )
A. /dev/hda2 B. /dev/hda3 
C. /dev/hda5 D. /dev/hda6
85、为了查看 Linux 启动信息,可以用:( B )
A、cat /etc/lilo.conf B、
dmesg C、
cat/proc/cpuinfo D、lilo
86、某文件的组外成员的权限为只写;所有者
有读写权限;组内的权限为只读,则该文件的
权限为( B )
A 467 B 
642 C 
476 D 764
87、下面哪个命令行可用来马上重新启动正在
运行的 Linux 系统?( D )
A. restart 
--delay=0 B. reboot -w
C. halt -p D.
shutdown -r now 
88、在 bash 命令中,当用( B )参数时,表
示 bash 是交互的。
A、-c B、-i C、-
s D、-d
89、重定向的符号“>>"表示:( A )
A、输出追加 B、输入追加 C、输出
重定向,原来的文件被改写 D、管道
90、Linux文件权限一共10位长度,分成四段,
第一段表示的内容是( A )
A 文件类型 B 文件所有者的权限
C 文件所有者所在组的权限 D 其他用户的权

91、( B )命令可更改一个文件的权限设置?
A. attrib B. 
chmod C. 
change D. file
92、你用 vi 编辑器编写了一个脚本文件
shell.sh,你想将该文件名称修改为
shell2.sh,下列命令( B )可以实现。
A. cp shell.sh shell2.sh 
B. mv shell.sh shell2.sh 
C. ls shell.sh >shell2.sh 
D. ll shell.sh >shell2.sh
93、在使用 GCC 编译器的过程中,以下(B)
选项可用来指定生成的目标文件名
A.-c B.-o C.-S
D.-E
94、假设当前目录下有文件 Makefile,下面是
其内容:
pr1: prog.o subr.o
gcc –o pr1 prog.o subr.o
prog.o: prog.c prog.h
gcc –c –l prog.o prog.c
subr.o: subr.c
gcc –c –o subr.o subr.c
clear:
rm –f pr1*.o
现在执行命令 make subr.o,实际执行的命令
是(C):
A. gcc –o pr1 prog.o subr.o 
B. gcc –c –l prog.o prog.c 
C. gcc –c –o subr.o subr.c 
D. 都执行
95、为了使用生成的目标文件能够用于 gdb 调
试,在编译时 GCC 应使用(C)选项。
A.-c B.-w C.-g D.-o
96、存盘并退出 vi 的指令是( D )。
A、q B、q! C、w D、wq
97. 下列关于/etc/fstab 文件描述,正确的是
( D )。
A. fstab 文件只能描述属于 linux 的文件系统
B. CD_ROM 和软盘必须是自动加载的
C. fstab 文件中描述的文件系统不能被卸载
D 启动时按 fstab 文件描述内容加载文件系统
98. ARM 嵌入式系统中,PC 指向的是正
在(C )的指令地
址。
A 执行 B 译
码 C 取指 D 
都不是
99. ARM 系统处理 16-bit 数据时,对应
的数据类型是
( B )。
A Byte B Halfw
ord C Word
D 三者都不是
100. 实时系统是指( B )
A 响应快的系统 B 时间约束的系
统 C 单任务系统 D 内核小的
系统
101. 下面属于 blob 运行过程第一阶段的是
(C)
A 外围的硬件初始化(串口,USB 等);
B 根据用户选择,进入命令行模块或启动
kernel。
C 寄存器的初始化
D 堆栈的初始化
答案:C 第一阶段的代码在 start.s 中定义,
大小为 1KB,它包括从系统上电后在
0x00000000 地址开始执行的部分。这部分代码
运行在 Flash 中,它包括对 S3C44B0 的一些寄
存器的初始化和将Blob第二阶段代码从Flash
拷贝到 SDRAM 中。
102.下列几种流行的嵌入式 GUI 中,没有采用
分层设计的一种是: B
A.MiniGUI B. Qt/Embedded C. 
Nano-XWindow D. OpenGUI
103. Qt/Embedded 的底层图形引擎基于一下哪
种接口技术: A
A.framebuffer B.GAL C.IAL
D.GFX
104.在 Linux 使用 GCC 编译器时有如下命
令:Gcc–g test.c –o test,其中参数-g 的
作用是(D)
A .生成目标文件 test.o B.生成汇编
文件 test.s C .进行预编译 D .包含
调试信息
105. LINUX 支持网络文件系统 NFS,下列哪个
命令实现了将位于 192.168.1.4 机器上的
/opt/sirnfs 目录挂载到本机/mnt/sirnfs
下: A
A.mount -t nfs 
192.168.1.4:/opt/sirnfs/mnt/sirnfs
B.mount -t nfs /mnt/sirnfs 
192.168.1.4:/opt/sirnfs
C.mount nfs –t 
192.168.1.4:/opt/sirnfs/mnt/sirnfs
D.mount nfs –t 
/mnt/sirnfs192.168.1.4:/opt/sirnfs
106、同 CISC 相比,下面哪一项不属于 RISC
处理器的特征___D_____
A、采用固定长度的指令格式,指令规整、简
单、基本寻址方式有 2~3 种。
B、减少指令数和寻址方式,使控制部件简化,
加快执行速度。
C、数据处理指令只对寄存器进行操作,只有
加载/存储指令可以访问存储器,以提高指令
的执行效率,同时简化处理器的设计。
D、RISC 处理器都采用哈佛结构
107、32 位数 0x12345678 用小端格式表示,则
在 AXD 调试器下观察数据在内存中分布的情况
是(B)
A 12 34 56 78 B 78 56 34 
12 C 21 43 65 
87 D 87 65 43 21
108、RISC 是指(C)
A 复杂指令计算机 B 并行机 C 精
简指令计算机 D 多处理器计算机
109、在 ARM 体系结构中,__C__寄存器作为连
接寄存器,当进入子程序时或者处理器响应异
常的时候,用来保存 PC 的返回值;_C___寄存
器作为处理器的程序计数器指针。
A、R0,R14 B、R13,R15
C、R14,R15 D、R14,R0
110、在 ARM 体系结构中,要从主动用户模式
(User)切换到超级用户模式(Supervisor),
应采用何种方法?C
A、直接修改 CPU 状态寄存器(CPSR)对应的
模式
B、先修改程序状态备份寄存器(SPSR)到对
应的模式,再更新 CPU 状态
C、使用软件中断指令(SWI)
D、让处理器执行未定义指令
111、表达式 A⊕B 实现的功能是(C)
A 逻辑与 B 逻辑非 C 逻辑异
或 D 逻辑或
112、嵌入式系统的开发通常是在交叉开发环
境实现的,交叉开发环境是指( A )
A 在宿主机上开发,在目标机上运
行 B 在目标机上开发,在宿主机上运

C 在宿主机上开发,在宿主机上运
行 D 在目标机上开发,在目标机上运行
113、在 ARM 系统结构中,MMU 映射最小的单元
空间是__D__
A、64KB B、
16KB C、4KB D、
1KB
114、在 ARM Linux 启动的过程中,开启 MMU
的时候,如何实现从实地址空间到虚拟地址空
间的过度?D
A、开启 MMU,在内存中创建页表(映射内核到
3G 以上的虚拟地址空间)并继续运行。
B、开启 MMU,在内存中创建页表(映射内核到
3G 以上的虚拟地址空间),跳转到虚拟地址空
间继续运行。
C、在内存中创建页表(映射内核到 3G 以上的
虚拟地址空间),开启 MMU,跳转到虚拟地址
空间继续运行。
D、在内存中创建页表(映射内核到 3G 以上的
虚拟地址空间,同时把内核所在的前 1MB 空间
到和其实地址相同的虚拟地址空间),开启 MMU,
跳转到虚拟地址空间继续运行。
115、在 ARM 体系中,MMU 的第一级描述符有___
项,每个描述符占用____字节
A、1024,32 B、
4096,4
C、4096,4 D、1024,
32
答案:C(B 和 C 一样的,A 和 D 是一样的)
116、在 ARM 体系中,下面 MMU 的一级描述符
中,是节描述符的是_A___
A、0xA0000C0E B、
0xA0000C0F
C、0x00000000 D、
0xC0000C01
117、在 ARM Linux 体系中,用来处理外设中
断的异常模式是_C_____
A、软件中断(SWI) B、
未定义的指令异常
C、中断请求(IRQ) D、
快速中断请求(FIQ)
118 、指令 ADD R2,R1,R1,LSR #2 中,
LSR 的含义是
(B)。
A 逻辑左移 B 逻辑右
移 C 算术右
移 D 循环右移
119、以下 ARM 异常中,优先级最高的是
(D )。
A Data 
abort B FIQ
C IRQ D Re
set
120、指令 LDR R0,[R4]对源操作数的寻址方式

( A )
A 寄存器间接寻址 B 寄存
器寻址 C 立即数寻
址 D 相对寻址
121、在 Linux 2.4 或者 2.6 内核中,和 ARM
体系结构相关的中断处理程序的 C 代码在源码
树的__B_文件中
A、kernerl/irq.c
B、arch/arm/kernel/irq.c
C、arch/arm/mach/irq.c
D、arch/arm/kernel/entry-armv.S
122、以下关于 init 进程,描述不正确的是:
(A)
A. 一个通用进程
B. 可以产生新的进程
C. 在某些程序退出的时候能重起它们
D. 负责在系统启动的时候运行一系列程序和
脚本文件
123、哈佛结构和冯诺依曼结构的区别是( A)
A 指令和数据分开存储 B 不需要程序
计数器 C 统一编址 D 单一数据总
线
124、fstab 文件存放在(A)目录中。
A./etc
B./boot
C./sbin
&
第一章、进程与线程 
1、什么是进程、线程,有什么区别? 
进程是资源(CPU、内存等)分配的基本单位,
线程是 CPU 调度和分配的基本单位(程序执行
的最小单
位)。同一时间,如果 CPU 是单核,只有一个
进程在执行,所谓的并发执行,也是顺序执行,
只不过由
于切换速度太快,你以为这些进程在同步执行
而已。多核 CPU 可以同一时间点有多个进程在
执行。
2、多进程、多线程的优缺点 
说明:一个进程由进程控制块、数据段、代码
段组成,进程本身不可以运行程序,而是像一
个容器一
样,先创建出一个主线程,分配给主线程一定
的系统资源,这时候就可以在主线程开始实现
各种功能。
当我们需要实现更复杂的功能时,可以在主线
程里创建多个子线程,多个线程在同一个进程
里,利用这
个进程所拥有的系统资源合作完成某些功能。
优缺点:(1)一个进程死了不影响其他进程,
一个线程崩溃很可能影响到它本身所处的整
个进程。(2) 创建多进程的系统花销大于创
建多线程。(3)多进程通讯因为需要跨越进
程边界,不适合大量数据的传送,适合小数据
或者密集数据的传送。多线程无需跨越进程边
界,适合各线程间大量数据的传送。并且多线
程可以共享同一进程里的共享内存和变量。
3、什么时候用进程,什么时候用线程 
(1)创建和销毁较频繁使用线程,因为创建
进程花销大。(2)需要大量数据传送使用线
程,因为多线程切 换速度快,不需要跨越进
程边界。(3)安全稳定选进程;快速频繁选
线程;
4、多进程、多线程同步(通讯)的方法 
进程间通讯:
(1)有名管道/无名管道(2)信号(3)共享
内存(4)消息队列(5)信号量(6)socket 
线程通讯(锁):
(1)信号量(2)读写锁(3)条件变量(4)
互斥锁(5)自旋锁
5、进程线程的状态转换图 
(1)就绪状态:进程已获得除 CPU 外的所有
必要资源,只等待 CPU 时的状态。一个系统会
将多个处于就绪状态的进程排成一个就绪队
列。
(2)执行状态:进程已获 CPU,正在执行。单
处理机系统中,处于执行状态的进程只一个;
多处理机系
统中,有多个处于执行状态的进程。
(3)阻塞状态:正在执行的进程由于某种原
因而暂时无法继续执行,便放弃处理机而处于
暂停状态,即进程执行受阻。(这种状态又称
等待状态或封锁状态) 通常导致进程阻塞的
典型事件有:请求 I/O,申请缓冲空间等。
一般,将处于阻塞状态的进程排成一个队列,
有的系统还根据阻塞原因不同把这些阻塞集
成排成多个队列。(1) 就绪→执行
处于就绪状态的进程,当进程调度程序为之分
配了处理机后,该进程便由就绪状态转变成执
行状态。
(2) 执行→就绪
处于执行状态的进程在其执行过程中,因分配
给它的一个时间片已用完而不得不让出处理
机,于是进程从执行状态转变成就绪状态。
(3) 执行→阻塞
正在执行的进程因等待某种事件发生而无法
继续执行时,便从执行状态变成阻塞状态。
(4) 阻塞→就绪
处于阻塞状态的进程,若其等待的事件已经发
生,于是进程由阻塞状态转变为就绪状态。
6、父进程、子进程 
父进程调用 fork()以后,克隆出一个子进程,
子进程和父进程拥有相同内容的代码段、数据
段和用户堆栈。父进程和子进程谁先执行不一
定,看 CPU。所以我们一般我们会设置父进程
等待子进程执行完毕。
7、说明什么是上下文切换? 
你可以有很多角度,有进程上下文,有中断上
下文。
进程上下文:一个进程在执行的时候,CPU 的
所有寄存器中的值、进程的状态以及堆栈中的
内容,当内核需要切换到另一个进程时,它需
要保存当前进程的所有状态,即保存当前进程
的进程上下文,以便再次执行该进程时,能够
恢复切换时的状态,继续执行。 中断上下文:
由于触发信号,导致 CPU 中断当前进程,转而
去执行另外的程序。那么当前进程的所有资
源要保存,比如堆栈和指针。保存过后转而去
执行中断处理程序,快读执行完毕返回,返回
后恢复上一 个进程的资源,继续执行。这就
是中断的上下文。
第二章、C/C++题目 
1、new 和 malloc 
做嵌入式,对于内存是十分在意的,因为可用
内存有限,所以嵌入式笔试面试题目,内存的
题目高频。
(1)malloc 和 free 是 c++/c 语言的库函数,
需要头文件支持 stdlib.h;new 和 delete 是
C++的关键字,不需要头文件,需要编译器支
持;
(2)使用 new 操作符申请内存分配时,无需
指定内存块的大小,编译器会根据类型信息自
行计算。而 malloc 则需要显式地支持所需内
存的大小。(3)new 操作符内存分配成功时,
返回的是对象类型的指针,类型严格与对象匹
配,无需进行类型转换, 故 new 是符合类型
安全性的操作符。而 malloc 内存分配成功则
是返回 void,需要通过强制类型转换将
void 指针转换成我们需要的类型。
(4)new 内存分配失败时,会抛出 bad_alloc
异常。malloc 分配内存失败时返回 NULL。
2、在 1G 内存的计算机中能否 malloc(1.2G)?
为什么?
答:是有可能申请 1.2G 的内存的。
解析:回答这个问题前需要知道 malloc 的作
用和原理,应用程序通过 malloc 函数可以向
程序的虚拟空间
申请一块虚拟地址空间,与物理内存没有直接
关系,得到的是在虚拟地址空间中的地址,之
后程序运行
所提供的物理内存是由操作系统完成的。
3 、extern”C” 的作用 
我们可以在 C++中使用 C 的已编译好的函数模
块,这时候就需要用到 extern”C”。也就是
extern“C” 都是在
c++文件里添加的。
extern 在链接阶段起作用(四大阶段:预处理
--编译--汇编--链接)。
4、strcat、strncat、strcmp、strcpy 哪些函
数会导致内存溢出?如何改进? 
 
strcpy 函数会导致内存溢出。
strcpy 拷贝函数不安全,他不做任何的检查措
施,也不判断拷贝大小,不判断目的地址内存
是否够用。
strncpy 拷贝函数,虽然计算了复制的大小,
但是也不安全,没有检查目标的边界。
strncpy_s 是安全的
strcmp(str1,str2),是比较函数,若
str1=str2,则返回零;若 str1<str2,则返回
负数;若 str1>str2,则
返回正数。(比较字符串)
strncat()主要功能是在字符串的结尾追加 n
个字符。
strcat()函数主要用来将两个 char 类型连接。
例如:
输出 d 为 GoldenView (中间无空格)
延伸:
memcpy 拷贝函数,它与 strcpy 的区别就是
memcpy 可以拷贝任意类型的数据,strcpy 只
能拷贝字符串
类型。
char *strcpy(char *strDest,const char 
*strSrc)

strncpy(dest, src, sizeof(dest)); 

char * strncat(char *dest, const char *src, 
size_t n); 

char d[20]="Golden"; 
char s[20]="View"; 
strcat(d,s); 
//打印 d 
printf("%s",d); 




5memcpy 函数用于把资源内存(src 所指向的
内存区域)拷贝到目标内存(dest 所指向的内
存区域);
有一个 size 变量控制拷贝的字节数;
函数原型:
5 、static 的用法(定义和用途)(必考) 
1)用 static 修饰局部变量:使其变为静态存
储方式(静态数据区),那么这个局部变量在函
数执行完成之
后不会被释放,而是继续保留在内存中。
2)用 static 修饰全局变量:使其只在本文件
内部有效,而其他文件不可连接或引用该变量。
3)用 static 修饰函数:对函数的连接方式产
生影响,使得函数只在本文件内部有效,对其
他文件是不可
见的(这一点在大工程中很重要很重要,避免
很多麻烦,很常见)。这样的函数又叫作静态
函数。使用
静态函数的好处是,不用担心与其他文件的同
名函数产生干扰,另外也是对函数本身的一种
保护机制。
6、const 的用法(定义和用途)(必考) 
const 主要用来修饰变量、函数形参和类成员
函数:
1)用 const 修饰常量:定义时就初始化,以
后不能更改。
2)用 const 修饰形参:func(const int a){};
该形参在函数里不能改变
3)用 const 修饰类成员函数:该函数对成员
变量只能进行只读操作,就是 const 类成员函
数是不能修改
成员变量的数值的。
被 const 修饰的东西都受到强制保护,可以预
防意外的变动,能提高程序的健壮性。
参考一个大佬的回答: 
我只要一听到被面试者说:"const 意味着常数
",我就知道我正在和一个业余者打交道。去
年 Dan Saks 已
经在他的文章里完全概括了 const 的所有用法,
因此 ESP(译者:Embedded Systems 
Programming)的
每一位读者应该非常熟悉 const 能做什么和不
能做什么.如果你从没有读到那篇文章,只要
能说出 const 意
味着"只读"就可以了。尽管这个答案不是完全
的答案,但我接受它作为一个正确的答案。如
果应试者能 正确回答这个问题,我将问他一
个附加的问题:下面的声明都是什么意思?
前两个的作用是一样,a 是一个常整型数。
第三个意味着 a 是一个指向常整型数的指针
(也就是,整型数是不可修改的,但指针可以)。
第四个意思 a 是一个指向整型数的常指针(也
就是说,指针指向的整型数是可以修改的,但
指针是不可修改的)。 最后一个意味着 a 是
一个指向常整型数的常指针(也就是说,指针
指向的整型数是不可修改的,同时指针
也是不可修改的)。
void *memcpy(void *dest, void *src, 
unsigned int count); 

const int a; 
int const a; 
const int *a; 
int * const a; 
int const * a const; 




57、volatile 作用和用法 
一个定义为 volatile 的变量是说这变量可能
会被意想不到地改变,这样,编译器就不会去
假设这个变量的
值了。精确地说就是,优化器在用到这个变量
时必须每次都小心地重新读取这个变量在内
存中的值,而
不是使用保存在寄存器里的备份(虽然读写寄
存器比读写内存快)。
回答不出这个问题的人是不会被雇佣的。这是
区分 C 程序员和嵌入式系统程序员的最基本的
问题。搞嵌
入式的家伙们经常同硬件、中断、RTOS 等等打
交道,所有这些都要求用到 volatile 变量。
不懂得 volatile 
的内容将会带来灾难。
以下几种情况都会用到 volatile:
1、并行设备的硬件寄存器(如:状态寄存器)
2、一个中断服务子程序中会访问到的非自动
变量
3、多线程应用中被几个任务共享的变量
8、const 常量和#define 的区别(编译阶段、
安全性、内存占用等) 
用#define max 100 ; 定义的常量是没有类型
的(不进行类型安全检查,可能会产生意想不
到的错误), 所给出的是一个立即数,编译
器只是把所定义的常量值与所定义的常量的
名字联系起来,define 所定义的宏变量在预处
理阶段的时候进行替换,在程序中使用到该常
量的地方都要进行拷贝替换;
用 const int max = 255 ; 定义的常量有类型
(编译时会进行类型检查)名字,存放在内存
的静态区域中,在编译时确定其值。在程序运
行过程中 const 变量只有一个拷贝,而#define
所定义的宏变量却有多个拷贝,所以宏定义在
程序运行过程中所消耗的内存要比 const 变量
的大得多
9、变量的作用域(全局变量和局部变量) 
全局变量:在所有函数体的外部定义的,程序
的所在部分(甚至其它文件中的代码)都可以
使用。全局变量不受作用域的影响(也就是说,
全局变量的生命期一直到程序的结束)。
局部变量:出现在一个作用域内,它们是局限
于一个函数的。局部变量经常被称为自动变量,
因为它们在进入作用域时自动生成,离开作用
域时自动消失。关键字 auto 可以显式地说明
这个问题,但是局部变量默认为 auto,所以没
有必要声明为 auto。局部变量可以和全局变量
重名,在局部变量作用域范围内,全局变量失
效,采用的是局部变量的值。
10、sizeof 与 strlen (字符串,数组) 
1.如果是数组
运行结果
#include<stdio.h> 
int main() 

int a[5]={1,2,3,4,5}; 
printf(“sizeof 数组名=%d\n”,sizeof(a)); 
printf(“sizeof *数组名
=%d\n”,sizeof(*a)); 








sizeof 数组名=20 
sizeof *数组名=4 

22.如果是指针,sizeof 只会检测到是指针的
类型,指针都是占用4个字节的空间(32位机)。
sizeof 是什么?是一个操作符,也是关键字,
就不是一个函数,这和 strlen()不同,strlen()
是一个函数。
那么 sizeof 的作用是什么?返回一个对象或
者类型所占的内存字节数。我们会对 sizeof()
中的数据或者指
针做运算吗?基本不会。例如 sizeof(1+2.0),
直接检测到其中类型是 double,即是
sizeof(double) = 8。如
果是指针,sizeof 只会检测到是指针的类型,
指针都是占用 4 个字节的空间(32 位机)。
除非使用 strlen(),仅对字符串有效,直到
'\0'为止了,计数结果不包括\0。
要是非要使用 sizeof 来得到指向内容的大小,
就得使用数组名才行,

关于 strlen(),它是一个函数,考察的比较简
单:
答案:11 
char *p = "sadasdasd"; 
sizeof(p):4 
sizeof(*p):1//指向一个 char 类型的



char a[10]; 
sizeof(a):10 //检测到 a 是一个数组的类型。


strlen “\n\t\tag\AAtang”
111、经典的sizeof(struct)和 sizeof(union)
内存对齐 
内存对齐作用:
1.平台原因(移植原因):不是所有的硬件平台
都能访问任意地址上的任意数据的;某些硬件
平台只能在某
些地址处取某些特定类型的数据,否则抛出硬
件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能
地在自然边界上对齐。原因在于,为了访问未
对齐的内存,
处理器需要作两次内存访问;而对齐的内存访
问仅需要一次访问。
结构体 struct 内存对齐的 3 大规则: 
1.对于结构体的各个成员,第一个成员的偏移
量是 0,排列在后面的成员其当前偏移量必须
是当前成员类型的整数倍;
2.结构体内所有数据成员各自内存对齐后,结
构体本身还要进行一次内存对齐,保证整个结
构体占用内存大小是结构体内最大数据成员
的最小整数倍;
3.如程序中有#pragma pack(n)预编译指令,
则所有成员对齐以 n 字节为准(即偏移量是 n
的整数倍),不再考虑当前类型以及最大结构
体内类型。
sizeof(fun) = 13 
1.使用 32 位编译,int 占 4, char 占 1,
unsigned short 占 2,char* 占 4,函数指针
占 4 个,由于是 32 位编译是 4 字节对齐,所
以该结构体占 16 个字节。(说明:按几字节
对齐,是根据结构体的最长类型决定的,这里
是 int 是最长的字节,所以按 4 字节对齐);
2.使用 64 位编译 ,int 占 4, char 占 1,
unsigned short 占 2,char* 占 8,函数指针
占 8 个,由于是 64 位编译是 8 字节对齐(说
明:按几字节对齐,是根据结构体的最长类型
决定的,这里是函数指针是最长的字节,所以
按 8 字节对齐)所以该结构体占 24 个字节。
#pragma pack(1)
struct fun{ 
int i; 
double d; 
char c; 
}; 







struct CAT_s 

int ld; 
char Color; 
unsigned short Age; 
char *Name; 
void(*Jump)(void); 
}Garfield; 







8char 是 1,然后在 int 之前,地址偏移量得
是 4 的倍数,所以 char 后面补三个字节,也
就是 char 占了 4 个字节,然后 int 四个字节,
最后是 short,只占两个字节,但是总的偏移
量得是 double 的倍数,也就是 8 的倍数,所
以 short 后面补六个字节联合体 union 内存对
齐的 2 大规则: 
1.找到占用字节最多的成员;
2.union 的字节数必须是占用字节最多的成员
的字节的倍数,而且需要能够容纳其他的成员
要计算 union 的大小,首先要找到占用字节最
多的成员,本例中是 long,占用 8 个字节,int 
k[5]中都是 int 类型,仍然是占用 4 个字节的,
然后 union 的字节数必须是占用字节最多的成
员的字节的倍数,而且需要能够容纳其他的成
员,为了要容纳 k(20 个字节),就必须要保证是
8 的倍数的同时还要大于 20 个字节,所以是 24
个字节。
C 语言允许在一个结构体中以位为单位来指定
其成员所占内存长度,这种以位为单位的成员
称为“位段”或称“位域”( bit field) 。
利用位段能够用较少的位数存储数据。一个位
段必须存储在同一存储单元中,不能跨两个单
元。如果第一个单元空间不能容纳下一个位段,
则该空间不用,而从下一个单元起存放该位
段。
1.位段声明和结构体类似
2.位段的成员必须是 int、unsigned int、
signed int 
3.位段的成员名后边有一个冒号和一个数字
//64 位
struct C 

double t; //8 1111 1111 
char b; //1 1 
int a; //4 0001111 
short c; //2 11000000 
}; 
sizeof(C) = 24; //注意:1 4 2 不能拼在一










//x64 
typedef union { 
long i; 
int k[5]; 
char c; 
}D 





6 答案 12 
m 和 n 一起,刚好占用一个字节内存,因为后
面是 short 类型变量,所以在 short s 之前,
应该补一个字
节。所以 m 和 n 其实是占了两个字节的,然后
是 short 两个个字节,加起来就 4 个字节,然
后联合体占了四
个字节,总共 8 个字节了,最后 int h 占了四
个字节,就是 12 个字节了
attribute((packed)) 取消对齐
GNU C 的一大特色就是 attribute 机制。
attribute 可以设置函数属性(Function 
Attribute)、变量属
性(Variable Attribute)和类型属性(Type 
Attribute)。
attribute 书写特征是:attribute 前后都有
两个下划线,并且后面会紧跟一对括弧,括弧
里面是相应的 attribute 参数。
跨平台通信时用到。不同平台内存对齐方式不
同。如果使用结构体进行平台间的通信,会有
问题。例如,发送消息的平台上,结构体为 24
字节,接受消息的平台上,此结构体为 32 字
节(只是随便举个例子),那么每个变量对应
的值就不对了。不同框架的处理器对齐方式会
有不同,这个时候不指定对齐的话,会产生错
误结果
12、inline 函数 
在 C 语言中,如果一些函数被频繁调用,不断
地有函数入栈,即函数栈,会造成栈空间或栈
内存的大量消耗。为了解决这个问题,特别的
引入了 inline 修饰符,表示为内联函数。
大多数的机器上,调用函数都要做很多工作:
调用前要先保存寄存器,并在返回时恢复,复
制实参,程序还必须转向一个新位置执行 C++
中支持内联函数,其目的是为了提高函数的执
行效率,用关键字 inline 放在函数定义(注意
是定义而非声明)的前面即可将函数指定为内
联函数,内联函数通常就是将它在程序中
的每个调用点上“内联地”展开。内联是以代
码膨胀(复制)为代价,仅仅省去了函数调用
的开销,从而提高函数的执行效率。
13、内存四区,什么变量分别存储在什么区域,
堆上还是栈上。 
typedef struct_data{ 
char m:3; 
char n:5; 
short s; 
union{ 
int a; 
char b; 
}; 
int h; 
}_attribute_((packed)) data_t; 









10 
11 
12 文字常量区,叫.rodata,不可以改变,改
变会导致段错误
a0 :全局初始化变量;生命周期为整个程序
运行期间;作用域为所有文件;存储位置为
data 段。
a1 :全局静态未初始化变量;生命周期为整
个程序运行期间;作用域为当前文件;储存位
置为 BSS 段。
a2 :全局静态变量
a3 :全局初始化变量;其他同 a0。
a4 :局部变量;生命周期为 fun 函数运行期
间;作用域为 fun 函数内部;储存位置为栈。
a5 :局部易变变量;
int a0=1; 
static int a1; 
const static a2=0; 
extern int a3; 
void fun(void) 

int a4; 
volatile int a5; 
return; 










10 
1114、使用 32 位编译情况下,给出判断所使
用机器大小端的方法。 
联合体方法判断方法:利用 union 结构体的从
低地址开始存,且同一时间内只有一个成员占
有内存的特
性。大端储存符合阅读习惯。联合体占用内存
是最大的那个,和结构体不一样。
a 和 c 公用同一片内存区域,所以更改 c,必
然会影响 a 的数据
指针方法
通过将 int 强制类型转换成 char 单字节,p 指
向 a 的起始字节(低字节)
#include<stdio.h> 
int main(){ 
union w 

int a; 
char b; 
}c; 
c.a = 1; 
if(c.b == 1) 
printf("小端存储\n"); 
else 
printf("大端存储\n"); 
return 0; 










10 
11 
12 
13 
14 
15 
#include <stdio.h> 
int main () 

int a = 1; 
char *p = (char *)&a; 
if(*p == 1) 

printf("小端存储\n"); 

else 

printf("大端存储\n"); 

return 0; 










10 
11 
12 
13 
14 
1515、用变量 a 给出下面的定义 
16、与或非,异或。运算符优先级 
sum=a&b<<c+a^c; 
其中 a=3,b=5,c=4(先加再移位再&再异或)
答案 4 
第三章、网络编程 
1 、TCP、UDP 的区别 
TCP---传输控制协议,提供的是面向连接、可
靠的字节流服务。当客户和服务器彼此交换数
据前,必须先
在双方之间建立一个 TCP 连接,之后才能传输
数据。
UDP---用户数据报协议,是一个简单的面向数
据报的运输层协议。UDP 不提供可靠性,它只
是把应用程
序传给 IP 层的数据报发送出去,但是并不能
保证它们能到达目的地。
a) 一个整型数;
b)一个指向整型数的指针;
c)一个指向指针的指针,它指向的指针是指
向一个整型数;
d)一个有 10 个整型的数组;
e)一个有 10 个指针的数组,该指针是指向一
个整型数;
f)一个指向有 10 个整型数数组的指针;
g)一个指向函数的指针,该函数有一个整型
参数并返回一个整型数;
h)一个有 10 个指针的数组,该指针指向一个
函数,该函数有一个整型参数并返回一个整型

答案:
a)int a 
b)int *a; 
c)int **a; 
d)int a[10]; 
e)int *a [10]; 
f) int a[10], *p=a; 
g)int (*a)(int) 
h) int( *a[10])(int) 









10 
11 
12 
13 
14 
15 
16 
171)TCP 是面向连接的,UDP 是面向无连接的
2)UDP 程序结构较简单
3)TCP 是面向字节流的,UDP 是基于数据报的
4)TCP 保证数据正确性,UDP 可能丢包
5)TCP 保证数据顺序到达,UDP 不保证
2 、TCP、UDP 的优缺点 
TCP 优点:可靠稳定
TCP 的可靠体现在 TCP 在传输数据之前,会有
三次握手来建立连接,而且在数据传递时,有
确认、窗口、
重传、拥塞控制机制,在数据传完之后,还会
断开来连接用来节约系统资源。
TCP 缺点:慢,效率低,占用系统资源高,易
被攻击
在传递数据之前要先建立连接,这会消耗时间,
而且在数据传递时,确认机制、重传机制、拥
塞机制等
都会消耗大量时间,而且要在每台设备上维护
所有的传输连接。然而,每个连接都会占用系
统的 CPU,
内存等硬件资源。因为 TCP 有确认机制、三次
握手机制,这些也导致 TCP 容易被利用,实现
DOS、
DDOS、CC 等攻击。
UDP 优点:快,比 TCP 稍安全
UDP 没有 TCP 拥有的各种机制,是一种无状态
的传输协议,所以传输数据非常快,没有 TCP
的这些机
制,被攻击利用的机会就少一些,但是也无法
避免被攻击。
UDP 缺点:不可靠,不稳定
因为没有TCP的这些机制,UDP在传输数据时,
如果网络质量不好,就会很容易丢包,造成数
据的缺
失。
3 、TCP UDP 适用场景 
TCP:传输一些对信号完整性,信号质量有要
求的信息。
UDP:对网络通讯质量要求不高时,要求网络
通讯速度要快的场景。
4、 TCP 为什么是可靠连接? 
因为 tcp 传输的数据满足 3 大条件,不丢失,
不重复,按顺序到达。
5、OSI 典型网络模型,简单说说有哪些 6、三
次握手、四次挥手 
三次握手:
1、TCP 服务器进程先创建传输控制块 TCB,时
刻准备接受客户进程的连接请求,此时服务器
就进入了
LISTEN(监听)状态;
2、TCP 客户进程也是先创建传输控制块 TCB,
然后向服务器发出连接请求报文,这是报文首
部中的同部
位 SYN=1,同时选择一个初始序列号 seq=x ,
此时,TCP 客户端进程进入了 SYN-SENT(同步
已发送状
态)状态。TCP 规定,SYN 报文段(SYN=1 的报
文段)不能携带数据,但需要消耗掉一个序号。
3、TCP 服务器收到请求报文后,如果同意连接,
则发出确认报文。确认报文中应该 ACK=1,
SYN=1,
确认号是 ack=x+1,同时也要为自己初始化一
个序列号 seq=y,此时,TCP 服务器进程进入
了 SYN
RCVD(同步收到)状态。这个报文也不能携带
数据,但是同样要消耗一个序号。
4、TCP 客户进程收到确认后,还要向服务器给
出确认。确认报文的 ACK=1,ack=y+1,自己的
序列号
seq=x+1,此时,TCP 连接建立,客户端进入
ESTABLISHED(已建立连接)状态。TCP 规定,
ACK 报文
段可以携带数据,但是如果不携带数据则不消
耗序号。
5、当服务器收到客户端的确认后也进入
ESTABLISHED 状态,此后双方就可以开始通信
了。四次挥手:
1、客户端进程发出连接释放报文,并且停止
发送数据。释放数据报文首部,FIN=1,其序
列号为
seq=u(等于前面已经传送过来的数据的最后
一个字节的序号加 1),此时,客户端进入
FIN-WAIT-1(终
止等待 1)状态。TCP 规定,FIN 报文段即使不
携带数据,也要消耗一个序号。
2、服务器收到连接释放报文,发出确认报文,
ACK=1,ack=u+1,并且带上自己的序列号 seq=v,

时,服务端就进入了 CLOSE-WAIT(关闭等待)
状态。TCP 服务器通知高层的应用进程,客户
端向服务器
的方向就释放了,这时候处于半关闭状态,即
客户端已经没有数据要发送了,但是服务器若
发送数据,
客户端依然要接受。这个状态还要持续一段时
间,也就是整个 CLOSE-WAIT 状态持续的时间。
3、客户端收到服务器的确认请求后,此时,
客户端就进入 FIN-WAIT-2(终止等待 2)状态,
等待服务器
发送连接释放报文(在这之前还需要接受服务
器发送的最后的数据)。
4、服务器将最后的数据发送完毕后,就向客
户端发送连接释放报文,FIN=1,ack=u+1,由
于在半关闭
状态,服务器很可能又发送了一些数据,假定
此时的序列号为 seq=w,此时,服务器就进入
了 LAST
ACK(最后确认)状态,等待客户端的确认。
5、客户端收到服务器的连接释放报文后,必
须发出确认,ACK=1,ack=w+1,而自己的序列
号是
seq=u+1,此时,客户端就进入了 TIME-WAIT
(时间等待)状态。注意此时 TCP 连接还没有
释放,必须
经过 2∗ *∗MSL(最长报文段寿命)的时间后,
当客户端撤销相应的 TCB 后,才进入 CLOSED
状态。
6、服务器只要收到了客户端发出的确认,立
即进入 CLOSED 状态。同样,撤销 TCB 后,就
结束了这次的
TCP 连接。可以看到,服务器结束 TCP 连接的
时间要比客户端早一些。
第四章、常见算法 
十种常见排序算法可以分为两大类:
非线性时间比较类排序:通过比较来决定元素
间的相对次序,由于其时间复杂度不能突破
O(nlogn),因
此称为非线性时间比较类排序。
线性时间非比较类排序:不通过比较来决定元
素间的相对次序,它可以突破基于比较排序的
时间下界,
以线性时间运行,因此称为线性时间非比较类
排序。算法优劣评价术语 
稳定性: 
稳定:如果 a 原本在 b 前面,而 a = b,排
序之后 a 仍然在 b 的前面;
不稳定:如果 a 原本在 b 的前面,而 a = b,
排序之后 a 可能会出现在 b 的后面;
排序方式: 
内排序:所有排序操作都在内存中完成,占用
常数内存,不占用额外内存。
外排序:由于数据太大,因此把数据放在磁盘
中,而排序通过磁盘和内存的数据传输才能进
行,占用额
外内存。
复杂度: 
时间复杂度: 一个算法执行所耗费的时间。
空间复杂度: 运行完一个程序所需内存的大
小。至于各种算法的原理以及代码实现,由于
太多并且比较复杂,不在本文列出。但推荐两
本入门的书:
《啊哈!算法》、《大话数据结构》。电子版
我会发在交流群里。
排序算法很多,嵌入式要求的不会太多,你会
冒泡排序、快速排序、插入排序就可以解决很
多问题。难
的比如动态规划问题,图的路径问题,嵌入式
考的比较少,纯软才会考这些。(大公司和独
角兽公司考
的会相对难一些)
第五章、Linux 操作系统题目 
1、 Linux 内核的组成部分 
Linux 内核主要由五个子系统组成:进程调度,
内存管理,虚拟文件系统,网络接口,进程间
通信。
2、Linux 系统的组成部分 
Linux 系统一般有 4 个主要部分:
内核、shell、文件系统和应用程序。3、用户
空间与内核通信方式有哪些? 
1)系统调用。用户空间进程通过系统调用进入
内核空间,访问指定的内核空间数据;
2)驱动程序。用户空间进程可以使用封装后的
系统调用接口访问驱动设备节点,以和运行在
内核空间的
驱动程序通信;
3)共享内存 mmap。在代码中调用接口,实现内
核空间与用户空间的地址映射,在实时性要求
很高的项
目中为首选,省去拷贝数据的时间等资源,但
缺点是不好控制;
4)copy_to_user()、copy_from_user(),是在
驱动程序中调用接口,实现用户空间与内核空
间的数据拷贝
操作,应用于实时性要求不高的项目中。
以及:
4、系统调用与普通函数调用的区别 
系统调用:
1.使用 INT 和 IRET 指令,内核和应用程序使
用的是不同的堆栈,因此存在堆栈的切换,从
用户态切换到内
核态,从而可以使用特权指令操控设备
2.依赖于内核,不保证移植性
3.在用户空间和内核上下文环境间切换,开销
较大
4.是操作系统的一个入口点
普通函数调用:
1.使用 CALL 和 RET 指令,调用时没有堆栈切

procfs(/proc) 
sysctl (/proc/sys) 
sysfs(/sys) 
netlink 套接口



42.平台移植性好
3.属于过程调用,调用开销较小
4.一个普通功能函数的调用
5、内核态,用户态的区别 
内核态,操作系统在内核态运行——运行操作
系统程序
用户态,应用程序只能在用户态运行——运行
用户程序
当一个进程在执行用户自己的代码时处于用
户运行态(用户态),此时特权级最低,为 3
级,是普通的用
户进程运行的特权级,大部分用户直接面对的
程序都是运行在用户态。Ring3 状态不能访问
Ring0 的地址
空间,包括代码和数据;当一个进程因为系统
调用陷入内核代码中执行时处于内核运行态
(内核态),
此时特权级最高,为 0 级。执行的内核代码会
使用当前进程的内核栈,每个进程都有自己的
内核栈。
6、 bootloader、内核 、根文件的关系 
启动顺序:bootloader->linux 
kernel->rootfile->app 
Bootloader 全名为启动引导程序,是第一段代
码,它主要用来初始化处理器及外设,然后调
用 Linux 内
核。Linux 内核在完成系统的初始化之后需要
挂载某个文件系统作为根文件系统
(RootFilesystem),然
后加载必要的内核模块,启动应用程序。(一
个嵌入式 Linux 系统从软件角度看可以分为四
个部分:引导
加载程序(Bootloader),Linux 内核,文件
系统,应用程序。)
7 、Bootloader 启动的两个阶段: 
Stage1:汇编语言
1)基本的硬件初始化(关闭看门狗和中断,
MMU(带操作系统),CACHE。配置系统工作时
钟)
2)为加载 stage2 准备 RAM 空间
3)拷贝内核映像和文件系统映像到 RAM 中
4)设置堆栈指针 sp 
5)跳到 stage2 的入口点
Stage2:c 语言
1)初始化本阶段要使用到的硬件设备(led 
uart 等)
2)检测系统的内存映射
3)加载内核映像和文件系统映像
4)设置内核的启动参数
嵌入式系统中广泛采用的非易失性存储器通
常是 Flash,而 Bootloader 就位于该存储器的
最前端,所以
系统上电或复位后执行的第一段程序便是
Bootloader。
8、 linux 下检查内存状态的命令 
假如一个公司服务器有很多用户,你使用 top
命令,可以看到哪个同事在使用什么命令,做
什么事情,占
用了多少 CPU。
1)查看进程:top 
2)查看内存:free 
3)cat /proc/meminfo 
4)vmstat 
9 、一个程序从开始运行到结束的完整过程
(四个过程) 
预处理(Pre-Processing)、编译(Compiling)、
汇编(Assembling)、链接(Linking)
10、什么是堆,栈,内存泄漏和内存溢出? 
栈由系统操作,程序员不可以操作。
所以内存泄漏是指堆内存的泄漏。堆内存是指
程序从堆中分配的,大小任意的(内存块的大
小可以在程
序运行期决定),使用完后必须显式释放的内
存。应用程序一般使用 malloc,new 等函数从
堆中分配到
一块内存,使用完后,程序必须负责相应的调
用 free 或 delete 释放该内存块,否则,这块
内存就不能被
再次使用。
内存溢出:你要求分配的内存超出了系统能给
你的,系统不能满足需求,于是产生溢出。
内存越界:向系统申请了一块内存,而在使用
内存时,超出了申请的范围(常见的有使用特
定大小数组
时发生内存越界)
内存溢出问题是 C 语言或者 C++ 语言所固
有的缺陷,它们既不检查数组边界,又不检查
类型可靠性
(type-safety)。众所周知,用 C/C++ 语言开
发的程序由于目标代码非常接近机器内核,因
而能够直接访
问内存和寄存器,这种特性大大提升了 C/C++ 
语言代码的性能。只要合理编码,C/C++ 应用
程序在执行
效率上必然优于其它高级语言。然而,C/C++ 
语言导致内存溢出问题的可能性也要大许多。
11、死锁的原因、条件 
产生死锁的原因主要是:
(1)
因为系统资源不足。
(2)
进程运行推进的顺序不合适。
(3)
资源分配不当等。
如果系统资源充足,进程的资源请求都能够得
到满足,死锁出现的可能性就很低,否则就会
因争夺有限
的资源而陷入死锁。其次,进程运行推进顺序
与速度不同,也可能产生死锁
这四个条件是死锁的必要条件,只要系统发生
死锁,这些条件必然成立,而只要上述条件之
一不满足,
就不会发生死锁。
(1)
互斥条件:一个资源每次只能被一个进程使用。
(2)
请求与保持条件:一个进程因请求资源而阻塞
时,对已获得的资源保持不放。
(3)
不剥夺条件:进程已获得的资源,在末使用完
之前,不能强行剥夺。
(4)
循环等待条件:若干进程之间形成一种头尾相
接的循环等待资源关系。
12、硬链接与软链接 
链接操作实际上是给系统中已有的某个文件
指定另外一个可用于访问它的名称。对于这个
新的文件名,
我们可以为之指定不同的访问权限,以控制对
信息的共享和安全性的问题。如果链接指向目
录,用户就
可以利用该链接直接进入被链接的目录而不
用打一大堆的路径名。而且,即使我们删除这
个链接,也不会破坏原来的目录。
1>硬链接
硬链接只能引用同一文件系统中的文件。它引
用的是文件在文件系统中的物理索引(也称为
inode)。当您移动或删除原始文件时,硬链接
不会被破坏,因为它所引用的是文件的物理数
据而不是文件在文件结构中的位置。硬链接的
文件不需要用户有访问原始文件的权限,也不
会显示原始文件的位置,这样有助于文件的安
全。如果您删除的文件有相应的硬链接,那么
这个文件依然会保留,直到所有对它的引用都
被 删除。2>软链接(符号链接)
软连接,其实就是新建立一个文件,这个文件
就是专门用来指向别的文件的(那就和
windows 下的快捷 方式的那个文件有很接近
的意味)。软连接产生的是一个新的文件,但
这个文件的作用就是专门指向某个文件的,删
了这个软连接文件,那就等于不需要这个连接,
和原来的存在的实体原文件没有任何关系,但
删除原来的文件,则相应的软连接不可用。
13、计算机中,32bit 与 64bit 有什么区别 
64bit 计算主要有两大优点:可以进行更大范
围的整数运算;可以支持更大的内存。
64 位操作系统下的虚拟内存空间大小:地址空
间大小不是2^32,也不是2^64,而一般是2^48。
因为并不需要 2^64 那么大的寻址空间,过大
的空间只会造成资源的浪费。所以 64 位 Linux
一般使用 48 位表示虚拟空间地址,40 位标识
物理地址。
14、中断和异常的区别 
内中断:同步中断(异常)是由 cpu 内部的电
信号产生的中断,其特点为当前执行的指令结
束后才转而产生中断,由于有 cpu 主动产生,
其执行点必然是可控的。外中断:异步中断是
由 cpu 的外设产生的电信号引起的中断,其发
生的时间点不可预期。
15、中断怎么发生,中断处理流程 
请求中断→响应中断→关闭中断→保留断点
→中断源识别→保护现场→中断服务子程序
→恢复现场→中断返回。
16、 Linux 操作系统挂起、休眠、关机相关
命令 
关机命令有 halt, init 0, poweroff ,
shutdown -h 时间,其中 shutdown 是最安全

重启命令有 reboot,init 6,,shutdow -r 时
间 在 linux 命令中 reboot 是重新启动,
shutdown -r now 是立即停止然后重新启动
17、说一个 linux 下编译优化选项: 
加:-o 
18、在有数据 cache 情况下,DMA 数据链路为: 
外设-DMA-DDR-cache-CPU19、linux 命令 
1、改变文件属性的命令:chmod (chmod 777 
/etc/squid 运行命令后,squid 文件夹(目录)
的权限
就被修改为 777(可读可写可执行)

2、查找文件中匹配字符串的命令:grep 
3、查找当前目录:pwd 
4、删除目录:rm -rf 目录名
5、删除文件:rm 文件名
6、创建目录(文件夹):mkdir 
7、创建文件:touch 
8、vi 和 vim 文件名也可以创建
9、解压:tar -xzvf 压缩包
打包:tar -cvzf 目录(文件夹)
10、查看进程对应的端口号
20、硬实时系统和软实时系统 
软实时系统:
Windows、Linux 系统通常为软实时,当然有补
丁可以将内核做成硬实时的系统,不过商用没
有这么做的。
硬实时系统:
对时间要求很高,限定时间内不管做没做完必
须返回。
VxWorks,uCOS,FreeRTOS,WinCE,RT-thread
等实时系统;
21、MMU 基础 
现代操作系统普遍采用虚拟内存管理
(Virtual Memory Management)
机制,这需要 MMU( Memory Management Unit,
内存管理单元)的支持。有些嵌入式处理器没
有 MMU,则不能运行依赖于虚拟内存管理的操
作系统。也就是说:操作系统可以分成两类,
用MMU的、不用MMU的。用MMU的是:Windows、
MacOS、Linux、Android;不用 MMU 的是:
FreeRTOS、VxWorks、
UCOS……
与此相对应的:CPU 也可以分成两类,带 MMU
的、不带 MMU 的。带 MMU 的是:Cortex-A 系列、
ARM9、ARM11 系列;
不带 MMU 的是:Cortex-M 系列……(STM32 是
M 系列,没有 MMU,不能运行 Linux,只能运行
一些 UCOS、FreeRTOS 等等)。
1、先查看进程 pid 
ps -ef | grep 进程名
2、通过 pid 查看占用端口
netstat -nap | grep 进程 pid 




5MMU 就是负责虚拟地址(virtual address)
转化成物理地址(physical address),转换
过程比较复
杂,可以自行百度。
第六章、单片机常见面试题 
1、ROM 与 RAM 
这一点我另一篇文章讲解过,这里放链接:
ROM 与 RAM 的区别
2、 IO 口工作方式(学过 STM32 的人应该很熟
悉) 
上拉输入、下拉输入、推挽输出、开漏输出。
3、请说明总线接口 USRT、I2C、USB 的异同点 
(串/并、速度、全/半双工、总线拓扑等)
4、IIC 协议时序图 
必须会画出来,我面试被问到过,让我画,我
画了个大概。
IIC 协议有两根线,一根 SCL 时钟线,一根 SDA
数据线,如图可以看到开始信号和结束信号的
电平状态。
开始后,因为 IIC 总线可以挂在很多设备(不
超过 8 个),所以先发送一个设备地址,选中
这个设备,设备地址最后一位代表了是写还是
读。选中设备后,再发送寄存器地址,代表选
中某个寄存器,再开始传输数据。
八位设备地址=7 位从机地址+读/写地址,
再给地址添加一个方向位位用来表示接下来
数据传输的方向,
0 表示主设备向从设备(write)写数据,
1 表示主设备向从设备(read)读数据开始信号:
SCL 为高电平时,SDA 由高电平向低电平跳变,
开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向
高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数
据后,向发送数据的 IC 发出特定的低电平脉
冲,表示已收到数据。CPU 向受控单元发出一
个信号后,等待受控单元发出一个应答信号,
CPU 接收到应答信号后,根据实际情况作出是
否继续传递信号的判断。若未收到应答信号,
由判断为受控单元出现故障。
IIC 信号在数据传输过程中,当 SCL=1 高电平
时,数据线 SDA 必须保持稳定状态,不允许有
电平跳变,只有在时钟线上的信号为低电平期
间,数据线上的高电平或低电平状态才允许变
化。SCL=1 时 数据线 SDA 的任何电平变换会看
做是总线的起始信号或者停止信号。
IIC 我也有一篇文章有讲解,请看链接:
IIC 总线最多可以挂多少个设备
5、单片机的 SP 指针始终指向 
栈顶
6、IIC 总线在传送数据过程中共有三种类型信
号: 
它们分别是:开始信号、结束信号和应答信号。
7、FIQ 中断向量入口地址: 
FIQ 和 IRQ 是两种不同类型的中断,ARM 为了
支持这两种不同的中断,提供了对应的叫做
FIQ和IRQ处理器模式(ARM有7种处理模式)。
FIQ 的中断向量地址在 0x0000001C,而 IRQ 的
在 0x00000018。
8、SPI 四种模式,简述其中一种模式,画出时
序图 
在芯片资料上极性和相位一般表示为 CPOL
(Clock POLarity)和 CPHA(Clock PHAse), 极
性和相位组合成 4 种工作模式。spi 四种模式
SPI 的相位(CPHA)和极性(CPOL)分别可以为 0
或 1,对应的 4 种组合构成了 SPI 的 4 种模式
(mode) 
Mode 0 CPOL=0, CPHA=0 
Mode 1 CPOL=0, CPHA=1 
Mode 2 CPOL=1, CPHA=0 
Mode 3 CPOL=1, CPHA=1 
时钟极性 CPOL: 即 SPI 空闲时,时钟信号 SCLK
的电平(1:空闲时高电平; 0:空闲时低电平)
时钟相位
CPHA: 即 SPI 在 SCLK 第几个边沿开始采样(0:
第一个边沿开始; 1:第二个边沿开始)
sd 卡的 spi 常用的是 mode 0 和 mode 3,这两
种模式的相同的地方是都在时钟上升沿采样
传输数据,区别这两种方式的简单方法就是看
空闲时,时钟的电平状态,低电平为 mode 0 ,
高电平为 mode 3。
80 memcpy 函数用于把资源内存(src 所指向
的内存区域)拷贝到目标内存(dest 所指向的
内存区域);
有一个 size 变量控制拷贝的字节数;
5、写一个双向链表的插入删除 
6、解释一下动态内存和静态内存 
回答:
a) 静态内存分配在编译时完成,不占用 CPU
资源; 动态内存分配在运行时,分配与释放都
占用 CPU 资源。
b) 静态内存在栈(stack)上分配; 动态内存
在堆(heap)上分配。
c) 动态内存分配需要指针和引用类型支持,
静态不需要。
d) 静态内存分配是按计划分配,由编译器负
责; 动态内存分配是按需分配,由程序员负责。
C++面试题全集 
11. 面向对象的程序设计思想是什么? 
答:把数据结构和对数据结构进行操作的方法
封装形成一个个的对象。
2. 什么是类? 
答:把一些具有共性的对象归类后形成一个集
合,也就是所谓的类。
3. 对象都具有的两方面特征是什么?分别是
什么含义? 
答:对象都具有的特征是:静态特征和动态特
征。
静态特征是指能描述对象的一些属性(成员变
量),动态特征是指对象表现出来的行为(成
员函数)
4. 在头文件中进行类的声明,在对应的实现
文件中进行类的定义有什么意义? 
答:这样可以提高编译效率,因为分开的话只
需要编译一次生成对应的.obj 文件后,再次应
用该类的地方,这个类就不会被再次编译,从
而大大的提高了编译效率。
5. 在类的内部定义成员函数的函数体,这种
函数会具备那种属性? 
答:这种函数会自动为内联函数,这种函数在
函数调用的地方在编译阶段都会进行代码替
换。
6. 成员函数通过什么来区分不同对象的成员
数据?为什么它能够区分? 
答:通过 this 指针指向对象的首地址来区分
的。
7. C++编译器自动为类产生的四个缺省函数
是什么? 
答:默认构造函数,拷贝构造函数,析构函数,
赋值函数。
8. 拷贝构造函数在哪几种情况下会被调用? 
答:
1.当类的一个对象去初始化该类的另一个对
象时;
2.如果函数的形参是类的对象,调用函数进行
形参和实参结合时;
3.如果函数的返回值是类对象,函数调用完成
返回时。
9. 构造函数与普通函数相比在形式上有什么
不同?(构造函数的作用,它的声明形式来分
析) 
答:构造函数是类的一种特殊成员函数,一般
情况下,它是专门用来初始化对象成员变量的。
构造函数的名字必须与类名相同,它不具有任
何类型,不返回任何值。
10. 什么时候必须重写拷贝构造函数? 
答:当构造函数涉及到动态存储分配空间时,
要自己写拷贝构造函数,并且要深拷贝。
11. 构造函数的调用顺序是什么? 
答:1.先调用基类构造函数
 2.按声明顺序初始化数据成员
3.最后调用自己的构造函数。
12. 哪几种情况必须用到初始化成员列表? 
答:类的成员是常量成员初始化;
类的成员是对象成员初始化,而该对象没有无
参构造函数。
类的成员为引用时。
13. 什么是常对象? 
答:常对象是指在任何场合都不能对其成员的
值进行修改的对象。
14. 静态函数存在的意义? 
答:静态私有成员在类外不能被访问,可通过
类的静态成员函数来访问;
当类的构造函数是私有的时,不像普通类那样
实例化自己,只能通过静态成员函数来调用构
造函数。
15. 在类外有什么办法可以访问类的非公有成
员? 
答:友元,继承,公有成员函数。
16. 什么叫抽象类? 
答:不用来定义对象而只作为一种基本类型用
作继承的类。
 
17. 运算符重载的意义? 
答:为了对用户自定义数据类型的数据的操作
与内定义的数据类型的数据的操作形式一致。
18. 不允许重载的 5 个运算符是哪些? 
答:
1. .*(成员指针访问运算符号)
2. ::域运算符
3. Sizeof 长度运算符号
4. ?:条件运算符号
5. .(成员访问符)
19. 运算符重载的三种方式? 
答:普通函数,友元函数,类成员函数。
20. 流运算符为什么不能通过类的成员函数重
载?一般怎么解决? 
答:因为通过类的成员函数重载必须是运算符
的第一个是自己,而对流运算的重载要求第一
个参数是流对象。所以一般通过友元来解决。
21. 赋值运算符和拷贝构造函数的区别与联
系? 
答:相同点:都是将一个对象 copy 到另一个
中去。
不同点:拷贝构造函数涉及到要新建立一个对
象。
22. 在哪种情况下要调用该类的析构函数? 
答:对象生命周期结束时。
23. 对象间是怎样实现数据的共享的? 
答:通过类的静态成员变量来实现对象间的数
据共享。静态成员变量占有自己独立的空间不
为某个对象所私有。
24. 友元关系有什么特性? 
答:单向的,非传递的,不能继承的。
25. 对对象成员进行初始化的次序是什么? 
答:它的次序完全不受它们在初始化表中次序
的影响,只有成员对象在类中声明的次序来决
定的。
26. 类和对象之间的关系是什么? 
答:类是对象的抽象,对象是类的实例。
27. 对类的成员的访问属性有什么? 
答:public,protected,private。
28.const char *p 和 char * const p; 的
区别 
答:
如果 const 位于星号的左侧,则 const 就是用
来修饰指针所指向的变量,即指针指向为常量;
如果 const 位于星号的右侧,const 就是修饰
指针本身,即指针本身是常量。
29. 是不是一个父类写了一个 virtual 函数,
如果子类覆盖它的函数不加 virtual ,也能实
现多态? 
答:
virtual 修饰符会被隐形继承的。
virtual 可加可不加,子类覆盖它的函数不加
virtual ,也能实现多态。
30. 函数重载是什么意思?它与虚函数的概念
有什么区别? 
答:函数重载是一个同名函数完成不同的功能,
编译系统在编译阶段通过函数参数个数、参数
类型不同,函数的返回值来区分该调用哪一个
函数,即实现的是静态的多态性。但是记住:
不能仅仅通过函数返回值不同来实现函数重
载。而虚函数实现的是在基类中通过使用关键
字 virtual 来申明一个函数为虚函数,含义就
是该函数的功能可能在将来的派生类中定义
或者在基类的基础之上进行扩展,系统只能在
运行阶段才能动态决定该调用哪一个函数,所
以实现的是动态的多态性。它体现的是一个纵
向的概念,也即在基类和派生类间实现。
31. 构造函数和析构函数是否可以被重载,为
什么? 
答:构造函数可以被重载,析构函数不可以被
重载。因为构造函数可以有多个且可以带参数,
而析构函数只能有一个,且不能带参数。
32. 如何定义和实现一个类的成员函数为回调
函数? 
答:
所谓的回调函数,就是预先在系统的对函数进
行注册,让系统知道这个函数的存在,以后,
当某个事件发生时,再调用这个函数对事件进
行响应。
定 义 一 个 类 的 成 员 函 数 时 在 该 函 数 前 加
CALLBACK 即将其定义为回调函数,函数的实现
和普通成员函数没有区别
33. 虚函数是怎么实现的? 
答:简单说来使用了虚函数表.
34. 抽象类不会产生实例,所以不需要有构造
函数。 错 
 
35. 从一个模板类可以派生新的模板类,也可
以派生非模板类。 对 
36. main 函数执行以前,还会执行什么代码? 
答案:全局对象的构造函数会在 main 函数之
前执行。
37. 当一个类A 中没有生命任何成员变量与成
员函数,这时 sizeof(A)的值是多少,如果不是
零,请解释一下编译器为什么没有让它为零。
(Autodesk) 
答案:肯定不是零。举个反例,如果是零的话,
声明一个 class A[10]对象数组,而每一个对
象 占 用 的 空 间 是 零 , 这 时 就 没 办 法 区 分
A[0],A[1]…了。
38. delete 与 delete []区别: 
答:delete 只会调用一次析构函数,而 delete[]
会调用每一个成员的析构函数。
39.子类析构时要调用父类的析构函数吗? 
答:会调用。析构函数调用的次序是先派生类
的析构后基类的析构,也就是说在基类的的析
构调用的时候,派生类的信息已经全部销毁了
40. 继承的优缺点。 
1、类继承是在编译时刻静态定义的,且可直
接使用,
2、类继承可以较方便地改变父类的实现。
缺点:
1、因为继承在编译时刻就定义了,所以无法
在运行时刻改变从父类继承的实现
2、父类通常至少定义了子类的部分行为,父
类的任何改变都可能影响子类的行为
3、如果继承下来的实现不适合解决新的问题,
则父类必须重写或被其他更适合的类替换。这
种依赖关系限制了灵活性并最终限制了复用
性。
41. 解释堆和栈的区别。 
答:栈区(stack)— 由编译器自动分配释放 ,
存放函数的参数值,局部变量的值等。
堆(heap)一般由程序员分配释放, 若程序
员不释放,程序结束时可能由 OS 回收 。
42. 一个类的构造函数和析构函数什么时候被
调用,是否需要手工调用? 
答:构造函数在创建类对象的时候被自动调用,
析构函数在类对象生命期结束时,由系统自动
调用。
43. 何时需要预编译: 
答:总是使用不经常改动的大型代码体。
程序由多个模块组成,所有模块都使用一组标
准的包含文件和相同的编译选项。在这种情况
下,可以将所有包含文件预编译为一个预编译
头。
44. 多态的作用? 
答:主要是两个:
1. 隐藏实现细节,使得代码能够模块化;扩
展代码模块,实现代码重用;
2. 接口重用:为了类在继承和派生的时候,
保证使用家族中任一类的实例的某一属性时
的正确调用
45. 虚拟函数与普通成员函数的区别?内联函
数和构造函数能否为虚拟函数? 
答案:区别:虚拟函数有 virtual 关键字,有
虚拟指针和虚函数表,虚拟指针就是虚拟函数
的接口,而普通成员函数没有。内联函数和构
造函数不能为虚拟函数。
46. 构造函数和析构函数的调用顺序? 析构函
数为什么要虚拟? 
答案:构造函数的调用顺序:基类构造函数—
对象成员构造函数—派生类构造函数;析构函
数的调用顺序与构造函数相反。析构函数虚拟
是为了防止析构不彻底,造成内存的泄漏。
47. C++中类型为 private 的成员变量可以由
哪些函数访问? 
答:只可以由本类中的成员函数和友元函数访

48. 请说出类中 private,protect,public
三种访问限制类型的区别 
答:private 是私有类型,只有本类中的成员
函数访问;protect 是保护型的,本类和继承类
可以访问;public 是公有类型,任何类都可以
访问.
49. 类中成员变量怎么进行初始化? 
答:可以通过构造函数的初始化列表或构造函
数的函数体实现。
50. 在什么时候需要使用“常引用”? 
答:如果既要利用引用提高程序的效率,又要
保护传递给函数的数据不在函数中被改变,就
应使用常引用。
51. 引用与指针有什么区别? 
答 、1) 引用必须被初始化,指针不必。
2) 引用初始化以后不能被改变,指针可以改
变所指的对象。
3) 不存在指向空值的引用,但是存在指向空
值的指针。
52. 描述实时系统的基本特性 
答 、在特定时间内完成特定的任务,实时性
与可靠性。
54.全局变量和局部变量在内存中是否有区别?
如果有,是什么区别? 
答 、全局变量储存在静态数据区,局部变量
在堆栈中。
55. 堆栈溢出一般是由什么原因导致的? 
答 、没有回收垃圾资源
56. 什么函数不能声明为虚函数? 
答 构造函数(constructor)
57. IP 地址的编码分为哪俩部分? 
答 IP 地址由两部分组成,网络号和主机号。
58. 不能做 switch()的参数类型是: 
答 、switch 的参数不能为实型。
59. 如何引用一个已经定义过的全局变量? 
答 、可以用引用头文件的方式,也可以用
extern 关键字,如果用引用头文件方式来引用
某个在头文件中声明的全局变理,假定你将那
个变写错了,那么在编译期间会报错,如果你
用 extern 方式引用时,假定你犯了同样的错
误,那么在编译期间不会报错,而在连接期间
报错
60. 对于一个频繁使用的短小函数,在 C 语言
中应用什么实现,在 C++中应用什么实现? 
答 、c 用宏定义,c++用 inline
61. C++是不是类型安全的? 
答案:不是。两个不同类型的指针之间可以强
制转换(用 reinterpret cast) 
62. 当一个类A 中没有生命任何成员变量与成
员函数,这时 sizeof(A)的值是多少,请解释一
下编译器为什么没有让它为零。 
答案:为 1。举个反例,如果是零的话,声明
一个 class A[10]对象数组,而每一个对象占
用 的 空 间 是 零 , 这 时 就 没 办 法 区 分
A[0],A[1]…了。
63. 简述数组与指针的区别? 
答:数组要么在静态存储区被创建(如全局数
组),要么在栈上被创建。指针可以随时指向
任意类型的内存块。
(1)修改内容上的区别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意 p 指向常量字
符串
p[0] = ‘X’; // 编译器不能发现该错误,
运行时错误
(2) 用运算符 sizeof 可以计算出数组的容量
(字节数)。sizeof(p),p 为指针得到的是一
个指针变量的字节数,而不是 p 所指的内存容
量。
64. C++函数中值的传递方式 
答:有三种方式:值传递、指针传递、引用传

65. 内存的分配方式 
答:分配方式有三种,
1、 静态存储区,是在程序编译时就已经分配
好的,在整个运行期间都存在,如全局变量、
常量。
2、 栈上分配,函数内的局部变量就是从这分
配的,但分配的内存容易有限。
3、 堆上分配,也称动态分配,如我们用
new,malloc 分配内存,用 delete,free 来释放
的内存。
66. extern“C”有什么作用? 
答:Extern “C”是由C++提供的一个连接
交换指定符号,用于告诉C++这段代码是C
函数。这是因为 C++编译后库中函数名会变得
很长,与 C 生成的不一致,造成C++不能直
接调用 C 函数,加上 extren “c”后,C++就
能直接调用 C 函数了。
Extern “C”主要使用正规 DLL 函数的引用和
导出 和 在 C++包含 C 函数或 C 头文件时使用。
使用时在前面加上 extern “c”关键字即可。
可以用一句话概括 extern “C”这个声明的真
实目的:实现C++与C及其它语言的混合编程。
67. 用什么函数开启新进程、线程。 
答案:
线程:CreateThread/AfxBeginThread 等
进程:CreateProcess 等
68. SendMessage 和 PostMessage 有什么区别 
答案:SendMessage 是阻塞的,等消息被处理
后,代码才能走到 SendMessage 的下一行。
PostMessage 是非阻塞的,不管消息是否已被
处理,代码马上走到 PostMessage 的下一行。
69. CMemoryState 主要功能是什么
答案:查看内存使用情况,解决内存泄露问题。
70. #include <filename.h> 和 #include 
“filename.h” 有什么区别? 
答:对于#include <filename.h> ,编译器从
标准库路径开始搜索 filename.h
对于#include “filename.h” ,编译器从用
户的工作路径开始搜索 filename.h
71. 处理器标识#error 的目的是什么? 
答:编译时输出一条错误信息,并中止继续编
译。
72. #if!defined(AFX_…_HADE_H) 
#define(AFX_…_HADE_H) 
…… 
#endif 作用? 
答:防止该头文件被重复引用。
73. 在定义一个宏的时候要注意什么? 
答:定义部分的每个形参和整个表达式都必须
用括号括起来,以避免不可预料的错误发生
74. 数组在做函数实参的时候会转变为什么类
型? 
答:数组在做实参时会变成指针类型。
75. 系统会自动打开和关闭的 3 个标准的文件
是? 
(1) 标准输入----键盘---stdin
(2) 标准输出----显示器---stdout
(3) 标准出错输出----显示器---stderr
76. .在 Win32 下 char, int, float, double
各占多少位? 
(1) Char 占用 8 位
(2) Int 占用 32 位
(3) Float 占用 32 位
(4) Double 占用 64 位
77. strcpy()和 memcpy()的区别? 
答:strcpy()和 memcpy()都可以用来拷贝字符
串,strcpy()拷贝以’\0’结束,但 memcpy()
必须指定拷贝的长度。
78. 说明define和const在语法和含义上有什
么不同? 
答:(1) #define 是 C 语法中定义符号变量
的方法,符号常量只是用来表达一个值,在编
译阶段符号就被值替换了,它没有类型;
(2) Const 是 C++语法中定义常变量的方法,常
变量具有变量特性,它具有类型,内存中存在
以它命名的存储单元,可以用 sizeof 测出长
度。
79. 说出字符常量和字符串常量的区别,并使
用运算符 sizeof 计算有什么不用? 
答:字符常量是指单个字符,字符串常量以‘\0’
结束,使用运算符 sizeof 计算多占一字节的
存储空间。
80. 简述全局变量的优缺点? 
答:全局变量也称为外部变量,它是在函数外
部定义的变量,它属于一个源程序文件,它保
存上一次被修改后的值,便于数据共享,但不
方便管理,易引起意想不到的错误。
81. 总结 static 的应用和作用? 
答:(1)函数体内 static 变量的作用范围为
该函数体,不同于 auto 变量,该变量的内存
只被分配一次,因此其值在下次调用时仍维持
上次的值;
(2)在模块内的 static 全局变量可以被模块
内所用函数访问,但不能被模块外其它函数访
问;
(3)在模块内的 static 函数只可被这一模块
内的其它函数调用,这个函数的使用范围被限
制在声明它的模块内;
(4)在类中的 static 成员变量属于整个类所
拥有,对类的所有对象只有一份拷贝;
(5)在类中的 static 成员函数属于整个类所
拥有,这个函数不接收 this 指针,因而只能
访问类的 static 成员变量。
82. 总结 const 的应用和作用? 
答:(1)欲阻止一个变量被改变,可以使用
const 关键字。在定义该 const 变量时,通常
需要对它进行初始化,因为以后就没有机会再
去改变它了;
(2)对指针来说,可以指定指针本身为 const,
也可以指定指针所指的数据为 const,或二者
同时指定为 const;
(3)在一个函数声明中,const 可以修饰形参,
表明它是一个输入参数,在函数内部不能改变
其值;
(4)对于类的成员函数,若指定其为 const
类型,则表明其是一个常函数,不能修改类的
成员变量;
(5)对于类的成员函数,有时候必须指定其
返回值为 const 类型,以使得其返回值不为“左
值”。
83. 什么是指针?谈谈你对指针的理解? 
答:指针是一个变量,该变量专门存放内存地
址;
指针变量的类型取决于其指向的数据类型,在
所指数据类型前加*
指针变量的特点是它可以访问所指向的内存。
84. 什么是常指针,什么是指向常变量的指
针? 
答:常指针的含义是该指针所指向的地址不能
变,但该地址所指向的内容可以变化,使用常
指针可以保证我们的指针不能指向其它的变
量,
指向常变量的指针是指该指针的变量本身的
地址可以变化,可以指向其它的变量,但是它
所指的内容不可以被修改。指向长变量的指针
定义,
85. 函数指针和指针函数的区别? 
答:函数指针是指向一个函数入口的指针;
指针函数是函数的返回值是一个指针类型。
87. 简述 Debug 版本和 Release 版本的区别? 
答:Debug 版本是调试版本,Release 版本是
发布给用户的最终非调试的版本,
88. 指针的几种典型应用情况? 
答:
int *p[n];-----指针数组,每个元素均为指
向整型数据的指针。
int (*)p[n];---p 为指向一维数组的指针,这
个一维数组有 n 个整型数据。
int *p();------函数带回指针,指针指向返
回的值。
int (*)p();----p 为指向函数的指针。
89. static 函数与普通函数有什么区别? 
答:static 函数在内存中只有一份,普通函数
在每个被调用中维持一份拷贝
90. struct(结构) 和 union(联合)的区别? 
答:1. 结构和联合都是由多个不同的数据类
型成员组成, 但在任何同一时刻, 联合中只
存放了一个被选中的成员(所有成员共用一块
地址空间), 而结构的所有成员都存在(不同
成员的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成
员重写, 原来成员的值就不存在了, 而对于
结构的不同成员赋值是互不影响的。
91. class 和 struct 的区别? 
答:struct 的成员默认是公有的,而类的成
员默认是私有的。
92. 简述枚举类型? 
答:枚举方便一次定义一组常量,使用起来很
方便;
93. assert()的作用? 
答:ASSERT()是一个调试程序时经常使用的宏,
在程序运行时它计算括号内的表达式,如果表
达式为 FALSE (0), 程序将报告错误,并终止
执行。如果表达式不为 0,则继续执行后面的
语句。这个宏通常原来判断程序中是否出现了
明显非法的数据,如果出现了终止程序以免导
致严重后果,同时也便于查找错误。
94. 局部变量和全局变量是否可以同名? 
答:能。局部会屏蔽全局。要用全局变量,需
要使用"::"(域运算符)。
95. 程序的局部变量存在于(堆栈)中,全局
变量存在于(静态区 )中,动态申请数据存
在于( 堆)中。 
96. 在什么时候使用常引用? 
答:如果既要利用引用提高程序的效率,又要
保护传递给函数的数据不在函数中被改变,就
应使用常引用。
97. 类的声明和实现的分开的好处? 
答:1. 起保护作用;
2. 提高编译的效率。
98. windows 消息系统由哪几部分构成? 
答:由一下 3 部分组成:
1. 消息队列:操作系统负责为进程维护一个
消息队列,程序运行时不断从该消息队列中获
取消息、处理消息;
2. 消息循环:应用程序通过消息循环不断获
取消息、处理消息。
3. 消息处理:消息循环负责将消息派发到相
关的窗口上使用关联的窗口过程函数进行处
理。
99. 什么是消息映射? 
答:消息映射就是让程序员指定 MFC 类(有消
息处理能力的类)处理某个消息。然后由程序
员完成对该处理函数的编写,以实现消息处理
功能。
100. 什么是 UDP 和 TCP 的区别是什么? 
答:TCP 的全称为传输控制协议。这种协议可
以提供面向连接的、可靠的、点到点的通信。
UDP 全称为用户报文协议,它可以提供非连接
的不可靠的点到多点的通信。用TCP还是UDP,
那要看你的程序注重哪一个方面?可靠还是
快速?
101. winsock 建立连接的主要实现步骤? 
答: 
服务器端:socket()建立套接字,绑定(bind)
并监听(listen),用 accept()等待客户端
连接, accept()发现有客户端连接,建立一
个新的套接字,自身重新开始等待连接。该新
产生的套接字使用 send()和 recv()写读数
据,直至数据交换完毕,closesocket()关闭
套接字。 
客户端:socket()建立套接字,连接(connect)
服务器,连接上后使用 send()和 recv(),
在套接字上写读数据,直至数据交换完毕,
closesocket()关闭套接字。
102. 进程间主要的通讯方式? 
答:信号量,管道,消息,共享内存
103. 构成 Win32 API 函数的三个动态链接库
是什么? 
答:内核库,用户界面管理库,图形设备界面
库。
104. 创建一个窗口的步骤是? 
答:填充一个窗口类结构->注册这个窗口类->
然后再创建窗口->显示窗口->更新窗口。
105. 模态对话框和非模态对话框有什么区
别? 
答:1.调用规则不同:前者是用 DoModal()调
用,后者通过属性和 ShowWindow()来显示。
2.模态对话框在没有关闭前用户不能进行其
他操作,而非模态对话框可以。
3.非模态对话框创建时必须编写自己的共有
构造函数,还要调用 Create()函数。
106. 从 EDIT 框中取出数据给关联的变量,已
经把关联的变量的数据显示在 EDIT 框上的函
数是什么? 
答 : UpdateData(TRUE), 
Updatedata(FALSE).
107. 简单介绍 GDI? 
答: GDI 是 Graphics Device Interface 的
缩写,译为:图形设备接口;是一个在 Windows
应用程序中执行与设备无关的函数库,这些函
数在不同的输出设备上产生图形以及文字输
出。
108. windows 消息分为几类?并对各类做简
单描述。 
答:
1. 窗 口 消 息 : 与 窗 口 相 关 的 消 息 , 除
WM_COMMAND 之外的所有以 WM_开头的消息;
2. 命 令 消 息 ; 用 于 处 理 用 户 请 求 , 以
WM_COMMAND 表示的消息;
3.控件通知消息:统一由 WM_NOTIFT 表示,
4.用户自定义消息。
109. 如何自定义消息? 
答:使用 WM_USER 和 WM_APP 两个宏来自定义
消息,
110. 简述 Visual C++ 、Win32 API 和 MFC 之
间的关系? 
答:(1) Visual C+是一个以 C++程序设计
语言为基础的、集成的、可视化的编程环境;
(2) Win32 API是32位Windows操作系以C/C++
形式提供的一组应用程序接口;
(3) MFC 是对 Win32 API 的封装,简化了开发
过程。
111.怎样消除多重继承中的二义性? 
答: 1.成员限定符 2.虚基类
112 什么叫静态关联,什么叫动态关联 
答:在多态中,如果程序在编译阶段就能确定
实际执行动作,则称静态关联,
如果等到程序运行才能确定叫动态关联。
113 多态的两个必要条件 
答:1.一个基类的指针或引用指向一个派生类
对象 2.虚函数
114.什么叫智能指针? 
答:当一个类中,存在一个指向另一个类对象
的指针时,对指针运算符进行重载,那么当前
类对象可以通过指针像调用自身成员一样调
用另一个类的成员。
115.什么时候需要用虚析构函数? 
答:当基类指针指向用 new 运算符生成的派生
类对象时,delete 基类指针时,派生类部分没
有释放掉而造成释放不彻底现象,需要虚析构
函数。 补充:虚函数就是让派生类调用基类
的虚函数。
116. MFC 中,大部分类是从哪个类继承而来? 
答:CObject
117.什么是平衡二叉树? 
答:左右子树都是平衡二叉树,而且左右子树
的深度差值的约对值不大于 1。
118.语句 for( ;1 ;)有什么问题?它是什么
意思? 
答:无限循环,和 while(1)相同。
119.派生新类的过程要经历三个步骤 
答:1.吸收基类成员 2.改造基类成员 3.
添加新成员
121. TCP/IP 建立连接的过程 
答:在 TCP/IP 协议中,TCP 协议提供可靠的连
接服务,采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送连接请
求到服务器,并进入 SYN_SEND 状态,等待服
务器确认;
第二次握手:服务器收到客户端连接请求,向
客户端发送允许连接应答,此时服务器进入
SYN_RECV 状态;
第三次握手:客户端收到服务器的允许连接应
答,向服务器发送确认,客户端和服务器进入
通信状态,完成三次握手
122. memset ,memcpy 的区别 
答:memset 用来对一段内存空间全部设置为某
个字符,一般用在对定义的字符串进行初始化
为'\0'。
memcpy 用来做内存拷贝,你可以拿它拷贝任何
数据类型的对象,可以指定拷贝的数据长度;
123. 在 C++ 程序中调用被 C 编译器编译后
的函数,为什么要加 extern “C”? 
答:C++语言支持函数重载,C 语言不支持函
数重载。函数被 C++编译后在库中的名字
与 C 语言的不同。假设某个函数的原型为:
void foo(int x, int y);该函数被 C 编译器
编译后在库中的名字为_foo , 而 C++编译器
则会产生像_foo_int_int 之类的名字。C++提
供了 C 连接交换指定符号 extern“C”来解决
名字匹配问题。
124 怎样定义一个纯虚函数?含有纯虚函数的
类称为什么? 
答:在虚函数的后面加=0,含有虚函数的类称
为抽象类。
125.已知 strcpy 函数的原型是: 
char * strcpy(char * strDest,const char * 
strSrc);不调用库函数,实现 strcpy 函数。
其中,strSrc 是原字符串,strDest 是目标字
符串 。 
答案: 
char *strcpy(char *strDest, const char 
*strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ; //指针 tempptr
指向 strDest 的地址;
while( (*strDest++ = *strSrc++) != ‘\\0’) 
//注意:别忘了转义符;
;
return tempptr ; //返回指针向的地址;
}
 
126.已知类 String 的原型为: 
class String 

public: 
String(const char *str = NULL); // 普通
构造函数 
String(const String &other); // 拷贝
构造函数 
~ String(void); // 析 构 函
数 
String & operate =(const String &other); 
// 赋值函数 
private: 
char *m_data; // 用 于
保存字符串 
}; 
请编写 String 的上述 4 个函数。 
答案:
// 普通构造函数
String::String(const char *str)
{
if ( str == NULL ) //strlen 在
参数为 NULL 时会抛异常才会有这步判断
{
m_data = new char[1] ;
m_data[0] = '' ;
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data,str);
}
}
//拷贝构造函数
String::String(const String &other)
{
m_data = new char[strlen(other.m_data) 
+ 1];
strcpy(m_data,other.m_data);
}
//赋值函数(重载运算符) 
String & String::operator =(const String 
&other)
{
if ( this == &other)
return *this ;
delete []m_data;
m_data = new char[strlen(other.m_data) 
+ 1];
strcpy(m_data,other.m_data);
return *this ;
}
//析构函数
String::~ String(void)
{
delete []m_data ;
}
127.类成员函数的重载、覆盖和隐藏的区别 
答案:
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字。
“隐藏”是指派生类的函数屏蔽了与其同名的
基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,
但是参数不同。此时,不论有无 virtual 关键
字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,
并且参数也相同,但是基类函数没有 virtual 
关键字。此时,基类的函数被隐藏(注意别与
覆盖混淆)
128.如何打印出当前源文件的文件名以及源
文件的当前行号? 
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏
并不是在某个文件中定义的,而是由编译器定
义的。
129.文件中有一组整数,要求排序后输出到
另一个文件中 
答案:
void Order(vector<int> &data) //冒泡排

{
int count = data.size() ;
int tag = false ;
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i -
1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c:\\data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close();
Order(data);
ofstream out("c:\\result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close();
}
130.一个链表的结点结构 
struct Node 

int data ; 
Node *next ; 
}; 
typedef struct Node Node ; 
已知链表的头结点 head,写一个函数把这个链
表逆序 ( Intel) 
答案:
Node * ReverseList(Node *head) //链表逆

{
if ( head == NULL || head->next == 
NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
131. 一个链表的结点结构 
struct Node 

int data ; 
Node *next ; 
}; 
typedef struct Node Node ; 
已知两个链表 head1 和 head2 各自有序,请
把它们合并成一个链表依然有序。 
答案:
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
132.已知两个链表 head1 和 head2 各自有序,
请把它们合并成一个链表依然有序,这次要求
用递归方法进行。 ( Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Node 
*head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = 
MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = 
MergeRecursive(head1,head2->next);
}
return head ;
}
133.分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)
{
cout<<"constructed by parameter" << data 
<<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
int main(int argc, char* argv[])
{
B temp = Play(5);
return 0;
}
133 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一
样的。这时,被调函数的形参就成为原来主调
函数中的实参变量或对象的一个别名来使用,
所以在被调函数中对形参变量的操作就是对
其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并
没有产生实参的副本,它是直接对实参操作;
而使用一般变量传递函数的参数,当发生函数
调用时,需要给形参分配存储单元,形参变量
是实参变量的副本;如果传递的是对象,还将
调用拷贝构造函数。因此,当参数传递的数据
较大时,用引用比用一般变量传递参数的效率
和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到
与使用引用的效果,但是,在被调函数中同样
要给形参分配存储单元,且需要重复使用"*指
针变量名"的形式进行运算,这很容易产生错
误且程序的阅读性较差;另一方面,在主调函
数的调用点处,必须用变量的地址作为实参。
而引用更容易使用,更清晰。
134. 什么时候需要“引用”?
流操作符(<<、>>)和赋值操作符(=)的返
回值、拷贝构造函数的参数、赋值操作符的参
数、其它情况都推荐使用引用。
135.面向对象的三个基本特征,并简单叙述
之?
1. 封装:将客观事物抽象成类,每个类对自
身的数据和方法实行 protection(private, 
protected,public)
2. 继承:广义的继承有三种实现形式:实现
继承(指使用基类的属性和方法而无需额外编
码的能力)、可视继承(子窗体使用父窗体的
外观和实现代码)、接口继承(仅使用属性和
方法,实现滞后到子类实现)。前两种(类继
承)和后一种(对象组合=>接口继承以及纯虚
函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多
的他的子对象相等的技术,赋值之后,父对象
就可以根据当前赋值给它的子对象的特性以
不同的方式运作。简单的说,就是一句话:允
许将子类类型的指针赋值给父类类型的指针。
136.求下面函数的返回值(微软)
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;

假定 x = 9999。 答案:8
思路:将 x 转化为 2 进制,看含有的 1 的个数。 
137、写出下列代码的输出内容
#i nclude<stdio.h>
int inc(int a)
{
return(++a);
}
int multi(int*a,int*b,int*c)
{
return(*c=*a**b);
}
typedef int(FUNC1)(int in);
typedef int(FUNC2) (int*,int*,int*);
void show(FUNC2 fun,int arg1, int*arg2)
{
INCp=&inc;
int temp =p(arg1);
fun(&temp,&arg1, arg2);
printf("%d\n",*arg2);
}
main()
{
int a;
show(multi,10,&a);
return 0;
}
答:110
138。编写一个 C 函数,该函数在一个字符串
中找到可能的最长的子字符串,且该字符串是
由同一字符组成的。
char * search(char *cpSource, char ch)
{
 char *cpTemp=NULL, 
*cpDest=NULL;
 int iTemp, iCount=0;
 while(*cpSource)
 {
 if(*cpSource == ch)
 {
 iTemp = 0;
cpTemp = 
cpSource;
 
while(*cpSource == ch) 
++iTemp, ++cpSource;
 if(iTemp > 
iCount) 
iCount = iTemp, cpDest = cpTemp;
 if(!*cpSource) 
break;
 }
 ++cpSource;
}
return cpDest;

139。请编写一个 C 函数,该函数在给定的内
存区域搜索给定的字符,并返回该字符所在位
置索引值。
int search(char *cpSource, int n, char ch)
{
 int i;
 for(i=0; i<n && *(cpSource+i) != 
ch; ++i);
 return i;
}
140.一个单向链表,不知道头节点,一个指针
指向其中的一个节点,问如何删除这个指针指
向的节点?
将这个指针指向的 next 节点值 copy 到本节点,
将 next 指向 next->next,并随后删除原 next
指向的节点。
141、用指针的方法,将字符串“ABCD1234efgh”
前后对调显示 
#i nclude <stdio.h>
#i nclude <string.h>
#i nclude <dos.h>
int main()
{
 char str[] = "ABCD1234efgh";
 int length = strlen(str);
 char * p1 = str;
 char * p2 = str + length - 1;
 while(p1 < p2)
 {
 char c = *p1;
 *p1 = *p2;
 *p2 = c;
 ++p1;
 --p2;
 }
 printf("str now is %s\n",str);
 system("pause");
 return 0;
}
142、有一分数序列:1/2,1/4,1/6,1/8……,
用函数调用的方法,求此数列前 20 项的和
#i nclude <stdio.h>
double getValue()
{
 double result = 0;
 int i = 2;
 while(i < 42)
 {
 result += 1.0 / i;//一定要使用 1.0
做除数,不能用 1,否则结果将自动转化成整
数,即 0.000000
 i += 2;
 }
 return result;
}
int main()
{
 printf("result is %f\n", getValue());
 system("pause");
 return 0;
}
143、有一个数组 a[1000]存放 0--1000;要求
每隔二个数删掉一个数,到末尾时循环至开头
继续进行,求最后一个被删掉的数的原始下标
位置。
以 7 个数为例:
 {0,1,2,3,4,5,6,7} 0-->1-->2 (删除)
-->3-->4-->5(删除)-->6-->7-->0(删除),
如此循环直到最后一个数被删除。
方法 1:数组
#include <iostream>
using namespace std;
#define null 1000
int main()
{
int arr[1000];
for (int i=0;i<1000;++i)
arr[i]=i;
int j=0;
int count=0;
while(count<999)
{
while(arr[j%1000]==null)
j=(++j)%1000;
j=(++j)%1000;
while(arr[j%1000]==null)
j=(++j)%1000;
j=(++j)%1000;
while(arr[j%1000]==null)
j=(++j)%1000;
arr[j]=null;
++count;
}
while(arr[j]==null)
j=(++j)%1000;
cout<<j<<endl;
return 0;
}
方法 2:链表
#i nclude<iostream>
using namespace std;
#define null 0
struct node
{
int data;
node* next;
};
int main()
{
node* head=new node;
head->data=0;
head->next=null;
node* p=head;
for(int i=1;i<1000;i++)
{
node* tmp=new node;
tmp->data=i;
tmp->next=null;
head->next=tmp;
head=head->next;
}
head->next=p;
while(p!=p->next)
{
p->next->next=p->next->next->next;
p=p->next->next;
}
cout<<p->data;
return 0;
}
方法 3:通用算法
#i nclude <stdio.h>
#define MAXLINE 1000 //元素个数
/*
MAXLINE 元素个数
a[] 元素数组
R[] 指针场
suffix 下标
index 返回最后的下标序号
values 返回最后的下标对应的值
start 从第几个开始
K 间隔
*/
int find_n(int a[],int R[],int K,int& 
index,int& values,int s=0) {
 int suffix;
 int front_node,current_node;
 suffix=0;
 if(s==0) {
 current_node=0;
 front_node=MAXLINE-1;
}
 else {
 current_node=s;
 front_node=s-1;
 }
 while(R[front_node]!=front_node) 
{
 
printf("%d\n",a[current_node]);
 
R[front_node]=R[current_node];
 if(K==1) {
 
current_node=R[front_node];
 continue;
 }
 for(int i=0;i<K;i++){
 
front_node=R[front_node];
 }
 current_node=R[front_node];
 }
index=front_node;
values=a[front_node];
return 0;
}
int main(void) {
int 
a[MAXLINE],R[MAXLINE],suffix,index,valu
es,start,i,K;
suffix=index=values=start=0;
K=2;
for(i=0;i<MAXLINE;i++) {
a[i]=i;
R[i]=i+1;
}
R[i-1]=0;
find_n(a,R,K,index,values,2);
printf("the value 
is %d,%d\n",index,values);
return 0;
}
144、指出下列程序有什么错误:
void test2() 

 char string[10], str1[10]; 
 int i; 
 for(i=0; i<10; i++) 
 { 
 str1[i] = 'a'; 
 } 
 strcpy( string, str1 ); 

解答:对试题 2,如果面试者指出字符数组 str1
不能在数组内结束可以给 3 分;如果面试者指
出 strcpy(string, str1)调用使得从 str1 内
存起复制到 string 内存起所复制的字节数具
有不确定性可以给 7 分,在此基础上指出库函
数 strcpy 工作方式的给 10 分;
str1 不能在数组内结束:因为 str1 的存储为:
{a,a,a,a,a,a,a,a,a,a},没有'\0'(字符串结
束符),所以不能结束
strcpy( char *s1,char *s2)他的工作原理是,
扫描 s2 指向的内存,逐个字符付到 s1 所指向
的内存,直到碰到'\0',因为 str1 结尾没有
'\0',所以具有不确定性,不知道他后面还会
付什么东东。
正确应如下
void test2() 

 char string[10], str1[10]; 
 int i; 
 for(i=0; i<9; i++) 
 { 
 str1[i] = 'a'+i; //把 abcdefghi 赋
值给字符数组
 } 
 str[i]='\0';//加上结束符
 strcpy( string, str1 ); 
}
145、实现 strcmp
int StrCmp(const char *str1, const char 
*str2)
{
 assert(str1 && srt2);
while(*str1 && *str1++ = = *str2++);
return *str1-*str2; 
}
146.符串A和B,输出A和B中的最大公共子串。
比如 A="aocdfe" B="pmcdfa" 则输出"cdf"
*/
//Author: azhen
#i nclude<stdio.h>
#i nclude<stdlib.h>
#i nclude<string.h>
char *commanstring(char shortstring[], 
char longstring[])
{
int i, j;
char *substring=malloc(256);
if(strstr(longstring, shortstring)!=NULL) 
//如果……,那么返回 shortstring
return shortstring; 
for(i=strlen(shortstring)-1;i>0; i--) 
//否则,开始循环计算
{
for(j=0; j<=strlen(shortstring)-i; j++){
memcpy(substring, &shortstring[j], i);
substring[i]='\0';
if(strstr(longstring, substring)!=NULL)
return substring;
}
}
return NULL;
}
main()
{
char *str1=malloc(256);
char *str2=malloc(256);
char *comman=NULL;
gets(str1);
gets(str2);
if(strlen(str1)>strlen(str2)) 
//将短的字符串放前面
comman=commanstring(str2, str1);
else
comman=commanstring(str1, str2);
printf("the longest comman string 
is: %s\n", comman);
}
147、写一个函数比较两个字符串 str1 和 str2
的大小,若相等返回 0,若 str1 大于
str2 返回 1,若 str1 小于 str2 返回-1
int strcmp ( const char * src,const char 
* dst)
{
 int ret = 0 ;
 while( ! (ret = *(unsigned char 
*)src - *(unsigned char *)dst) && *dst)
{
 ++src;
++dst;
}
 if ( ret < 0 )
 ret = -1 ;
 else if ( ret > 0 )
 ret = 1 ;
 return( ret );
}
148、判断一个字符串是不是回文
int IsReverseStr(char *aStr)
{
int i,j;
int found=1;
if(aStr==NULL)
return -1;
j=strlen(aStr);
for(i=0;i<j/2;i++)
if(*(aStr+i)!=*(aStr+j-i-1))
{
found=0;
break;
}
return found;
149 #include main()
{
int c[3][3]={1,2,3,4,5,6,7,8,9}; 
for(int i=0;i<3;i++) 
for(int j=0;j<3;j++) 
printf("%ld\n",&c[j]); 
printf("-------------------------\n"); 
printf("%ld\n",(c+1)); 
printf("%ld\n",(*c+1)); 
printf("%ld\n",&c[0][0]); 
printf("%ld\n",**c);
printf("%ld\n",*c[0]); 
if(int(c)==int(*c)) printf("equl"); 

为什么 c,*c 的值相等,(c+1),(*c+1)
的值不等 c,*c,**c,代表什么意思?
参考答案:
c 是第一个元素的地址,*c 是第一行元素的首
地址,其实第一行元素的地址就是第一个元素
的地址,这容易理解。**c 是提领第一个元素。
为什么 c,*c 的值相等?
int c 因为 直接用 c 表示数 组 c[0][0] 
printf("%ld\n",*c[0]);语句已将指针移到
数组头。int(*c)表示c0的值为1,所以相等。
数组 c 的存放空间示意如下:(机器中是行优
先存放的) c[0][0] c[0][1] c[0][2] c[1][0] 
c[1][1] c[1][2] c[2][0] c[2][1] c[2][2] c
是一个二维数组名,实际上它是一个指针常量,
不能进行自加、自减运算,即:c++、c--、++c、
--c 都是不允许的;
c: 数组名;是一个二维指针,它的值就是数
组的首地址,也即第一行元素的首地址(等于
*c),也 等于第一行第一个元素的地址( & 
c[0][0]);可以说成是二维数组的行指针。*c:
第一行元素的首地址;是一个一维指针,可以
说成是二维数组的列指针。 **c:二维数组中
的第一个元素的值;即:c[0][0] 所以: c 和
*c 的值是相等的,但他们两者不能相互赋值,
(类型不同); (c + 1) :c 是行指针,(c 
+ 1)是在 c 的基础上加上二维数组一行的地
址长度,即从&c[0][0] 变到了&c[1][0];(*c 
+ 1):*c 是列指针,(*c + 1)是在*c 的基
础上加上二数组一个元素的所占的长度,即从
&c[0][0]变到了&c[0][1] 从而(c + 1)和(*c 
+ 1)的值就不相等了
150、定义 int **a[3][4], 则变量占有的内
存空间为:__32___ 参考答案:
int **p; /*16 位下 sizeof(p)=2, 32 位下
sizeof(p)=4*/ 
总共 3*4*sizeof(p) 
151、写出判断 ABCD 四个表达式的是否正确, 
若正确, 写出经过表达式中 a 的值
int a = 4;
(A)a += (a++); (B) a += (++a) ;(C) (a++) 
+= a;(D) (++a) += (a++);
a = ?
答:C 错误,左侧不是一个有效变量,不能赋
值,可改为(++a) += a;
改后答案依次为 9,10,10,11
152、某 32 位系统下, C++程序,请计算 sizeof 
的值
char str[] = “http://www.ibegroup.com/”
char *p = str ;
int n = 10;
请计算
(1)sizeof (str ) = ?
(2)sizeof ( p ) = ?
(3)sizeof ( n ) = ?
void Foo ( char str[100]){
请计算
sizeof( str ) = ?(4)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = ?(5)
答:(1)25 (2)4 (3) 4 (4)4 (5)4
153、 回答下面的问题
(1).Void GetMemory(char **p, int num){
*p = (char *)malloc(num);
}
void Test(void){
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:输出“hello”
154、 void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL){
strcpy(str, “world”);
printf(str);
}
}
请问运行 Test 函数会有什么样的结果?
答:输出“world”
155、 char *GetMemory(void){
char p[] = "hello world";
return p;
}
void Test(void){
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:无效的指针,输出不确定
156、编写 strcat 函数 
已知 strcat 函数的原型是 char *strcat 
(char *strDest, const char *strSrc); 
其中 strDest 是目的字符串,strSrc 是源字
符串。 
(1)不调用 C++/C 的字符串库函数,请编写
函数 strcat 
答: 
VC 源码:
char * __cdecl strcat (char * dst, const 
char * src)
{
char * cp = dst;
while( *cp )
cp++;
while( *cp++ = *src++ ) ;
return( dst );
}
157 、 strcat 能 把 strSrc 的 内 容 连 接 到
strDest,为什么还要 char * 类型的返回值? 
答:方便赋值给其他变量 
158、MFC 中 CString 是类型安全类么?
答:不是,其它数据类型转换到 CString 可以
使用 CString 的成员函数 Format 来转换
159.C++中什么数据分配在栈或堆中?
答:栈: 存放局部变量,函数调用参数,函数
返回值,函数返回地址。由系统管理
堆: 程序运行时动态申请,new 和 malloc 申
请的内存就在堆上
160、函数模板与类模板有什么区别?
答:函数模板的实例化是由编译程序在处理函
数调用时自动完成的,而类模板的实例化
必须由程序员在程序中显式地指定。
161、 int i=10, j=10, k=3; k*=i+j; k 最后
的值是?
答 : 60 , 此 题 考 察 优 先 级 , 实 际 写 成 :
k*=(i+j);,赋值运算符优先级最低
162、do……while 和 while……do 有什么区
别?
答 、前一个循环一遍再判断,后一个判断以
后再循环
163、请写出下列代码的输出内容
#i nclude
main()
{
int a,b,c,d;
a=10;
b=a++;
c=++a;
d=10*a++;
printf("b,c,d:%d,%d,%d",b,c,d);
return 0;
}
答 、10,12,120
164.在 c 语言库函数中将一个字符转换成整型
的函数是 atol()吗,这个函数的原型是什么?
答 、函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:
#include
#include
int main(void)
{
 long l;
 char *str = "98765432";
 l = atol(lstr);
 printf("string = %s integer = %ld\n", 
str, l);
 return(0);
}
165. 以下三条输出语句分别输出什么?
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char* str5 = "abc";
const char* str6 = "abc";
cout << boolalpha << ( str1==str2 ) << endl; 
// 输出什么?
cout << boolalpha << ( str3==str4 ) << endl; 
// 输出什么?
cout << boolalpha << ( str5==str6 ) << endl; 
// 输出什么?
答:分别输出 false,false,true。str1 和 str2
都是字符数组,每个都有其自己的存储区,它
们的值则是各存储区首地址,不等;str3 和
str4 同上,只是按 const 语义,它们所指向的
数据区不能修改。str5 和 str6 并非数组而是
字符指针,并不分配存储区,其后的“abc”
以常量形式存于静态数据区,而它们自己仅是
指向该区首地址的指针,相等。
166 以下代码中的两个 sizeof 用法有问题
吗?
void UpperCase( char str[] ) // 将 str 中
的小写字母转换成大写字母
{
for( size_t i=0; 
i<sizeof(str)/sizeof(str[0]); ++i )
if( 'a'<=str[i] && str[i]<='z' )
str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str 字 符 长 度 为 : " << 
sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
答:函数内的 sizeof 有问题。根据语法,sizeof
如用于数组,只能测出静态数组的大小,无法
检测动态分配的或外部数组大小。函数外的
str 是一个静态定义的数组,因此其大小为 6,
函数内的 str 实际只是一个指向字符串的指针,
没有任何额外的与数组相关的信息,因此
sizeof 作用于上只将其当指针看,一个指针为
4 个字节,因此返回 4。
167 非 C++内建型别 A 和 B,在哪几种情况下
B 能隐式转化为 A?
答:
a. class B : public A { ……} // B 公有继
承自 A,可以是间接继承的
b. class B { operator A( ); } // B 实现了
隐式转化为 A 的转化
c. class A { A( const B& ); } // A 实现了
non-explicit 的参数为 B(可以有其他带默认
值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操
作,虽不是正宗的隐式类型转换,但也可以勉
强算一个
168.以下代码有什么问题?
struct Test
{
Test( int ) {}
Test() {}
void fun() {}
};
void main( void )
{
Test a(1);
a.fun();
Test b();
b.fun();
}
答:变量 b 定义出错。按默认构造函数定义对
象,不需要加括号。
169 以下代码有什么问题?
cout << (true?1:"1") << endl;
答:三元表达式“?:”问号后面的两个操作
数必须为同一类型。
170 以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2 定义出错,size2 非编译器期间常量,
而数组定义要求长度必须为编译期常量。
171. 以下代码中的输出语句输出 0 吗,为什
么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;
答:不能。在默认构造函数内部再调用带参的
构造函数属用户行为而非编译器行为,亦即仅
执行函数调用,而不会执行其后的初始化表达
式。只有在生成对象时,初始化表达式才会随
相应的构造函数一起调用。
172 C++中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋
值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址
运算符 const
};
173 以下两条输出语句分别输出什么?
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) 
<< endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) 
<< endl; // 输出什么?
答:分别输出false和true。注意转换的应用。
(int)a实际上是以浮点数a为参数构造了一个
整型数,该整数的值是 1,(int&)a 则是告诉
编译器将 a 当作整数看(并没有做任何实质上
的转换)。因为 1 以整数形式存放和以浮点形
式存放其内存数据是不一样的,因此两者不等。
对 b 的两种转换意义同上,但是 0 的整数形式
和浮点形式其内存数据是一样的,因此在这种
特殊情形下,两者相等(仅仅在数值意义上)。
注意,程序的输出会显示(int&)a=1065353216,
这个值是怎么来的呢?前面已经说了,1 以浮
点数形式存放在内存中,按 ieee754 规定,其
内容为 0x0000803F(已考虑字节反序)。这也
就是 a 这个变量所占据的内存单元的值。当
(int&)a 出现时,它相当于告诉它的上下文:
“把这块地址当做整数看待!不要管它原来是
什么。”这样,内容 0x0000803F 按整数解释,
其值正好就是 1065353216(十进制数)。
通过查看汇编代码可以证实“(int)a 相当于重
新构造了一个值等于 a 的整型数”之说,而
(int&)的作用则仅仅是表达了一个类型信息,
意义在于为 cout<<及==选择正确的重载版
174、请简述以下两个for循环的优缺点(5分)
for (i=0; i<N; i++)
{
if (condition)
 DoSomething();
else
 DoOtherthing();
}
if (condition)
{
for (i=0; i<N; i++)
 DoSomething();
}
else
{
 for (i=0; i<N; i++)
 DoOtherthing();
}
优点:程序简洁
缺点:多执行了 N-1 次逻辑判断,并且打断了
循环“流水线”作业,使得编译器不能对循环
进行优化处理,降低了效率。
优点:循环的效率高
缺点:程序不简洁
175
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str); 
strcpy(str, "hello world");
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:程序崩溃。
因为 GetMemory 并不能传递动态内存,
Test 函数中的 str 一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
176
char *GetMemory(void)

char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory(); 
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:可能是乱码。
因为 GetMemory 返回的是指向“栈内存”的指
针,该指针的地址不是 NULL,但其原现的内
容已经被清除,新内容不可知。
177
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello"); 
printf(str); 
}
请问运行 Test 函数会有什么样的结果?
答:
(1)能够输出 hello
(2)内存泄漏
178
void Test(void)
{
char *str = (char *) malloc(100);
 strcpy(str, “hello”);
 free(str); 
 if(str != NULL)
 {
 strcpy(str, “world”);
 printf(str);
 }
}
请问运行 Test 函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,
非常危险。
因为 free(str);之后,str 成为野指针,
if(str != NULL)语句不起作用。
179、请阅读以下一段程序,并给出答案。
class A
{
public:
 A(){ doSth(); }
 virtual void doSth(){printf("I am 
A");}
};
class B:public A
{
public:
 virtual void doSth(){ printf("I am 
B");}
};
B b;
执行结果是什么?为什么?
答:执行结果是 I am A
因为 b 对象构造时调用基类 A 的构造函数 A(),
得此结果。
180 实现双向链表删除一个节点 P,在节点 P
后插入一个节点,写出这两个函数;
答:双向链表删除一个节点 P
template<class type> void 
list<type>::delnode(int p)
{
int k=1;
listnode<type> *ptr,*t;
ptr=first; 
while(ptr->next!=NULL&&k!=p)
{
 ptr=ptr->next;
 k++;
}
 t=ptr->next;
cout<<"你已经将数据项 "<<t->data<<"删
除"<<endl;
ptr->next=ptr->next->next;
length--;
delete t;

在节点 P 后插入一个节点:
template<class type> bool 
list<type>::insert(type t,int p)
{
listnode<type> *ptr;
ptr=first; 
int k=1;
while(ptr!=NULL&&k<p) 
{
 ptr=ptr->next;
 k++;
}
if(ptr==NULL&&k!=p)
 return false;
else
{
 listnode<type> *tp;
 tp=new listnode<type>;
 tp->data=t;
 tp->next=ptr->next;
 ptr->next=tp;
 length++;
 
 return true;
}

181.完成下列程序 
 

 
*.*. 
 
*..*..*.. 
 
*...*...*...*... 
 
*....*....*....*....*.... 
 
*.....*.....*.....*.....*.....*..... 
 
*......*......*......*......*......*...
...*...... 
 
*.......*.......*.......*.......*......
.*.......*.......*....... 
#i nclude<iostream>
using namespace std; 
const int n = 8; 
main()
{
 int i;
 int j;
 int k; 
 for(i = n; i >= 1; i--)
 {
 for(j = 0; j < n-i+1; j++)
 {
 cout<<"*";
 for(k=1; k < n-i+1; k++)
 {
 cout<<".";
 }
 }
 cout<<endl; 
 }
 system("pause");
}
182 完成程序,实现对数组的降序排序
#include <iostream>
using namespace std; 
void sort(int* arr, int n); 
int main() 
{
 int 
array[]={45,56,76,234,1,34,23,2,3};
 sort(array, 9);
 for(int i = 0; i <= 8; i++)//曾经在这
儿出界
 cout<<array[i]<<" ";
 cout<<endl;
 system("pause");

void sort(int* arr, int n)

 int temp;
 for(int i = 1; i < 9; i++)
 {
 for(int k = 0; k < 9 - i; k++)//曾
经在这儿出界
 {
 if(arr[k] < arr[k + 1])
 {
 temp = arr[k];
 arr[k] = arr[k + 1];
 arr[k + 1] = temp;
 } 
 }
 }

183. 以下两条输出语句分别输出什么?[C++
难]
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) 
<< endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) 
<< endl; // 输出什么?
1
1065353216
boolalpha0
0
0
boolalpha1 
51. 以下反向遍历 array 数组的方法有什么错
误?[STL 易]
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; 
i>=0; --i ) // 反向遍历 array 数组
{
 cout << array[i] << endl;

184 写一个函数,完成内存之间的拷贝。[考
虑问题是否全面]
答:
void* mymemcpy( void *dest, const void 
*src, size_t count )
{
 char* pdest = 
static_cast<char*>( dest );
 const char* psrc = static_cast<const 
char*>( src );
 if( pdest>psrc && pdest<psrc+cout ) 
能考虑到这种情况就行了
 {
 for( size_t i=count-1; i!=-1; 
--i )
 pdest[i] = psrc[i];
 }
 else
 {
 for( size_t i=0; i<count; ++i )
 pdest[i] = psrc[i];
 }
 return dest;
}
int main( void )
{
 char str[] = "0123456789";
 mymemcpy( str+1, str+0, 9 );
 cout << str << endl; 
 system( "Pause" );
 return 0;

185 对于 C++中类(class) 与结构(struct)的
描述正确的为:
 A,类中的成员默认是 private 的,当是可以
声明为 public,private 和 protected,
结构中定义的成员默认的都是 public;
 B,结构中不允许定义成员函数,当是类中可
以定义成员函数;
 C,结构实例使用 malloc() 动态创建,类对
象使用 new 操作符动态分配内存;
 D,结构和类对象都必须使用 new 创建;
 E,结构中不可以定义虚函数,当是类中可以
定义虚函数.
 F,结构不可以存在继承关系,当是类可以存
在继承关系.
答:A,D,F 
186.两个互相独立的类:ClassA 和 ClassB,
都 各 自 定 义 了 非 静 态 的 公 有 成 员 函 数
PublicFunc() 和 非 静 态 的 私 有 成 员 函 数
PrivateFunc();
 现在要在 ClassA 中增加定义一个成员函
数 ClassA::AdditionalPunction(ClassA 
a,ClassB b); 则可以在
AdditionalPunction(ClassA x,ClassB y)的
实现部分(函数功能体内部)
 出现的合法的表达是最全的是: 
 
A,x.PrivateFunc();x.PublicFunc();y.Priv
ateFunc();y.PublicFunc();
 
B,x.PrivateFunc();x.PublicFunc();y.Publ
icFunc();
 
C,x.PrivateFunc();y.PrivateFunc();y.Pub
licFunc();
 D,x.PublicFunc();y.PublicFunc();
答:B
186.C++程序下列说法正确的有:
 A,对调用的虚函数和模板类都进行迟后编
译.
 B,基类与子类中函数如果要构成虚函数,除
了要求在基类中用 virtual 声名,而且必须名
字相同且参数类型相同返回类型相同
 C,重载的类成员函数都必须要:或者返回类
型不同,或者参数数目不同,或者参数序列的
类型不同.
 D,静态成员函数和内联函数不能是虚函数,
友员函数和构造函数也不能是虚函数,但是析
构函数可以是虚函数.
答:A
***************************************
************************************ 
187 头文件的作用是什么?
答:一、通过头文件来调用库功能。在很多场
合,源代码不便(或不准)向用户公布,只要
向用户提供头文件和二进制的库即可。用户只
需要按照头文件中的接口声明来调用库功能,
而不必关心接口怎么实现的。编译器会从库中
提取相应的代码。
二、头文件能加强类型安全检查。如果某个接
口被实现或被使用时,其方式与头文件中的声
明不一致,编译器就会指出错误,这一简单的
规则能大大减轻程序员调试、改错的负担。
188、以下为 Windows NT 下的 32 位 C++程序,
请计算 sizeof 的值(10 分)
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
sizeof (str ) = 6 (2 分)
 
sizeof ( p ) = 4 (2 分)
 
sizeof ( n ) = 4 (2 分)void Func ( char 
str[100])
{
请计算
sizeof( str ) = 4 (2 分)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = 4 (2 分)
3 写出下列程序的运行结果。
unsigned int i=3; 
cout<<i * -1; 
189.写出下列程序所有可能的运行结果。
int a; 
int b; 
int c; 
void F1() 

b=a*2; 
a=b; 

void F2() 

c=a+1; 
a=c; 

main() 

a=5; 
//Start F1,F2 in parallel 
F1(); F2(); 
printf("a=%d\n",a); 
}a=11 
190 一个链表的操作,注意代码的健壮和安全
性。要求:
(1)增加一个元素;
(2)获得头元素;
(3)弹出头元素(获得值并删除)。
191.unsigned short 
array[]={1,2,3,4,5,6,7};
int i = 3;
*(array + i) = ? 
答:

192
class A
{
 virtual void func1();
 void func2();
}
Class B: class A
{
 void func1(){cout << "fun1 in class B" 
<< endl;}
 virtual void func2(){cout << "fun2 in 
class B" << endl;}

A, A 中的 func1 和 B 中的 func2 都是虚函数.
B, A 中的 func1 和 B 中的 func2 都不是虚函数.
C, A 中的 func2 是虚函数.,B 中的 func1 不
是虚函数.
D, A 中的 func2 不是虚函数,B 中的 func1 是
虚函数. 
答:

193 输出下面程序结果。
#include <iostream.h> 
class A 

public:
virtual void print(void) 

 cout<<"A::print()"<<endl; 

};
class B:public A 

public:
virtual void print(void) 

 cout<<"B::print()"<<endl;
}; 
}; 
class C:public B
{
public:
virtual void print(void)
{
 cout<<"C::print()"<<endl;
}
};
void print(A a) 

 a.print(); 

void main(void) 

 A a, *pa,*pb,*pc; 
 B b; 
 C c; 
 
 pa=&a; 
 pb=&b; 
 pc=&c; 
 
 a.print(); 
 b.print(); 
 c.print(); 
 
 pa->print(); 
 pb->print(); 
 pc->print(); 
 
 print(a); 
 print(b); 
 print(c); 

A::print()
A::print()
B::print()
C::print() 
A::print()
B::print()
C::print()
A::print()
A::print()
A::print() 
---------------------------------------
-----------------------------------
194.程序改错
class mml
{
 private:
 static unsigned int x;
 public:
 mml(){ x++; }
 mml(static unsigned int &) {x++;}
 ~mml{x--;}
 pulic:
 virtual mon() {} = 0;
 static unsigned int mmc(){return x;}
 ...... 
};
class nnl:public mml
{
 private:
 static unsigned int y;
 public:
 nnl(){ x++; }
 nnl(static unsigned int &) {x++;}
 ~nnl{x--;}
 public:
 virtual mon() {};
 static unsigned int nnc(){return y;}
 ...... 
}; 
代码片断:
mml* pp = new nnl;
..........
delete pp; 
A:
基类的析构函数应该为虚函数
virtual ~mml{x--;} 
---------------------------------------
-----------------------------------
195.101 个硬币 100 真、1 假,真假区别在于
重量。请用无砝码天平称两次给出真币重还是
假币重的结论。
答:
101 个先取出 2 堆,
33,33
第一次称,如果不相等,说明有一堆重或轻
那么把重的那堆拿下来,再放另外35个中的33
如果相等,说明假的重,如果不相等,新放上去
的还是重的话,说明假的轻(不可能新放上去
的轻) 
第一次称,如果相等的话,这 66 个肯定都是真
的,从这 66 个中取出 35 个来,与剩下的没称过
的 35 个比
下面就不用说了
方法二:
第 3 题也可以拿 A(50),B(50)比一下,一样的
话拿剩下的一个和真的比一下。
如果不一样,就拿其中的一堆。比如 A(50)再
分成两堆 25 比一下,一样的话就在
B(50)中,不一样就在 A(50)中,结合第一次的
结果就知道了。
196.写出程序结果:
void Func(char str[100])
{
 printf("%d\n", sizeof(str));

答:
4
分析:
指针长度
197.int id[sizeof(unsigned long)];
 这个对吗?为什么?? 
答:

这个 sizeof 是编译时运算符,编译时就确定

可以看成和机器有关的常量。
198、 sizeof 应用在结构上的情况
请看下面的结构:
struct MyStruct 

double dda1; 
char dda; 
int type 
}; 
对结构 MyStruct 采用 sizeof 会出现什么结果
呢?sizeof(MyStruct)为多少呢?也许你会
这样求:
sizeof(MyStruct)=sizeof(double)+sizeof(
char)+sizeof(int)=13 
但是当在 VC 中测试上面结构的大小时,你会
发现 sizeof(MyStruct)为 16。你知道为什么
在 VC 中会得出这样一个结果吗?
其实,这是 VC 对变量存储的一个特殊处理。
为了提高 CPU 的存储速度,VC 对一些变量的起
始地址做了"对齐"处理。在默认情况下,VC 规
定各成员变量存放的起始地址相对于结构的
起始地址的偏移量必须为该变量的类型所占
用的字节数的倍数。下面列出常用类型的对齐
方式(vc6.0,32 位系统)。
类型
对齐方式(变量存放的起始地址相对于结构的
起始地址的偏移量)
Char 
偏移量必须为 sizeof(char)即 1 的倍数
int 
偏移量必须为 sizeof(int)即 4 的倍数
float 
偏移量必须为 sizeof(float)即 4 的倍数
double 
偏移量必须为 sizeof(double)即 8 的倍数
Short 
偏移量必须为 sizeof(short)即 2 的倍数
各成员变量在存放的时候根据在结构中出现
的顺序依次申请空间,同时按照上面的对齐方
式调整位置,空缺的字节 VC 会自动填充。同
时 VC 为了确保结构的大小为结构的字节边界
数(即该结构中占用最大空间的类型所占用的
字节数)的倍?
199 
#include "stdafx.h"
Y n P }2{&k O v H `,o0 
#define SQR(X) X*X
int main(int argc, char* argv[])
{
int a = 10;
int k = 2; 
int m = 1; 
a /= SQR(k+m)/SQR(k+m); 
printf("%d\n",a); 
return 0;
}
这道题目的结果是什么啊?
define 只是定义而已,在编择时只是简单代
换 X*X 而已,并不经过算术法则的
a /= k+m*k+m/k+m*k+m;
=>a /= (k+m)*1*(k+m);
=>a = a/9;
=>a = 1;
200.下面的代码有什么问题?
void DoSomeThing(...)
{
char* p;
p = malloc(1024); // 分配 1K 的空间
2y x 
if (NULL == p)
return;
p = realloc(p, 2048); // 空间不够,重新
分配到 2K
if (NULL == p)
return;
}
A: 
p = malloc(1024); 应该写成: p = (char 
*) malloc(1024); 
 没有释放 p 的空间,造成内存泄漏。
201 下面的代码有什么问题?并请给出正确的
写法。
void DoSomeThing(char* p)
{
char str[16]; 
int n;
assert(NULL != p);
sscanf(p, "%s%d", str, n);
if (0 == strcmp(str, "something"))

}
}
A:
sscanf(p, "%s%d", str, n); 这句该写成:
sscanf(p, "%s%d", str, &n); 
---------------------------------------
-----------------------------------
202.下面代码有什么错误?
Void test1()
{
char string[10]; 
char *str1="0123456789"; 
strcpy(string, str1); 

数组越界
203.下面代码有什么问题?
Void test2() 

char string[10], str1[10]; 
for(i=0; i<10;i++) 

 str1[i] ='a';

strcpy(string, str1); 

}
数组越界
204 下面代码有什么问题?LUPA 开源社区+j 
H2B F,c U
Void test3(char* str1) 

char string[10]; 
if(strlen(str1)<=10) 
{
 strcpy(string, str1); 


==数组越界
==strcpy 拷贝的结束标志是查找字符串中的
\0 因此如果字符串中没有遇到\0 的话 会一
直复制,直到遇到\0,上面的 123 都因此产生
越界的情况
建议使用 strncpy 和 memcpy
205.写出运行结果:
{
 char str[] = "world"; cout << 
sizeof(str) << ": ";
 char *p = str; cout << 
sizeof(p) << ": ";
 char i = 10; cout << 
sizeof(i) << ": "; 
 void *pp = malloc(10); cout << 
sizeof(p) << endl;
}
6:4:1:4
---------------------------------------
-----------------------------------
206.C 和 C++有什么不同?
从机制上:c 是面向过程的(但 c 也可以编写
面向对象的程序);c++是面向对象的,提供
了类。但是,c++编写面向对象的程序比 c 容

从适用的方向:c 适合要求代码体积小的,效
率高的场合,如嵌入式;c++适合更上层的,
复杂的; llinux 核心大部分是 c 写的,因为
它是系统软件,效率要求极高。
从名称上也可以看出,c++比 c 多了+,说明 c++
是 c 的超集;那为什么不叫 c+而叫 c++呢,是
因为 c++比
c 来说扩充的东西太多了,所以就在 c 后面放
上两个+;于是就成了 c++
C 语言是结构化编程语言,C++是面向对象编
程语言。LUPA 开源社区 } n*r2C/M8f
C++侧重于对象而不是过程,侧重于类的设计
而不是逻辑的设计。
207 在不用第三方参数的情况下,交换两个参
数的值
#include <stdio.h>
void main()
{
 int i=60;
 int j=50;
 i=i+j;
 j=i-j;

 i=i-j;
 printf("i=%d\n",i);
 printf("j=%d\n",j); 
}
方法二:
i^=j;
j^=i;
i^=j;
方法三:
// 用加减实现,而且不会溢出
a = a+b-(b=a)
208.下面的函数实现在一个固定的数上加上
一个数,有什么错误,改正
int add_n(int n) 

static int i=100;
i+=n; 
return i; 

答:
因为 static 使得 i 的值会保留上次的值。
去掉 static 就可了
209.union a {
int a_int1;
double a_double;
int a_int2;
};
typedef struct
{ a a1;
char y;
} b;
class c

double c_double;
b b1;
a a2;
}; 
输出 cout<<sizeof(c)<<endl;的结果?
答:
VC6 环境下得出的结果是 32
我(sun)在 VC6.0+win2k 下做过试验:
int-4
float-4
double-8
指针-4
210. unsigned short 
array[]={1,2,3,4,5,6,7};
int i = 3;
*(array + i) = ?
答:4
211. class A
{
virtual void func1();
void func2();

Class B: class A
{
void func1(){cout << "fun1 in class B" << 
endl;}
virtual void func2(){cout << "fun2 in 
class B" << endl;}
}
A, A 中的 func1 和 B 中的 func2 都是虚函数.
B, A 中的 func1 和 B 中的 func2 都不是虚函数.
C, A 中的 func2 是虚函数.,B 中的 func1 不
是虚函数.
D, A 中的 func2 不是虚函数,B 中的 func1 是
虚函数.
答: 
A
212 输出下面程序结果。
#include <iostream.h>
class A 

public:
virtual void print(void) 

 cout<<"A::print()"<<endl; 

};
class B:public A 

public:
virtual void print(void) 

 cout<<"B::print()"<<endl;
};
}; 
class C:public 
{
public:
virtual void print(void) 
{
 cout<<"C::print()"<<endl; 
}
}; 
void print(A a) 

 a.print(); 
}
void main(void) 

 A a, *pa,*pb,*pc; 
 pa=&a; 
 pb=&b; 
 pc=&c; 
 a.print();
 b.print(); 
 c.print(); 
 a->print(); 
 pb->print(); 
 pc->print(); 
 print(a); 
 print(b); 
 print(c); 
}
A::print()
A::print()
B::print()
C::print()
A::print()
B::print()
C::print()
A::print()
A::print()
A::print()
213 C++语言是在___ C ______语言的基础上
发展起来的。
214 C++ 语 言 的 编 译 单 位 是 扩 展 名 为
____ .cpp______的____程序______文件。
215. 行 尾 使 用 注 释 的 开 始 标 记 符 为 ____ 
//_____。
216 多行注释的开始标记符和结束标记符分
别为_____ /*_____和___ */_______。
217. 用于输出表达式值的标准输出流对象是
____ cout_____。
218 用于从键盘上为变量输入值的标准输入
流对象是__ cin______。
219. 一个完整程序中必须有一个名为____ 
main____的函数。
220 一个函数的函数体就是一条____复合
_____语句。
221. 当执行 cin 语句时,从键盘上输入每个
数据后必须接着输入一个___空白_____符,然
后才能继续输入下一个数据。
222 在 C++程序中包含一个头文件或程序文件
的预编译命令为____#include ______。
223. 程序中的预处理命令是指以___#___字
符开头的命令。
224. 一条表达式语句必须以___分号___作为
结束符。
225. 在#include 命令中所包含的头文件,可
以是系统定义的头文件,也可以是___用户(或
编程者_____定义的头文件。
226. 使用#include 命令可以包含一个头文件,
也可以包含一个__程序____文件。
227.一个函数定义由__函数头______和__函
数体_____两部分组成。
228.若一个函数的定义处于调用它的函数之
前,则在程序开始可以省去该函数的__原型
(或声明)____语句。
229.C++头文件和源程序文件的扩展名分别为
__.h ___和___.cpp ___。
230.程 序文件 的编 译错误 分为 ____ 警告
(warning)____和____致命(error) ____两
类。
231.当使用___ void ____保留字作为函数类
型时,该函数不返回任何值。
232.当函数参数表用___ void __保留字表示
时,则表示该参数表为空。
233.从一条函数原型语句“int fun1(void);”
可知,该函数的返回类型为______,该函数带
有______个参数。
234. 当执行 cout 语句输出 endl 数据项时,
将使 C++显示输出屏幕上的光标从当前位置移
动到___下一行_____的开始位置。
235. 假定 x=5,y=6,则表达式 x++*++y 的值
为___35_______。
236. 假定 x=5,y=6,则表达式 x--*--y 的值
为___25_______。
237. 假定 x=5,y=6,则执行表达式 y*=x++计
算后,x 和 y 的值分别为___6___和___30 _____。
238. 假定 x=5,y=6,则执行表达式 y+=x--计
算后,x 和 y 的值分别为____4__和___11___。
239. C++常数 0x145 对应的十进制值为___325 
___。
240. C++常数 0345 对应的十进制值为____ 
229__。
241. 十进制常数 245 对应的十六进制的 C++
表示为____0xF5___。
242. 十进制常数245对应的八进制的C++表示
为___0365 ___。
243. signed char 类型的值域范围是__-128__
至___+127 __之间的整数。
244. int 和 float 类型的数据分别占用___ 
4___和____ 4___个字节。
245. float 和 double 类型的数据分别占用
____ 4___和_____8___个字节。
246. bool 和 char 类型的数 据分别 占 用
_____1____和____1___个字节。
247. unsigned short int 和 int 类型的长度
分别为____ 2___和____4___。
248. 字符串”This\’s a book.\n”的长度
为_____ 15____。
249. 字符串”\nThis\’s a pen\n\n”的长
度为_____ 15_____。
250. 在 C++中存储字符串”abcdef”至少需要
___7 _____个字节。
251. 在 C++中存储字符串”a+b=c”至少需要
_____6 ___个字节。
252. 假定 x 和 y 为整型,其值分别为 16 和 5,
则 x%y 和 x/y 的值分别为___1_______和
____3____。
253. 假定 x 和 y 为整型,其值分别为 16 和 5,
则 x/y 和 double(x)/y 的值分别为____3____
和___3.2____。
254. 假定 x 是一个逻辑量,则 x && true 的
值为___ x ____。
255. 假定 x 是一个逻辑量,则 x || true 的
值为_____ true(或 1)_____。
256. 假定 x 是一个逻辑量,则 x && false 的
值为____ false(或 0) ___。
257. 假定 x 是一个逻辑量,则 x || false 的
值为 x。
258. 假定 x 是一个逻辑量,则!x || false 的
值为____!x ____。
259. 假定 x 是一个逻辑量,则 x && !x 的值
为____ false(或 0)____。
260. 假定 x 是一个逻辑量,则 x || !x 的值
为____ true(或 1)___。
261. 设 enum 
Printstatus{ready,busy,error}; 则
cout<<busy 的输出结果是_____1___。
262. 设 enum 
Printstatus{ready=2,busy,error}; 则
cout<<busy 的输出结果是____3____。
263. 常 数 -4.205 和 6.7E-9 分 别 具 有
___4_____和____2___位有效数字。
264. 枚举类型中的每个枚举值都是一个____
枚举常量_____,它的值为一个___整数____。
265. 常数 100 和 3.62 的数据类型分别为____ 
int ___和_____ double ___。
266. 若 x=5, y=10, 则计算 y*=++x 表达式后,
x 和 y 的值分别为___6___和__60 ___。
267. 假定 x 和 ch 分别为 int 型和 char 型,
则 sizeof(x)和 sizeof(ch)的值分别为__4__
和__1__。
268. 假定 x=10,则表达式 x<=10?20:30 的值
为__ 20 __。
269. 表达式 sqrt(81)和 pow(6,3)的值分别为
___9 ___和___216___。
270. 含随机函数的表达式 rand()%20 的值在
___0__至___ 19 __区间内。
271. 在 switch 语句中,每个语句标号所含关
键字 case 后面的表达式必须是___常量___。
272. 在 if 语句中,每个 else 关键字与它前
面同层次并且最接近的____ if ____关键字相
配套。
273. 作为语句标号使用的 C++保留字 case 和
defaule 只能用于___ switch ___语句的定义
体中。
274. 执行 switch 语句时,在进行作为条件的
表达式求值后,将从某个匹配的标号位置起向
下执行,当碰到下一个标号位置时(停止/不
停止)___不停止__执行。
275. 若 while 循 环 的 “ 头 ” 为
“while(i++<=10)”,并且 i 的初值为 0,同
时在循环体中不会修改 i 的值,则循环体将被
重复执行__11___次后正常结束。
276. 若 do 循环的“尾”为“while(++i<10)”,
并且 i 的初值为 0,同时在循环体中不会修改
i 的值,则循环体将被重复执行___10 ___次后
正常结束。
277. 当在程序中执行到 break 语句时,将结
束本层循环类语句或 switch 语句的执行。
278. 当在程序中执行到___ continue___语句
时,将结束所在循环语句中循环体的一次执行。
279. 在程序中执行到__ return ___语句时,
将结束所在函数的执行过程,返回到调用该函
数的位置。
280.在程序执行完____主(或 main)__函数调
用后,将结束整个程序的执行过程,返回到 C++
集成开发窗口。
281. 元素类型为 int 的数组 a[10]共占用___ 
40___字节的存储空间。
282. 元素类型为 double 的二维数组 a[4][6]
共占用____192__字节的存储空间。
283. 元素类型为 char 的二维数组 a[10][30]
共占用___300__字节的存储空间。
284. 存储字符’a’和字符串”a”分别需要
占用_____1___和____2 ___个字节。
285
#include "stdafx.h"
#define SQR(X) X*X
int main(int argc, char* argv[])

int a = 10;
int k = 2;
int m = 1;
a /= SQR(k+m)/SQR(k+m); 
printf("%d\n",a); 
return 0;
}
这道题目的结果是什么啊?
define 只是定义而已,在编择时只是简单代
换 X*X 而已,并不经过算术法则的
a /= (k+m)*(k+m)/(k+m)*(k+m);
=>a /= (k+m)*1*(k+m);
=>a = a/9;
=>a = 1;
286. 以面向对象方法构造的系统,其基本单
位是_____对象___。
287. 每个对象都是所属类的一个__实例__。
288. C++支持两种多态性:___编译____时的
多态性和____运行__时的多态性。
289. 在 C++中,编译时的多态性是通过___重
载___实现的,而运行时的多态性则是通过___
虚函数____实现的。
290. 对于类中定义的任何成员,其隐含访问
权限为___ private(或私有)__。
291. 对于结构中定义的任何成员,其隐含访
问权限为__ public(或公有)_。
292. 若在类的定义体中给出了一个成员函数
的完整定义,则该函数属于__内联__函数。
293. 为了避免在调用成员函数时修改对象中
的任何数据成员,则应在定义该成员函数时,
在函数头的后面加上__ const __关键字。
294. 若只需要通过一个成员函数读取数据成
员的值,而不需要修改它,则应在函数头的后
面加上__ const __关键字。
295.判断一个字符串是不是回文
int IsReverseStr(char *aStr)
{
int i,j;
int found=1;
if(aStr==NULL)
return -1;
j=strlen(aStr);
for(i=0;i<j/2;i++)
if(*(aStr+i)!=*(aStr+j-i-1))
{
found=0;
break;
}
return found;
}
296..写出判断 ABCD 四个表达式的是否正确, 
若正确, 写出经过表达式中 a 的值(3 分)
int a = 4;
(A)a += (a++); (B) a += (++a) ;(C) (a++) 
+= a;(D) (++a) += (a++);
a = ?
答:C 错误,左侧不是一个有效变量,不能赋
值,可改为(++a) += a;
改后答案依次为 9,10,10,11
298.动态连接库的两种方式?
答:调用一个 DLL 中的函数有两种方法:
1 . 载 入 时 动 态 链 接 ( load-time dynamic 
linking),模块非常明确调用某个导出函数,
使得他们就像本地函数一样。这需要链接时链
接那些函数所在 DLL 的导入库,导入库向系统
提供了载入 DLL 时所需的信息及 DLL 函数定位。
2 . 运 行 时 动 态 链 接 ( run-time dynamic 
linking),运行时可以通过 LoadLibrary 或
Loa
dLibraryEx 函数载入 DLL。DLL 载入后,模块
可以通过调用 GetProcAddress 获取 DLL 函数
的出口地址,然后就可以通过返回的函数指针
调用 DLL 函数了。如此即可避免导入库文件了。
299.请写出下列代码的输出内容
#i nclude
main()
{
int a,b,c,d;
a=10;
b=a++;
c=++a;
d=10*a++;
printf("b,c,d:%d,%d,%d",b,c,d);
return 0;
}
答 、10,12,120
300.设有以下说明和定义:
typedef union {long i; int k[5]; char c;} 
DATE;
struct data { int cat; DATE cow; double 
dog;} too;
DATE max;
则语句 printf("%d",sizeof(struct 
date)+sizeof(max));的执行结果是?
答 、结果是:___52____。DATE 是一个 union, 
变 量 公 用 空 间 . 里 面 最 大 的 变 量 类 型 是
int[5], 占用 20 个字节. 所以它的大小是 20
data 是一个 struct, 每个变量分开占用空间. 
依次为 int4 + DATE20 + double8 = 32.
所以结果是 20 + 32 = 52.
当然...在某些 16 位编辑器下, int 可能是 2
字节,那么结果是 int2 + DATE10 + double8 = 
20
301. 以下代码有什么问题?
cout << (true?1:"1") << endl;
答:三元表达式“?:”问号后面的两个操作
数必须为同一类型。
302.以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2 定义出错,size2 非编译器期间常量,
而数组定义要求长度必须为编译期常量。
303.以下反向遍历 array 数组的方法有什么
错误?
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; 
i>=0; --i ) // 反向遍历 array 数组
{
cout << array[i] << endl;
}
答:首先数组定义有误,应加上类型参数:
vector<int> array。其次 vector::size_type
被定义为 unsigned int,即无符号数,这样做
为循环变量的 i 为 0 时再减 1 就会变成最大的
整数,导致循环失去控制。
304. 以下代码中的输出语句输出 0 吗,为什
么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;
答:不能。在默认构造函数内部再调用带参的
构造函数属用户行为而非编译器行为,亦即仅
执行函数调用,而不会执行其后的初始化表达
式。只有在生成对象时,初始化表达式才会随
相应的构造函数一起调用。

1. 指出以下变量数据存储位置
全局变量
int(*g_pFun)(int);g_pFun=myFunction;g_p
Fun 存储的位置(A ) 为全局的函数指针
指向空间
的位置( B) 所有函数代码位于 TEXT 段
函数内部变量 static int nCount; ( A) 静
态变量总是在 DATA 段或 BSS 段中
函数内部变量 char p[]=”AAA”; p 指向空
间的位置( C) 局域变量的静态数组,空间在
Stack 中
函数内部变量 char *p=”AAA”; p 指向空
间的位置( E) ,”AAA”为一字符常量空间,不
同编译器有不同处理方法,大部分保存在
TEXT(代码段中),也有编译的 rodata 段中
函数内部变量 char *p=new char; p 的位置(C ) 
指向空间的位置(D ) 所有 malloc 空间来自于
heap(堆)
A. 数据段
B. 代码段
C. 堆栈
D. 堆
E. 不一定, 视情况而定
以上知识参见 C 语言变量的作用域相关课件
2. 以下程序的输出结果为 ( )
#include <iostream>
main( )
{
using namespace std;
int num[5]={1,2,3,4,5};
cout <<*((int *)(&num+1)-1) 
<<endl;
}
A. 1 B. 2 C. 3 D. 4
E. 5 F. 0 G. 未初始化内存,无
法确定
在 C 语言中,一维数组名表示数组的首地址,而
且是一个指针.如上例 num,
对&num,表示指针的指针.意味着这里强制转
换为二维数组指针.
这样 &num+1 等同于 num[5][1],为代码空间. 
(&num+1)-1 表示 num[4][0].即 num[4].所以
这里答案是 E.
扩展题目:
 *((int *)(num+1)-1) 的值是多少? 
 Num 是首指针,num+1 是第二个元素指针,-1
后又变成首指针.所以这里是答案是 num[0]
即,A.1
 
3. 以下哪些是程序间可靠的通讯方式( C ),
哪些可以用于跨主机通讯( C,D ,F).Windows
命名管道跨机器也可跨机器.
A. 信号 B. 管道 C. TCP
D. UDP E. PIPE F,.串口 I/O
4. class a
{
public:
virtual void funa( ); 
virtual void funb( );
void fun( );
static void fund( );
static int si;
private:
int i;
char c; 
};
问: 在 32 位编译器默认情况下,sizeof(a)等
于( )字节?
A. 28 B. 25 C.24 D. 20 
E. 16 F.12 G. 8
答案在 VC++下是 12. 这里需要考虑三个问题,
一是虚函数表 vtable 的入口表地址,二是字节
对齐.三 ,静态成员是所有对象共享,不计入
sizeof 空间.
在大部分 C++的实现中,带有虚函数的类的前 4
个 BYTE 是虚函数 vtable 表的这个类入口地址.
所以sizeof必须要加入这个4个byte的长度,
除此外,类的 sizoef()为所有数据成员总的
sizeof 之和,这里是 int i,和 char c.其中
char c 被字节对齐为 4.这样总长度为
Sizeof(a) = 
sizeof(vtable)+size(int)+sizeof(char + 
pad) = 12;
5. 32 位 Windows 系统或 Linux 系统下
 struct
{
char a;
char b;
char c;
}A;
struct 
{
short a;
short b;
short c;
}B;
struct
{
short a;
long b;
char c;
}C;
printf(“%d,%d,%d\n”,sizeof(A),sizeof(
B),sizeof(C)); 的执行结果为: ( )
A. 3,6,7 B. 3,6,8 C. 4,8,12
D. 3,6,12 E. 4,6,7 F. 4,8,9
C 语法的字节对齐规则有两种情况要字节对齐, 
在 VC++,gcc 测试都是如此
1) 对同一个数据类型(short,int,long)发
生了跨段分布,(在 32CPU 里,即一个数据类型
分布在两个段中)才会发生字节对齐. 
2) 数据类型的首部和尾部必须有其一是与 4
对齐.而且违反上一规则.
⚫ Sizeof(A),sizeof(B)虽然总字节数
不能被4整除.但刚好所有数据平均分布在以4
为单位的各个段中.所以无需字节对齐,所以
结果是 3 和 6
⚫ struct {char a;char b;char c;char 
d;char e;}F; 的 sizoef(F)是等于 5.
⚫ 用以下实例更加清楚
struct {
char a[20];
short b;
}A;
struct {
 char a[21];
 short b;
}B;
Sizeof(A)=22,sizoef(B)=24.因为前者没有
发生跨段分布.后者,如果不字节对齐.a[21]
占用最后一个段的首地址,b 无法作到与首部
与尾部与 4 对齐,只能在 a[21]与 b 之间加入一
个 byte,使用 b 的尾部与 4 对齐.
⚫ C 就是比较好理解.要补多个成 12
6. 依据程序,以下选择中那个是对的? ( )
class A
{
int m_nA;
}; 
class B
{
int m_nB; 
};
class C:public A,public B
{
int m_nC;
};
void f (void) 
{
C* pC=new C;
B* pB=dynamic_cast<B*>(pC);
A* pA=dynamic_cast<A*>(pC);
}
A. pC= =pB,(int)pC= =(int)B
B. pC= =pB,(int)pC!=(int)pB
C. pC!=pB,(int)pC= =(int)pB
D. pC!=pB,(int)pC!=(int)pB
这里主要考多态..将程序变为如下比较易懂
#include <stdio.h>
class A
{
public:
int m_nA;
}; 
class B
{
public:
int m_nB; 
};
class C:public A,public B
{
public:
int m_nC;
};
void f (void) 
{
C* pC=new C;
B* pB=dynamic_cast<B*>(pC);
A* pA=dynamic_cast<A*>(pC);
}
void f1 (void) 
{
C* pC=new C;
pC->m_nA = 1;
 pC->m_nB = 2;
pC->m_nC = 3;
B* pB=dynamic_cast<B*>(pC);
A* pA=dynamic_cast<A*>(pC);
printf("A=%x,B=%x,C=%x,iA=%d,iB=%d,i
C=%d\n",pA,pB,pC,(int)pA,(int)pB,(int)p
C);
}
void test1();
int main()
{
// test1();
f1();
 getchar();
 return 0;
}
以上程序输出:
A=4318d0,B=4318d4,C=4318d0,iA=4397264,i
B=4397268,iC=4397264 
即C从,A,B继承下来,由下图可以知道 pA=pC.
而 pB 强制转换后,只能取到 C 中 B 的部分.所
以 pB 在 pC 向后偏移 4 个 BYTE,(即 m_nA)的空

 
7, 请写出能匹配” [10]:dddddd ”和”
[9]:abcdegf ”,不匹配”[a]:xfdf ”的正则
表达式________,linux 下支持正则的命令
有:___find,grep_________
8.如下程序:
 int i=1,k=0;
long *pl=NULL;
char *pc=NULL;
if(k++&&i++)
k++, pl++, pc++;
B
A
m_nC
if(i++||k++)
i++, pl++, pc++;
printf("i=%d,k=%d,pl=%ld,pc=%ld\n",i
,k,(long)pl,(long)pc);
 打 印 结 果 为
__i=3,k=1,pl=4,pc=1________
主要测试逻辑表达式的短路操作.
&&操作中,前一个表达式为 0,后一表达式不执

||操作中, 前一个表达式为 1,后一表达式不
执行
9. 以下程序的输出为______________
 #include<iostream>
 using std::cout;
 class A
 {
 public:
 void f(void){
 cout<< ”A::f” <<’ ‘;
 }
 virtual void g(void)
 {
 cout <<”A::g” << ‘ ‘;
 }
};
class B : public A
{
 public:
 void f(void)
 {
 cout << “B :: f “ << ‘ ‘;
 }
 void g(void)
 {
 cout << “B:: g “ << ‘ ‘;
 }
};
int main()
{
 A* pA =new B;
 pA->f();
 pA->g();
 B* pB = (B*)pA;
 pB->f();
 pB->g();
}
A::f B:: g B :: f B:: g 
多态中虚函数调用.
f()为非虚函数,这样强制转换后,执行本类的
同名函数.
G()为虚函数,指针总是执行虚函数,这就是多
态..
10.下列代码的作用是删除 list lTest 中值为
6 的元素:
list<int> :: iterator Index = 
ITest .begin();
for( ; Index != ITest .end(); ++ Index)
{
 if((*Index) = = 6)
 {
 ITest .erase(Index);
 }
}
请 问 有 什 么 错 误 ____ Index = 
ITest .erase(Index);___________________
_,
STL 的游标处理,erase 已经将 Index 破坏掉,
需要用新的 Index,否则下一循环的++Index 被
破坏掉
请写出正确的代码,或者在原代码上修正.
11.找错误_以下程序:
char* ptr = malloc(100);
if(!ptr)
{

}

//ptr 指向的空间不够需要重新分配
ptr = realloc(ptr,200); 
if(!ptr)
{

}

请 问 有 什 么 错 误 ___if(ptr 
==NULL)____________________, 请写出正确
的代码,或者在原代码上修正.
12.以下为 window NT 下 32 位 C++程序,请填
写如下值
class myclass
{
 int a ;
 int b;
};
 char *p = “hello”;
 char str[] = “world”;
 myclass classes[2];
void *p2= malloc(100);
sizeof(p)=_4__
sizeof(str)=_6_
sizeof(classes)=_16__
sizeof(p2)=_4___
13.直接在以下程序中的错误的行数后的填空
栏中打叉
程序 1:
int main(void)
{
int i=10;_____
int *const j=&i;_______
(*j)++;____
j++;___*_____
}
程序 2:
int main(void)
{
int i=20;_____
const int *j=&i;_________
*j++;______
(*j)++;____*____
}
主要考 const 出现在*前后不同含意,const 
在*后表示指针本身不能改,const 在*前面指
针内容不能改,程序 1 中 j 不能修改指针,所以
j++是错,程序 2,j 不能改改内容,所以
14.用 C/C++代码实现以下要求:从 1-100 中挑
选出 10 个不同的数字,请把可能的所有组合打
印出来.
15.有一个非常大的全局数组 int a[],长度 n
超过 2 的 24 次方,写一个针对该数组的查找算
法 unsigned search(int value)(返回值下标),
插入算法 insert(int value,unsigned index).
再次注意该数组的长度很长.
题目不太清,可能可以把数值本身作下标.并
且按顺序排序.
16.有两个单向链表,表头 pHeader1,pHeader2,
请写一个函数判断这两个链表是否有交叉.如
果有交叉,给出交叉点.程序不能改变链表的
内容,可以使用额外的空间,时间复杂度尽量
小,最好给出两种解.(双重循环的解由于时间
复杂度高,不算正解).
 1.移动链表指针,如果最终
17.编写程序,将一棵树从根节点到叶子的所
有最长路径都打印出来.比如一棵树从跟到最
末端的叶子最远要经
过 4 个节点,那么就把到所有要经过 4 个节点
才能到达的叶子的搜索路径(所有途径节点)
都分别打印出来.
18.请分别对一个链表和一个数组进行排序,
并指出你排序的算法名称以及为何选择该算

数组可用交换法排序
19.有单向链表,其中节点结构为 Node{int 
value;Node *pNext};只知道指向某个节点的
指针 pCurrent;并且知道该节点不是尾节点,
有什么办法把他删除吗?要求不断链.
 从链表头开始,找到 pCurrent 上一个结点
pPrev, 然 后 pPrev->pNext = 
pCurrent->pNext;
20.问题 A:用什么方法避免 c/c++编程中的头
文件重复包含?问题 B:假设解决了重复包含问
题,但是又需要在两个不同的头文件中引用各
申明的类,应该如何处理?具体代码如下:
在头文件 Man.h 中
....
Class Cman
{
 ....
 CFace m_face;
};
....
在头文件 Face.h 中
...
Class CFace
{
 ...
 Cman *m_owner;
};
....
这样类 CMan.CFace 就相互引用了,该如何处理
呢?
1.#ifndef ….
#define …..
2.类的前向声明
21.多线程和单线程各自分别在什么时候效率
更高?
多线程在并发,并且各线程无需访问共享数
据情况详细最高
如果多线程过于频繁切换,或共享数据很多
情况下,使用单线程较好
22.在程序设计中,对公共资源(比如缓冲区等)
的操作和访问经常需要使用锁来进行保护,但
在大并发系统中过多的锁会导致效率很低,通
常有那些方法可以尽量避免或减少锁的使用?
减少锁的粒度,每次尽可能减少锁范围
采用队列处理,这样无需使用锁.
23.请详细阐述如何在 release 版本(windows
程序或 linux 程序都可以)中,查找段错误问
题.
可以用编译器生成 map 文件来定位源码.通过
地址反查源码
24.假设你编译链接 release 版本后得到一个
可执行程序(由多个 cpp 文件和 H 文件编译),
结果可执行程序文件非常大,你如何找到造成
文件太大的可能原因,可能的原因是什么?
使用一个已经初始化的巨大的全局数组
25.在编写 C++赋值运算符时有哪些要注意的
地方?
返回值,参数最好用引用
减少友元函数使用,移植有问题.
26.假设你是参与设计嫦娥卫星的嵌入式单板
软件工程师,其中有一个快速搜索可能要用到
哈希变或者平衡二叉树,要求不管什么条件下,
单板必须在指定的短时间内有输出,你会采取
那种算法?为什么用这种算法,为什么不用另
一种算法?
HASH.HASH 访问速度较快.
27.strcpy()容易引起缓冲区溢出问题,请问
有什么函数可以替代以减少风险,为什么?
strncpy
28. 请指出
spinlock,mutex,semaphore,critical 
section 的作用与区别,都在哪些场合使用.
spin_lock Linux 内核自旋锁. Mutex Windows 
互质量 , semaphore POSIX ,critical 
section Windows
29.在哪些方法使阻塞模式的 recv 函数在没有
收到数据的情况下返回(不能将 socket 修改为
非阻塞模式)请描述得详细点.
使用 select
30.有 3 个红色球,2 个白色球,1 个绿色球.取
出两个不同颜色的球就能变成两个第三种颜
色的球(比如:取出 1 红球,1 白球,就能变成 2
个绿球).问,最少几次变化能将所有球都变成
同一颜色,说明步骤和原因?
31.单向链表的反转是一个经常被问到的一个
面试题,也是一个非常基础的问题。比如一个
链表是这样的: 1->2->3->4->5 通过反转后
成为 5->4->3->2->1。
最容易想到的方法遍历一遍链表,利用一
个辅助指针,存储遍历过程中当前指针指向的
下一个元素,然后将当前节点元素的指针反转
后,利用已经存储的指针往后面继续遍历。源
代码如下:
1. struct linka { 
2. int data; 
3. linka* next; 
4. }; 
5. void reverse(linka*& head) { 
6. if(head ==NULL) 
7.
return; 
8. linka *pre, *cur, *ne; 
9. pre=head; 
10. cur=head->next; 
11. while(cur) 
12. { 
13. ne = cur->next; 
14. cur->next = pre; 
15. pre = cur; 
16. cur = ne; 
17. } 
18. head->next = NULL; 
19. head = pre; 
20. } 
还有一种利用递归的方法。这种方法的基
本思想是在反转当前节点之前先调用递归函
数反转后续节点。源代码如下。不过这个方法
有一个缺点,就是在反转后的最后一个结点会
形成一个环,所以必须将函数的返回的节点的
next 域置为 NULL。因为要改变 head 指针,所
以我用了引用。算法的源代码如下:
1. linka* reverse(linka* p,linka*& h
ead) 
2. { 
3. if(p == NULL || p->next == NULL) 
4. { 
5. head=p; 
6. return p; 
7. } 
8. else 
9. { 
10. linka* tmp = reverse(p->next,
head); 
11. tmp->next = p; 
12. return p; 
13. } 
14. } 
32.已知 String 类定义如下:
class String
{
public:
String(const char *str = NULL); // 通用
构造函数
String(const String &another); // 拷贝
构造函数
~ String(); // 析构函数
String & operater =(const String &rhs);
// 赋值函数
private:
char *m_data; // 用于保存字符串
};
尝试写出类的成员函数实现。
答案:
String::String(const char *str)
{
if ( str == NULL ) //strlen 在参数为 NULL
时会抛异常才会有这步判断
{
m_data = new char[1] ;
m_data[0] = '\0' ;
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data,str);
}

String::String(const String &another)
{
m_data = new char[strlen(another.m_data)
+ 1];
strcpy(m_data,other.m_data);
}
String& String::operator =(const String
&rhs)
{
if ( this == &rhs)
return *this ;
delete []m_data; //删除原来的数据,新开
一块内存
m_data = new char[strlen(rhs.m_data) + 
1];
strcpy(m_data,rhs.m_data);
return *this ;
}
String::~String()
{
delete []m_data ;
}
33.求下面函数的返回值(微软) 
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;

假定 x = 9999。 答案:8
思路:将 x 转化为 2 进制,看含有的 1 的个数。
34. 什么是“引用”?申明和使用“引用”要
注意哪些问题? 
答:引用就是某个目标变量的“别名”(alias),
对应用的操作与对变量直接操作效果完全相
同。申明一个引用的时候,切记要对其进行初
始化。引用声明完毕后,相当于目标变量名有
两个名称,即该目标原名称和引用名,不能再
把该引用名作为其他变量名的别名。声明一个
引用,不是新定义了一个变量,它只表示该引
用名是目标变量名的一个别名,它本身不是一
种数据类型,因此引用本身不占存储单元,系
统也不给引用分配存储单元。不能建立数组的
引用。
45. 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一
样的。这时,被调函数的形参就成为原来主调
函数中的实参变量或对象的一个别名来使用,
所以在被调函数中对形参变量的操作就是对
其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并
没有产生实参的副本,它是直接对实参操作;
而使用一般变量传递函数的参数,当发生函数
调用时,需要给形参分配存储单元,形参变量
是实参变量的副本;如果传递的是对象,还将
调用拷贝构造函数。因此,当参数传递的数据
较大时,用引用比用一般变量传递参数的效率
和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到
与使用引用的效果,但是,在被调函数中同样
要给形参分配存储单元,且需要重复使用"*指
针变量名"的形式进行运算,这很容易产生错
误且程序的阅读性较差;另一方面,在主调函
数的调用点处,必须用变量的地址作为实参。
而引用更容易使用,更清晰。
36. 在什么时候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保护
传递给函数的数据不在函数中被改变,就应使
用常引用。常引用声明方式:const 类型标识
符 &引用名=目标变量名;
例 1
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确
例 2
string foo( );
void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));
bar("hello world");
原因在于 foo( )和"hello world"串都会产生
一个临时对象,而在 C++中,这些临时对象都
是 const 类型的。因此上面的表达式就是试图
将一个 const 类型的对象转换为非 const 类型,
这是非法的。
引用型参数应该在能被定义为 const 的情况下,
尽量定义为 const 。
37. 将“引用”作为函数返回值类型的格式、
好处和需要遵守的规则? 
格式:类型标识符 &函数名(形参列表及类型
说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注
意:正是因为这点原因,所以返回一个局部变
量的引用是不可取的。因为随着该局部变量生
存期的结束,相应的引用也会失效,产生 run
time error!
注意事项:
(1)不能返回局部变量的引用。这条可以参
照 Effective C++[1]的 Item 31。主要原因是
局部变量会在函数返回后被销毁,因此被返回
的引用就成为了"无所指"的引用,程序会进入
未知状态。
(2)不能返回函数内部 new 分配的内存的引
用。这条可以参照 Effective C++[1]的 Item 
31。虽然不存在局部变量的被动销毁问题,可
对于这种情况(返回函数内部 new 分配内存的
引用),又面临其它尴尬局面。例如,被函数
返回的引用只是作为一个临时变量出现,而没
有被赋予一个实际的变量,那么这个引用所指
向的空间(由 new 分配)就无法释放,造成 m
emory leak。
(3)可以返回类成员的引用,但最好是 cons
t。这条原则可以参照 Effective C++[1]的 It
em 30。主要原因是当对象的属性是与某种业
务规则(business rule)相关联的时候,其
赋值常常与某些其它属性或者对象的状态有
关,因此有必要将赋值操作封装在一个业务规
则当中。如果其它对象可以获得该属性的非常
量引用(或指针),那么对该属性的单纯赋值
就会破坏业务规则的完整性。
(4)流操作符重载返回值申明为“引用”的
作用:
流操作符<<和>>,这两个操作符常常希望被连
续使用,例如:cout << "hello" << endl;
因此这两个操作符的返回值应该是一个仍然
支持这两个操作符的流引用。可选的其它方案
包括:返回一个流对象和返回一个流对象指针。
但是对于返回一个流对象,程序必须重新(拷
贝)构造一个新的流对象,也就是说,连续的
两个<<操作符实际上是针对不同对象的!这无
法让人接受。对于返回一个流指针则不能连续
使用<<操作符。因此,返回一个流对象引用是
惟一选择。这个唯一选择很关键,它说明了引
用的重要性以及无可替代性,也许这就是 C++
语言中引入引用这个概念的原因吧。赋值操作
符=。这个操作符象流操作符一样,是可以连
续使用的,例如:x = j = 10;或者(x=10)=10
0;赋值操作符的返回值必须是一个左值,以便
可以被继续赋值。因此引用成了这个操作符的
惟一返回值选择。
例 3
#i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以 put(0)函数值作为左值,等
价于 vals[0]=10;
put(9)=20; //以 put(9)函数值作为左值,等
价于 vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return 
error; }
}
(5)在另外的一些操作符中,却千万不能返
回引用:+-*/ 四则运算符。它们不能返回引
用,Effective C++[1]的 Item23 详细的讨论
了这个问题。主要原因是这四个操作符没有 s
ide effect,因此,它们必须构造一个对象作
为返回值,可选的方案包括:返回一个对象、
返回一个局部变量的引用,返回一个 new 分配
的对象的引用、返回一个静态对象引用。根据
前面提到的引用作为返回值的三个规则,第 2、
3 两个方案都被否决了。静态对象的引用又因
为((a+b) == (c+d))会永远为 true 而导致错
误。所以可选的只剩下返回一个对象了。
38. “引用”与多态的关系? 
引用是除指针外另一个可以产生多态效果的
手段。这意味着,一个基类的引用可以指向它
的派生类实例。
例 4
Class A; Class B : Class A{...}; B b; A
& ref = b;
39. “引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它
所指向的变量间接操作。程序中使用指针,程
序的可读性差;而引用本身就是目标变量的别
名,对引用的操作就是对目标变量的操作。此
外,就是上面提到的对函数传 ref 和 pointer
的区别。
40. 什么时候需要“引用”?
流操作符<<和>>、赋值操作符=的返回值、拷
贝构造函数的参数、赋值操作符=的参数、其
它情况都推荐使用引用。
以上 2-8 参考:http://blog.csdn.net/wfwd
/archive/2006/05/30/763551.aspx
41. 结构与联合有和区别?
1. 结构和联合都是由多个不同的数据类型成
员组成, 但在任何同一时刻, 联合中只存放
了一个被选中的成员(所有成员共用一块地址
空间), 而结构的所有成员都存在(不同成员
的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成
员重写, 原来成员的值就不存在了, 而对于
结构的不同成员赋值是互不影响的。
42. 下面关于“联合”的题目的输出? 
a)
#i nclude <stdio.h>
union
{
int i;
char x[2];
}a;
void main()
{
a.x[0] = 10;
a.x[1] = 1;
printf("%d",a.i);
}
答案:266 (低位低地址,高位高地址,内存
占用情况是 Ox010A)
b)
main()
{
union{ /*定义一个联合*/
int i;
struct{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}number;
number.i=0x4241; /*联合成员赋值*/
printf("%c%c\n", number.half.first, mum
ber.half.second);
number.half.first='a'; /*联合中结构成员
赋值*/
number.half.second='b';
printf("%x\n", number.i);
getch();
}
答案: AB (0x41 对应'A',是低位;Ox42 对应
'B',是高位)
6261 (number.i和number.half共用一块地址
空间)
43. 已知 strcpy 的函数原型:char *strcpy(c
har *strDest, const char *strSrc)其中 st
rDest 是目的字符串,strSrc 是源字符串。
不调用 C++/C 的字符串库函数,请编写函数 
strcpy。 
答案:
char *strcpy(char *strDest, const char 
*strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ;
while( (*strDest++ = *strSrc++) != ‘\0’)
return tempptr ;
}
44. .h 头文件中的 ifndef/define/endif 的
作用? 
答:防止该头文件被重复引用。
45. #i nclude<file.h> 与 #i nclude "f
ile.h"的区别? 
答:前者是从 Standard Library 的路径寻找
和引用 file.h,而后者是从当前工作路径搜寻
并引用 file.h。
46.在 C++ 程序中调用被 C 编译器编译后的函
数,为什么要加 extern “C”?
首先,作为 extern 是 C/C++语言中表明函数和
全局变量作用范围(可见性)的关键字,该关
键字告诉编译器,其声明的函数和变量可以在
本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它
模块引用的函数和全局变量以关键字 extern
声明。例如,如果模块 B 欲引用该模块 A 中定
义的全局变量和函数时只需包含模块 A 的头文
件即可。这样,模块 B 中调用模块 A 中的函数
时,在编译阶段,模块 B 虽然找不到该函数,
但是并不会报错;它会在连接阶段中从模块 A
编译生成的目标代码中找到此函数
extern "C"是连接申明(linkage declaratio
n),被 extern "C"修饰的变量和函数是按照 C
语言方式编译和连接的,来看看 C++中对类似 C
的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,
而过程式语言 C 则不支持。函数被 C++编译后
在符号库中的名字与 C 语言的不同。例如,假
设某个函数的原型为:
void foo( int x, int y );
 
该函数被 C 编译器编译后在符号库中的名字为
_foo,而 C++编译器则会产生像_foo_int_int
之类的名字(不同的编译器可能生成的名字不
同,但是都采用了相同的机制,生成的新名字
称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函
数参数数量及类型信息,C++就是靠这种机制
来实现函数重载的。例如,在 C++中,函数 vo
id foo( int x, int y )与 void foo( int x,
float y )编译生成的符号是不相同的,后者
为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还
支持类成员变量和全局变量。用户所编写程序
的类成员变量可能与全局变量同名,我们以".
"来区分。而本质上,编译器在进行编译时,
与函数的处理相似,也为类中的变量取了一个
独一无二的名字,这个名字与用户程序中同名
的全局变量名字不同。
未加 extern "C"声明时的连接方式
假设在 C++中,模块 A 的头文件如下:
// 模块 A 头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif 
在模块 B 中引用该函数:
// 模块 B 实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
 
实际上,在连接阶段,连接器会从模块 A 生成
的目标文件 moduleA.obj 中寻找_foo_int_int
这样的符号!
加 extern "C"声明后的编译和连接方式
加 extern "C"声明后,模块 A 的头文件变为:
// 模块 A 头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif 
在模块 B 的实现文件中仍然调用 foo( 2,3 ),
其结果是:
(1)模块 A 编译生成 foo 的目标代码时,没
有对其名字进行特殊处理,采用了 C 语言的方
式;
(2)连接器在为模块B的目标代码寻找foo(2,
3)调用时,寻找的是未经修改的符号名_foo。
如果在模块 A 中函数声明了 foo 为 extern "C
"类型,而模块 B 中包含的是 extern int foo
( int x, int y ) ,则模块 B 找不到模块 A
中的函数;反之亦然。
所以,可以用一句话概括 extern “C”这个声
明的真实目的(任何语言中的任何语法特性的
诞生都不是随意而为的,来源于真实世界的需
求驱动。我们在思考问题时,不能只停留在这
个语言是怎么做的,还要问一问它为什么要这
么做,动机是什么,这样我们可以更深入地理
解许多问题):实现 C++与 C 及其它语言的混
合编程。 
明白了 C++中 extern "C"的设立动机,我们下
面来具体分析 extern "C"通常的使用技巧:
extern "C"的惯用法
(1)在 C++中引用 C 语言中的函数和变量,在
包含 C 语言头文件(假设为 cExample.h)时,
需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在 C 语言的头文件中,对其外部函数只能指
定为 extern 类型,C 语言中不支持 extern "C
"声明,在.c 文件中包含了 extern "C"时会出
现编译语法错误。
C++引用 C 函数例子工程中包含的三个文件的
源代码如下:
/* c 语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c 语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用 add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果 C++调用一个 C 语言编写的.DLL 时,当包
括.DLL 的头文件或声明接口函数时,应加 ext
ern "C" { }。
(2)在 C 中引用 C++语言中的函数和变量时,
C++的头文件需添加 extern "C",但是在 C 语
言中不能直接引用声明了 extern "C"的该头
文件,应该仅将 C 文件中将 C++中定义的 exte
rn "C"函数声明为 extern 类型。
C 引用 C++函数例子工程中包含的三个文件的
源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C 实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.
h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
15 题目的解答请参考《C++中 extern “C”含
义深层探索》注解:
47. 关联、聚合(Aggregation)以及组合(Comp
osition)的区别?
涉及到 UML 中的一些概念:关联是表示两个类
的一般性联系,比如“学生”和“老师”就是
一种关联关系;聚合表示 has-a 的关系,是一
种相对松散的关系,聚合类不需要对被聚合类
负责,如下图所示,用空的菱形表示聚合关系:
从实现的角度讲,聚合可以表示为:
class A {...} class B { A* a; .....}
而组合表示 contains-a 的关系,关联性强于
聚合:组合类与被组合类有相同的生命周期,
组合类要对被组合类负责,采用实心的菱形表
示组合关系:
实现的形式是:
class A{...} class B{ A a; ...}
参考文章:http://blog.csdn.net/wfwd/arch
ive/2006/05/30/763753.aspx
http://blog.csdn.net/wfwd/archive/2006/
05/30/763760.aspx
48.面向对象的三个基本特征,并简单叙述之? 
1. 封装:将客观事物抽象成类,每个类对自
身的数据和方法实行 protection(private, p
rotected,public)
2. 继承:广义的继承有三种实现形式:实现
继承(指使用基类的属性和方法而无需额外编
码的能力)、可视继承(子窗体使用父窗体的
外观和实现代码)、接口继承(仅使用属性和
方法,实现滞后到子类实现)。前两种(类继
承)和后一种(对象组合=>接口继承以及纯虚
函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多
的他的子对象相等的技术,赋值之后,父对象
就可以根据当前赋值给它的子对象的特性以
不同的方式运作。简单的说,就是一句话:允
许将子类类型的指针赋值给父类类型的指针。
49. 重载(overload)和重写(overried,有的
书也叫做“覆盖”)的区别? 
常考的题目。从定义上来说:
重载:是指允许存在多个同名函数,而这些函
数的参数表不同(或许参数个数不同,或许参
数类型不同,或许两者都不同)。
重写:是指子类重新定义复类虚函数的方法。
从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名
函数的名称做修饰,然后这些同名函数就成了
不同的函数(至少对于编译器来说是这样的)。
如,有两个同名函数:function func(p:inte
ger):integer;和 function func(p:string):
integer;。那么编译器做过修饰后的函数名称
可能是这样的:int_func、str_func。对于这
两个函数的调用,在编译器间就已经确定了,
是静态的。也就是说,它们的地址在编译期就
绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父
类的虚函数后,父类指针根据赋给它的不同的
子类指针,动态的调用属于子类的该函数,这
样的函数调用在编译期间是无法确定的(调用
的子类的虚函数的地址无法给出)。因此,这
样的函数地址是在运行期绑定的(晚绑定)。
50. 多态的作用? 
主要是两个:1. 隐藏实现细节,使得代码能
够模块化;扩展代码模块,实现代码重用;2.
接口重用:为了类在继承和派生的时候,保
证使用家族中任一类的实例的某一属性时的
正确调用。
51. New delete 与 malloc free 的联系与区
别? 
答案:都是在堆(heap)上进行动态的内存操作。
用 malloc 函数需要指定内存分配的字节数并
且不能初始化对象,new 会自动调用对象的构
造函数。delete 会调用对象的 destructor,
而 free 不会调用对象的 destructor.
52. 有哪几种情况只能用 intialization lis
t 而不能用 assignment?
答案:当类中含有 const、reference 成员变
量;基类的构造函数都需要初始化表。
53. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强
制转换(用 reinterpret cast)。C#是类型安
全的。
54. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在 main 函数之
前执行。
55. 描述内存分配方式以及它们的区别?
1) 从静态存储区域分配。内存在程序编译的
时候就已经分配好,这块内存在程序的整个运
行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部
变量的存储单元都可以在栈上创建,函数执行
结束时这些存储单元自动被释放。栈内存分配
运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在
运行的时候用 malloc 或 new 申请任意多少的
内存,程序员自己负责在何时用 free 或 dele
te 释放内存。动态内存的生存期由程序员决
定,使用非常灵活,但问题也最多。
56.struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的
成员默认是私有的。struct 和 class 在其他
方面是功能相当的。
从感情上讲,大多数的开发者感到类和结构有
很大的差别。感觉上结构仅仅象一堆缺乏封装
和功能的开放的内存位,而类就象活的并且可
靠的社会成员,它有智能服务,有牢固的封装
屏障和一个良好定义的接口。既然大多数人都
这么认为,那么只有在你的类有很少的方法并
且有公有数据(这种事情在良好设计的系统中
是存在的!)时,你也许应该使用 struct 关
键字,否则,你应该使用 class 关键字。
57.当一个类 A 中没有生命任何成员变量与成
员函数,这时 sizeof(A)的值是多少,如果不是
零,请解释一下编译器为什么没有让它为零。
(Autodesk) 
答案:肯定不是零。举个反例,如果是零的话,
声明一个 class A[10]对象数组,而每一个对
象占用的空间是零,这时就没办法区分 A[0],
A[1]…了。
58. 在 8086 汇编下,逻辑地址和物理地址是
怎样转换的?(Intel) 
答案:通用寄存器给出的地址,是段内偏移地
址,相应段寄存器地址*10H+通用寄存器内地
址,就得到了真正要访问的地址。
59. 比较 C++中的 4 种类型转换方式?
请参考:http://blog.csdn.net/wfwd/archiv
e/2006/05/30/763785.aspx,重点是 static_
cast, dynamic_cast 和 reinterpret_cast 的
区别和应用。
60.分别写出 BOOL,int,float,指针类型的变
量 a 与“零”的比较语句。
答案:
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NU
LL)
61.请说出 const 与#define 相比,有何优点? 
答案:1) const 常量有数据类型,而宏常量
没有数据类型。编译器可以对前者进行类型安
全检查。而对后者只进行字符替换,没有类型
安全检查,并且在字符替换可能会产生意料不
到的错误。
2) 有些集成化的调试工具可以对 const 常量
进行调试,但是不能对宏常量进行调试。
62.简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),
要么在栈上被创建。指针可以随时指向任意类
型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意 p 指向常量字
符串
p[0] = ‘X’; // 编译器不能发现该错误,
运行时错误
(2) 用运算符 sizeof 可以计算出数组的容量
(字节数)。sizeof(p),p 为指针得到的是一
个指针变量的字节数,而不是 p 所指的内存容
量。C++/C 语言没有办法知道指针所指的内存
容量,除非在申请内存时记住它。注意当数组
作为函数的参数进行传递时,该数组自动退化
为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不
是 100 字节
}
63.类成员函数的重载、覆盖和隐藏区别?
答案:
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有 virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名
的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,
但是参数不同。此时,不论有无 virtual 关键
字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,
并且参数也相同,但是基类函数没有 virtual 
关键字。此时,基类的函数被隐藏(注意别与
覆盖混淆)
64. 如何打印出当前源文件的文件名以及源
文件的当前行号? 
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏
并不是在某个文件中定义的,而是由编译器定
义的。
65. main 主函数执行完毕后,是否可能会再
执行一段代码,给出说明? 
答案:可以,可以用_onexit 注册一个函数,
它会在 main 之后执行 int fn1(void), fn2(v
oid), fn3(void), fn4 (void);
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first.\n" );
}
int fn1()
{
printf( "next.\n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
The _onexit function is passed the addr
ess of a function (func) to be called w
hen the program terminates normally. Su
ccessive calls to _onexit create a regi
ster of functions that are executed in 
LIFO (last-in-first-out) order. The fun
ctions passed to _onexit cannot take pa
rameters.
66. 如何判断一段程序是由 C 编译程序还是
由 C++编译程序编译的? 
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
67.文件中有一组整数,要求排序后输出到另
一个文件中 
答案:
#i nclude<iostream>
#i nclude<fstream>
using namespace std;
void Order(vector<int>& data) //bubble 
sort
{
int count = data.size() ;
int tag = false ; // 设置是否需要继续冒
泡的标志位
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j
++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c:\\data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close(); //关闭输入文件流
Order(data);
ofstream out("c:\\result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close(); //关闭输出文件流
}
68. 链表题:一个链表的结点结构 
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知链表的头结点 head,写一个函数把这
个链表逆序 ( Intel)
Node * ReverseList(Node *head) //链表逆

{
if ( head == NULL || head->next == NULL
)
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知两个链表 head1 和 head2 各自有序,
请把它们合并成一个链表依然有序。(保留所
有结点,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(3)已知两个链表 head1 和 head2 各自有序,
请把它们合并成一个链表依然有序,这次要求
用递归方法进行。 (Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Nod
e *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,
head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2
->next);
}
return head ;
}
69. 分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i) //B(int) works as a co
nverter ( int -> instance of B)
{
cout<<"constructed by parameter " << da
ta <<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
(1) results:
int main(int argc, char* argv[]) constr
ucted by parameter 5
{ destructed B(5)形参析构
B t1 = Play(5); B t2 = Play(t1); des
tructed t1 形参析构
return 0; d
estructed t2 注意顺序!
} destructed t1
(2) results:
int main(int argc, char* argv[]) constr
ucted by parameter 5
{ destructed B(5)形参析构
B t1 = Play(5); B t2 = Play(10); con
structed by parameter 10
return 0; d
estructed B(10)形参析构
} destructed t2 注意顺序!
destructed t1
70. 写一个函数找出一个整数数组中,第二大
的数 (microsoft)
答案:
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int coun
t)
{
int maxnumber = data[0] ;
int sec_max = MINNUMBER ;
for ( int i = 1 ; i < count ; i++)
{
if ( data[i] > maxnumber )
{
sec_max = maxnumber ;
maxnumber = data[i] ;
}
else
{
if ( data[i] > sec_max )
sec_max = data[i] ;
}
}
return sec_max ;
}
71. 写一个在一个字符串(n)中寻找一个子串
(m)第一个位置的函数。
KMP 算法效率最好,时间复杂度是O(n+m)。
72. 多重继承的内存分配问题:
比如有 class A : public class B, public 
class C {}
那么 A 的内存结构大致是怎么样的?
这个是 compiler-dependent 的, 不同的实现
其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;
否则的话,相当复杂。
可以参考《深入探索 C++对象模型》,或者:
http://blog.csdn.net/wfwd/archive/2006/
05/30/763797.aspx
73. 如何判断一个单链表是有环的?(注意不
能用标志位,最多只能用两个额外指针)
struct node { char val; node* next;}
bool check(const node* head) {} //retur
n false : 无环;true: 有环
一种 O(n)的办法就是(搞两个指针,一个每
次递增一步,一个每次递增两步,如果有环的
话两者必然重合,反之亦然):
bool check(const node* head)
{
if(head==NULL) return false;
node *low=head, *fast=head->next;
while(fast!=NULL && fast->next!=NULL)
{
low=low->next;
fast=fast->next->next;
if(low==fast) return true;
}
return false;
}
74. 不 使 用 库 函 数 , 编 写 函 数 int 
strcmp(char *source, char *dest)相等返
回 0,不等返回-1
int StrCmp(char *source, char *dest)
{
assert(source !=NULL) ;
assert(dest!=NULL) ;
while(*source= 
=*dest&&*source&&*dest)
{
source++;
dest++;
}
return (*source!=*dest)?-1:0;
}
75.写一个函数,实现将一个字符串中的’\t’
替换成四个’*’, ’\t’个数不定。如 char 
*p=”ht\thdsf\t\ttt\tfds dfsw\t ew\t”,
替换后 p= ” ht****hdsf********tt****fds 
dfsw**** ew****”。
char *Replace(char *Sorce)
{
char *pTemp=Sorce;
int iCount=0;
int iSizeOfSorce=0;
while(*pTemp!='\0')
{
if('\t'==*pTemp)
iCount++;
pTemp++;
}
iSizeOfSorce=pTemp-Sorce;
char *pNewStr=new 
char[iSizeOfSorce+3*iCount*sizeof(char)
+1];
char *pTempNewStr=pNewStr;
pTemp=Sorce;
while(*pTemp!='\0')
{
if('\t'==*pTemp)
{
for(int iLoop=0; iLoop<4; 
iLoop++)
{
*pTempNewStr='*';
pTempNewStr++;
}
pTemp++;
}
else
{
*pTempNewStr=*pTemp;
pTemp++;
pTempNewStr++;
}
}
*pTempNewStr='\0';
return pNewStr;
}
76.写一函数实现将一个字符串中的数字字符
全部去掉。 
void RemoveNum(char strSrc[]) 

 char *p=strSrc; 
 char *q; 
 while(*p!='\0') 
 { 
 if(*p>='0'&&*p<='9') 
 { 
 q=p; 
 while(*q!='\0') 
 { 
 *q=*(q+1); 
 q++; 
 } 
 } 
 else 
 { 
 p++; 
 } 
 } 

77、链表节点结构如下:
struct STUDENT
{
long num;
float score;
STUDENT *pNext;
};
编写实现将两棵有序(按学号从小到大)的链
表合并的函数,要求合并后的链表有序(按学
号从小到大)
STUDENT *EmergeList(STUDENT 
*pHead1,STUDENT *pHead2)
{
 //取小者为表头
 STUDENT * pHead;
STUDENT *p1;
STUDENT *p2;
STUDENT *pCur;
STUDENT *pTemp;
 if (pHead1->num<= pHead2->num)
 {
 pHead = pHead1; 
p2 = pHead2;
p1=pHead1->pNext;
 }
 else
 {
 pHead = pHead2; 
p1 = pHead1;
p2=pHead2->pNext;
 }
pCur=pHead;
while(p1!=NULL&&p2!=NULL)
{
if(p2->num<p1->num)
{
pCur->pNext=p2;
p2=p2->pNext;
pCur=pCur->pNext;
}
else if(p2->num>p1->num)
{
pCur->pNext=p1;
p1=p1->pNext;
pCur=pCur->pNext;
}
else if(p2->num==p1->num)
{
pCur->pNext=p1;
p1=p1->pNext;
pCur=pCur->pNext;
pTemp= p2;
p2=p2->pNext;
delete pTemp;
pTemp = NULL;
}
}
if(NULL==p1)
{
pCur->pNext=p2;
}
else if(NULL==p2)
{
pCur->pNext=p1;
}
 
 return pHead;
}
78、封装 CMyString 类,要求声明和实现分开,
声明见 MyString.h,该类的声明可以添加内容,
完成 STLTest 文件夹下 main 文件的要求。
参见 STLTest Answer 文件夹
79.请你分别画出 OSI 的七层网络结构图和
TCP/IP 的五层结构图。
80.请你详细地解释一下 IP 协议的定义,在哪
个层上面?主要有什么作用?TCP 与 UDP 呢?
81.请问交换机和路由器各自的实现原理是什
么?分别在哪个层次上面实现的?
82.请问 C++的类和 C 里面的 struct 有什么区
别?
83.全局变量和局部变量有什么区别?是怎么
实现的?操作系统和编译器是怎么知道的?
84. 非 C++内建型别 A 和 B,在哪几种情况下
B 能隐式转化为 A?[C++中等
a. class B : public A { ……} // B 公有
继承自 A,可以是间接继承的
b. class B { operator A( ); } // B 实现
了隐式转化为 A 的转化
c. class A { A( const B& ); } // A 实现
了 non-explicit 的参数为 B(可以有其他带默
认值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操
作,虽不是正宗的隐式类型转换,但也可以勉
强算一个
85. 以下代码中的两个 sizeof 用法有问题吗?
[C 易]
void UpperCase( char str[] ) // 将 str 
中的小写字母转换成大写字母
{
 for( size_t i=0; i<sizeof(str)/s
izeof(str[0]); ++i )
 if( 'a'<=str[i] && str[i]<='
z' )
 str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str 字符长度为: " << sizeof(str)
/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
86. 以下代码有什么问题?[C 难]
void char2Hex( char c ) // 将字符以 16 进
制表示
{
 char ch = c/0x10 + '0'; if( ch >
'9' ) ch += ('A'-'9'-1);
 char cl = c%0x10 + '0'; if( cl >
'9' ) cl += ('A'-'9'-1);
 cout << ch << cl << ' ';
}
char str[] = "I love 中国";
for( size_t i=0; i<strlen(str); ++i )
 char2Hex( str[i] );
cout << endl;
87. 以下代码有什么问题?[C++易]
struct Test
{
 Test( int ) {}
 Test() {}
 void fun() {}
};
void main( void )
{
 Test a(1);
 a.fun();
 Test b();
 b.fun();
}
88. 以下代码有什么问题?[C++易]
cout << (true?1:"1") << endl;
89. 以下代码能够编译通过吗,为什么?[C++
易]
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
90. 以下代码中的输出语句输出 0 吗,为什么?
[C++易]
struct CLS
{
 int m_i;
 CLS( int i ) : m_i(i) {}
 CLS()
 {
 CLS(0);
 }
};
CLS obj;
cout << obj.m_i << endl;
91. C++中的空类,默认产生哪些类成员函数?
[C++易]
答:
class Empty
{
public:
 Empty(); 
 // 缺省构造函数
 Empty( const Empty& ); 
 // 拷贝构造函数
 ~Empty(); 
 // 析构函数
 Empty& operator=( const Empty& );
// 赋值运算符
 Empty* operator&(); 
 // 取址运算符
 const Empty* operator&() const; 
 // 取址运算符 const
};
92. 以下两条输出语句分别输出什么?[C++
难]
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)
a ) << endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)
b ) << endl; // 输出什么?
93. 以下反向遍历 array 数组的方法有什么错
误?[STL 易]
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1;
i>=0; --i ) // 反向遍历 array 数组
{
 cout << array[i] << endl;
}
94. 以下代码有什么问题?[STL 易]
typedef vector IntArray;
IntArray array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 2 );
array.push_back( 3 );
// 删除 array 数组中所有的 2
for( IntArray::iterator itor=array.begi
n(); itor!=array.end(); ++itor )
{
 if( 2 == *itor ) array.erase( it
or );

95. 写一个函数,完成内存之间的拷贝。[考
虑问题是否全面]
答:
void* mymemcpy( void *dest, const void 
*src, size_t count )
{
 char* pdest = static_cast<char*>
( dest );
 const char* psrc = static_cast<c
onst char*>( src );
 if( pdest>psrc && pdest<psrc+cou
t ) 能考虑到这种情况就行了
 {
 for( size_t i=count-1; i!=-1;
--i )
 pdest[i] = psrc[i];
 }
 else
 {
 for( size_t i=0; i<count; ++
i )
 pdest[i] = psrc[i];
 }
 return dest;
}
int main( void )
{
 char str[] = "0123456789";
 mymemcpy( str+1, str+0, 9 );
 cout << str << endl;
 system( "Pause" );
 return 0;
}
华为 C/C++笔试题(附答案)2008 年 02 月 15 日
星期五 18:001.写出判断 ABCD 四个表达式的
是否正确, 若正确, 写出经过表达式中 a 的
值(3 分)
96.int a = 4;
(A)a += (a++); (B) a += (++a) ;(C) (a++) 
+= a;(D) (++a) += (a++);
a = ?
答:C 错误,左侧不是一个有效变量,不能赋
值,可改为(++a) += a;
改后答案依次为 9,10,10,11
97.某 32 位系统下, C++程序,请计算 sizeof 
的值(5 分).
char str[] = “http://www.ibegroup.com/”
char *p = str ;
int n = 10;
请计算
sizeof (str ) = ?(1)
sizeof ( p ) = ?(2)
sizeof ( n ) = ?(3)
void Foo ( char str[100]){
请计算
sizeof( str ) = ?(4)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = ?(5)
答:(1)17 (2)4 (3) 4 (4)4 (5)4
98. 回答下面的问题. (4 分)
(1).头文件中的 ifndef/define/endif 干什
么用?预处理
答:防止头文件被重复引用
(2). # i nclude 和 # i nclude 
“filename.h” 有什么区别?
答:前者用来包含开发环境提供的库头文件,
后者用来包含自己编写的头文件。
(3).在 C++ 程序中调用被 C 编译器编译后的
函数,为什么要加 extern “C”声明?
答:函数和变量被 C++编译后在符号库中的名
字与 C 语言的不同,被 extern "C"修饰的变
量和函数是按照 C 语言方式编译和连接的。由
于编译后的名字不同,C++程序不能直接调
用 C 函数。C++提供了一个 C 连接交换指定符
号 extern“C”来解决这个问题。
(4). switch()中不允许的数据类型是?
答:实型
99. 回答下面的问题(6 分)
(1).Void GetMemory(char **p, int num){
*p = (char *)malloc(num);
}
void Test(void){
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:输出“hello”
(2). void Test(void){
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL){
strcpy(str, “world”);
printf(str);
}
}
请问运行 Test 函数会有什么样的结果?
答:输出“world”
100. char *GetMemory(void){
char p[] = "hello world";
return p;
}
void Test(void){
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:无效的指针,输出不确定
101. 编写 strcat 函数(6 分)
已知strcat函数的原型是char *strcat (char 
*strDest, const char *strSrc);
其中 strDest 是目的字符串,strSrc 是源字
符串。
(1)不调用 C++/C 的字符串库函数,请编写
函数 strcat
答:
VC 源码:
char * __cdecl strcat (char * dst, const 
char * src)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to 
end of dst */
return( dst ); /* return dst */
}
(2)strcat 能把 strSrc 的内容 连接到
strDest,为什么还要 char * 类型的返回值?
答:方便赋值给其他变量
102.MFC 中 CString 是类型安全类么?
答:不是,其它数据类型转换到 CString 可以
使用 CString 的成员函数 Format 来转换
103.C++中为什么用模板类。
答:(1)可用来创建动态增长和减小的数据结

(2)它是类型无关的,因此具有很高的可复
用性。
(3)它在编译时而不是运行时检查数据类型,
保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
104.CSingleLock 是干什么的。
答:同步多个线程对一个数据类的同时访问
105.NEWTEXTMETRIC 是什么。
答:物理字体结构,用来设置字体的高宽大小
106.程序什么时候应该使用线程,什么时候单
线程效率高。
答:1.耗时的操作使用线程,提高应用程序
响应
2.并行操作时使用线程,如 C/S 架构的服务
器端并发线程响应用户的请求。
3.多 CPU 系统中,使用线程提高 CPU 利用率
4.改善程序结构。一个既长又复杂的进程可
以考虑分为多个线程,成为几个独立或半独
立的运行部分,这样的程序会利于理解和修改。
其他情况都使用单线程。
107.Windows 是内核级线程么。
答:见下一题
108.Linux 有内核级线程么。
答:线程通常被定义为一个进程中代码的不同
执行路线。从实现方式上划分,线程有两
种类型:“用户级线程”和“内核级线程”。
用户线程指不需要内核支持而在用户程序
中实现的线程,其不依赖于操作系统核心,应
用进程利用线程库提供创建、同步、调度
和管理线程的函数来控制用户线程。这种线程
甚至在象 DOS 这样的操作系统中也可实现
,但线程的调度需要用户程序完成,这有些类
似 Windows 3.x 的协作式多任务。另外一
种则需要内核的参与,由内核完成线程的调度。
其依赖于操作系统核心,由内核的内部
需求进行创建和撤销,这两种模型各有其好处
和缺点。用户线程不需要额外的内核开支
,并且用户态线程的实现方式可以被定制或修
改以适应特殊应用的要求,但是当一个线
程因 I/O 而处于等待状态时,整个进程就会
被调度程序切换为等待状态,其他线程得不
到运行的机会;而内核线程则没有各个限制,
有利于发挥多处理器的并发优势,但却占
用了更多的系统开支。
Windows NT 和 OS/2 支持内核线程。Linux 支
持内核级的多线程
109.C++中什么数据分配在栈或堆中,New 分配
数据是在近堆还是远堆中?
答:栈: 存放局部变量,函数调用参数,函数
返回值,函数返回地址。由系统管理
堆: 程序运行时动态申请,new 和 malloc 申
请的内存就在堆上
110.使用线程是如何防止出现大的波峰。
答:意思是如何防止同时产生大量的线程,方
法是使用线程池,线程池具有可以同时提
高调度效率和限制资源使用的好处,线程池中
的线程达到最大数时,其他线程就会排队
等候。
111.一般数据库若出现日志满了,会出现什么
情况,是否还能使用?
答:只能执行查询等读操作,不能执行更改,
备份等写操作,原因是任何写操作都要记
录日志。也就是说基本上处于不能使用的状态。
112 SQL Server 是否支持行级锁,有什么好
处?
答:支持,设立封锁机制主要是为了对并发操
作进行控制,对干扰进行封锁,保证数据
的一致性和准确性,行级封锁确保在用户取得
被更新的行到该行进行更新这段时间内不
被其它用户所修改。因而行级锁即可保证数据
的一致性又能提高数据操作的迸发性。
113 关于内存对齐的问题以及 sizof()的输出
答:编译器自动对齐的原因:为了提高程序的
性能,数据结构(尤其是栈)应该尽可能
地在自然边界上对齐。原因在于,为了访问未
对齐的内存,处理器需要作两次内存访问
;然而,对齐的内存访问仅需要一次访问。
114. int i=10, j=10, k=3; k*=i+j; k 最后
的值是?
答 : 60 , 此 题 考 察 优 先 级 , 实 际 写 成 :
k*=(i+j);,赋值运算符优先级最低
115.对数据库的一张表进行操作,同时要对另
一张表进行操作,如何实现?
答:将操作多个表的操作放入到事务中进行处

116.TCP/IP 建立连接的过程?(3-way shake)
答:在 TCP/IP 协议中,TCP 协议提供可靠的连
接服务,采用三次握手建立一个连接。
 第一次握手:建立连接时,客户端发送 syn
包(syn=j)到服务器,并进入 SYN_SEND 状
态,等待服务器确认;
第二次握手:服务器收到 syn 包,必须确认客
户的 SYN(ack=j+1),同时自己也发送一个
SYN 包(syn=k),即 SYN+ACK 包,此时服务器
进入 SYN_RECV 状态;
 第三次握手:客户端收到服务器的 SYN+
ACK 包,向服务器发送确认包 ACK(ack=k+1)
, 此 包 发 送 完 毕 , 客 户 端 和 服 务 器 进 入
ESTABLISHED 状态,完成三次握手。
117.ICMP 是什么协议,处于哪一层?
答:Internet 控制报文协议,处于网络层(IP
层)
118.触发器怎么工作的?
答:触发器主要是通过事件进行触发而被执行
的,当对某一表进行诸如 UPDATE、 INSERT
、 DELETE 这些操作时,数据库就会自动执行
触发器所定义的 SQL 语句,从而确保对数
据的处理必须符合由这些 SQL 语句所定义的
规则。
119.winsock 建立连接的主要实现步骤?
答:服务器端:socker()建立套接字,绑定
(bind)并监听(listen),用 accept()
等待客户端连接。
客户端:socker()建立套接字,连接(connect)
服务器,连接上后使用 send()和 recv(
),在套接字上写读数据,直至数据交换完毕,
closesocket()关闭套接字。
服务器端:accept()发现有客户端连接,建
立一个新的套接字,自身重新开始等待连
接。该新产生的套接字使用 send()和 recv()
写读数据,直至数据交换完毕,closesock
et()关闭套接字。
120.动态连接库的两种方式?
答:调用一个 DLL 中的函数有两种方法:
1 . 载 入 时 动 态 链 接 ( load-time dynamic 
linking),模块非常明确调用某个导出函数
,使得他们就像本地函数一样。这需要链接时
链接那些函数所在 DLL 的导入库,导入库向
系统提供了载入 DLL 时所需的信息及 DLL 函数
定位。
2 . 运 行 时 动 态 链 接 ( run-time dynamic 
linking),运行时可以通过 LoadLibrary 或
Loa
dLibraryEx 函数载入 DLL。DLL 载入后,模块
可以通过调用 GetProcAddress 获取 DLL 函数

出口地址,然后就可以通过返回的函数指针调
用 DLL 函数了。如此即可避免导入库文件了

121.IP 组播有那些好处?
答:Internet 上产生的许多新的应用,特别是
高带宽的多媒体应用,带来了带宽的急剧
消耗和网络拥挤问题。组播是一种允许一个或
多个发送者(组播源)发送单一的数据包
到多个接收者(一次的,同时的)的网络技术。
组播可以大大的节省网络带宽,因为无
论有多少个目标地址,在整个网络的任何一条
链路上只传送单一的数据包。所以说组播
技术的核心就是针对如何节约网络资源的前
提下保证服务质量。
122. 以下代码中的两个 sizeof 用法有问题吗?
[C 易]
void UpperCase( char str[] ) // 将 str 中
的小写字母转换成大写字母
{
 for( size_t i=0; 
i<sizeof(str)/sizeof(str[0]); ++i )
 if( 'a'<=str[i] && str[i]<='z' )
 str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str 字 符 长 度 为 : " << 
sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
答:函数内的 sizeof 有问题。根据语法,sizeof
如用于数组,只能测出静态数组的大小,无法
检测动态分配的或外部数组大小。函数外的
str 是一个静态定义的数组,因此其大小为
123,函数内的 str 实际只是一个指向字符串
的指针,没有任何额外的与数组相关的信息,
因此 sizeof 作用于上只将其当指针看,一个
指针为 4 个字节,因此返回 4。
一个 32 位的机器,该机器的指针是多少位
指针是多少位只要看地址总线的位数就行了。
80386 以后的机子都是 32 的数据总线。所以指
针的位数就是 4 个字节了。
124.
main()
{
 int a[5]={1,2,3,4,5};
 int *ptr=(int *)(&a+1);
 printf("%d,%d",*(a+1),*(ptr-1));
}
输出:2,5
*(a+1)就是 a[1],*(ptr-1)就是 a[4],执行
结果是 2,5
&a+1 不是首地址+1,系统会认为加一个 a 数组
的偏移,是偏移了一个数组的大小(本例是 5
个 int)
int *ptr=(int *)(&a+1); 
则 ptr 实际是&(a[5]),也就是 a+5
原因如下:
&a 是数组指针,其类型为 int (*)[5];
而指针加 1 要根据指针类型加上一定的值,
不同类型的指针+1 之后增加的大小不同
a 是长度为 5 的 int 数组指针,所以要加
5*sizeof(int)
所以 ptr 实际是 a[5]
但是 prt 与(&a+1)类型是不一样的(这点很重
要)
所以 prt-1 只会减去 sizeof(int*)
a,&a 的地址是一样的,但意思不一样,a 是数
组首地址,也就是 a[0]的地址,&a 是对象(数
组)首地址,a+1 是数组下一元素的地址,即
a[1],&a+1 是下一个对象的地址,即 a[5].
125.请问以下代码有什么问题:
int main()
{
char a;
char *str=&a;
strcpy(str,"hello");
printf(str);
return 0;
}
没有为 str 分配内存空间,将会发生异常
问题出在将一个字符串复制进一个字符变量
指针所指地址。虽然可以正确输出结果,但因
为越界进行内在读写而导致程序崩溃。
char* s="AAA";
printf("%s",s);
s[0]='B';
printf("%s",s);
有什么错?
"AAA"是字符串常量。s 是指针,指向这个字符
串常量,所以声明 s 的时候就有问题。
cosnt char* s="AAA";
然后又因为是常量,所以对是 s[0]的赋值操作
是不合法的。
126、关键字 volatile 有什么含意?并举出三
个不同的例子?
提示编译器对象的值可能在编译器未监测到
的情况下改变。
127、将树序列化 转存在数组或 链表中
128.下面哪个实体不是 SIP 协议定义的(A)。
A )MGC B)UA C)proxy D)
Redirector
UA = User Agent 用户代理,指客户端的协议

PROXY = SIP Proxy ,指 SIP 服务器
Redirector = 重定向模块。一般用于跨服务
器通讯
129.VOIP 中本端与对端呼叫接通后,将通话
转接到第三方称之为(C)。
A)呼叫转接 B)呼叫前转 C)呼叫转移 
D)三方通话
跟普通电话相同,A,B 都是没有接通前就把呼
叫转接。D 是指三方同时在通话。
130.VOIP 的主要优点是(D)
A)价格便宜并且能为客户提供更好的增值服
务。
B)语音质量比传统的 PSTN 电话好。
C)通话的安全性和可靠性有更高的保障。
D)不需要服务提供商就可以使用。
音质,可靠性是传统电话好。这题的问题在于,
增值服务是指什么?如是 SP 则 VOIP 不支持。
还是服务器提供商是指什么?VOIP 需要服务
器。
131.下面哪个技术不属于语音处理技术(D)
A)静音检测
B)分组丢失补偿
C)同声消除
D)网路穿越
D 是网络传输的问题,主要是穿透 NAT 网关。
特别是比较复杂的网络。
132.SIP 协议是使用下面哪种方式编解码的()
A)ASN.1 B)BER C)ABNF D)
PER
网络应用
133.在不同网络中 MTU 会发生变化,由于 MTU
的变化,会导致那些值也相应发生变化(A)
A)IP 总长度字段
B)TCP MSS 字段
C)UDP 长度字段
D)TCP 长度字段
待查,MTU 变化会让 IP 分片或重组。因此变化
就是 IP
134.下列关于 TCP 和 UDP 的正确的说法是(C)
A)TCP 实时性比 UDP 好
B)TCP 比 UDP 效率高
C)TCP 首部比 UDP 的首部长
D)TCP 安全性比 UDP 高
实时性,效率。安全性,TCP 不见得比 UDP 高
135.一个套接口应该匹配(D)
A)源目标 IP
B)源目标 IP 端口
C)目标 IP 和目标端口
D)本地 IP 和本地端口
SOCKET 相当一 IP 连接上用端口标识队列
136.TCP 服务器出现异常并马上重启,通常会
出现那些情况()
A)socket 调用失败
B)bind 调用失败
C)listen 调用失败
D)select 调用失败
此题有问题,一般软件很难自动重启。而 TCP
服务器可以在任何一个阶段出问题,上述哪一
个都能出现,这个本意应该是指 Select.
底层开发
137.在一台以字节为最小存储单位的机器上,
如果把 0x12345678 写到从 0x0000 开始的地址
上,下列关于big—endian和little—enddian
说法正确的是(B)
A)在 big—endian 模式下,地址 0x0000 到
0x0003 存储的数据依次为:0x56,0x78,0x12,
0x34
B)在 big—endian 模式下,地址 0x0000 到
0x0003 存储的数据依次为:0x12,0x34,0x56,
0x78
C)在 little—endian 模式下,地址 0x0000
到 0x0003 存储的数据依次为:0x34,0x12,
0x78,0x56
D)在 little—endian 模式下,地址 0x0000
到 0x0003 存储的数据依次为:0x56,0x78,
0x12,0x34
138.以下关于交叉编译器概述正确的是(A)
A)交叉编译器一般按照 CPU 类型分类,不同
的 CPU 就有不同的交叉编译器
B)交叉编译器的速度比其他编译器的速度要

C)linux 开发环境中的交叉编译器不是 gcc 编
译器
D)交叉编译器编译出来的目标文件一般也能
在开发机(普通 PC)上运行
139.以下关于 linux 下中断的说法正确的是
()
A)中断只能由硬件产生
B)中断服务例程不能运行参数传递
C)中断服务例程可以在函数结束的时候返回
一个值
D)中断服务例程是不可中断的
D,B?
140.以下关于 linux 下系统调用的说法错误
的是()
A)应用程序通过系统调用访问内核
B)每个系统调用都有一个唯一的系统调用号
C)用户可以定制自己的系统调用
D)
可能是 A,系统调用在内核执行。但这里访问
比较模糊。
141.关于 SPI 说法正确的是()
A)SPI 工作在全双工模式下
B)
C)
D)SPI 接口一般工作在主从模式下
C 语言
142.Char Test[10];char *pTest=test;问:
&Test 在数值上等于(A)
A)Test B) Test[0] C)&pTest D)
符号表某个符号的地址
&Test 相当于二维指针首指针,TEST 是一维的
首指针
143 .在顺序表
{3,6,8,10,12,15,16,18,21,25,30}中,用二
分法查找关键码值 11,所雪的关键码比较次数
为()B?
A)2 B)3 C)4 D)5 
144.单链表中每个结点中包括一个指针 link,
它向该结点,现要将指针 q 指向的新结点放到
指针 p 指向的单链表接点之后,下面的操作序
列中哪一个是正确的(C)
A)q:=p^.link;p^.link:=q^:link
B)p^.link:=q^.link;q:=p^.link
C)q^.link:=p^.link;p^.link:=q
D)p^.link:=q;q^.link:=p^.link
145.以下叙述正确的是(C)
A)在 C 程序中,main 函数必须位于程序的最
前面
B)C 程序的每行中只能写一条语句
C)C 语言本身没有输入输出语句
D)在对一个 C 程序进行编译的过程中,可发
现注释中的拼写错误
146.有以下程序
Main()
{
Char a[]= ” programming ” ,b[]= ”
language”;
Char *p1,*p2;
Int i;
P1=a;p2=b;
For(i=0;i<7;i++)
If(*(p1+i)==*(p2+i))
Printf(“%c”,*(p1+i));
)
打印出什么()
147.请简述以下两个for循环的优缺点(6分)
// 第一个
for (i=0; i<N; i++)
{
if (condition)
// 第二个
if (condition)
{
for (i=0; i<N; 
 
DoSomething();
else
 
DoOtherthing();
}
i++)
 
DoSomething();
}
else
{
 for (i=0; i<N; 
i++)
 
DoOtherthing();
}
优点:
当 N 较大时,效率较

缺点:
每一个循环都要
做一次 if 判断
优点:
当 N 较小时,效率
较高
缺点:
循环次数较多
148.位运算:给定一个整型变量 a,(1)给
bit3 置数(2)清除 bit3(6 分)
 a|=(0x1<<3);
 a&=~(0x1<<3);
149.评述下面的代码(6 分)
Main()
{
Int a[100];
Int *p;
P=((unsigned int *)a+1);
Printf(“0x%x”,*p);
}
1.数组没有赋初值,
2.指针类型转换不一致
3..打印一个未赋值的整数值,p=a[1]
#include <stdio.h>
main()
{
int a[100]={1,2,3};
unsigned int *p;
p=((unsigned int *)a+1);
printf("%x,%x\n",a,p);
printf("0x%x",*p);
getchar();
}
150.编程题;(10 分)
从键盘输入一组字符;
字母按升序排列;其余的字符按升
序排列
字母放前,其余的放后
例如:输入:_@AB-@ab 结果:ABab-@@_
151.简述需求分析的过程和意义
152.网状、层次数据模型与关系数据模型的最
大的区别是什末
153.软件质量保证体系是什末 国家标准中与
质量保证管理相关的几个标准是什末 编号和
全称是什末号和全称是什末
153 文件格式系统有哪几种类型?分别说说
win95、win98、winMe、w2k、winNT、winXP 分
别支持那些文件系统
154.我现在有个程序,发现在 WIN98 上运行
得很慢,怎么判别是程序存在问题还是软硬件
系统存在问题?
155.有关 P2P 点对点文件传输的原理
156.一台计算机的 IP 是 192.168.10.71 子网
掩码 255.255.255.64 与 192.168.10.201 是同
一局域网吗?
157.internet 中 e-mail 协仪,IE 的协仪,NAT
是什么,有什么好处,能带来什么问题?DNS 是
什么,它是如何工作的?
158.PROXY 是如何工作的?
169.win2k 系统内 AT 命令完成什么功
能,Messenger 服务是做什么,怎么使用?
170 进程,线程的定义及区别
171,32 位操作系统内,1 进程地址空间多大,进
程空间与物理内存有什么关系?
172.网络攻击常用的手段,防火墙如何保证安
全.
173.如何配静态 IP,如何测网络内 2 台计算机
通不通,PING 一次返几个数据包?
174.WIN9X与WINNT以上操作系统有"服务"吗,
服务是什么,如何停止服务?
175.AD 在 WIN2KSERVER 上建需什么文件格
式,AD 是什么?XP 多用户下"注销"与"切换"的
区别.
176.UDP 可以跨网段发送吗?
177.最简单的确认远程计算机(win2K 以上)某
个监听端口是正常建立的?
178. 找错
void test1()
{
 char string[10];
 char* str1="0123456789";
 strcpy(string, str1);
}
答:表面上并且编译都不会错误。但如果
string 数组原意表示的是字符串的话,那这个
赋值就 没有达 到意 图。最 好定义 为 char 
string[11],这样最后一个元素可以存储字符
串结尾符'\0';
void test2()
{
 char string[10], str1[10];
 for(int I=0; I<10;I++)
 {
 str1[I] ='a';
 }
 strcpy(string, str1);
}
答:strcpy 使用错误,strcpy 只有遇到字符
串末尾的'\0'才会结束,而 str1 并没有结尾
标志,导致 strcpy 函数越界访问,不妨让
str1[9]='\0',这样就正常了。
void test3(char* str1)
{
 char string[10];
 if(strlen(str1)<=10)
{
 strcpy(string, str1);
}
}
答:这又会出现第一道改错题的错误了。
strlen(str1)算出来 的值是不 包含结 尾 符
'\0'的,如果 str1 刚好为 10 个字符+1 结尾
符 , string 就 得 不 到 结 尾 符 了 。 可 将
strlen(str1)<=10 改为 strlen(str1)<10。
179. 找错
#define MAX_SRM 256
DSN get_SRM_no()
{
 static int SRM_no;
 int I;
 for(I=0;I<MAX_SRM;I++,SRM_no++)
 {
 SRM_no %= MAX_SRM;
 if(MY_SRM.state==IDLE)
 {
 break;
 }
 }
 if(I>=MAX_SRM)
 return (NULL_SRM);
 else
 return SRM_no;
}
答:我不知道这段代码的具体功能,但明显有
两个错误
1,SRM_no 没有赋初值
2,由于 static 的声明,使该函数成为不可重
入(即不可预测结果)函数,因为 SRM_no 变
量放在程序的全局存储区中,每次调用的时候
还可以保持原来的赋值。这里应该去掉 static
声明。
180. 写出程序运行结果
int sum(int a)
{
 auto int c=0;
 static int b=3;
 c+=1;
 b+=2;
 return(a+b+c);
}
void main()
{
 int I;
 int a=2;
 for(I=0;I<5;I++)
 {
 printf("%d,", sum(a));
 }
}
答:8,10,12,14,16
该题比较简单。只要注意 b 声明为 static 静
态全局变量,其值在下次调用时是可以保持住
原来的赋值的就可以。
181. 
int func(int a)
{
 int b;
 switch(a)
 {
 case 1: b=30;
 case 2: b=20;
 case 3: b=16;
 default: b=0;
 }
 return b;
}
则 func(1)=?
答:func(1)=0,因为没有 break 语句,switch
中会一直计算到 b=0。这是提醒我们不要忘了
break。呵呵。
182:
 int a[3];
 a[0]=0; a[1]=1; a[2]=2;
 int *p, *q;
 p=a;
 q=&a[2];
则 a[q-p]=?
答:a[q-p]=a[2]=2;这题是要告诉我们指针的
运算特点
183. 
定义 int **a[3][4], 则变量占有的内存空间
为:_____
答:此处定义的是指向指针的指针数组,对于
32 位系统,指针占内存空间 4 字节,因此总空
间为 3×4×4=48。
184.
编写一个函数,要求输入年月日时分秒,输出
该年月日时分秒的下一秒。如输入 2004 年 12
月 31 日 23 时 59 分 59 秒,则输出 2005 年 1
月 1 日 0 时 0 分 0 秒。
答:
/*输入年月日时分秒,输出年月日时分秒的下
一秒,输出仍然在原内存空间*/
bool NextMinute(int *nYear,int 
*nMonth,int *nDate,int *nHour,int 
*nMinute,int *nSecond)
{
 if(*nYear<0 || *nMonth>12 || *nMonth<0 
|| *nHour>23 || *nHour<0 || *nMinute<0 || 
*nMinute>59 || *nSecond<0 || *nSecond>59) 
return false;
 int nDays;
 switch(*nMonth)
 {
 case 1:
 case 3:
 case 5:
 case 7:
 case 8:
 case 10:
 case 12:
 nDays=31;
 break;
 case 2:// 判断闰年
 
if(*nYear%400==0||*nYear%100!=0&&*nYear
%4==0)
 {
 nDays=29;
 }
 else
 {
 nDays=28;
 }
 break;
 default:
 nDays=30;
 break;
 }
 if(*nDate<0 || *nDate>nDays) return 
false;
 (*nSecond)++; // 秒加 1
 if(*nSecond>=60) // 秒满 60,做出特殊
处理,下面时,日,月等类同
 {
 *nSecond=0;
 (*nMinute)++;
 if(*nMinute>=60)
 {
 *nMinute=0;
 (*nHour)++;
 if(*nHour>=24)
 {
 *nHour=0;
 (*nDate)++;
 if(*nDate>nDays)
 {
 *nDate=1;
 (*nMonth)++;
 if(*nMonth>12)
 {
 *nMonth=1;
 (*nYear)++;
 }
 }
 }
 }
 }
 return true;
}
/*示例可运行代码*/
void main()
{
 int 
nYear=2004,nMonth=12,nDate=31,nHour=23,
nMinute=59,nSecond=59;
 bool res = 
NextMinute(&nYear,&nMonth,&nDate,&nHour
,&nMinute,&nSecond);
 if(res)
 printf("The 
result:%d-%d-%d %d:%d:%d",nYear,nMonth,
nDate,nHour,nMinute,nSecond);
 else
 printf("Input error!\n");
}
185. 写一个函数,判定运算环境(16 位以上
字长)是 little-endian 还是 big-endian
186. 操作系统的主要组成部分?
187.操作系统中进程调度策略有哪几种?
188.进程间主要的通讯方式?
189.写出进程的主要状态?
190.以太网物理地址和 IP 地址转换采用什么
协议?
191.IP 地址的编码分为哪两部分?
192.写出以太网数据帧格式
/193.8031 和 8051 的主要区别?
194.C++中的空类,默认产生哪些类成员函
数?
分析以下程序的执行结果
#include<iostream.h>
class base
{
public:
base(){cout<< “constructing base class”
<<endl;}
~base(){cout<<”destructing base class”
<<endl;}
};
class subs:public base
{
public:
subs(){cout<<”constructing sub class”
<<endl;}
~subs(){cout<<”destructing sub class”
<<endl;}
};
void main()
{
subs s;
}
195.指出下面程序的错误
#define SIZE 5
struct String
{
 char *pData;
};
void main()
{
 char *pData;
};
void main()
{
 char acValue1[SIZE]={‘H’,’E’,’L’,’
L’,’O’};
 char acValue2[SIZE]={‘W’,’O’,’L’,’
D’};
 struct String a,b,c;
a.pData=malloc(SIZE*sizeof(char));
memcpy(a.pData,acValuel,SIZE);
b.pData=malloc(SIZE*sizeof(char));
mempcpy(b.pData,acValue2,SIZE);
b=a;
free(a.pData);
c=b;
}
196.指出下面两段程序的区别
【1】
main()
{
 int loop=1;
 int arr[10];
 int i=0;
 while(loop<5)
 {
 for(;i<=10;i++)
 {
 arr[i]=1;
 }
 loop++;
 }
}
【2】
main()
{
 int arr[10];
 int loop=1;
 int i=0;
 while(loop<5)
 {
 for(i=0;i<=10;i++)
 {
 arr[i]=1;
 }
 loop++;
 }
}
197.指出下面程序的错误(函数 GetValue 返回
unsigned char 类型的值)
#define MAXNUM 400;
unsigned char 
uclndex,uclnputVar,aucArray[MAXNUM];
for(ucIndx =0;ucIndex<=MAXNUM;ucIndex++)
{
 aucArray[ucIndex]=aucArray[ucIndex]+1;
}
ucInputVar=GetValue();
for(ucIndex=0;ucIndex>(ucInputVar-1);ucInd
ex++)
{
 
aucArray[ucIndex]=aucArray[ucIndex]*2+1
;
}
198.什么是 com 和 ActiveX,简述 DCOM。
答:COM(Component Object Mode)即组件对
象模型,是组件之间相互接口的规范。其作用
是使各种软件构件和应用软件能够用一种统
一的标准方式进行交互。COM 不是一种面向对
象的语言,而是一种与源代码无关的二进制标
准。
ActiveX 是 Microsoft 提出的一套基于 COM 的
构件技术标准,实际上是对象嵌入与炼接(OLE)
的新版本。基于分布式环境下的 COM 被称作
DCOM(Distribute COM,分布式组件对象模型),
它实现了 COM 对象与远程计算机上的另一个对
象之间直接进行交互。DCOM 规范定义了分散对
象创建和对象间通信的机制,DCOM 是 ActiveX
的基础,因为 ActiveX 主要是针对 Internet
应用开发(相比 OLE)的技术,当然也可以用
于普通的桌面应用程序。
199.列出 3 个常用网络协议使用的端口。
答:HTTP 协议用 80 端口,FTP 协议用 21 端口,
POP3 协议用 110 端口
199 什么是 ODBC? 
答:ODBC(Open Database Connectivity,
开放数据库互连)是微软公司开放服务结
构 (WOSA , Windows Open Services 
Architecture)中有关数据库的一个组成
部分,它建立了一组规范,并提供了一组
对数据库访问的标准 API(应用程序编程
接口)。ODBC 的最大优点是能以统一的方
式(用它提供的 API 访问数据库)处理所有
的数据库。
200 结构化编程和 goto 语句的区别和关系?
答:结构化编程设计思想采用了模块分解
与功能抽象和自顶向下、分而治之的方法,
从而有效地将一个较复杂的程序系统设计
任务分解成许多易于控制和处理的子程序,
便于开发和维护。goto 语句可以实现无条
件跳转,改变程序流向,破坏结构化编程
设计风格。但 goto 语句在结构化编程中并
非不可使用,只是要受到限制的使用。
201 MFC 中 SendMessage 和 PostMessage 的区
别?
答:PostMessage 和 SendMessage 的区别
主要在于是否等待应用程序做出消息处理。
PostMessage 只是把消息放入队列,然后
继续执行;而 SendMessage 必须等待应用
程序处理消息后才返回继续执行。这两个
函数的返回值也不同,PostMessage 的返
回值表示 PostMessage 函数执行是否正确,
而 SendMessage 的返回值表示其他程序处
理消息后的返回值。
202.改错
#include 
#include 
class CBuffer
{
char * m_pBuffer;
int m_size;
public:
CBuffer()
{
m_pBuffer=NULL;
}
~CBuffer()
{
Free();
}
void Allocte(int size) (3) {
m_size=size;
m_pBuffer= new char[size];
}
private:
void Free()

if(m_pBuffer!=NULL)
{
delete m_pBuffer;
m_pBuffer=NULL;
}
}
public:
void SaveString(const char* pText) const
{
strcpy(m_pBuffer, pText);
}
char* GetBuffer() const
{
return m_pBuffer;
}
};
void main (int argc, char* argv[])
{
CBuffer buffer1;
buffer1.SaveString("Microsoft");
printf(buffer1.GetBuffer());
}
答:改正后
主要改正 SaveString 函数

void SaveString(const char* pText) const
{
strcpy(m_pBuffer, pText);
}
改为
void SaveString(const char* pText) (1)
{
Allocte(strlen(pText)+1); (2)
strcpy(m_pBuffer, pText);
}
原因:
(1) const 成员函数表示不会修改数据成员,
而 SaveString 做不到,去掉 const 声明
(2) m_pBuffer 指向 NULL,必须用 Allocte
分配空间才能赋值。
(3) 另外需要将 Allocte 成员函数声明为私
有成员函数更符合实际
203.下来程序想打印“Welcome MSR Asia”,
改正错误
#include 
#include 
char * GetName (void)
{
//To return “MSR Asia” String
char name[]="MSR Asia";
return name;
}
void main(int argc, char* argv[])
{
char name[32];
//Fill in zeros into name
for(int i=0;i<=32;i++)
{
name[i]='\0';
}
//copy “Welcome” to name
name="Welcome";
//Append a blank char
name[8]=" ";
//Append string to name
strcat(name,GetName());
//print out
printf(name);
}
答:改正后为
#include 
#include 
char * GetName (void)
{
//To return “MSR Asia” String
//char name[]="MSR Asia"; (1)
char *name=(char *)malloc(strlen("MSR 
Asia")+1); 
strcpy(name,"MSR Asia");
return name;
}
void main(int argc, char* argv[])
{
char name[32];
//Fill in zeros into name
for(int i=0;i<=32;i++)
{
name[i]='\0';
}
//copy “Welcome” to name
//name="Welcome"; (2)
strcat(name,"Welcome ");
//Append a blank char
// name[8]=' '; (3)
//Append string to name
char *p=GetName(); (4)
strcat(name,p);
free (p);
//print out
printf(name);
}
原因:(1)在函数内部定义的变量在函数结
束时就清空了,必须动态分配内存
(2)字符串赋值语句错误,应该用 strcat
(3)该语句无效,可去掉
(4)定义一个指针指向动态分配的内存,用
完后需用 free 语句释放
204.写出下面程序的输出结果
#include 
class A
{
public:
void FuncA()
{
printf("FuncA called\n");
}
virtual void FuncB()
{
printf("FuncB called\n");
}
};
class B: public A
{
public:
void FuncA()
{
A::FuncA();
printf("FuncAB called\n");
}
virtual void FuncB()
{
printf("FuncBB called\n");
}
};
void main(void)
{
B b;
A *pa;
pa=&b;
A *pa2=new A;
b.FuncA(); (1)
b.FuncB(); (2)
pa->FuncA(); (3)
pa->FuncB(); (4)
pa2->FuncA(); (5)
pa2->FuncB();
delete pa2;
}
答:
1.b.FuncA(); 输出
FuncA called
FuncAB called
2.b.FuncB();输出
FuncBB called
上两者好理解,直接调用类 B 的相应成员函数
3.pa->FuncA();输出
FuncA called 调用类 A 的 FuncA()
4.pa->FuncB();输出
FuncBB called 调用类 B 的 FuncB(),原因是
C++的动态决议机制,当基类函数声明为
virtual 时,指向派生类对象的基类指针来调
用该函数会选择派生类的实现,除非派生类没
有才调用基类的虚函数。还有一点注意的是:
指向基类类型的指针可以指向基类对象也可
以指向派生类对象,如 pa=&b;
5. pa2->FuncA();
pa2->FuncB();输出
FuncA called
FuncB called
这也好理解,直接调用类 A 的相应成员函数
206 . In the main() function, after 
ModifyString(text) is called, what’s the 
value of ‘text’?
#include 
#include
int FindSubString(char* pch)
{
int count=0;
char* p1=pch;
while(*p1!='\0')
{
if(*p1==p1[1]-1)
{
p1++;
count++;
}
else
{
break;
}
}
int count2=count;
while(*p1!='\0')
{
if(*p1==p1[1]+1)
{
p1++;
count2--;
}
else
{
break;
}
}
if(count2==0)
return count;
return 0;
}
void ModifyString(char* pText)
{
char* p1=pText;
char* p2=p1;
while(*p1!='\0')
{
int count=FindSubString(p1);
if(count>0)
{
*p2++=*p1;
sprintf(p2, "%I", count);
while(*p2!= '\0')
{
p2++;
}
p1+=count+count+1;
}
else
{
*p2++=*p1++;
}
}
}
void main(void)
{
char text[32]="XYBCDCBABABA";
ModifyString(text);
printf(text);
}
答:我不知道这个结构混乱的程序到底想考察
什 么 , 只 能 将 最 后 运 行 结 果 写 出 来 是
XYBCDCBAIBAAP
207. Programming (Mandatory) 
 Linked list
 a. Implement a linked list for 
integers,which supports the insertafter 
(insert a node after a specified node) and 
removeafter (remove the node after a 
specified node) methods;
 b. Implement a method to sort the 
linked list to descending order.
答:题目的意思是实现一个整型链表,支持插
入,删除操作(有特殊要求,都是在指定节点
后进行操作),并写一个对链表数据进行降序
排序的方法。
那我们不妨以一个线性链表进行编程。
// 单链表结构体为
typedef struct LNode 
{
int data;
struct LNode *next;
}LNode, *pLinkList;
// 单链表类
class LinkList
{
private:
pLinkList m_pList;
int m_listLength;
public:
LinkList();
~LinkList();
bool InsertAfter(int afternode, int 
data);//插入
bool RemoveAfter(int removenode);//删除
void sort();//排序
};
实现方法
//insert a node after a specified node
bool LinkList::InsertAfter(int afternode, 
int data)
{
LNode *pTemp = m_pList;
int curPos = -1;
if (afternode > m_listLength ) // 插入点
超过总长度
{
return false;
}
while (pTemp != NULL) // 找到指定的节点
{
curPos++;
if (curPos == afternode) 
break;
pTemp = pTemp->next;
}
if (curPos != afternode) // 节点未寻到,
错误退出
{
return false;
}
LNode *newNode = new LNode; // 将新节点
插入指定节点后
newNode->data = data;
newNode->next = pTemp->next;
pTemp->next = newNode;
m_listLength++;
return true;
}
//remove the node after a specified node
bool LinkList::RemoveAfter(int 
removenode) 
{
LNode *pTemp = m_pList;
int curPos=-1;
if (removenode > m_listLength) // 删除点
超过总长度
{
return false;
}
// 找到指定的节点后一个节点,因为删除的
是后一个节点
while (pTemp != NULL) 
{
curPos++;
if (curPos == removenode+1) 
break;
pTemp = pTemp->next;
}
if (curPos != removenode) // 节点未寻到,
错误退出
{
return false;
}
LNode *pDel = NULL; // 删除节点
pDel = pTemp->next;
pTemp->next = pDel->next;
delete pDel;
m_listLength--;
return true;
}
//sort the linked list to descending order.
void LinkList::sort()
{
if (m_listLength<=1)
{
return;
}
LNode *pTemp = m_pList;
int temp;
// 选择法排序
for(int i=0;i<M_LISTLENGTH-1;I++)
for(int j=i+1;j<M_LISTLENGTH;J++)
if (pTemp[i].data<PTEMP[J].DATA)
{
temp=pTemp[i].data;
pTemp[i].data=pTemp[j].data;
pTemp[j].data=temp;
}
}
前两个函数实现了要求 a,后一个函数 sort()
实现了要求 b
208. Debugging (Mandatory)
a. For each of the following recursive 
methods, enter Y in the answer box if the 
method terminaters (assume i=5), 
Otherwise enter N.
(题目意思:判断下面的递归函数是否可以结
束)
static int f(int i){
return f(i-1)*f(i-1);
}
Ansewr: N,明显没有返回条件语句,无限递归

static int f(int i){
if(i==0){return 1;}
else {return f(i-1)*f(i-1);}

Ansewr:Y,当 i=0 时可结束递归
static int f(int i){
if(i==0){return 1;}
else {return f(i-1)*f(i-2);}
}
Ansewr:N,因为 i=1 时,f(i-2)=f(-1),进入
一个无限递归中
209.编程
 将 整 数 转 换 成 字 符 串 : void 
itoa(int,char);
例如 itoa(-123,s[])则 s=“-123”;
答:
char* itoa(int value, char* string)
{
char tmp[33];
char* tp = tmp;
int i;
unsigned v;
char* sp;
// 将值转为正值
if (value < 0)
v = -value;
else
v = (unsigned)value;
// 将数转换为字符放在数组 tmp 中
while (v)
{
i = v % 10;
v = v / 10;
*tp++ = i+'0';
}
// 将 tmp 里的字符填入 string 指针里,并加
上负号(如果有)
sp = string;
if (value < 0)
*sp++ = '-';
while (tp > tmp)
*sp++ = *--tp;
*sp = 0;
return string;
}
 
210.完成下列程序
*
*.*.
*..*..*..
*...*...*...*...
*....*....*....*....*....
*.....*.....*.....*.....*.....*.....
*......*......*......*......*......*...
...*......
*.......*.......*.......*.......*......
.*.......*.......*.......
#include 
#define N 8
int main()
{
int i;
int j;
int k;
---------------------------------------
------------------
│ │
│ │
│ │
---------------------------------------
------------------
return 0;
}
答:#define N 8
int main()
{
int i;
int j;
int k;
for(i=0;i<N;I++)
{
for(j=0;j<I+1;J++)
{
printf("*");
for(k=0;k<I;K++)
printf(".");
}
printf("\n");
}
return 0;
}
211.下列程序运行时会崩溃,请找出错误并
改正,并且说明原因。
#include “stdio.h”
#include “malloc.h”
typedef struct TNode
{
TNode* left;
TNode* right;
int value;
}TNode;
TNode* root=NULL;
void append(int N);
int main()
{
append(63);
append(45);
append(32);
append(77);
append(96);
append(21);
append(17); // Again, 数字任意给出
return 0;
}
void append(int N)
{
TNode* NewNode=(TNode 
*)malloc(sizeof(TNode));
NewNode->value=N;
NewNode->left=NULL; //新增
NewNode->right=NULL; //新增
if(root==NULL)
{
root=NewNode;
return;
}
else
{
TNode* temp;
temp=root;
while((N>=temp->value && 
temp->left!=NULL)||(Nvalue && temp-
>right!=NULL))
{
while(N>=temp->value && temp->left!=NULL)
temp=temp->left;
while(Nvalue && temp->right!=NULL)
temp=temp->right;
}
if(N>=temp->value)
temp->left=NewNode;
else
temp->right=NewNode;
return; 
}
}
答:因为新节点的左右指针没有赋 NULL 值,
至使下面的 while 循环不能正确结束而导致内
存 越 界 , 最 后 崩 溃 ( 注 意 结 束 条 件 是
temp->left!=NULL 或 temp->right!=NULL)。
改正就是增加两条赋值语句,如上文红色部分
字体就是新增的两条语句。
212.打印如下图案,共 19 行,只能有一个 for
循环(题目已经提供)
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
*****************
***************
*************
***********
*********
*******
*****
***
*
for(i=0;i<19;i++)
{
}
答:
#include "stdio.h"
void main()
{
for(int i=0;i<19;i++)
{
int j=0;
while (j<19) 
{
if (i<=9) 
{
if (j<=9) 
{
if (i+j>=9) 
printf("*");
else 
printf(" ");
}
else 
if (j-i<=9) 
printf("*");
else 
printf(" ");
}
else
{
if (j<=9)
{
if (i-j<=9) 
printf("*");
else
printf(" ");
}
else
if (j+i<=27) 
printf("*");
else 
printf(" ");
}
j++;
}
printf("\n");
}
}
213.stack data (栈)存在于
A.rom, B .flash C .eeprom D.ram E .none of 
the above
答:D.ram。这题稍微涉及到一点硬件知识,
ROM 的全称是 Read Only Memory,即只读存储
器,flash ,eeprom 都是 ROM 家族的一员,RAM
是 Random Access Memory 的简称,意为随机
存取存储器,也就是内存了。不管是堆还是栈
都是放在内存里的。
214.
int i;
int x=0x12345678;
unsigned char *p=(unsigned char *)&x;
for(i=0;i<SIZEOF(X);I++)
printf("%2x",*(p+i));
在 80x86pc 机器上运行结果?
答:x 在 PC 机上的内存存放顺序为 78 56 34 12,
高字节在前,低字节在后,因此输出 78563412
Sun Sparc Unix 上运行结果?
215.
char 
a[2][2][3]={{{1,6,3},{5,4,15}},{{3,5,33
},{23,12,7}} };
for(int i=0;i<12;i++)
printf("%d ",_______);
在空格处填上合适的语句,顺序打印出 a 中的
数字
答:*(*(*(a+i/6)+(i/3%2))+i%3)
这题主要是要将输出的序号依次写出一些,如
000,001,002,010,011,012,100,101... 然 后
找序号变化规律
216.请用标准 C 语言实现一个双向循环链表的
查找与删除。
typedef struct doublecyclelink{
int key;
struct doublecyclelink *prev;
struct doublecyclelink *next;
}DoubleCycleLinkT;
DoubleCycleLinkT 
*findKey(DoubleCycleLinkT *link,int key);
遍历整个双向循环链表,将第一个与 key 值相
同的结点移出链表,并返回。
若没有找到则返回 NULL。
答:
函数为
DoubleCycleLinkT 
*findKey(DoubleCycleLinkT *link,int key)
{
DoubleCycleLinkT *p;
p=link->next;
while (p->next!=link) // 链表结尾
{
if (p->key==key) // 查找到 key 值相同,删
除该节点,并返回
{
p->prev->next=p->next;
p->next->prev=p->prev;
free(p);
return link;
}
else 
p=p->next; // 否则查找下一节点
}
if (p->next == link) return NULL; //没找
到,返回 NULL 
}
217、请用标准 C 语言实现下列标准库函数,
设计中不得使用其他库函数。
char *strstr(char *str1,char *str2);
在字符串 str1 中,寻找字串 str2,若找到返
回找到的位置,否则返回 NULL。
答:
函数为
char * strstr ( const char * str1, const 
char * str2 )
{
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp)
{
s1 = cp;
s2 = (char *) str2;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}
218.实现双向链表删除一个节点 P,在节点 P
后插入一个节点,写出这两个函数;
答:
假设线性表的双向链表存储结构
typedef struct DulNode{
struct DulNode *prior; //前驱指针
ElemType data; //数据
struct DulNode *next; //后继指针
}DulNode,*DuLinkList;
删除操作
Status ListDelete_DuL(DuLinkList &L,int 
i,ElemType &e)
{
if(!(p=GetElemP_DuL(L,i)))
return ERROR;
e=p->data;
p->prior->next=p->next;
p->next->prior=p->pror;
free(p);
return OK;
}
插入操作
Status ListInsert_DuL(DuLinkList &L,int 
i,ElemType &e)
{
if(!(p=GetElemP_DuL(L,i)))
return ERROR;
if(!(s=(DuLinkList)malloc(sizeof(DuLNod
e)))) 
return ERROR;
s->data=e;
s->prior=p->prior;
p->prior->next=s;
s->next=p;
p->prior=s;
return OK;
}
219.写一个函数,将其中的\t 都转换成 4 个空
格。
答:
该函数命名为 convert,参数的意义为:
*strDest 目 的 字 符 串 ,*strSrc 源字符
串,length 源字符串的长度
函数实现为:
char* convert(char *strDest, const char 
*strSrc,int length)
{
char * cp = strDest;
int i=0;
while(*strSrc && i
{
if (*strSrc=='\t') //将\t 转换成 4 个空格
{
for(int j=0;j<4;j++)
*cp++=' ';
}
else //否则直接拷贝
*cp++=*strSrc;
strSrc++;
i++;
}
return strDest;
}
230.Windows 程序的入口是哪里?写出
Windows 消息机制的流程。
答:
Windows 程序的入口是 WinMain 函数
消息机制:系统将会维护一个或多个消息队列,
所有产生的消息都会被放入或是插入队列中。
系统会在队列中取出每一条消息,根据消息的
接收句柄而将该消息发送给拥有该窗口的程
序的消息循环。每一个运行的程序都有自己的
消息循环,在循环中得到属于自己的消息并根
据接收窗口的句柄调用相应的窗口过程。而在
没有消息时消息循环就将控制权交给系统。
231.如何定义和实现一个类的成员函数为回
调函数?
答:
所谓的回调函数,就是预先在系统的对函数进
行注册,让系统知道这个函数的存在,以后,
当某个事件发生时,再调用这个函数对事件进
行响应。
定 义 一 个 类 的 成 员 函 数 时 在 该 函 数 前 加
CALLBACK 即将其定义为回调函数,函数的实现
和普通成员函数没有区别
232.C++里面是不是所有的动作都是 main()引
起的?如果不是,请举例。
答:不是,比如中断引起的中断处理不是直接
由 main()引起的,而是由外部事件引起的。
233.C++里面如何声明 const void f(void)函
数为 C 程序中的库函数
答:在该函数前添加 extern “C”声明
234. 内联函数在编译时是否做参数类型检查
答:做类型检查,因为内联函数就是在程序编
译时,编译器将程序中出现的内联函数的调用
表达式用内联函数的函数体来代替。
235.请你详细地解释一下 IP 协议的定义,
在哪个层上面?主要有什么作用?TCP 与 UDP
呢?
答:IP 是 Internet Protocol 的简称,是网络
层的主要协议,作用是提供不可靠、无连接的
数 据 报 传 送 。 TCP 是 Transmit Control 
Protocol(传输控制协议)的缩写,在运输层,
TCP 提供一种面向连接的,可靠的字节流服务;
UDP 是 User Datagram Protocol(用户数据报
协议)的缩写,在运输层,UDP 提供不可靠的
传输数据服务
236.请问交换机和路由器各自的实现原理
是什么?分别在哪个层次上面实现的?
答:交换机属于OSI第二层即数据链路层设
备。它根据MAC地址寻址,通过站表选择路
由,站表的建立和维护由交换机自动进行。路
由器属于OSI第三层即网络层设备,它根据
IP地址进行寻址,通过路由表路由协议产生。
交换机最大的好处是快速,路由器最大的好处
是控制能力强。
237.全局变量和局部变量有什么区别?是怎
么实现的?操作系统和编译器是怎么知道的?
答:一些变量在整个程序中都是可见的,它们
称为全局变量。一些变量只能在一个函数中可
知,称为局部变量。这就是他们的区别。
在任何函数外面定义的变量就是全局变量,在
函数内部定义的变量是局部变量,这是它们在
程序中的实现过程。
操作系统和编译器是根据程序运行的内存区
域知道他们的,程序的全局数据放在所分配内
存的全局数据区,程序的局部数据放在栈区。
238. 有两个文件 a.txt,b.txt.a.txt 中存储
的是 aaaaaa,b.txt 中存储的是 bbb。将两个
文件合并成 c.txt 如果是 a 并 b 的话存储为
abababaaa.要是 b 并 a 的话就是 bababaaaa.
用 c 语言编程实现。
#include "stdio.h" 
 void fmerge(FILE *fa,FILE 
*fb,FILE *fc) 
 { 
 char cha,chb; 
 cha=fgetc(fa); 
 chb=fgetc(fb); 
 while ((cha!=EOF)&&(chb!=EOF)) 
 { 
 fputc(cha,fc); 
 fputc(chb,fc); 
 cha=fgetc(fa); 
 chb=fgetc(fb); 
 } 
 while (cha!=EOF) 
 { 
 fputc(cha,fc); 
 cha=fgetc(fa); 
 } 
 while (chb!=EOF) 
 { 
 fputc(chb,fc); 
 chb=fgetc(fb); 
 } 
 } 
 int main() 
 { 
 FILE *fa,*fb,*fc; 
 fa=fopen("a.txt","r"); 
 fb=fopen("b.txt","r"); 
 fc=fopen("c.txt","w"); 
 fmerge(fa,fb,fc); 
 fclose(fa); 
 fclose(fb); 
 fclose(fc); 
 return 0; 
 }
 
239.C++:memset ,memcpy 和 strcpy 的根本区
别?
#include "memory.h"
memset 用来对一段内存空间全部设置为某个
字符,一般用在对定义的字符串进行初始化为
‘ '或‘\0';例:char a[100];memset(a, '\0', 
sizeof(a)); 
memcpy 用来做内存拷贝,你可以拿它拷贝任何
数据类型的对象,可以指定拷贝的数据长度;
例 : char a[100],b[50]; memcpy(b, a, 
sizeof(b));注意如用 sizeof(a),会造成 b 的
内存地址溢出。
strcpy 就只能拷贝字符串了,它遇到'\0'就结
束拷贝;例:char a[100],b[50];strcpy(a,b);
如用 strcpy(b,a),要注意 a 中的字符串长度
(第一个‘\0'之前)是否超过 50 位,如超过,
则会造成 b 的内存地址溢出。
strcpy 
原型:extern char *strcpy(char *dest,char 
*src); 
用法:#include 
功能:把 src 所指由 NULL 结束的字符串复制
到 dest 所指的数组中。
说明:src 和 dest 所指内存区域不可以重叠且
dest 必须有足够的空间来容纳 src 的字符串。
返回指向 dest 的指针。
memcpy 
原型:extern void *memcpy(void *dest, void 
*src, unsigned int count);
用法:#include 
功能:由 src 所指内存区域复制 count 个字节
到 dest 所指内存区域。
说明:src 和 dest 所指内存区域不能重叠,函
数返回指向 dest 的指针。
memset
原型:extern void *memset(void *buffer, 
char c, int count);
用法:#include 
功能:把 buffer 所指内存区域的前 count 个
字节设置成字符 c。
说明:返回指向 buffer 的指针。
240.ASSERT()是干什么用的
ASSERT() 是一个调试程序时经常使用的宏,
在程序运行时它计算括号内的表达式,如果表
达式为 FALSE (0), 程序将报告错误,并终止
执行。如果表达式不为 0,则继续执行后面的
语句。这个宏通常原来判断程序中是否出现了
明显非法的数据,如果出现了终止程序以免导
致 严重后果,同时也便于查找错误。例如,
变量 n 在程序中不应该为 0,如果为 0 可能导
致错误,你可以这样写程序:
...... 
ASSERT( n != 0); 
k = 10/ n; 
...... 
ASSERT 只有在 Debug 版本中才有效,如果编译
为 Release 版本则被忽略。
assert()的功能类似,它是 ANSI C 标准中规
定的函数,它与 ASSERT 的一个重要区别是可
以用在 Release 版本中。
241. 二分查找算法:
1、递归方法实现:
int BSearch(elemtype a[],elemtype x,int 
low,int high)
/*在下届为 low,上界为 high 的数组 a 中折半
查找数据元素 x*/
{
int mid;
if(low>high) return -1;
mid=(low+high)/2;
if(x==a[mid]) return mid;
if(x<a[mid]) 
return(BSearch(a,x,low,mid-1));
else return(BSearch(a,x,mid+1,high));
}
2、非递归方法实现:
int BSearch(elemtype a[],keytype key,int 
n)
{
int low,high,mid;
low=0;high=n-1;
while(low<=high) 
{
mid=(low+high)/2;
if(a[mid].key==key) return mid;
else if(a[mid].key<key) low=mid+1;
else high=mid-1;
}
return -1;
}
242,写出下面代码段的输出结果,并说明理
由:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
1, str1,str2,str3,str4 是数组变量,它
们有各自的内存空间;
而 str5,str6,str7,str8 是指针,它们
指向相同的常量区域。
243. 以下代码中的两个 sizeof 用法有问题吗?
void UpperCase( char str[] ) 
{
for( size_t i=0; 
i<sizeof(str)/sizeof(str[0]); ++i )
if( 'a'<=str[i] && str[i]<='z' )
str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str 字符长度为: " << 
sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
函数内的 sizeof 有问题。根据语法,sizeof
如用于数组,只能测出静态数组的大小,无法
检测动态分配的或外部数组大小。函数外的
str 是一个静态定义的数组,因此其大小为 6,
函数内的 str 实际只是一个指向字符串的指针,
没有任何额外的与数组相关的信息,因此
sizeof 作用于上只将其当指针看,
244,下面程序输出结果是多少:
main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
2,5
*(a+1)就是 a[1],*(ptr-1)就是 a[4],执行
结果是 2,5
&a+1 不是首地址+1,系统会认为加一个 a 数组
的偏移,是偏移了一个数组的大小(本例是 5
个 int)
int *ptr=(int *)(&a+1); 
则 ptr 实际是&(a[5]),也就是 a+5
原因如下:
&a 是数组指针,其类型为 int (*)[5];
而指针加 1 要根据指针类型加上一定的值,
不同类型的指针+1 之后增加的大小不同
a 是长度为 5 的 int 数组指针,所以要加
5*sizeof(int)
所以 ptr 实际是 a[5]
但是 prt 与(&a+1)类型是不一样的(这点很重
要)
所以 prt-1 只会减去 sizeof(int*)
a,&a 的地址是一样的,但意思不一样,a 是数
组首地址,也就是 a[0]的地址,&a 是对象(数
组)首地址,a+1 是数组下一元素的地址,即
a[1],&a+1 是下一个对象的地址,即 a[5].
245,请问运行 Test 函数会有什么样的结果?
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void) 
{
char *str = NULL;
GetMemory(str); 
strcpy(str, "hello world");
printf(str);
}
,请问运行 Test 函数会有什么样的结果?
char *GetMemory(void)

char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory(); 
printf(str);
}
,请问运行 Test 函数会有什么样的结果?
Void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello"); 
printf(str); 
}
,请问运行 Test 函数会有什么样的结果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str); 
if(str != NULL)
{
strcpy(str, “world”); 
printf(str);
}
}
几种排序:
1.稳定性比较
插入排序、冒泡排序、二叉树排序、二路归并
排序及其他线形排序是稳定的
选择排序、希尔排序、快速排序、堆排序是不
稳定的
2.时间复杂性比较
插入排序、冒泡排序、选择排序的时间复杂性
为 O(n2)
其它非线形排序的时间复杂性为 O(nlog2n)
线形排序的时间复杂性为 O(n);
3.辅助空间的比较
线形排序、二路归并排序的辅助空间为 O(n),
其它排序的辅助空间为 O(1);
4.其它比较
插入、冒泡排序的速度较慢,但参加排序的序
列局部或整体有序时,这种排序能达到较快的
速度。
反而在这种情况下,快速排序反而慢了。
当 n 较小时,对稳定性不作要求时宜用选择排
序,对稳定性有要求时宜用插入或冒泡排序。
若待排序的记录的关键字在一个明显有限范
围内时,且空间允许是用桶排序。
当 n 较大时,关键字元素比较随机,对稳定性
没要求宜用快速排序。
当 n 较大时,关键字元素可能出现本身是有序
的,对稳定性有要求时,空间允许的情况下。
宜用归并排序。
当 n 较大时,关键字元素可能出现本身是有序
的,对稳定性没有要求时宜用堆排序。
***************************************
***************************************
*******
重温经典排序思想--C 语言常用排序全解
/*
=======================================
======================================
相关知识介绍(所有定义只为帮助读者理解相
关概念,并非严格定义):
1、稳定排序和非稳定排序
简单地说就是所有相等的数经过某种排序方
法后,仍能保持它们在排序之前的相对次序,
我们就
说这种排序方法是稳定的。反之,就是非稳定
的。
比如:一组数排序前是 a1,a2,a3,a4,a5,其中
a2=a4,经过某种排序后为 a1,a2,a4,a3,a5,
则我们说这种排序是稳定的,因为 a2 排序前
在 a4 的前面,排序后它还是在 a4 的前面。假
如变成 a1,a4,
a2,a3,a5 就不是稳定的了。
2、内排序和外排序
在排序过程中,所有需要排序的数都在内存,
并在内存中调整它们的存储顺序,称为内排序;
在排序过程中,只有部分数被调入内存,并借
助内存调整数在外存中的存放顺序排序方法
称为外排序。
3、算法的时间复杂度和空间复杂度
所谓算法的时间复杂度,是指执行算法所需要
的计算工作量。
一个算法的空间复杂度,一般是指执行这个算
法所需要的内存空间。
=======================================
=======================================
==
*/
/*
=======================================
=========
功能:选择排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
在要排序的一组数中,选出最小的一个数与第
一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位
置的数交换,如此循环
到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度 O(n2)--[n
的平方]
=======================================
==============
*/
void select_sort(int *x, int n)
{
int i, j, min, t;
for (i=0; i<n-1; i++) /*要选择的次数:
0~n-2 共 n-1 次*/
{
 min = i; /*假设当前下标为 i 的数最小,
比较后再调整*/
 for (j=i+1; j<n; j++)/*循环找出最小的
数的下标是哪个*/
 {
 if (*(x+j) < *(x+min))
 { 
 min = j; /*如果后面的数比前面的小,
则记下它的下标*/
 }
 } 
 
 if (min != i) /*如果 min 在循环中改变
了,就需要交换数据*/
 {
 t = *(x+i);
 *(x+i) = *(x+min);
 *(x+min) = t;
 }
}
}
/*
=======================================
=========
功能:直接插入排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
在要排序的一组数中,假设前面(n-1) [n>=2] 
个数已经是排
好顺序的,现在要把第 n 个数插到前面的有序
数中,使得这 n 个数
也是排好顺序的。如此反复循环,直到全部排
好顺序。
直接插入排序是稳定的。算法时间复杂度
O(n2)--[n 的平方]
=======================================
==============
*/
void insert_sort(int *x, int n)
{
int i, j, t;
for (i=1; i<n; i++) /*要选择的次数:1~n-1
共 n-1 次*/
{
 /*
 暂存下标为 i 的数。注意:下标从 1 开始,
原因就是开始时
 第一个数即下标为 0 的数,前面没有任何
数,单单一个,认为
 它是排好顺序的。
 */
 t=*(x+i);
 for (j=i-1; j>=0 && t<*(x+j); j--) /*
注意:j=i-1,j--,这里就是下标为 i 的数,
在它前面有序列中找插入位置。*/
 {
 *(x+j+1) = *(x+j); /*如果满足条件就
往后挪。最坏的情况就是 t 比下标为 0 的数都
小,它要放在最前面,j==-1,退出循环*/
 }
 *(x+j+1) = t; /*找到下标为 i 的数的放
置位置*/
}
}
/*
=======================================
=========
功能:冒泡排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
在要排序的一组数中,对当前还未排好序的范
围内的全部数,自上
而下对相邻的两个数依次进行比较和调整,让
较大的数往下沉,较
小的往上冒。即:每当两相邻的数比较后发现
它们的排序与排序要
求相反时,就将它们互换。
下面是一种改进的冒泡算法,它记录了每一遍
扫描后最后下沉数的
位置 k,这样可以减少外层循环扫描的次数。
冒 泡 排 序 是 稳 定 的 。 算 法 时 间 复 杂 度
O(n2)--[n 的平方]
=======================================
==============
*/
void bubble_sort(int *x, int n)
{
int j, k, h, t;
 
for (h=n-1; h>0; h=k) /*循环到没有比较范
围*/
{
 for (j=0, k=0; j<h; j++) /*每次预置 k=0,
循环扫描后更新 k*/
 {
 if (*(x+j) > *(x+j+1)) /*大的放在后
面,小的放到前面*/
 {
 t = *(x+j);
 *(x+j) = *(x+j+1);
 *(x+j+1) = t; /*完成交换*/
 k = j; /*保存最后下沉的位置。这样 k
后面的都是排序排好了的。*/
 }
 }
}
}
/*
=======================================
=========
功能:希尔排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
在直接插入排序算法中,每次插入一个数,使
有序序列只增加 1 个节点,
并且对插入下一个数没有提供任何帮助。如果
比较相隔较远距离(称为
增量)的数,使得数移动时能跨过多个元素,
则进行一次比较就可能消除
多个元素交换。D.L.shell 于 1959 年在以他名
字命名的排序算法中实现
了这一思想。算法先将要排序的一组数按某个
增量 d 分成若干组,每组中
记录的下标相差 d.对每组中全部元素进行排
序,然后再用一个较小的增量
对它进行,在每组中再进行排序。当增量减到
1 时,整个要排序的数被分成
一组,排序完成。
下面的函数是一个希尔排序算法的一个实现,
初次取序列的一半为增量,
以后每次减半,直到增量为 1。
希尔排序是不稳定的。
=======================================
==============
*/
void shell_sort(int *x, int n)
{
int h, j, k, t;
for (h=n/2; h>0; h=h/2) /*控制增量*/
{
 for (j=h; j<n; j++) /*这个实际上就是
上面的直接插入排序*/
 {
 t = *(x+j);
 for (k=j-h; (k>=0 && t<*(x+k)); k-=h)
 {
 *(x+k+h) = *(x+k);
 }
 *(x+k+h) = t;
 }
}
}
/*
=======================================
=========
功能:快速排序
输入:数组名称(也就是数组首地址)、数组
中起止元素的下标
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
快速排序是对冒泡排序的一种本质改进。它的
基本思想是通过一趟
扫描后,使得排序序列的长度能大幅度地减少。
在冒泡排序中,一次
扫描只能确保最大数值的数移到正确位置,而
待排序序列的长度可能只
减少 1。快速排序通过一趟扫描,就能确保某
个数(以它为基准点吧)
的左边各数都比它小,右边各数都比它大。然
后又用同样的方法处理
它左右两边的数,直到基准点的左右只有一个
元素为止。它是由
C.A.R.Hoare 于 1962 年提出的。
显然快速排序可以用递归实现,当然也可以用
栈化解递归实现。下面的
函数是用递归实现的,有兴趣的朋友可以改成
非递归的。
快速排序是不稳定的。最理想情况算法时间复
杂度 O(nlog2n),最坏 O(n2)
=======================================
==============
*/
void quick_sort(int *x, int low, int high)
{
int i, j, t;
if (low < high) /*要排序的元素起止下标,
保证小的放在左边,大的放在右边。这里以下
标为 low 的元素为基准点*/
{
 i = low;
 j = high;
 t = *(x+low); /*暂存基准点的数*/
 while (i<j) /*循环扫描*/
 {
 while (i<j && *(x+j)>t) /*在右边的只
要比基准点大仍放在右边*/
 {
 j--; /*前移一个位置*/
 }
 if (i<j) 
 {
 *(x+i) = *(x+j); /*上面的循环退出:
即出现比基准点小的数,替换基准点的数*/
 i++; /*后移一个位置,并以此为基准点
*/
 }
 while (i<j && *(x+i)<=t) /*在左边的
只要小于等于基准点仍放在左边*/
 {
 i++; /*后移一个位置*/
 }
 if (i<j)
 {
 *(x+j) = *(x+i); /*上面的循环退出:
即出现比基准点大的数,放到右边*/
 j--; /*前移一个位置*/
 }
 }
 *(x+i) = t; /*一遍扫描完后,放到适当
位置*/
 quick_sort(x,low,i-1); /*对基准点
左边的数再执行快速排序*/
 quick_sort(x,i+1,high); /*对基准点
右边的数再执行快速排序*/
}
}
/*
=======================================
=========
功能:堆排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
=======================================
=========
*/
/*
=======================================
=============
算法思想简单描述:
堆排序是一种树形选择排序,是对直接选择排
序的有效改进。
堆的定义如下:具有 n 个元素的序列
(h1,h2,...,hn),当且仅当
满足( hi>=h2i,hi>=2i+1 ) 或
(hi<=h2i,hi<=2i+1)(i=1,2,...,n/2)
时称之为堆。在这里只讨论满足前者条件的堆。
由堆的定义可以看出,堆顶元素(即第一个元
素)必为最大项。完全二叉树可以
很直观地表示堆的结构。堆顶为根,其它为左
子树、右子树。
初始时把要排序的数的序列看作是一棵顺序
存储的二叉树,调整它们的存储顺序,
使之成为一个堆,这时堆的根节点的数最大。
然后将根节点与堆的最后一个节点
交换。然后对前面(n-1)个数重新调整使之成
为堆。
依此类推,直到只有两个节点
的堆,并对它们作交换,最后得到有 n 个节点
的有序序列。
从算法描述来看,堆排序需要两个过程,一是
建立堆,二是堆顶与堆的最后一个元素
交换位置。所以堆排序有两个函数组成。一是
建堆的渗透函数,二是反复调用渗透函数
实现排序的函数。
堆 排 序 是 不 稳 定 的 。 算 法 时 间 复 杂 度
O(nlog2n)。
*/
/*
功能:渗透建堆
输入:数组名称(也就是数组首地址)、参与
建堆元素的个数、从第几个元素开始
*/
void sift(int *x, int n, int s)
{
int t, k, j;
t = *(x+s); /*暂存开始元素*/
k = s; /*开始元素下标*/
j = 2*k + 1; /*右子树元素下标*/
while (j<n)
{
 if (j<n-1 && *(x+j) < *(x+j+1))/*判断
是否满足堆的条件:满足就继续下一轮比较,
否则调整。*/
 {
 j++;
 }
 if (t<*(x+j)) /*调整*/
 {
 *(x+k) = *(x+j);
 k = j; /*调整后,开始元素也随之调整
*/
 j = 2*k + 1;
 }
 else /*没有需要调整了,已经是个堆了,
退出循环。*/
 {
 break;
 }
}
*(x+k) = t; /*开始元素放到它正确位置*/
}
/*
功能:堆排序
输入:数组名称(也就是数组首地址)、数组
中元素个数
*/
void heap_sort(int *x, int n)
{
int i, k, t;
int *p;
for (i=n/2-1; i>=0; i--)
{
 sift(x,n,i); /*初始建堆*/

for (k=n-1; k>=1; k--)
{
 t = *(x+0); /*堆顶放到最后*/
 *(x+0) = *(x+k);
 *(x+k) = t;
 sift(x,k,0); /*剩下的数再建堆*/ 
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值