金亨俊整容(10)既然有了malloc/free,C++中为什么还需要new/delete呢?
(12)new和 new[] / delete和delete[]的区别
(14)宏定义(#define)和const定义的联系与区别(编译阶段、安全性、内存占用等)
(16) C++的STL介绍(这个系列也很重要),包括内存管理,函数,实现机理,多线) STL源码中的hash表的实现
(26) C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(热门,重要)
(41) 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?
(51) STL中的sort()算法是用什么实现的,stable_sort()呢
③ C++需要事先定义变量的类型,而Python不需要。Python的基本类型只有:数字,布尔值,字符串, 列表,元组,字典等。
我们都知道C面向过程,C++面向对象,说两者的区别其实就是比较面向过程和面向对象的区别
③ 面向对象不会关心具体的过程,关心的是有哪些对象,对象如何来进行交互,更符合对事物的认知过程,适合大型的工程或者复杂的问题,可扩展性/维护性比较友好
④ 面向对象就是高度实物抽象化(功能划分)、面向过程就是自顶向下的编程(步骤划分)
自动类型推导auto:auto的自动类型推导用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作
lambda(匿名函数):它可以用于创建并定义匿名的函数对象,以简化编程工作。
形参在函数未调用之前都是没有分配存储空间的,在函数调用结束之后,形参弹出栈空间,清除形参空间。
数组作为参数的函数调用方式是地址传递,形参和实参都指向相同的内存空间,调用完成后,形参指针被销毁,但是所指向的内存空间依然存在,不能也不会被销毁。
当函数有多个返回值的时候,不能用普通的 return 的方式实现,需要通过传回地址的形式进行,即地址/指针传递
定义就是对这个变量和函数进行内存分配和初始化。需要分配空间,同一个变量可以被声明多次,但是只能被定义一次
C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class的成员默认访问方式是private。
使用struct时,它的限默认访问权限和继承方式都是public的,而class默认继承和访问方式则都是private的
(1)管理方式不同,栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有 2 种分配方式:静态分配和动态分配。
,栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
栈是一种提供后进先出存储方式的线性表,只允许在表的一端操作数据,存取其他项很慢。在输入数据规模可预知的情况下使用数组实现的栈叫顺序栈,未知就用链表实现叫链栈,并且数组实现栈的效率更高。但是顺序栈创建大小不当会造成栈溢出。
堆是一种常用的树形结构,它总是一棵完全二叉树,堆中任意节点总是不大于或不小于其子节点的值。它插入删除较快,对最值的存取极快,对其他项存取很慢
(10)既然有了malloc/free,C++中为什么还需要new/delete呢?
malloc / free 和 new / delete都是用来动态申请内存和回收内存的。
首先malloc / free是库函数,new / delete是操作符
在对非内置类型的对象使用的时候,对象创建的时候需要执行构造函数进行初始化操作,销毁的时候要执行析构函数。而malloc/free是库函数,创建和释放空间的时候不会调用构造和析构函数。
在申请自定义类型的空间时,new申请空间会调用构造函数对空间进行初始化,delete释放空间会调用析构函数,而malloc与free不会
malloc申请出来的不是一个自定义类型的对象,而是与该类型大小相同的一段空间
(12)new和 new[] / delete和delete[]的区别
在C++中,被const修饰的已经是常量,而且具有宏替换属性(但是替换实际在程序编译时)
define定义的常量没有类型,只是进行了简单的替换,可能会有多个拷贝,占用的内存空间大,const定义的常量是有类型的,存放在静态存储区,只有一个拷贝,占用的内存空间小。
define定义的常量是在预处理阶段进行替换,而const在编译阶段确定它的值。
define不会进行类型检测,安全性低,而const会进行类型安全检查,安全性更高。
static修饰函数,改变函数链接属性,表明:该函数只能在当前文件中进行使用,不能被其他文件调用。
类:而在类中,被static修饰的成员变量或成员函数是类静态成员,静态成员变量在类中声明,类外定义。静态成员不属于某个具体的对象,被类的所有对象共用。通过类名::静态成员变量名字(Date::name)来进行访问,静态成员存放在静态存储区,不占用类的大小, 且静态成员函数无法访问非静态成员变量,非静态成员函数(但是非静态成员函数可以访问静态成员函数),不能使用const修饰(const修饰的是this指针)
(16) C++的STL介绍(这个系列也很重要),包括内存管理,函数,实现机理,多线程实现等
STL大体分为6大组件:算法,容器,迭代器,仿函数,适配器,空间配置器。
线性探测法:该元素的哈希值对应的桶不能存放元素时,循序往后一 一查找,直到找到一个空桶为止,在查找时也一样,当哈希值对应位置上的元素与所要寻找的元素不同时,就往后一 一查找,直到找到吻合的元素,或者空桶。(PS:要求tableSize一定要大于dataSize,不然哈希表上就没有空置的位置来存放冲突的数据了)
。不过与动态数组不同的是,vector可以根据需求,自动扩大容器的大小。具体策略是每次容量不够用时重新申请一块大小为原来容量两倍的内存,并将原容器的元素拷贝至新容器,并释放原空间,返回新空间的指针。
重载(overload)是指函数名相同,参数列表不同的函数实现方法。它们的返回值可以不同,但返回值不可以作为区分不同重载函数的标志。
重写(overwide)是指函数名相同,参数列表相同,只有方法体不相同的实现方法。一般用于子类继承父类时对父类方法的重写。子类的同名方法屏蔽了父类方法的现象称为隐藏。
在C++中,内存分成5个区,他们分别是堆、栈、全局/静态存储区和常量存储区和代码区。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配效率很高,但是分配的内存容量有限。
堆:就是那些由new分配的内存块,由程序员手动开辟与释放,如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
全局/静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据(局部static变量,全局static变量)、全局变量和常量。
C++中的构造函数主要有三种类型:默认构造函数、重载构造函数和拷贝构造函数
因为结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来。
动态开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。
③ 没有将父类的析构函数定义为虚函数,当父类指针指向子类对象时,如果父类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有得到正确释放,因此造成内存泄露
C++中的智能指针有auto_ptr,unique_ptr, shared_ptr, weak_ptr。智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存造成内存泄漏。
auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并且将旧指针指向空,但是这种方式在需要访问旧指针的时候,就会出现问题。
unique_ptr是auto_ptr的一个改良版,不能赋值也不能拷贝,保证一个对象同一时间只有一个智能指针。
shared_ptr可以使得一个对象可以有多个智能指针,当这个对象所有的智能指针被销毁时就会自动进行回收。(内部使用计数机制进行维护)
weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。
inline是内联的意思,可以定义比较小的函数。因为函数频繁调用会占用很多的栈空间,进行入栈出栈操作也耗费计算资源,所以可以用inline关键字修饰频繁调用的小函数。
3、在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
4、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。
5、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。
成员初始化列表就是在类或者结构体的构造函数中,在参数列表后以冒号开头,逗号进行分隔的一系列初始化字段。如下:
用于各种隐式转换。具体的说,就是用户各种基本数据类型之间的转换,比如把int换成char,float换成int等。以及派生类(子类)的指针转换成基类(父类)指针的转换。
在子类指针转换到父类指针时,是没有任何问题的,在父类指针转换到子类指针的时候,会有安全问题。
用于动态类型转换。具体的说,就是父类到子类指针,或者子类到父类指针的转换。dynamic_cast能够提供运行时类型检查,只用于含有虚函数的类,如果不能转换会返回NULL。
用于去除const常量属性,使其可以修改 ,也就是说,原本定义为const的变量在定义后就不能进行修改的,但是使用const_cast操作之后,可以通过这个指针或变量进行修改; 另外还有volatile属性的转换。
几乎什么都可以转,用在任意的指针之间的转换,引用之间的转换,指针和足够大的int型之间的转换,整数到指针的转换等。但是不够安全。
string,其实是对char* 进行了封装,封装的string包含了char* 数组,容量,长度等属性。
string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间,然后将原字符串拷贝过去,并加上新增的内容。
不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。
2)提高函数调用和运行的效率(因为没有了传值和生成副本的时间和空间消耗)
友元提供了不同类的成员函数之间,或和一般函数之间进行数据共享的机制。通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元。
volatile的意思是“脆弱的”,表明它修饰的变量的值十分容易被改变,所以编译器就不会对这个变量进行优化(CPU的优化是让该变量存放到CPU寄存器而不是内存),进而提供稳定的访问。每次读取volatile的变量时,系统总是会从内存中读取这个变量,并且将它的值立刻保存。
STL中的sort是用快速排序和插入排序结合的方式实现的,stable_sort()是归并排序。
会, 当vector在插入的时候,如果原来的空间不够,会将申请新的内存并将原来的元素移动到新的内存,此时指向原内存地址的迭代器就失效了,first和end迭代器都失效
当vector在删除的时候,被删除元素以及它后面的所有元素迭代器都失效。
首先,实现一个垃圾回收器会带来额外的空间和时间开销。你需要开辟一定的空间保存指针的引用计数和对他们进行标记mark。然后需要单独开辟一个线程在空闲的时候进行free操作。
|