wangj第 八章 IO库
8.1 IO类
为了支持宽字符的语言,在原先的基础上添加一个w,比如cin、cout 对应的是 wcin与wcout
IO对象无拷贝或者赋值
条件查询
列出一些函数和标志,可以帮助我们访问和操作流的条件状态
每一个输出流都管理一个缓冲区,执行这个代码,不一定立刻打印出来,也可以保存在缓冲区后面打印,要是缓冲区刷新的话就一定打印出来,加上一个endl可以刷新缓冲区
os << "please enter a value " ;
endl、fiush、ends 都是刷新缓冲区
ubitbuf操作符
告诉流在接下来的每次写操作之后都进行一次flush刷新
nounitbuf
回到正常的缓冲方式
关联输入输出流
当一个输入流被关联到输出流,任何试图从输入流读取数据的操作都会先刷新关联的输出流
cin >> val 导致cout的缓冲区被刷新
8.2 文件输入
ifstream :从一个给定文件读取数据
ofstream : 向一个给定文件写入数据
fstream: 读写给定文件
将文件中的数据写入vector容器中
按行写
string infile = "1.txt";
vector<string>v;
ifstream in(file);//创建一个文件流对象
if(in)//流中还有数据
{
string buf;
while(getline(in,buf)) //获取in流中的一行数据给buf
v.push_back(buf);
}
else
cerr << "cannot open this file:" << infile << endl;
按字符写
string infile = "1.txt";
vector<string>v;
ifstream in(file);//创建一个文件流对象
if(in)//流中还有数据
{
string buf;
while(in>>buf) //获取in流中的一个字符给buf
v.push_back(buf);
}
else
cerr << "cannot open this file:" << infile << endl;
文件模式
读写是相对cpu而言的,
读是指将文件数据写给别人,写是将流中对象写入文件中
8.3 string 流
sstream头文件定义了上类型来支持内存io
- istringstream:从string读取数据
- ostringstream:向string写入数据
- stringstream:既可以读又可以写
小结:
iostream 处理控制台
frstream 处理文件io
stringstream 完成内存string的io
类fstream和stringstream都是继承类iostream
输出类都是继承自istream
输出类都是继承自ostream
第九章 顺序容器
9.1 概述
顺序容器就是排放顺序与先后加入容器有关,所有的容器都是基于模板实现的因为要可以装下任何类型的数据,c++内置一个序列式的容器array[数组]
array 与 forward 是c++标准增加的类型
array的底层数据结构是固定数组,与c语言的数组类型,它的大小不能改变因此有以下特性
固定大小、不支持添加和删除元素或者改变容器大小等
int digs[10] = {0,1,2,3,4,5,6,7,8,9};
int cpy[10] = digs;//错误,数组不支持数组之间的拷贝或者赋值
array<int,10>digits = {0,1,2,3,4,5,6,7,8,9};
array<int,10> copy = digits; //正确: 只要数组类型匹配即合法
list 和 forward_list 任何位置的删除和添加都很块,但是访问一个元素要遍历整个容器
forward_list 没有size 操作
顺序容器可以保存任意的数据类型
vector<vector<string>> lines;//vector的vector
注意:在构造容器的时候,不能只传递给它一个元素参数
vector<noDefault> v1(10,init);
vector<noDefault> v2(10)//错误必须提供一个元素初始化器
9.2 容器库概述
迭代器
迭代器的范围是左闭右开 【beg,end)
每个容器中都有两个迭代器
begin:第一个元素
end: 最后一个元素的下一个
迭代器的类型
list<string>:: iterator iter;//iter是通过list<string>定义的一个迭代器类型
容器的定义和初始化
处理array以外其他容器默认构造都是构造一个空容器
将容器初始化为一个容器的拷贝
1、直接拷贝整个容器(要求容器类型和元素类型一样)
2、拷贝由一个迭代器指定的元素范围
拷贝元素,但是不包括it指向的元素
标准库array具有固定大小
array<int,42> ia1; //指定元素类型和大小
容器的大小操作
size
empty
max_size 返回一个大于或者等于该类型容器能容纳的最大元素的值
9.3 顺序容器操作
容器元素是拷贝
当我们用一个对象来初始化容器时,或将一个对象插入到容器中去,实际上是放入的是原对象的拷贝,拷贝对象不会与原对象关联任何关系
在容器中的特定位置添加元素/插入一个范围
slist.insert(slist.begin(),"Hello!");//在slist容器开头之前插入元素Hello!
c.insert(p,n,t) 在p之前插入n个t
c.insert(p,b,e) 在p之前插入b与e之间的元素
c.insert(p,il) 在p之前插入il花括号范围内的元素列表
emplace 操作
emplace_front 将元素存放的容器头部
emplace 在某个位置之前插入
emplace_back 容器尾部
emplace函数在容器中直接构造元素,传递给emplace函数的参数必须与元素类型的构造函数相匹配
9.3 访问元素
在所有的容器中都存在成员函数 front 与back用来返回容器中的首尾端元素(单链表中不能使用back)
空容中不能使用front和back函数
pop_front 与pop_back(string、vector不支持)
删除首尾元素
erase 从容器中指定位置删除元素
删除list中的索引奇数元素
erase返回指向删除得(最后一个元素之后位置的迭代器)
list<int> lst = {0,1,2,3,4,5,6,7,8,9};
auto it = lst.begin();
while(it != lst.end())
if(*it % 2) //为奇数
it = lst.erase(it);删除此元素,返回的值是被删除之后的下一个位置
else
++it;
erase删除范围内的元素
elem1 = slist.erase(elem1,elem2)
单链表的特殊操作
resize
array不支持resize
9.4 vector怎么增长的
vector会有预留空间作为备用,可以用来保存更多的新元素
capacity :不扩张的时候容量
size:保存了多少元素
reserver:通知容器扩展容量 reserver不会变小
9.5 额外的string操作
构造string 函数的其他方法
substr
在拷贝字符的时候,提供一个开始位置和计数值
string s("hellow");
string s2 = s.substr(0,5);// s2 = hello
append和replace 函数
append 在string尾部插入
replace 删除范围内的元素
string搜索
string name ("AnnBelle");
auto pos1 = name.find("Anna"); //pos1 =0,name.find()会返回一个索引位置
compare 函数
数值转换
将数值数数据转换成字符表示形式
int i= 42;
string s = to_string(i);//将整数i转换成字符表示形式
double d = stod(s);//将字符串 s转换浮点型
9.6 容器适配器
stack 、queue 、priority_queue
本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样
每个适配器都定义两个构造函数
1、默认构造函数创建一个空对象
2、接受一个容器的构造函数拷贝该函数来初始化适配器
第 10章 泛型算法
所有容器都可以使用的算法
泛型算法永远不会执行容器的操作,它们只会运行于迭代器之上
// find算法
int val = 42;//我们将要查找的值
//如果在vec中找到我们想要的元素,则返回结果指向它,否则返回结果vec.cend();
auto result = find(vec.cbegin(),vec.cend(),val);
//报告结果
cout << "The value " << val
<< (result == vec.cend())
? " is not present" : " is present") << endl;
算法永远不会改变底层容器的大小,可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或者删除元素
迭代器令算法不依赖于容器,但算法依赖与元素类型的操作
大多数算法都定义在头文件algorithm中,标准库还在头文件numeric中定义了一组泛型算法
10.2 初识泛型算法
只读算法
一些算法只会读取其输入范围内的元素,而不改变元素
find、accumulate、equal
accumulate要求序列与第三个参数类型匹配
string sum = accumulate(v.cbegin(),v.cend(),string(""));//连接一个字符串
写容器的算法
fill
file(vec.begin(),vec.end(),0);//将每个元素重置0
算法不检查写的操作
一些算法将新值赋予序列中的元素,当我们使用这类算法时,必须确保序列原大小至少不小于我们要求算法写入的元素数目
back_insert
一种保证算法有足够元素空间输出数据的算法,插入迭代器是一种向容器中添加元素的迭代器。通常情况下,当我们通过一个迭代器想容器中赋值的时候,值被赋给迭代器指向的元素,而当我们通过一个插入迭代器赋值,一个新元素被添加到容器中
vector<int> vec;//空向量
fill_n(back_insert(vec),10,0);//添加10个元素到vec
拷贝算法
传递给copy的目的序列至少要包含与输入序列一样多的元素,这一点很重要。此算法接三个迭代器
前两个表示一个输入范围,第三个表示目的序列的起始位置。
int a1 [] = {0,1,2,3,4,5,6,7,8,9};
int a2 [sizeof(a1)/sizeof(*a1)];
auto ret = copy(begin(a1),end(a1),a2);//把a1的内容拷贝给a2
重排容器中的元素
sort 函数进行排序
unique 覆盖掉相邻重复的字符
10.3 定制操作
将算法的一些东西进行重载,自定义一些算法的功能,允许自定义的操作来替代默认运算符
向算法中传递函数
//比较函数,用来按长度排序单词
bool isShorter (const string &s1,const string &s2){
return s1.size() < s2.size();
}
//sort (words.begin(),words.end(),isShorter)//按照长度排序
lambda 表达式
匿名函数,也就是没有名字的函数,我们
[capture List ] (parameter list) ->return type{function body}
capture list : 是一个lambda所在函数中定义的局部变量的列表,通常为空,让我们的匿名函数可以访问甚至修改函数外部的变量
return type :返回类型
parameter :参数列表
function body :函数体
10.4 再探迭代器
除去为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了几额外的几种迭代器包括:
插入迭代器:向容器中插入元素
流迭代器:这些迭代器被绑定在输入或输出流上,可以用来遍历有所关联的IO流
反向迭代器:这些迭代器是向后的,而不是向前移动,除了forward_list之外的标志库容器中都有反向迭代器
移动迭代器:这些专门的迭代器不是拷贝其中的元素而是移动他们
10.5 泛型算法结构
在任何算法的最基本的特性是它要求其迭代器提供哪些操作。某些算法,比如find,只能要求通过迭代器访问元素,递增迭代器以及比较两个迭代器是否相等这些能力。其他一些算法,如sort还要求读,写和随机访问元素的能力.算法所要求的迭代器操作可以分为5个迭代类
类迭代器
类似容器,迭代器也定义了一组公共操作,一些操作所有迭代器都支持,另外一些只有特定类别的迭代器才支持,列如:
ostream_iterator只支持递增,解引用和赋值。vector,string和deque的迭代器除了这些操作外,还支持递减,关系运算符和算术操作
算法的形参模型
alg(beg,end,other args);
alg(beg,end,dest,other args);
alg(beg,end,beg2,other args);
alg(beg,end,beg2,end2,other args);
10.6 特定容器的算法
与其它容器不同,链表类型list和forward_list定义了几个成员函数形式的算法。特别是,它们定义了独有的sort,merge,remove,reverse和unique
链表类型定义的其他算法的通用版本可以用于链表,但代价太高,这些算法需要交换输入序列中的元素。一个链表可以通过改变元素间的链接而不是真的交换它们的值来快速交换元素,因此这些链表版本好的多
spllice 成员
链表还定义了splice算法,此算法是链表数据结构所特有的,因此不需要通用版本
第十一章 关联容器
关联容器和顺序容器有着根本的不同:关联容器的元素中的按关键字来保存和访问的,与之相对,顺序容器中的元素是按照它们在它们在位置先后进入顺序来保存和访问的。
关联容器支持高效的关键词查找和访问,两个关键的关联容器是map和set
map:map中的元素是一些关键字-值对。关键字起到索引的作用,值则表示与索引相关联的数据。
set:set中每一个元素只包含了一个关键字:set支持高效的关键字查询操作
标准库提供8个关联容器,如下表所示。
11.1 使用关联容器
map<string,size_t>word_cout;//string到size_t的空map
string word;
while(cin >> word)
++word_count[word];//提取word的计数器并且加1
for(const auto &w : word_count)//对map中的每个元素
//打印结果
cout << w.first << "occurs"<<w.second
<<((w.second > 1) ? " times " : "time") << endl;
11.2 关联容器的概述
定义关联容器
定义map的时候,必须要指定关键字类型:定义set时,只需指定关键字类型,因为set中没有值。
初始化map时,必须提供关键字类型和值类型,提供每个键值对用花括号{}包围
map<string,size_t>word_count;
//列表初始化
set<string> exclude = {"the","but","and","or"};
map<string,string> authors = {
{"Joyce","James"},
{"Austen","Jane"}
{"Dickens","Charles"}
}
//使用迭代器初始化
vector<int> ivec = {1,1,2,2,3,3,4,4};
set<int> iset(ivec.cbegin(), ivec.cend());
set<int> miset(ivec.cbegin(), ivec.cend());
cout << ivec.size() << endl; //8
cout << iset.size() << endl; //4
cout << miset.size() << endl; //8
pair 类型
pair的标准库类型,一个pair保存两个数据成员,类似容器pair是一个用来生成特定类型的模板。当创建一个pair时,我们必须提供两个类型别名,pair的数据成员将具有对应的类型
pair<string, string> annon //保存两个string
pair<string,size_t> word_count; //保存一个string和一个size_t
pair<string,vector<int>> line; //保存string 和 vector<int>
pair<string,string> autor {"James","jotces"};//初始化
11.3 关键容器操作
关联容器定义了类型别名来表示容器关键字和值的类型:
对于set类型,key_type和value_type是一样的。set容器中保存的值就是关键字。对于map类型,元素是关键字-值对。即每个元素是一个pair对象,包含一个关键字和一个关联的值。由于元素的关键字不可以改变,因此pair的关键字部分是const,key不可改变
另外,只有map类型,(unordered_map、unordered_multima、multimap、map)才定义了mapped_type.
关联容器迭代器
解引用关联容器迭代器时,会得到一个类型为容器的value_type的引用。对map而言,value_type是pair类型,其first成员保存const关键字,second保存值
//获得指向word_count中的一个元素的迭代
auto map_it = word_count.begin();//定义一个迭代器
cout << map_it ->first;//打印出此元素的关键字
cout << " " << map_it ->second;//打印此元素的值
map_it ->first = "new key";//错误:关键字是const
++map_it ->second;//改变元素可以
虽然set同时定义了iterator 和const_iterator,类似map,set中的关键字也是const
set<int> iset = {0,1,2,3,
4,5,6,7,8,9};
set<int>:: iterator set_it = iset.begin();
if(set_it != iset.end())
{
*set_it = 42;//错误,关键字是只读的
cout << *set_it << endl;//正确
}
map和set都支持begin和end操作,使用迭代器遍历map、multimap、set或multiset时,迭代器按关键字升序遍历元素
// 获得一个指向元素的迭代器
auto map_it = word_count.cbegin();
//比较当前迭代器和尾后迭代器
while(map_it != word_cout)
{
//解引用迭代器,打印关键字-值对
cout << map_it ->first << "occur" << map_it ->second << "times" << endl;
++map_it;//迭代器移动
}
一般不对关联的容器使用泛型算法
添加元素
使用insert 成员可以向关联容器中添加元素。向map和set中添加已经存在的元素对容器没有影响
通常情况下,对想要添加到map中的数据,并没有现成的pair对象,可以直接在insert的参数列表中创建pair
//向word_count插入word的4种方法
word_count.insert({word,1});
word_count.insert(make_pair(word,1));
word_count.insert(pair<string,size_t>(word,1));
关联容器的insert操作
insert 或者 emplace 的返回值依赖于容器类型和参数
对于不包含重复关键字的容器,添加单一元素insert 和 emplace 版本返回一个pair,表示操作是否成功。pair的first 成员是一个迭代器,指向具有给定关键字的元素:second成员是一个bool值。如果关键字已在容器中,则insert直接返回false,相反就返回true
对于允许包含重复关键字的容器,添加单一元素的insert 和 emplace 版本返回指向新元素的迭代器
删除元素
关联容器的删除操作
与顺序容器不同,关联容器提供一个额外的erase操作,它接受一个key_typr参数,删除所有匹配给定关键字的元素(如果存在),返回实际删除的元素数量
map的下标操作
map和unordered_map容器提供了下标运算符和一个对应的at函数。
set不支持下标操作,因为set中没有关键字相对应的值,元素本身就是关键字
不能对multimap或unordered_multimap进行下标操作,因为这些容器可能有多个值与一个关键字相关联
例子:
word_count[" Anna"] = 1;
1、word_count中搜索关键字为Anna的元素,未找到
2、将一个新关键字插入到word_count ,值进行初始化为0
3、提取出新插入的元素,并将值1赋给它
使用下标操作的返回值
与其他下标运算符相同的是,map的下标运算符返回一个左值,所以既可以读也可以写元素
如果我们只想知道这个元素在不在里面,不想往里面添加东西,就不能用下标操作
访问元素
如果我们所在的关系不过是一个特定的元素或者已经在容器中可能find是最好的。对应不允许重复关键字的容器,可能使用find还是count没有什么区别。但允许重复关键字的容器,count还会做更多的工作,如果元素在容器中,它还会统计有多少个元素有相同的关键字。
在multimap或者multiset中查找元素
find 和 count
string search_item("Alain de Botton");
auto entries = authors.count(search_item);
auto iter = authors.find(search_item);
while(entries)
{
count << iter ->second << endl;
++iter;
-- entries;
}
11.4 无序容器
无序容器不是用比较运算符来组织元素的,而是使用一个哈希函数和关键字类型的==运算符。
如果关键字类型固有就是无序的,或者性能测试发现问题可以用哈希技术解决,就可以使用无序容器。
除了哈希管理操作之外,无序容器害提供了与有序容器相同的操作(find、insert等)。这意味着我们曾用于map和set的操作也可以用于unordered_map和unordered_set。类似的,无序容器也有允许重复关键字的版本。
由于元素未按顺序存储,所以无序容器的程序的输出通常会与使用有序容器的版本不同。
unordered_map<string,size_t> word_count;
string word;
while (cin>>word)
++word_count[word];
for (const auto &w:word_count)
cout << ...
管理桶
无序容器在存储上组织为一组桶,每个桶保存0个或多个元素。
无序容器使用一个哈希函数将元素映射到桶。
为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。
容器将具有一个特定哈希值的所有元素都保存在桶中。
因此,无序容器的性能依赖于哈希函数的质量和桶的数量大小。
理想的情况下,哈希函数还能将每个特定的值映射到唯一的桶。但是,将不同的关键字的元素映射到相同的桶也是允许的。每个桶保存多个元素时,需要顺序搜索这些元素来查照我们想要哪个。
无序容器提供了一组管理桶的函数
第十二章 动态内存
全局对象在程序启动时分配,在程序结束时销毁。
局部自动对象:当进入其定义所在的程序块是被创建,在离开块时销毁。
局部static对象在第一次使用前分配,在程序结束时销毁。
除自动和static对象外,C++还支持动态分配对象。
动态分配对象的生存期与他们在哪里创建无关,只有当显式地被释放时,这些对象才会被销毁。
为了更安全的使用动态对象,标准库定义了两个智能指针类型来管理动态分配的对象。
静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量
栈内存:保存定义在函数内的非static对象
内存池:每个程序都有一个内存池,叫做自由空间(free store)或者堆(heap),程序使用堆来存储动态分配的对象
12、1 动态内存与智能指针
new在动态空间内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化,delete接受一个动态对象的指针,销毁该对象,并释放与之关联的内存
使用new与delete会出现一些问题,为了更加便利,新的标准库提供了两种智能指针类型来管理动态内存,
智能指针的行为类型常规指针,重要的区别是它负责自动释放所指向的对象。
shared_ptr 允许多个指针指向一个对象
unique_ptr 独占所指对象
weak_ptr 弱引用,指向shared_ptr所管理的对象
上面三个都定义在memory头文件中
每个shared_ptr都有一个关联的计数器,我们称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。无论何时我们拷贝一个shared_ptr,计数器都会递增。
make_shared函数
//生成一个指向值为42的内存空间的shared_ptr指针
shared_ptr<int> p3 = make_shared<int>(42);
//生成一个指向值10个9的内存空间的shared_ptr指针
shared_ptr<string> p4 = make_shared<string>(10,'9');
//生成一个指向值为0的内存空间的shared_ptr指针
shared_ptr<int> p5 = make_shared<int>();
shared_ptr 函数
auto p = make_shared<int> (42);
auto p(q);//p和q指向相同的对象,此对象有两个引用者
auto r = make_shared<int>(42);
r = q;
//给r赋值,令它指向另一个地址
//递增q指向对象的引用计数
//r原来指向对象已经没有引用者,会自动释放
直接管理内存
new分配内存,delete释放new分配的内存
int * pi = new int //pi指向一个动态分配的,未被初始化的无名对象
//内置类型或者组合类型的对象的值将是未定义的,类类型对象将用默认构造函数进行初始化
string *ps = new string;初始化为空的string
int *pi = new(1024);// pi指向的对象的值为1024
int *ps = new string (3,'9');
vector<int>*pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
string *ps1 = new string;//默认初始化为空string
string *ps2 = new string();//默认初始化为空string
int *pi1 = new int;//默认初始化*pi1的值未定义
int *pi2 = new int();//值初化为0 *pi2为0
以下初始化不是并列的
构造初始化:使用圆括号
列表初始化:使用花括号
拷贝初始化:使用=,编译器把等号的右边的初值拷贝到新创建的对象中去
直接初始化:不使用等号
值初始化:只提供对象容纳的元素数量而不是去略去初始值此时库会创建一个初始化元素初值
默认初始化:使用默认构造函数进行初始化
auto p1 = new auto {0bj};//p指向与obj类型相同的对象
auto p2 = new auto{a,b,c};错误,只能单个值
const int *pci = new const int(1024);
const string *pcs = new const string;
内存耗尽
若new不能分配所要求的内存空间,抛出类型为bad_alloc的异常
bad_alloc和nothrow都定义在头文件new中
int *p1 = new int ;//如果分配失败,new抛出std:bad_alloc
int *p2 = new(nothrow) int;//如果分配失败,new返回一个空指针
释放动态内存
delete expression
忘记delete内存会导致人们常说的内存泄漏问题
delete p;//p
传递给delete的指针必须指向动态分配的内存,或者是一个空指针
const对象的值不能被改变,但它本身是可以被销毁的
const int *pci = new const int(1024);
delete pci;
int *p(new int(42));
auto q = p;//pq指向相同的内存
delete p;//p和q均变为无效
p = nullptr;
shared_ptr和new结合使用
shared_ptr<double> p1;
shared_ptr<int> p2(new int(42));
我们不能将一个内置指针隐式转换为一个智能指针
shared_ptr<int> p1 = new int (1024);//错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));//正确:使用了直接初始化
shared_ptr<int> clone(int p){
return new int(p); //错误:隐式转换
}
shared_ptr<int> clone(int p){
return shared_ptr<int>(new int(p)); /正确:显式
}
unique_ptr
unique_ptr<string> p1(new string("abc"));
unique_ptr<string> p2(p1.release());//将所有权从p1转移给p2,release将p1置为空
unique_ptr<string> p3(new string("Text"));
weak_ptr
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //wp弱共享p,p的引用计数器未改变
if(auto np = wp.lock()){}
12、2 动态数组
int *p = new int[42];
初始化动态分配对象的数组
int *pia = new int[10]; //10个未初始化的int
int *pia2 = new int[10](); //10个值初始化为0的int
string *psa = new string[10]; //10个空string
string *psa2 = new string[10]();//10个空string
初始化器初始化
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
string *psa3 = new string[10]{"a","an","the"};//初始化前几个
释放动态数组
delete [] pa;//pa必须指向一个动态分配的数组或为空
typedef int arr[42];//arr是42个int 的数组的类型别名
int *p = new arrT; //分配一个42个int的数组,p指向第一个元素
delete [] p; //方括号是必须的,
空悬指针
在delete之后,指针就变成空悬指针。即指向一块曾经保存数据对象但现在已经无效的内存指针
可以在delete之后将nullptr赋给指针,这样就清楚地指出指针不指向任何对象
allocate 类
allocate分配未构造的内存
当我们用完对象后,必须对每个构造的元素调用destory来销毁它们
//分配比vi中元素所占用空间大一倍的动态内存
auto p = alloc.allocate(vi.size() * 2);
//通过拷贝vi中的元素来构造从p开始的元素
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
//将剩余元素初始化为42
uninitialized_fill_n(q. vi.size(), 42);
总结
c++中动态内存通过new表达式分配,通过delete表达式释放。标准库还定义了allocator类来分配动态内存块
尽量不要用手动的方式管理动态内存,用智能指针
堆:自由空间的同义词
堆:是一颗完全二叉树