实在不想说自己的面试过程有多惨烈了,现在自己一个人想想都是尬到抠出几室几厅的程度,基础实在是太差了,不得不承认自己就是一个API程序员……
但还是希望自己能够成长,内心强大一些,大不了再重新开始。
废话不多说了,以下是自己面试的实况转播,希望看到的人如果和我一样,不要再范同样低级的错误……
1. new、delete和malloc、free的区别
①类型不同
new、delete是c++的运算符,可以进行重载,而malloc和free是c的标准库函数。
②返回类型不同
new 直接带具体的类类型,malloc则是返回void*类型,需要进行类型转换。
③执行的操作不同
new一般分为两步:new操作和构造。new操作对应与malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。new 可以执行构造函数,delete会执行析构函数,而malloc、free不会。
该问题是个比较常见的问题了,本以为我会答的很好,但是让在现场手写,我的大脑就空白了。(这是面试的第一个问题也是全场最让我尴尬的时刻,虽然当时并未觉得,但这一秒的我回想一天前的自己,也只能感慨真是少妇英勇,无知无畏啊~~~~~~~~)
/* ----- 基础类型数组的创建----------- */
// ①new/delete
int *a = new int[10];
for (int i = 0; i < 10; ++i)
a[i] = i;
delete[] a;
a = nullptr;
// ②malloc和free
int *b = (int*)malloc(sizeof(int) * 10);
for (int i = 0; i < 10; ++i)
b[i] = i;
free(b);
b = nullptr;
/* ----- 自定义类型数组的创建----------- */
D* pArr = new D[10];
delete[] pArr;
pArr = nullptr;
D* pArr2 = (D*)malloc(sizeof(D) * 10);
free(pArr2);
pArr2 = nullptr;
2.二叉树的深度和层序遍历
void dfs(TreeNode* node)
{
if (node == nullptr)
return;
// 访问根节点的值
dfs(node->left);
dfs(node->right);
}
void bfs(TreeNode* node)
{
if (node == null)
return;
queue<TreeNode*> q;
q.push(node);
while(!q.empty())
{
int size = q.size();
for (int i = 0; i < size(); ++i)
{
TreeNode* temp = q.front();
q.pop();
// 处理值
if (temp->left)
q->push(temp->left);
if (temp->right)
q->push(temp->right);
}
}
}
3 递归需要注意什么
① 终止条件:必须要有终止条件,否则将无休止的递归下去,直至资源耗尽。
②栈的开销:每调用一次函数都有栈的开销,防止出现深度过深,内存消耗太大的情况出现。(这一点当时没有说,忘记想到内存这方面了。)
4 lambda表达式
lambda表达式可以理解为一个匿名的内联函数。
语法:[capture list] (parameter list) -> return type {function body}
capture list:表示捕获列表,是一个lambda所在函数中定义的局部变量列表
parameter list:表示参数列表
return type:返回类型
function body:函数体
可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体,忽略参数列表等价于指定一个空函数列表,忽略返回类型,lambda会根据函数体中的代码推断出来(如果函数体直接return,则是void类型)。例如:
auto f = [ ] {return 42;};
cout << f() << endl;
捕获列表
类型 | 说明 |
---|---|
[] | 空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有在捕获变量后才能使用它们 |
[ names ] | names 是使用逗号分隔的名字裂变,这些名字都是lambda所在函数的局部变量。默认情况下是值捕获,名字前加&指明是引用捕获。 |
[ = ] | 采用值捕获方式,lambda体将拷贝所使用的来自所在函数的实体的值。就是能够捕获所有父作用域中的变量(包括this) |
[ & ] | 采用引用方式捕获,lambda体中所使用的来自所在函数的实体的值。就是表示引用传递捕捉所有父作用域中的变量(包括this)。 |
// [] 空捕获列表
void test()
{
int num = 9;
auto f = [] { return num; }; // 提示语法错误,num不能被捕获
}
void test1()
{
int num = 9;
auto f = [num] { return num; };
}
5.怎样避免头文件重复包含
① 宏定义的方式
#ifndef _NAME_H
#define _NAME_H//头文件内容#endif
② 使用#pragma once避免重复引入
将其附加到指定文件的最开头位置,则该文件就只会被 #include 一次。
#ifndef 是通过定义独一无二的宏来避免重复引入的,这意味着每次引入头文件都要进行识别,所以效率不高。但考虑到 C 和 C++ 都支持宏定义,所以项目中使用 #ifndef 规避可能出现的“头文件重复引入”问题,不会影响项目的可移植性。
和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高。
③ 使用_Pragma操作符
当处理头文件重复引入问题时,可以将_Pragma("once")如下语句添加到相应文件的开头。
其可以看做是 #pragma 的增强版,不仅可以实现 #pragma 所有的功能,更重要的是,_Pragma 还能和宏搭配使用。
6 拷贝构造函数的参数为什么是引用
这道题第一反应是减少一次拷贝,但是对于拷贝构造函数,仅仅是减少拷贝这个答案是不对的。(可惜当时我知道是不对,但是却没有回答出其他的答案,唉!!!!)
原因如下:如果拷贝构造函数中的参数如果不是一个引用,那么就相当于采用了传值的方式,而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数必须是一个引用。