练习12.1:在此代码的结尾,b1和b2各包含多少个元素?
StrBlob b1;
{
StrBlob b2 = {"a","an","the"};
b1 = b2;
b2.push_back("about");
}
b2被销毁了,b1还有4个元素。
练习12.2 编写你自己的StrBlob类,包含const版本的front和back。
#include "StrBlob1.h"
StrBlob1::StrBlob1() :data(std::make_shared<std::vector<std::string>>()){}
StrBlob1::StrBlob1(std::initializer_list<std::string> il):
data(std::make_shared<std::vector<std::string>>(il)){}
void StrBlob1::check(size_type i, const std::string &msg) const {
if (i >= data->size())
throw std::out_of_range(msg);
}
std::string& StrBlob1::front() {
check(0,"front on empty StrBlob");
return data->front();
}
const std::string & StrBlob1::front() const {
check(0,"front on empty StrBlob");
return data->front();
}
std::string& StrBlob1::back() {
check(0,"back on empty StrBlob");
return data->back();
}
const std::string& StrBlob1::back() const {
check(0,"back on empty StrBlob");
return data->back();
}
void StrBlob1::pop_back() {
check(0,"pop_back on empty StrBlob");
data->pop_back();
}
#ifndef TEST_STRBLOB1_H
#define TEST_STRBLOB1_H
#include <vector>
#include <string>
#include <memory>
class StrBlob1{
public:
typedef std::vector<std::string>::size_type size_type ;
StrBlob1();
StrBlob1(std::initializer_list<std::string> il);
size_type size(){return data->size();}
bool empty() const {return data->empty();}
void push_back(const std::string &t){data->push_back(t);}
void pop_back();
std::string& front();
std::string& back();
const std::string &front() const;
const std::string &back() const;
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string &msg) const;
};
#endif //TEST_STRBLOB1_H
练习12.3 StrBlob 需要const版本的push_back和pop_back吗?如果需要,添加进去。否则,解释为什么不需要。
这个data是一个指针,加上const 则变成了一个常量指针,即指向常量的指针,在push_back和pop_back的时候并没有修改指向对象的数据。所以加不加都行。加了也用不到。
练习12.4 在我们的check函数中,没有检查i是否大于0,。为什么可以忽略这个检查?
因为size_type是个无符号的,输入一个负的会转换为一个比0大的数。
练习12.5 我们未编写接受一个initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。
explicit阻止了构造函数的隐式转换,只能将函数直接初始化,不能拷贝初始化。这种设计让程序变得容易使用但是很难dubug。
练习12.6 编写函数,返回一个动态分配的int的vector。将此vector传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector。
vector<int>* _dyn_ivec(){return new vector<int>(10, 1);}
vector<int>* _in_ivec(istream& in, vector<int> *p){
int i ;
cin>>i;
p->push_back(i);
return p;
}
void _out_ivec(ostream& out, vector<int> *p){
for (auto &i:*p) {
cout<<i<<" ";
}
delete p;
}
int main()
{
_out_ivec(cout,_in_ivec(cin,_dyn_ivec()));
return 0;
}
练习12.7 重做上一题,这次使用shared_ptr而不是内置指针。
shared_ptr<vector<int>> _dyn_ivec(){return make_shared<vector<int>>(10,1);}
shared_ptr<vector<int>> _in_ivec(istream& in, shared_ptr<vector<int>> p){
int i ;
cin>>i;
p->push_back(i);
return p;
}
void _out_ivec(ostream& out, shared_ptr<vector<int>>p){
for (auto &i:*p) {
cout<<i<<" ";
}
}
int main()
{
_out_ivec(cout,_in_ivec(cin,_dyn_ivec()));
return 0;
}
练习12.8 下面的函数是否有误?如果有,解释错误原因。
bool b(){
int* p = new int;
//...
return p;
}
这个地方将p转换为了bool值,离开了b函数p指针消失,但内存还有,造成了内存泄漏。
练习12.9 解释下面的代码执行结果:
int *q = new int(42), *r = new int(10);//手动开辟两个动态内存
r = q;// r 和q都指向 42的这个内存空间,10的内存空间造成泄漏
auto q2 = make_shared<int>(42), r2=make_shared<int>(100);//定义了两个智能指针
r2 = q2; //r2 指向的100的内存空间被释放,r2和q2都指向了42的内存空间
练习12.10 下面的代码调用了第413页中定义的process函数,解释此调用是否正确。如果不正确,应如何修改?
shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));
void process(std::shared_ptr<int> ptr)
{
std::cout << "inside the process function:" << ptr.use_count() << "\n";
}
int main()
{
shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));//这里我理解为进行了类型转换,但是p本来就是shared_ptr,在实参这个地方有没有shared_ptr<int>都一样
std::cout << p.use_count() << "\n";
auto q = p;
std::cout << p.use_count() << "\n";
std::cout << "the int p now points to is:" << *p << "\n";
return 0;
}
练习12.11 如果我们像下面这样调用process,会发生什么?
process(shared_ptr<int>(p.get()));
会发生错误。get函数返回了一个内置指针,指向智能指针所管理的对象。将一个智能指针绑定到get返回的指针上是错误的。这是因为这两个智能指针独立创建,独自计数,却指向了同一个内存,当一个智能指针计数为0释放掉内存后,另一个智能指针可能还存在,变成空悬指针。
练习12.12 p和sp的定义如下,对于接来下的对process的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:
auto p = new int();
auto sp = make_shared<int>();
(a)process(sp);//正确 将智能指针传入
(b)process(new int());//不对,内置指针不能隐式转换为智能指针
(c)process(p);//不对,内置指针不能隐式转换为智能指针
(d)process(shared_ptr<int>(p));//正确,显示地将内置指针转换为智能指针,但最好不要这样混用智能指针和内置指针。
练习12.13 如果执行下面的代码,会发生什么?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
sp是一个智能指针, p是一个与sp指向同一内存的内置指针。delete p将 p所指向对象及内存释放,但是智能指针sp还在,造成了sp空悬,会报错。
练习12.14 编写你自己版本的用shared_ptr管理connection的函数。
struct connection {
std::string ip;
int port;
connection(std::string ip_, int port_):ip(ip_), port(port_){ }
};
struct destination {
std::string ip;
int port;
destination(std::string ip_, int port_):ip(ip_), port(port_){ }
};
connection connect(destination* pDest)
{
std::shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
return *pConn;
}
void disconnect(connection pConn)
{
std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;
}
void end_connection(connection *pConn)
{
disconnect(*pConn);
}
void f(destination &d)
{
connection conn = connect(&d);
std::shared_ptr<connection> p(&conn, end_connection);
std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main()
{
destination dest("202.118.176.67", 3316);
f(dest);
}
练习12.15 重写第一题的程序,用lambda代替end_connection函数。
struct connection {
std::string ip;
int port;
connection(std::string ip_, int port_):ip(ip_), port(port_){ }
};
struct destination {
std::string ip;
int port;
destination(std::string ip_, int port_):ip(ip_), port(port_){ }
};
connection connect(destination* pDest)
{
std::shared_ptr<connection> pConn(new connection(pDest->ip, pDest->port));
std::cout << "creating connection(" << pConn.use_count() << ")" << std::endl;
return *pConn;
}
void disconnect(connection pConn)
{
std::cout << "connection close(" << pConn.ip << ":" << pConn.port << ")" << std::endl;
}
void f(destination &d)
{
connection conn = connect(&d);
std::shared_ptr<connection> p(&conn, [](connection *p){ disconnect(*p);});
std::cout << "connecting now(" << p.use_count() << ")" << std::endl;
}
int main()
{
destination dest("202.118.176.67", 3316);
f(dest);
}
练习12.16 如果你试图拷贝或赋值unique_ptr,编译器并不总能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);
error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = std::__cxx11::basic_string<char>; _Dp = std::default_delete<std::__cxx11::basic_string<char> >]'
unique_ptr<string> p2(p1);
note: declared here
unique_ptr& operator=(const unique_ptr&) = delete;
练习12.17 下面的unique_ptr声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。
(a) 没有从int到new int()的隐式转换;
练习12.18 shared_ptr为什么没有release成员?
练习12.23 编写一个程序 ,连接两个字符串字面常量,将结果保存在一个动态分配的char数组中。重写这个程序,连接两个标准库string对象。
string s1 = "this ";
string s2 = "is ";
unique_ptr<char[]> ch(new char[s1.size() + s2.size()]);
for (int i = 0; i < s1.size() + s2.size(); ++i) {
if (i < s1.size())
ch[i] = s1[i];
else
ch[i] = s2[i - s1.size()];
cout<<ch[i];
}
int main(){
char *ch(new char[strlen("hello""world") + 1]);
strcat(ch,"hello ");
strcat(ch,"world");
cout<<ch<<endl;
std::string str1{ "hello " }, str2{ "world" };
std::cout << str1 + str2 << std::endl;
return 0;
}
练习12.24 编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。
int sz;
cout<<"lenth:"<<endl;
cin>>sz;
char * ch = new char[sz + 1];
cout<<"string:"<<endl;
char *s ;
cin>>s;
strcat(ch,s);
cout<<ch;
练习12.25 给定下面的new表达式,你应该如何释放pa?
int * pa = new int[10];
delete [] pa;
练习12.26 用allocator重写第427页中的程序。
int main(){
int n = 5;
allocator<string> alloc ;
auto const p = alloc.allocate(n);
string s ;
auto q = p;
while(cin>>s && q != p + n)
alloc.construct(q++,s);
while(q != p){
alloc.destroy(q--);
}
alloc.deallocate(p,n);
return 0;
}
allocator的使用多少有点双指针的意思,首先为p创建了一块n的内存,p作为一个参考不动了,创建一个指针q指向与q相同的区域,然后再通过q为这片内存初始化,通过q和p的比较来判断有哪些内存初始化了,哪些值没有被初始化。从而可以为这片内存初始化和释放掉内存上的值。
练习12.27 TestQuery和QueryResult类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。
只在一个类中定义并实现查找会简单一些。因为没有txt文件,将文件读取替换为了cin命令行读入。第一版代码如下:
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <memory>
class TestQuery {
private:
std::vector<std::string> text;
std::map<std::string, std::set<int>> word_to_row;
std::string find_word;
public:
void look_up_word();
//friend QueryResult;
TestQuery(std::istream &is,std::string fword);
};
#include <sstream>
#include "TestQuery.h"
TestQuery::TestQuery(std::istream &is,std::string fword):find_word(fword) {
std::string file;
while(std::getline(is,file)){
text.push_back(file);
int n = text.size() - 1;
std::istringstream line(file);
std::string word;
while(line >> word){
word_to_row[word].insert(n);
}
}
look_up_word();
}
void TestQuery::look_up_word() {
if (word_to_row.count(find_word)){
for (auto row : word_to_row[find_word]) {
std::cout<<text[row]<<std::endl;
}
}
else
std::cout<<"Don't have the word"<<std::endl;
}
using namespace std;
int main(){
TestQuery n(cin,"the");
return 0;
}
练习12.29 我们曾经用do while 循环来编写管理用户交互的循环,while和do while 更倾向于哪个版本,为什么?
都一样。
练习12.31 如果用vector代替set保存行号,会有什么差别?哪种方法更好?为什么?
vector不能不保证行号不重复,set就可以保证行号不重复。
这一章不会的内容较多,需要抽空再看看。