c++求助:这题应该怎么做?

昨天一位读者分享了自己的 C++ 面试经历。简单沟通后,读者双非本硕,自嘲注定就是炼狱模式。50 家公司的 C++ 面经也整理好了。本次就分享下面经。以后分享学习路线和心得。

读者情况:双非本硕,本科机械,硕士做的软件开发课题。 学过一点点 C++,但是面试前连多态都不会写。研三经历炼狱校招,从零开始学习,数据结构和算法也是从零开始。 时间:2020 年 10 月- 2021 年 4 月 结果:几乎面试了所有大厂,收到了 13 份 offer。虽然没有进大厂,但是拿到音视频开发 offer,待遇刚好是大厂白菜价。已经很满足。
  1. 左值引用与右值引用有什么区别,左值引用和右值引用的目的是什么
  2. 构造函数和析构函数能不能是虚函数
  3. TCP/IP 的连接和断开过程
  4. I/O 多路复用是什么
  5. http 接口的函数用过吗
  6. 多线程和多进程的优缺点
  7. Qt信号与槽实现的机制
  8. 项目用的多线程是怎么实现的

  1. 项目:有做过嵌入式项目吗 总结:应该是 KPI,他想找做嵌入式的多点

3.医科达——电话面(10-23)

  1. 单例模式怎么处理多线程

4.联影——电话面(10-24)

  • 怎么处理队员之间的问题

  1. 看过哪些 C++ 的书
  2. 多线程,怎么判断线程结束
  3. vector 中迭代器失效的情况
  4. FFmpeg 中对视频和音频的同步方法

  1. 静态链接库与动态链接库的区别
  2. 怎么对图像进行变换,变换矩阵是什么
  3. 还有其他简单的 OpenGL 问题

  1. 怎么自己实现 Qt 的信号与槽?
  • 其他忘了,面试官脾气不是很好,,

(终于拿到第一份 offer!)

  1. 假如定义了一个指针,后面接着一个循环并 return,会造成什么后果?(内存泄漏)
  2. 其他忘了(没有问项目)

  1. 音视频切换的多线程怎么处理帧切换的问题
  2. 大数据处理题:建立一个数据结构,存储 1 到 10 亿的数,要能够去重和插入(答案:用两个数组,建立二维表格)
  3. 找出一个 RGB 文件中出现最多的前十种颜色

9.传音控股电话面(11-7)

  1. 直接问项目的 OpenGL 怎么绘制视频,结果回答不好被嫌弃了;
  2. 死锁怎么产生的?有没有遇到过死锁问题?(多线程不是一般般的重要!)

  1. 先做算法题:找出数组中次数最多的前 k 个数
  2. 说下 C++ 新特性 auto变量的使用注意事项
  3. OpenGL 的着色器在每个渲染步骤是怎样的?
  4. 设计模式、单例模式的线程安全问题

  1. HTTP 网络协议好多内容
  2. 还有好多问题,几乎把计算机基础的难点都问了

做富士康的工业互联网。看简历,没问技术基础。 给了 offer,但是只有 7K,其他补贴加起来只有 10K,难怪没人去

1.C 和 C++ 的区别 2.栈和堆的区别 3.双链表和单链表的优缺点 4.面向对象三大特性:封装、继承、多态,继承的作用是什么 5.了解 Qt 和 MFC 吗 6.工作地点 7.对薪资有什么要求

  1. 最近的项目——视频播放器设计
  2. 用英语简单描述自己的项目

  1. OOP 三大特性、继承中三个修饰符的用法
  2. TCP 连接过程中客户端与服务端使用什么函数实现连接
  3. 智能指针,weak_ptr 能够破坏环型引用的原理(引用计数的原理)

(秃头架构师面试,态度很好,叫我不要紧张。我印象深刻的一场面试之一)

16.广和通电话面(12-1)

  1. 说下项目解决过程你有没有请教别人

  1. 信号与槽机制、信号与槽与函数指针的比较
  2. 多线程下用信号与槽的优缺点 (connect函数的第五个参数实际上是用在多线程的情况下的)
  3. 怎么保证客户端与服务端在突然断网的情况下保证另一端知道? (饱和机制、心跳机制、看门狗机制)
  4. auto关键字 (不能定义数组)
  5. Qt的事件过滤器 (事件的传递是由子类往父类上传的)
  6. 开放性问题:大量数据无序输入,怎么有序输出? (使用最大堆最小堆) (一面的面试官是个态度很好的年轻小哥)

汇川技术二面(12-4)

  1. 介绍项目组成 - 滤波器的类型 - 怎么确定使用什么滤波算法
  2. 比赛过程中的团队合作事情
  3. 上学期间最有成就感的事情

  1. 介绍音视频测评岗位的工作内容
  2. 视频播放器、直播推流协议
  3. 文件传输、视频协议相关工作
  4. 讲解下用了音视频库的什么模块

19.不知名的公司的音视频开发岗(12-4)

  • 用两个线程实现循环加数
  • 项目:视频播放器用到 FFmpeg 哪些库
  • ALCode 视频解析工具的使用
  • 直播工具使用了什么服务器

(地点在武汉,印象最深的一场面试,面试官很友好,给我讲了半小时的音视频岗位的优势。从此便决定了一定要做音视频开发)

20.腾讯会议客户端(12-4)

  1. 项目——视频播放器的功能
  • 视频中的视频信息、音频信息的数据怎么存放
  • 有几种 NAL,如果在视频中存放信息存放在哪个帧

  1. C++11 什么新特性好用 多线程
  2. 用队列处理数据有什么好处?
  3. 多线程与多进程的区别?
  4. 你认为你的优缺点是什么?

  • 没问技术问题,只问背景问题。应该是 KPI

  1. Linux 中用什么命令查找文件

  • 写一个多态,展示内存泄漏

  1. 操作系统: 消息队列、进程消息
  2. 指针、引用怎么在多态里面用的

  1. 进程和线程、进程的通信方式、线程的安全问题
  2. 怎么用两个栈实现一个队列

  1. 除了 C++,学过其他语言吗
  2. MP4 包含了什么协议
  3. 抖音、直播分别用什么协议
  4. 设计模式有哪几种?单例模式有几种创建方式?
  5. Linux 获取字符串的方式?
  6. 建议:多了解前沿的流媒体格式!

  1. 顶点着色器与片元着色器的不同
  2. 画面闪烁是什么原因造成

  1. 群面,3 个候选人 1 个面试官
  2. 公司介绍:世界 500 强 咨询公司 外企
  3. 在实习、项目中,怎么处理不属于自己工作范围内的工作
  4. 职业方向规划?技术专家还是项目管理

  1. C++ 有几种构造函数
  2. STL 的迭代器失效,怎么解决
  3. C++ 与其他语言的区别?(指针、回收)
  4. 智能指针的使用场景:连接数据库
  5. STL 是复制性还是侵入性
  6. 红黑树比AVL的优势,为何用红黑树
  7. 数据库的锁、事务、引擎
  • 用户的余额显示、余额减少等,要不要用事务?
  • 用户系统,有用户ID。加入有手机号注册,怎么验证是否注册
  • 用户扩大时,怎么扩容?
  • I/O 多路复用的理解
  • 动态规划与贪心算法的区别(背包问题分析)

  1. 岗位必备技能是:C++ 基础、网络、数据库。是电商中台

28.富途证券后台开发(1-4)

  1. 说说从浏览器输入网站用到的协议
  2. MySQL 加快查询的方式

  1. 虚函数表原理,虚表编译的过程
  2. TCP 怎么保证可靠传输
  3. 在一个局域网中怎么连接两台电脑?

  1. QSS 属于第几个版本
  2. 视频播放器怎么音视频同步

  • 信号与槽、和事件的区别

  • 信号与槽的优点和缺点是什么
  • TCP 三次握手的过程
  • 手写代码:自定义 String 类(真的要每天都复习八股文

33.华阳国际设计(1-20)

34.中软国际(外包公司)

  1. vector 改变容量时要注意什么
  • GDB 怎么调试段错误,怎么查看栈

  1. 代码的处理过程、汇编的作用
  • Qt 多线程用什么函数
  • 场景题:找出前 K 个最大元素的值(最大堆、最小堆的用法)

三面(1-28)——经典好问题,必须好好分析

  1. 音视频:YUV 格式与 RGB 格式的区别
  2. 怎么实现一个服务端连接多个客户端?
  3. auto 的类型判断发生在什么阶段(编译期) 如果是在运行期的话会发生什么问题
  4. 锁的类型,自旋锁的原理

35.中国电子系统(1-26)

  1. 实习的时候做的项目,举例做过的工作
  2. 哈希表的原理以及作用、怎么查找值
  • 从 CPU 的角度区别线程和进程
  • 快速排序的思路、复杂度

  1. 代码题:十进制字符串转十六进制字符串

  1. 学过什么数据结构、举例二叉树的用法
  • 自定义结构体到信号与槽要注意什么
  • 工厂模式、单例模式原理和用法
  • 了解软件架构吗?MVC 模式了解吗?
  • 怎么使用软件设计思维?是正向还是逆向?

  • YUV 有很多采样格式,你采用什么采样格式
  • map 与 unordered_map 的区别(考虑到有序与否的区别、哈希函数)
  • lambda 表达式、怎么捕获外部变量
  • socket 阻塞和非阻塞的区别
  • TCP 中间连接的时候断开会发生什么(重传、超时、等待状态、TCP 可靠连接原理)
  • 进程内部的栈内存、堆内存、各自的增长方式
  • 双链表怎么查找倒数第二个结点
  • OpenGL 常见的坐标系的变换顺序

  • C++ 怎么申请连续的内存
  • C++ 的垃圾回收机制
  • 工程模式是什么,3 种工工厂模式的区别
  • 排序算法有哪些,哪些的复杂度是稳定不变的
  • vector 是不是线程安全的,怎么写个线程安全的 vector

41.腾讯企业微信客户端(2-26)

  1. 1 小时内做 3 到算法题
  • 找出数组中最小的K个数
  • 讲解题目(做的不好,分析复杂度)
  • 描述 Qt 的消息传递机制
  • 引用的用法(左值引用与右值引用、引用不能改变绑定对象)

  1. 信号与槽的底层原理;信号与槽怎么做到性能优化
  2. 哈希表与红黑树的对比:结构、查找
  3. Q t的多线程的信号与槽
  4. 构造函数调用虚函数可以吗?会发生什么?
  5. HTTP 的底层模型用什么实现(TCP)
  6. 网络的七层模型,作用、传输单位分别是什么
  7. 写一个快排;能否用非递归方式实现;什么时候复杂度最大?

  1. 听过多用组合,少用继承吗
  2. Qt 的信号与槽原理,怎么了解这个原理的?
  3. 进程、线程的区别,对 OS 而言有什么目的
  4. 对 I、B、P 帧的了解、MOOV 的格式了解
  • 考虑数据采集中断的情况吗

  1. 给出一个二维 vector 表示点到原点的距离。一个五个点,5X5 的二维 vector。求从原点出发,再回到原点的最短路径,要求必须打印输出结果
  2. (条件概率)已知城市中蓝色:绿色车的比例是 15:85,目击者称看到蓝色车肇事逃逸,但是人区别蓝色和绿色的正确率是 80%。求真的是蓝色车肇事逃逸的概率

(三面、四面在同一天进行。据说有的人腾讯面了六面。我止步于吃已经很满足。代码确实不会写)

  1. Qt、C++ 分别用过多久?
  2. Qt 用过多线程、网络吗
  3. C++11 新特性;解释右值引用是为了解决什么问题?(移动语义)

  1. 用过 C++ 的闭包吗?
  2. 你目前的学习方式是什么?

  • 多线程共享内容问题,共享对象存放在哪个空间
  • 多进程通信的方式,有几种信号
  • 手撕代码:把有序链表转为平衡二叉树

  • 虚函数是类的定义出现还是对象的时候出现
  • auto 关键字能给数组赋值,但是不能定义
  • 用过 Qt 的什么模块

  1. 讲一下对 OOP 的理解
  • 互斥量为何能够用在多线程

  1. 对 IT 工程师的理解
  2. 举例压力大的实例,压力大的时候怎么解决
  3. 收了什么 offer,为什么拒了
  4. 举例说明你说服别人接收自己观点的一个例子

  1. 虚表是怎么使用的?虚表指针存放在哪里?
  2. 构造函数能否为 virtual,能否调用虚函数?
  3. coding:两数之和,非有序
  4. coding:LeetCode61 ——给定链表,按照某个规定旋转链表
  5. 逻辑题:有 10 箱金子,,,,,,

  1. 下面是有一个全局变量 a 和两个线程,这两个线程同时开始并发执行各自的代码, 在两个线程都执行结束后,请问 a 的值为______ static int a = 0;
  1. 已知公司 OA 数据库有一个员工信息表,包含员工 ID,员工姓名,入职时间,和离职时间。财务审核时发现 201803 到 201808 这 6 个月,当时所有在职员工都少发了工资,现在老板需要了解有多少人受影响需要获得补偿。请写出查询语句。
  1. 实验室有 100 个瓶子,其中有一瓶装有慢性毒药(第 3 天发作),另外 99 瓶装有蒸馏水。请问至少需要多少只小白鼠才能在3天内找出哪一瓶是慢性毒药?_______只

4.找出出现频率最高的前 K 个数,或者从海量数据中找出最大的前 K 个数

5.实现排序二叉树的插入方法

  1. 最近的 offer?为何不要?
  2. 怎么完成学校到公司的过渡?

  • 事务的定义、4 个特性
  • 索引(数据结构、优缺点)
  • 如何查看 OS 的大小(free)
  • 举例说明遇到的难题以及解决方法
  • 说服别人接收你的观点的例子

  1. 做过窗口的信号共享吗?
  2. 项目的滤波算法是什么?
  3. 音视频项目的难题是什么?
  4. 遇到过程序崩溃的情况吗?
  5. TCP 的连接、释放过程
  • 分别发生在客户端和服务端的什么阶段?

(终于拿到所谓大厂 offer。但是是 IT 部门,不是研发部门,在佛山。所以拒了)

  1. 视频文件是什么格式,怎么存放的
  2. 怎么设计视频播放器的暂停?后端是怎么处理的?进度条怎么显示的?
  3. 指针和引用的区别?在传递参数上有什么区别?
  4. 函数未定义时会怎么样?
  5. 写代码:用 C++ 实现全排列
  6. 工作问题:如何看待加班?

  • C/S 模式是如何处理的?
  • 用 TCP 怎么传递消息?
  • 用 UDP 怎么实现可靠的传输?
  • 算法问题: 网格中有 2 个点,怎么找到从 A 到 B 的最短路径?(用广度优先搜索)
  • 数据库:为何索引的数据结构用 B-tree? (因为数据库主要消耗在磁盘 I/O 上,所以要优化磁盘 I/O ,如果用红黑树,树的深度太高,消耗磁盘 I/O 太多,速度会太慢)

50.英特尔现场面(4-12)

  1. 做一份笔试,问的比较细节,操作系统比较重要,几个 C语言的关键字没用过
  2. 三个面试官轮流进来房间面试,一个面试官问半小时。问的技术问题不多,主要问项目细节。
  3. 其中一个面试官的一个问题用英语问,要求用英语回答并讨论。这个面试官告诉我好多面对项目的态度和看法,要发散思维,考虑用户体验。

(最后一家面试以英特尔结束,算是完美结束了。反正我不留上海,不过就不过了)

说一下static关键字的作用

在全局变量前加上关键字 static,全局变量就定义成一个全局静态变量。

静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为 0(自动对象的值是任意的,除非他被显式初始化);

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

在局部变量之前加上关键字 static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区

初始化:未经初始化的全局静态变量会被自动初始化为 0(自动对象的值是任意的,除非他被显式初始化);

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

在函数返回类型前加 static,函数就定义为静态函数。函数的定义和声明在默认情况下都是 extern 的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

函数的实现使用 static 修饰,那么这个函数只可在本 cpp 内使用,不会同其他 cpp 中的同名函数引起冲突;

warning:不要再头文件中声明 static 的全局函数,不要在 cpp 内声明非static 的全局函数,如果你要在多个 cpp 中复用该函数,就把它的声明提到头文件里去,否则 cpp 内部声明需加上 static 修饰;

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

说一下C++和C的区别

C++ 是面向对象的语言,而C是面向过程的结构化编程语言

  • C++ 具有封装、继承和多态三种特性
  • C++ 相比 C,增加多许多类型安全的功能,比如强制类型转换、
  • C++ 支持范式编程,比如模板类、函数模板等

说一说c++中四种cast转换

用于各种隐式转换,比如非 const 转 const,void* 转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

  • 向上转换:指的是子类向基类的转换
  • 向下转换:指的是基类向子类的转换

它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5、为什么不使用 C 的强制转换?

C 的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

请说一下C/C++ 中指针和引用的区别?

  1. 指针有自己的一块空间,而引用只是一个别名;
  2. 使用 sizeof 看一个指针的大小是4,而引用则是被引用对象的大小;
  3. 指针可以被初始化为 NULL,而引用必须被初始化且必须是一个已有对象 的引用;
  4. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
  5. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  6. 指针可以有多级指针(**p),而引用止于一级;
  7. 指针和引用使用++运算符的意义不一样;
  8. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

给定三角形ABC和一点P(x,y,z),判断点P是否在ABC内,给出思路并手写代码

根据面积法,如果 P 在三角形 ABC 内,那么三角形 ABP 的面积+三角形 BCP 的面积+三角形 ACP 的面积应该等于三角形 ABC 的面积。算法如下:

* @brief 判断给定一点是否在三角形内或边上

怎么判断一个数是二的倍数,怎么求一个数中有几个1,说一下你的思路并手写代码

  1. 判断一个数是不是二的倍数,即判断该数二进制末位是不是 0: a % 2 == 0 或者a & 0x0001 == 0
  2. 求一个数中 1 的位数,可以直接逐位除十取余判断:

请你回答一下野指针是什么?

野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针

请你介绍一下 C++ 中的智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11 中最常用的智能指针类型为 shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为 0 时,智能指针才会自动释放引用的内存资源。对 shared_ptr 进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过 make_shared 函数或者通过构造函数传入普通指针。并可以通过 get 函数获得普通指针。

请你回答一下智能指针有没有内存泄露的情况

当两个对象相互使用一个 shared_ptr 成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。例如:

请你来说一下智能指针的内存泄漏如何解决

为了解决循环引用导致的内存泄漏,引入了 weak_ptr 弱指针,weak_ptr 的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数 考点:虚函数 析构函数

将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

C++ 默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此 C++ 默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

请你来说一下 C++ 中析构函数的作用

析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。

析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如 ~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括 void 类型)。只能有一个析构函数,不能重载。

如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。

  • 派生类本身的析构函数;

请你来说一下静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销

请你来说一说重载和覆盖

重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中

重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写

strcpy 是字符串拷贝函数,原型:

从 src 逐字节拷贝到 dest,直到遇到 '\0' 结束,因为没有指定长度,可能会导致拷贝越界,造成缓冲区溢出漏洞,安全版本是 strncpy 函数。

strlen 函数是计算字符串长度的函数,返回从开始到 '\0' 之间的字符个数。

请你说一说你理解的虚函数和多态

多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了 virtual 关键字的函数,在子类中重写时候不需要加 virtual 也是虚函数。

虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

请你来回答一下 ++i 和 i++ 的区别

请你来说一说++i和i++的实现

请你来写个函数在 main 函数执行前先运行

以下四行代码的区别是什么?

//字符串123保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样 //字符串123保存在常量区,这个arr指针指向的是同一个位置,同样不能通过brr去修改"123"的值 //这里123本来是在栈上的,但是编译器可能会做某些优化,将其放到常量区 //字符串123保存在栈区,可以通过drr去修改

请你来说一下 C++ 里是怎么定义常量的?常量存放在内存的哪个位置?

常量在 C++ 里的定义就是一个 top-level const 加上对象类型,常量定义必须初始化。对于局部对象,常量存放在栈区,对于全局对象,常量存放在全局/静态存储区。对于字面值常量,常量存放在常量存储区。

请你来回答一下 const 修饰成员函数的目的是什么?

const 修饰的成员函数表明函数调用不会对对象做出任何更改,事实上,如果确认不会对对象做更改,就应该为函数加上 const 限定,这样无论 const 对象还是普通对象都可以调用该函数。

如果同时定义了两个函数,一个带 const,一个不带,会有问题吗?

不会,这相当于函数的重载。

请你来说一说隐式类型转换

首先,对于内置类型,低精度的变量给高精度变量赋值会发生隐式类型转换,其次,对于只存在单个参数的构造函数的对象构造来说,函数调用可以直接使用该参数传入,编译器会自动调用其构造函数生成临时对象。

请你来说一说 C++ 函数栈空间的最大值

默认是 1M,不过可以调整

首先,new/delete 是 C++ 的关键字,而 malloc/free 是 C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数

请你说说你了解的RTTI

请你说说虚函数表具体是怎样实现运行时多态的?

子类若重写父类虚函数,虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在 VS 中,对象的对象模型的头部存放指向虚函数表的指针,通过该机制实现多态。

请你说说C语言是怎么进行函数调用的?

每一个函数调用都会分配函数栈,在栈内进行函数执行过程。调用前,先把返回地址压栈,然后把当前函数的 esp 指针压栈。

请你说说 C语言参数压栈顺序?

请你说说 C++ 如何处理返回值?

生成一个临时变量,把它的引用作为函数参数传入函数内。

请你回答一下 C+ +中拷贝赋值函数的形参能否进行值传递?

不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满。

select 在使用前,先将需要监控的描述符对应的 bit 位置 1,然后将其传给 select,当有任何一个事件发生时,select 将会返回所有的描述符,需要在应用程序自己遍历去检查哪个描述符上有事件发生,效率很低,并且其不断在内核态和用户态进行描述符的拷贝,开销很大

父进程产生子进程使用 fork 拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec 函数可以加载一个 elf 文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork 从父进程返回子进程的 pid,从子进程返回 0.调用了 wait 的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回 0,错误返回 -1。exec 执行成功则子进程从新的程序开始运行,无返回值,执行失败返回 -1

请你回答一下静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销

  • 重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中
  • 重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写

请你来说一下map和set有什么区别,分别又是怎么实现的?

map 和 set 都是 C++ 的关联容器,其底层实现都是红黑树(RB-Tree)。由于 map 和 set 所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 map 和set的操作行为,都只是转调 RB-tree 的操作行为。

  1. map 中的元素是 key-value(关键字—值)对:关键字起到索引的作用,值则表示与索引相关联的数据;Set 与之相对就是关键字的简单集合,set 中每个元素只包含一个关键字。
  2. set 的迭代器是 const 的,不允许修改元素的值;map 允许修改 value,但不允许修改 key。其原因是因为 map 和 set 是根据关键字排序来保证其有序性的,如果允许修改 key 的话,那么首先需要删除该键,然后调节平衡,再插入修改后的键值,调节平衡,如此一来,严重破坏了 map 和 set 的结构,导致 iterator 失效,不知道应该指向改变前的位置,还是指向改变后的位置。所以 STL 中将 set 的迭代器设置成 const,不允许修改迭代器的值;而 map 的迭代器则不允许修改 key 值,允许修改 value 值。
  3. map 支持下标操作,set 不支持下标操作。map 可以用 key 做下标,map 的下标运算符[ ]将关键码作为下标去执行查找,如果关键码不存在,则插入一个具有该关键码和 mapped_type 类型默认值的元素至 map 中,因此下标运算符[ ]在 map 应用中需要慎用,const_map 不能用,只希望确定某一个关键值是否存在而不希望插入元素时也不应该使用,mapped_type 类型没有默认值也不应该使用。如果 find 能解决需要,尽可能用 find。

STL 的分配器用于封装 STL 容器在内存管理上的底层细节。在 C++ 中,其内存配置和释放如下:

  • new 运算分两个阶段:
  1. 调用对象构造函数构造对象内容
  • delete 运算分两个阶段:

同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL 采用了两级配置器,当分配的空间大小超过 128B 时,会使用第一级空间配置器;当分配的空间大小小于 128B 时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

请你来说一说 STL 迭代器删除元素

这个主要考察的是迭代器失效的问题。

  1. 对于序列容器vector,deque来说,使用erase(itertor)后,后边的每个元素的迭代器都会失效,但是后边每个元素都会往前移动一个位置,但是erase会返回下一个有效的迭代器;
  2. 对于关联容器map set来说,使用了erase(iterator)后,当前元素的迭代器失效,但是其结构是红黑树,删除当前元素的,不会影响到下一个元素的迭代器,所以在调用erase之前,记录下一个元素的迭代器即可。
  3. 对于list来说,它使用了不连续分配的内存,并且它的erase方法也会返回下一个有效的iterator,因此上面两种正确的方法都可以使用。

请你说一说 STL 中 MAP 数据存放形式

参考回答: 红黑树。unordered map底层结构是哈希表

请你讲讲 STL 有什么基本组成

STL 主要由:以下几部分组成:

容器迭代器仿函数算法分配器配接器

他们之间的关系:分配器给容器分配存储空间,算法通过迭代器获取容器中的内容,仿函数可以协助算法完成各种操作,配接器用来套接适配仿函数

  1. Map映射,map 的所有元素都是 pair,同时拥有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。不允许键值重复。

适用场景:有序键值对不重复映射

    的第一元素被视为键值,第二元素被视为实值。所有元素都会根据元素的键值自动被排序。允许键值重复。

适用场景:有序键值对可重复映射

请你说一说 vector 和 list 的区别,应用,越详细越好

  1. Vector 连续存储的容器,动态数组,在堆上分配空间

vector 增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调整迭代器。 如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构并释放原空间,之前的迭代器会失效。

插入:在最后插入(空间够):很快

  • 在最后插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。
  • 在中间插入(空间够):内存拷贝
  • 在中间插入(空间不够):需要内存申请和释放,以及对之前数据进行拷贝。 删除:

适用场景:经常随机访问,且不经常对非尾节点进行插入删除。 2. List 动态链表,在堆上分配空间,每插入一个元数都会分配空间,每删除一个元素都会释放空间。

访问:随机访问性能很差,只能快速访问头尾节点。

插入:很快,一般是常数开销

删除:很快,一般是常数开销

适用场景:经常插入删除大量数据

  • vector 底层实现是数组;list 是双向 链表。
  • vector 在中间节点进行插入删除会导致内存拷贝,list 不会。
  • vector 一次性分配好内存,不够时才进行 2 倍扩容;list 每次插入新节点都会进行内存申请。
  • vector 随机访问性能好,插入删除性能差;list 随机访问性能差,插入删除性能好。 应用 vector 拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用 vector。

list 拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用 list。

请你来说一下 STL 中迭代器的作用,有指针为何还要迭代器

Iterator(迭代器)模式又称 Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。或者这样说可能更容易理解:Iterator 模式是运用于聚合对象的一种模式,通过运用该模式,使得我们可以在不知道对象内部表示的情况下,按照一定顺序(由 iterator 提供的方法)访问聚合对象中的各个元素。

由于 Iterator 模式的以上特性:与聚合对象耦合,在一定程度上限制了它的广泛运用,一般仅用于底层聚合支持类,如 STL 的 list、vector、stack 等容器类及 ostream_iterator 等扩展 iterator。

迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->、*、++、--等。迭代器封装了指针,是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象, 本质是封装了原生指针,是指针概念的一种提升(lift),提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的 ++,--等操作。

迭代器返回的是对象引用而不是对象的值,所以 cout 只能输出迭代器使用 * 取值后的值而不能直接输出其自身。

Iterator 类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。

请你说一说epoll原理

首先创建一个 epoll 对象,然后使用 epoll_ctl 对这个对象进行操作,把需要监控的描述添加进去,这些描述如将会以 epoll_event 结构体的形式组成一颗红黑树,接着阻塞在 epoll_wait,进入大循环,当某个 fd 上有事件发生时,内核将会把其对应的结构体放入到一个链表中,返回有事件发生的链表。

n 个整数的无序数组,找到每个元素后面比它大的第一个数,要求时间复杂度为 O(N)

reserve():改变当前容器的最大容量(capacity),它不会生成元素,只是确定这个容器允许放入多少对象,如果reserve(len)的值大于当前的capacity(),那么会重新分配一块能存len个对象的空间,然后把之前v.size()个对象通过copy construtor复制过来,销毁之前的内存;

请你来说一下 C++ 中类成员的访问权限

C++ 通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。在类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制。在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问

在 C++ 中,可以用 struct 和 class 定义类,都可以继承。区别在于:structural 的默认继承权限和默认访问权限是 public,而 class 的默认继承权限和默认访问权限是 private。 另外,class 还可以定义模板类形参,比如 template。

请你回答一下C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。

请你回答一下 malloc 的原理,另外 brk 系统调用和 mmap 系统调用的作用分别是什么?

Malloc 函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc 其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。 Malloc 采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时 malloc 采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。

当进行内存分配时,Malloc 会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc 采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。

Malloc 在申请内存时,一般会通过 brk 或者 mmap 系统调用进行申请。其中当申请内存小于 128K 时,会使用系统函数 brk 在堆区中分配;而当申请内存大于 128K 时,会使用系统函数 mmap 在映射区分配。

请你说一说C++的内存管理是怎样的?

在 C++ 中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。

  • 代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
  • 数据段:存储程序中已初始化的全局变量和静态变量
  • bss 段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。
  • 堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
  • 映射区:存储动态链接库以及调用mmap函数进行的文件映射
  • 栈:使用栈空间存储函数的返回地址、参数、局部变量、返回值

请你来说一下 C++/C 的内存分配

32bitCPU 可寻址 4G 线性空间,每个进程都有各自独立的 4G 逻辑地址,其中 0~3G 是用户态空间,3~4G 是内核空间,不同进程相同的逻辑地址会映射到不同的物理地址中。其逻辑地址其划分如下:

3G 用户空间和 1G 内核空间

  • text segment(代码段):包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
  • data segment(数据段):存储程序中已初始化的全局变量和静态变量
  • bss segment:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量,对于未初始化的全局变量和静态变量,程序运行main之前时会统一清零。即未初始化的全局变量编译器会初始化为 0 动态区域:
  • heap(堆):当进程未调用 malloc 时是没有堆段的,只有调用 malloc 时采用分配一个堆,并且在程序运行过程中可以动态增加堆大小(移动 break 指针),从低地址向高地址增长。分配小内存时使用该区域。 堆的起始地址由 mm_struct 结构体中的 start_brk 标识,结束地址由 brk 标识。
  • stack(栈):使用栈空间存储函数的返回地址、参数、局部变量、返回值,从高地址向低地址增长。在创建进程时会有一个最大栈大小,Linux 可以通过 ulimi t命令指定。

请你回答一下如何判断内存泄漏?

内存泄漏通常是由于调用了 malloc/new 等内存申请的操作,但是缺少了对应的 free/delete。为了判断内存是否泄露,我们一方面可以使用 linux 环境下的内存泄漏检查工具 Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。

请你来说一下什么时候会发生段错误

段错误通常发生在访问非法内存地址的时候,具体来说分为以下几种情况:

  • 试图修改字符串常量的内容

请你来回答一下什么是 memory leak,也就是内存泄漏

内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

  • 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过 malloc,realloc new 等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
  • 系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET 等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
  • 没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是 virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

请你来说一下 reactor 模型组成

reactor 模型要求主线程只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程,除此之外,主线程不做任何其他实质性的工作,读写数据、接受新的连接以及处理客户请求均在工作线程中完成。其模型组成如下:

  1. Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接。
  2. Synchronous Event Demultiplexer(同步事件复用器):阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的 Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的 select 来实现。

请自己设计一下如何采用单线程的方式处理高并发

在单线程模型中,可以采用 I/O 复用来提高单线程处理多个请求的能力,然后再采用事件驱动模型,基于异步回调来处理事件来

请你说说 select,epoll 的区别,原理,性能,限制都说一说

IO 复用模型在阻塞 IO 模型上多了一个 select 函数,select 函数有一个参数是文件描述符集合,意思就是对这些的文件描述符进行循环监听,当某个文件描述符就绪的时候,就对这个文件描述符进行处理。

这种 IO 模型是属于阻塞的 IO。但是由于它可以对多个文件描述符进行阻塞监听,所以它的效率比阻塞 IO 模型高效。

当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select() 函数就可以返回。

server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

select:是最初解决 IO 阻塞问题的方法。用结构体 fd_set 来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理。 存在的问题:

  • 内置数组的形式使得 select 的最大文件数受限与 FD_SIZE;
  • 每次调用 select 前都要重新初始化描述符集,将 fd 从用户态拷贝到内核态,每次调用 select 后,都需要将 fd 从内核态拷贝到用户态;
  • 轮寻排查当文件描述符个数很多时,效率很低;

poll:通过一个可变长度的数组解决了select 文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll 解决了 select 重复初始化的问题。轮寻排查的问题未解决。

epoll:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll 采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。

LT(level triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。

告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比LT模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

3、LT 模式与 ET 模式的区别如下:

LT 模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET 模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

请问 C++11 有哪些新特性?

C++11 最常用的新特性如下:

  • auto 关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导
  • nullptr 关键字:nullptr 是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而 NULL 一般被宏定义为 0,在遇到重载时可能会出现问题。
  • 初始化列表:使用初始化列表来对类进行初始化
  • 右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率
  • atomic 原子操作用于多线程资源互斥操作

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

我在这想看到几件事情:

1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少 秒而不是计算出实际的值,是更清晰而没有代价的。

3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

这个测试是为下面的目的而设的:

1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

2)三重条件 操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。

3) 懂得在宏中小心地把参数用括号括起来

4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

3. 预处理器标识#error的目的是什么?

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只 有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

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

这个问题用几个解决方案。我首选的方案是:

一些程序员更喜欢如下方案:

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原 理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。

第三个方案是用 goto

应试者如给出上面的方案,这说明或者他是一个汇编 语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

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

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当 我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少 大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数 据和代码范围的好处和重要性。

我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可 以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正 确回答这个问题,我将问他一个附加的问题:

下面的声明都是什么意思?

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可 以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常 指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句, 也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理 其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

如果我遇到这种题,我只想在试卷上写道:

// 求在C++03下a的值,以及本段代码中出现了多少UB。

然后交卷,走人,拉黑电话、QQ、微信、邮箱,把这家公司加入就业黑名单。

我大四校招时就是直接拉黑考这种题目的公司,不过现在早忘了拉黑了哪些了,因为就一些名不见经传的小公司喜欢问这个,印象中还没拉黑过哪家大厂。

没想到养猪网在四年之后,又一次刷新了我的下限。

也许那个问题的关注者中,会有人觉得,网易的C++考官,水平比你们这些大四学生高不知道哪去了,你们凭什么看不起他们?

不好意思,技术高不代表我不能看不起你。更何况出这种问题的人,可以说自己技术高,但千万别说自己C/C++技术高。没错,这口锅,C都不愿意背,人家也是有typedef的。

不友善的话说完了,回归知乎本质,认真讨论下我为什么觉得应该拉黑这样的公司:

  1. 这种代码风格,极有可能撞上UB。并且,喜欢写这种代码的人,反而会把灵活掌握各平台编译器UB当做自己技术高的表现,大用特用。
  2. 我们是工程师,不是语言律师。我们要的是可读性,可维护性,健壮性(好想装B说鲁棒性)。写这种代码的人,大学要么没学过软件工程,要么这门水课都能考挂科。我不敢和这种人共事,否则产品上线出问题背不起锅。
  3. 出这种笔试题的人,根本不懂thinking in C++,更别提C++11开始的modern C++了。而且,也不懂C,反而拿着自己以为很厉害的C来写C++。再联想到,出笔试题的至少也得是个研发部门的技术经理/架构师/TL这种岗吧?一想到以后要跟着类似的boss写shi一样的代码,我就不寒而栗。

所以,我选择用他们最喜欢的代码风格,去回答他们的这个问题。

如果是在面试上遇到的话,我会用我那道题怼回去。从面试官的回答里,我就能知道,他是真的语言律师,还是喜欢奇技淫巧的,亦或是根本没本事自己出题所以瞎问的。

对了,不管是哪一种,我都选择拉黑。

最后,我那段代码的UB主要有三:

  1. 复合表达式的执行顺序,在C++17前是UB。
  2. 函数输入参数的计算顺序,在C++17前也是UB。(据说MSVC编译器就因为这方面的历史原因,无法100%实现某些调用约定)
  3. Signed integer overflow,整形数据溢出也是UB——虽然因为CPU架构问题,在现行几乎所有平台上,整形溢出都会变成补码表示的负数,有可能C语言、离散数学、编译原理、计算机组成原理这些课程中,讲计算机中的数值处理的章节也会这么教,但这依然是UB

我要回帖

更多关于 怎么做语文阅读理解题 的文章

 

随机推荐