这几天被单纯的小美好洗脑,每天都活在粉红泡泡里面,hahaha
为了让自己心安,于是决定把之前准备的一些面试的东东分享出来,希望大家都能找到一个好的归宿......
一,多态的类中内存布局是怎样的?
(1)普通的类:成员变量按照声明的顺序进行排列,成员函数不占用存储空间
(2)继承:子类继承父类的成员变量,在内存排布上,先排布父类的成员变量,再排布子类的成员变量,成员函数不占字节
(3)给父类加一个virtual函数
父类中的内存布局:
虚表指针------->vptr
父类成员变量
(4)在子类中加一个virtual函数,子类就会继承父类的虚表指针
子类中的内存布局:
虚表指针-------->vptr
父类成员变量
子类成员变量
(5)子类中重新定义了一个虚方法
Base::fun()
Derived:fun1()
用父类的指针pd-->子类的对象pb pd->fun();父类的fun();
pd->fun1();子类中的fun1();
(6)子类中既覆盖了父类中的虚函数,也添加了新的虚函数
用父类的指针pd-->子类的对象pb pd->fun();子类的fun();
pd->fun1();子类中的fun1();
(7)多重继承
Base
Derived1:public Base
Derived2:public Base
Derived3 public Derived1, public Derived2
Base的内存布局:
虚表指针------->vptr
成员变量
Derived1和Derived2的内存布局
虚表指针------->vptr
父类成员变量
子类成员变量
Derived3:
虚表指针-->vptr 虚表指针-->vptr
排布父类 排布父类
排布子类 排布子类
(两份虚表)
(8)虚继承:减少对基类的重复继承
Base
Derived1 :virtual public Base
Derived 2: virtual public Base
Derived 3: public Derived1, public Derived2
Base的内存布局:
虚表指针----->vptr
成员变量
Derived1和Derived2的内存布局:
虚表指针---->vptr1=====>指向虚基类对应的虚表
Base
虚表指针---->vptr2======>指向Derived1的虚表
父类成员变量
子类成员变量
(虚继承有两份虚指针)
Derived3的内存布局
虚表指针----->vptr1======>指向虚基类对应的虚表
Base
虚表指针----->vptr2======>指向Derived1的虚表
虚表指针------>vptr3=====>指向Derived2的虚表
总结:
当基类中有虚函数时:
(1)每个类都有虚指针和虚表
(2)如果不是虚继承,子类将父类的虚指针继承下来,并指向自己的虚表
(3)如果是虚继承,子类就会有两份虚表,一份指向自己的虚表,另一份指向虚基表 ,多重继承时虚基表和虚指针只有一份
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
二.extern"C"的作用
extern 关键字表明函数和全局变量作用范围的关键字,告诉编译器,其声明的函数和变量可以在本模块或者其他模块使用
extern"C"修饰的变量和函数是按照C语言方式编译和连接的,请看例子:
我们知道C++支持函数重载,C不支持函数重载,C++编译后在符号库中的名字与C不同
void fun(int x, int y)
C:_fun
C++:_fun_int_int<包含函数名,参数的数量和类型>
extern"C"告诉编译器:找_fun 而不是_fun_int_int
用法:
在CPP文件中添加extern"C",在Cyu语言中仅将C++中定义的extern"C"函数声明为extern类型
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------三.unordered_set的底层数据结构
unordered_set和unordered_map是C++中出现的两种新的关联式容器,内部实现与set,map不同,set,map的底层是红黑树,而unordered_set的底层是哈希表
哈希表是根据关键码进行直接访问,通过相应的哈希函数处理关键字得到相应的关键码,关键码对应一个特定的位置,用来存储相应的信息,以较快的速度获得关键字的信息
unordered_set特点:
存在唯一键值(无重复),元素无秩序
如果空间不足,扩容时与vector类似,但每次扩容都要对表中的数据重新计算
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
四.隐式类型转换与显示类型转换
(1)隐式类型转换:编译器私下进行的类型转换行为
C++的內建类型:char, double, int 默认有隐式转换
(低精度-------》高精度)
看一个特别的例子:
class string
{
public:
string(const char *p);
};
string s1 = "hello";
//隐式转换 string s1 = string("hello");
如果要避免隐式转换需要加关键字explicit
(2)显式转换(强制类型转换:之前有一篇专门写这个)
static_cast
dynamic_cast
const_cast
reinterpret_cast
static_cast
- 编译器隐式执行的任何类型都可以是static_cast完成
- 类层次之间可以进行上行(把子类的指针或者引用转化为基类)/下行(把基类的指针或者引用转换为子类)转换
- 在进行下行转换时没有类型安全检查
dynamic_cast
- 涉及类型安全检查,运行时类型安全检查,需要运行时的类型信息,这个信息存储在类的虚函数中,所以用dynamic_cast的类中必须有虚函数
- 进行上行转换时效果和static_case效果相同
- 进行下行装换时会进行类型的安全检查
- 转换表达式的const性质:添加或者删除const性质
reinterpret_cast
- 相当于() int *p; char *ptr = (char *)p;
五.服务器和客户端的建立过程
主要是通过socket套接字,在应用层和传输层之间建立一个抽象层,建立TCP/IP复杂的通信,把通信转化为接口供应用层调用来实现网络通信
服务器 客户端
创建套接字socket 创建套接字socket
绑定套接字bind 连接指定的计算机端口connect
监听套接字listen 向socket中写入数据send()
接受来自客户端的请求accept 关闭socket close
从socket中读字符 recv
关闭套接字 close
相关过程:
服务器根据地址类型创建socket------------>服务器为socket绑定ip地址和端口号------------->服务器socket监听端口号请求,随时准备接受客户端发来的连接,此时服务器的socket还没有打开------------------>客户端创建socket----------------------->客户端打开socket,根据ip地址和端口号试图连接服务器socket--------------->服务器socket收到客户端socket的连接请求,被动打开,接收客户端请求,直到客户端返回连接信息-------------------------->客户端连接成功,向服务器发送连接状态信息----------->服务器accept方法返回,连接成功
----------------->客户端向socket中写数据------------------------>服务器从socket中读数据-------------->客户端关闭------------------->服务器关闭
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------六.TCP与UDP的区别
- TCP是面向连接的,UDP是无连接的
- TCP要求的资源多,UDP要求的资源少
- TCP是流模式,UDP是数据报模式
- TCP保证数据的正确性,UDP可能会出现乱序,丢包的可能
- TCP提供充分的数据传输过程中的各种传输机制,丢包重发,对次序乱掉的包进行顺序的控制,只有在确认对端存在时才会发送数据,可以避免对流量的浪费
- UDP不提供复杂的控制机制,只提供作为传输层协议最基本的功能,将部分的功能转移到应用程序中去处理
- 具体编程的区别:
- socket参数不同,TCP参数为SOCK_STREAM,表示socket用于流式通信,具有可靠性和有序性;UDP的参数为SOCK_DGRAM是无连接的,不可靠的,通信双方发送数据之后不知道对方是否已经接收到数据
- UDP的服务端不需要监听(listen),接收(recv)
- TCP发送接收数据用recv/send ,UDP用sendto/recvfrom
- UDP在sendto/recvfrom函数中每次都需要指定地址信息,TCP在accept/connect时地址信息已经确定
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
七.大小端
采用小端模式的CPU对操作数的存储方式是从低字节到高字节
采用大端模式的CPU对操作数的存储方式是从高字节到低字节
0x1234
小端:
0x01 0x34
0x02 0x12
大端:
0x01 0x12
0x02 0x34
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------八,UDP适用场景
- 面向数据报模式
- 拥有大量客户端
- 对网络的安全性没有要求
- 网络负担重,对于响应速度要求非常高
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
九.TCP和UDP的编程对比
TCP
服务器端 客户端
创建一个socket 创建一个socket
绑定bind 连接connect
监听listen send/recv
接收accept 关闭连接close
发送send/recv
关闭close
UDP
服务器端 客户端
创建socket 创建socket
绑定bind 发送sendto
循环接收recvfrom 关闭连接close
关闭网络连接
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
十,select,poll,epoll
(1)select
- 用途:在一段时间内监听用户感兴趣的文件描述符上的可读,可写,异常事件
- 五个参数:第一个参数指定被监听的最大文件描述符的个数,通常被设置为select监听的所有文件描述符的最大值+1,第二个,第三个,第四个参数分别指向可读,可写,异常事件的集合,应用程序调用select时,通过这三个参数传入自己感兴趣的文件描述符,select调用返回时,内核将通过修改他们来通知应用程序那些文件描述符已经准备就绪
- select成功调用,返回就绪的文件描述符的个数
- 每次调用select时,就必须对这三个参数进行重置
- poll与select相似,在指定时间内去轮询一定数量的文件描述符,以测试其中是否有就绪的文件描述符
- 封装成一个结构体,里面包含注册的时间和实际发生的时间
- 内核通过修改实际发生的事件来通知应用程序实际上发生了哪些事件
- 通过一组函数来完成epoll_create epoll_ctl epoll_wait
- epoll把用户关心的文件描述符的事件放在一个事件表中,不需要像select,poll每次都要重新传入文件描述符和事件集
- 需要一个额外的文件描述符来唯一标示内核中的这个事件表,由epoll_create创建,用epoll_ctl来操作这个事件表
- epoll_wait在一段超时时间内等待一组文件描述符上的事件,第二个参数用来传出epoll_wait检测到的就绪事件,如果检测到事件,就把所有就绪时间从内核事件表中复制到第二个参数中,不会像select,poll的参数既用于传入用户注册的事件,又用于输出内核检测到的就绪事件