1.字符串的末尾'\0'也算一个字符,一个字节。c++
2.使用库函数strcpy(a,b)进行拷贝b->a操做,strcpy会从源地址一直日后拷贝,直到遇到'\0'为止。因此拷贝的长度是不定的。若是一直没有遇到'\0'致使越界访问非法内存,程序就崩了。git
3.strlen的结果未统计’\0’所占用的1个字节。 程序员
4.写出完整的strcpy函数算法
char * strcpy( char *strDest, const char *strSrc ) { assert( (strDest != NULL) && (strSrc != NULL) ); char *address = strDest; while( (*strDest++ = * strSrc++) != ‘\0’ ); return address; }
5.malloc和free要对应使用,防止内存泄漏编程
6.printf(str) 改成 printf("%s",str),不然可以使用格式化 字符串攻击。printf会把str当成一个格式化字符串(formatstring),printf在其中寻找特殊的格式字符好比"%d"。若是碰到格式字符,一个变量的参数值就从堆栈中取出。很明显,攻击者至少能够经过打印出堆栈中的这些值来偷看程序的内存。设计模式
7.malloc以后应该对内存分配是否成功进行判断。free(p)以后应该把p=NULL,不然p会变成野指针。api
8.浮点型变量并不精确,因此不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。数组
9.数组名的本质以下:
(1)数组名指代一种数据结构,这种数据结构就是数组; 安全
char str[10]; cout << sizeof(str) << endl;
输出结果为10,str指代数据结构char[10]。
(2)数组名能够转换为指向其指代实体的指针,并且是一个指针常量,不能做自增、自减等操做,不能被修改;
char str[10];
str++; //编译出错,提示str不是左值 数据结构
C++是面向对象的语言,而C是面向过程的结构化编程语言
语法上:
C++具备重载、继承和多态三种特性
C++相比C,增长多许多类型安全的功能,好比强制类型转换、
C++支持范式编程,好比模板类、函数模板等
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
3.指针能够被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
4.做为参数传递时,指针须要被解引用才能够对对象进行操做,而直接对引 用的修改都会改变引用所指向的对象;
5.能够有const指针,可是没有const引用;
6.指针在使用中能够指向其它对象,可是引用只能是一个对象的引用,不能 被改变;
7.指针能够有多级指针(**p),而引用至于一级;
8.指针和引用使用++运算符的意义不同;
9.若是返回动态内存分配的对象或者内存,必须使用指针,引用可能引发内存泄露。
1. auto_ptr(c++98的方案,cpp11已经抛弃)
采用全部权模式。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”)); auto_ptr<string> p2; p2 = p1; //auto_ptr不会报错.
此时不会报错,p2剥夺了p1的全部权,可是当程序运行时访问p1将会报错。因此auto_ptr的缺点是:存在潜在的内存崩溃问题!
2. unique_ptr(替换auto_ptr)
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针能够指向该对象。它对于避免资源泄露(例如“以new建立对象后由于发生异常而忘记调用delete”)特别有用。
采用全部权模式,仍是上面那个例子
unique_ptr<string> p3 (new string ("auto")); //#4 unique_ptr<string> p4; //#5 p4 = p3;//此时会报错!!
若是确实想执行相似的操做,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你可以将一个unique_ptr赋给另外一个。例如:
unique_ptr<string> ps1, ps2; ps1 = demo("hello"); ps2 = move(ps1); ps1 = demo("alexia"); cout << *ps2 << *ps1 << endl;
3. shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针能够指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就能够看出了资源能够被多个指针共享,它使用计数机制来代表资源被几个指针共享。能够经过成员函数use_count()来查看资源的全部者个数。除了能够经过new来构造,还能够经过传入auto_ptr, unique_ptr,weak_ptr来构造。当咱们调用release()时,当前指针会释放资源全部权,计数减一。当计数等于0时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象全部权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了能够共享全部权的智能指针。
成员函数:
use_count 返回引用计数的个数
unique 返回是不是独占全部权( use_count 为 1)
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的全部权或拥有对象的变动, 会引发原有对象的引用计数的减小
get 返回内部对象(指针), 因为已经重载了()方法, 所以和直接使用对象是同样的.如 shared_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的
4. weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工做, 它只能够从一个 shared_ptr 或另外一个 weak_ptr 对象构造, 它的构造和析构不会引发引用记数的增长或减小。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,若是说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能降低为0,资源永远不会释放。它是对对象的一种弱引用,不会增长对象的引用计数,和shared_ptr之间能够相互转化,shared_ptr能够直接赋值给它,它能够经过调用lock函数来得到shared_ptr。
注意的是咱们不能经过weak_ptr直接访问对象的方法,好比B对象中有一个方法print(),咱们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
指针 |
数组 |
保存数据的地址 |
保存数据 |
间接访问数据,首先得到指针的内容,而后将其做为地址,从该地址中提取数据 |
直接访问数据, |
一般用于动态的数据结构 |
一般用于固定数目且数据类型相同的元素 |
经过Malloc分配内存,free释放内存 |
隐式的分配和删除 |
一般指向匿名数据,操做匿名函数 |
自身即为数据名 |
虚表是属于类的,而不是属于某个具体的对象,一个类只须要一个虚表便可。同一个类的全部对象都使用同一个虚表。
为了指定对象的虚表,对象内部包含一个虚表的指针,来指向本身所使用的虚表。为了让每一个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在建立时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。
多继承状况下,派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖全部虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。
类析构顺序:1)派生类自己的析构函数;2)对象成员析构函数;3)基类析构函数。
C++默认的析构函数不是虚函数是由于虚函数须要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来讲,其析构函数若是是虚函数,就会浪费内存。所以C++默认的析构函数不是虚函数,而是只有当须要看成父类时,设置为虚函数。
函数指针是指向函数的指针变量。
函数指针自己首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组同样,这里是指向函数。
C在编译时,每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其余类型变量同样,在这些概念上是大致一致的。
二、用途:
调用函数和作函数的参数,好比回调函数。
三、示例:
char * fun(char * p) {…} // 函数fun char * (*pf)(char * p); // 函数指针pf pf = fun; // 函数指针pf指向函数fun pf(p); // 经过函数指针pf调用函数fun
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
成功调用fork( )会建立一个新的进程,它几乎与调用fork( )的进程如出一辙,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。若是出现错误,fork( )返回一个负值。
new运算分两个阶段:(1)调用::operator new配置内存;(2)调用对象构造函数构造对象内容
delete运算分两个阶段:(1)调用对象希构函数;(2)掉员工::operator delete释放内存
为了精密分工,STL allocator将两个阶段操做区分开来:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责;对象构造由::construct()负责,对象析构由::destroy()负责。
同时为了提高内存管理的效率,减小申请小内存形成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,经过空闲链表来管理内存。
另外,class还能够定义模板类形参,好比template <class T, int i>。
//公有继承 对象访问 成员访问 public --> public Y Y protected --> protected N Y private --> private N N //保护继承 对象访问 成员访问 public --> protected N Y protected --> protected N Y private --> protected N N //私有继承 对象访问 成员访问 public --> private N Y protected --> private N Y private --> private N N
预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
编译阶段:将通过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
连接阶段:将多个目标文件及所须要的库链接成最终的可执行目标文件
Include头文件的顺序:对于include的头文件来讲,若是在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,而且要先引用b.h,后引用a.h,不然汇报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不同。
对于使用双引号包含的头文件,查找头文件路径的顺序为:
当前头文件目录
编译器设置的头文件路径(编译器可以使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
对于使用尖括号包含的头文件,查找头文件的路径顺序为:
编译器设置的头文件路径(编译器可以使用-I显式指定搜索路径)
系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径
Malloc函数用于动态分配内存。为了减小内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存做为堆区,而后将堆区分为多个内存块,以块做为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分红连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理全部的空闲块,即便用一个双向链表将空闲块链接起来,每个空闲块记录了一个连续的、未分配的地址。
当进行内存分配时,Malloc会经过隐式链表遍历全部的空闲块,选择知足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,根据每一个块的先后块是否已经分配来决定是否进行块合并。
Malloc在申请内存时,通常会经过brk或者mmap系统调用进行申请。其中当申请内存小于128K时,会使用系统函数brk在堆区中分配;而当申请内存大于128K时,会使用系统函数mmap在映射区分配。
在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、文件映射区以及栈区六部分。
代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
数据段:存储程序中已初始化的全局变量和静态变量
bss 段:存储未初始化的全局变量和静态变量(局部+全局),以及全部被初始化为0的全局变量和静态变量。
堆区:调用new/malloc函数时在堆区动态分配内存,同时须要调用delete/free来手动释放申请的内存。
映射区:存储动态连接库以及调用mmap函数进行的文件映射
栈:使用栈空间存储函数的返回地址、参数、局部变量、返回值
C++/C的内存分配
使用野指针
试图修改字符串常量的内容
内存泄漏的分类:
1. 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据须要分配经过malloc,realloc new等从堆中分配的一块内存,再是完成后必须经过调用对应的 free或者delete 删掉。若是程序的设计的错误致使这部份内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
2. 系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源好比 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,致使系统资源的浪费,严重可致使系统效能下降,系统运行不稳定。
3. 没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,若是基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,所以形成内存泄露。
二、new返回的是指定对象的指针,而malloc返回的是void*,所以malloc的返回值通常都须要进行类型转化。
三、new不只分配一段内存,并且会调用构造函数,malloc不会。
四、new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。
五、new是一个操做符能够重载,malloc是一个库函数。
六、malloc分配的内存不够的时候,能够用realloc扩容。扩容的原理?new没用这样操做。
七、new若是分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。
八、申请数组时: new[]一次分配全部内存,屡次调用构造函数,搭配使用delete[],delete[]屡次调用析构函数,销毁数组中的每一个对象。而malloc则只能sizeof(int) * n。
1)新建共享内存shmget
int shmget(key_t key,size_t size,int shmflg);
key:共享内存键值,能够理解为共享内存的惟一性标记。
size:共享内存大小
shmflag:建立进程和其余进程的读写权限标识。
返回值:相应的共享内存标识符,失败返回-1
2)链接共享内存到当前进程的地址空间shmat
void *shmat(int shm_id,const void *shm_addr,int shmflg);
shm_id:共享内存标识符
shm_addr:指定共享内存链接到当前进程的地址,一般为0,表示由系统来选择。
shmflg:标志位
返回值:指向共享内存第一个字节的指针,失败返回-1
3)当前进程分离共享内存shmdt
int shmdt(const void *shmaddr);
4)控制共享内存shmctl
和信号量的semctl函数相似,控制共享内存
int shmctl(int shm_id,int command,struct shmid_ds *buf);
shm_id:共享内存标识符
command: 有三个值
IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中。
IPC_SET:设置共享内存的状态,把buf复制到共享内存的shmid_ds结构。
IPC_RMID:删除共享内存
buf:共享内存管理结构体。
平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。一句话表述为:以树中全部结点为根的树的左右子树高度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的全部结点的平衡因子只多是-一、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
红黑树:
红黑树是一种二叉查找树,但在每一个节点增长一个存储位表示节点的颜色,能够是红或黑(非红即黑)。经过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,所以,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来讲,它的旋转次数少,因此对于搜索,插入,删除操做较多的状况下,一般使用红黑树。
性质:
1. 每一个节点非红即黑
2. 根节点是黑的;
3. 每一个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的;
4. 若是一个节点是红色的,则它的子节点必须是黑色的。
5. 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
区别:
AVL 树是高度平衡的,频繁的插入和删除,会引发频繁的rebalance,致使效率降低;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。
红黑树较AVL树的优势:
AVL 树是高度平衡的,频繁的插入和删除,会引发频繁的rebalance,致使效率降低;红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。
因此红黑树在查找,插入删除的性能都是O(logn),且性能稳定,因此STL里面不少结构包括map底层实现都是使用的红黑树。
须要无序容器,快速查找删除,不担忧略高的内存时用unordered_map;有序容器稳定查找删除效率,内存很在乎时候用map。
对于map,其底层是基于红黑树实现的,优势以下:
1)有序性,这是map结构最大的优势,其元素的有序性在不少应用中都会简化不少的操做
2)map的查找、删除、增长等一系列操做时间复杂度稳定,都为logn
缺点以下:
1)查找、删除、增长等操做平均时间复杂度较慢,与n相关
对于unordered_map来讲,其底层是一个哈希表,优势以下:
查找、删除、添加的速度快,时间复杂度为常数级O(c)
缺点以下:
由于unordered_map内部基于哈希表,以(key,value)对的形式存储,所以空间占用率高
Unordered_map的查找、删除、添加的时间复杂度不稳定,平均为O(c),取决于哈希函数。极端状况下可能为O(n)
堆是由低地址向高地址扩展;栈是由高地址向低地址扩展
堆中的内存须要手动申请和手动释放;栈中内存是由OS自动申请和自动释放,存放着参数、局部变量等内存
堆中频繁调用malloc和free,会产生内存碎片,下降程序效率;而栈因为其先进后出的特性,不会产生内存碎片
堆的分配效率较低,而栈的分配效率较高
栈的效率高的缘由:
栈是操做系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行;而堆是由C/C++函数库提供的,机制复杂,须要一些列分配内存、合并内存和释放内存的算法,所以效率较低。
栈由系统自动分配和管理,堆由程序员手动分配和管理。
2)效率:
栈由系统分配,速度快,不会有内存碎片。
堆由程序员分配,速度较慢,可能因为操做不当产生内存碎片。
3)扩展方向
栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。
4)程序局部变量是使用的栈空间,new/malloc动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。
数组的优势:
1. 随机访问性强
2. 查找速度快
数组的缺点:
1. 插入和删除效率低
2. 可能浪费内存
3. 内存空间要求高,必须有足够的连续内存空间。
4. 数组大小固定,不能动态拓展
链表的优势:
1. 插入删除速度快
2. 内存利用率高,不会浪费内存
3. 大小没有固定,拓展很灵活。
链表的缺点:
不能随机查找,必须从第一个开始遍历,查找效率低
开放定址法: 当发生地址冲突时,按照某种方法继续探测哈希表中的其余存储单元,直到找到空位置为止。
再哈希法:当发生哈希冲突时使用另外一个哈希函数计算地址值,直到冲突再也不发生。这种方法不易产生汇集,可是增长计算时间,同时须要准备许多哈希函数。
链地址法:将全部哈希值相同的Key经过链表存储。key按顺序插入到链表中
创建公共溢出区:采用一个溢出表存储产生冲突的关键字。若是公共溢出区还产生冲突,再采用处理冲突方法处理。
最长公共子串长度就为max{c[i][j]}了