(1)指针有自己的一块空间,而引用只是一个别名;
(2)使用 sizeof 看一个指针的大小为4
字节(32位,如果要是64位的话指针为8字节),而引用则是被引用对象的大小。
(3)指针可以被初始化为 NULL
,而引用必须被初始化且必须是一个已有对象的引用。
(4)作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象。
(5)指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能被改变。
(6)指针可以是多级,而引用没有分级
(7)如果返回动态分配内存的对象或者内存,必须使用指针,引用可能引起内存泄漏。
(1)堆栈空间分配区别:
(2)堆栈的缓存方式区别
(3)堆栈数据结构上的区别
new操作针对数据类型的处理,分为两种情况:
(1) 简单数据类型(包括基本数据类型和不需要构造函数的类型)
operator new
分配内存;new_handler
来处理 new
失败的情况;new
分配失败的时候不像 malloc
那样返回 NULL
,它直接抛出异常(bad_alloc
)。要判断是否分配成功应该用异常捕获的机制;(2)复杂数据类型(需要由构造函数初始化对象)
构造函数
。delete也分为两种情况:
(1) 简单数据类型(包括基本数据类型和不需要析构函数的类型)
(2)复杂数据类型(需要由析构函数销毁对象)
析构函数
再调用operator delete。从原理上来分析可以看看这篇博客:C++ new和delete的实现原理
与 malloc 和 free 的区别:
(1)属性上:
new / delete 是c++关键字,需要编译器支持。 malloc/free是库函数,需要c的头文件支持。
(2)参数:
使用new操作符申请内存分配时无须制定内存块的大小,编译器会根据类型信息自行计算。而mallco则需要显式地指出所需内存的尺寸。
(3)返回类型:
new操作符内存分配成功时,返回的是对象类型的指针
,类型严格与对象匹配,故new是符合类型安全性的操作符。而malloc内存成功分配返回的是void *
,需要通过类型转换将其转换为我们需要的类型。
(4)分配失败时:
new内存分配失败时抛出bad_alloc
异常;malloc分配内存失败时返回 NULL。
(5)自定义类型:
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。 malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
(6)重载:
C++允许重载 new/delete 操作符。而malloc为库函数不允许重载。
(7)内存区域:
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。其中自由存储区为:C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
(1)首先说一下C中的结构体和C++中的结构体的异同:
(2)C++中 struct 与 class 的区别:
public
的,而 class 默认的访问属性是private
的public
的,而 class 是 private
class
这个关键字还可用于定义模板参数,就等同于 typename
;而strcut不用与定义模板参数(1)起作用的阶段: #define是在编译的预处理
阶段起作用,而const是在编译、运行
的时候起作用。
(2)作用的方式:const常量有数据类型,而宏常量没有数据类型,只是简单的字符串替换。编译器可以对前者进行类型安全检查。而对后者没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。
(3)存储的方式:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份,const比较节省空间,避免不必要的内存分配,提高效率。
(1)static:
修饰全局变量
:存储在静态存储区;未经初始化的全局静态变量自动初始化为 0;作用域为整个文件之内。修饰局部变量
:存储在静态存储;未经初始化的局部静态变量会被初始化为0;作用域为局部作用域,但离开作用域不被销毁。修饰静态函数
:静态函数只能在声明的文件中可见,不能被其他文件引用修饰类的静态成员
:在类中,静态成员可以实现多个对象之间的数据共享,静态成员是类的所有对象中共享的成员,而不属于某一个对象;类中的静态成员必须进行显示的初始化修饰类的静态函数
:静态函数同类的静态成员变量一个用法,都是属于一个类的方法。而且静态函数中只可以使用类的静态变量。(2)const:
修成类成员
:在C++中,const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数; const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。修饰类函数
:该函数中所有变量均不可改变。ClassTest ct1("ab");
这条语句属于直接初始化,它不需要调用复制构造函数,直接调用构造函数ClassTest(constClassTest ct2 = "ab";
这条语句为复制初始化,它首先调用构造函数 ClassTest(const char* pc)
函数创建一个临时对象,然后调用复制构造函数,把这个临时对象作为参数,构造对象ct2;所以当复制构造函数变为私有时,该语句不能编译通过。ClassTest ct3 = ct1;
这条语句为复制初始化,因为 ct1 本来已经存在,所以不需要调用相关的构造函数,而直接调用复制构造函数,把它值复制给对象 ct3;所以当复制构造函数变为私有时,该语句不能编译通过。ClassTest ct4(ct1);
这条语句为直接初始化,因为 ct1 本来已经存在,直接调用复制构造函数,生成对象 ct3 的副本对象 ct4。所以当复制构造函数变为私有时,该语句不能编译通过。要点就是拷贝初始化和直接初始化调用的构造函数是不一样的,但是当类进行复制时,类会自动生成一个临时的对象,然后再进行拷贝初始化。
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器不要修改你的函数名。
引入的原因:编写单一的模板,它能适应大众化,使每种类型都具有相同的功能,但对于某种特定类型,如果要实现其特有的功能,单一模板就无法做到,这时就需要模板特例化。
定义:是对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。
template
后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。template<typename T> //函数模板
int compare(const T &v1,const T &v2)
{
if(v1 > v2) return -1;
if(v2 > v1) return 1;
return 0;
}
//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template<>
int compare(const char* const &v1,const char* const &v2)
{
return strcmp(p1,p2);
}
此处如果是compare(3,5),则调用普通的模板,若为compare(“hi”,”haha”)则调用特例化版本(因为这个cosnt char*相对于T,更匹配实参类型),注意,二者函数体的语句不一样了,实现不同功能。
template<typename T>class Foo
{
void Bar();
void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
//进行int类型的特例化处理
}
Foo<string> fs;
Foo<int> fi;//使用特例化
fs.Bar();//使用的是普通模板,即Foo::Bar()
fi.Bar();//特例化版本,执行Foo::Bar()
//Foo::Bar()和Foo::Bar()功能不同
STL内存管理使用二级内存配置器
。
(1) 第一级配置器:
第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数。一级空间配置器分配的是大于128字节的空间,如果分配不成功,调用句柄释放一部分内存,如果还不能分配成功,抛出异常。
第一级配置器只是对malloc函数和free函数的简单封装,在allocate
内调用malloc,在deallocate
内调用free。同时第一级配置器的oom_malloc函数,用来处理malloc失败的情况。
(2) 第二级配置器:
第一级配置器直接调用malloc和free带来了几个问题:
如果分配的区块小于128bytes,则以内存池管理,第二级配置器维护了一个自由链表数组,每次需要分配内存时,直接从相应的链表上取出一个内存节点就完成工作,效率很高。
自由链表数组:
自由链表数组其实就是个指针数组,数组中的每个指针元素指向一个链表的起始节点。数组大小为16,即维护了16个链表,链表的每个节点就是实际的内存块,相同链表上的内存块大小都相同,不同链表的内存块大小不同,从8一直到128。如下所示,obj为链表上的节点,free_list就是链表数组。
内存分配:
allocate函数内先判断要分配的内存大小,若大于128字节,直接调用第一级配置器,否则根据要分配的内存大小从16个链表中选出一个链表,取出该链表的第一个节点。若相应的链表为空,则调用refill函数填充该链表。默认是取出20个数据块。
填充链表 refill:
若allocate函数内要取出节点的链表为空,则会调用refill函数填充该链表。refill函数内会先调用chunk_alloc函数从内存池分配一大块内存,该内存大小默认为20个链表节点大小,当内存池的内存也不足时,返回的内存块节点数目会不足20个。接着refill的工作就是将这一大块内存分成20份相同大小的内存块,并将各内存块连接起来形成一个链表。
内存池:
chunk_alloc函数内管理了一块内存池,当refill
函数要填充链表时,就会调用chunk_alloc
函数,从内存池取出相应的内存。
chunk_alloc
函数内首先判断内存池大小是否足够填充一个有20个节点的链表,若内存池足够大,则直接返回20个内存节点大小的内存块给refill;chunk_alloc
函数会将内存池中那一点点的内存大小分配给其他合适的链表,然后去调用malloc函数分配的内存大小为所需的两倍。若malloc成功,则返回相应的内存大小给refill
;若malloc失败,会先搜寻其他链表的可用的内存块,添加到内存池,然后递归调用chunk_alloc
函数来分配内存,若其他链表也无内存块可用,则只能调用第一级空间配置器。在一个vector的尾部之外的任何位置添加元素,都需要重新移动元素。而且,向一个vector添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移到新的空间。
参数的类型,个数,顺序不同
)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。virtual
修饰。这篇博客《C++类内存布局图(成员函数和成员变量分开讨论)》 看完之后真的就大彻大悟了,作者写的太好了!!!忍不住拍案叫绝啊!
先看下面这段程序
#include
using namespace std;
class Person
{
public:
virtual ~Person() //加了virtual,讲析构函数声明为虚函数
{
cout << "Person::~Person()" << endl;
}
};
class Student : public Person
{
public:
~Student() // virtual可加可不加
{
cout << "Student::~Student()" << endl;
}
};
int main()
{
Person *pt1 = new Person;
Person *pt2 = new Student; // 用基类的指针指向子类
// Student *pt3 = new Person; // 不能用子类指针指向基类,错误!
Student *pt4 = new Student;
delete pt1;
cout << "*********" << endl;
delete pt2;
cout << "*********" << endl;
//delete pt3;
//cout << "*********" << endl;
delete pt4;
cout << "*********" << endl;
return 0;
}
运行结果:
Person::~Person()
Student::~Student()
Person::~Person()
Student::~Student()
Person::~Person()
如果在基类中析构函数不加virtual,结果为:
Person::~Person()
Person::~Person()
Student::~Student()
Person::~Person()
可以看出:只有在用基类的指针指向派生类的时候
虚函数发挥了动态的作用。
析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用delete销毁对象时,只调用了基类的析构函数,未调用派生类的析构函数。这样会造成销毁对象不完全,容易造成内存泄露
。
C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。
explicit构造函数是用来防止隐式转换的。
explicit
只对一个实参的构造函数有效explicit
的从C++之父Bjarne的回答我们应该知道C++为什么不支持构造函数是虚函数了,简单讲就是没有意义。虚函数的作用在于通过子类的指针或引用来调用父类的那个成员函数。而构造函数是在创建对象时自己主动调用的,不可能通过子类的指针或者引用去调用。
虚函数相应一个指向vtable虚函数表的指针,但是这个指向vtable的指针事实上是存储在对象的内存空间的。假设构造函数是虚的,就须要通过 vtable来调用,但是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
总的来说,构造函数和析构函数调用虚函数并不能达到多态的效果,因为在析构和构造过程中,该对象变为一个基类对象,调用的方法都是基类的方法。
在子类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的
。静态类型和动态类型:
对象在声明是采用的类型,在编译期确定;
当前对象所指的类型,在运行期决定,对象的动态类型可以更改,但静态类型无法更改。
静态绑定和动态绑定:
绑定的是对象的静态类型,某特性(比如函数)依赖于对象的静态类型,发生在编译期。
绑定的是对象的动态类型,某特性(比如函数)依赖于对象的动态类型,发生在运行期。