文章目录
- 【常用设计模式】
- 【C++的类是怎样实现的】
- 【服务器的高并发解决方案】
- 【C++面向对象的六大设计原则】
- 【C++内存管理】
- 【C++基本数据类型】
- 【STL中erase和remove的区别】
- 【全局变量 静态变量 局部变量的生命周期和与作用域】
- 【STL中的迭代器和迭代器失效问题】
- 【指针和数组的区别】
- 【const 类型的指针】
- 【封装 继承 多态 三者之间的区别】
- 【哈希冲突】
- 【map和multimap--黑马讲解]
- 【IP数据报的传输过程】
- 【指针 空悬指针 野指针 内存泄露 内存溢出 原因和避免方法】
- 【http的短连接和长连接】
- 【IP地址的组成和IP报文的大小】
- linux下查看某个端口是不是被占用
- pragma预处理指令
- delete和delete []的区别
- 堆栈详解&&堆栈溢出
- 常用端口号
- vector内存释放问题
- 【get和post的区别】
【常用设计模式】
【C++的类是怎样实现的】
在 C++ 中,类是通过定义一个结构体或类
来实现的。结构体或类定义了一组成员变量和成员函数,以便对象能够存储和操作数据。
在类定义中,可以使用各种访问修饰符来控制成员变量和成员函数的可见性和访问权限
。常见的访问修饰符有 public、private 和 protected。public 成员变量和成员函数可以被类外的代码访问,而 private 成员变量和成员函数只能在类内访问。protected 成员变量和成员函数可以被类内和派生类的代码访问。
除了成员变量和成员函数外,C++ 中的类还可以包含构造函数、析构函数、静态成员变量和静态成员函数等特殊成员。
构造函数用于在创建类的实例时初始化成员变量,而析构函数用于在对象被销毁时清理资源。
静态成员变量和静态成员函数与类本身相关联,而不是与类的实例相关联。静态成员可以在类外部访问,而不需要创建类的实例。
【服务器的高并发解决方案】
【C++面向对象的六大设计原则】
/*
* 面向对象的六大设计原则:
* <1>单一职责原则:单一职责原则是在描述一个类其中应该只包含一组和其功能相关的成员函数和数据,而不应该再含有其他功能,
* 当一个类中含有较多不想关的成员变量或者函数时,我们应该考虑将这个类拆分成多个类
* 单一职责原则的优点:
* 单一职责原则可以降低类的复杂度,提高系统的可维护性。
* 单一职责实际也减少了类的冗余性,提高了类的重组性和功能单一化
* 消除耦合,减小由业务变化引起的代码僵化
* 单一职责原则解决的问题:
* 当有类A,需要有两个功能T1和T2时,当T1的功能需要改变时,可能会导致T2的功能有障碍,因此在设计时就需要将T1和T2两个
* 分别构建类Fun1和Fun2,来分别完成他们的功能
*
* <2>里式替换原则
*
* 里式替换原则:里式替换原则是依赖于继承和多态的,当有父类出现的地方,子类也可以出现,并且子类对象可以替换父类对象,并且
* 使用者不需要知道是父类还是子类,但是反之却不行。因为父类中不一定有子类的方法。
*
* 里式替换的优点:里式替换可以提高代码的可重用性
* 保证子类中含有父类的所有属性,
* 缺点:子类中有时候可能就比较冗余
*
*
* <3>依赖倒置原则:依赖倒置原则在讲各个类之间是通过接口去耦合,而不是和类的接口的实现方法相关
* 是和类方法的地层实现不相关的。
*
*
* <4>开放封闭原则:开放封闭原则讲的是对于修改我们应该封闭,而对于拓展我们是应该开放
*
* <5>迪米特原则: 对象与对象之间应该使用尽可能少的方法来关联,避免千丝万缕的关系。
* 一个对象应该对其他对象有最少的了解。一个类应该对自己需要耦合或调用的类知道的最少,
* 类的内部如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需知道它需要的方法即可。
* 一般是伴随着类的组合出现
*
* <6>接口隔离原则:接口隔离原则推荐在设计类时应该使用最小的接口,使用多个单一功能的接口比使用一个总的接口要好。
*/
【C++内存管理】
【C++基本数据类型】
【STL中erase和remove的区别】
在C++的STL中:
erase是真正的删除元素
了,迭代器无法再次访问;
而remove只是简单的把要remove的元素移到了容器的最后
,迭代器还是可以访问的,不知道容器的内部结构,只是移动了位置,无法真正的删除元素;
【全局变量 静态变量 局部变量的生命周期和与作用域】
【STL中的迭代器和迭代器失效问题】
不同于OOP(Object Oriented Programming)将数据(datas)和行为(methods)组织在一起的思想,STL的中心思想GP(Generic Programming)在于:将数据容器(containers)和算法(algorithms)分开,彼此独立设计,最后再以胶着剂将它们撮合在一起。那么之间的胶着剂就是迭代器(iterator)
。
迭代器是一种行为类似指针的对象(智能指针对象 主要的行为就是对容器中的数据成员进行遍历访问(遍历范围[first,last]
),所以迭代器要对operator&()和operator*()进行重载,同时要兼顾自身的有效性(析构)的问题。
【指针和数组的区别】
1、概念:
数组 是存储多个相同类型数据的集合
指针 指针相当于一个变量,但是它和变量不同,它存放的是变量的内存地址;
2、赋值 存储方式 求sizeof 初始化等方面的区别
1)赋值:同类型指针变量可以相互赋值,数组不行,只能一个个元素的赋值或复制;
2)存储方式
数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组下标进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的;
数组存储方式:数组存储空间,不是在静态区就是在栈上;
指针:指针很灵活,可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
指针存储方式:指针的存储空间不能确定
,由于指针本身就是一个变量,再加上它所存放的也是变量,因此存储空间不确定;
3)求sizeof
数组:sizeof(数组名):求数组所占存储空间的内存或是数组的大小
指针:32位下 sizeof(指针名)=4 ;64位下 sizeof(指针名)=8
4)访问效率
数组通过下标直接访问
,虽然访问效率高于指针,但灵活性很差;
指针通过地址找到指针所指的对象,再访问对象的内存单元
,属于间接访问, 访问效率低于数组,但是灵活性高;
5)安全性
数组安全性高于指针
数组可能存在的问题(数组访问越界
)
指针可能出现的问题(野指针 空悬指针 造成内存泄露
)
【const 类型的指针】
【重点】 const 对象一旦创建之后就不能改变,所以const对象必须初始化;
由于一个指针变量涉及到了两个内存空间,指针变量本身所占用的内存空间以及指针变量本身所指向的数据的内存空间
,而const类型的变量表示该变量的值不能改变,因此将const与指针结合应该有两种形式:一是指针本身是常数(顶层const),另一种是指针变量所指的数据为常数(底层const)
;
1、指向常量的指针:指针所指的数据为常量,指向一个只读变量。格式: <类型>const* <指针变量>或const <类型>*<指针变量>
;比如 int const*p const int *p
const是用来修饰*p的 代表p指向的空间中的内容不能通过此指针来进行改变;
2、常量指针 指针本身是个常量 必须初始化 一旦初始化完成 不可再改。
格式:<类型>*const<指针变量>
比如:int * const p ; const修饰的是指针p本身,表示指针的值不能修改,即指针的指向不能修改;
3、指向常量的常量指针 即指针的值为常量 同时指针所指向的数据也为常量
** 格式: const<类型>*const <指针变量> **
【封装 继承 多态 三者之间的区别】
参考资料
多态实现之重写
:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。
三大特性的作用:
封装可以隐藏实现细节,使得代码模块化;
继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用
。
而多态则是为了实现另一个目的——接口重用
【哈希冲突】
- 什么是哈希冲突?
一般是key-value是一一对应的,但是如果不同的key通过哈希映射到的value是同一个
,那么就产生了冲突,这就是哈希冲突; - 解决哈希冲突的四种方式
一、开放定址法
所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
※ 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。
二、再哈希法
也称双哈希法
。
基本实现思路是
:当关键字为key的哈希地址p=H(key)出现冲突时,以p为基础产生另一个哈希地址p1,如果p1还是冲突的,再以p为基础产生另一个哈希地址p2…直到找到一个不冲突的哈希地址为止。
三、拉链法
也称链地址法
,基本思想是:每个哈希表节点都有一个next指针,多个哈希节点可以用next指针构成一个单向链表
,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。
如:
键值对k2, v2与键值对k1, v1通过计算后的索引值都为2,这时即使产生冲突,但是可以通道next指针将k2, k1所在的节点连接起来,这样就解决了哈希的冲突问题 ;
四、建立公共溢出区
基本思想是:将哈希表分为基本表和溢出表
两部分,凡是和基本表发生冲突的元素,一律填入一处表;
【map和multimap–黑马讲解]
map multimap都是关联容器
;底层是二叉树
元素类型都是pair
key-value
结构
所有元素的都会根据元素的键值自动排序
二者的区别是:
map容器不允许容器中由重复的key值元素
multimap允许容器中有重复的key值
元素
【IP数据报的传输过程】
MAC头部
mac头部是以太网使用的头部。在数据报的传输过程中,IP包中的源IP地址和目的IP地址始终不变,通过改变mac头部的接收方mac地址和发送方mac地址达到两点之间的传输
;
【指针 空悬指针 野指针 内存泄露 内存溢出 原因和避免方法】
指针
是一个变量,存储的是指针所指对象的内存地址,可以通过指针进行遍历访问。首先通过指针上的地址找到指针指向的对象,然后访问对象的内存空间,属于间接访问。
空悬指针
:指向了已经销毁或已经回收的地址的指针,但是这个指针还没有指向NULL;
启发我们在编程时,当调用了free或delete时,除了释放动态申请的内存,还要将相关的指针指向NULL
,避免出现空悬指针;
野指针
:没有初始化
的指针就是野指针;
内存泄露
:指程序中有申请内存的过程,但是在程序结束后,始终没有释放占有的内存,导致这块内存它自己不能再使用,操作系统也不能再把它分配给别的程序使用,这种情况就是内存泄露;
内存溢出
:可用的内存均被占用,没法申请内存的状况;
解决方案是用引用计数型智能指针shared_ptr/weak_ptr
假设存在两个指针指向同一个对象,两个指针分别是两个线程里的,当其中一个指针释放对象时,另一个对象就指向了空的内存,这时这个指针就是空悬指针。
解决方案是引入引用计数型智能指针
:
引入一个智能指针对象,这个智能指针保存有指针和计数器
,计数器记录有多少个指针指向了该对象,每当有指针释放对象时,引用计数就减一,直到减到0时,就可以安全的销毁对象了。
shared_ptr 是引用计数型智能指针,也是一个类模板,它只有一个类型参数。引用计数是常用的RALL机制(资源获取即初始化),当引用计数减少到0时,对象就会被销毁;
weak_ptr也是引用计数型指针,但是它不增加对象的引用次数
,即弱引用;
多个线程同时访问shared_ptr时,需要用mutex来保证它的线程安全
;
【http的短连接和长连接】
参考资料
长短指的是客户端和服务器端的连接维持时间的长短
;
HTTP的长连接和短连接本质上是TCP长连接和短连接。
HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。
在HTTP/1.0
中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作(传输HTTP数据),就建立一次TCP连接,任务结束就中断连接
。 当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话
。
而从HTTP/1.1
起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
Keep-Alive不会永久保持连接,它有一个保持时间
,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
【IP地址的组成和IP报文的大小】
版本号--IP报头长度--服务类型--IP包总长--标识符--标记--片偏移
-生存时间--协议--头部校验--源端和目标端地址--可选项--填充
版本号(Version:长度4比特标识目前采用的IP协议的版本号。一般的值为0100(IPv4),0110(IPv6)
IP包头长度(Header Length):长度4比特
。这个字段的作用是为了描述IP包头的长度,因为在IP包头中有变长的可选部分。该部分占4个bit位,单位为32bit(4个字节),即本区域值= IP头部长度(单位为bit)/(84),因此,一个IP包头的长度最长为“1111”,即154=60个字节。IP包头最小长度为20字节。
服务类型(Type of Service):长度8比特
。8位 按位被如下定义 PPP DTRC0
PPP:定义包的优先级,取值越大数据越重要
IP包总长(Total Length):长度16比特
。 以字节为单位计算的IP包的长度 (包括头部和数据),所以IP包最大长度65535字节。
标识符(Identifier):长度16比特
。该字段和Flags和Fragment Offest字段联合使用,对较大的上层数据包进行分段(fragment)操作。路由器将一个包拆分后,所有拆分开的小包被标记相同的值,以便目的端设备能够区分哪个包属于被拆分开的包的一部分。
标记(Flags):长度3比特
。该字段第一位不使用。第二位是DF(Don’t Fragment)位,DF位设为1时表明路由器不能对该上层数据包分段。如果一个上层数据包无法在不分段的情况下进行转发,则路由器会丢弃该上层数据包并返回一个错误信息。第三位是MF(More Fragments)位,当路由器对一个上层数据包分段,则路由器会在除了最后一个分段的IP包的包头中将MF位设为1。
片偏移(Fragment Offset):长度13比特
。表示该IP包在该组分片包中位置,接收端靠此来组装还原IP包。
生存时间(TTL):长度8比特
。当IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于路由环路而导致IP包在网络中不停被转发。
协议(Protocol):长度8比特
。标识了上层所使用的协议。
头部校验(Header Checksum):长度16位
。用来做IP头部的正确性检测,但不包含数据部分。 因为每个路由器要改变TTL的值,所以路由器会为每个通过的数据包重新计算这个值。
起源和目标地址(Source and Destination Addresses):这两个地段都是32比特。标识了这个IP包的起源和目标地址。要注意除非使用NAT,否则整个传输的过程中,这两个地址不会改变。
至此,IP包头基本的20字节已介绍完毕,此后部分属于可选项,不是必须的部分。
可选项(Options):这是一个可变长的字段。该字段属于可选项,主要用于测试,由起源设备根据需要改写。可选项目包含以下内容:
松散源路由(Loose source routing):给出一连串路由器接口的IP地址。IP包必须沿着这些IP地址传送,但是允许在相继的两个IP地址之间跳过多个路由器。
严格源路由(Strict source routing):给出一连串路由器接口的IP地址。IP包必须沿着这些IP地址传送,如果下一跳不在IP地址表中则表示发生错误。
路由记录(Record route):当IP包离开每个路由器的时候记录路由器的出站接口的IP地址。
时间戳(Timestamps):当IP包离开每个路由器的时候记录时间。
填充(Padding):因为IP包头长度(Header Length)部分的单位为32bit,所以IP包头的长度必须为32bit的整数倍。因此,在可选项后面,IP协议会填充若干个0,以达到32bit的整数倍。
linux下查看某个端口是不是被占用
1.查看80端口号是否被占用 命令:netstat -anp | grep 80
2.查看当前环境下已经使用了的端口信息 命令:netstat -nultp
3.通过kill -9 PID
,将该线程强杀即可
pragma预处理指令
常用的pragma指令的详细解释
1.#pragma once。
保证所在文件只会被包含一次,它是基于磁盘文件的,而#ifndef则是基于宏的。
2.#pragma warning。
允许有选择性的修改编译器的警告消息的行为。有如下用法:
#pragma warning(disable:4507 34; once:4385; error:164) 等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误
#pragma warning(default:176) // 重置编译器的176号警告行为到默认状态
同时这个pragma warning也支持如下格式,其中n代表一个警告等级(1—4):
#pragma warning(push) // 保存所有警告信息的现有的警告状态
#pragma warning(push,n) // 保存所有警告信息的现有的警告状态,并设置全局报警级别为n
#pragma comment
(lib,“XXX.lib”)
表示链接XXX.lib这个库,和在工程设置里写上XXX.lib的效果一样。
#pragma comment(linker,“/ENTRY:main_function”)
表示指定链接器选项/ENTRY:main_function
delete和delete []的区别
delete只会调用一次析构函数。
delete[]会调用数组中每个元素的析构函数
。
delete 释放new分配的单个对象指针
指向的内存
delete[] 释放new分配的对象数组指针
指向的内存
堆栈详解&&堆栈溢出
参考
参考
函数调用过程中堆栈的变化:
在一次函数调用中,首先分配一个栈帧,然后在栈帧中将被依次压入:参数,返回地址,EBP(指针寄存器)。如果函数有局部变量,接下来,就在堆栈中开辟相应的空间以构造变量。
函数执行结束,这些局部变量的内容将被丢失。但是不被清除。在函数返回的时候,弹出EBP,恢复堆栈到函数调用的地址,弹出返回地址到EIP以继续执行程序。
堆栈溢出:
堆栈溢出就是不顾堆栈中数据块大小,向该数据块写入了过多的数据,导致数据越界
,结果覆盖了老的堆栈数据。
C++内存分区:
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
。
堆栈的区别:
堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。 栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。 每个进程拥有的栈大小要远远小于堆大小。
(3)生长方向不同。 堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。 堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca()函数分配,但是栈的动态分配和堆是不同的,它的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。 栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多
。
(6)存放内容不同。 栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等
。
堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的
。
从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。 虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。
无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据
,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。
TCP建立连接后服务器出现断电 崩溃和拔掉网线会怎样
1.服务器崩溃:服务器会发送RST包
让客户端关闭连接
发送RST包的情况:
1)服务器没有打开对应的端口
2)服务器想主动关闭连接(非优雅关闭)
3)在一个已关闭的socket中收到数据(一般是半打开连接,一方关闭了另一方却不知道)
参考:https://my.oschina.net/costaxu/blog/127394
2.服务器断电、网线被拔:包不能被接收,需要客户端开启keep-alive
,长期未收到响应就主动关闭连接。
一般心跳包的机制
是:客户端主动发送,服务器定时接收。若客户端没有收到响应就判定服务器断连,若服务器没有到收到心跳包就判定客户端断连。
常用端口号
SSH端口号默认端口号是TCP 22端口
redis端口号6379
Mysql端口号 3306
vector内存释放问题
vector的内存空间是只增加不减少的,我们常用的操作clear()和erase(),实际上只是减少了size(),清除了数据,并不会减少capacity,所以内存空间没有减少。
那么如何释放内存空间呢,正确的做法是swap()操作
。将我们需要释放内存的vector和一个空的vector 如vector<int>()
进行swap()操作。
【get和post的区别】
GET请求是一种用于客户端从服务器获取数据的方法
。 POST请求用于从客户端向服务器提交数据
。
GET和POST是HTTP协议中常用的两种请求方法,用于在客户端和服务端之间传输数据。
它们在以下几个方面有所区别:
参数位置:GET请求的参数通常以查询字符串的形式附加在URL的末尾,而POST请求的参数则包含在请求的正文(body)中。
参数长度限制:由于GET请求的参数位于URL中,URL的长度限制了参数的长度,传递的数据量也会有所限制。而POST请求的参数在请求体中,没有特定长度限制,可以传递较大的数据量。
安全性:由于GET请求的参数出现在URL中,敏感信息会暴露在浏览器的历史记录、服务器的日志文件中,甚至可以通过网络抓包获取,因此不适合传递敏感信息。而POST请求的参数在请求体中,相对更安全,不会在URL中直接暴露。
缓存:GET请求可以被浏览器缓存,如果多次发送相同的GET请求,浏览器可能会直接返回缓存的响应结果,而不会发送实际的请求。POST请求默认情况下不会被浏览器缓存。
幂等性:GET请求是幂等的,即多次发送相同的GET请求应该产生相同的结果,不会对服务器产生副作用。而POST请求不是幂等的,每次发送POST请求,可能会对服务器产生不同的结果或产生副作用。
根据请求的目的和要传递的数据,选择合适的请求方法(GET或POST)来进行数据传输是很重要的。GET适合用于获取资源,而POST适合用于提交数据或执行对服务器产生影响的操作。