(1) 指针和引用的区别.html
语法上:指针和引用没有关系,引用就是一个已经存在的对象的别名。对引用的任何操做等价于对被引用对象的操做。c++
1.当引用被建立时,它必须被初始化。而指针则能够在任什么时候候被初始化。未初始化的引用不合法,未初始化的指针合法但危险。(悬空指针)程序员
2.一旦一个引用被初始化为指向一个对象,它就不能被改变为对另外一个对象的引用。而指针则能够在任什么时候候指向另外一个对象。web
3.不可能有NULL引用。必须确保引用是和一块合法的存储单元关联。由于不存在指向空值的引用,因此在使用引用以前不需验证它的合法性,而使用指针须要验证合法性。因此使用引用的代码效率要比使用指针的要高。redis
底层实现上(汇编层):引用是经过指针实现的。在程序一层只要直接涉及对引用变量的操做,操做的老是被引用变量,编译器实现了一些操做,老是在引用前面加上*。实际上如int a=0;int &b=a;中变量b中存放的是a的地址,int*const b=&a;但编译器让对b的操做都自动为*b,算法
指针的大小:在32位系统中是4字节,在64位系统中是8字节。由于指针指示的是一个内存地址,因此与操做系统有关。但这个也不是绝对正确的,由于64位系统兼容32位,对应的32程序的指针也是32位的,此时使用sizeof()获得的即是4(即32位),例如编写win32程序时,指针就是32位。数据库
(2) extern,const,static,volatile关键字(定义,用途)express
extern关键字的做用:编程
1、extern用在变量或者函数的声明前,用来讲明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。也就是说,在一个文件中定义了变量和函数, 在其余文件中要使用它们, 能够有两种方式:1.使用头文件,在头文件中声明它们,而后其余文件去包含头文件;2.在其余文件中直接extern,就可使用。windows
2、extern C做用:extern “C” 不但具备上述传统的声明外部变量的功能,还具备告知C++连接器使用C函数规范来连接的功能。 还具备告知C++编译器使用C规范来命名的功能。(由于C++支持函数的重载,C++编译器若是以C++规范翻译这个函数名时会把函数名翻译得很乱。)
static关键字的做用:
1、函数体内static变量的做用范围为该函数体,该变量的内存只被分配一次,其值在下次调用的时候仍然维持原始值,要是函数体内有对该变量进行更改的行为,再次访问时变量的值是更改后的值。
2、 在文件内的static全局变量和static全局函数能够被文件内可见,不能被其余文件可见。其余文件内能够有相同名字的其余的对象和函数,即文件范围的static能够限定变量在在文件范围内部,对其余文件不可见。而非static全局变量和全局函数能够在文件间使用。
3、在类中的static成员变量属于整个类全部,对类的全部对象只有一份拷贝。存储在静态存储区。静态数据成员能够被初始化,初始化在类体外进行,而前面不加static,以避免与通常静态变量或对象相混淆;若未对静态数据成员赋初值,则编译器会自动为其初始化为0。全局变量和静态变量存储在静态数据区,在全局静态数据区,内存中全部的字节默认值都是0x00。
4、在类中的static成员函数属于整个类全部,static成员函数不接受this指针,没有this指针,于是只能访问类的static成员变量和static成员函数。不能做为虚函数。
不能将静态成员函数定义为虚函数:虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中初始化,而且只能用this指针来访问它,由于它是类的一个成员,而且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,因此没法访问vptr. 这就是为什么static函数不能为virtual。
虚函数的调用关系:this -> vptr -> vtable ->virtual function
const关键字的做用:const意味着“只读”, const离谁近,谁就不能被修改;
1、想要阻止一个变量被改变,可使用const关键字。在定义该const关键字时,一般要对它进行初始化,由于之后再也没有机会去改变它。
2、对于指针来讲,能够指定指针自己为const,也能够指定指针所指向的数据为const,或者两者同时指定为const。
3、在一个函数声明中,const能够修饰形参,代表它是一个输入参数,在函数内部不能改变其值。若是形参是一个指针,为了防止在函数内部修改指针指向的数据,就能够用 const 来限制。
4、对于类的成员函数,若指定为const,则代表其实一个常函数,只有类的成员函数有常函数的说法,不能修改类的非静态成员变量。当肯定类成员函数不会改变成员变量时,必定将其设为const的;类的const的对象只能调用其const成员函数,由于调用非const函数就有改变变量属性的风险。
5、对于类的成员函数,有时候必须制定其返回值为const,以使得其返回值不能为左值。效率考虑,参数传递,返回值尽可能返回const&,除了必须值返回(返回的是一个函数内的临时对象,离开做用域对象清除,此时不能引用返回,必须值返回。)和可变引用&(如对象的操做符重载,须要连续赋值的状况,或cout的状况,必须使用可变引用)
6. const修饰成员变量,必须在构造函数列表中初始化;同时成员数据为引用的也必须在构造函数列表中初始化;static成员数据的初始化,放在类定义外,不加static,若static成员数据没有初始化,则默认为0;
volatile关键字的做用:
volatile int iNum = 10;
volatile 指出 iNum 是随时可能发生编译器觉察不到的变化的变量,变量可能被某些编译器未知的因素(好比:操做系统、硬件或者其它线程等更改),编译器觉察不到。
程序执行中每次使用它的时候必须从原始内存地址中去读取,于是编译器生成的汇编代码会从新从iNum的原始内存地址中去读取数据。而不是只要编译器发现iNum的值没有发生变化(由于多是已经发生了变化编译器觉察不到),就只读取一次数据,并放入寄存器中,下次直接从寄存器中去取值(优化作法),而是从新从内存中去读取(再也不优化).
(3) #define 和const的区别(编译阶段、安全性、内存占用等)
1编译器处理方式不一样
define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
2 类型和安全检查不一样
define宏没有类型,不作任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。
3 存储方式不一样
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(能够是堆中也能够是栈中)
(4)关于typedef和#define;
typedef 定义一种类型的别名,不一样于宏,它不是简单的字符串替换。如:
typedef void (*pFunParam)(int); pFunx b[10]; 定义了一个函数指针类型的数组,该函数指针指向的函数原型void fun(int)的函数实体
typedef 与 #define的区别案例:
STL中经过将typedef 写在类内部和模板的泛化偏特化,特别针对指针类型实现迭代器的特性萃取。 struct里边写typedef int Aa并不会使得 对象的空间增大。
(5)C++程序中内存使用状况分析(堆和栈的区别)
C++中,内存分红5个区,他们分别是栈、堆、自由存储区(能够和堆不区分)、全局/静态存储区,常量存储区。一个由C/C++编译的程序占用的内存分为如下几个部分 :
栈(堆栈):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操做方式相似于数据结构中的栈。 。
堆: 通常由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配组织方式却是相似于链表。经常使用C++的new/delete 运算符C的malloc()/free(),realloc等函数;
全局区(静态区)(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另外一块区域。 程序结束后有系统释放 。函数和成员函数代码也存储在静态区。
常量区:存放常量,字符串,保证常量不被修改; 程序结束后由系统释放 。
程序代码区:存放函数体的二进制代码。静态区。
(6)new 与 malloc的异同处,new和delete是如何实现的。
new的实现过程:
1.调用相应的 operator new(size_t) 函数,若是 operator new(size_t) 不能成功得到内存,则调用 new_handler() 函数用于处理new失败问题。能够用set_ new_handler()函数设置不一样的new_handler() 函数实现不一样的内存分配失败时的处理策略。operator new(size_t) 函数能够重载,可是必须包含size_t参数,不一样的重载形式,对应到不一样形式的new,如placement_new。 operator new的内存分配底层实现调用的也是malloc()函数。
2.在分配到的动态内存块上 调用相应类型的构造函数构造对象并返回其首地址。若是构造函数调用失败。则自动调用operate new对应的operator delete;释放内存。
new包含的分配内存和构造对象两个过程必须都要完成。
delete的实现过程:
1,先调用对应内存上对象的析构函数、2调用相应的 operator delete(void *) 函数。 operator delete(void *)也是调用free()释放内存。
new 与 malloc的异同处:1,new/delete属于运算符,malloc/free属于库函数。2.malloc在申请内存空间失败之后会返回一个null指针,而new在申请内存空间失败之后会返回一个异常。也可使用nothrow让new失败返回空指针,照顾c程序员的编程习惯。3.malloc只负责申请内存,他不能对内存进行初始化,new不只能申请内存,还能够对内存进行初始化和调用对应对象的构造函数。new是C++的运算符,底层的内存分配动做仍然是经过malloc()实现,经过new_handle引入对内存分配失败的处理机制。New没有相似relloc的机制。
malloc分配的内存不够的时候,能够用realloc扩容。realloc是从堆上分配内存的。当扩大一块内存空间时,realloc()试图直接从堆上现存的数据后面的那些字节中得到附加的字节,若是可以知足,天然天下太平;若是数据后面的字节不够,那么就使用堆上第一个有足够大的自由块,现存的数据而后就被拷贝至新的位置,而老块则放回到堆上。这句话传递的一个重要的信息就是数据可能被移动。使用realloc无需手动把旧的内存空间释放. 由于realloc 函数改变地址后会自动释放旧的内存。
new若是分配失败了会抛出bad_alloc的异常,而malloc失败了会返回NULL。所以对于 new,正确的姿式是采用try...catch语法,而malloc则应该判断指针的返回值。为了兼容不少c程序员的习惯,C++也能够采用new(nothrow)的方法禁止抛出异常而返回NULL。 new(nothrow)也是经过重载operator new实现的一种placement new。New是使用new hander 处理内存分配失败的状况。
assert 一种预处理宏,使用单个表达式做为断言条件。若条件为false, assert输出信息并终止程序的执行。为true do nothing。
(7) C和C++的区别
C++ =C+OOP(面向对象,多态)+GP(泛型编程,模板,STL,模板元编程)+异常处理。
sizeof()类的大小:
class A {};: sizeof(A) = 1;//空类大小为1;g++中每一个空类型的实例占1字节空间。
class A { virtual Fun(){} }; sizeof(A) = 4;: //存在虚函数,即存在一个虚指针
class A { static int a; };: sizeof(A) = 1;//静态成员不算类的大小,和空类同样
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
(8)C++中的重载,重写,重定义(隐藏)的区别:
重载:全局函数之间或同一个类体里的成员函数之间,函数名相同,参数不一样(参数个数,类型)。注意成员函数是不是const的也是不一样的重载。函数是不是const只有成员函数。函数返回值不参与重载断定。
重写:子类对父类的重写,要求子类函数与父类函数彻底相同,除了修饰符能够不一样,好比父类private,子类能够是public。此外,最重要的一点就是,父类的函数必须是虚函数,也就是要有virtual来修饰。父类的虚函数,子类能够重写出本身的版本,能够不重写直接继承父类的版本。对于父类的纯虚函数,子类必须重写本身的版本;有纯虚函数函数的类为抽象类,抽象类不可实例化。抽象基类相似于Java的接口,都不可实例化。抽象基类中的纯虚函数相似于接口中的方法,实现接口的类必须实现接口中的方法。
重定义:子类有父类同名函数,父类的函数就会被隐藏,调用子类对象只能调用子类的函数。这种状况只是简单的做用域限制,不具备面向对象的特性。
(9)析构函数通常写成虚函数的缘由。
何时类须要定义析构函数:若是类的数据成员中不存在成员(指针)与动态分配的内存相关联,咱们通常不用本身定义析构函数,而是采用默认的析构函数析构类对象。一旦与动态分配的内存相关联,为了防止内存泄露,咱们须要本身定义析构函数,手动释放动态分配的内存。由于系统默认的析构函数是没法帮助释放动态内存的。由于系统只会释放栈内存,分配的动态内存(堆内存)必须由程序手头释放。
三法则:若是一个类须要析构函数,几乎也须要定义赋值构造函数和重载赋值操做符。由于此时类的成员有指针,此时不能使用默认的复制构造,赋值运算符。
析构函数通常写成虚函数的缘由:
在类的继承体系中,在分析基类析构函数为何要定义为虚析构函数以前,咱们要先明白虚函数存在的意义就是为了动态绑定,实现面向对象的特性之一 :多态。
咱们知道经过基类的指针或者引用能够实现对虚函数的动态绑定,那么当咱们经过一个基类指针或者引用来析构一个对象时,咱们是没法判断基类如今绑定的对象是基类仍是派生类,若是析构函数不是虚函数,那么基类指针只会调用基类的析构函数,如此就发生了一些不应发生的事。只有将析构函数定义为虚函数,才能经过动态绑定,调用对应的析构函数版本,正确的析构类对象。
能够这么说:任何class只要有virtual函数都几乎肯定也要有一个virtual析构函数(引用自Effective C++ 条款7)
(10)构造函数为何通常不定义为虚函数
构造函数不能为虚函数主要有如下两点:
1、必要性分析:当定义派生类对象时,它会主动依次调用构造函数,顺序为基类的构造函数->一级派生类构造函数->二级派生类构造函数….直到当前派生类的构造函数调用完毕为止,到此派生类对象生成。 而虚函数存在的意义为动态绑定,从上一段话可知,它会从基类开始依次自动调用相应的构造函数,根本就不存在动态绑定的必要。
2、内存角度分析:
构造函数的做用是生成相应的类对象。虚函数的动态绑定是依据一张虚函数表来确认的最终绑定到哪个虚函数版本。 而调用构造函数以前,咱们对类对象所作的操做仅限于分配内存,尚未对内存进行初始化。此时,内存空间上也不存在虚函数表,所以,按照这样的执行顺序,虚函数的动态绑定是实现不了的。
(11)构造函数或者析构函数中调用虚函数会怎样。
从语法上讲,调用彻底没有问题。可是从效果上看,每每不能达到须要的目的。
1.构造:派生类对象构造期间会首先进入基类的构造函数,在基类构造函数执行时继承类的成员变量还没有初始化,此时调用虚函数,调用的必定是基类的虚函数版本,由于继承类的成员变量还没有初始化,此时对象类型是基类类型,vptr指向的也是基类的vptb,调用不到派生类的虚函数版本。此时虚函数和普通函数没有区别了。起不到多态的效果。
2.析构:假设一个派生类的对象进行析构,首先调用了派生类的析构,而后再调用基类的析构时,遇到了一个虚函数,这个时候有两种选择:Plan A是编译器调用这个虚函数的基类版本,那么虚函数则失去了运行时调用正确版本的意义;Plan B是编译器调用这个虚函数的派生类版本,可是此时对象的派生类部分已经完成析构,“数据成员就被视为未定义的值”,这个函数调用会致使未知行为。
总结:调用虚函数时,对应的基类或者派生类对象都必须是一个完整正确的对象状态。而在构造或者析构的过程当中对象不是一个完整的状态。
(12)析构函数能抛出异常吗
不能。C++标准指明析构函数不能、也不该该抛出异常。
(1) 若是析构函数抛出异常,则异常点以后的程序不会执行,若是析构函数在异常点以后执行了某些必要的动做好比释放某些资源,则这些动做不会执行,会形成诸如资源泄漏的问题。
(2) 一般异常发生时,c++的异常处理机制会调用已经构造对象的析构函数来释放资源,此时若析构函数自己也抛出异常,则前一个异常还没有处理,又有新的异常,会形成程序崩溃的问题。
(13)纯虚函数和抽象类
virtual ~myClass()=0;有纯虚函数的类是抽象类,不能实例化,抽象类的功能相似于Java的接口。
(14)多态的实现条件,虚指针vptr, 虚函数表vbtl
静态绑定和动态绑定:
静态绑定是经过重载和模板技术实现,在编译的时候肯定。动态绑定经过虚函数和继承关系来实现,执行动态绑定,在运行的时候肯定。
多态实现有几个条件:1.虚函数 2.一个基类的指针或引用指向派生类的对象
基类指针在调用成员函数(虚函数)时,就会经过对象的虚指针vptr去查找该对象的vptl虚函数表。虚函数表的地址vptr在每一个对象的首地址。查找该虚函数表中该虚函数的函数指针进行调用。
每一个对象中保存的只是一个虚函数表的指针,C++内部为每个类维持一个虚函数表,该类的对象都指向这同一个虚函数表。
虚函数表中为何就能准确查找相应的函数指针呢?由于在类设计的时候,派生类的虚函数表是直接从基类继承过来的,若是派生类的虚函数overiide某个基类的虚函数,那么虚函数表的函数指针就会被替换,所以能够根据指针准确找到该调用哪一个函数。
虚函数在设计上还具备封装和抽象的做用。好比抽象工厂模式???
(15)深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
浅拷贝在针对有指针的类时,会致使一个后果。两个指针指向同一块内存,在释放内存时,该内存会被释放两次,这就会有内存泄露的危险。
深拷贝,指先获取一块内存,而后将要拷贝的内容复制过去。两个指针指向不一样的内存,就不会有内存泄露的风险了。
浅拷贝是没有定义拷贝构造函数时系统的默认拷贝构造函数的拷贝方式。
因此,在对含有指针成员的对象(有动态分配内存的对象)进行拷贝时,必需要本身定义拷贝构造函数,使拷贝后的对象指针成员有本身的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。
(16)什么状况下会调用拷贝构造函数(三种状况)
用类的一个对象去初始化另外一个对象时;当函数的形参值传递对象时;当函数的返回值是以值传递对象。
(17)struct内存对齐方式,为何要对齐?怎么修改默认对齐方式?struct,union
从0位置开始存储; 成员变量存储的起始位置是该变量大小的整数倍; 结构体总的大小是其最大元素的整数倍,不足的后面要补齐;。当CPU访问正确对齐的数据时,它的运行效率最高。
Struct和union: union:
(1). 共用体和结构体都是由多个不一样的数据类型成员组成, 但在任何同一时刻, 共用体只存放了一个被选中的成员, 而结构体的全部成员都存在。
(2). 对于共用体的不一样成员赋值,原来成员的值就不存在了,成为了无定义状态。 而对于结构体的不一样成员赋值是互不影响的
修改对齐方式:#pragma pack (2) /*指定按2字节对齐*/
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
(18)内存泄露的定义,如何检测与避免?内存检查工具的了解。
内存泄漏:内存泄漏指的是在程序里动态申请的内存在使用完后,没有进行释放,致使这部份内存没有被系统回收,长此以往,可能致使程序内存不断增大,系统内存不足。排除内存泄漏对于程序的稳健型特别重要,尤为是程序须要长时间、稳定地运行时。
检查工具:1.Linux下经过工具valgrind检测。2.VS中的
定位内存泄露:
2. 在windows平台下经过CRT中的库函数进行检测;(只适用于Debug环境下)
VS2013中在Debug环境下,经过CRT库自己的内存泄漏检测函数可以分析出内存泄漏,定位内存泄露的位置。
检查方法:一.在main函数最后一行,加上一句_CrtDumpMemoryLeaks()。调试程序,天然关闭程序让其退出(不要定制调试),查看输出以下:
{453} normal block at 0x02432CA8, 868 bytes long.
被{}包围的453就是咱们须要的内存泄漏定位值(编译器的内存分配编号),868 bytes long就是说这个地方有868比特内存没有释放。此时只能知道在哪一次的内存分配(编译器的内存分配编号)在程序结束没有释放发生内存泄露,并无定位到具体的内存泄露的代码行。
接下来,定位代码位置:
在main函数第一行加上:_CrtSetBreakAlloc(453); 意思就是让程序执行到申请453这块内存的位置中断。而后调试程序,……程序中断了。查看调用堆栈。双击咱们的代码调用的最后一个函数(栈顶),这里是CDbQuery::UpdateDatas(),就定位到了申请内存的代码:
在线上运行的时候:
对象计数
方法:在对象构造时计数++,析构时--,每隔一段时间打印对象的数量;若发现对象的个数只增不减的异常,则能够推测该类的对象发生了内存泄露。
优势:没有性能开销,几乎不占用额外内存。定位结果精确。
缺点:侵入式方法,需修改现有代码,并且对于第三方库、STL容器、脚本泄漏等因没法修改代码而没法定位。
Hook Windows系统API
方法:使用windows分配内存的系统Api:HeapAlloc/HeapRealloc/HeapFree(new/malloc的底层调用),记录分配点,按期打印。
优势:非侵入式方法,无需修改现有文件,检查全面,对第三方库、脚本库等等都能统计到。
缺点:记录内存须要占用大量内存,并且多线程环境须要加锁。
(19)成员初始化列表的概念,为何用成员初始化列表会快一些(性能优点)?
使用成员初始化列表定义构造函数是显式地初始化类的成员,若是不用成员初始化列表,那么类对象对本身的类成员分别进行的是一次隐式的默认构造函数的调用(在进入函数体以前)初始化类的成员,和一次拷贝赋值运算符的调用(进入函数体以后),若是是类对象,这样作效率就得不到保障。
类类型的数据成员对象在进入构造函数体前己经构造完成,也就是说在成员初始化列表处进行对象的构造工做,调用构造函数,在进入函数体以后,进行的是对己构造好的类对象赋值,又调用个拷贝赋值操做符才能完成(若是并未提供,则使用编译器默认的按成员赋值行为))。
(20)必须在构造函数初始化列表里进行初始化的数据成员有哪些?
(1) 常量成员,由于常量只能初始化不能赋值,因此必须放在初始化列表里面
(2) 引用类型,引用必须在定义的时候初始化,而且不能从新赋值,因此也要写在初始化列表里面
(3) 没有默认构造函数的类类型,若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若成员类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。使用初始化列表能够没必要调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
对象成员:A类的成员是B类的对象,在构造A类时需对B类的对象进行构造,当B类没有默认构造函数时须要在A类的构造函数初始化列表中对B类对象初始化
类的继承:派生类在构造函数中要对自身成员初始化,也要对继承过来的基类成员进行初始化,当基类没有默认构造函数的时候,经过在派生类的构造函数初始化列表中调用基类的构造函数实现。
(21) C++的调用惯例(C++函数调用的压栈过程)
在函数调用时,第一个进栈的是主函数中调用点后的下一条指令(函数调用语句的下一条可执行语句)的地址,而后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,而后是函数中的局部变量。注意静态变量是不入栈的。 静态变量在全局静态局。
当本次函数调用结束后,局部变量先出栈,而后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
(22)C++的四种强制转换static_cast,const_cast,dynamic_cast,reinterpret_cast
C的强制转换表面上看起来功能强大什么都能转,可是转化不够明确,不能进行错误检查,容易出错。
static_cast:
static_cast用的最多,对于各类隐式转换如int转double,非const转const,void*转类型指针等。
C转换 :(type) expression C++转换:static_cast<type>(expression)
const_cast:
只能够用来移除表达式的常量性;
dynamic_cast:在多态的环境下向下转型。
用在继承体系多态环境中,将“指向基类对象的指针或引用”转型为“指向派生类对象的指针或引用”。若指向基类对象的指针或引用在运行时接受的一个派生类的对象,则转型成功。不然转型失败,会以一个null指针(当转型对象是指针)或一个exception(当转型对象是引用)表现出来。
reinterpret_cast:转换函数指针类型:
例:设有一数组,存储的是函数指针,有特定类型;
//FuncPtr是函数指针,指向某个函数,该函数无参数,返回类型为void。
typedef void (*FuncPtr)();
FuncPtr funcPtrArray[10];//funcPtrArray是个数组,有10个FuncPt
若想将如下函数的一个指针n放入该数组:
dosomething的类型与funcPtrArray接受的类型不一样。funcPtrArray内各函数指针所指向的函数返回类型是void。
funcPtrArray[0]=&dosomething;//错
funcPtrArray[0]=reinterpret_cast<FuncPtr>(&dosomething);//对。
注:函数指针的转型动做不具移植性(C++不保证全部的函数指针都能以此方式从新呈现)。某些状况下这样的转型可能会致使不正确的结果。
(23)多重继承,菱形结构,虚基类,虚继承,以及在多继承虚继承下多态的实现,虚继承下对象的内存分布
多重继承在菱形结构的情形下,每每致使virtual base classes(虚拟基类)的需求。在non-virtual base的状况下,若是派生类对于基类有多条继承路径,那么派生类会有不止一个基类部分,使用虚继承,让基类为virtual能够消除这样的复制现象。然而虚基类也可能致使另外一成本:其实现作法经常利用指针,指向"virtual base class"部分,所以对象内可能出现一个(或多个)这样的指针。
(24)内联函数有什么优势?内联函数与宏定义的区别?
虚函数不能够内联:内联在编译的时候替换,但只有在运行时才能肯定调用哪一个虚函数)
(25)STL容器迭代器失效总结.
vector 迭代器失效状况:
1.push_back()必定使得end()返回的迭代器失效,若发生capacity()增加,致使vector容器的全部迭代器都失效。由于发生了数据移动。
2. erase()使得删除点和删除点后面的迭代器都失效。失效的迭代器不能够进行迭代器操做,如++iter,*iter,指向的是位置内存。但erase(iter)能够返回下一个有效的迭代器。erase的返回值是删除元素下一个元素的迭代器。这个迭代器是vector内存调整事后新的有效的迭代器。
list,set, map 迭代器失效状况:
使用了不连续分配的内存,删除当前的iterator,仅仅会使当前的iterator失效, erase迭代器返回值为void,因此要采用erase(iter++)的方式删除迭代器。如:
解析dataMap.erase(iter++);这句话分三步走,先把iter值传递到erase里面,而后iter自增,而后执行erase,因此iter在失效前已经自增了。
list中,erase(*iter)会返回下一个有效迭代器的值, erase(iter)也会返回void,也需使用erase(iter++)的方式删除迭代器。
deque迭代器失效状况:
1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。2. 在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。3.在deque容器的任何其余位置的插入和删除操做将使指向该容器元素的全部迭代器失效。
(26.)对继承和组合的理解
继承是一种is-a关系,组合是一种has-a关系。在功能上来看,它们都是实现功能重用,代码复用的最经常使用的有效的设计技巧,都是在设计模式中的基础结构。类继承容许咱们根据本身的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的,因此咱们通常称之为白盒复用。对象持有一般用来实现配接,如,STL中deque和stack,总体类对部分类的功能的复用接口的修饰,使其成为另外一种特定的面貌。总体类和部分类之间不会去关心各自的实现细节。即它们之间的实现细节是不可见的,故成为黑盒复用。
继承中父类定义了子类的部分实现,而子类中又会重写这些实现,修改父类的实现,设计模式中认为这是一种破坏了父类的封装性的表现。这个结构致使结果是父类实现的任何变化,必然致使子类的改变。然而组合这不会出现这种现象。对象的组合还有一个优势就是有助于保持每一个类被封装,并被集中在单个任务上(类设计的单一原则)。这样类的层次结构不会扩大,通常不会出现不可控的庞然大类。而类的继承就可能出来这些问题,因此通常编码规范都要求类的层次结构不要超过3层。
通常优先优先使用对象组合,而不是类继承。
(27)c++ main函数执行以前须要作哪些准备
1. 设置栈指针
2. 将non-local static对象构造完成。
non-local static对象包括文件下(全局),命名空间下,类的static对象成员,non-local static对象要在main函数以前构造。函数中的static对象是local static对象,local static对象直到方法被调用的时候,才进行初始化,并且只初始化一次。local static 变量(局部静态变量)一样是在main前就已分配内存,第一次使用时初始化。全部的static对象都分配在全局区,程序结束才释放内存。
3. 将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL,等等,即.bss段的内容
4..运行全局构造器,估计是C++中构造函数之类的吧
5.将main函数的参数,argc,argv等传递给main函数,而后才真正运行main函数。,
全局变量、non-local static变量在main执行以前就已分配内存并初始化;local static 变量一样是在main前就已分配内存,第一次使用时初始化。
(28)手写智能指针?shared_ptr何时改变引用计数?weak_ptr如何解决引用传递?这些是线程安全的吗?线程安全的智能指针是哪个?
智能指针:使用普通指针,容易形成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,可是对象的读取须要加锁。
引用计数改变:构造函数中计数初始化为1;拷贝构造函数中计数值加1;析构函数中引用计数减1;赋值运算符中,左边的对象引用计数减/1,右边的对象引用计数加1;在赋值运算符和析构函数中,若是减1后为0,则调用delete销毁对象并释放它占用的内存
unique_ptr“惟一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(经过禁止拷贝语义、只有移动语义来实现)。
weak_ptr是为了配合shared_ptr而引入的一种智能指针,不具备普通指针的行为,没有重载operator*和->,做用在于协助shared_ptr工做,观测资源的使用状况。成员函数use_count()能够观测资源的引用计数,成员函数lock()从被观测的shared_ptr得到一个可用的shared_ptr对象,从而操做资源。
shared_ptr的循环引用致使的内存泄漏怎么解决?
https://www.cnblogs.com/itZhy/archive/2012/10/01/2709904.html
使用weak_ptr
http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html
类成员变量用memset()设置会有什么问题?
不能,由于memset会破坏成员变量对象的内部结构(都赋值为0),当类对象析构时,析构到该成员变量对象时,该成员变量对象不能正常进行析构操做,最终致使crash。
注:若是类包含虚函数,则不能用 memset 来初始化类对象。由于包含虚函数的类对象都有一个虚指针指向虚函数表(vtbl),进行memset操做时,虚指针的值也要被overwrite,这样一来,只要一调用虚函数,程序便崩溃。
(30)STL alloc实现,alloc的优点和局限,STL中其余的配置器
gnuC中使用了内存池设计,减少了小内存分配的分配次数,提升效率。减小内存的碎片化。可是同时内存池的设计只分配不释放(只拿不还,服务容器),alloc在运行期间不会释放分配的内存。这种占用可能使得其余的进程不能得到足够的内存。在gunc4.9 中有其余的配置器。给8k,16k,..., 128k比较小的内存片都维持一个空闲链表。
_pool_alloc, loki_allocator
(31)模板的用法与适用场景,模板泛化,偏特化,特化,可变模板参数,举出实例。
(29)单例模式,C++实现一个线程安全的单例类;用C++设计一个不能被继承的类;如何定义一个只能在堆上(栈上)生成对象? fianl对象
单例模式:一个类只能被实例化一次,并提供一个访问它的全局访问点。
饿汉和懒汉:懒汉式在第一次用到类实例的时候才会去实例化,一般须要用加锁机制实现线程安全。饿汉式在单例类定义的时候就进行实例化。使用no-local static变量存储单例对象,类一加载就实例化。会提早占用系统资源。
特色与选择:因为要进行线程同步,因此在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,能够实现更好的性能。这是以空间换时间。在访问量较小时,采用懒汉实现。这是以时间换空间。
分析:instance是非局部静态变量,在main执行前就分配内存并初始化,是线程安全的。潜在问题在于no-local static对象(函数外的static对象)在不一样编译单元(可理解为cpp文件和其包含的头文件)中的初始化顺序是未定义的。
使用场景: 在整个项目中须要一个共享访问点或共享数据,或者相似的实体(有且只有一个,且须要全局访问),那么就能够将其实现为一个单例。
例如一个Web页面上的计数器,能够不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的;
日志类,一个应用每每只对应一个日志实例;
管理器,好比windows系统的任务管理器就是一个例子,老是只有一个管理器的实例。
单例模式经常与工厂模式结合使用,由于工厂只须要建立产品实例就能够了,在多线程的环境下也不会形成任何的冲突,所以只须要一个工厂实例就能够了。
只能创建在堆上:将析构函数设为私有,类对象就没法创建在栈上了。当对象创建在栈上时,是由编译器分配内存空间的,编译器调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。若是类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
只能创建在栈上:在类的内部重载operator new(),并设为私有便可。只有使用new运算符,对象才会创建在堆上,所以,只要禁用new运算符就能够实现类对象只能创建在栈上。
设计一个fianl类: 法一:构造析构放在私有,public中放一个static接口函数,用来建立和释放类的实例。,可是该类只能获得位于堆上的实例,而得不到位于栈上实例。
法二:友元函数;https://www.cnblogs.com/luxiaoxun/archive/2013/06/07/3124948.html
C++11中已经有了final关键字:做用是指定一个类成为一个不能被继承的类(final class),或者指定类的虚函数不能被该类的继承类重写(override),。
使用场景:当一个方法被final修饰后。表示该方法不能被子类重写。好比涉及到某些须要统一处理的需求。
make: 一个自动化编译工具,依据makefile文件(编译规则) 批处理编译多个源文件。
cmake:一个读入源文件,自动生成makefile文件的工具,cmakelist文件是cmake工具生成makefile文件的规则,cmakelist一般由程序员编写。
https://blog.csdn.net/weixin_42491857/article/details/80741060
把capicity减小到元素个数,减小容量
,如:vector<int>(ivec).swap(ivec);将 ivec shrink_to_fit
表达式vector<int>(ivec)创建一个临时vector,它是ivec的一份拷贝:vector的拷贝构造函数作了这个工做。可是,vector的拷贝构造函数只分配拷贝的元素须要的内存,因此这个临时vector没有多余的容量。而后咱们让临时vector和ivec交换数据,这时,ivec只有临时变量的修整过的容量,而这个临时变量则持有了曾经在ivec中的没用到的过剩容量。最后,临时vector被销毁,所以释放了之前ivec使用的内存,收缩到合适。
char (*p) [5]:定义了个指针,指针指向一个有5个char的数组;
char *p[5]:定义了一个数组,里面有5个指向char的指针;
char (*p)():函数指针,指向 char fun();类型的函数;
是将构造函数和拷贝构造函数声明为private,或者采用c++11的delete关键字,
delete关键字可用来禁用某种类型的函数,unique_ptr只能使用移动构造函数,使用delete关键字禁用了拷贝构造函数。
main函数执行前:定义在main( )函数以前的全局对象、静态对象的构造函数在main( )函数以前执行。
main函数执行后:全局/静态对象的析构函数会在main函数以后执行;能够用atexit()来注册程序正常终止时要被调用的函数,而且在main函数结束时,调用这些函数,调用顺序与他们被注册时相反
不管程序是否正常退出,均可以用atexit()来调用资源释放的函数;
遍历删除,考虑迭代器失效问题
{ if(iter指向的元素是奇数)
mapTest.erase(iter);
} //错误,erase会让迭代器会失效!
{ if(iter指向的元素是奇数)
mapTest.erase(iter++);//正确,iter值传递以后,再++;
}
{ if(iter指向的元素是奇数)
iter=mapTest.erase(iter);// erase() 成员函数返回下一个元素的迭代器
}
C++的分离式编译:c++开发中普遍使用声明和实现分开的开发形式,其编译过程是分离式编译,就是说各个cpp文件彻底分开编译,而后生成各自的obj目标文件,最后经过链接器link生成一个可执行的exe文件。一个编译单元(translation unit)是指一个.cpp文件以及它所#include的全部.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,而后编译器编译该.cpp文件为一个.obj文件。.obj文件已是二进制码,可是不必定可以执行,由于并不保证其中必定有main函数。当编译器将一个工程里的全部.cpp文件以分离的方式编译完毕后,再由链接器(linker)进行链接成为一个.exe文件。
C++类模板不支持分离式编译:模板代码的实现体在一个文件里,而实例化模板的测试代码在另外一个文件里,编译器编译一个文件时并不知道另外一个文件的存在,所以,模板代码就没有进行实例化,编译器天然不会为其生成代码,所以会抛出一个连接错误!
C++类模板不支持分离式编译,即咱们必须把类模板的声明和定义写在同一个.h文件中;
函数重载考虑参数个数,参数类型,不考虑函数返回值类型(函数调用时独立于上下文),
两个文件a,b,文件内部分别定义两个全局变量,用g 编译的时候如何保证两个全局变量初化顺序
全局变量 int a = 5; int b = a; 在不一样文件中,不能保证b也等于5,也就是说不能保证a先初始化。
解决这种问题的方法是不直接使用全局变量,而改用一个包装函数来访问,例如
int get_a()
{
static int a = 5;
return a;
}
int get_b()
{
static int b = get_a();
return b;
}
这样的话,不管get_a和get_b是否认义在同一个文件中,get_b老是可以返回正确的结果,缘由在于,函数内部的静态变量是在第一次访问的时候来初始化。
哈希表的冲突处理和数据迁移。
处理冲突:hash表实际上由size个的桶组成一个桶数组table[0...size-1] 。当一个对象通过哈希以后。获得一个对应的value , 因而咱们把这个对象放到桶table[ value ]中。当一个桶中有多个对象时。咱们把桶中的对象组织成为一个链表。这在冲突处理上称之为拉链法。
负载因子: 若是一个hash表中桶的个数为 size , 存储的元素个数为used .则咱们称 used / size 为负载因子loadFactor . 通常的状况下,当loadFactor<=1时,hash表查找的指望复杂度为O(1). 所以。每次往hash表中加入元素时。咱们必须保证是在loadFactor <1的状况下,才能够加入。
数据迁移:Hash表中每次发现loadFactor==1时,就开辟一个原来桶数组的两倍空间(称为新桶数组),而后把原来的桶数组中元素全部转移过来到新的桶数组中。注意这里转移是需要元素一个个又一次哈希到新桶中的。
缺点:容量扩张是一次完毕的,期间要花很长时间一次转移原hash表中的所有元素。
改进: redis中的dict.c中的设计思路是用两个hash表来进行扩容和转移的工做:当第一个hash表的loadFactor=1时,假设要往字典里插入一个元素。首先为第二个hash表开辟2倍第一个hash表的容量。同一时候将第一个hash表的一个非空桶中全部元素转移到第二个hash表中。而后把待插入元素存储到第二个hash表里。继续往字典里插入第二个元素,又会将第一个hash表的一个非空桶中全部元素转移到第二个hash表中,而后把元素存储到第二个hash表里……直到第一个hash表为空。
这样的策略就把第一个hash表所有元素的转移分摊为屡次转移,而且每次转移的指望时间复杂度为O(1)。
vector的容量扩张为何是2倍 最好的策略是什么?reverse()
vector 在须要的时候会扩容,在 VS 下是 1.5倍,在 GCC 下是 2 倍。
答:采用成倍方式扩容,能够保证push_back 常数的时间复杂度,而增长指定大小的容量只能达到O(n)的时间复杂度
以 大于2 倍的方式扩容,下一次申请的内存会大于以前分配内存的总和,致使以前分配的内存不能再被使用。因此,最好的增加因子在 (1,2)之间。
数学上的证实:当 k =1.5 时,在几回扩展之后,能够重用以前的内存空间了
reserve(n):因为vector动态增加会引发从新分配内存空间、拷贝原空间、释放原空间,这些过程会下降程序效率。所以,可使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会从新分配内存空间的,这样便提高了效率。只有当n>capacity()时,调用reserve(n)才会改变vector容量。
C语言里面字符串,strcpy和strncpy的区别?哪一个函数更安全?
strcpy函数:把从src地址开始且含有NULL结束符的字符串赋值到以dest开始的地址空间,返回dest(地址中存储的为复制后的新值)。要求:src和dest所指内存区域不能够重叠且dest必须有足够的空间来容纳src的字符串。
strncpy函数:将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy同样遇到NULL才中止复制,而是等凑够n个字符才开始复制),返回指向dest的指针。要求:若是n > dest串长度,dest栈空间溢出产生崩溃异常。
安全性分析:strncpy要比strcpy安全得多,strcpy没法控制拷贝的长度,不当心就会出现dest的大小没法容纳src的状况,就会出现越界的问题,程序就会崩溃。而strncpy就控制了拷贝的字符数避免了这类问题,可是要注意的是dest依然要注意要有足够的空间存放src,并且src 和 dest 所指的内存区域不能重叠,
malloc涉及的系统调用(说了brk指针和mmap,没说清楚,很是不满意)。
malloc调用brk或mmap系统调用去获取内存。malloc小于128k的内存,使用brk分配内存,malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配(对应独立内存,并且初始化为0),
C++11新特性? lambda表达式, =default;, =deleted 函数
Lambda 表达式就是用于建立匿名函数的。
lambda表达式的本质就是重载了()运算符的类,这种类一般被称为functor,即行为像函数的类。所以lambda表达式对象其实就是一个匿名的functor。编译器自动将lambda表达式转换成函数对象执行
=default; 指示编译器生成该函数的默认实现。这有两个好处:一是让程序员轻松了,少敲键盘,二是有更好的性能。
与 defaulted 函数相对的就是 =deleted 函数, 实现 non copy-able 防止对象拷贝,要想禁止拷贝,用 =deleted 声明一下两个关键的成员函数就能够了:
不能,由于C++支持重载,在编译函数的声明时,会改写函数名(能够经过连接指示进行解决);另外,C语言不支持类,没法直接调用类的成员函数(能够经过加入中间层进行解决);C语言也不能调用返回类型或形参类型是类类型的函数。
restrict是c99标准引入的,它只能够用于限定和约束指针,并代表指针是访问一个数据对象的惟一且初始的方式. 即它告诉编译器,全部修改该指针所指向内存中内容的操做都必须经过该指针来修改, 而不能经过其它途径(其它变量或指针)来修改;这样作的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码。
如今程序员用restrict修饰一个指针,意思就是“只要这个指针活着,我保证这个指针独享这片内存,没有‘别人’能够修改这个指针指向的这片内存,全部修改都得经过这个指针来”。因为这个指针的生命周期是已知的,编译器能够放心大胆地把这片内存中前若干字节用寄存器cache起来。
http://www.javashuo.com/article/p-ktkcsbqc-ha.html
注:(1)静态函数不能被定义为虚函数,也不能被重载
(2)重写函数的访问修饰符能够不一样
面向对象的三大特性,结合C++语言支持来说。
多态的好处:能够忽略派生类和基类的区别,而以统一的方式使用派生类和基类的对象,提升了代码的复用性和可拓展性。
红黑树性质:红黑树是许多“平衡”搜索树中的一种,能够保证在最坏状况下基本动态操做的时间复杂度为O(lgn)。经过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其余路径长出2倍,于是使近似于平衡。
红黑树须知足条件:
AVL树和红黑树的区别:
malloc的底层实现:
malloc函数将可用的内存块链接为一个空闲链表。调用malloc函数时,它沿着空闲链表寻找一个大到足以知足用户所须要的内存块。而后,将该内存块一分为二。一块分配给用户使用,另外一个块从新链接到空闲链表。当用户申请一个大的内存片断,而内存块被切分为小的内存片断,没法知足用户的请求时,malloc函数请求延时,将相邻的小的空闲块合并成大的内存块。若是找不到合适的内存块,就经过系统调用brk,将break指针向高地址移动,获取新的内存块,链接到空闲链表中。另外,若是所申请的内存大于128k,调用mmap在文件映射区域找一块空闲的虚拟内存。若是分配内存失败,会返回NULL指针。
++iter和iter++那个好?
前置版本的递增运算符避免了没必要要的工做,它把值加1后直接返回改变了的运算对象。与之相比,后置版本须要将原始值存储下来以便于返回这个位修改的内容,若是咱们不须要修改前的值,那么后置版本的操做就是一种浪费。对于相对复杂的迭代器类型,这种后置版本的操做就是一种浪费。
用 c 实现重载 ? 函数指针
如何突破private的限制?友元函数
用 C 模拟虚函数?
如何设计一个好的字符串hash函数
对于一个Hash函数,评价其优劣的标准应为随机性或离散性,即对任意一组标本,进入Hash表每个单元(cell)之几率的平均程度,由于这个几率越平均,两个字符串计算出的Hash值相等hash collision的可能越小,数据在表中的分布就越平均,表的空间利用率就越高。
C++ 11 定义了一个新增的哈希结构模板定义于头文件 <functional>:std::hash<T>,模板类,(重载了operator()),实现了散列函数: unordered_map和unordered_multimap 默认使用std::hash; std::hash;实现太简单
同时,C++ STL 里面实现了一个万用的hash function 针对任何类型的
boost::hash 的实现也是简单取值。
DJBHash是一种很是流行的算法,俗称"Times33"算法。Times33的算法很简单,就是不断的乘33,原型以下:
hash(i) = hash(i-1) * 33 + str[i],Time33在效率和随机性两方面上俱佳
https://blog.csdn.net/g1036583997/article/details/51910598