如何设置GNU编译器对C++11的支持
运行编译器的时候指定-std = C++11
参数
黑窗口下编译运行源文件
// windows下
gcc test.c -o test //-o表示指定可执行文件的文件名
.\test //运行程序
g++ test.cpp -o test
C++语言特点
C++是一种静态数据类型语言,它的类型检查发生在编译时。
基本内置类型
- wchar_t:宽字符
- char16_t:Unicode字符
- char32_t:Unicode字符
- long:四个字节
- long long:八个字节
类型转换注意的问题
赋值给带符号类型一个超出他表示的范围值时,结果是未定义的。赋值给无符号类型一个超出他表示的范围值时,结果是初始值对无符号类型表示数值总数取模后的余数。
int a = 2147483647;
int b = 2147483648; // -2147483648
printf("%d\t%d",a,b);
unsigned char c = -1;
printf("%d",c); // 255
// 有符号整数先转为无符号整数
unsigned u = 10;
int i = -42;
std::cout << i + u << std::endl; // 4294967264
"a"与'a'的区别
- "a"表示一个字符数组,该数组包含两个字符,一个a另一个是空字符'\0'
- "a"占用两个字节
默认初始化
默认值到底是什么,由变量类型和定义变量的位置决定。
- 如果是内置类型的变量未被显示初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0;定义在函数体内部的内置类型变量将不被初始化,其值未定义。
- 类的对象如果没有显示的初始化,则其值由类决定
变量定义和声明的关系
- 分离式编译:允许将程序分割为若干文件,每个文件可以被独立编译。
- 为了支持分离式编译,C++将声明和定义区分开来。声明使得名字为程序所知,一个文件想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。
- 定义和声明的区别
1. 声明规定了变量的类型和名字
2. 定义还申请存储空间,可能会为变量赋予初始值
extern int i; // 声明
int j; // 声明并且定义
extern int k = 0; // 定义
- 声明一个变量而非定义它,在变量名之前添加关键字extern,而且不要显示的初始化变量
extern int i; // 声明一个变量
常量指针与指针常量
- 常量指针:指针本身即为常量,存放在指针中的那个地址不能改变。常量指针必须初始化。是一种指针。
int *const ptr = &number;
- 指针常量:不能修改指针所指向的对象,对该指针来说,对象是常量。对该指针来说,哈哈
const int *ptr = &number;
关键字constexpr和常量表达式
1.常量表达式
- 常量表达式:指值不会改变,并且在编译过程中就可以得到计算结果的表达式。两个要点:其值不会改变,编译过程中确定结果。
const int a = getNumber(); //a不是一个常量表达式
const int b = 100; //b是一个常量表达式
2.constexpr变量
- constexpr变量:因为很难分辨一个初始值是不是常量表达式,所以C++11允许将变量声明为constexpr类型以便让编译器来验证变量的值是否为常量表达式。、
// 声明为constexpr的变量一定是一个常量
constexpr int i = getI(); // getI()必须是一个constexpr函数
3.指针和constexpr
constexpr int *p = nullptr; // p是一个指向整数的常量指针
定义类型别名
- 传统方法使用关键字typedef
typedef unsigned int u_int32;
typedef char* pstring;
// const是对给定类型的修饰
const pstring cstr = "Hello"; // cstr为一个指向char的常量指针
- 使用别名声明来定义类型的别名
using u_int32 = uunsigned int;
// 下述两句语句等价
using arrT = int[10];
typedef int arrT[10];
类型说明符auto
auto可以让编译器通过初始值推断变量的类型。所以auto定义的变量必须有初始值。
auto i = 0, *p = &i;
类型指示符decltype
选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到他的类型却不实际计算表达式的值。
decltype( f() ) sum = x; // sum变量的类型为函数f的返回类型
decltype(function_name)* // 指明函数指针类型
使用decltype的时候,左值和右值也有所不同。如果表达式的求值结果是左值,decltype作用该表达式将得到一个引用类型
int i = 100;
int* ptr = &i;
decltype(*ptr) j = i; // ptr的类型为int&
decltype(&ptr) pp = &ptr; // pp的类型为int**
数组中不允许拷贝和赋值
int a[] = {1, 2};
int b[] = {2, 3};
int c[] = a; // array must be initialized with a brace-enclosed initializer
b = a; // invalid array assignment
标准库函数begin和end
// 数组的遍历
int a[] = {100,200,300};
for (int *p = begin(a); p != end(a); p++)
{
cout << *p << endl;
}
C风格字符串的一些注意事项
注意事项:
1.C风格的字符串最好以空字符结尾
char a[] = {'h','l','o'};
// strlen函数内部遇到空字符才停下来,
// 而形参是一个不包含空字符的字符数组
cout << strlen(a) << endl; // 6
2. 比较两个C风格字符串使用strcmp函数
const char ch1[] = "Hello";
const char ch2[] = "Hellp";
printf("%p,%p",ch1,ch2);
// 使用数组的时候真正使用的是指向数组首元素的指针
// 比较的是两个指针的值
cout << (ch1 > ch2) << endl; // 1
空语句
; // 表示空语句
除非必须,否则不用递增递减运算符的后置版本
后置版本需要将原始值存储下来以便于返回这个未修改的内容。
位运算符
- 位运算符:位运算符作用于整数类型的运算对象,并将运算对象看成是二进制位的集合。
- 常用的位运算符
sizeof运算符的返回值是一个常量表达式
命名的强制类型转换
1. 一个命名的强制类型转换具有如下形式:
// cast-name是dynamic_cast,static_cast,const_cast,reinterpret_cast的其中一种。
cast-name<type>(expression)
2.static_cast
// 使用static_cast找回void*指针中的值
int i = 100;
int* p = &i;
void* vp = (void*)p;
// cout << *vp << endl;error
int* pp = static_cast<int*>(vp);
cout << *pp << endl;
3.const_cast
可以将常量对象转换为非常量对象,即去掉某个对象的const性质。只有const_cast可以转换表达式的常量属性
int i = 100;
const int* pi = &i;
int* p = const_cast<int*>(pi);
*p = 200;
cout << i << endl; // 200
// 对象本身就是一个常量,使用强制类型转换获得写权限执行写操作
// 将产生未定义的行为
const char* pstr = "Hello";
char* ps = const_cast<char*>(pstr);
// 产生未定义的行为
ps[0] = 'W';
cout << ps << endl;
4.reinterpret_cast
5.dynamic_cast
dynamic_cast
局部静态变量
局部变量分为自动变量和局部静态变量。内置类型的局部静态变量默认初始化为0.
参数传递的两种方式
- 值传递:比如指针作为形参
- 引用传递:比如数组的引用作为形参
void print(int (&arr)[3])
{
for (auto& i : arr)
{
cout << i << endl;
}
}
优先选择引用传递,它可以避免拷贝
引用不是对象,它是别名
不能用字面值初始化一个非常量引用。
// int& i = 100; // error
// int&& i = 100; // OK
// const int& i = 100;// OK
含有可变形参的函数
1.initial_list作为形参
2.省略符形参
不要返回局部对象的引用或者指针
数组指针或者引用作为返回值
因为数组不能被拷贝,所以函数不能返回数组。但是函数可以返回数组的指针或者引用。
// func函数接受一个int类型的实参,返回一个指针,该指针指向一个数组
auto func(int i) -> int(*)[10];
assert是一种预处理宏(预处理变量)
如果文件中定义了NDEBUG宏,则assert什么也不做。默认状态下没有定义NDEBUG宏,此时assert将执行运行时检查。assert应该作为调试程序的一种手段。
constexpr函数
- 函数的返回值和所有形参的类型都得是字面值类型
- 函数体中必须有且只有一条return语句。
- constexpr函数被编译器隐式的指定为内联函数
和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义。但是对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。因此,内联函数和constexpr函数通常定义在头文件中。
类
1.const成员函数
使用const的成员函数被称为常量成员函数。类中的成员函数有一个隐式参数this指针,默认是指针常量。使用const关键字修饰该成员函数后,this指针就成了既是指针常量也是常量指针。
// 常量对象,以及常量对象的指针或者引用只能调用常量成员函数
class Example
{
public:
void print()
{
cout << "world" << endl;
}
//void print()const
//{
// cout << "Hello" << endl;
//}
};
const Example ex; // 定义一个常量对象
ex.print(); // error
2.建议最好手动提供无参构造函数
当类中没有声明任何构造函数时,编译器会自动生成默认构造函数。但是,在C++中,如果没有为类中的成员变量提供属性值(类内初始值)或者为类提供构造函数,则由编译器初始化的属性的值是我们所预料不到的(默认初始化)。这个情况和Java中不同。
class Example
{
private:
int a;
int *p;
public:
void show()const
{
cout << a << p << endl; // 8,0x31
}
};
int main()
{
Example a;
a.show();
return 0;
}
提供类内初始值的两种方式:
- 使用等号:
int i = 100
- 使用花括号
std::vector<string>vec{"Hello"}
3.使用struct或者class定义类的区别
唯一区别:默认访问权限不同,前者默认公有,后者默认私有。
4.友元
- 友元函数:友元函数可以是某个全局函数或者某个类的成员函数,但是在某个类中定义的友元函数不是该类的成员函数
- 友元类:如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员(包括私有成员)。
class Example
{
// 将Test类指定为Example的友元
friend class Test;
}
5.内联函数
- 定义在类内部的成员函数是自动内敛(inline)的,隐式内联
- 显示内敛,使用inline关键字
6.mutable关键字声明可变成员变量
在常量成员函数中,可以修改使用mutable关键字声明的变量,即使调用该成员函数的对象是一个const修饰的对象。
class Example
{
private:
mutable int i = 100;
public:
inline int getI()const
{
i = 1000; // no problem
return i;
}
};
int main()
{
const Example e;
cout << e.getI() << endl; // 1000
return 0;
}
7.作用域运算符
- 指定类作用域:
类名::
- 指定全局作用域:
::
int x = 200;
class Example
{
private:
int x = 100;
public:
int getX()const
{
//return Example::x; // 100
return ::x; // 200
}
};
int main()
{
Example x;
cout << x.getX() << endl;
return 0;
}
8.构造函数中尽量使用构造函数初始值列表
如果成员变量是const,或者引用,或者属于某种未提供默认构造函数的类类型,则必须通过构造函数初始值列表或者类内初始值为这些成员提供初值。构造函数体内执行的是赋值操作,类中的属性的初始化是在初始值列表中完成。
class Example
{
private:
const int c;
int& r;
int i;
public:
Example():c(100),r(i),i(10)
{
}
void show()const
{
cout << c << r << i << endl;
}
};
9.最好令构造函数初始值的顺序和成员变量声明的顺序保持一样
构造函数中初始值列表中初始值的顺序不会影响实际的初始化顺序
class Example
{
private:
int i, j;
public:
// warning:when initialized here [-Wreorder]
Example(int val):j(val),i(j)
{
}
void show()const
{
cout << i << "\t" << j << endl; // i的结果不可预料
}
};
10.转换构造函数
转换构造函数:可以通过一个实参调用的构造函数定义了一条从构造函数的参数类型向类类型隐式转换的规则。
11.explicit关键字的使用
在要求隐式转换的程序上下文中(仅含有一个形式参数的构造函数),可以通过将构造函数声明为explicit加以阻止。使用explicit关键字声明了构造函数后,将只能以直接初始化的形式定义类类型的对象,而不是由编译器使用explicit声明的构造函数自动转换。
class Example
{
private:
int x;
public:
explicit Example(int x):x(x)
{
}
void show()const
{
cout << x << endl;
}
};
int main()
{
//Example x = 100; // error
Example x(100);
x.show();
return 0;
}
IO库
1.IO类
- iostream头文件中定义了用于读写流的基本类型
从流读取数据:istream,wistream
从流写入数据:ostream,wostream
读写流:iostream,wiostream
- fstream头文件定义了读写命名文件的类型
从文件读取数据:ifstream,wiftream
向文件写入数据:ostream,woftream
读写文件:ftream,wftream
- sstream头文件定义了读写内存string对象的类型
从string中读取数据:istringstream,wistringstream
向string写入数据:ostringstream,wostringstream
读写string:stringstream,wstringstream
类fstream和stringstream都是继承自iostream的。
2.IO库的条件状态
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态完整的功能。这个类型应作为一个位集合来使用,它定义了四个constptr值:
- badbit:表示系统级错误,如不可恢复的读写错误。badbit被置位,流就无法使用
- failbit:比如期望读取数值却读出一个字符等错误。这种问题可以修正,流还可以继续使用。
- eofbit:达到文件结束位置
- goodbit:表示流未发生错误
IO库还定义了一组函数查询这些标志位的状态。如上图所示。
3.刷新输出缓冲区
使用IO库中的操纵符endl,flush,ends
cout << "hi" << endl; // 输出hi和一个换行,然后刷新缓冲区
cout << "hi" << flush; // 输出hi然后刷新缓冲区,但是不输出任何额外的字符
cout << "hi" << ends; // 输出hi和一个空字符,然后刷新缓冲区
4.unitbuf操纵符的使用
5.fstream的特有操作
6.文件模式
注意事项:
7.以out模式打开文件会丢弃已有的数据
以out模式打开文件,通常意味着同时使用trunc模式。
// file1都将会被截断
//ofstream of(file1);
//ofstream of(file1,ofstream::out);
//ofstream of(file1,ofstream::out | ofstream::trunc);
解决方案:显式指定app模式
ofstream of(file1,ofstream::out | ofstream::app);
8.stringstream特有的操作
容器类型
1. 顺序容器类型
note1:forward_list中不存在尾指针
note2:array相当于静态数组,vector即动态数组
2.三个顺序容器适配器
一个容器适配器接受一种已有的容器类型,其内部含有一个实现适配器的底层容器类型container_type.
- stack
- queue
- priority_queue
3.关联容器类型
前四个有序关联容器定义在map,set头文件中,后四个无序关联容器定义在unordered_map,unordered_set头文件中。这八个容器间的不同:
1. 要么是一个set,要么是一个map
2. 要么要求不重复的关键字,要么允许重复的关键字。是否重复看multi
3. 要么元素有序(关键字按照顺序存储),要么无序保存.是否无序看unordered
顺序容器
1.顺序容器中的emplace
- emplace_front,emplace,emplace_back,这些成员函数直接将参数传递给容器中元素类型对应的构造函数,分别对应于push_front,insert,push_back。emplace系列的成员函数使用这些参数在容器管理的空间中直接构造元素。
class Example
{
private:
int i = 0;
int j = 0;
public:
Example()
{
}
Example(int i, int j):i(i),j(j)
{
cout << "called" << endl;
}
};
int main()
{
vector<Example>vec;
//vec.push_back(10,10);// error
vec.emplace_back(10, 10);
}
2.下标操作和安全的随机访问
随机访问顺序容器中的元素时,使用at成员函数(类似于下标运算符),因为如果下标越界at会抛出一个out_of_range异常,而使用下标运算符,使用了不合法的下标,程序直接运行时出错。
vector<int>vec;
try
{
// vector::_M_range_check: __n (which is 0) >= this->size()
// (which is 0)
cout << vec.at(0) << endl;
}
catch (out_of_range out)
{
cout << out.what() << endl;
}
3.string和数值之间的转换
4.vector的内存分配策略
在每次需要分配新内存空间时将当前容量翻倍。
vector<int>vec;
cout << vec.size() << vec.capacity() << endl; // 00
vec.reserve(50);
cout << vec.capacity() << endl; // 50
while (vec.size() != vec.capacity())
{
vec.push_back(10);
}
vec.push_back(50);
cout << vec.capacity() << endl; // 100
vec.shrink_to_fit();
cout << vec.capacity() << endl; // 51
迭代器
0.迭代器类别
1.插入迭代器
1.插入迭代器的作用:这些迭代器绑定到一个容器上,可用来向容器插入元素
2.插入迭代器的操作
3.插入迭代器的获取:插入器是一种迭代器适配器,他接受一个容器,生成一个迭代器,能实现向给定容器添加元素。当通过一个插入迭代器进行赋值时,该迭代器调用容器操作来向给定容器的指定位置插入元素。
4.插入器的类型
// 示例
vector<int> vec{1,3};
auto end = vec.end();
// 获取一个插入迭代器
auto iterator = inserter(vec, end);
*iterator = 5;
for (auto i : vec)
{
cout << i << endl; // 135
}
2.流迭代器
- 流迭代器的作用:这些迭代器绑定到输入流或者输出流,可用来遍历所关联的IO流。
- 流迭代器的种类:
istream_iterator:读取输入流
ostream_iterator:向一个输出流写数据
- istream_iterator使用
vector<int>vec;
// 使用istream_iterator从标准输入读取数据
istream_iterator<int> in_iter(cin);
// eof被定义为空的istream_iterator,可以当作尾后迭代器使用
istream_iterator<int> eof;
while (in_iter != eof)
{
vec.push_back(*in_iter++);
}
// 上述代码等价于下面两行
istream_iterator<int>in_iter(cin), eof;
vector<int> vec(in_iter, eof);
// 使用算法操作流迭代器的一个实例
// 计算从标准输入读取的整数值的和
istream_iterator<int> in_iter(cin), eof;
auto i = accumulate(in_iter, eof, 0);
- ostream_iterator的常用操作
- ostream_iterator的使用示例
// 打印vector中的元素
vector<int>vec{1,3,5};
ostream_iterator<int> out_iter(cout, " ");
copy(vec.begin(), vec.end(), out_iter);
3.反向迭代器
- 反向迭代器:在容器中从尾元素向首元素反向移动的迭代器。除了forward_list,其他容器都支持反向迭代器。
- 反向迭代器的获取:通过rbegin,rend,crbegin,crend成员函数获取。这些函数返回尾元素的迭代器和首前迭代器。
- 反向迭代器使用示例
int number;
// 逆序遍历vector中的元素
vector<int> vec;
while (cin >> number)
{
vec.push_back(number);
}
auto iter_begin = vec.crbegin(), iter_end = vec.crend();
for (; iter_begin != iter_end; iter_begin++)
{
cout << *iter_begin << endl;
}
- base方法:将一个reverse_iterator转换为一个普通迭代器,指向的元素位置是
pointing to the element next to the one the reverse_iterator is currently pointing to
4.移动迭代器
这些迭代器移动容器中的元素,而不是拷贝他们
5.两个概念:首前迭代器和尾后迭代器
首前迭代器:第一个元素之前元素的迭代器
尾后迭代器:最后一个元素之后元素的迭代器
定制操作
谓词
谓词是一个可调用的表达式,其返回结果能用做条件的值。标准库算法所使用的谓词分为一元谓词和二元谓词。一元谓词只接收一个参数,二元谓词只接受两个参数。接受谓词作为参数的算法对输入序列中的元素调用谓词,因此元素类型必须能够转换为谓词的参数类型
lambda表达式
关联容器
1.关联容器中关键字类型的要求
- 有序关联容器关键字类型的要求:关键字类型必须定义元素比较的方法
2.pair类型
一个pair保存两个数据成员,它是一个用来生成特定类型的模板。当创建一个pair时需要提供两个类型名。pair定义在头文件utility中。四种与map相关的类中的value_type就是pair类型,可以改变pair的值,但是不能改变关键字成员的值。set类型中的元素也是const的。理由如下:
typedef std::pair<const _Key, _Tp> value_type;
使用pair的简单示例:
pair<string, size_t> _pair;
// pair的默认构造函数对数据成员进行值初始化
cout << _pair.first << ":" << _pair.second << endl; // :0
_pair = make_pair("zhangsan",23);
cout << _pair.first << ":" << _pair.second << endl; // zhangsan:23
pair的常用操作:
3.关联容器的插入操作
4.关联容器的删除操作
5.关联容器的访问操作
map
1.map和unordered_map的下标操作和at函数
- 对一个map或者unordered_map使用下标操作,将返回对应关键字的元素。如果使用一个不在容器中的关键字作为下标,会添加一个具有此关键字的元素到map中(对其进行值初始化)。使用下标运算符返回的类型为mapped_type,左值
注意:不能使用下标运算符来检查一个元素是否存在,因为如果关键字不存在的话,下标运算符会插入一个新元素。这种情况下应该使用find函数
multimap<string, size_t> _map = {{"张三",2011},{"李四",2012}};
multimap<string, size_t>::iterator iter;
if ((iter = _map.find("张三")) != _map.cend())
{
cout << iter->second << endl; // 2011
}
- 对于at函数:访问对应关键字的元素,如果关键字不在容器中,会抛出一个out_of_range异常。
multimap或者multiset中查找元素
- 方法1:使用find和count成员函数
// 查找姓名张三的所有人的学号
multimap<string, size_t> _map = {{"张三",2011},{"李四",2012},{"张三",2018},{"张伟",2013}};
auto iter = _map.find("张三");
size_t _count = _map.count("张三");
cout << _count << endl; // 2
while (_count > 0 && iter != _map.end())
{
cout << iter->first << "," << iter->second << endl;
iter++;
_count--;
}
- 方法2:使用lower_bound和upper_bound成员函数
// 查找姓名张三的所有人的学号
multimap<string, size_t> _map = {{"张三",2011},{"李四",2012},{"张三",2018},{"张伟",2013}};
for (auto begin = _map.lower_bound("张三"), end = _map.upper_bound("张三"); begin != end; begin++)
{
cout << begin->first << "," << begin->second << endl;
}
- 方法3:使用equal_range成员函数
// 查找姓名张三的所有人的学号
multimap<string, size_t> _map = {{"张三",2011},{"李四",2012},{"张三",2018},{"张伟",2013}};
for (auto pos = _map.equal_range("张三"); pos.first != pos.second; pos.first++)
{
cout << pos.first->first << "," << pos.first->second << endl;
}
无序关联容器
1.无序关联容器的管理操作
智能指针
新的标准库提供了智能指针类型:unique_ptr,shared_ptr。两者的区别是前者独占所指向的对象,后者允许多个指针指向同一个对象。标准库还定义了一个weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
shared_ptr和unique_ptr支持的共有操作
1.shared_ptr类
- shared_ptr的独有操作
- make_shared函数
// 类似顺序容器的emplace成员函数,make_shared函数用其参数构造给定类型的对象
shared_ptr<string> s_ptr = make_shared<string>(10,'h');
cout << *s_ptr << endl; // hhhhhhhhhh
// 函数不传递任何参数,对象就进行值初始化
// 默认初始化一个空串
shared_ptr<string> s1_ptr = make_shared<string>();
cout << int((*s1_ptr)[0]) << endl; // 0
- shared_ptr的拷贝和赋值
- 定义和改变shared_ptr的其他方法
- 不要使用get成员函数初始化另一个智能指针或者为智能指针赋值(难以避免delet掉eget()成员函数返回的指针)
shared_ptr<int> ptr(new int(100));
cout << ptr.use_count() << endl; // 引用计数为1
// 如果delete掉pi指针,则使用ptr是未定义的行为
int* pi = ptr.get();
cout << ptr.use_count() << endl; // 引用计数为1,并不会递增
2.unique_ptr类
- unique_ptr的操作
- unique_ptr类中不支持拷贝和赋值,可以通过调用release或者reset成员函数将指针的所有权从一个unique_ptr转移到另一个unique_ptr.
unique_ptr<int> ptr_src(new int(100));
// 将指针的所有权转移
unique_ptr<int> ptr_des(ptr_src.release());
//cout << *ptr_src << endl; // 未定义值
cout << *ptr_des << endl; // 100
unique_ptr<int> ptr_des1;
ptr_des1.reset(ptr_des.release());
//cout << *ptr_des << endl; // 未定义值
cout << *ptr_des1 << endl; // 100
3.weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象,== 将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。==
- weak_ptr的常用操作
// 使用一个shared_ptr来初始化weak_ptr,weak_ptr和shared_ptr共享一个对象
shared_ptr<int> ptrs = make_shared<int>(100);
weak_ptr<int> ptrw(ptrs);
cout << ptrs.use_count() << endl; // 1,并未增加
// 访问共享对象
if (shared_ptr<int> ptrtemp = ptrw.lock())
{
cout << *ptrtemp << endl; // 100
}
- weak_ptr的应用:其lock成员函数检查shared_ptr管理的对象是否存在
4.智能指针与动态数组(大小可变)
unique_ptr可以管理new分配的数组;shared_ptr不直接支持管理动态数组,如果希望管理动态数组,需要提供自定义的删除器。
unique_ptr<int[]> ptr(new int[4]());
cout << ptr[3] << endl; // 0
5.allocator类
new和delete有灵活性上的限制。new一方面表现在它将内存分配和对象构造组合在了一起。delete则将对象析构和内存释放组合在了一起。allocator类将内存分配和对象构造分离开来。他提供一种类型感知的内存分配方法,他分配的内存是原始的,未构造的。
- allocator类及其算法