C++Primer 第五版 2.C++标准库

来源:互联网 发布:视频加背景音乐软件 编辑:程序博客网 时间:2024/05/21 22:07

我们不能拷贝或者对IO对象赋值。

ofstream out1,out2;out1 = out2; //错误:不能对流对象赋值ofstream print(ofstream); //错误:不能初始化ofstream参数out2 = print(out2); //错误:不能拷贝流对象
进行IO操作的函数通常以引用方式传递和返回流的,读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。如:

ostream &opeartor << (ostream &os, const Sales_data &item);istream &operator >> (istream &is, Sales_data &item);

每一个输出流都管理一个缓冲区,用来保存程序读写的数据。由于设备的读写操作很费时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能上的提升。

导致缓冲刷新(即数据真正写到输出设备或文件)的原因有很多:

1.程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行。

2.缓冲区满时,需要刷新缓冲,而后新的数据才能继续写入缓冲区。

3.可以使用操作符如endl来显式刷新缓冲区。

4.在每个输出操作之后,可以用操作符unitbuf设置流的内部状态,来清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。

5.一个输出流可能被关联到另一个流,在这种情况下,关联到的流的缓冲区会被刷新。


除了endl之外,还有两个类似的操作符。flush刷新缓冲区,但不输出任何额外的字符。ends向缓冲区插入一个空字符,然后刷新缓冲区。


在新C++标准中,文件名既可以是string对象,又可以使C风格的字符数组。旧版本的标准库只允许C风格字符数组。


进行open是否成功的检测通常是一个好习惯。为了将文件流关联到另外一个文件,首先必须用close关闭已经关联的文件。这是在同一个作用域之下的。如果超出了那个作用域,当fstream对象被销毁是,close会自动被调用。


默认情况下,当我们打开一个ofstream时,文件的内容会被丢弃。阻止一个ofstream清空给定文件内容的方法就是同时指定app模式:

#include <iostream>#include <fstream>using namespace std;int main(){    for(int i = 1; i <= 100; ++i)    {        ofstream cout("F://in.txt",ofstream::app);        cout << i << " ";        if(i % 10 == 0)            cout << endl;        cout.close(); //可以省略    }    return 0;}

sstream头文件定义了三个类型来支持IO,这些类型可以向string写入数据,从string读入数据,就像string是一个IO流一样。

特有的操作:

sstream strm(s):保存string s 的一个拷贝。

strm.str():返回strm所保存的string的拷贝。


当我们某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词时,通常可以使用istringstream。

这里举一个例子,文件中每条记录都以一个人名开始,后面跟随一个或多个电话号码。

struct PersonInfo{    string name;    vector<string> phones;  //这里构造很巧妙,name在前面};int main(){    string line,word;    vector<PersonInfo> people;    while(getline(cin,line))    {        PersonInfo info;        istringstream record(line);        record >> info.name;        while(record >> word)            info.phones.push_back(word);        people.push_back(info);    }}  

当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。此外,也可以在多种数据类型之间实现格式的转换。如stringToInt,IntToString

#include <iostream>#include <vector>#include <string>#include <sstream>#include <fstream>using namespace std;struct PersonInfo{    string name;    vector<string> phones;};bool CheckBadNum(const string &str){    return str.size() == 11;}int main(){    string line,word;    vector<PersonInfo> people;    ifstream cin("F://in.txt");    ofstream cout1("F://out1.txt");    ofstream cout2("F://out2.txt");    while(getline(cin,line))    {        PersonInfo info;        istringstream record(line);        record >> info.name;        while(record >> word)            info.phones.push_back(word);        people.push_back(info);    }    for(const auto &entry :people)    {        ostringstream formatted,badNums;        for(const auto &num : entry.phones)        {            if(!CheckBadNum(num))                badNums << " " << num;            else                formatted << " " << num;        }        if(badNums.str().empty())        {           cout1 << entry.name << " " << formatted.str() << endl;        }        else        {            cout2 << entry.name << " " << badNums.str() << endl;        }    }}

顺序容器的都提供了快速顺序访问元素的能力。除了vector,deque,list,string之外,forward_list和array是新C++标准增加的类型。(使用时候需要添加相应的头文件)


与内置数组相比,array是一种更安全,更容易使用的数组类型,与内置数组类型,array对象大小是固定的。因此,array不支持添加和删除元素以及改变容器大小的操作。forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。因此,forward_list没有size操作,因为保存或计算其大小就会比手写链表多出额外的开销。(可见effective STL第四条:对一些list实现,size()耗费线性时间。而list不提供常数时间的size()函数在于其独有的splice操作。)  新标准的容器比旧版本的快很多。

下面是一些选择容器的基本原则:

1.除非你有很好的理由选择其他容器,否则应使用vector。

2.如果你的程序有很多小的元素,而且空间的额外开销很重要,则不要使用list或者forward_list。

3.如果程序要求随机访问元素,应使用vector或deque。

4.如果程序要求在容器的中间或者末尾插入元素,应使用list或者forward_list。

5.如果程序需要在头尾位置插入或者删除元素,但不会在中间位置进行插入或者删除操作,则使用deque。

6.如果程序只有在读取输入是才需要在容器中间位置插入元素,随后需要随机访问元素。则

   确定是否真的需要在容器中间添加元素,当处理输入数据时,通常可以向vector添加数据,然后再调用sort函数来重排容器中的元素,从而避免在中间位置添加元素。

   如果必须在中间位置插入元素,可以考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。


forward_list迭代器不支持递减运算符(--)。


标准库array的大小也是类型的一部分,当定义一个array时,除了指定元素类型,还要指定元素的大小。

值得注意的是,array初始化如果用列表初始化不能直接初始化,而是要用=初始化。

#include <iostream>#include <array>using namespace std;int main(){array<int, 3> c = { 1, 2, 3 };    //  array<int, 3> c{ 1, 2, 3 }; errorfor (const auto &elem : c)cout << elem << endl;return 0;}

值得注意的是,虽然我们不能对内置数组类型进行拷贝或对象赋值的操作,但array并无此限制。但是要求类型和大小一样。


赋值操作,除了c1 = c2之外,还可以c1 = {a,b,c}(array除外,因为右边运算对象的大小可能与左边的不相同)。所以array也不支持assign操作。


assign操作允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值。assign操作用参数所指定的元素替换左边容器的所有元素。

1.seq.assign(b,e):将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素。

2.seq.assign(il):将seq中的元素替换为初始化列表il中的元素。

3.seq.assing(n,t):将seq中的元素替换为n个值为t的元素。


在新标准库中,容器既提供成员函数版本的swap,也提供非成员函数版本的swap。非成员函数版本的swap在泛型编程中是非常重要的。统一使用非成员函授版本的swap是一个好习惯。


顺序容器提供了四种insert的方法,这四种方法都返回指向新添加元素的第一个元素的迭代器(旧版本很多是返回void)

1.c.insert(p,t)

2.c.insert(p,n,t)

3.c,insert(p,b,e)

4.c.insert(p,il)

将元素插入到vector,deque和string中的任何位置都是合法的,但是这样做可能很耗时。


C++11新标准引入了三个新成员函数:emplace_front,emplace,和emplace_back,允许我们将元素放置在容器头部,一个指定位置前或者容器尾部。

和push_front,push_back的区别在于emplace函数在容器中是直接构造元素!传递给emplace函数的参数必须与元素类型的构造函数相匹配。

class Student{private:int m_id;string m_name;public:Student(int id = -1, const string &name = "") :m_id(id), m_name(name){}};int main(){vector<Student> stu;stu.push_back(Student(1, "Tom"));      //stu.push_back(1, "Tom");  errorstu.emplace_back(1, "Tom");}

在容器中访问元素的成员函数(即front,back,下标和at)返回的都是引用。如果容器是一个const对象,那返回值是const的引用,如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。

#include <iostream>#include <vector>using namespace std;int main(){vector<int> ivec{ 1, 2, 3 };//输出:1cout << ivec.front() << endl;auto &v = ivec.front();v = 10;//输出:10cout << ivec.front() << endl;return 0;}

顺序容器提供了两种erase的方法,都是返回一个指向最后一个被删除之后元素的迭代器

1.c.erase(p)

2.c.erase(b,e)


forward_list有自己版本的insert和empalce,不支持push_back和emplace_back。(支持push_frront和emplace_front。)

在forward_list中插入或者删除元素的操作:

1.lst.before_begin()

2.lst.cbefore_begin() 

前两个返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。

3.lst.insert_after(p,t)

4.lst.insert_after(p,n,t)

5.lst.insert_after(p,b,e)

6.lst.insert_after(p,il)

7.lst.emplace_after(p,args)

在迭代器p之后插入元素。返回一个指向最后一个插入元素的迭代器!!(这里和前面的insert不同!!)。也就是说p指向的这个元素是没变的,只不过p后面指向的元素变了。(参数p没变!)

8.lst.erase_after(p)

9.lst.erase_after(b,e)

删除p指向元素之后的元素,返回一个指向最后一个被删除之后元素的迭代器。也就是说前驱元素是没变的,只不过指向前驱元素的后续元素变了。(参数p没变!)


当在forward_list中添加或者删除元素时,必须关注两个迭代器,一个是指向我们要处理的元素,一个是指向其前驱的元素。(添加可以只关注要处理的元素)

如删除元素容器中的奇数元素:

#include <iostream>#include <forward_list>using namespace std;int main(){forward_list<int> lst{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };auto prev = lst.before_begin();auto curr = lst.begin();while (curr != lst.end()){if (*curr % 2)curr = lst.erase_after(prev);else{prev = curr;curr++;}}//输出:2 4  6 8 10for (auto c : lst)cout << c << " ";cout << endl;return 0;}

当我们添加或者删除vector或string的元素后,或者在deque中首元素之外的任何位置添加或者删除元素之后,原来end返回的迭代器总是失效。


在C++11新标准中我们可以调用shrink_to_fit来要求deque,vector,或者string退回不需要的内存空间。(以前是通过小技巧,如vector<int> (v).swap(v),可以见Effective STL 条款17)


vector重新分配多少内存,取决于具体的实现!


C++11新标准引入了多个函数,实现数值数据与标准库string之间的转换。


数值转string:

1.to_string(val)

string转数值:

2.stoi(s,p,b)

3.stol(s,p,b)

4.stod(s,p,b)

5.stof(s,p,d)

p是一个size_t指针,保存第一个非数值字符的下标,默认为0。b是转换的进制,默认是10。

注意,要转为数值的string中第一个非空白字符必须是数值中可能出现的字符。

#include <iostream>#include <string>using namespace std;int main(){string s1 = "pi = 3.14";double d = stod(s1.substr(s1.find_first_of("+-.0123456789")));//输出:3.14cout << d << endl;string s2 = "13";int e = stoi(s2, 0, 4);//输出:7cout << e << endl;string s3 = "123abc";string::size_type sz;int f = stoi(s3, &sz);//输出:123cout << f << endl;//输出:abccout << s3.substr(sz) << endl;return 0;}

容器适配器也支持emplace操作。


对于一个对象或者一个表达式,如果可以对其使用调用运算符(就是()),则称它为可调用对象。即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。


C++语言中有几种可调用的对象:函数,函数指针,lambda表达式,bind创建的对象,和重载了函数调用运算符的类。


一个lambda表达式具有如下的形式:

[ capture list ] ( parameter list ) -> return type { function body }

其中,capture list是一个lambda所在函数中定义的局部变量的列表(好好理解这句话,可以包括所在函数的参数,也可以包括函数里面新定义的变量)(通常为空)。我们可以忽略参数列表和返回类型,但是必须永远包含捕获列表和函数体。


#include <iostream>using namespace std;int main(){auto f = []{return 42; };//输出:42cout << f() << endl;return 0;}

如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来,否则返回void。如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。

捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。


又例:

stable_sort(word.begin(), word.end(), [](const string &a, const string &b){return a.size() < b.size(); });
auto wc = find_if(word.begin(), word.end(), [sz](const string &a){return a.size() >= sz; });
for_each(wc, word.end(), [](const string &a){cout << a << " "; });

习题1:

编写一个lambda,接受两个int,返回它们的和。

#include <iostream>using namespace std;int main(){auto c = [](int a, int b){return a + b; };cout << c(1, 2) << endl;return 0;}
习题2:

编写一个lambda,捕获它所在函数的int,并接受一个int参数,lambda应该返回捕获的int和int参数的和。

#include <iostream>using namespace std;void AAA(int a,int b){auto f = [b](int a){return a + b; };cout << f(a) << endl;}int main(){AAA(1, 2);return 0;}

lambda捕获有三种,值捕获,引用捕获,隐式捕获。


值捕获要注意的是,与参数不同,被捕获的变量的值是在lambda创建是拷贝,而不是调用时拷贝。

void fcn1(){size_t v1 = 42; //局部变量//将v1拷贝到名为f的可调用对象auto f = [v1]{return v1; };v1 = 0;auto j = f(); // j为42,f保存了我们创建它时v1的拷贝}

引用捕获实际上是使用引用所绑定的对象。在下例中,当lambda返回v1时,它返回的是v1指向的对象的值。

void fcn1(){size_t v1 = 42; //局部变量//将v1拷贝到名为f的可调用对象auto f = [&v1]{return v1; };v1 = 0;auto j = f(); // j为0,f保存v1的引用}

一般来说,我们应该尽肯可能的减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可能的话,应该避免捕获指针或引用。


除了显示列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda函数体中的代码来推断我们要使用哪些变量,应在捕获列表中写一个&或者=。&告诉编译器采用引用捕获的方式,=则表示采用值捕获的方式。


默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们我们希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字mutable。而一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是非const类型。(这个好理解。)

当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。

transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int{if (i < 0) return -i; else return i; });


总结,如果lambda的捕获列表为空,通常可以使用函数来代替它。但是对于捕获局部变量的lambda,用函数来替换则不是一件简单的事情了(很多时候算法是接受一元谓词)。这里再介绍一种新的方法,使用标准库bind函数。


调用bind的一般形式为:

auto newCallable = bind(callable, arg_list);

其中,newCallable本身就是一个可调用的对象,callable是一个函数名,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list的参数。

arg_list中的参数可能包含形如_n的名字,这些参数是占位符,它们占据了传递给newCallable的参数的“位置”,也就是说可以用newCallable(参数n),这个参数n就带入到callable第n个参数中。

举个例子,这里使用bind生成一个调用check_size的对象,如下所示,它用一个定值作为其大小参数来调用check_size:

auto check6 = bind(check_size, _1,6);

此bind调用只有一个占位符,表示check6只接受单一参数。占位符出现在arg_list第一个位置,表示check6的此参数对应check_size的第一个参数。因此,调用check6必须传递给它一个string类型的参数,check6会将此参数传递给check_size。

#include <iostream>#include <vector>#include <algorithm>#include <functional>using namespace std;using namespace std::placeholders;bool check_size(const string &str, string::size_type sz){return str.size() >= sz;}int main(){auto check3 = bind(check_size, _1, 2);bool b1 = check3("hello");cout << b1 << endl;return 0;}

注意,名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中(注意要引入functional头文件)。

bind还可以重排参数的顺序。如:

sort(words.begin(),words.end(),bind(isShorter,_2,_1));


默认情况下,bind的那些不是占位符的参数是被拷贝到bind返回的可调用的对象中的,但是有时候我们希望通过引用方法传递,则可以使用标准库ref函数。如:

for_each(words.begin(),words.end(),bind(print,ref(os),_1,' '));


在新标准中已经弃用了bind1st和bind2nd,全部采用bind。


#include <iostream>#include <algorithm>#include <vector>#include <functional>using namespace std;using namespace std::placeholders;int main(){vector<int> ivec{ 1, 2, 3, 4 };auto it = find_if(ivec.begin(), ivec.end(), bind2nd(greater<int>(), 3));if (it != ivec.end())cout << *it << endl;it = find_if(ivec.begin(), ivec.end(), bind(greater<int>(), _1,2));  //Attention!if (it != ivec.end())cout << *it << endl;return 0;}

小技巧:可以向sort传递反向迭代器来使vector为递减序。


标准库提供8个关联容器。其中4个是无序容器,是unordered_XXX类型的。这样的无序容器使用哈希函数来组织元素。


在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,在cstddef.h头文件中定义。而使用容器的下标为XXX<T>::size_type


如果要对关联容器使用自定义的比较方法,除了可以用重载了函数调用运算符的类,也可以使用普通的比较函数,只不过使用需要注意。


#include <set>#include <iostream>using namespace std;class AAA{public:bool operator() (int a, int b){return a > b;}};bool BB(int a, int b){return a > b;}int main(){set<int, decltype(BB)*> iset(BB);iset.insert(2);iset.insert(1);//输出:2 1for (const auto &c : iset)cout << c << endl;return 0;}


关联容器现在可以有新的添加方法。

set<int> iset({ 1, 2, 3, 4, 5 });map<int, int> imap;imap.insert({ 1, 2 });imap.insert(make_pair(1, 1));
注意,对set和map使用单一元素的insert版本会返回一个pair类型,告诉插入是否成功。而使用迭代器和花括号的插入返回类型为void。


关联容器删除元素有三种方法,直接删除关键字返回删除元素的个数。如果删除单个迭代器和返回迭代器,返回删除迭代器后面的一个迭代器。(以前没有返回值的)


无序容器在存储上组织为一组桶,每个桶保存零个或者多个元素。无序容器使用一个哈希函数将元素隐射到桶。为了访问一个元素,容器首先计算元素的哈希值,它指出应该搜索哪个桶。容器将具有一个特定哈希值的所有元素都保存在相同的桶中。如果容器允许允许重复关键字,所有具有相同关键字的元素也都会在同一个桶中。因此,无序容器的性能依赖于哈希函数的质量和桶的数量的大小。对于相同的参数,哈希函数必须总是产生相同的结果。


为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针自动释放所指向的对象。shared_ptr允许多个指针指向同一个对象,unique_ptr则独占所指向的对象。头文件(memory)。


shared_ptr和unique_ptr都支持的操作:

1.shared_ptr<T> sp   unique_ptr<T> up:空智能指针,可以指向类型为T的对象

2.p:将p用作一个条件判断,若p指向一个对象,则为true。

3.*p:解引用p,获得它指向的对象。

4.p.get():返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也消失了。

5.swap(p,q)   p.swap(q):交换p和q中的指针。


shared_ptr独有的操作:

1.make_shared<T> (args):返回一个shared_ptr,指向一个动态分配的类型对象T,使用args初始化此对象。

2.shared_ptr<T> p(q):p是shard_ptr q的拷贝,此操作会递增q中的计数器。q中的指针必须能转换成*T。

3.p = q:p和q都是shared_ptr,所保存的指针必须能相互转换,此操作会递减p的引用计数,递增q的引用计数。若p的引用计数变为0,则将其管理的远内存释放。

4.p.use_count():返回与p共享对象的智能指针数量;可能很慢,主要用于调试。

5.p.unique():若p.use_count()为1,返回true,否则返回false。


最安全的分配和使用动态内存的方法是调用make_shared的标准库函数。通常我们可以用auto定义一个对象来保存make_shared的结果,这种方式较为简单。

//p指向一个动态分配的空vector<string>auto p = make_shared<vector<string>>();

每个shared_ptr都有一个关联的计数器,通常成器为引用计数。无论我们何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另外一个shared_ptr,或将它作为参数传递给一个函数,以及作为函数的返回值,它所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或者shared_ptr被销毁时(如一个局部的shared_ptr离开其作用域),计数器就会递减,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。(使用析构函数)

#include <iostream>#include <memory>using namespace std;int main(){shared_ptr<int> p = make_shared<int>(43);cout << *p << endl; //输出43cout << p.use_count() << endl; //输出1cout << p.unique() << endl;//输出1shared_ptr<int> q = p;cout << p.use_count() << endl;//输出2cout << p.unique() << endl;//输出0return 0;}

小tips:如果将shared_ptr存放于一个容器中,而后不需要全部元素,只使用其中一部分,要记得用erase删除不再需要的那些元素。


使用动态生存期的资源的类:

1.程序不知道自己需要使用多少对象。(容器)

2.程序不知道所需对象的准确类型。

3.程序需要在多个对象间共享数据。


C++语言定义了两个运算符来分配和释放动态内存。运算符new分配内存,delete释放new分配的内存。

相对于智能指针,使用这两个运算符管理内存非常容易出错。使用智能指针的程序更容易编写和调试。


小技巧,可以用auto来推断我们需要对象的类型。(只能对于单一参数)

#include <iostream>using namespace std;int main(){int c = 10;auto p = new auto(c);cout << *p << endl;return 0;}

我们传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的。可以在delete之后将nullptr赋予指针。


explicit构造函数适用于直接初始化式而不可以用于复制初始化。

而智能指针的构造函数就是explicit的,所以

shared_ptr<int> p1 = new int(1024); // errorshared_ptr<int> p2(new int (1024)); //okshared_ptr<int> clone1(int p){return new int(p); // error}shared_ptr<int> clone2(int p){return shared_ptr<int>(new int p); // ok}

不要混合使用普通指针和智能指针。推荐使用make_shared而不是new初始化智能指针。

假如有这样一个函数:

void process(shared_ptr<int> ptr){}
由于参数是值传递的,所以实参会被拷贝进来,增加引用次数。因此在运行过程中,引用计数至少是2,当process结束时,ptr的引用计数会递减,但是不会为0,。因此当局部变量ptr被销毁时,ptr指向的内存不会被释放。所以使用这个函数的正确方法是传递给它一个shared_ptr。

#include <iostream>#include <memory>using namespace std;void process(shared_ptr<int> ptr){}int main(){shared_ptr<int> p(new int(32));process(p);int i = *p;//输出:32cout << i << endl;return 0;}

如果是采用普通指针,则很可能会导致错误。

#include <iostream>#include <memory>using namespace std;void process(shared_ptr<int> ptr){}int main(){int *p(new int(32));//process(p); // error  process(shared_ptr<int>(p));  int j = *p;//输出一个随机数cout << j << endl; return 0;}
在上面的调用中,将一个临时的shard_ptr传递给process,调用结束后,这个临时对象被销毁了,此时这个临时对象指向的内存就被释放,也就是p指向的内存被释放了。

智能指针提供了一个get函数,它返回一个内置指针,指向智能指针管理的对象。此函数是为了这样一种情况设计的:我们需要向不能使用智能指针的代码传递一个内置指针。

注意使用get返回的指针的代码不能delete此指针。不要把get返回的指针绑定到另外一个智能指针上面!!(因为这种绑定是分开的 ,相互独立创建,如果另外一个智能指针被销毁则造成指向的对象释放)。

#include <iostream>#include <memory>using namespace std;int main(){int *p = new int(20);{shared_ptr<int> p1(p); //如果去掉大括号则为输出20}//输出一个未知数!cout << *p << endl;return 0;}

特别注意使用普通指针初始化智能指针的问题。不要混合使用普通指针和智能指针。所以要么就不要使用普通指针。


#include <iostream>#include <memory>using namespace std;int main(){shared_ptr<int> p(new int(10));int *q = p.get();{shared_ptr<int> r(q);}        //输出随机数cout << *p << endl;return 0;}


此外还可以使用reset 来讲一个新的指针赋予一个shared_ptr。


unique_ptr在某个时刻只能拥有一个给定的对象。所以unique_ptr不支持普通的拷贝或者赋值操作。但是可以通过调用release或者reset将指针的所有权从一个(非const)unique_ptr转移给另外一个unique_ptr。

#include <iostream>#include <memory>using namespace std;int main(){unique_ptr<int> p1(new int(10));unique_ptr<int> p2(p1.release()); //p1已经置空了p1.reset(p2.release());//输出:10cout << *p1 << endl;return 0;}


u.release():u放弃对指针的控制权,返回指针,并将u置为空。

调用release会切断unique_ptr和它原来管理的对象之间的联系。release返回的指针通常被用来初始化另一个智能指针或者给另一个指针赋值。如果不用另外一个智能指针来保存release返回的指针,则我们的程序要负责资源的释放。

#include <iostream>#include <memory>using namespace std;int main(){int *q = new int(100);unique_ptr<int> p(q);auto r = p.release();delete r;cout << *q << endl;  //输出随机数return 0;;}
如果没有delete r,则还是输出100。这也就证明如果不用另外一个智能指针来保存release返回的指针,则我们的程序要负责资源的释放。

大多数应用应该使用标准库容器而不是使用动态分配的数组,使用容器更为简单,更不容易出现内存管理错误并且可能有更好的性能。


当用new分配一个数组是,我们并未得到一个数组类型的对象,而只是得到一个数组类型的指针。由于不是一个数组类型,所以不能对动态数组调用begin或end。处于相同的原因,也不能使用范围for语句来处理动态数组中的元素。要记住动态数组并不是数组类型,这是重要的。


在C++新标准中,可以采用初始化列表的方法初始化动态数组。

int *p = new int[4]{1, 2, 3, 4};


标准库提供了一个可以管理new分配的数组的unique_ptr版本。(shared_ptr不直接支持管理动态数组,如果需要,则必须提供自己的删除器,且shared_ptr没有提供下标操作)

此时不能使用点和箭头成员运算符,但是可以使用下标运算符。

unique_ptr<int[]> up(new int[10]);for (size_t i = 0; i != 10; ++i)up[i] = i;


0 0