@markheng
2018-10-13T23:36:03.000000Z
字数 19558
阅读 1707
找工作
右边(上角)有目录。
printf可变参数的实现原理:依据最前面的参数知道有多少个可变参数,从参数栈中逐一取出参数
[closure](parameters)->return_value{statements;}
template <typename T>
const T operator++(int){
...
}
返回const T 防止 T t; t++++;
的用法
- prefix
template <typaname T>
T& operator++(){
...
}
参数只是用来区分前置后置
export
类似extern,但是用于template
explicit 关键字 只能修饰构造函数,表示不允许在构造函数中进行隐式转换
extern关键词的作用
两个作用,一是extern “C” 表示编译器按照C的规则去翻译相应的函数名而不是C++的,二是声明函数或者全局变量的作用范围的关键字
相关的,
extern与static
1.extern和static不能修饰同一变量
2.声明static修饰的全局变量同时也就定义了它
3.static修饰全局变量的作用域只能是本身的编辑单元
extern与const
const单独使用时与static相同,与extern一起使用时与extern相同
static在C中的作用
static修饰局部变量
被修饰的变量成为静态变量,存储在静态区。存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁。
static修饰全局变量
被static修饰的全局变量只能被该包含该定义的文件访问。
static修饰函数
static修饰函数使得函数只能在包含该函数定义的文件中被调用
static在C++中的使用
static可以实现不同对象之间数据共享
volatile关键词
用法
int volatile i = 10;
该关键词表明被修饰的变量可能会被某些编译器不可未知的原因发生变化,因此阻止编译器对该变量的读取进行优化,每次读取该变量时,都从内存读取
volatile指针(多线程中常用)和 const 修饰的类型称为常类型,常类型不允许被更改
const修饰指针,以*号画线,在*之前的表示所指的类型是const,在*之后的表示指针本身是const
//修饰由指针指向的对象、数据是 const 或 volatile 的
const char* p;
volatile char* p;
// 指针自身是只读的
//本身的值是const或volatile的
char * const p;
char* volatile p;
static关键字
extern关键字
const关键字,const与#define的区别
const修饰成员函数
1.const修饰类的成员函数,则该成员函数不能调用类中任何非const成员函数
2.const成员函数能够访问对象的const成员,而其他成员函数不可以。
new返回的指针必须是const类型的
new与malloc的区别
new/delete
从堆上分配内存
是c++操作符
new可以用malloc实现,反过来不行
new会自己计算需要申请空间的大小
malloc/free
是库函数
从自由存储区分配内存
malloc可以使用realloc来方便的扩展内存,而new没有相关的方法
operator new/ 和operator delete
可以被重载,前者是申请内存,后者是释放内存,new/delete不能被更改
new[]和delete[]
new[]专门进行动态数组分配,用delete[]进行销毁。new[]会申请一次分配内存,然后逐一多次调用构造函数,;delete[]会逐一先多次调用析构函数,然后统一销毁一起释放
new 、operator new 和 placement new 区别
(1)new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。
new 操作符的执行过程:
1. 调用operator new分配内存 ;
2. 调用构造函数生成类对象;
3. 返回相应指针。
(2)operator new:要实现不同的内存分配行为,应该重载operator new,而不是new。
operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。
(3)placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。
覆盖与隐藏
基类函数没有virtual关键字时,只要子类中含有与该函数名称相同的函数,基类函数就会被隐藏。隐藏本质上是C++在命名空间内的查找顺序,先查找子类空间,因此父类的函数好像被隐藏了。
如果有virtual关键词,且子类函数名和参数与基类完全相同,才叫做覆盖。
编译时多态:通过重载函数实现;运行时多态:通过虚函数实现
纯虚函数表明基类没有实现,需要子类去实现自己的方法
含有纯虚函数的类为抽象类,抽象类的存在是为了规范类的行为,提高编码效率
动态绑定的实现
只有采用“指针->函数()”或“引用变量.函数()”的方式调用C++类中的虚函数才会执行动态绑定
虚函数表
每个类会持有一个自己的虚函数表,存在全局数据区(C++中内存分为栈、堆、代码区、全局数据区和文字常量区),虚函数表中记录了父类和自己的函数位置,父类虚函数在前,子类虚函数在后,如果子类覆盖了父类的虚函数,那么父类虚函数的位置会被直接用子类的虚函数位置替代,不管是单继承还是多继承。
构造函数与析构函数
指针和引用的区别
指针与数组千丝万缕的联系
int b[][3]
时++b
的步长是3*sizeof(int)智能指针的实现*
template<typename T>
class SmartPtr{
public:
SmartPtr(T *p);
~SmartPtr();
SmartPtr(const SmartPtr<T> &ori);
SmartPtr<T>& operator=(const SmartPtr<T> &rhs);
T & operator*()
{
return *_ptr;
}
T * operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _count;
};
template<typename T>
SmartPtr<T>::SmartPtr(T *p): _ptr(p)
{
try{
_count = new int(1);
}catch(...)
{
delete _ptr;
_ptr = nullptr;
_count = nullptr;
cout << "Allocate memory for use_count fails." << endl;
exit(1);
}
}
template<typename T>
SmartPtr<T>::~SmartPtr()
{
if(--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
}
}
template <typename T>
SmartPtr<T>::SmartPtr(SmartPtr<T> &ori)
{
_ptr = ori._ptr;
_count = ori.count;
++(*_count);
cout << "Copy Constructor is called." << endl;
}
/**
* 赋值操作中,需要先把右操作数的计数加1,防止自身赋值时,指针内容提前销毁
*/
template<typename T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs)
{
++(*rhs._ptr);
if(--(*_count)==0)
{
delete _ptr;
delete _count;
cout << "Left side object is deleted." << endl;
}
_ptr = rhs._ptr;
_count = rhs._count;
cout << "Assignment operator is called." << endl;
return *this;
}
share_ptr与weak_ptr,unique_ptr的区别
必须在构造函数初始化式里进行初始化的数据成员有哪些
1.类里面的任何成员变量在定义时是不能初始化的。
2.一般的数据成员可以在构造函数中初始化。
3.const数据成员必须在构造函数的初始化列表中初始化。
4.static要在类的定义外面初始化。
5.数组成员是不能在初始化列表里初始化的。
6.不能给数组指定明显的初始化。
类成员变量的初始化不是按照初始化表的顺序被初始化的,而是按照在类中声明的顺序被初始化的
STL里的内存池实现(allocator)
通常做法是申请一大块内存,当需要小块内存时,从这一大块中选取,否则向操作系统申请。该大块内存中用自由链表来表示不同的可用内存大小。
模板特化
函数模板只有全特化没有偏特化
template<typename T1, typename T2>
class T{
public:
T(T1 a, T2 b): t1(a), t2(b){cout << "模板类" << endl;}
private:
T1 t1;
T2 t2;
};
template<>
class T<int, char>{
public:
T(int a, char b):t1(a), t2(b){cout << "全特化" << endl;}
private:
int t1;
char t2;
};
template<typename T2>
class T<int, T2>
{
public:
T(int a, T2 b): t1(a), t2(b){cout << "偏特化" << endl;}
private:
int t1;
T2 t2;
}
函数模板只能全特化
内存泄漏定位
(1)在windows平台下通过CRT中的库函数进行检测;
(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置
(3)Linux下通过工具valgrind检测
手写 strcpy, strcmp, strcat, memcpy函数
strcpy
char* strcpy(char* dst, const char* src)
{
assert((dst != NULL) && (src != NULL));
const char* temp = dst;
while(*src != '\0')
*dst++ = *src++;
return temp;
}
strncpy
char* strncpy(char* dst, const char* src, size_t n)
{
size_t i = 0;
for(; i < n && src[i] != '\0'; ++i)
{
dst[i] = src[i];
}
for(; i < n; ++i)
dst[i] = '\0';
return dst;
}
strcat
char* strcat(char* dst, const char* src)
{
char* temp = dst;
while(*temp != '\0') temp ++;
while(*src != '\0')
*temp ++ = *src ++;
temp = '\0';
return dst;
}
strncat
char* strncat(char* dst, const char* src, size_t n)
{
size_t i, j;
for(j = 0; dst[j]!='\0'; ++j);
for(i = 0; i < n && src[i] != '\0';)
{
dst[j++] = src[i++];
}
for(; i <= n; ++i) dst[j] = '\0';
return dst;
}
strlen
int strlen(const char* s)
{
assert(s!=NULL);
int len = 0;
while((*s++) != '\0')
{
len++;
}
return len;
}
strcmp
int strcmp(const char* s1, const char* s2)
{
assert((s1 != NULL) && (s2 != NULL));
while((*s1) == (*s2))
{
if(*s1 == '\0') return 0;
++s1;++s2;
}
return *s1 - *s2;
}
memcpy
// 考虑src与dst重叠的问题
void* mymemcpy(void* dst, const void* src, size_t n)
{
if(NULL == src || NULL == dst)
return NULL;
char* dst_lay = (char*) dst;
char* src_lay = (char*) src;
int i; // 必须用int
if(dst_lay > src_lay && src_lay + n > dst_lay)
{
//src与dst重叠,从后往前拷贝
for(i = n - 1; i >= 0; --i)
{
dst_lay[i] = src_lay[i];
}
}else{
for(i = 0; i < n; ++i)
{
dst_lay[i] = src_lay[i];
}
}
return dst;
}
STL源码剖析笔记 - STL的Allocator
STL源码剖析笔记 - traits编程技法
https://www.jianshu.com/p/f11724034d50
协程是用户态的轻量级线程,协程由一个线程执行,所以共享资源不需要加锁,协程的调度由用户来完成,切换成本更低,协程是异步编程
进程和协程的选择
多核CPU,CPU密集型应用
此时多线程的效率是最高的,多线程可以使到全部CPU核心满载,又避免了协程间切换造成性能损失。当CPU密集型任务时,CPU一直在利用着,切换反而会造成性能损失,即便协程上下文切换消耗最小,但也还是有消耗的。
多核CPU,IO密集型应用
此时采用多线程多协程效率最高,多线程可以使到全部CPU核心满载,而一个线程多协程,则更好的提高了CPU的利用率。
单核CPU,CPU密集型应用
单进程效率是最高,此时单个进程已经使到CPU满载了。
单核CPU,IO密集型应用
多协程,效率最高。例如,看了上面应该也是知道的了
进程的内存结构
// ***创建线程
// tid 线程id指针
// attr 指定创建线程的属性
// func 线程中要执行的函数
// arg 线程中执行的函数的参数
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
// ***等待某个线程退出
// tid 要等待的线程id
// void ** status:如果不为NULL,那么线程的返回值存储在status指向的空间中(这就是为什么status是二级指针的原因!这种才参数也称为“值-结果”参数)。
int pthread_join (pthread_t tid, void ** status);
// *** 返回当前线程的id
pthread_t pthread_self (void);
// *** 将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源
int pthread_detach (pthread_t tid);
// ***用于终止线程,可以指定返回值,以便其他线程通过pthread_join函数获取该线程的返回值。
void pthread_exit (void *status);
互斥锁相关的函数
#include <pthread.h>
pthread_mutex_t mutex; //锁
//在对临界资源进行操作之前需要pthread_mutex_lock先加锁,操作完之后pthread_mutex_unlock再解锁。而且在这之前需要声明一个pthread_mutex_t类型的变量,用作前面两个函数的参数
int pthread_mutex_lock(pthread_mutex_t * mptr);
int pthread_mutex_unlock(pthread_mutex_t * mptr);
线程同步
在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段
在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使) 知道这些锁之间的区别,使用场景?
#include <pthread.h>
pthread_cond_t cond_t; // 条件变量
// 配合一个互斥锁,以免有多个线程竞争同一条件
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
//***pthread_cond_wait只是唤醒等待某个条件变量的一个线程。如果需要唤醒所有等待某个条件变量的线程,需要调用broadcast
int pthread_cond_broadcast (pthread_cond_t * cptr);
//***默认情况下面,阻塞的线程会一直等待,知道某个条件变量为真。如果想设置最大的阻塞时间可以调用:
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);
匿名管道与命名管道的区别:匿名管道只能在具有公共祖先的两个进程间使用。
key_t是什么,为什么有的key_t导致msgsnd阻塞
信号量与锁的区别
信号量、消息队列、管道的操作函数
共享文件映射mmap
mmap建立进程空间到文件的映射,在建立的时候并不直接将文件拷贝到物理内存,同样采用缺页中断。mmap映射一个具体的文件可以实现任意进程间共享内存,映射一个匿名文件,可以实现父子进程间共享内存。
mmap的效率
write()和read()都是系统调用,read为例,当需要读取磁盘文件时,向内核发出请求,内核先把磁盘数据读取到内核空间,再把内核空间数据读取到用户空间,需要两次拷贝。而mmap直接将用户空间映射到磁盘空间,读取数据时,内核根据这个映射关系,将数据直接从磁盘拷贝到用户空间,只有一次拷贝,效率更高。
常见的信号有哪些?:SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM
- 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
- 信号 ( signal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。
fork与vfork区别
fork和vfork都用于创建子进程。但是vfork创建子进程后,父进程阻塞,直到子进程调用exit()或者excle()。
对于内核中过程fork通过调用clone函数,然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体,然后复制其他关于内存,文件,寄存器等信息。fork采用写时拷贝技术,因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表,因为父进程会一直阻塞,直接使用父进程页表。
exit()与_exit()区别
exit()清理后进入内核,_exit()直接陷入内核。
孤儿进程与僵死进程
死锁
https://blog.csdn.net/carry1314lele/article/details/2649572
软连接存储了被连接文件的目录,相当于指针;硬链接指向被连接文件的地址,相当于别名。
管道与重定向
grep*(examples)
find
LRU最近最久未使用
//list 实现 https://www.cnblogs.com/dolphin0520/p/3741519.html
// map<int, list<int>::iterator> 存储key到list指针的映射,方便使用find方法
// 也可以在Node{int k, value;}中实现operator==,然后用std::find()进行查找
LFU最近最少使用
https://www.jianshu.com/p/ef892323e68f
三次握手:
client --------------------------- server
| |
SYN_SEND | -----------SYN------------> | SYN_RCVD
ESTB | <=========-SYN &ACK======== |
| -----------ACK------------> | ESTB
三次握手分别是 SYN, SYN&ACK, s
四次挥手
client --------------------------- server
| |
FIN_WAIT1 | -----------FIN------------> | CLOSE_WAIT
FIN_WAIT2 | <==========ACK=========== |
. ......... .
| <========data ACKn======= |
TIME_WAIT | <========FIN============= | LAST_ACK
(2MSL) | -----------ACK------------> | CLOSE
TIME_WAIT也叫2MSL等待状态
四次挥手分别是 client FIN, ACK, server FIN, ACK
三次握手原因:server端的syn和ack作为一个包发送,所以是三次握手。
而断开连接时,server对fin确认之后,通常还会发送一些数据,因此server端的fin和ack是分开发送的,因此是四次。
https://yq.aliyun.com/articles/609071
1.0需要每个链接都建立TCP链接,1.1不需要
HTTP 是一种无状态的连接,客户端每次读取 web页面时,服务器都会认为这是一次新的会话。但有时候我们又需要持久保持某些信息,比如登录时的用户名、密码,用户上一次连接时的信息等。这些信息就由 Cookie 和 Session 保存。
Cookie
cookie实际上是一小段文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个cookie,客户端浏览器会把cookie保存起来,当浏览器再次请求访问该网站时,浏览器把请求的网站连同该cookie一同提交给服务器,服务器检查该cookie,以此来辨认用户状态。
简单来说,cookie的工作原理可总结如下:client连接server
client生成cookie(有效期),再次访问时携带cookie
server根据cookie的信息识别用户身份
Session
Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些。同一个客户端每次和服务端交互时,不需要每次都传回所有的 Cookie 值,而是只要传回一个 ID,这个 ID 是客户端第一次访问服务器的时候生成的,而且每个客户端是唯一的。这样每个客户端就有了一个唯一的 ID,客户端只要传回这个 ID 就行了,这个 ID 通常是 name为 JSESIONID 的一个 Cookie。Session依据这个id来识别是否为同一用户(只认ID不认人)。
区别:
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
列不可分
非主属性完全依赖于主属性,不能只依赖主属性的一部分,如果有依赖主属性一部分的情况,那么应当将多余的部分重新组成新的实体,防止冗余
非主属性不能包含其他实体中非主属性已经出现的属性,避免冗余
主属性中的一部分依赖于主属性的另一部分,应当避免
https://www.cnblogs.com/wangdake-qq/p/7358322.html
SQL语句
索引
https://www.cnblogs.com/heiming/p/5865101.html
实例 https://blog.csdn.net/whoamiyang/article/details/51901888
用processlist kill进程解决mysql死锁
其它关于查看死锁的命令:
1:查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
MVCC只能在可重复读和可提交读的隔离级别下生效。不可提交读不能使用它的原因是不能读取符合事物版本的行版本。它们总是读取最新的行版本。可序列化不能使用MVCC的原因是,它总是要锁定行。
树的增删改查
https://blog.csdn.net/puqutogether/article/details/41805083
https://www.cnblogs.com/upcwanghaibo/p/6628240.html
// 1.闭区间
// 取值的范围是[left,right],一定要保证每次循环结束后left+1或者right-1,结束的状态left>right,left在右边,right在左边,目标值下标确定是left。
int binarySearch(vector<int>& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size() - 1;
while(left <= right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid - 1; }
}
// End Condition: left > right
return -1;
}
//2 开区间
// 取值范围是[left,right),right无法得到,它有两种形式left+1<right和left<right.
// 当是left+1<right这种形式时,left和right都不需要加一或者减一,结束状态是left<right,无法确定最后的目标值下标是left还是right,最后还需要做一次判断。
int binarySearch(vector<int>& nums, int target){
if(nums.size() == 0)
return -1;
int left = 0, right = nums.size();
while(left < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid; }
}
// Post-processing:
// End Condition: left == right
if(left != nums.size() && nums[left] == target) return left;
return -1;
}
//3
int binarySearch(vector<int>& nums, int target){
if (nums.size() == 0)
return -1;
int left = 0, right = nums.size() - 1;
while (left + 1 < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid;
} else {
right = mid;
}
}
// Post-processing:
// End Condition: left + 1 == right
if(nums[left] == target) return left;
if(nums[right] == target) return right;
return -1;
}