宏定义的常量没有类型,只是简单的替换;在预处理时进行替换;不可以用指针去指向;可以定义简单的函数。
const定义的常量有类型名字,存放在静态区域;所定义的变量在编译时确定其值;可以使用指针指向;不可以定义函数。
inline 用于修饰函数,表示在调用该函数的时候直接在调用的地方进行展开,但是这只是向编译器提出这种需求,具体会不会展开取决于编译器本身。内联函数一般为简单函数,这将会减少函数调用时的压栈出栈函数等的开销。
对于普通的常量而言,宏定义的方式缺少类型检查,因而将会导致很多的隐患,而 const 机制可以很好的继承了他的优点,同时也克服了他的缺点。而对于 inline 而言,他是一种很好的替代宏函数的方式,对宏函数进行参数传递的时候并不会产生向宏函数这样的多次求值的错误,而且内联函数同时支持调试。因而我们在使用的过程中应该尽量的使用 const 常量和 inline 函数来替代宏函数的使用。
函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部,不能跨作用域。
class Animal
{
public:
void func1(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
void func1(const char *s)//函数的重载
{
cout << "I'm an animal func1 -" << s << endl;
}
};
也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) ,指子类的函数屏蔽了与其同名的父类函数。除非将子类强制转换为父类,才可以重新调用父类的函数。
class Animal
{
public:
void func1(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
virtual void func2(int tmp)
{
cout << "I'm virtual animal func2 -" << tmp << endl;
}
};
class Fish :public Animal
{
public:
void func1()//函数的重定义 会隐藏父类同名方法
{
cout << "I'm a fish func1" << endl;
}
void func2(int tmp) //函数的重写, 覆盖父类的方法 override
{
cout << "I'm a fish func2 -" << tmp << endl;
}
};
也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的虚函数,必须要有virtual关键字。这样可以使用父类的对象或指针调用子类的函数。
当子类进行重写时,尽量加上override关键字,这样编译器可以检测是否重写,就不会产生不小心打错的情况。
当子类进行函数重写时,如果加上final关键字,则该函数就不能被它的子类继承。
new/delete,malloc/free都是动态分配内存的方式
假如使用new[]创建一个对象数组,则delete只会调用第一个对象的析构函数(造成内存泄漏),而delete[]会调用每个成员的析构函数。
用new分配的内存用delete释放,用new[]分配的内存用delete[]释放。
在C++中,内存被分成五个区:栈、堆、全局区/静态存储区、常量存储区、代码区
栈:存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。
堆:动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。
全局区/静态存储区:存放全局变量和静态变量,程序运行结束操作系统自动释放。
常量存储区:存放的是常量,不允许修改,程序运行结束自动释放。
代码区:存放代码,不允许修改,但可以执行。编译后的二进制文件存放在这里。
int g_var = 0; // g_var 在全局区
char *gp_var; // gp_var 在全局区
int main()
{
int var; // var 在栈区
char *p_var; // p_var 在栈区
char arr[] = "abc"; // arr 为数组变量,存储在栈区;"abc"为字符串常量,存储在常量区
char *p_var1 = "123456"; // p_var1 在栈区;"123456"为字符串常量,存储在常量区
static int s_var = 0; // s_var 为静态变量,存在静态存储区
p_var = (char *)malloc(10); // 分配得来的 10 个字节的区域在堆区
free(p_var);
return 0;
}
虚函数和纯虚函数都可以在子类中被重写
大端模式: 数据的高字节保存在内存的低地址中。
小端模式:数据的高字节保存在内存的高地址中。
// 在小端模式中
char a[4] = 0x11223344; // 则a[0] = 0x44, a[3] = 0x11
为什么要区分大小端:因为大小端有着各自的优点,比如小端存储当进行强制类型转换时不需要进行字节内容的调整,直接按照数据的大小尽心截断即可。而大端储存方式中符号位永远位于第一个字节,很方便判断正负。
与普通函数一样,只是它返回的是一个对象的指针。
注意:当函数返回指针时,一定要是一个指向全局变量、静态局部变量(两者均分配到全局存储区)或者分配到堆上的变量(使用malloc或者new分配的对象)的指针,因为局部变量的栈空间会在函数调用结束时被销毁,导致指针将访问一个未知的地址。
指向函数入口地址的指针,其声明需要明确的包含函数的返回类型和参数类型。
假如有函数:
void fun(int a, int b);
如果我们要定义一个指向函数fun的函数指针,则:
void (*p)(int, int);
p = fun; // 指向函数
p(4, 5); // 等同于fun(4, 5)
是一个数组,是一个存放元素是指针的数组,其定义如下:
int* a[10]; //存放十个int*的指针数组;
是一个指针,指针的类型是数组类型,即指针指向一个数组的首地址
int (*p)[10];
int a[5][10];
p = a; //p指向int类型 大小为n的数组指针,即该指针指向a的第一行数据
p++; //此时指针将指向a的第二行数组
和数组类似,是一段连续的内存空间,当插入新的元素内存不够时,通常以2倍重新申请更大的一块内存,将原来的元素拷贝过去,释放旧空间。因为内存空间是连续的,能很好的支持随机存取,因此vector::iterator支持“+”,“+=”,“<”等操作符,但是在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以list不支持随机访问,不支持“+”、“+=”、“<”等,时间复杂度为o(n); 但由于链表的特点,能高效地进行插入和删除。
extern可以置于变量或函数之前,表示变量或函数的定义在别的文件中,提示编译器在其它模块中寻找其定义。
注意:extern只能将其它文件中的非静态全局变量引入,而且在引入的同时不能进行赋值。相比较通过包含头文件的方式来引入其它文件中的变量,通过extern引入更为方便安全(不会扩大数据的作用域,不会破坏封装性)。
extern "C"的主要作用就是为了能够正确实现 C++ 代码调用其他C语言代码。加上 extern “C” 后,会指示编译器这部分代码按 C 语言的方式进行编译,而不是 C++ 的方式。
没有加static关键字的变量和函数具有全局可见性,加了static后,就会对其他源文件隐藏。
被static修饰的变量在程序刚开始运行时就会初始化,并存储在静态存储区(还有全局变量),生存期为整个源程序。
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
注意:和const的区别!!!const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象
修饰类的成员变量,表示常量不可能被修改
修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非const的成员函数
const函数只能调用const函数,非const函数可以调用const函数
int const p1中,const修饰p1的值,故p1的值不可改变,p1只能指向固定的一个变量地址,但可以通过p1来读写这个变量的值。
int const p2或者const int p2中,const修饰p1,故p1的值不可改变,即不可以给*p1赋值改变p1指向变量的值,但可以通过给p1赋值不同的地址来改变指针指向。
mutalbe的意思是“可变的,易变的”。
在C++中,mutable也是为了突破const的限制而设置的。
被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
volatile的作用: 确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。还可以保证volatile变量间的顺序性,编译器不会进行乱序优化。
可以,一个例子是只读状态寄存器。
只对一个实参的构造函数有效,只能在类内定义,它表示禁止对构造函数进行隐式转换。
当我们用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用,而不能使用赋值初始化。
explicit A(const string &s):a(s) {};
A a("abc");//直接初始化。
a.fun(static_cast<A>(cin));//强制执行显式转换。
public:可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问;
protected:可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问;
private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。
当子类是父类的public继承时,父类中的所有权限与原有的保持一致。
当子类是父类的protected继承时,父类中的public会降级为protected,其它不变。
当子类是父类的private继承时,父类中的所有权限都会降级为private类型。
把客观事物封装成抽象的类
实现继承、接口继承、可视继承
同一事物表现出不同事物的能力
重载是编译时多态,虚函数是运行时多态
浅拷贝:对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存,如下所示,这种默认的拷贝行为就是浅拷贝,这和调用 memcpy() 函数的效果非常类似。
class Base{
public:
Base(): m_a(0), m_b(0){ }
Base(int a, int b): m_a(a), m_b(b){ }
private:
int m_a;
int m_b;
};
int main(){
int a = 10;
int b = a; //浅拷贝
Base obj1(10, 20);
Base obj2 = obj1; //浅拷贝
return 0;
}
对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。
深拷贝:将对象和其所持其它资源一并拷贝,深拷贝必须显示地定义拷贝构造函数。
如果一个类拥有指针类型的成员变量,那么一般使用深拷贝,才能将指针指向的内容再复制出一份来,让原有对象和新对象相互独立不受影响。如果类的成员变量没有指针,一般使用浅拷贝。
隐式类型转换:指不需要用户干预,编译器私下进行的类型转换行为。
转换原则:基本数据类型的转换以低精度到高精度,即保证精度不丢失;自定义对象的转换以子类对象到父类对象。
隐式转换风险: 隐式转换的风险一般存在于自定义的类构造函数中。 按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。
explicit关键字:c++ 中的 explicit 关键字只能用于修饰只有一个参数的类构造函数,即作用为:禁止隐式调用类内的单参数构造函数。
显示类型转换就是指强制类型转换,主要有两种方式:函数型和类C型。
double x = 10.3;
int y;
y = int (x); // 函数型
y = (int) x; // 类C型
这些类型转换的通用形式的功能足以满足大多数基本数据类型的需求。但是,这些操作符可以不加区别地应用于类和指向类的指针上,这可能导致代码在语法正确的情况下导致运行时错误。编译器检查不出错误,可能导致运行时出错。对于指针来说,不受限制的显式类型转换允许将任何指针转换为任何其他指针类型,而不依赖于指针所指向的类型。
所以C++引入的四种类型转换,不同场景下不同需求使用不同的类型转换方式,同时有利于代码审查。
static_cast
const_cast
dynamic_cast
reinterpret_cast
C++11 赋予 auto 关键字新的含义,使用它来做自动类型推导,编译器会在编译期间自动推导出变量的类型,这样我们就不用手动指明变量的数据类型了。auto 除了可以独立使用,还可以和某些具体类型混合使用,这样 auto 表示的就是“半个”类型,而不是完整的类型。
注意:auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代。因为C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。
auto 类型推导的简单例子:
auto n = 10; //10 是一个整数,默认是 int 类型,所以推导出变量 n 的类型是 int。
auto f = 12.8; //12.8 是一个小数,默认是 double 类型,所以推导出变量 f 的类型是 double。
auto p = &n; //&n 的结果是一个 int* 类型的指针,所以推导出变量 p 的类型是 int*。
auto url = "http://c.biancheng.net/cplus/"; //由双引号""包围起来的字符串是 const char* 类型,所以推导出变量 url 的类型是 const char*,也即一个常量指针。
// auto与其它具体类型混合使用
int x = 0;
auto *p1 = &x; //p1 为 int *,auto 推导为 int
auto p2 = &x; //p2 为 int*,auto 推导为 int*
auto &r1 = x; //r1 为 int&,auto 推导为 int
auto r2 = r1; //r2 为 int,auto 推导为 int
int main(){
vector< vector<int> > v;
//vector< vector >::iterator i = v.begin();
auto i = v.begin(); //使用 auto 代替具体的类型
return 0;
}
decltype 是“declare type”的缩写,译为“声明类型”, 它与auto作用一样,都用来在编译时期进行自动类型推导。
它与auto的用法区别:
auto varname = value; //auto根据=右边value来推导变量类型
decltype(exp) varname = value; //decltype根据exp表达式来推导变量类型,与value无关,故可以不用初始化
decltype(exp) varname;
//普通表达式
int a = 0;
decltype(a) b = 1; //b 被推导成了 int
decltype(10.8) x = 5.5; //x 被推导成了 double
decltype(x + 100) y; //y 被推导成了 double
//函数声明
int& func_int_r(int, char); //返回值为 int&
int&& func_int_rr(void); //返回值为 int&&
int func_int(double); //返回值为 int
//decltype类型推导
decltype(func_int_r(100, 'A')) e = 1000; //a 的类型为 int&
decltype(func_int_rr()) f = 0; //b 的类型为 int&&
decltype(func_int(10.5)) g = 0; //c 的类型为 int
//带有括号的表达式
decltype(a) h = 0; //obj.x 为类的成员访问表达式,符合推导规则一,a 的类型为 int
decltype((a)) b = 100; //obj.x 带有括号,符合推导规则三,b 的类型为 int&。
//加法表达式
int n = 0, m = 0;
decltype(n + m) c = 0; //n+m 得到一个右值,符合推导规则一,所以推导结果为 int
decltype(n = n + m) d = c; //n=n+m 得到一个左值,符号推导规则三,所以推导结果为 int&
匿名函数即没有名字的函数。使用匿名函数,可以免去函数的声明和定义。这样匿名函数仅在调用函数的时候才会创建函数对象,而调用结束后立即释放,所以匿名函数比非匿名函数更节省空间。
C++中的匿名函数通常定义为
[capture](parameters)->return-type{body}
当parameters为空的时候,()可以被省去,当body只有一个return或者返回为void,那么”->return-type“可以被省去,下面对其中的参数一一解释
capture:
parameters:存储函数的参数
return-type:函数的返回值
body:函数体
直接写在main函数中,与inline函数相似。
int main(){
int x=1,y=2,z=0;
auto add = [&z](auto x,auto y){z=x+y;return z;};
auto res = add(x,y);
cout<<res<<z<<endl;
}
与sort()函数等参数中可传入函数的方法等搭配
int main()
{
int num[4] = {4, 2, 3, 1};
//对 a 数组中的元素按照从小到大进行排序
sort(num, num+4, [=](int x, int y) -> bool{ return x < y; } );
return 0;
}
C++ 11 标准中,为 for 循环添加了一种全新的语法格式,(与旧版限定遍历范围不同,新版的只会逐个遍历指定序列的每个元素),如下所示:
// declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。可以用auto
// expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。
for (declaration : expression){
//循环体
}
下面程序演示了如何用 C++ 11 标准中的 for 循环遍历实例一定义的 arc 数组和 myvector 容器:
int main() {
char arc[] = "http://c.biancheng.net/cplus/11/";
//for循环遍历普通数组
for (char ch : arc) {
cout << ch; // 输出的字符串和 "!" 之间还输出有一个空格,这是因为新格式的 for 循环在遍历字符串序列时,
// 不只是遍历到最后一个字符,还会遍历位于该字符串末尾的 '\0'(字符串的结束标志)。
}
cout << '!' << endl; //http://c.biancheng.net/cplus/11/ !
vector<char>myvector(arc, arc + 23);
//for循环遍历 vector 容器
for (auto ch : myvector) { //这里的 ch 不是迭代器类型,而表示的是 myvector 容器中存储的每个元素。
cout << ch;
}
cout << '!'; //http://c.biancheng.net/!
for (int num : {1, 2, 3, 4, 5}) {
cout << num << " "; //1 2 3 4 5
}
return 0;
}
传统的指针,没有及时释放不再使用的内存资源,容易造成堆内存泄漏(忘记释放),二次释放(会导致程序运行崩溃),资源已被释放但是指针没有改变执行(成为了野指针)。
智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。
C++11 标准提出了三个新的智能指针:unique_ptr、shared_ptr和 weak_ptr。它们是 C++11 标准提供的模板类,用于管理 new 申请的堆内存空间。需要包含头文件memory
这些模板类定义了一个以堆内存空间(new 申请的)指针为参数的构造函数,在创建智能指针对象时,将 new 返回的指针作为参数。同样,这些模板类也定义了析构函数,在析构函数中调用 delete 释放 new 申请的内存。当智能指针对象生命周期结束时,系统调用析构函数释放 new 申请的内存空间。
创建 unique_ptr 智能指针对象的语法格式如下所示:
unique_ptr<T> 智能指针对象名称(指针); //指针指用new运算符申请堆空间返回的指针
unique_ptr 智能指针的用法示例代码如下所示:
unique_ptr<int> pi(new int(10)); //智能指针对象 pi,用于管理一个 int 类型堆内存空间指针;
class A {…};
unique_ptr<A> pA(new A); //智能指针对象 pA,用于管理一个 A 类型的堆内存空间指针。
当程序运行结束时,即使没有 delete,编译器也会调用 unique_ptr 模板类的析构函数释放 new 申请的堆内存空间。
unique_ptr 智能指针对象之间不可以赋值,如果需要实现 unique_ptr 智能指针对象之间的赋值,可以调用 C++ 标准库提供的 move() 函数,示例代码如下所示:
unique_ptr<string> ps(new string("C++"));
unique_ptr<string> pt;
pt = move(ps); //正确,可以通过编译
pt = ps; //错误,不能对unique_ptr智能指针赋值
shared_ptr 是一种智能级别更高的指针,它在实现时采用了引用计数的方式,多个 shared_ptr 智能指针对象可以同时管理一个 new 对象指针。
每增加一个 shared_ptr 智能指针对象,new 对象指针的引用计数就加 1;当 shared_ptr 智能指针对象失效时,new 对象指针的引用计数就减 1,而其他 shared_ptr 智能指针对象的使用并不会受到影响。只有在引用计数归为 0 时,shared_ptr 才会真正释放所管理的堆内存空间。
shared_ptr 提供了一些成员函数以更方便地管理堆内存空间,下面介绍几个常用的成员函数。
下面通过案例演示 shared_ptr 智能指针的使用,C++ 代码如下:
int main()
{
//创建shared_ptr智能指针对象language1、language2、language3
shared_ptr<string> language1(new string("C++"));
shared_ptr<string> language2 = language1;
shared_ptr<string> language3 = language1;
//通过智能指针对象language1、language2、language3调用get()函数
cout << "language1: " << language1.get() << endl; //language1: 0x1410e70
cout << "language2: " << language2.get() << endl; //language2: 0x1410e70
cout << "language3: " << language3.get() << endl; //language3: 0x1410e70
cout << "引用计数:";
cout << language1.use_count() <<" "; // 3
cout << language2.use_count() <<" "; // 3
cout << language3.use_count() <<endl; // 3
language1.reset();
cout << "引用计数:";
cout << language1.use_count()<<" "; // 0
cout << language2.use_count()<<" "; // 2
cout << language3.use_count() << endl; // 2
cout << "language1: " << language1.get() << endl; //language1: 0
cout << "language2: " << language2.get() << endl; //language2: 0x1410e70
cout << "language3: " << language3.get() << endl; //language3: 0x1410e70
return 0;
}
相比于 unique_ptr 与 shared_ptr,weak_ptr 智能指针的使用更复杂一些,它可以指向 shared_ptr 管理的 new 对象,却没有该对象的所有权,即无法通过 weak_ptr 对象管理 new 对象。
weak_ptr 最常见的用法是验证 shared_ptr 对象的有效性。
为消除函数调用的时空开销,C++提供一种提高效率的方法,即在编译时将函数调用处用函数体来替换,类似于宏定义,这种函数称为内联函数(Inline Function)。
用法:在函数定义前添加关键字inline,一般只将短小的、频繁调用的函数声明为内联函数。
注意:内联函数里面不能使用循环语句
借助友元(friend),可以使其它类中的函数或全局函数访问当前类的private成员,(即、将B类中的函数或者全局函数,在A类中声明为友元函数,那么该函数便可以访问A类中的一切成员)
class Student{
public:
Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){}
friend void show(Student *pstu); // 将全局函数show声明为友元函数
private:
char *m_name;
int m_age;
float m_score;
};
void show(Student *pstu){
cout << pstu->m_name << pstu->m_age << pstu->m_score << endl; // 访问Student的私有成员
// cout << m_name << m_age << m_score << endl; // 不能直接使用Student的成员,要借助对象,因为show不是成员函数,没有this指针
}
上述可将其他类的成员函数声明为友元函数;还可以将整个类声明为另一个类的“朋友”,即友元类,友元类中的所有成员都是另一个类的友元函数。
父类构造函数 -> 子类成员函数的构造函数 -> 子类构造函数
默认构造函数
初始化构造函数(带参数)
拷贝构造函数:当使用实例化对象初始另一个对象时调用
移动构造函数:(move和右值引用)
委托构造函数
转换构造函数
子类析构函数 -> 子类成员函数的析构函数 -> 父类析构函数
使用虚函数,可以使父类指针访问子类的成员(包括成员函数和成员变量),它的唯一用途就是构成多态,即,父类指针即可以按照父类的方式做事,又可以按照子类的方式做事,有多种形态。
class{
public:
virtual void area(int a, int b) = 0; // 将area声明为纯虚函数,其子类将其实现,才可以创建对象
};
运算符重载规则:
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定)内存块,使用完后必须显式释放的内存。应用程序般使用malloc、realloc、 new等函数从堆中分配到块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了
计数法:使用new或者malloc时,让该数+1,delete或free时,该数-1,程序执行完打印这个计数,如果不为0则表示存在内存泄露
一定要将基类的析构函数声明为虚函数
对象数组的释放一定要用delete []
普通不加任何参数的new,它在空间分配失败的情况下,抛出异常bad_alloc,而不是返回NULL
int main()
{
try
{
char *p = new char[10e11]; // 内存分配失败
delete p;
}
catch (const bad_alloc &ex)
{
cout << ex.what() << endl;
}
return 0;
}
//执行结果:bad allocation
加参数nothrow,在空间分配失败时不抛出异常,而是返回NULL
int main()
{
char *p = new(nothrow) char[10e11]; // 内存分配失败
if (p == NULL)
{
cout << "alloc failed" << endl;
}
delete p;
return 0;
}
//运行结果:alloc failed
这类new允许在一块已经分配成功的内存上重新构造对象,不用担心内存分配失败,因为它不分配内存,只是调用对象的构造函数。
零拷贝是一种避免CPU将数据从一块存储拷贝到另一块存储的技术,可以减少数据拷贝和共享总线操作的次数。
在C++中,vector的一个成员函数**emplace_back()很好地体现了零拷贝技术,它跟push_back()**函数一样可以将一个元素插入容器尾部,区别在于:使用push_back()函数向容器尾部添加元素时,首先会创建这个元素,然后将这个元素拷贝或移动到容器中;而使用emplace_back()是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
**定义:**当发生某种事件时,系统或者其它函数自动调用的一段函数(相当于中断处理函数和QT中的槽函数)
**本质:**回调函数就是一个通过函数指针调用的函数,把函数的地址作为参数传递给另一个函数
内存池(memory pool)是一种内存分配方式,在真正使用内存之前,先申请分配一定数量的内存块作内存池,当有新内存需求时,从内存池中分出一部分内存块,避免了多次向内存申请,产生大量的内存碎片。