Julie在为各种规模的企业构建软件应用程序和领导工程团队方面拥有超过20年的经验. She has expertise in Java, JavaScript, C, C++, and Perl, 并且熟悉许多流行的框架. 最近,Julie为沃尔玛设计并实现了一个大规模的Oracle数据库分片解决方案.com.
Darien是一位精通图像处理的软件工程师, desktop application, and systems development. 在他职业生涯的早期,他是一名研究工程师, 用于获取和处理大量科学数据的书写工具. 后来,他开始为医疗设备开发符合FDA标准的软件. 最近,他一直在为一家3D打印公司编写应用软件.
At Toptal, 我们对c++开发人员进行了彻底的筛选,以确保我们只为您匹配最优秀的人才. Of the more than 200,每年有5000人申请加入Toptal网络, fewer than 3% make the cut. 你将与工程专家(而不是一般的招聘人员或人力资源代表)一起了解你的目标, technical needs, and team dynamics. 最终的结果是:经过专家审查的人才从我们的网络,定制匹配,以满足您的业务需求.
我可以在48小时内通过Toptal雇佣c++开发人员吗?
取决于可用性和进度, 您可以在注册后48小时内开始与c++开发人员合作.
Toptal c++开发人员的无风险试用期是什么?
我们确保您和您的c++开发人员之间的每次约定都以长达两周的试用期开始. 这意味着你有时间确认订婚是否成功. 如果你对结果完全满意, 我们会给你开时间单的,你愿意多久我们就多久. 如果您不完全满意,我们不会向您收费. From there, we can either part ways, 或者我们可以为您提供另一位可能更合适的专家,我们将与他开始第二轮谈判, no-risk trial.
Share
如何聘请优秀的c++开发人员
C++ is a powerful, 用于从后端到嵌入式开发的所有内容的通用编程语言, to desktop apps, 到它们运行的操作系统. 它是少数几种经受住时间考验的语言之一, 因此,它当之无愧地受到欢迎和尊重. 但是,尽管ISO c++标准委员会和社区努力使其对程序员更友好, 它仍然可以说是最难掌握的语言之一.
What’s Special About C++?
A great C++ developer is primarily a great software developer具有很强的解决问题能力和抽象思维能力的人, 找到合适的工具和框架的能力, 以及对计算机科学的热情.
There are plenty of interview questions that are language independent and designed to check the engineering prowess of the candidate; on a more basic level, the FizzBuzz question 在过滤一般编码能力方面是出了名的有效. 但我们目前的重点将非常具体地针对c++.
如果候选人也熟悉应用程序开发 other languages,你就有机会让面试过程更有成效. In that case, 这前三个问题将开启一场关于不同语言哲学的有趣讨论, 它们的基本结构, 以及由此产生的优点和缺点.
问:什么是RAII ?它与没有RAII的事实有什么关系 finally c++异常处理中的关键字?
RAII代表“资源获取即初始化”,是一种特定于c++的资源管理技术. 这是基于这样一个事实:c++有析构函数,并且保证当对象超出作用域时将调用它们, even in exception handling.
We don’t need finally 来处理我们的资源,因为我们可以用RAII包装它们,并确保析构函数将被调用, thanks to stack unwinding.
The fact that GetBaseSalary and MonthlyPerformance are const 成员函数告诉我们 employee 在这些调用之后不会改变它的状态. GetDebt takes a const Employee&也就是说,它没有被修改过. The PaySalary 然而,函数需要一个 Employee&这就是我们要深入研究的地方.
而且仍然有大量的c++ 98和c++ 03代码需要持续的关注和关注. c++ 11和c++ 14也是如此. Besides knowing about the constexpr if (which came with C++17), 一个优秀的c++程序员也应该知道如何在使用旧标准的情况下获得同样的结果.
一个有趣的开始讨论的方法是问候选人他们最喜欢的特性是什么。. 强有力的候选人应该能够列出给定标准的最重要的特性, choose a favorite one, 并给出他们选择的理由. Of course, 没有正确或错误的答案, 但它很快就揭示了候选人对语言的感觉.
问:c++ 11/14引入了几个重要的特性. 你认为哪一个带来了最大的改善,为什么?
The auto Keyword and Range-based for Loops
The auto 关键字使我们不必在编译器能够推断变量类型时显式地指定它. 这将产生更清晰、更易读和更通用的代码. The range-based for 循环是最常见用例的语法糖.
// before C++11
for (std::vector::const_iterator it = vec.begin(); it != vec.end(); ++it) {
// after C++11
for (const auto& val : vec) {
Lambda Functions
这是一种允许开发人员就地定义函数对象的新语法.
std::count_if(vec.begin(), vec.end(), [](const int value) { return value < 10; });
c++ 11引入了新的智能指针: unique_ptr, shared_ptr, and weak_ptr. unique_ptr effectively made auto_ptr obsolete.
对线程的内置支持
无需纠结于os原生和c风格的库.
问:你觉得c++ 17的哪个特性最有用?为什么?
Structured Bindings
这个特性增加了可读性.
// before C++17
for (const auto& Name_and_id: name_to_id) {
const auto& name = name_and_id.first;
const auto& id = name_and_id.second;
// after C++17
for (const auto& [name, id] : name_to_id) {
Compile-time if
With if constexpr,我们可以根据编译时条件启用代码的不同部分. 这使得模板元编程(TMP) enable_if 魔术更容易和更好地实现.
内置文件系统库
就像c++ 11中的线程支持一样, 这个内置库继续减少c++开发人员编写特定于操作系统的代码的需要. 它提供了一个接口来处理文件本身(而不是其内容)和目录, 让开发者复制它们, remove them, 递归地迭代它们, and so on.
Parallel STL
c++ 17包含了大量使用的STL算法的并行替代方案,这是一个重要的特性,因为多核处理器甚至在桌面上也变得很常见.
背后的c++程序员 ranges-v3 library 提倡将其包含在c++ 20中. With Unix pipe-like syntax, composable constructs, lazy range combinators, and other features, 这个库的目的是给c++一个现代的, functional look.
std::vector numbers = …;
for (auto number : numbers
视图| std::::变换(abs)
| std::views::filter(is_prime)) {
…
}
//不带范围的选项
For (auto orig_number: numbers) {
自动编号= abs(orig_number);
if (!is_prime(number)) {
continue;
}
…
}
In the example above, the transform and filter 操作在运行时执行,不影响原容器的内容.
Initially, Bjarne Stroustrup, the creator of C++, 把它命名为“带类的C”,,其动机是支持面向对象编程(OOP). 对OOP概念和设计模式的一般理解是在语言之外的, 因此,在大多数情况下,检查c++程序员的OOP知识并不需要是特定于c++的. 然而,有一些小的特殊性. 一个很常见的面试问题是:
多态性(源自希腊语 poly, meaning “many,” and morph(意思是“形状”)就是这样 AskToPlay 根据对象的类型以不同的方式运行 musician 指向(它可以是a Pianist or a Guitarist). 问题是c++代码必须被编译成一种机器代码,每种代码都有一组固定的指令, 包括,尤其是 AskToPlay function. 这些指令必须选择在运行时跳转(调用)到哪里 Pianist::Play or Guitarist::Play.
编译器最常用和最有效的方法是使用 virtual tables (or vtables). 虚表是虚函数的地址数组. 每个类都有自己的虚函数表,该类的每个实例都有一个指向它的指针. 在我们的示例中,调用 Play 实际上变成了对一个函数的调用,该函数驻留在写在虚函数表的第一个条目中的地址上 *musician points to. 如果它被实例化为 Pianist,则其指向虚表的指针将被设置为 Pianist我们最终调用了正确的函数.
另一个特性来自于c++支持多重继承的事实,并且两者之间没有正式的区别 interface and a class. 经典的问题 diamond problem 这是一个好的开始吗.
容器——一些基本数据结构的实现,比如 vector, list, map, and unordered_map
算法——一些基本算法的实现,比如 sort, binary_search, transform, and partition
迭代器——用于处理算法的容器的抽象
函子——一种更加定制算法的方法
STL的设计和理念是独特而广泛的. 任何优秀的c++程序员都必须对STL有很强的了解,但问题是,了解到什么程度? 第一层是所谓的 user level knowledge. 这时应聘者至少知道最流行的容器和算法, how and when to use them. 接下来的三个问题是检验这一点的经典问题:
问:两者的区别是什么 list and vector? c++开发者应该如何选择呢?
Both list and vector 顺序容器,但基于不同的数据结构. list 是基于双链表的,而 vector contains a raw array.
优势被他们平分了. vector 连续存储数据的优点是什么, 没有内存开销和固定时间索引访问. In contrast, list 具有恒定时间插入和删除在任何位置,并支持功能,如 splice, merge, and in-place sort and reverse.
As a result, vector 是更受欢迎,大多数时候是正确的选择. list 在某些极端情况下会是更好的选择吗, 就像处理重拷贝对象一样, 这样开发人员就可以让它们保持有序, 或者通过操纵节点连接从一个容器移动到另一个容器.
问:如何从a中移除所有的_42_s vector? What about a list?
最简单的方法是遍历容器并使用 erase 成员函数,这两个容器都有. 这在a的情况下是完全有效的 list; in fact, its remove 成员函数做的差不多.
But there’s a reason why vector doesn’t have such a member function. 这种方法效率很低,因为 vector底层数据结构. The elements of vector 在内存中是连续的,所以删除一个元素意味着移动 all 后面的元素后退一个位置. 这将导致大量额外的复制.
First, we apply remove 在整个范围内,它不会移除 42它只是把其他的移到最开始,而且是最有效的方式. Instead of moving 6 向左移动两个位置(如果我们单独擦除就会发生这种情况 42s), it only moves 6 once, by two positions. With erase 然后,我们擦掉最后的“浪费”.
一个给定运算的渐近复杂度是多少.g., remove] on [a given container]?
vector 使用大小标记将元素存储在基础数组中. When it’s full, a new, 分配更大的存储空间, 元素是从旧存储中复制的, which then is released. Its iterators are of the random access category. ()的渐近复杂度push_back) is amortized constant time, indexed access is constant time, remove is linear.
list 实现一个双重链表,存储指向头节点和尾节点的指针. Its iterators are bidirectional. The complexity of insert (push_back and push_front) and remove (pop_back and pop_front) are constant time,而索引访问是 linear.
set and map 是否在平衡二叉搜索树上实现, 更具体地说,是红黑树, 这样就保持了元素的排序. Their iterators are bidirectional as well. 插入、查找(查找)和删除的复杂性是 logarithmic. 使用迭代器删除元素(erase member function) has amortized constant time complexity.
unordered_set and unordered_map 哈希表的实现是数据结构吗. 通常,它是一个所谓桶的数组,桶就是简单的链表. 为一个元素选择一个桶(在插入或查找期间)取决于它的哈希值和桶的总数. 当容器中的元素太多,平均桶大小大于实现定义的阈值(通常为1.0),则桶的数量增加,并将元素移动到正确的桶中. This process is called rehashing,这使得插入变得复杂 amortized constant time. Find and remove have constant time complexities.
这些是最基本的容器—必须了解上述细节, 但是否每一个细节都经过测试取决于面试官的直觉.
问:给定算法的渐近复杂度是多少??
一些著名的STL算法的渐近复杂性是:
Algorithm
Complexity
std::sort
O(N log(N))
std::nth_element
O(N)
std::advance
O(1) for random access iterators, and O(N) otherwise
std::binary_search
O(log(N)) for random access iterators, and O(N) otherwise
std::set_intersection
O(N)
或者,用一个简单的问题来解决所有问题:
问:哪些集装箱可以使用 insertion 操作使迭代器失效?
这个问题的巧妙之处在于它很难被记住, 正确的答案需要对容器的底层数据结构和一些逻辑有广泛的了解.
The insertion 操作可能使上的迭代器失效 vector, string, deque, and unordered_set/map (在扩展/重新散列的情况下). For list, set, and map但是,由于它们基于节点的数据结构,情况并非如此.
这些问题很有价值,因为它们可以一次检查很多事情. 大多数面试官会故意从一个不明确的问题陈述开始,以测试候选人解决问题的技巧和提出正确问题的能力. Then, 候选人必须解决问题, write the code, and, most importantly, do a complexity analysis.
在使用STL容器和算法时,擅长后者并拥有强大的数据结构知识足以做出明智的决策, 还有写新的. 有些人可能会更进一步,看看实际的实现. 每个人都会在某些时候使用STL代码,要么是偶然的,要么是在调试时,要么是自愿的. 如果候选人去了那里,并设法弄明白了那些胡言乱语, 这说明了他们的经历, curiosity, and persistence. For example:
问:哪种排序算法可以 std::sort implement?
The ISO C++ standard doesn’t specify the algorithm; it only sets the requirement of asymptotic complexities. 然后,选择取决于实现(例如.e., that of Microsoft vs. GNU.)
它们中的大多数不会只用一个算法来解决,比如 mergesort or quicksort. 一种常见的方法是将它们混合使用,或者根据大小选择一种. 例如,GCC实现了 introsort, which is a hybrid of quicksort and heapsort. 它从第一个开始,并在长度小于实现定义的阈值时切换到堆排序.