C++多态(polymorphism)是通过虚函数来实现的,虚函数允许子类重新定义成员函数。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,实现一个接口,多种方法,称为多态
重载发生在编译时
数组
是一块连续的内存区域,在使用的时候需要预先申请空间(数组的空间在编译器就需要进行确定,所以需要提前给出数组的大小,在运行期间不允许改变)
数组插入的时候,待插入位置的的元素和它后面的所有元素都需要向后搬移;
删除数据时,待删除位置后面的所有元素都需要向前搬移;随机访问效率很高,时间复杂度O(1);
数组不够的时候需要进行扩容,扩容的话涉及到需要把旧数组的所有元素向新数组中copy,数组的空间是从栈分配的
链表
可以在内存的任何地方,空间是分散的,不需要连续,使用的时候不需要预先分配空间,使用的时候动态申请
链表的元素会有两个属性,一个是元素的值,一个是元素的索引,一个是元素的值,此索引指向下一个元素的地址
查找数据时效率低,不具有随机访问特性,需要从第一个元素查起
vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
因此能高效的进行随机存取,时间复杂度为o(1);
但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。
list是由双向链表实现的,因此内存空间是不连续的。
只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);
但由于链表的特点,能高效地进行插入和删除。
我们都知道数组的查询比较快,因为地址是连续的,可以通过下标直接找到元素。不过增删较慢,每次在数组中间增删一个元素,都涉及数组拷贝。
HashMap设计高明之处,是用hashCode代替数组下标访问,不再要求元素存储必须连续,增删的时候也就不需要数组拷贝。
使用链表解决hash冲突,使用阈值默认75%控制容量,避免元素过多、hash冲突过多,退化成链表存储,影响查询性能。HashMap通常情况下,增删改查的效率都是O(1)
B+树是一B树的一个升级版
b+树相比于b树的查询优势:
重传?
selectpoll,epoll
epoll分为三个部分:
1:调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2:调用epoll_ctl向epoll对象中添加这100万个连接的套接字
3:调用epoll_wait收集发生的事件的连接
epoll向内核注册了一个文件系统,用于存储上述的被监控socket,这些socket会以红黑树的形式存在,同时会建立一个list链表,用于存储准备就绪的事件.
执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
检测到描述符事件发生并将此事件通知应用程序;
当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
执行epoll_wait时立刻返回准备就绪链表里的数据即可。
C++多态(polymorphism)是通过虚函数来实现的,虚函数允许子类重新定义成员函数。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,实现一个接口,多种方法,称为多态
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
关键字以及新语法:
1.auto关键字
2.for循环
3.nullptr
STL容器:
1.array (和数组没有太大区别)
2.forward_list新增的线性表,与list的区别他是单向链表。
3.unorderd_map:内部是hash表的实现方式
4.unordered_set也是哈希表但是不会自动排序
多线程:
1.thread
2.atmoic原子变量
3.condition_variable
condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到别唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。
4.智能指针
智能指针只是用对象去管理一个资源指针,同时用一个计数器计算当前指针引用对象的个数,当管理指针的对象增加或者减少时,计数器也相应的+1或者-1;
shared_ptr:可以多个指针引用一个对象,采用引用计数,每有一个指针引用一个对象,引用一次,计数加1,每当智能指针销毁了,引用计数减1,引用计数减少到0,就释放引用的对象,这种引用计数的增减发生在智能指针的构造函数,复制构造函数,赋值操作符,析构函数中。
weak_ptr:只是一个观察者,配合shared_ptr共同工作,weak_ptr构造不会引起引用计数的增加,析构不会引起引用计数的减少;weak_ptr是和shared_ptr配合使用的,在实现shared_ptr的时候也就考虑了weak_ptr的因素。weak_ptr是shared_ptr的观察者,它不会干扰shared_ptr所共享对象的所有权,当一个weak_ptr所观察的shared_ptr要释放它的资源时,它会把相关的weak_ptr的指针设置为空,防止weak_ptr持有悬空的指针。
注意:weak_ptr并不拥有资源的所有权,所以不能直接使用资源。可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权
unique_ptr:独占型的智能指针,不允许其他的智能指针共享其内部的指针,不允许赋值和拷贝,只支持移动语义
1、海量日志数据,提取出某日访问百度次数最多的那个IP。
既然是海量数据处理,那么可想而知,给我们的数据那就一定是海量的。针对这个数据的海量,我们如何着手呢?对的,无非就是分而治之/hash映射 + hash统计 + 堆/快速/归并排序,说白了,就是先映射,而后统计,最后排序:
具体而论,则是: “首先是这一天,并且是访问百度的日志中的IP取出来,逐个写入到一个大文件中。注意到IP是32位的,最多有个2^32个IP。同样可以采用映射的方法,比如%1000,把整个大文件映射为1000个小文件,再找出每个小文中出现频率最大的IP(可以采用hash_map对那1000个文件中的所有IP进行频率统计,然后依次找出各个文件中频率最大的那个IP)及相应的频率。然后再在这1000个最大的IP中,找出那个频率最大的IP,即为所求。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL||head->next==NULL)return head;
ListNode *pre=head,*cur=head->next;
ListNode* temp;
while(cur!=NULL){
if(pre==head){
pre->next=NULL;
}
temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL||head->next==NULL) return head;
ListNode *node=reverseList(head->next);
head->next->next = head;
head->next=NULL;
return node;
}
};
TCP:
服务端:
socket():套接字初始化,用于创建一个socket描述符(socket descriptor),它唯一标识一个socket
bind():绑定socket和地址
listen():监听端口
accept():listen队列中接收一个连接
recv()send():读写函数
close():关闭连接
客户端:
connect():发起连接
send()recv():读写函数
close():关闭连接
总结:
TCP Server端 :create --> bind --> listen --> accept --> recv/send --> close
TCP Client端 :create --> conncet --> send/recv --> close.
UDP:
. 服务器端:
1)创建套接字create;
2)绑定端口号bind;
3)接收/发送消息recvfrom/sendto;
4)关闭套接字。
2. 客户端:
1)创建套接字create;
2)发送/接收消息sendto/recvfrom;
3)关闭套接字.
总结:
UDP Server端 :create --> bind --> recvfrom/sendto -- >close
UDP Client端 :create --> sendto/recvfrom --> close.
1:共享内存
进程通信最快的一种方式,允许两个进程或者更多进程同时访问同一块内存,就如果malloc向不同进程返回了指向了同一个物理内存区域的指针,当一个进程改变了这块地址中的内容的时候,其他进程都会感应到
2:管道通信
分为有名管道(pipe)和无名管道(FIFO)
有名管道(pipe):只支持单向数据流,只能用于具有亲缘关系的进程之间
管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据
为了克服只能用于亲缘关系的进程通信,提出了有名管道,
有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信,因此,通过有名管道不相关的进程也能交换数据。有名管道的名字存在于文件系统中,内容存放在内存中
3.消息队列
存放在内核中的消息链表,每个消息队列由消息队列标识符表示,与管道不通的是消息队列放在内核中(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统),只有等内核重启,或者显示的删除一个消息队列的时候,该队列才会被真正的额删除。
4.socket 异机通信
5.信号(信号是软件层次上对中断机制的一种模拟,是一种异步通信方式)
信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态
通过代码理解僵尸进程
会产生僵尸进程:
如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
僵尸进程的危害:
如果有一个进程定期的产生一个子进程,但是子进程退出之后,父进程不进行回收,一段时间之后,就会产生很多的僵尸进程,如果用ps命令查看的话,会有很多状态为Z的进程,严格来说问题不在于僵尸进程而在于产生很多僵尸进程的父进程
如何消除:
通过kill命令杀死父进程,僵尸进程就变成了孤儿进程,孤儿进程会被Init接管,INIT进程会wait这些孤儿进程,释放她们占用的系统进程表中的资源,这样这些僵死的孤儿进程就可以处理了。
应用:布隆过滤器次可以快速的解决一些比较棘手的问题,如URL去重、垃圾邮件识别、大集合红重复元素的判断和缓存穿透等问题
Tip:布隆过滤器不能完全解决缓存穿透,只能将其控制在一个可以容忍的范围内
原理:
布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0
为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1”。而对于布隆过滤器来说,我们将使用多个哈希函数,这将会产生多个索引值。
三次哈希(三次哈希的函数不一样)之后,对应具体的位置设置成1,再次三次哈希取出值,看一下三个部分相乘是不是等于1
当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中。
虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址来调用虚函数