C++基础

计网

http & https

参考:https://www.cnblogs.com/gotodsp/p/6366163.html
参考:https://www.cnblogs.com/ranyonsue/p/5984001.html
参考:https://cyc2018.github.io/CS-Notes/#/notes/HTTP

  1. 请求、响应报文格式(4部分)
    请求行,请求头部,空行,数据部分
  2. 长连接和短链接
    请求响应完成后是否立即断开区分,1.0默认短连接,1.1默认长连接
  3. 请求响应过程
  4. 状态码
    200,301,400,404,500,503
  5. 请求方法:
    1.0:get, hed,post
    2.0补充:put,delete,connect,options,trace
  6. get/post区别:
    (1)GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditPosts.aspx?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的Body中.
    (2)GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码.
    (3)GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.

select函数

参考:https://www.cnblogs.com/ccsccs/articles/4224253.html
函数原型:int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

  1. fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
  2. 理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
    (1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
    (2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
    (3)若再加入fd=2,fd=1,则set变为0001,0011
    (4)执行select(6,&set,0,0,0)阻塞等待
    (5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
    基于上面的讨论,可以轻松得出select模型的特点:
    (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
    (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
    (3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

poll()函数

函数原型:int poll(struct pollfd * fds,unsigned int nfds ,int timeout);
先把服务器的描述符加入到描述符集合中,需要注意的是, select 所用的描述符集合是
一个fd_set 的结构体中,而
poll 的描述符却是在一个以pollfd 为元素的数组中进行传递**

epoll()函数

参考:https://blog.csdn.net/to_be_better/article/details/47349573

  1. 函数原型:
    epoll 操作过程需要3 个接口,分别如下:
    int epoll_create(int size); // 创建一个epoll的句柄,size告诉内核要监听的数目
    int epoll_ctl(int epfd , int op , int fd , struct epoll_event *event) ;//注册事件,ev结构体存放所有要监听的事件
    int epoll_wait(int epfd, struct epoll _event * events, int maxevents, int
    timeout);//等待事件发生,events存放发生了的事件
  2. 相对于select 和poll 来说, epoll 更加灵活,没有描述符限制。epoll 使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间之间的数据拷贝只需一次。

算法

递归算法时间复杂度

T(n)=a*T(n/b)+O(n^d)

  • log(b,a) > d : O(n^log(b,a))
  • log(b,a) =d : O(n^d *log(n))
  • log(b,a)<d : O(n^d)

a:子问题个数
n/b:子问题规模
O(n^d):合并子问题的复杂度

排序稳定性

  • 可以稳定:冒泡、插入、归并
  • 不可以:选择、快排、堆排

二叉树

  • 前、中、后、按层 非递归遍历,Z 遍历
  • 深度、镜像 :递归
  • 宽度:按层遍历
  • 转链表:中序遍历
  • 最低公共父节点、节点间的路径长度
  • 打印从左边、右边看到的节点:按层遍历,打印每层第一个或最后一个

链表

  • 主要用快慢指针、反转

  • 删除涉及头节点时,可以申请新节点使得 newHead->next=head。这样可以将头节点当作普通节点操作,避免分类讨论。

  • 删除到数第k个节点、删除所有重复节点

  • 是否有环、环的入口

  • 中位数:快慢指针,遍历一次,fast区分奇偶数目,(画图归纳)

  • 反转

  • Y型相交:一是借助两个栈,可以返回相交点;二是反转其中一个,遍历第二个的 尾 是不是等于第一个原始的头

  • 回文链表:快慢指针、反转后半部分

二分查找

注意循环条件,l、h赋值,返回值

  • 正常查找
  • 有重复时返回第一次的下标、最后一次的下标
  • 开根号:正常查找
  • 旋转数组最小值

  • bfs、dfs遍历
  • 最小生成树、最短路径

C++

线程锁

互斥锁、条件锁、自旋锁、读写锁
https://blog.csdn.net/bian_qing_quan11/article/details/73734157

INT_MAX INT_MIN

#include<limits.h>
int 最大值,最小值

宏函数和内联函数

宏只是进行简单的文本替换,而且是先替换再计算,会导致二义性。解决办法是把参数()起来,把结果()起来,比较麻烦
内联函数是真正的函数,采用参数传递

#include  <iostream>
using namespace std;

#define sqr(x) (x*x)

int main()
{
int a,b=3;
a =  sqr (b+2);
printf("%d",a);
return 0;
}

答案是11而不是25,原因是预编译宏展开的时候,被展开成了b+2*b+2。

将x括起来,改成如下程序
#include  <iostream>
using namespace std;
#define sqr(x) ((x)*(x))
int main()
{int a,b=3;a =  sqr (b+2);printf("%d",a);return 0;
}
则结果是25

typedef #define

typedef char *String_t; 和 #define String_d char *; 这两句在使用上有什么区别?

  1. 前者定义了一个新的类型别名,有类型检查;后者只是做了个简单的替换,无类型检查。
  2. 前者在编译的时候处理,后者在预编译的时候处理。
  3. 在使用上同时定义多个变量的时候有区别,如 String_t a, b; 或 String_d c, d; 其中 a, b 和 c 都是 char * 类型,而 d 为 char 类型;typedef 比 #define 安全,主要是 typedef 会做类型检查。

野指针是什么?

也叫空悬指针,不是指向null的指针,是指向垃圾内存的指针

产生原因及解决办法:

  1. 指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
  2. 指针free或delete之后没有及时置空 => 释放操作后立即置空。

链接

软链接、硬链接、inode、连接数
参考:https://www.cnblogs.com/cherishry/p/5885107.html
参考:https://blog.csdn.net/bitboss/article/details/53940236

sizeof && strlen

sizeof()是运算符,获得对象的大小。当对象是指针式返回指针的大小(而不是指向的内存大小);当是数组时,返回数组所占的空间大小;当把数组作为实参传入时,返回指针大小。

strlen()是函数,返回字符串的字符数

int main()
{
 char s1[]="hello";//计算末尾的‘\0'
 char s2[5]={'a','b'};
 char *s3="helll";
 char *p=s2;
 cout<<sizeof(s1)<<" "<<sizeof(s2)<<" "<<sizeof(s3)<<" "<<sizeof(p)<<endl;
 
 return 0;
}

在这里插入图片描述

智能指针

参考 https://www.cnblogs.com/wxquare/p/4759020.html

new和malloc的区别

  1. 性质
    new是操作符,操作符可以进行重载,malloc是库函数
  2. 使用
    new使用的时候会自动计算大小,malloc则要指定大小
    new/delete在对象(如类对象)创建的同时会【自动执行构造函数】做初始化,在对象在消亡时会自动执行析构函数。
    而malloc只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的;
  3. 成功
    new成功后返回对象类型的指针,malloc返回void* 指针,需要自己做类型转换
  4. 失败
    new抛出异常,malloc返回会NULL,所以用new之后在判断是否为NULL没什么意义
  5. 扩容
    new不支持扩容, malloc在使用过程中发现内存不够可以使用realloc来进行扩容

const

左结合,左为空,右结合

  1. const修饰变量(包括指针)
  2. 修饰函数返回值,表示函数的返回值不能修改
  3. 修饰函数参数,表示该参数不能被改变
  4. 修饰函数
    int get()const;
    用在类中,表示函数仅能访问数据,而不能修改

static 作用

参考:https://www.cnblogs.com/songdanzju/p/7422380.html

  1. 限制全局变量的作用域:当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
//a.c
char a = 'A'; // global variable
void msg()
{
     printf("Hello\n");
}
 
//main.c
int main()
{
     extern char a; // extern variable must be declared before use
     printf("%c ", a);
     (void)msg();
     return 0;
}
结果是 A hello
  1. static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)
    存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见
    PS:如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
  2. static 默认初始化为 0
  3. 在类中使用static
    在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
    (1) 类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。
    (2) 不能将静态成员函数定义为虚函数。

(3)静态成员初始化与一般数据成员初始化不同:
初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
数据类型><类名>::<静态数据成员名>=<值>

数组指针&&指针数组&&函数指针

数组指针:int (*p)[n];且()优先级高,首先说明p 是一个指针,且指向一个整型的一维
数组。这个一维数组的长度是n ,也可以说是p 的步长,也就是说执行p+l 时, p 要跨过n
个整型数据的长度。或者 int a[10] ; int *p=&a;

指针数组:int *p[n];p是一个数组,每个元素是一个指针。

函数指针:函数指针是指向函数的指针变量。所以,函数指针首先是个指针变量,而且这个变量指向一个函数。

#include<iostream>
using namespace std;

int  max(int a,int b)	
{
	if(a>b)
		return a;
	return b;
}
int main()
{
	int (*f)(int a,int b);//申明
	f=max;//赋值
	cout<<f(10,20)<<endl;
}

区别以下几种const变量

const int a;
int const a;
const int *a;
int *const a;

  1. int const a和const int a均表示定义常量类型a。
  2. const int *a,其中a为指向int型变量的指针,const在 * 左侧,表示a指向不可变常量。(看成const (*a),对引用加const)
  3. int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const)

区别以下指针类型?

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)

  1. int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
  2. int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
  3. int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  4. int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

大端/小端

大端是内存低地址存高位数字
小端是内存低地址存地位数字
注意:一个字节存两个16进制数字

#include<iostream>
#include<stdio.h>
using namespace std;
union A{
	char c[4];
	int i;
};

int main()
{
	cout<<sizeof(A)<<endl;       //4
	A a;
	a.i=0x41424344;
	printf("%x\n",a.i);        //41424344
	cout<<a.c<<endl;          //DCBA
	//证明系统是小端,因为i的值从左到右表示从高位到低位,c的输出是从低地址到到地址
}

结构体&类的区别

结构体的成员访问权限默认是public
而class默认是篇private

strcpy strncpy memcpy区别

strcpy:复制后加‘\0’,用于字符串
strncpy:遇到’\0’或达到size时结束,不加’\0’
memcpy:可用于数组/结构体等,用途广泛,void *memcpy(void *dest, const void src, size_t n); 起初,不能处理重叠,现在可以了。
memmove:支持内存地址重叠,其他都不支持,void memmove( void dest, const void
src, size_t count );

void* my_memcpy(void* dst, const void* src, size_t n)//n是字节数
{
    char *tmp_d = (char*)dst;
    char *tmp_s = (char*)src;

    if(tmp_d>tmp_s && tmp_s+n > tmp_d)//重叠
    {
        while(n--)
            *(tmp_d+n-1)=*(tmp_s+n-1);
        return dst;
    }

    
    while(n--) 
    {
        *tmp_d++ = *tmp_s++;
    }
    return dst;
}

初始化列表更快

对于用户定义类型,使用列表初始化可以减少一次默认构造函数调用过程

class A{
	private:
		string s;
	public:
		A(string str)
		{
			s=str;  //先调用string类的default构造函数,初始化s,
				//再调用赋值函数将str赋值给s
		}
		
		A(string str)
		:s(str)   //直接使用str作为实参,调用string的拷贝函数进行拷贝构造s
		{}
};

拷贝构造函数&&赋值

区别是对象是否存在

class A;
A=B;//赋值

class C=B;//拷贝构造函数

explicit关键字

参考:https://blog.csdn.net/qq_35524916/article/details/58178072
C++中, 只有一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色:

  1. 构造函数;
  2. 是个默认且隐含的类型转换操作符。
    类型转换:我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造函数的参数类型, 这时候编译器就自动调用这个构造函数, 创建一个AAA的对象。
    为了禁止这种做法,需要在构造函数前面加上explicit关键字,表明该构造函数只能被明确的调用,而不能用作类型转换。

虚函数实现和使用

参考1:https://www.cnblogs.com/malecrab/p/5572730.html
参考2: https://www.cnblogs.com/malecrab/p/5572730.html

  1. 执行构造函数时将虚函数的指针放到虚函数表里。如果是派生类,先复制基类的虚函数表,替换掉重写的虚函数地址,在后面加上自己实现的别的虚函数地址。若继承了多个基类,则会有多个虚函数表。
  2. 调用虚函数:pBase->show()
    2.1 断Base类中show是否为虚函数。
    2.2 若不是虚函数则找到pBase所指向的对象所属类Base。执行Base::show()。若是虚函数则执行步骤 2.3 .
    2.3 访问pBase所指对象的虚函数表指针得到pBase所指对象所在类的虚函数表。
    2.4 查找Base中show()在声明时的位序为x,到步骤3得到的虚函数表中找到位序x,从而得到要执行的show的函数地址。
    2.5 根据函数地址和Base中声明的show的函数类型(形参和返回值)访问地址所指向的函数.

继承关键字

https://www.cnblogs.com/spring-hailong/p/6166191.html

虚继承

参考:https://blog.csdn.net/bxw1992/article/details/77726390
在这里插入图片描述
在菱形继承中,D会有两个A的副本,这使得:

  1. 在D中访问A的变量 a 时,不知道是哪一个,A->B->B 还是 A->C->D 指的 a, 产生歧义
  2. D包含两个A的副本是多余的,会浪费存储空间。

虚继承
class B:virtual public A
class C:virtual public A
class D:public B,C

  1. B、C会有虚基类表(类似虚函数表),存储虚基类相对于本类的偏移地址,保存指向表的虚基类指针。
  2. D继承时会继承B、C的虚基类指针,保留一份A的拷贝(我的理解是,D继承时通过两个虚基类指针访问表,再访问虚基类,发现是同一个基类,然后就只拷贝一份)

友元

  1. 友元函数 :
    友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
    friend 类型 函数名(形式参数);
  2. 友元类 :
    友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
    当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
    friend class 类名;
  3. (1) 友元关系不能被继承。
    (2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
    (3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元
    在这里插入图片描述

const使用

  1. const数据成员要在初始化列表里初始化(还有 基类数据,子类数据,static数据,引用数据)
  2. const成员函数不能修改数据
  3. const对象的非const成员函数不能访问任何数据成员
  4. int get() 和 int get() const 是不一样的。

什么时候调用拷贝构造函数

  1. 用一个对象去初始化另一个对象
  2. 对象作为参数传递(此时是按值传递,要拷贝一个副本传递)
  3. 函数返回值是一个对象或引用时

空间大小

  1. 空类大小为 1
  2. static成员、成员函数、构造函数、析构函数不占空间
  3. 虚函数占用一个指针大小的空间(虚函数的指针)

构造函数、析构函数顺序

先基类或者子类的构造函数,当多重继承时,递归向上
再派生类或当前类
如 A -->B–>C, C c时,构造函数顺序:A(),B(),C()

析构函数顺序相反

构造函数不能为虚函数,析构函数可以而且常常是虚函数

this指针

在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this 指
针。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。例
如,当调用成员函数a.volume 时,编译系统就把对象a 的起始地址赋给this 指针,在成员
函数引用数据成员时,就按照this 的指向找到对象a 的数据成员。

静态数据

有时需要为某个类的所有对象分配一个单一的存储空间。在C 语言中,可以使用全局变
量,但这样很不安全。全局数据可以被任何人修改,而且在一个项目中,它很容易和其他名
字冲突。如果可以把数据当成全局变量那样去存储,但又被隐藏在类的内部,而且清楚地与
这个类相联系,这种处理方法就是最理想的。这个可以用类的静态数据成员来实现。**类的静
态成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据
成员都共享这一块静态存储空间,这就为这些对象提供了一种互相通信的方法。**静态数据成
员是属于类的,它只在类的范围内有效,可以是public 、private 或protected 的范围。
因为不管产生了多少对象,类的静态数据成员都有着单一的存储空间,所以存储空间必
须定义在一个单一的地方。如果一个静态数据成员被声明而没有被定义, 链接器会报告一个
错误:“ 定义必须出现在类的外部而且只能定义一次” 。因此静态数据成员的声明通常会放在
一个类的实现文件中。
在这里插入图片描述

STL

sort函数

  1. Sort函数包含在头文件为#include的c++标准库中:sort(begin,end,cmp)
  2. Sort函数有三个参数:
    (1)第一个是要排序的数组的起始地址。
    (2)第二个是结束的地址(最后一位要排序的地址+1)
    (3)第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是从小到大排序。
#include<iostream>
#include<algorithm>
using namespace std;

bool cmp(int a,int b)//比较函数,可以自定义,如升序,降序,结构体排序
{
	return a>b;
}
int main()
{
	int a[10]={9,6,3,8,5,2,7,4,1,0};
   for(int i=0;i<10;i++)
    cout<<a[i]<<" ";
    cout<<endl;
    
    sort(a,a+10);
    for(int i=0;i<10;i++)
       cout<<a[i]<<" ";
   cout<<endl;
    
       sort(a,a+10,cmp);
    for(int i=0;i<10;i++)
       cout<<a[i]<<" ";
       
   return 0;
}

在这里插入图片描述

string

string int互转

  1. string转int
string s="123456";
int n=atoi(s.c_str());
  1. int 转string
/*#include<sstream>

int n=123456;
string s;
stringstream ss;

ss<<n;
ss>>s;*/
string s=to_string(n);
  1. 常用函数
str.substr(int start,int length);//子串
int str.find(int str1,int start);//str1是否是str得子串,是返回下标,否则返回-1
								   start是查找的起始位置,可省略

vector

基础知识

在这里插入图片描述
两个关键大小:
大小:size=_Mylast - _Myfirst;
容量:capacity=_Myend - _Myfirst;
分别对应于resize()、reserve()两个函数。
size表示vector中已有元素的个数,容量表示vector最多可存储的元素的个数;为了降低二次分配时的成本,vector实际配置的大小可能比客户需求的更大一些,以备将来扩充,这就是容量的概念。即capacity>=size,当等于时,容器此时已满,若再要加入新的元素时,就要重新进行内存分配,整个vector的数据都要移动到新内存。二次分配成本较高,在实际操作时,应尽量预留一定空间,避免二次分配。
使用resize(n)指定大小后,若再次push_back(),使得size>n时,后面的值会被抹去。而在初始化时指明大小,在超过时会扩容。

用法

定义:
        vector<T> vec;

    插入元素:
        vec.push_back(element);
        vec.insert(iterator, element);

    删除元素:
        vec.pop_back();
        vec.erase(iterator);
        
	//循环删除
	for(it=map.begin();it!=map.end())
	{
		if(it->second == ...)
			it=map.erase(it);
		else
			it++;
	}
	
    修改元素:
        vec*[p*osition] = element;

    遍历容器:
        for(auto it = vec.begin(); it != vec.end(); ++it) {......}

    其他:
        vec.empty();    //判断是否空
        vec.size();    // 实际元素
        vec.capacity();    // 容器容量
        vec.begin();    // 获得首迭代器
        vec.end();    // 获得尾迭代器
        vec.clear();    // 清空

map

基础知识

底层是红黑树,会自动排序,所以内部是有序的,插入删除的代价为O(log N),查找O(log N)

用法

 定义:
        map<T_key, T_value> mymap;

    插入元素:
        mymap.insert(pair<T_key, T_value>(key, value));    // 同key不插入
        mymap.insert(map<T_key, T_value>::value_type(key, value));    // 同key不插入
        mymap[key] = value;    // 同key覆盖

    删除元素:
        mymap.erase(key);    // 按值删
        mymap.erase(iterator);    // 按迭代器删

    修改元素:
        mymap[key] = new_value;

    查找:
    	int f=mymap.count(key)//找到返回1,否则返回0
    	map<int ,string>::iterator it=mymap.find(key)//找到返回指向的迭代器,否则返回end();
    	
    遍历容器:
          for(auto it = mymap.begin(); it != mymap.end(); ++it) 
          {
            cout << it->first << " => " << it->second << '\n';
          }

hash_map

基础知识

hash table 和 hash map 是类似的,只是前者是线程安全的,后者不是。

基本实现是 数组+链表
自定义数据类型时要重载 hash函数 和 比较函数
hash函数:得到数组下标,也就是桶号
比较函数:用于查找时确定时桶内的哪个节点

在这里插入图片描述

冲突解决

  1. 链地址法
  2. 开放地址检测
    2.1 线性探测
    2.2 平方探测
  3. 再散列
    参考:https://blog.csdn.net/qq_32595453/article/details/80660676

为什么hash函数常见对素数取模

使得hash值均匀分布,降低冲突率。
常用的hash函数是选一个数m取模(余数),这个数在课本中推荐m是素数,但是经常见到选择m=2n,因为对2n求余数更快,并认为在key分布均匀的情况下,key%m也是在[0,m-1]区间均匀分布的。但实际上,key%m的分布同m是有关的。
  证明如下: key%m = key - xm,即key减掉m的某个倍数x,剩下比m小的部分就是key除以m的余数。显然,x等于key/m的整数部分,以floor(key/m)表示。假设key和m有公约数g,即key=ag, m=bg, 则 key - xm = key - floor(key/m)m = key - floor(a/b)m。由于0 <= a/b <= a,所以floor(a/b)只有a+1中取值可能,从而推导出key%m也只有a+1中取值可能。a+1个球放在m个盒子里面,显然不可能做到均匀。
  由此可知,一组均匀分布的key,其中同m公约数为1的那部分,余数后在[0,m-1]上还是均匀分布的,但同m公约数不为1的那部分,余数在[0, m-1]上就不是均匀分布的了。把m选为素数,正是为了让所有key同m的公约数都为1,从而保证余数的均匀分布,降低冲突率。

加载因子

所谓的加载因子就是 HashMap (当前的容量/总容量) 到达一定值的时候,HashMap 会实施扩容。加载因子也可以通过构造方法中指定,默认的值是 0.75 。举个例子,假设有一个 HashMap 的初始容量为 16 ,那么扩容的阀值就是 0.75 * 16 = 12 。也就是说,在你打算存入第 13 个值的时候,HashMap 会先执行扩容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值