string类、智能指针
1. string 类 p531
1)构造函数
一个比较特殊的构造函数
template <class Iter> string (Iter begin, Iter end); // 范围包括 begin 在内,但不包括 end
例如:
char c[] = "All's well that ends well"; ... string s(c+6, c+10);
注意,假设有另外一个 string 对象 s1:
string s1; ... string s(s1+6, s1+10); // not allowed string s(&s1[6], &s1[10]; // allowed
因为 s1 (对象名,不同于数组名)不会被看作是对象的地址,因此 s1 不是指针,所以 s1 + 6 是没有意义的;
而 s1[6] 是一个 char 字符,因此 &s1[6] 是一个地址,可以用作该构造函数的一个参数。
2)string 类的输入 p533
对于 C 风格字符串有 3 种输入方式
char info[100]; cin >> info; cin.getline(info, 100);// read a line, discard \n cin.get(info, 100);// read a line, leave \n in queue
对于 string 对象,有两种输入方式
string stuff; cin >> stuff; getline(cin, stuff);// read a line, discard queue
string 版本的 getline() 函数从输入中读取字符,并将其存储到目标 string 中,直到发生下列三种情况之一:p534
- 到达文件尾;这种情况下,输入流的 eofbit 将被设置,方法 fail() 和方法 eof() 都返回 true;
- 遇到分界字符(默认为 \n);这种情况下,把分界字符从输入流中删除,但不存储它;
- 读取字符的数量达到最大允许值(string::npos 和可供给分配的内存字节数中较小的一个),这种情况下,将设置输入流的 failbit 为1,fail() 返回 true;
string 版本的 operator>>() 函数的行为与此类似;只是它不断读取,直到遇到空白字符并将其留在输入队列中。(cin 不丢弃空白符,p68)
3)string 指定分界符
两个版本的 getline() 函数都有一个可选参数,用于指定使用哪个字符来确定输入的边界:
cin.getline(info, 100, ':'); // 读取直到遇到 :,并丢弃: getline(stuff, ':'); // 读取直到遇到:,并丢弃:
注意,指定分界字符后,换行符 \n 将被视为常规字符。
例子:见 p534
3)使用字符串 p535
size(), length(), find()
capacity() // 返回当前分配给字符串的内存块的大小
reserve() // 请求内存块长度
2. 智能指针 p539
智能指针是行为类似于指针的类对象。
auto_ptr, unique_ptr, shard_ptr 都定义了类似指针的对象,可以将 new 获得(直接或间接)的地址赋给这种对象。
当智能指针过期时,其析构函数将使用 delete 来释放内存;在智能指针过期时,这些内存将自动被释放。
void remodel (std::string & str) { std::string * ps = new std::string(str); ... if (weird_thing()) throw exception(); str = *ps; delete ps; return; }
在上述代码中,当出现异常时,delete 将不被执行,将导致内存泄漏。
1)使用智能指针
要创建智能指针对象,必须包含头文件 memory;该文件包含模板定义,然后使用通常的模板语法来实例化所需类型的指针;
例如,模板 auto_ptr 包含如下构造函数
template<class X> class auto_ptr { public: explicit auto_ptr(X* p = 0) throw(); //throw() 表示构造函数不会引发异常
请求 X 类型的 auto_ptr 将获得一个指向 X 类型的 auto_ptr:
auto_ptr<double> pd(new double); // pd an auto_ptr to double(use in place of double * pd) auto_ptr<string> ps(new string); // ps an auto_ptr to string(use in place of string * ps)
new double 是 new 返回的指针,指向新分配的内存块。它是构造函数 auto_ptr<double> 的参数,即对应于原型中 p 的实参。
其他两种智能指针使用同样的语法:
unique_ptr<double> pdu(new double); // pdu and unique_ptr to double shared_ptr<string> pss(new string); //pss a shared_ptr to string
因此,可以使用智能指针来修改 remodel 函数:
#include <memory> ... void remodel (std::string & str) { auto_ptr<string> ps (new string(str)); // new string 返回一个 new 申请的空string的地址;new string(str) 返回一个 new 申请的内容为 str 的 string 的地址 ... if (weird_thing()) throw exception(); str = *ps; //delete ps; NO LONGER NEED return; }
所有智能指针类都有一个 explicit 构造函数,该构造函数将指针作为参数;因此,不能自动地将指针转换为智能指针对象
shared_ptr<double> pd; double *p_reg = new double; pd = p_reg; // now allowed (implicit conversion) pd = shared_ptr<double>(p_reg); // allowed (explicit conversion) shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion) shared_ptr<double> pshared(p_reg); //allowed (explicit conversion)
注意,不能将非堆内存中地地址作为智能指针构造函数地参数
string vacation("I wandered lonely as a cloud."); shared_ptr<string> pvac(&vacation); // NO!
pvac 过期时,程序将把 delete 运算符用作非堆内存,这是错误的。
2)智能指针的区分 p542
auto_ptr 智能指针在 c++11 中已被摒弃。
auto_ptr<string> ps (new string("I reigned lonely as a cloud.")); auto_ptr<string> vocation; vocation = ps;
上述代码中,如果 ps 和 vocation 是常规指针,则两个指针指向同一个 string 对象,这样程序将试图删除同一个 string 对象两次。解决方法如下:
- 定义赋值运算符,执行深复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本;
- 建立所有权(ownership)概念;对于特定的对象,只有一个智能指针可以拥有它,只有拥有该对象的智能指针会删除该对象;让赋值操作转让所有权;这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
- 跟踪引用特定对象的智能指针数,这称为引用计数(reference counting);仅当最后一个指针过期时,才调用 delete;这是 shared_ptr 采用的策略。
auto_ptr, shared_ptr, unique_ptr 的区分
在 auto_ptr 放弃对象的所有权后,便不能再使用它来访问对象:
auto_ptr<string> s(new string("Fowl Balls")); auto_ptr<string> q; q = s; cout << *s << endl;// not allowed
上面的程序中 auto_ptr s 将所有权转让给 q,因此 s 不再引用该字符串。
如果在上述程序中使用 shared_ptr 代替 auto_ptr,则程序将正常运行。在执行 q = s; 语句时,此时 shared_ptr s 和 shared_ptr q 指向同一个对象,引用计数从 1 增加到 2;在程序末尾,后声明的 q 首先调用其析构函数,该析构函数将引用计数降低到1;然后 s 调用析构函数时,将引用计数降低到 0,并释放该字符串对象所在的空间。
如果在上述程序中使用 unique_ptr,unique_ptr 与 auto_ptr 一样,也采用所有权模型;但使用 unique_ptr 时,程序不会等到运行阶段崩溃,而在编译阶段因执行下述代码而出现错误:
q = s;
探讨 auto_ptr 和 unique_ptr 之间的区别:
在下面的程序中
auto_ptr<string> p1 (new string("auto")); // #1 auto_ptr<string> p2; // #2 p2 = p1; // #3
在语句 #3 中,p2 接管 string 对象的所有权后,p1 的所有权将被剥夺,这可以防止 p1 和 p2 的析构函数试图删除同一个对象;但如果程序随后试图使用 p1 将会出错,因为 p1 不再指向有效的数据;
而在
unique_ptr<string> p3 (new string("auto")); // #1 unique_ptr<string> p4; // #2 p4 = p3; // #3
编译器认为 #3 非法,避免了 p3 不再指向有效数据的问题。因此 unique_ptr 比 auto_ptr 更安全;
但是有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。
unique_ptr<string> demo(const char * s) { unique_ptr<string> temp(new string(s)); return temp; }
...
unique_ptr<string> ps;
ps = demo("Uniquely special")
在上述程序中,demo() 返回一个临时 unique_ptr,然后该临时 unique_ptr 将其指向的对象(即 "Uniquely special" 字符串)的所有权转让给 ps,紧接着该临时 unique_ptr 被销毁。
这没有问题,因为 ps 拥有了 string 对象的所有权;此外,demo() 返回的临时 unique_ptr 很快被销毁,没有机会使用它来访问无效的数据,没有理由禁止这种赋值。
编译器的确允许这种赋值!
总之,如果程序试图将一个 unique_ptr 赋给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这样做;如果源 unique_ptr 将存在一段时间,编译器将禁止这样做:
using namespace std; unique_ptr<string> pu1(new string("Hi ho!")); unique_ptr<string> pu2; pu2 = pu1; // #1 not allowed unique_ptr<string> pu3; pu3 = unique_ptr<string>(new string "Yo!"); // #2 allowed
语句 #1 将留下悬挂的 unique_ptr(pu1),这可能导致危害。语句 #2 不会留下悬挂的 unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权转让给 pu3 后就会被销毁。
unique_ptr 如何区分安全和不安全的用法?---> 它使用了 C++11 新增的移动构造函数和右值引用。
此外,unique_ptr 还有另一个优点:它有一个可用于数组的变体。
在之前提到,必须将 new 和 delete 配对使用,将 new [] 和 delete [] 配对使用;
模板 auto_ptr 使用 delete 而不是 lelete [],因此只能与 new 一起使用,而不能与 new [] 一起使用;但 unique_ptr 有使用 new [] 和 delete [] 的版本:
unique_ptr<double []> pda(new double(5)); // will use delete []
注意,使用 new 分配内存时,才能使用 auto_ptr 和 shared_ptr,使用 new [] 分配内存时,不能使用它们;不使用 new 分配内存时,不能使用 auto_ptr 或 shared_ptr;不使用 new 或 new [] 分配内存时,不能使用 unique_ptr。