C++ Primer 学习之 C++ 标准库

目录

 

一、标准IO库

1、IO流

1.1条件状态

1.2 管理输出缓冲

2、文件输入输出

2.1 文件流对象

2.2 文件模式

 3、string 流

二、顺序容器

1、容器库概览

1.1 容器初始化

1.2 赋值(swap与assign)

2、顺序容器操作

2.1 添加元素和删除元素

2.2 访问元素

2.3 容器大小操作

2.4 特殊的forward_list操作

2.5 vector是如何让增长的

3、额外的string操作

4、适配器

三、泛型算法

1、初始泛型算法

1.1 只读算法

      find

      accumulate

      equal

1.2 写容器元素的算法

      fill

      拷贝算法

      排序算法

2、定制操作

2.1 lambda表达式 

2.2 参数绑定

      bind

3、迭代器

3.1 插入迭代器

3.2 iostream迭代器

3.3 反向迭代器

4、一些特定容器算法

四、关联容器

1、关联容器概述

1.1 关键字类型的要求

1.2 pair类型

2、关联容器操作

2.1 关联容器和算法

      添加元素(insert)

      删除元素 erase

      map的下标操作 []、at()

      访问元素

 3、无序容器

五、动态内存

1、动态内存与智能指针

1.1 shared_ptr

       make_shared函数

       shared_ptr的拷贝和赋值

1.2 直接管理内存

       定位new

       new与delete

       new与shared_ptr

       修改shared_ptr默认的delete操作

1.3 unique_ptr

       修改unique_ptr的默认删除器delete

1.3 weak_ptr

2、动态数组

2.1 智能指针和动态数组

2.2 allocator类




STL六大组件:容器、算法、迭代器、仿函数、适配器、空间配置器

一、标准IO库

cerr:ostream 对象,向标准错误写入数据(没有缓冲,直接发给屏幕)

getline 函数:从 istream 对象读取一行数据,写入 string 对象。

宽字符版本的IO类型和函数的名字以 w 开始(wcin→wistream)

头文件 iostream 定义了用于读写流的基本类型,fstream 定义了读写 命名文件的类型,sstream 定义了读写内存中 string 对象的类型。

1、IO流

IO对象无拷贝或赋值,所以不能将形参或返回类型设置为流类型,而通常以引用方式传递。且读写一个IO对象会改变其状态,因此也不能是const 

1.1条件状态

只有当流处于无错状态时,才能从它写入或读取数据

while(cin>>i)     的终止条件:遇到了文件结束符,或者遇到了IO流错误,或者读入了无效数据。

badbit 表示系统级错误,如不可恢复的读写错误。通常情况下,一旦  badbit 被置位,流就无法继续使用了。在发生可恢复错误后,failbit 会被置位,如期望读取数值却读出一个字符。如果到达文件结束位置, eofbit 和 failbit 都会被置位。如果流未发生错误,则 goodbit 的值为0。 如果 badbit、failbit 和 eofbit 任何一个被置位,检测流状态的条件都会 失败。 

while(in>>v, !in,eod())      //直到遇到文件结束符才停止读取

管理条件状态P281:

1.2 管理输出缓冲

由于设备的读写操作可能很耗时,允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升

cout << "hi!" << endl;   // 输出hi和一个换行,然后刷新缓冲区

cout << "hi!" << flush;  // 输出hi,然后刷新缓冲区,不附加任何额外字符

cout << "hi!" << ends;   // 输出hi和一个空字符,然后刷新缓冲区

cout << unitbuf;    // 所有输出操作后都会立即刷新缓冲区,cerr是设置unitbuf

cout << nounitbuf;  // 回到正常的缓冲方式 如果程序异常终止,输出缓冲区是不会被刷新的。

当一个程序崩溃后, 它所输出的数据很可能停留在输出缓冲区中等待打印。

关联输入和输出流                tie

2、文件输入输出

头文件 fstream 定义了三个类型来支持文件IO:ifstream 从给定文件读取数据,ofstream 向指定文件写入数据,fstream 可以同时读写指定文件。

分别继承自istream、ostream、iostream

ios_base <- ios <- istream <- ifstream

所以对于:std::istream::operator>>     输入终端 cin 和 ifstream 都是 istream 的子类,所以输入操作符 >> 用法相同

istream& getline (istream& is, string& str);

从流对象is中读取一行存到字符串str 直到遇到截止字符,如果遇到截止字符,则把它从流中取出来,然后丢弃(它不被存储,下一个操作的起点在它之后)函数调用前str 中的内容将被覆盖。

2.1 文件流对象

ifstream in(infile);         //构造一个ifstream并打开给定文件(提供了文件名,则open自动调用)

检查文件是否被打开:

if(fin.fail())

if(!fin)      

ifstream对象与istream对象一样,被放在需要bool类型的地方时,将被转换为bool值

if(!fin.is_open())         最好的方式

同样,当一个fstream对象被销毁时,close会自动被调用,而关闭其关联的文件

2.2 文件模式

默认情况下为trunc

ofstream app(“file1”,ofstream::out | ofstream::app)          //此处ofstream::out可不要,因为与 ofstream关联的文件默认以(out|trunc)模式打开 

 保留被 ofstream 打开的文件中已有数据的唯一方法是显式指定 app 或 in 模式。

 3、string 流

#include<sstream>

istringstream可以从string读取数据,ostringstream向string写入数据,stringstream可读可写

二、顺序容器

1、容器库概览

  1. forward_list 和 array 是C++11新增类型。与内置数组相比,array 更安全易用。 array 对象的大小是固定的,forward_list 没有 size 操作。 
  2. 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用 list 或 forward_list。
  3. 不确定应该使用哪种容器时,可以先只使用 vector 和 list 的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样在必要时选择 vector 或 list 都很方便。 

C++11新增cbegin和cend函数

auto it3 = v.cbegin();              // it3的类型是vector<int>::const_iterator

array<int, 10>::size_type i; array相比于内置数组类型,一个很大的区别就是array可以进行拷贝或对象赋值操作

1.1 容器初始化

1.2 赋值(swap与assign)

v1.swap(v2);         

swap(v1, v2);    

将v2赋值给v1,此时v2变成了v1 将vec与本身的元素互换。实质上只是交换vector中用于指示空间的三个指针而已,也就是空间的交换实际是指针指向的交换,所以其速度比v2向v1拷贝元素快得多

 但是对于array,执行swap操作将真正交换他们的元素

assign,仅顺序容器(array除外)

允许我们从一个不同但相容的类型赋值,或者从容器的一个子序列赋值,以替换左边容器中的所有元素

list<string> names;

vector<const char*> oldstyle;

names = oldstyle;   // 错误: 容器类型不匹配,注意将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同

// 正确:可以将const char*转换为string

①names.assign(oldstyle.cbegin(), oldstyle.cend());

②names.assign(10, “Hiya!”);

所有容器支持关系运算(==和!=)

除无序关联容器外,其他容器都支持关系运算符(>、>=、<、<=)

关系运算符两边的运算对象必须是相同类型、相同元素类型。其比较方法与string类似

2、顺序容器操作

2.1 添加元素和删除元素

//返回指向新添加的第一个元素的迭代器

c.insert(p,t)          c.insert(p, n, t)           //在p指向的元素前插入n个t          

c.insert(p, b, e)             //在p所指元素前插入b,e迭代器范围内的元素       

c.insert(p, il)                 //il是一个花括号包起来的元素值列表 {“I”, “am”, “a”, “teacher”}

push_front (还有pop_front)            但是vector和string都不支持push_front

svec.push_front(“Hello!”)      相当于:

vector<string> svec;              

svec.insert(serv.begin() , “Hello!”);             //当然插入到vector末尾之外的任何位置都可能很慢

earse()            返回指向最后一个被删元素之后为止的迭代器

emplace          将参数传递给元素类型的构造函数,直接在容器的内存空间中构造元素。

c.emplace_back("978-0590353403", 25, 15.99);      //三个参数将传递给Sales_data的构造函数

                                                                                  //直接初始化

相当于        c.push_back(Sales_data("978-0590353403", 25, 15.99));      //拷贝初始化

2.2 访问元素

2.3 容器大小操作

resize的第二个参数指定当容器变长时填充在新位置上的值

reserve():只修改capacity的大小,不修改size的大小(修改后capacity大于或等于传递给reserve的参数)

resize():既修改capacity的大小,也修改size的大小                 不适用于array

forward_list 支持 max_size 和 empty,但不支持size

max_size()返回一个大于或等于该类型容器所能容纳的最大元素数的值

2.4 特殊的forward_list操作

由于删除或添加的元素之前的那个元素的后继会发生改变,所以要访问这个前驱,以改变它的链接,但是单向链表没有简单的方法来获取一个元素的前驱

forward_list 并未定义 insert、emplace 和 erase,而是定义了名为 insert_after、emplace_after和 erase_after 的操作

p = lst.before_begin()             //返回指向dummyhead的迭代器                P313

lst.erase_after(p)                    //删除首元素

2.5 vector是如何让增长的

容器必须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间,然后添加新元素,释放旧内存空间,这样每添加一个新元素,就要执行这样的一次内存释放和分配操作,性能极低。

所以, vector 和 string 的实现通常会分配比新空间需求更大的内存空间,容器预留这些空间作为备用,可用来保存更多新元素    (capacity>=size)                

当size超过capacity时,再次分配新的内存空间

在C++11中可以使用 shrink_to_fit 函数来要求 deque、vector 和 string 退回不需要的内存空间(并不保证退回)

清空vector容器

int main(){
    vector <int> vecInt;
    for (int i=0;i<50;i++)
    {
        vecInt.push_back(i);
    }
    
    cout<<"capacity:"<<vecInt.capacity();   //j=64
    cout<<"size:"<<vecInt.size();          //i=50           
    cout<<endl;
    
    //1、使用clear ,清空元素,不回收空间
    vecInt.clear();
    cout<<"capacity:"<<vecInt.capacity();      //j=64
    cout<<"size:"<<vecInt.size();         //i=50
    cout<<endl;
    //2、erase循环删除,不回收空间
    for (int i=0;i<50;i++)
    {
        vecInt.push_back(i);
    } 
    for ( vector <int>::iterator iter=vecInt.begin();iter!=vecInt.end();)
    {
        iter=vecInt.erase(iter);
    }
    cout<<"capacity:"<<vecInt.capacity();      //j=64
    cout<<"size:"<<vecInt.size();         //i=50   
    cout<<endl;

    //3、使用swap,清除元素并回收内存
    vector <int>().swap(vecInt);  //清除容器并最小化它的容量,
    //   vecInt.swap(vector<int>()) ;     另一种写法
    cout<<"capacity:"<<vecInt.capacity();      //j=0
    cout<<"size:"<<vecInt.size();         //i=0
    cout<<endl;
}

思考:为什么swap可以回收内存?

3、额外的string操作

getline(str,’:’)             //读到‘:’,抛弃:                   与 cin.getline(info,100,’:’); 效果相同

  size()、length()返回字符串的字符数 

 s.substr(pos, n) 默认是拷贝pos开始的所有字符        P321

 string s5 = "hiya";  // 拷贝初始化

string s6("hiya") ;  // 直接初始化

string s7(10, 'c');  // 直接初始化,s7的内容是cccccccccc

在执行读取操作时,string 对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。

 string的构造、substr(截断其中一部分)、insert、erase、assign(替换s本身)、append(尾部追加)、replace(替换s中的一部分)、find(搜索字符或者字符串)、compare(相当于strcmp)、to_string(数值转换)

4、适配器

一个容器适配接受一种已有的容器类型,使其行为看起来像一种不同的类型。

默认情况下,stack 和 queue 是基于 deque 实现的,priority_queue 是基于 vector 实现的。三者的相关操作见 P330(push、pop、top)

priority_queue

priority_queue允许我们为队列中的元素建立优先级。新加入的元素会排在所有优先级比它低的已有元素之前

优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的

priority_queue<Type, Container, Functional>

//升序队列

priority_queue <int,vector<int>,greater<int> > q;

//降序队列

priority_queue <int,vector<int>,less<int> >q;

默认是大顶堆

所有适配器都要求容器具有添加和删除元素的能力,因此适配器不能构造在 array 上。适配器还要求容器具有添加、删除和访问尾元素的能力,因此也不能用 forward_list 构造适配器。

stack<string, vector<string>> str_stk2(svec);                 // strstk2在vector上实现,初始化时保存svec的拷贝

三、泛型算法

泛型:可用于不同类型的元素和多种容器类型(及类型的序列)

算法:实现了一些经典算法的公共接口

迭代器的使用令算法不依赖于容器类型

1、初始泛型算法

1.1 只读算法

      find

前两个参数用迭代器表示元素范围,第三个参数为一个元素值

#include<algorithm>

find(vec.cbegin(), vec.cend(), val)                //从vector对象vec中查找val

其中底层实现包含着各种类型元素之间的比较操作(==),运算符重载

C++11在头文件 iterator 中定义了两个名为 begin 和 end 的函数,功能与容器中的两个同名成员函数类似,参数是一个数组。

#include<iterator>

int ia[] = {0,1,2,3,4,5,6,7,8,9};   // ia是一个含有10个整数的数组

int *beg = begin(ia);               // 指向ia首元素的指针

int *last = end(ia);                // 指向arr尾元素的下一位置的指针

int* result = find(begin(ia), end(ia), val);

      accumulate

#include<numeric>

accumulate(v.cbegin(), v.cend, string(“ ”));       //对vector中的元素求和,第三个参数是和的初值

底层实现(+),这里则被重载为字符串拼接,序列中元素类型必须与第三个参数相匹配

accumulate(v.cbegin(), v.cend, “ ”);         编译出错,字符串字面值将对应于cosnt char *,这种类型不存在“+”运算符

      equal

前两个参数用迭代器表示一个子序列,第三个参数表示第二个序列的首元素

因为要求第一个序列的每个元素,在第二个序列中都有一个与之对应的元素,所以第二序列需要不短于第一序列

equal(roster1.cbegin(), roster.cend(), roster2.cbegin());

底层实现(==),两个序列的元素可以不一样,只要能用==来比较就可以了

1.2 写容器元素的算法

      fill

可见,执行泛型算法时,修改容器元素甚至需要扩大容器的情况下,需要借助插入迭代器 

      拷贝算法

copy(begin(a1), end(a1), a2)           //将前两个参数指定的输入范围中的元素拷贝到第三个参数指

                                                         //定的目标序列中

replace(ilst.begin(), ilst.end(), 0, 42);         //将序列中所有等于0的元素替换为42

replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);                             

从cbegin和cend也可以看出,原序列ilst并未改变,而是将replace后的ilst的拷贝放在了ivec中

_copy版本的算法,都是写到额外目的空间

      排序算法

sort(words.begin(), words.end())         底层(<)

unique(words.begin(), words.end())      //不重复元素会排在前面(相邻重复元素被覆盖,返回的迭代器指向最后一个不重复元素之后的位置,再后面的元素将未知)

 容器大小不会变,算法不能添加(或删除)元素,标准库算法是对迭代器而不是容器进行操作。

remove_copy_if(v.begin(), v.end(), back_inserter(v1), [](int i { return i%2; }));

2、定制操作

向算法传递函数

谓词是一个可调用的表达式,其返回结果是一个能用做条件的值

接受谓词参数的算法对输入序列中的元素调用谓词,因此,元素类型必须能转换为谓词的参数类型

sort(words.begin(), words.end(), isShorter);

2.1 lambda表达式 

_if版本的算法,查找使得可调用对象返回非零值的元素

(这里的捕获问题到P507学习了生成类后再理解)

transform(vi.begin(), vi.end(), vi.begin(), [](int i) { return i<0 ? -I : i; });

算法对输入序列(前两个参数)中的每个元素调用可调用对象,并将结果写到目的位置(第三个参数)

for_each接受两个参数、一个可调用对象

2.2 参数绑定

除了lambda表达式,还有一个方法调用bind可将函数变成一个可调用对象

      bind

#include<functional>                      //bind函数

using namespace std::placeholders;        //占位符

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

bool b1 = check6(s)       //check6(s)会调用check_size(s, 6) 

auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));      //对比lambda表达式

_1对应string类型参数,sz是被绑定的参数

3、迭代器

3.1 插入迭代器

back_inserter(push_back)、front_inserter(push_front)、inserter( insert(t,p) )

插入器是一种迭代器适配器,它们接受一个容器,生成一个迭代器

back_inserter接受一个指向容器的引用,返回一个与容器绑定的插入迭代器

 auto it = inserter(vec);

*it = val            相当于:

  1. it = vec.insert(it, val);           //it指向新加入的元素(注意insert的val是插入在it所指向的元素之前的)
  2. ++it;                  //递增it使它指向原来的元素

3.2 iostream迭代器

istream_iterator<int> in_iter(cin);               //从cin读取int型数据

ostream_iterator<int>out_iter(cout, “ ”);           //输出数据到cout,且每输出一个int加一个空格

out_iter可看成cout的一个元素

当然这里更好的方法是调用copy:

copy(vec.begin(), vec.end(), out_iter); 

3.3 反向迭代器

sort(vec.rbegin(), vec.rend()) ;

4、一些特定容器算法

链表定义的几种成员函数形式的算法 merge         lst.merge(lst2, comp)              会改变容器,lst2中的元素会被删除,而通用版本的merge不会

remove、reverse、sort、unique

lst.splice(p, lst2)   或   flst.splice_after(p,lst2,p2)   //将lst2的全部或者其中一个元素(p所指)或者一段元素范围(b、e指定),移动到(相当于剪切)lst中p迭代器所指的元素的前面或者后面

四、关联容器

// 统计每个单词在输入中出现的次数
map<string, size_t> word_count;     // 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;

1、关联容器概述

map 和 multimap 类型定义在头文件 map 中;

set 和 multiset 类型定义在头文件 set 中;

无序容器定义在头文件 unordered_map 和 unordered_set 中。

 允许重复保存关键字的容器名字都包含单词 multi;无序保存元素的容器名字都以单词 unordered 开头。

map类型通常称为关联数组,与普通数组的差别在于它是通过一个关键字而不是位置来查找值

初始化

map<string, size_t> word_count;   // 空容器
// 列表初始化
set<string> exclude = { "the", "but", "and" };
// 三个元素; authors将姓映射为名
map<string, string> authors =
{
    {"Joyce", "James"},
    {"Austen", "Jane"},
    {"Dickens", "Charles"}
};

1.1 关键字类型的要求

对于有序容器,关键字类型必须定义元素比较的方法,默认情况下为<

如果需要使用自定义的比较操作,则必须在定义关联容器类型时提供此操作的类型。操作类型在尖括号中紧跟着元素类型给出。                P379

 multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn)

这里decltype想通过一个函数名获得函数指针类型,必须加上*

1.2 pair类型

  • pair的默认构造函数对数据成员进行值初始化
  • pair的数据成员是public的
  • pair对象是map的元素

 #include<utility>

pair<string, int> process(vector<string> &v)
{
    // 处理v
    if (!v.empty())
       

         // 列表初始化
        return { v.back(), v.back().size() };

        // 隐式构造返回值
       return pair<string, int>();

        //显式构造返回值

        return pair<string, int>(v.back, v.back().size());

        //make_pair

        return make_pair(v.back(), v.back().size());
}

2、关联容器操作

与顺序容器一样,使用作用域运算符来提取一个类型的成员

set<string>::value_type v1;        // v1是一个string
set<string>::key_type v2;          // v2是一个string
map<string, int>::value_type v3;   // v3是一个pair<const string, int>
map<string, int>::key_type v4;     // v4是一个string
map<string, int>::mapped_type v5;  // v5是一个int

一个map的value_type是一个pair,可以改变pair的值,但不能改变pair的关键字成员的值(关键字是const的)

同样的,因为set容器中保存的值就是关键字,所以也是只读的,set的迭代器就是const的

2.1 关联容器和算法

由于关键字是const这一特性,意味着不能使用修改或重排容器元素的算法,可用于只读取元素的算法,但使用关联容器定义的find成员又会比调用泛型find快得多

一般只当作一个原序列,或当作一个目的位置(copy、inserter)时,可以使用算法

      添加元素(insert)

可以是insert(b, e)      //插入迭代器所指范围的元素

// 向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));
word_count.insert(map<string, size_t>::value_type(word, 1));

insert的返回值

对于map:

pair<map<string, size_t>::iterator, bool> ret = word_count.insert(make_pair(word, 1)); //注意现今的编译器,ret前面复杂的声明部分已经不需要了

++ret.first->second;           P385

可见它的返回值是一个pair类型,pair的first成员是一个迭代器,它指向word_count中具有关键字word的元素;pair的second成员是一个bool类型,如果关键字word已在容器中,则insert什么也不做,bool为false,否则插入该元素,bool值为true

对于mutimap和mutiset:

直接插入,返回一个指向新元素的迭代器

      删除元素 erase

与顺序容器一样,支持一个迭代器参数和两个迭代器范围参数;不同的是:

word_count.erase(val);          //删除所有匹配给定关键字val的元素,返回实际删除的元素的数量

      map的下标操作 []、at()

(只对map可用)

map 下标运算符接受一个关键字,获取与此关键字相关联的值。如果关键字不在容器中,下标运算符会向容器中添加该关键字,并值初始化关联值。

hashtable[nums[i]] = i       //(nums[i],i)由于不存在该关键字的话会直接添加,所以可视为一种插入数据的方法

由于下标运算符可能向容器中添加元素,所以只能对非 const 的 map 使用下标操作。

at

· 如果存在:返回key对应的值,可以直接修改,和[]操作一样。

· 如果不存在:抛出 out_of_range 异常.

mymap.at(“Mars”) = 3396;            //mymap[“Mars”] = 3396

下标运算返回左值,可读可写:

      访问元素

 P391

 P392的单词转换程序

 3、无序容器

使用一个哈希函数和关键字类型的==运算符来组织元素

五、动态内存

静态内存:局部static对象、类static数据成员、定义在任何函数之外的变量

栈内存:定义在函数内的非static对象

栈对象仅在其定义的程序块运行时才存在,static对象在使用之前分配,在程序结束时销毁

自由空间或堆(内存池):动态分配的对象,即在程序运行时分配的对象

1、动态内存与智能指针

1.1 shared_ptr ***

可见内置指针(new分配)可作为参数构造出一个shared_ptr智能指针,见1.2节“new与shard_ptr”

p.get()     //返回p中保存的内置指针,但是要谨慎使用,若智能指针释放了其对象,则返回的指针所指向的对象也就消失了 

每个shared_ptr都会记录有多少个shared_ptr指向相同的对象,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象

#include<memory>

shared_ptr<list<int>> p1;        //shared_ptr,可以指向int的list,默认初始化保存一个空指针

       make_shared函数

在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr

#include<memory>

 可见make_shared传递的参数,跟emplace一样,能直接与<>中的某个构造函数相匹配(或者能用来初始化一个对象),直接构造一个对象,并用shared_ptr指向它

       shared_ptr的拷贝和赋值

无论何时我们拷贝一个shared_ptr ,计数器都会递增

  1. 一个对象初始化另外一个对象时

  2. 作为参数传递给一个函数时

  3. 作为函数的返回值时

当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域时,计数器就会递减。

智能指针是类类型,它是通过析构函数(destructor)完成销毁工作的

StrBlob类的设计

使用shared_ptr管理动态内存的类设计示例,它是利用一个标准库容器来管理一组可变大小的内存空间 P405

1.2 直接管理内存 new

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针

动态分配的对象是默认初始化的(内置类型或组合类型的对象的值将是未定义的)

int *pi = new int;            //pi指向一个未初始化的int

而类类型对象将用默认构造函数进行初始化:

int *ps = new string;    //初始化为空string

可以使用直接初始化的方式来初始化一个动态分配的对象:

int *pi = new int(1024);      //pi指向的对象的值为1024

int *pi = new int();     //值初始化为0,*pi为0

auto p1 = new auto(obj)       //auto类型由obj类型推断,所以括号中的obj显然只能有一个

  定位new

当内存耗尽时,new会失败,它会抛出一个bad_alloc异常

使用定位new,可阻止抛出异常,而是返回一个空指针

#include<new>

int *p1 = new int;          //分配失败,则new抛出std::bad_alloc

int *p2 = new (nothrow) int;   //分配失败,返回空指针

①p1 = new (buffer) int[20];     //从buffer中分配内存空间给一个包含20个元素的int数组,再将其地

                                                  址赋给指针p1

注意delete可与常规new运算符配合使用,但不能与定位new运算符配合使用

②pc1 = new (buffer) JustTesting;   //pc1是一个指向JustTesting类的指针,buffer是一个指向字符

                                                         数组的指针

这种情况下,要删除该语句创建的类对象(默认构造函数创建),必须显式地调用析构函数: pc1 -> ~ JustTesting( );

 new与delete

 instance_ = new Singleton;

这条语句实际上做了三件事,第一件事申请一块内存,第二件事调用构造函数,第三件是将该内存地址赋给instance_。

 注意delete与new一样,是包括两个动作:销毁给定的指针指向的对象;释放对应的内存

传递给delete的指针可以是一个空指针;如果释放了一块非new分配的内存,或者将相同的指针值释放多次,结果是未定义的(编译器无法分辨这两种情况是错误的)。

P410考虑一种情况,一个函数内new了一块动态内存,定义指针p指向它,但是当p离开作用域被释放以后,就再也没有指针指向这块内存了,也就没有办法释放这块内存了。这很容易导致空悬指针的产生

       new与shared_ptr

int *p(new int(42));     //直接初始化,p指向动态内存

shared_ptr<int> p2(new int(1024));   //只能直接初始化,而不能使用一个内置指针拷贝初始化的方式

shared_ptr<int> p2 = new int(1024);   //错误,要完成这个拷贝初始化,意味着右值必须是一个shared_ptr,也就是new int(1024)实际上一个指向值为1024的int *,它必须隐式转换为一个智能指针,而接受指针参数的智能指针构造函数是explicit的,所以无法完成这个隐式转换

P414   p.get()用法注意事项

if(!p.unique)    //p.use_count()返回与p共享对象的智能指针数量,大于等于1则返回true

p.reset(new int(1024));      //可用于修改p的指向,因为它释放了p原来指向的对象,又令p指向参数q,这里的q是new int(1024)返回的指针

       修改shared_ptr默认的delete操作

可以想到,如果在new与delete之间发生异常,内存将不被释放,所以需要使用shared_ptr

当然这个"q"也可以是一个shared_ptr,这样p就是q的拷贝

 d的示例(当然改变delete操作,一般被用在智能指针管理的资源不是new分配的内存的情况,更进一步,当一个类没有定义析构函数,可能导致内存泄漏(忘记或者发生异常)的情况也是会借助shared_ptr指针的,这个时候shared_ptr所管理的资源就不是new分配的内存)P415:

void end_connection(connection *p) { disconnect(*p); }

shared_ptr<connection> p(&c, end_connection);

可见此时的可调用对象end_connection已经取代了delete,同样完成connection对象资源的释放操作

    智能指针陷阱   P417

1.3 unique_ptr

1、定义一个unique_ptr时,需要将其绑定到new返回的指针,类似shared_ptr必须采用直接初始化:

unique_ptr<int> p(new int(42));           //p指向一个值为42的int

2、由于一个unique_ptr“拥有”它指向的对象,它持有对对象的独有权,即两个 unique_ptr 不能指向一个对象,因此unque_ptr不支持普通拷贝或赋值操作,只能进行移动操作;但是也有例外,可以拷贝或赋值一个将要被销毁的unique_ptr:

std::unique_ptr<int>p1(new int(5));
std::unique_ptr<int>p2=p1;// 编译会出错
std::unique_ptr<int>p3=std::move(p1);// 转移所有权, 现在那块内存归p3所有, p1成为无效的针.
p3.reset();//释放p3指向的内存,p3置为空
p1.reset();//无效

分析:std::move()返回绑定到左值上的右值引用,即返回的是一个右值引用,可见它是一个具体的值,这个引用绑定到了一个左值,即p1,p1是一个指针,保存的是一块内存的地址,所以这个右值引用不就是这块内存的地址,然后p3指向这个右值引用,也就是指向了这块内存

所以整个p3=std::move(p1)语句,并没有发生任何的拷贝,只是将指针指向了这块内存,称之为移动这块内存

但是对于移后源对象p1的值不做任何假设,所以可以销毁它,也可以赋予它新值,但不能使用这个值。

3、结合p.release()p.reset()完成unique的“拷贝与赋值”操作   P418

p1.reset(p2.release());

分析:这里的release会切断unique_ptr和它原来管理的对象间的联系,p2置空,同时返回一个指针

此时应该让一个新指针来保存它,否则就没有指针指向这块内存了,导致“内存泄漏”:

auto p3 = p2.release();      //其实就是智能指针p放弃了对内存的控制权,同时返回一个指向这块内存的内置指针,接住它就好了

而p1.reset(p3)的作用是,p1先释放它指向的内存,然后重新指向p3,所以结果就是p1指向p2所指向的内存,完成了赋值操作

       修改unique_ptr的默认删除器delete

其方法类似重载关联容器的比较操作   P419

unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);

unique_ptr与shread_ptr管理删除器的方式的比较  P599

1.4 weak_ptr

一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象

用途:由于它不影响shared_ptr所管理对象的生存周期,所以可以用于查看与它共享对象的其他shared_ptr 的情况     

w.lock()        函数的妙用

2、内存管理

详解new与delete关键字

全局new

使用new关键字时,会被编译器转为三个步骤,其中第一步是operator new的函数调用,该函数内部会使用malloc申请内存

new的源码中,如果malloc调用失败(内存耗尽),将会执行_callnewh,自定义的一个函数,用于释放一些不需要的内存,释放后又能回到malloc调用,此时可能成功

内存分配

使用new

容器的内存分配:

3、动态数组

array new

在执行左上角的new Foo[N]语句时,编译器将自动执行try中的语句,其中第一步是先执行operator new,申请一块内存,该语句调用的版本,如果Foo类中重载了new,则调用重载版本,否则调用全局::operator new()。接着强制转换指针类型,然后调用N次构造函数创建N个类对象

typedef int arrT[42];

int *p = new arrT;     //new分配要求数量的对象并(假定分配成功后)返回指向第一个对象的指针

                                //注意这里的动态数组并非真的是一个数组类型的对象,而是一个数组元素类型(int)的指针,

int *pia = new int[get_size()]     //方括号中大小必须是整型,但不必是常量

                                                 //get_size甚至可以是0,此时返回一个合法的非空指针,当然它不指向任何元素,类似于尾后指针

从上图中可以看出,动态数组将执行多次对象的构造

上图为例,一个Foo类对象占12字节(int、long、string各占4字节),执行operator new()以后,内存为64字节(12*5,另外4字节是一个计数器,其值为5,表示new分配的这个动态数组中有5个Foo对象),之后执行5次ctor(构造函数),也因此必须搭配delete[] 使用,才能对应执行5次dtor(析构函数),从图中可见这五次构造与析构的执行顺序。

以下是调用一次malloc申请的内存情况:

头尾cookie记录着这块内存块的大小,这是在new调用malloc时自动生成的,当要释放这段内存而调用 delete [] pac 时,pca指针也是凭借这个cookie才知道具体要释放多大的内存: 

内存管理之内存池设计

使用一个单向链表,将::operator new(BOLCK_SIZE*sizeof(Airplane))申请的一整块内存,按sizeof(Airplane),即8字节为一块小内存,通过next指针串接在一起

newBlock[i].next=&newBlock[i+1];         //其中newBolck是Airplane *

可以减少malloc的调用次数(尽管malloc很快),可以提高空间使用率(因为每次调用malloc申请的内存块的两端都需要各自耗费四字节保存一个cookie)

这里的union设计节省了next指针的使用空间,因为两者只存其一(Airplane本身因占5字节,对齐后占8个字节,而新添加的指针next是为了管理内存池的连接使用的新增数据,占四字节),如果不用union,这个类占用字节将达到16字节。现在只需要8字节:

embedded指针的实现是借助union联合体,union的所有成员相对于基地址的偏移量都为0,且联合体中的所有成员共享这一段内存,其中的成员在同一时间只有一个是被初始化的

当调用malloc分配一大块内存以后,先按照类对象的大小将内存分片,然后利用每块分片后的内存的前四个字节作为embedded指针,将这些空闲内存分片串接在一起方便管理。当要构建实例化一个类对象时,就从这个free list中取出一块并存储类的成员数据(此时由于union机制,embedded失效)

对于内存的回收,是在析构完这块内存所存储的对象以后,前四个字节重新为embedded指针,并接回free list中

以上解释了很多问题:(为什么C开发的结构体定义中,结构体成员最后要定义一个char data[0] ,为什么C++类,即使没有成员,它也是占用四个字节的)就是因为即使没有数据成员,一个类至少要占四个字节,以供embedded指针使用,方便内存管理

上图中carcass是Airplane*指针,指向准备释放的那块内存;但是这里并不是真的释放,而是将其收回插入到free list的前端,可用于重复使用,同时更新free list的头指针到carcess。

但是如果用到一个需要内存管理的类就设计一个operator new,以执行以上操作是很复杂的事情,所以将这些操作(内存池的分配与回收)封装成一个独立的allocator类:

封装的allocate类如下:

 当然可以借助宏定义更进一步降低类的设计复杂度,并且达到内存管理的目的:

allocator类

new将内存分配和对象构造组合在了一起,类似的delete则将对象析构和内存释放组合在了一起。而allocator将内存分配和对象构造分离

#include<memory>

allocator<string> alloc;

auto const p = alloc.allocate(n);        //分配未构造的内存,用于保存n个类型为string的对象

alloc.construct(p++,args);                     //在给定内存位置构造一个元素args

alloc.destroy(--p);                          //释放构造的string对象

alloc.deallocate(p, n);                    //释放这块大小为n的内存

其他一些拷贝和填充未初始化内存的算法  P429

 标准分配器都是简单调用::operator new(),每次调用都有两个cookie,空间利用率不高

std::alloc

 前面是使用一条自由链表,专门为一个类中类对象的内存分配服务

现在是同时维护16条大小不一的自由链表,每种相差8个字节,一直到128字节(如果元素类型超过128字节则调用malloc,不在此服务范围内)

当容器第一次申请一个元素所占内存,将调用一次malloc,一次申请的字节数为:

sizeof(容器元素)*(1~20)*2+RoundUp(已申请内存大小>>4)      //其中RoundUp是字节对齐,分片数量要尽量大,但是最大20

 其中内存池pool大小就是sizeof(容器元素)*(1~20)+RoundUp(已申请内存大小>>4)

当由该容器继续存储元素时,是从剩下的19个free list上的内存分片中取出并用于存储,headofFreeList移动

而当有新的容器(存储的元素类型不一样),则从内存池pool中同样申请sizeof(容器元素)*20新构成一个free-list:

此时内存池容量为0,如果再有新的容器要求存储器元素,就必须再次调用malloc分配新的内存了

当然如果这里还有一点点余量,但是又不足以应对之后新容器新元素的大小,此时所剩内存即为内存碎片

假设之前是在0号链表,内存池中剩余80字节,现在新容器申请104字节,解决方法如下,将80字节直接接到9号链表等待使用

 当然所剩空间足够应对下一个新元素类型的话,就分配尽量多的内存分片就行了(前面说的一次分片是1~20)

如果堆内存用尽,malloc失败,此时会从右边找比自己尺寸大,但是尽量与自己相同的空闲内存块,供当前链表使用。一直找到16号链表还是没有空闲内存块的话,malloc就失败了。

部分源码,可以看到deallocate并没有调用free,只是将内存块重新接回my_free_list的头部

所以这里也是一个需要改进的问题,一个用户程序在不断申请堆内存,调用deallocate却没有归还内存

std::alloc总结:

上面的Foo(1)是栈上的一个临时对象,通过push_back()存储到list容器中,容器通过alloc分配内存(以上可知调用new,通过malloc在堆上分配了内存),用于保存该元素。

而下面是直接通过new在堆上建立一个Foo(2)对象,调用malloc,所以它是带cookie的,但是存储到list中,管理内存的free_lists上以后,就没有cookie了 

malloc

上面已经给出了malloc申请到的内存情况

2.1 智能指针和动态数组

unique_ptr<int []> up(new int[10]);

可以使用下标来访问数组中的元素          up[i]

当up销毁它管理的指针时,会自动使用delete[]

shared_ptr不支持管理动态数组,它默认使用delete,所以除非自定义删除器

shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

shared_ptr未定义下标操作,但是可以使用get获取与它指向相同对象的内置指针来访问数组元素

六、STL源码剖析

string其实是typedef basic_string,其内部就有重载operator new,并且接受两个参数,第二个参数是一个extra(跟引用计数有关),使用的时候就是placement new

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.下载和安装VC++2008 2.下载和安装VC++2010 3.第1章 快速入门 4.第2章 基本内置类型 5.第2章 重点习题解答 6.第2章 字面值常量 7.第2章 变量 8.第2章 变量名 9.第2章 定义对象 10.第2章 声明和定义 11.第2章 名字的作用域 12.第2章 const 限定符 13.第2章 引用 14.第2章 typedef 15.第2章 枚举 16.第2章 类类型 17.第2章 编写自己的头文件 18.第3章 命名空间的using声明 19.第3章 标准库 string 类型 (1) 20.第3章 标准库 string 类型 (2) 21.第3章重点习题解答 22.第3章标准库string类型(3) 23.第3章标准库vector类型 24.第3章重点习题解答 25.第3章迭代器简介 26.二进制和十六进制 27.第3章标准库bitset类型 28.标准库bitset应用实例 29.第4章数组 30.第4章指针的引入 31.第4章使用指针操作数组元素 32.第4章指针和const限定符 33.第4章C风格字符串 34.第4章创建动态数组 35.第4章新旧代码的兼容 36.第4章多维数 37.第5章算术操作符 38.第5章关系操作符和逻辑操作符 39.第5章位操作符 40.第5章赋值操作符 41.第5章自增和自减操作符 42.第5章箭头操作符 43.第5章条件操作符和逗号操作符 44.第5章new和delete表达式 45.第5章显式转换 46.第6章简单语句 47.第6章if语句 48.第6章switch语句 49.第6章while语 50.第6章for语句 51.第6章dowhile语句 52.第6章break,continue,goto语句 53.第6章6.13try块和异常处理 54.第6章6.13.3标准异常 55.第6章6.14使用预处理器进行调试 56.第7章函数的定义 57.第7章参数传递-非引用形参 58.第7章参数传递-引用参数 59.第7章参数传递-vector和其他容器类型的形参 60.第7章参数传递-数组形参 61.第7章main处理命令行选项 62.第7章return语句 63.第7章递归 64.第7章函数声明 65.第7章局部对象 66.第7章内联函数 67.第7章类的成员函数 68.第7章类的构造函数 69.第7章重载函数 70.第7章重载与作用域 71.第7章重载函数函数匹配 72.第7章重载函数实参转换 73.第7章指向函数的指针 74.第8章面向对象的标准IO库 75.第8章条件状态 76.第8章文件流对象的使用 77.第8章重点习题解答 78.第8章文件模式 79.第8章一个打开并检查输入文件的程序 80.第8章字符串流
【原书名】 C++ Primer (4th Edition) 【原出版社】 Addison Wesley/Pearson 【作者】 (美)Stanley B.Lippman,Josée LaJoie,Barbara E.Moo 【译者】 李师贤 蒋爱军 梅晓勇 林瑛 【丛书名】 图灵计算机科学丛书 【出版社】 人民邮电出版社 【书号】 7-115-14554-7 【开本】 16开 【页码】 900 【出版日期】 2006-3-1 【版次】 4-1 【内容简介】 本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。本版对前一版进行了彻底的修订,内容经过了重新组织,更加入了C++ 先驱Barbara E. Moo在C++教学方面的真知灼见。既显著改善了可读性,又充分体现了C++语言的最新进展和当前的业界最佳实践。书中不但新增大量教学辅助内容,用于强调重要的知识点,提醒常见的错误,推荐优秀的编程实践,给出使用提示,还包含大量来自实战的示例和习题。对C++基本概念和技术全面而且权威的阐述,对现代C++编程风格的强调,使本书成为C++初学者的最佳指南;对于中高级程序员,本书也是不可或缺的参考书。本书的前言阐述了 第4版和前一版的不同之处。 【目录信息】 第1章 快速入门 1 1.1 编写简单的C++程序 2 1.2 初窥输入/输出 5 1.2.1 标准输入与输出对象 5 1.2.2 一个使用IO库的程序 5 1.3 关于注释 8 1.4 控制结构 10 1.4.1 while语句 10 1.4.2 for语句 12 1.4.3 if语句 14 1.4.4 读入未知数目的输入 15 1.5 类的简介 17 1.5.1 Sales_item类 17 1.5.2 初窥成员函数 19 1.6 C++程序 21 小结 22 术语 22 第一部分 基本语言 第2章 变量和基本类型 29 2.1 基本内置类型 30 2.1.1 整型 30 2.1.2 浮点型 32 2.2 字面值常量 34 2.3 变量 38 2.3.1 什么是变量 39 2.3.2 变量名 40 2.3.3 定义对象 42 2.3.4 变量初始化规则 44 2.3.5 声明和定义 45 2.3.6 名字的作用域 46 2.3.7 在变量使用处定义变量 48 2.4 const限定符 49 2.5 引用 50 2.6 typedef名字 53 2.7 枚举 53 2.8 类类型 54 2.9 编写自己的头文件 57 2.9.1 设计自己的头文件 58 2.9.2 预处理器的简单介绍 60 小结 62 术语 62 第3章 标准库类型 67 3.1 命名空间的using声明 68 3.2 标准库string类型 70 3.2.1 string对象的定义和初始化 70 3.2.2 String对象的读写 71 3.2.3 string对象的操作 72 3.2.4 string对象中字符的处理 76 3.3 标准库vector类型 78 3.3.1 vector对象的定义和初始化 79 3.3.2 vector对象的操作 81 3.4 迭代器简介 83 3.5 标准库bitset类型 88 3.5.1 bitset对象的定义和初始化 88 3.5.2 bitset对象上的操作 90 小结 92 术语 92 第4章 数组和指针 95 4.1 数组 96 4.1.1 数组的定义和初始化 96 4.1.2 数组操作 99 4.2 指针的引入 100 4.2.1 什么是指针 100 4.2.2 指针的定义和初始化 101 4.2.3 指针操作 104 4.2.4 使用指针访问数组元素 106 4.2.5 指针和const限定符 110 4.3 C风格字符串 113 4.3.1 创建动态数组 117 4.3.2 新旧代码的兼容 120 4.4 多维数组 122 小结 124 术语 125 第5章 表达式 127 5.1 算术操作符 129 5.2 关系操作符和逻辑操作符 131 5.3 位操作符 134 5.3.1 bitset对象或整型值的使用 135 5.3.2 将移位操作符用于IO 137 5.4 赋值操作符 137 5.4.1 赋值操作的右结合性 138 5.4.2 赋值操作具有低优先级 138 5.4.3 复合赋值操作符 139 5.5 自增和自减操作符 140 5.6 箭头操作符 142 5.7 条件操作符 143 5.8 sizeof操作符 144 5.9 逗号操作符 145 5.10 复合表达式的求值 145 5.10.1 优先级 145 5.10.2 结合性 146 5.10.3 求值顺序 148 5.11 new和delete表达式 150 5.12 类型转换 154 5.12.1 何时发生隐式类型转换 154 5.12.2 算术转换 155 5.12.3 其他隐式转换 156 5.12.4 显式转换 158 5.12.5 何时需要强制类型转换 158 5.12.6 命名的强制类型转换 158 5.12.7 旧式强制类型转换 160 小结 161 术语 162 第6章 语句 165 6.1 简单语句 166 6.2 声明语句 167 6.3 复合语句(块) 167 6.4 语句作用域 168 6.5 if语句 169 6.6 switch语句 172 6.6.1 使用switch 173 6.6.2 switch中的控制流 173 6.6.3 default标号 175 6.6.4 switch表达式与case标号 176 6.6.5 switch内部的变量定义 176 6.7 while语句 177 6.8 for循环语句 179 6.8.1 省略for语句头的某些部分 180 6.8.2 for语句头中的多个定义 181 6.9 do while语句 182 6.10 break语句 183 6.11 continue语句 184 6.12 goto语句 185 6.13 try块和异常处理 186 6.13.1 throw表达式 186 6.13.2 try块 187 6.13.3 标准异常 189 6.14 使用预处理器进行调试 190 小结 192 术语 192 第7章 函数 195 7.1 函数的定义 196 7.1.1 函数返回类型 197 7.1.2 函数形参表 198 7.2 参数传递 199 7.2.1 非引用形参 199 7.2.2 引用形参 201 7.2.3 vector和其他容器类型的形参 206 7.2.4 数组形参 206 7.2.5 传递给函数的数组的处理 209 7.2.6 main:处理命令行选项 210 7.2.7 含有可变形参的函数 211 7.3 return语句 211 7.3.1 没有返回值的函数 212 7.3.2 具有返回值的函数 212 7.3.3 递归 216 7.4 函数声明 217 7.5 局部对象 220 7.5.1 自动对象 220 7.5.2 静态局部对象 220 7.6 内联函数 221 7.7 类的成员函数 222 7.7.1 定义成员函数的函数体 223 7.7.2 在类外定义成员函数 225 7.7.3 编写Sales_item类的构造 函数 225 7.7.4 类代码文件的组织 227 7.8 重载函数 228 7.8.1 重载与作用域 230 7.8.2 函数匹配与实参转换 231 7.8.3 重载确定的三个步骤 232 7.8.4 实参类型转换 234 7.9 指向函数的指针 237 小结 239 术语 240 第8章 标准IO库 243 8.1 面向对象的标准库 244 8.2 条件状态 247 8.3 输出缓冲区的管理 249 8.4 文件的输入和输出 251 8.4.1 文件流对象的使用 251 8.4.2 文件模式 254 8.4.3 一个打开并检查输入文件的 程序 256 8.5 字符串流 257 小结 259 术语 259 第二部分 容器和算法 第9章 顺序容器 263 9.1 顺序容器的定义 264 9.1.1 容器元素的初始化 265 9.1.2 容器内元素的类型约束 267 9.2 迭代器和迭代器范围 268 9.2.1 迭代器范围 270 9.2.2 使迭代器失效的容器操作 271 9.3 顺序容器的操作 272 9.3.1 容器定义的类型别名 272 9.3.2 begin和end成员 273 9.3.3 在顺序容器中添加元素 273 9.3.4 关系操作符 277 9.3.5 容器大小的操作 278 9.3.6 访问元素 279 9.3.7 删除元素 280 9.3.8 赋值与swap 282 9.4 vector容器的自增长 284 9.5 容器的选用 287 9.6 再谈string类型 289 9.6.1 构造string对象的其他方法 290 9.6.2 修改string对象的其他方法 292 9.6.3 只适用于string类型的操作 293 9.6.4 string类型的查找操作 295 9.6.5 string对象的比较 298 9.7 容器适配器 300 9.7.1 栈适配器 301 9.7.2 队列和优先级队列 302 小结 303 术语 303 第10章 关联容器 305 10.1 引言:pair类型 306 10.2 关联容器 308 10.3 map类型 309 10.3.1 map对象的定义 309 10.3.2 map定义的类型 310 10.3.3 给map添加元素 311 10.3.4 使用下标访问map对象 311 10.3.5 map::insert的使用 313 10.3.6 查找并读取map中的元素 315 10.3.7 从map对象中删除元素 316 10.3.8 map对象的迭代遍历 316 10.3.9 “单词转换”map对象 317 10.4 set类型 319 10.4.1 set容器的定义和使用 319 10.4.2 创建“单词排除”集 321 10.5 multimap和multiset类型 322 10.5.1 元素的添加和删除 322 10.5.2 在multimap和multiset 中查找元素 323 10.6 容器的综合应用:文本查询程序 325 10.6.1 查询程序的设计 326 10.6.2 TextQuery类 327 10.6.3 TextQuery类的使用 328 10.6.4 编写成员函数 330 小结 332 术语 332 第11章 泛型算法 335 11.1 概述 336 11.2 初窥算法 339 11.2.1 只读算法 339 11.2.2 写容器元素的算法 341 11.2.3 对容器元素重新排序的算法 343 11.3 再谈迭代器 347 11.3.1 插入迭代器 348 11.3.2 iostream迭代器 349 11.3.3 反向迭代器 353 11.3.4 const迭代器 355 11.3.5 五种迭代器 356 11.4 泛型算法的结构 358 11.4.1 算法的形参模式 359 11.4.2 算法的命名规范 359 11.5 容器特有的算法 361 小结 362 术语 363 第三部分 类和数据抽象 第12章 类 367 12.1 类的定义和声明 368 12.1.1 类定义:扼要重述 368 12.1.2 数据抽象和封装 369 12.1.3 关于类定义的更多内容 372 12.1.4 类声明与类定义 374 12.1.5 类对象 375 12.2 隐含的this指针 376 12.3 类作用域 380 类作用域中的名字查找 382 12.4 构造函数 385 12.4.1 构造函数初始化式 387 12.4.2 默认实参与构造函数 391 12.4.3 默认构造函数 392 12.4.4 隐式类类型转换 393 12.4.5 类成员的显式初始化 396 12.5 友元 396 12.6 static类成员 398 12.6.1 static成员函数 400 12.6.2 static数据成员 400 小结 403 术语 403 第13章 复制控制 405 13.1 复制构造函数 406 13.1.1 合成的复制构造函数 409 13.1.2 定义自己的复制构造函数 409 13.1.3 禁止复制 410 13.2 赋值操作符 411 13.3 析构函数 412 13.4 消息处理示例 415 13.5 管理指针成员 419 13.5.1 定义智能指针类 421 13.5.2 定义值型类 425 小结 427 术语 427 第14章 重载操作符与转换 429 14.1 重载操作符的定义 430 14.2 输入和输出操作符 435 14.2.1 输出操作符<>的重载 437 14.3 算术操作符和关系操作符 439 14.3.1 相等操作符 440 14.3.2 关系操作符 441 14.4 赋值操作符 441 14.5 下标操作符 442 14.6 成员访问操作符 443 14.7 自增操作符和自减操作符 446 14.8 调用操作符和函数对象 449 14.8.1 将函数对象用于标准库算法 450 14.8.2 标准库定义的函数对象 451 14.8.3 函数对象的函数适配器 453 14.9 转换与类类型 454 14.9.1 转换为什么有用 454 14.9.2 转换操作符 455 14.9.3 实参匹配和转换 458 14.9.4 重载确定和类的实参 461 14.9.5 重载、转换和操作符 464 小结 466 术语 467 第四部分 面向对象编程与泛型编程 第15章 面向对象编程 471 15.1 面向对象编程:概述 472 15.2 定义基类和派生类 473 15.2.1 定义基类 474 15.2.2 protected成员 475 15.2.3 派生类 476 15.2.4 virtual与其他成员函数 479 15.2.5 公用、私有和受保护的继承 482 15.2.6 友元关系与继承 486 15.2.7 继承与静态成员 486 15.3 转换与继承 487 15.3.1 派生类到基类的转换 487 15.3.2 基类到派生类的转换 489 15.4 构造函数和复制控制 490 15.4.1 基类构造函数和复制控制 490 15.4.2 派生类构造函数 490 15.4.3 复制控制和继承 494 15.4.4 虚析构函数 495 15.4.5 构造函数和析构函数中的虚函数 497 15.5 继承情况下的类作用域 497 15.5.1 名字查找在编译时发生 498 15.5.2 名字冲突与继承 498 15.5.3 作用域与成员函数 499 15.5.4 虚函数与作用域 500 15.6 纯虚函数 502 15.7 容器与继承 503 15.8 句柄类与继承 504 15.8.1 指针型句柄 505 15.8.2 复制未知类型 507 15.8.3 句柄的使用 508 15.9 再谈文本查询示例 511 15.9.1 面向对象的解决方案 513 15.9.2 值型句柄 514 15.9.3 Query_base类 515 15.9.4 Query句柄类 516 15.9.5 派生类 518 15.9.6 eval函数 520 小结 522 术语 523 第16章 模板与泛型编程 525 16.1 模板定义 526 16.1.1 定义函数模板 526 16.1.2 定义类模板 528 16.1.3 模板形参 529 16.1.4 模板类型形参 531 16.1.5 非类型模板形参 533 16.1.6 编写泛型程序 534 16.2 实例化 535 16.2.1 模板实参推断 537 16.2.2 函数模板的显式实参 540 16.3 模板编译模型 542 16.4 类模板成员 545 16.4.1 类模板成员函数 548 16.4.2 非类型形参的模板实参 551 16.4.3 类模板中的友元声明 552 16.4.4 Queue和QueueItem的友元 声明 554 16.4.5 成员模板 556 16.4.6 完整的Queue类 558 16.4.7 类模板的static成员 559 16.5 一个泛型句柄类 560 16.5.1 定义句柄类 561 16.5.2 使用句柄 562 16.6 模板特化 564 16.6.1 函数模板的特化 565 16.6.2 类模板的特化 567 16.6.3 特化成员而不特化类 569 16.6.4 类模板的部分特化 570 16.7 重载与函数模板 570 小结 573 术语 574 第五部分 高级主题 第17章 用于大型程序的工具 579 17.1 异常处理 580 17.1.1 抛出类类型的异常 581 17.1.2 栈展开 582 17.1.3 捕获异常 583 17.1.4 重新抛出 585 17.1.5 捕获所有异常的处理代码 586 17.1.6 函数测试块与构造函数 586 17.1.7 异常类层次 587 17.1.8 自动资源释放 589 17.1.9 auto_ptr类 591 17.1.10 异常说明 595 17.1.11 函数指针的异常说明 598 17.2 命名空间 599 17.2.1 命名空间的定义 599 17.2.2 嵌套命名空间 603 17.2.3 未命名的命名空间 604 17.2.4 命名空间成员的使用 606 17.2.5 类、命名空间和作用域 609 17.2.6 重载与命名空间 612 17.2.7 命名空间与模板 614 17.3 多重继承与虚继承 614 17.3.1 多重继承 615 17.3.2 转换与多个基类 617 17.3.3 多重继承派生类的复制控制 619 17.3.4 多重继承下的类作用域 620 17.3.5 虚继承 622 17.3.6 虚基类的声明 624 17.3.7 特殊的初始化语义 625 小结 628 术语 628 第18章 特殊工具与技术 631 18.1 优化内存分配 632 18.1.1 C++中的内存分配 632 18.1.2 allocator类 633 18.1.3 operator new函数和 operator delete函数 636 18.1.4 定位new表达式 638 18.1.5 显式析构函数的调用 639 18.1.6 类特定的new和delete 639 18.1.7 一个内存分配器基类 641 18.2 运行时类型识别 646 18.2.1 dynamic_cast操作符 647 18.2.2 typeid操作符 649 18.2.3 RTTI的使用 650 18.2.4 type_info类 652 18.3 类成员的指针 653 18.3.1 声明成员指针 653 18.3.2 使用类成员的指针 655 18.4 嵌套类 658 18.4.1 嵌套类的实现 658 18.4.2 嵌套类作用域中的名字查找 661 18.5 联合:节省空间的类 662 18.6 局部类 665 18.7 固有的不可移植的特征 666 18.7.1 位域 666 18.7.2 volatile限定符 668 18.7.3 链接指示:extern "C" 669 小结 672 术语 673 附录 标准库 675 索引 703
C++ Primer习题集(第五版) , 带目录完整版。 --------------------------------------------------------------------------- 目录 第1章............................................................ 1 练习1.1 练 习1.25 第2 章变量和基本类型................................................. 12 练习2.1 练 习2.42 第3 章字符串、向量和数组..............................................37 练习3.1 练 习3.45 第4 章表达式......................................................... 80 练习4.1 练 习4.38 第5 章语句........................................................... 99 练习5.1 练 习5.25 第6 章函数.......................................................... 120 练习6.1 练 习6.56 m m m ...................................................................... 152 练习7.1 练 习7.58 第8 章1 0库..........................................................183 练习8.1 练 习8.14 第9 章顺序容器...................................................... 193 练习9.1 练 习9.52 第10章泛型算法..................................................... 234 练习10.1 练 习10.42 目录 ◄ v 第11章关联容器..................................................... 273 练习11.1 练 习11.38 第12章动态内存..................................................... 297 练习12.1 练 习12.33 第13章拷贝控制..................................................... 331 练习13.1 练 习13.58 第14章重载运算与类型转换............................................368 练习14.1 练 习14.53 第15章面向对象程序设计..............................................399 练习15.1 练 习15.42 第16章模板与泛型编程............................................... 424 练习16.1 练 习16.67 第17章标准库特殊设施............................................... 458 练习17.1 练 习17.39 第18章用于大型程序的工具............................................483 练习18.1 练 习18.30 第19章特殊工具与技术............................................... 502 练习19.1 练 习19.26

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值