面试要点和基础题
一些大神的总结:
https://github.com/linw7/Skill-Tree
https://blog.nowcoder.net/zhuanlan/3m2ONj
https://www.zhihu.com/column/c_1349860875424116736
1.重载、覆盖与隐藏
函数重载、覆盖(也有叫重写的)与隐藏
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
2.多态性
多态性:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
多态性:程序能通过引用或指针的动态类型获取类型特定行为的能力
3.消息机制
Qt信号与槽:
信号-槽是Qt自定义的一种通信机制(connect(object, signalname, object, slotname))。信号-槽的使用方法,是在普通的函数声明之前,加上signal、slot标记(signals: rettype signalname();,信号函数只要声明、不需要自己写实现。public slots/private slots/protected slots: rettype slotname() {};,槽函数一般要定义函数体来实现动作),然后通过connect函数把信号与槽连接起来。后续只要调用信号函数(通过emit signalname();或者直接调用信号函数 signalname();可以这样是因为emit是一个空的宏),就可以触发连接好的信号或槽函数。连接的时候,前面的是发送者,后面的是接收者。信号与信号也可以连接,这种情况把接收者信号看做槽即可。
信号-槽的实现,借助一个工具:元对象编译器MOC(Meta Object Compiler)。该工具是一个C++预处理程序,它为高层次的事件处理自动生成所需要的附加代码。这个工具被集成在了Qt的编译工具链qmake中,在开始编译Qt工程时,会先去执行MOC,从代码中解析signals、slots、emit等等这些标准C/C++不存在的关键字,以及处理Q_OBJECT、Q_PROPERTY、Q_INVOKABLE等相关的宏,生成一个moc_xxx.cpp的C++文件。比如信号函数只要声明、不需要自己写实现,就是在这个moc_xxx.cpp文件中,自动生成的。MOC之后就是常规的C/C++编译、链接流程了。
MOC的本质,其实是一个反射器,即Qt用MOC实现了反射功能。反射的定义:反射是一种计算机处理方式,有程序可以访问、检测和修改它本身状态或行为的这种能力,能提供封装程序集、类型的对象(程序集包含模块,而模块包含类型,类型又包含成员)。简单来说,反射就是运行过程中,获取对象的构造函数、成员函数、成员变量。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。设计模式中简单工厂模式就体现了反射的思想,代码如下,这里我们通过字符串“banana”和“apple”获取到了对应的banana和apple类的对象,即我们在对类的信息(构造函数)一无所知时也能创建对象。
参考:https://zhuanlan.zhihu.com/p/80539605
linux进程间通信、对象间的通信、观察者模式
4.TCP/UDP
UDP(用户数据报协议)只是在IP的数据报服务之上增加了很少一点的功能,主要时复用、分用和差错检测的功能,UDP主要特点有:1.UDP是无连接的,即发送数据之前不需要建立连接,因此减少了开销和发送数据之前的延时。2.UDP使用尽最大努力的交付,即不保证可靠交付,接收端收到报文后也不需要发回确认,因此主机不需要维持复杂的连接状态表。3.UDP是面向报文的,UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界,UDP一次交付一个完整的报文。4.UDP没有拥塞控制算法,因此网络出现的拥塞不会使源主机的发送速率降低。5.UDP支持一对一、一对多、多对一、多对多的交互通信,TCP只支持端到端通信。6.UDP的首部开销小,只有8个字节,比TCP的20个字节的首部要短。
TCP(传输控制协议)的特点:1.TCP是面向连接的传输层协议,这就是说,应用程序在使用TCP协议前,必须先建立TCP连接(传输完成后也必须释放)。2.TCP连接是点对点的,一条TCP连接只能由两个端点。3.TCP提供可靠交付的服务,TCP连接无差错、不丢失、不重复、按序到达。4.TCP提供全双工通信,为了支持全双工TCP连接的两端都设有发送缓存和接受缓存,用来临时存放双向通信的数据。5.面向字节流,即TCP把引用程序交下来的数据仅仅看成是一连串的无结构的字节流。
停止等待协议能够在不可靠的传输网络上实现可靠的通信,具体特征有:停止等待、超时重传、自动重传请求ARQ。连续ARQ协议和滑动窗口协议是TCP的基础
5.HTTP/HTTPS
https://mp.weixin.qq.com/s/Zr_tIlhAjH7v8I-L5FC31g
6.怎么解决tcp粘包问题
https://www.cnblogs.com/bigox/p/10833462.html
https://blog.csdn.net/weixin_43181521/article/details/108423973
为什么是2MSL而不是MSL?
为什么等待2MSL,从TIME_WAIT到CLOSE?
在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
——————————————————————————————————————————————————
基础题:
1.双栈实现队列
push操作就直接往stack1中push, pop操作需要分类一下:如果stack2为空,那么需要将stack1中的数据转移到stack2中,然后在对stack2进行pop,如果stack2不为空,直接pop就ok。
2.两个有序数组,快速找出相同部分
private static void Search(int[] m, int[] n) { int minLength = Math.min(m.length, n.length); int i = 0, j = 0; while (j < minLength || i < minLength) { if (m[i] == n[j]) { System.out.println(m[i]); i++; j++; } else if (m[i] < n[j]) { i++; } else { j++; } } }
3.有一个字符串,里面含有多个空格字符,删除空字符并统计空字符数量
双指针i = 0, j = 0;通过j遍历字符串,遇到空格记录并跳过,直到遇到字符将字符赋值给i指向的字符,指针i加一,继续遍历j指针
4.电脑有仅1G内存,如何在海量数据中快速找到100个目标数据?(海量数据可能有序,也可能无序)
【分块处理->块内排序】
5.单例模式与工厂模式
//单例模式是一种对象创建模式,可以保证为一个类只生成唯一的实例对象 //也就是说,在整个程序空间中,该类只存在一个实例对象 //懒汉式单例模式,每次都会要判断,多线程时存在互斥问题 class Singelton { private: Singelton() { cout << "Singelton构造函数执行" << endl; } public: static Singelton* GetInstance() { if (m_psl == NULL) { m_psl = new Singelton; } return m_psl; } static void FreeInstance() { if (m_psl != NULL) { delete m_psl; m_psl = NULL; } return; } private: static Singelton* m_psl; } Singelton* Singelton::m_psl = NULL; int main(void) { Singelton *p1 = Singelton::GetInstance(); Singelton *p2 = Singelton::GetInstance(); if (p1 == p2) { cout << "p1和p2指向同一个对象" << endl; } else { cout << "p1和p2指向不同对象" << endl; } Singelton::FreeInstance(); return 0; } //饿汉式单例模式,不管用不用单例都会创造单例 class Singelton { private: Singelton() { cout << "Singelton构造函数执行" << endl; } public: static Singelton* GetInstance() { return m_psl; } static void FreeInstance() { if (m_psl != NULL) { delete m_psl; m_psl = NULL; } return; } private: static Singelton* m_psl; } Singelton* Singelton::m_psl = new Singelton; //单例构造了 int main(void) { printf("我不是先执行的\n"); //单例先构造 Singelton *p1 = Singelton::GetInstance(); Singelton *p2 = Singelton::GetInstance(); if (p1 == p2) { cout << "p1和p2指向同一个对象" << endl; } else { cout << "p1和p2指向不同对象" << endl; } Singelton::FreeInstance(); return 0; }
//简单工厂模式属于类的创建型模式,又叫做静态工厂方法模式。 //通过专门定义一个工厂来负责创建其他的实例,被创建的实例通常都具有共同的父类 #include <iostream> using namespace std; class Fruit { public: virtual void GetFruit()=0; }; class Banana : public Fruit { public: virtual void GetFruit() { cout << "我是香蕉" << endl; } protected: private: } class Apple : public Fruit { public: virtual void GetFruit() { cout << "我是苹果" << endl; } protected: private: }; class Factory { public: Fruit* Create(char *p) { if (strcmp(p, "banana")) { return new Banana; } if (strcmp(p, "apple")) { return new Banana; } else { cout << "不支持创建" << endl; return NULL; } } protected: private: }; int main() { Factory* f = new Factory; Fruit* fruit = NULL; //工厂生产香蕉 fruit = f->Create("banane"); fruit->GetFruit(); delete fruit; //工厂生产苹果 fruit = f->Create("apple"); fruit->GetFruit(); delete fruit; return 0; }