10月13号在去面试旷视的路上,陆续收到南京841所、好未来和中信信用卡的录用通知,再加上之前美团的offer,算是结束秋招了,后面还尝试了一些独角兽公司(无果)。总的来说求职过程还算比较顺利,对于自动化半路出家IT的我来说,已经满意了(应该也找不到更好的了)。虽然在十一之前我还没收到一个正式秋招offer(除了海康实习转正),所以要相信只要准备好了,offer总会来的(后面还收到中兴、华为和宜信大数据的offer)。
关于内推
以我在海康实习期间内推的经历来看(其它公司不知道),内推基本就是公司宣传的一个手段,当然也能给员工带来一些福利。很多同学简历看起来很不错,但是我内推了四五十个,通过简历筛选进入面试的只有三个人。。。。。很多公司内推并不免笔试直接进入面试,虽然多了一次机会,但是还是会花很多时间的(找内推、发邮件、笔试、面试),尤其是对本来准备就不是很充分、项目和实习经验不是很优秀的同学来说。所以如果觉得自己准备的不是很充分,我建议放弃各种内推和提前批,留出时间来巩固知识点和刷题。
为找工作做准备
从研二开始,我根据自己的知识基础,把目标职位放在后台开发、软件开发。主要的知识点包括:C++、数据结构和算法、Linux、数据库、网络。
书籍:
- C陷阱与缺陷、C和指针、C专家编程
- C++ Primer、Effective C++、STL源码剖析
- 数据结构与算法分析、剑指offer
- 鸟哥的linux私房菜、Liunx程序设计、Unix环境高级编程、操作系统原理
- TCP/IP协议卷一、Unix网络编程
- Mysql、redis、SQL
关于笔试:刷题还是很有必要的
编程题
推荐先把剑指offer上面的题目做一到两遍,看看别人的解法,尝试自己用最简单、好记的方法实现,并做好归类总结。然后可以到leetcode上面刷一些题目。牛客网还有公司真题和其他的编程专题,可以根据自己的需要做一做。
不同公司的笔试难度差别很大,不过基本的算法还是要熟悉的,至少要有自己实现的代码,笔试的时候知道怎么修改变形。
比较常见的考点有:背包问题、最短路径、简单的动态规划问题、回溯法、全排列、深度优先、广度优先、链表和二叉树的操作、队列和栈的操作。还有一些技巧性比较强的算法题,如果没有提前做过,一般还是很难想到的。
选择、填空题
推荐在牛客网上做,根据编程语言,刷C++或者Java专项练习。可以在网页上,也可以在手机app上刷(很方便)。刷到每次能做对大部分题目为止吧。
笔试平台
建议在牛客网和赛码上刷一些编程题,熟悉一下输入输出格式。
关于面试
面试时被问到的知识点,如果熟悉的话,尽量多扩展一些讲,语速放慢一点。被问到不懂的知识点也不要慌,表现出自己的求知欲,虚心向面试官请教这个问题。回答的不清楚的知识点,面试完之后尽快做巩固和总结。
如果面试官聊日常,可以说说自己好的生活态度、学习态度,增加一些印象分。
如果面试官最后问你:你有什么想问我的吗?还是尽量问一些问题,表现出你对这个公司、这个岗位很感兴趣,表现出你的求知欲和学习的主动性。
九章算法提供了一些BAT常见的面试题,可以多学习:https://www.nowcoder.com/ta/nine-chapter
关于投递岗位
后台开发、软件开发的语言主要是JAVA、C++,总的来说互联网公司JAVA的需求量更大一些,像华为、商汤、大疆等公司C++需求量也挺多的。
很多公司不会在意你熟悉的语言是否和公司所使用的语言相符合(美团和中信信用卡给了我JAVA岗offer,三轮技术面没问过编程语言)。所以投递简历的时候,不用很在意岗位的要求,尤其是编程语言!尤其是大公司!!
下面附上我总结的知识点,内容可能有一些错误,欢迎批评指正;可能会有很多遗漏,还要靠自己多学习和总结。希望能对以后找工作的同学提供一些帮助,祝所有同学都能找到合适、满意的工作!!
笔试、面试常见知识点
C++内存分配机制
内存主要分为如下5个存储区:
-
静态存储区 (Global Static Area): 在编译时分配,保存全局变量和静态变量。
- 栈 (Stack):保存函数内的局部变量(包括函数实参),函数结束时由编译器负责分配释放。向低地址扩展。
- 堆(Heap):由new或malloc申请的内存,由程序员负责释放。向高地址扩展。
- 代码段:存放程序执行代码。
- 未初始化数据段:存放程序中未初始化的全局变量。
new、delete、malloc、free
都可用于申请动态内存和释放内存,区别:
- new和delete对应,malloc和free对应;
- new和delete是运算符,malloc和free 是库函数;
- 对于非内部数据类型,new 不止是分配内存,而且会调用类的构造函数,delete会调用类的析构函数;而malloc则只分配内存,不会进行初始化类成员的工作,free也不会调用析构函数。
申请内存空间后一定要释放,且置指针为NULL。
delete与 delete []
- delete调用一次析构函数 ,释放一个对象内存, delete []会调用多次析构函数,释放整个数组的内存。
- delete和new对应,delete []和new []对应
- malloc 失败时,返回的指针为NULL;new 失败时,抛出异常 bad_alloc。
虚函数与纯虚函数
虚函数:在基类中用virtual 关键字修饰,并在派生类中重新定义的成员函数。作用是借助指针或者引用来实现多态。
静态成员函数(没有this指针)、内联函数(编译器将函数替换到函数调用处,静态编译)、构造函数不能是虚函数;
在派生类中重新定义虚函数的参数个数和参数的类型与基类中相同;
纯虚函数:在类的成员函数声明的后面加 =0 。
含有纯虚函数的类叫抽象类,作用是定义接口。其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,则这个派生类仍然还是一个抽象类。不能创建一个抽象基类的对象。
构造函数和析构函数中调用虚函数:虚函数此时变成普通的函数,没有多态性质。因为派生类初始化时先调用基类构造函数,再初始化派生部分。因此在调用基类构造函数时,派生类对象还没有生成。
构造函数不能定义为虚函数:构造一个对象的时候,必须知道对象的实际类型,而虚函数是在运行时确定实际类型的,因此编译器无法知道对象的实际类型;虚函数的调用是通过虚函数指针指向的虚函数表来实现调用的,但是调用构造函数时,虚函数指针还没有被初始化,不能调用虚函数。
基类应该定义一个虚析构函数:基类的指针或引用可以绑定到动态分配的派生类对象,在释放对象时,需要调用派生类的析构函数,否则造成内存泄漏。
封装、继承、多态
封装:将客观事物抽象成类,每个类对自身的数据和方法实行保护。封装之后,类的接口和实现分离,隐藏了类的实现细节。
继承:继承是指这样一种能力:使用现有类的所有功能的同时,实现类功能的扩展。通过继承联系在一起的类构成一种层次关系,层次关系的根部叫做基类或父类,通过从基类直接或间接继承而来的类叫做派生类或子类。
多态:通俗地讲同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态分为两种,一种是编译时的多态,一种是运行时的多态。
- 动态多态:当使用基类的指针或引用调用一个虚函数,根据所绑定的对象类型的不同调用不同的方法。如果绑定到基类对象,则执行基类的虚函数,如果绑定的是派生类对象,则执行派生类的虚函数。
- 静态多态:一般是通过函数和运算符重载实现的,在编译时就确定。
函数重载、覆盖与隐藏
重载:相同作用域中声明多个名称相同,但参数个数或参数类型不同的函数。
覆盖:指子类重新定义基类的虚函数,形参类型必须和基类函数完全一致。返回类型一般也必须匹配,一个例外是:当返回类型是类本身的指针或引用。
隐藏:是指子类重新定义基类的非虚函数,派生类的名字将隐藏基类的名字。
不能重载的运算符:.*、:: 、sizeof 、? :
拷贝构造函数
三种调用拷贝构造函数的情况:
1. 用一个对象去初始化同一个类的另一个新对象;
2. 函数调用时,形参和实参的结合
3. 函数返回值为对象时
浅拷贝:只对对象中的数据成员进行简单的赋值,如果存在动态成员(指针等),两个对象的动态成员指向了同一个地址。
深拷贝:如果存在动态成员(指针等),则拷贝动态成员所指向的内容。
拷贝构造函数和赋值操作符:
- 拷贝构造函数是构造函数,而赋值操作符属于操作符重载;
- 拷贝构造函数没有返回值,而赋值操作符一般返回类的引用;
- 拷贝构造函数是用一个对象初始化一个新对象,而赋值操作符是作用于两个已经初始化的对象(因为是两个已经存在的对象,所以要检查是否是自身赋值);
指针和引用
- 指针是一个变量,占用存储单元;引用不是一种数据类型,不占存储单元。
- 引用不能为空,需要初始化;指针可以为空。
- 引用初始化后,不能再把该引用名作为其他变量名的别名;而指针初始化之后可以改变。
- sizeof 作用于指针,得到的是指针本身的大小,而作用于引用时,得到的是所指向的变量大小。
引用传递、值传递和指针传递
- 引用传递和指针传递的效果是一样的,都可以改变实参。
- 引用传递没有实参拷贝,它是直接对实参变量操作;指针传递需要分配存储空间,进行实参拷贝;通过地址间接访问变量。
- 引用传递会做类型检查,而指针传递不会。
- 值传递需要分配存储空间,进行实参拷贝;改变形参的值不会影响外部实参的值。
struct 和 class
- 默认访问权限和默认继承的权限不同,struct 的默认访问权限和默认继承权限为public, class 默认访问权限和默认继承权限为private。
- struct不能用来申明模板。
const 用法
int t = 1;
const int a=1; //修饰常量,需要初始化
const int *pa = &t; //*pa为常量,即pa指向的内容不能变
int const *pb = &t; //同上
int *const pc = &t; //pc为常量,即pc不能指向其它地址
const int * const pd = &t; //pd为常量,*pd也为常量
int fun(const int &a); //顶层const
int fun2(const int a) const; //常成员函数,不能调用非const成员
define宏定义和 const 常量
- define宏是在预处理阶段展开;const常量是编译运行阶段使用。
- define宏没有类型,只做简单的字符替换,不做任何类型检查;const常量有数据类型,在编译阶段会执行类型检查。
- const变量只有一个拷贝,而#define 宏变量有多个拷贝(每替换一次有一个拷贝)。
- const 还可以用来修饰变量、指针、函数和函数返回值。
define宏定义和内联函数
- inline函数是否展开由编译器决定;内联函数在运行时可调试,而宏定义不可以;
- 编译器会对内联函数的参数类型做安全检查,而宏定义不会;
- 内联函数可以访问类的成员变量,宏定义则不能。
不适合使用内联:函数体内的代码比较长,内存消耗大;函数体内出现循环,时间开销大。
sizeof
返回一个对象或者类型所占的内存字节数。
结构体大小
结构体的大小不是所有成员大小简单的相加,需要考虑内存对齐问题。一般遵循以下规则:
1) 第一个成员的首地址也即是结构体变量的首地址;
2) 每个成员的首地址相对于结构体的首地址的偏移量是该成员数据类型大小的整数倍;
3) 结构体的总大小必须是最大数据类型大小的整数倍。
如果用 #pragma pack(n) 指定了编译器的对齐系数n ,第2和3条要将对齐系数考虑进去。
char a[] 或 char a[0] 可以放在结构体最后,不占内存空间。
sizeof 和 strlen
- sizeof 是操作符,strlen 是库函数;
- sizeof 返回unsigned int类型,strlen 返回 int 类型;
- sizeof可以用来计算类型、函数、变量的字节数,strlen 只能计算字符数组的长度;
- sizeof 编译时计算,strlen要在运行时才计算。
类大小
只计算:非静态成员变量和虚函数指针(与虚函数个数无关)。
空类 A 的大小 sizeof(A)= 1。
空类可以被实例化。为了区分不同的实例,编译器在类中加的一个字节,这样每个实例就有唯一的地址。
实现strcpy 函数
char * strcpy( char *dst, const char *src ) {
assert( (dst != NULL) &&(src != NULL) );
char *ret = dst;
while( (*dst++ = * src++) != ‘\0’ );
return ret;
}
实现memcpy函数
void *memcpy(void *dst, const void *src, size_t count) {
assert((dst!=NULL)&&(src!=NULL));
char *pdst=(char *)(dst);
const char *psrc=(const char *)(src);
//目的地址和源地址重叠,从源地址的末尾方向开始拷贝
if( pdst>psrc && pdst<psrc + count ) {
pdst = pdst + count-1;
psrc = psrc + count-1;
while(count--) {
*pdst-- = *psrc--;
}
}
//目的地址和源地址不重叠,从源地址的开始方向拷贝
else {
while(count--) {
*pdst++ = *psrc++;
}
}
return dst;
}
实现 String 类
class String {
public:
String(const char *str = NULL); //构造函数
String(const String &other); //拷贝构造函数
~ String(); //析构函数
String & operater =(const String &rhs); // 赋值操作符重载
private:
char *m_data; // 用于保存字符串
};
String::String(const char *str) {
if ( str == NULL ) //当初始化字符串为空,为m_data申请空间存放 ’\0’
{
m_data = new char[1] ;
m_data[0] = '\0' ;
}
else { //为m_data 申请同样大小的空间
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 &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 ;
}
进程和线程
- 进程是系统资源分配的单位,是程序的一次执行;线程是CPU调度的基本单位。
- 进程是独立的,有自己独立的地址空间;线程是属于进程的,同一进程中的线程共享进程的地址空间。所以如果一个线程奔溃,会影响同进程中的其他线程。
- 线程因为共享同一进程的资源,执行时一般都要进行同步。
线程共享的资源:
1. 堆
2. 全局变量
3. 静态变量
4. 文件描述符
不共享的资源:
1. 栈
2. 寄存器
死锁
多个进程因争夺资源而产生相互等待的现象。
原因:
1)资源有限;
2)进程推进的顺序不合适。
四个必要条件:
1)互斥条件:一个资源每次只能被一个进程使用。
2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
进程同步方式
管道、命名管道、信号量、消息队列、共享内存、套接字。
线程同步方式
互斥锁、信号量。
常用并发网络模型
1.多进程/线程模型
为每个连接创建一个进程/线程。但是连接多了之后,资源消耗增加。同时多进程和多线程的创建和销毁、切换都需要很多开销,控制比较复杂。
2.select 模型
先构造一张文件描述符的列表,把需要监听的文件描述符置1,然后调用select函数,等待直到这些描述符中的一个准备好时才返回。缺点:
- 最大并发数限制。因为一个进程所打开的 FD(文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024,因此 select 模型的最大并发数就被相应限制了。
- 效率问题。 select 采用轮询处理,每次调用都会扫描0到maxfd之间的所有文件描述符。
- 内存拷贝问题。每次 select 调用,都会把fd集合从用户态拷贝到内核态。所以效率也会比较低。
3.epoll 模型
epoll 基于事件的回调机制。首先对文件描述符的事件进行注册,注册的时候就会把文件描述符拷贝到内核中。一旦某个事件准备好了,就会执行回调函数,把准备好的事件加到一个双向链表中。当epoll_wait调用时,只需要观察这个双向链表里有没有数据即可。工作模式:
- 水平触发(LT):如果不做任何操作,内核依旧会不断的通知进程文件描述符准备就绪。
- 边缘触发(ET): 只有当状态发生变化的时候,内核才会通知进程文件描述符准备就绪。之后如果不在发生文件描述符状态变化,内核就不会再通知进程文件描述符已准备就绪。
优点:
- Epoll 没有最大并发连接的限制。上限是最大可以打开文件的数目,这个数字一般远大于 1024。
- 效率提升。 Epoll 最大的优点就在于它只需要遍历就绪链表。(如果活跃的连接一直很多,效率可能会降低)
- 内存拷贝。Epoll 注册的时候就把所有的fd拷贝进内核,保证了每个fd在整个过程中只会拷贝一次。
gdb调试
gcc -g -o test main.c -g 表示调试模式
gdb test 进入调试
list 显示源码
break 设置断点
run 执行程序
step 单步(进入子函数)
next 单步(不进入子函数)
cont 跳到下一个断点或程序结尾
print 显示变量或表达式值
finish 运行到函数结束
quit 退出gdb
多进程/线程调试
默认情况下,在调试多进程程序时GDB只会调试主进程。需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
简单makefile
show: main.o list.o
gcc -o list main.o list.o
main.o: main.c list.h
gcc -c main.c
list.o: list.c list.h
gcc -c list.c
编译的四个阶段
- 预处理阶段:预处理器根据以字符#开头的的命令,读取指定系统头文件的内容插入到程序中,得到一个扩展文件hello.i。
- 编译阶段:编译器将扩展文件hello.i翻译成文本文件hello.s,包含一个汇编语言程序。汇编程序可以为不同高级语言提供通用的的输出语言。
- 汇编阶段:汇编器将文本文文件hello.s翻译成机器语言指令,并打包到目标文件中hello.o。
- 链接阶段:在程序中调用了标准C库中的函数,链接器负责把这些函数的目标文件合并到我们的程序中。最终得到可执行文件。
常用shell命令
sort
参数:
-n 按数值大小排序
-r 反向排序
-k 按第几列排序(默认空格)
-t 指定分隔符
常见用法:
sort log.txt #默认以字符串排序
sort –nk 2 log.txt #按第二列数值排序
sort –t ‘;’ –nrk 2 log.txt #以‘;’为分隔符,按第二列数值反向排序
uniq
参数:
-c 进行计数(在行首显示重复次数)
cat 按行顺序读取
tac 按行逆序读取
rev 每行按字节逆序输出
awk
awk '{print $1,$3}' log.txt #输出log.txt文件的1, 3项, 默认空格(一个或多个)分隔
awk -F ',' '{print $1}' log.txt #参数 –F, 指定分割符, 以逗号为分隔符
awk -F '[;,]' '{print}' log.txt #多个分隔符, 输出每行所有项目
awk '$1>2' log.txt #输出第一项大于2的行
awk '$1=2 && $2=="is" ' log.txt #第1项等于2, 且第2项等于is的行
awk 'length>100' log.txt #输出长度大于100的行
awk ‘root’ a.txt #匹配包含root的行
awk ‘$2 ~/root/ {print $1}’ a.txt #第2项包含root的行,输出第1项, ~表示匹配开始
sed
参数说明:
a 新增, 出现在下一行
c 取代, 取代两行之间的行
d 删除
i 插入, 出现在上一行
p 选择要输出的行, 与sed –n 同时用
s 替换单词
用法:
sed 4a\newLine log.txt #在文件第4行后面新增一行
sed 4i\newline log.txt #在第4行前新增一行
sed 1,3d log.txt #删除1-3行
sed 1,$d log.txt #删除1到最后一行
sed 1,2c line12 log.txt #用line12 取代第1,2行
sed /root/d log.txt #搜寻包含root的行, 并删除
sed s/root/newroot/g log.txt #root替换为newroot
sed -n 1,2p log.txt #输出1-2行
sed /start/,/.*end/d #删除以start开头到以end结尾之间的所有行(包括start和end所在行)
cut
显示每行从开头算起 num1 到 num2 的文字
cut -b 1-2 log.txt #输出前两个字节
cut -c 1-2 log.txt #输出前两个字符
cut –d ‘:’ –f 2 log.txt #以 ‘:’ 为分隔符,输出第2项(默认以单个空格分隔)
grep
参数:-i 忽略大小写
-n 列出行号
-r 递归查询, 指定目录及其子目录
-v 反转查询, 输出不符合条件的行
-o 只输出符合条件的字符
用法:
grep ‘word’ text.txt #搜寻特定字符串
grep ‘^[a-z1-9]’ text.txt #利用 [ ] 来搜寻集合字符
grep ‘^word’ text.txt #行首字符 ^ 与行尾字符 $
grep ‘w..d’ text.txt #任意一个字符 . 与重复字符 *
tr
用于转换或删除文件中的字符, 用法: tr [参数] [SET1][SET2]
参数:
-c 反选设定字符, 符合SET1的不作处理
-d 删除设定字符
-s 删除连续重复的字符成单个
字符集范围:
CHAR1-CHAR2 两个字符之间的所有字符
[:alnum:] 所有字母与数字
[:alpha:] 所有字母
[:cntrl:] 所有控制字符
[:digit:] 所有数字
[:punct:] 所有标点
[=CHAR=] 指定字符
用法:
cat log.txt | tr a-z A-Z #将文件中所有小写字母转换成大写
cat log.txt | tr -d A-Z #删除所有的大写字母
cat log.txt | tr –dc A-G #删除非A-G之间的所有字符
cat log.txt | tr -d [=';'=] #删除所有分号”;”
cat a.txt | tr –s '\t' ' ' #删除连续多个空格剩一个
sed s/' *'/' '/g a.txt #删除连续多个空格剩一个
find
常见用法:
find /home –name “*.txt” 指定目录下以.txt结尾的文件
find /home -name "*.txt" -o -name "*.pdf" 以.txt和.pdf结尾的文件
find /home ! –name “*.txt” 指定目录下不是以.txt结尾的文件
//find /home -type f 普通文件,d 目录,c 字符设备,b 块设备等
find /home –type f -atime -7 七天内被访问过的文件,atime访问时,mtime 修改时间
find /home –type f -atime 7 七天前(当天)被访问过的文件
find /home –type f -atime +7 超过七天被访问过的文件
find /home –type f -amin +10 10分钟之前被访问过的文件
find /home –type f -newer file.log 比 file.log 修改时间更早的文件
find /home –type f -mtime +7 –delete 删除7天前修改的文件
find /home –type f –size +10k 大于10kb 的文件; 10kb 等于;-10kb 小于
TCP/IP协议
TCP/IP协议结构:
TCP连接接口调用:
内容比较多,可以参考我的博客:
TCP/IP协议基本概念:http://blog.csdn.net/xgf415/article/details/75199519
IP网络层:http://blog.csdn.net/xgf415/article/details/75670095
UDP用户数据报协议:http://blog.csdn.net/xgf415/article/details/75670139
TCP连接建立和终止:http://blog.csdn.net/xgf415/article/details/75670194
TCP成块数据流:http://blog.csdn.net/xgf415/article/details/75670211
TCP超时与重传:http://blog.csdn.net/xgf415/article/details/75670230
算法题
内容比较多,总结了另外一篇博客:http://blog.csdn.net/xgf415/article/details/75200106