东软集团 C++开发 一面
1. C 和 C++ 的不同
答案:C 和 C++ 最大的区别,不只是“一个面向过程,一个面向对象”,而是它们在抽象能力、类型系统和资源管理方式上差异很大。
从语言层面看,C 更偏底层过程式编程,强调结构体、函数、指针和手动资源管理。C++ 在兼容 C 的基础上,增加了很多更强的抽象能力:
- 面向对象:封装、继承、多态
- 泛型编程:模板
- 资源管理:RAII
- 标准库:STL 容器、算法、智能指针、线程库
- 更强的类型系统:引用、函数重载、命名空间、异常等
实际开发里,C 更适合:
- 对运行时开销极度敏感的场景
- 偏底层、硬件、驱动、嵌入式开发
C++ 更适合:
- 大型工程
- 需要抽象能力和可维护性的系统
- 对性能和工程化都要求较高的场景
代码:
// C 风格
struct Point {
int x;
int y;
};
void init(Point* p, int x, int y) {
p->x = x;
p->y = y;
}
// C++ 风格
class PointCpp {
public:
PointCpp(int x, int y) : x_(x), y_(y) {}
void print() const {}
private:
int x_;
int y_;
};
2. 可调用对象有哪些?函数指针、仿函数、Lambda、std::function 有什么区别
答案:
常见可调用对象包括:
- 普通函数
- 函数指针
- 仿函数对象
- Lambda 表达式
std::function- 成员函数指针(需要绑定对象)
它们的区别主要体现在:
- 是否能保存状态
- 是否有类型擦除
- 调用开销是否固定
- 使用方式是否灵活
函数指针:
- 本质是指向函数入口地址
- 轻量,开销小
- 只能表示普通函数或静态函数
仿函数对象:
- 通过重载
operator() - 可以保存状态
- 编译期类型明确,优化空间大
Lambda:
- 本质上通常也是一个匿名仿函数对象
- 支持捕获上下文
- 使用很灵活
std::function:
- 提供统一调用包装
- 能装函数指针、Lambda、仿函数
- 使用方便,但可能有额外开销
代码:
#include <iostream>
#include <functional>
using namespace std;
void foo() {
cout << "foo\n";
}
struct Functor {
void operator()(int x) const {
cout << "Functor: " << x << "\n";
}
};
int main() {
void (*fp)() = foo;
fp();
Functor f;
f(10);
auto lam = [](int x) { cout << "Lambda: " << x << "\n"; };
lam(20);
std::function<void(int)> func = lam;
func(30);
}
3. 值传递、指针传递、引用传递有什么区别
答案:核心区别在于:是否拷贝实参、函数内是否能直接影响外部对象、语义是否清晰。
值传递:
- 会拷贝一份参数
- 函数内修改的是副本
- 不影响外部原对象
- 对于大对象可能有拷贝开销
指针传递:
- 传的是地址的副本
- 通过解引用可以修改外部对象
- 可能为空,需要判空
- 语义上更偏“可选对象”或“需要显式地址操作”
引用传递:
- 本质上是别名
- 可以直接操作原对象
- 语法更自然
- 不能为空,通常比指针更适合作为必须参数
如果参数是大对象,又不需要修改,一般常用:
const T&
这样既避免拷贝,也不允许修改。
代码:
#include <iostream>
using namespace std;
void byValue(int x) {
x = 100;
}
void byPointer(int* x) {
if (x) *x = 200;
}
void byReference(int& x) {
x = 300;
}
int main() {
int a = 10;
byValue(a);
cout << a << endl; // 10
byPointer(&a);
cout << a << endl; // 200
byReference(a);
cout << a << endl; // 300
}
4. list 和 vector 的内容和区别
答案:vector 和 list 都是 STL 容器,但底层结构完全不同。
vector:
- 底层是连续内存
- 支持随机访问
- 尾部插入效率高
- 扩容时可能整体搬迁
- 遍历性能通常很好,cache 友好
list:
- 底层是双向链表
- 不支持随机访问
- 任意位置插入删除方便
- 每个节点有额外指针开销
- 内存不连续,遍历局部性差
如果只看理论复杂度,很多人会觉得插删多就用 list。但真实工程里,vector 往往仍然更常见,因为:
- 连续内存的访问性能通常更好
- 常数开销更小
- 大多数场景读多写少
代码:
#include <vector>
#include <list>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
list<int> l = {1, 2, 3};
v.push_back(4);
l.push_back(4);
}
5. 模板和模板特化
答案:模板的作用是把类型参数化,让同一份代码适配不同类型。它是 C++ 泛型编程的基础。
常见模板有两类:
- 函数模板
- 类模板
模板特化是在“通用模板”基础上,对某些特定类型提供专门实现。常见形式有:
- 全特化
- 偏特化(主要用于类模板)
适合使用模板特化的场景通常是:
- 某些类型需要特殊处理
- 通用实现对特定类型效率不高
- 需要根据类型定制行为
代码:
#include <iostream>
using namespace std;
template <typename T>
class Printer {
public:
void print() {
cout << "generic type\n";
}
};
template <>
class Printer<int> {
public:
void print() {
cout << "int specialization\n";
}
};
int main() {
Printer<double> p1;
p1.print();
Printer<int> p2;
p2.print();
}
6. 什么是完美转发?T&& 在模板里为什么不一定是右值引用
答案:
在模板里,T&& 不总是普通意义上的右值引用。如果它出现在类型推导场景里,往往是转发引用。它的特点是:
- 传入左值时,
T会被推导成左值引用类型 - 传入右值时,
T会被推导成普通类型
完美转发的目的,是把参数原本的值类别保留下来,再继续传给其他函数,避免不必要的拷贝或错误地把左值当右值。
常见搭配是:
T&&std::forward<T>(arg)
代码:
#include <iostream> #include <utility> usin
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

查看30道真题和解析