《C++ Primer》读书笔记(5.11,6.1,6.2,6.3)

本文是《C++ Primer》的读书笔记,重点介绍了类模板的定义,强调了C++中命名空间的作用,以及顺序容器如list、vector和deque的特性,特别是vector的增长机制和内存管理。此外,还对比了不同数据类型在vector中的性能表现,提到了reserve()操作对性能的影响。

关于类模板

   类模板的定义以关键字template 开始,后面是用尖括号括起来的参数表,类型参数由typenameclass 加上一个标识符构成 例如 

         template<class elemType> 
         class list_item; 
   关键字typenameclass 可以互换typename 是标准C++中新引入的 这种写法更利于记忆,但是在本书写作时 对 typename 的支持没有class 广泛,由于这个原因 我们使用关键字 class


关于命名空间

    更通用的解决方案是使用C++名字空间机制,名字空间使得库厂商可以封装全局名字以防止名字冲突。另外,名字空间也提供了访问符号,从而允许在我们的程序中使用这些名字,例如 C++标准库被包装在名字空间std 中 本书第二版的代码也被放到一个唯一的名字空间中。

        namespace Primer_Third_Edition 
        { 
              template<typename elemType> 
              class list_item{ ... }; 


              template<typename elemType> 
              class list{ ... }; 
              // ... 
        } 

    如果用户希望练习我们的list 类 那么他可以这样写 
        // 我们的 list类头文件 
        #include "list.h" 


        // 使定义对程序可见 
        using namespace Primer_Third_Edition; 


        // ok: 访问我们的 list 
        list< int > ilist; 


        // ... 

抽象容器

    顺序容器[sequence container] 拥有由单一类型元素组成的一个有序集合,两个主要的顺序容器是list 和vector(第三个顺序容器为双端队列deque,发音为 deck。它提供了与vector 相同的行为,但是对于首元素的有效插入和删除提供了特殊的支持。)


关联容器[associative container]支持查询一个元素是否存在,并且可以有效地获取元素两个基本的关联容器类型是map[映射]和set[集合]。map 是一个键/值[key/value] 对。键[key] 用于查询,而值[value]包含我们希望使用的数据。


vector,deque与list

    vector 表示一段连续的内存区域 每个元素被顺序存储在这段内存中

   deque 也表示一段连续的内存区域,但是与vector 不同的是,它支持高效地在其首部插入和删除元素。它通过两级数组结构来实现,一级表示实际的容器,第二级指向容器的首和尾。

    list 表示非连续的内存区域,并通过一对指向首尾元素的指针双向链接起来,从而允许向前和向后两个方向进行遍历。在list 的任意位置插入和删除元素的效率都很高,指针必须被重新赋值,但是不需要用拷贝元素来实现移动;另一方面,它对随机访问的支持并不好访问一个元素需要遍历中间的元素。另外,每个元素还有两个指针的额外空间开销 。


vector如何增长的

         #include <vector> 
         #include <iostream> 

         int main() 
         { 
               vector< int > ivec; 
               cout << "ivec: size: " << ivec.size() 
                   << " capacity: " << ivec.capacity() << endl; 

               for ( int ix = 0; ix < 24; ++ix ) { 
                   ivec.push_back( ix ); 
                   cout << "ivec: size: " << ivec.size() 
                        << " capacity: " << ivec.capacity() << endl; 
               } 
         }
   在ivec的定义之后,它的 长度和容量都是0。但是在插入第一个元素之后,ivec的容量是256,长度为1。这意味着在ivec 下一次需要增长之前,我们可以向它加入256 个元素,当我们插入第256 个元素时,vector以下列方式重新自我增长:它 分配双倍于当前容量的存储区,把当前的值拷贝到新分配的内存中,并释放原来的内存。

容量是指在容器下一次需要增长自己之前能够被加入到容器中的元素的总数。容量只与连续存储的容器相关。例如vector,deque 或string。list 不要求容量。

正如稍后我们将要看到的 同list 相比 数据类型越大越复杂 则vector 的效率也就越低 


下表 各种数据类型 它们的长度 及其相关vector 的初始容量 

    数据类型 长度(字节)初始插入后的容量 
int4256
double8128
simple class1285
String1285
 large simple class80001
large         complex class80001
    正如你所看到的, 对于小的数据类型,vector 的性能要比list 好得多;而对于大型的数据类型则相反,list 的性能要好得多。区别是由于vector 需要重新增长以及拷贝元素,但是数据类型的长度不是影响容器性能的惟一标准,数据类型的复杂性也会影响到元素插入的性能。

    无论是list 还是vector,对于已定义拷贝构造函数的类来说,插入这样的类的元素都需要 调用拷贝构造函数。拷贝构造函数用该类型的一个对象初始化该类型的另一个对象——这正说明了在简单类和string 的链表之间 插入代价的区别。简单类对象和大型简单类对象通过 按位拷贝插入,一个对象的所有位被拷贝到第二个对象的位中;而string 类对象和大型复杂类对象通过调用拷贝构造函数来插入。

    另外 随着每次 重新分配内存 vector 必须为每个元素调用拷贝构造函数,而且在释放原来的内存时,它要为每个元素调用其相关类型的析构函数。vector 的动态自我增长越频繁,元素插入的开销就越大。 


reserve()操作允许程序员将容器的容量设置成一个显式指定的值

	vector< string > svec; 
        svec.reserve( 32 ); // 把容量设置为 32 

但根据经验发现,用一个非1 的缺省值来调整vector 的容量看起来总会引起性能退化。例如,对于string 和double 型的vector,通过reserve()增加容量导致很差的性能;另一方面,加大型复杂类的容量会大大改善性能。(注,非简单类 8000字节大小,并且带有构造函数和析构函数 )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值