zoukankan      html  css  js  c++  java
  • C++标准程序库笔记之一

    本篇博客笔记顺序大体按照《C++标准程序库(第1版)》各章节顺序编排。

    --------------------------------------------------------------------------------------------

    2. C++及其标准程序库简介

    2.2-1

    注意:如果要把一个template中的某个标识符号指定为一种型别,就算意图显而易见,关键字typename也不可或缺,因此C++的一般规则是,除了以typename修饰之外,template内的任何标识符号都被视为一个值(value)而非一个型别。如:

    // 关键字typename被用来作为型别之前的标识符号
    template <class T>
    class MyClass
    {
        typename T::Subtype * ptr;
        ....
    };

    2.2-2
    class member function 可以是个template(类的成员函数模板),但这样的member template 既不能是virtual,也不能有缺省参数。(原因?好像《Effective C++ 》有讲过这个问题*-*)

    2.2-3

    template <class T>
    class MyClass
    {
        private:
            T value;
        public:
            template <class X>
            void assign(const MyClass<X>& x)
            {
                // this型别可以直接取用(在类里面操作私有或保护成员);x作为一个对象,要取用私有或保护成员,必须使用getValue()之类的接口
                // this.value = x.getValue;
                value = x.getValue();
            };
            T getValue() const 
            {
                return value;
            }
    };

    2.2-4
    Template constructor 是member template 的一种特殊形式。Template constructor 通常用于“在复制对象时实现隐式型别转换”。注意,template constructor并不遮蔽implicit copy constructor。如果型别完全吻合,implicit copy constructor (隐式拷贝构造函数)就会被产生出来并被调用。如下:

    template <class T>
    class MyClass
    {
        public:
            template <class U>
            MyClass (const MyClass<U>& x);
            ...
    };
    void f()
    {
        MyClass <double> xd;
        ...
        MyClass<double> xd2(xd);       // calls built-in copy constructor
        MyClass<int> xi(xd);          // calls template constructor
        ...
    };

    2.2-5
    异常throw 语句开始了stack unwinding(堆栈辗转开解)过程,也就是说,它将使得退离任何函数区段时的行为就像以return语句返回一样,然后程序却不会跳转到任何地点。对于所有被声明于某区段——而该区段却因程序异常而退离——的局部对象而言,其destructor(析构函数)会被调用。Stack unwinding的动作会持续知道退出main() 或直到有某个catch子句捕捉并处理了该异常为止。

    2.2-6
    命名空间namespace与class雷同,要引用该namespace内的符号,必须加上namespace标识符。不同于class的是,namespace是开放的,你可以在不同模块(modules)中定义和扩展namespace(也即,和class 不同,namespace 具有扩展开放性,可以出现在任何源码文件中。因此你可以利用一个namespace来定义一些组件,而它们可散步于多个实质模块上)。

    2.2-7
    根据C++标准规格,只有两种mian() 是可移植的:

    int main()
    {
        .....
    }
    //
    int main(int argc, char* argv[])
    {
        ....
    }

    这里argv(命令行参数数组)也可定义为char**。请注意,由于不允许“不言而喻”的返回型别int,所以返回型别必须明白写为int。你可以使用return语句来结束main(),但不必一定如此。这一点和C不同,换句话说,C++在main()的末尾定义了一个隐式的:  return 0; 这意味如果你不采用return 语句离开main(),实际上就表示成功退出(传回任何一个非零值都代表某种失败)。

    --------------------------------------------------------------------------------------------

    3. 一般概念

    3.4-1

    将C++标准程序库中所有标识符都定义于namespace std里头,这种做法是标准化过程中引入的。这个做法不具向下兼容性,因为原先的C/C++头文件都将C++标准程序库的标识符定义于全局范围(global scope)。此外标准化过程中有些classes 的接口也有了更动。为此,特别引入了一套新的头文件命名风格。如:

    #include <string>        // C++ class string
    #include <cstring>     // char* functions from C, was : <string.h>
    #include <cstdlib>     // char* functions from C, was : <stdlib.h>

    3.4-2
    标准异常类别可分为三组:
    (1)语言本身支持的异常;
    (2)C++标准程序库发出的异常;
    (3)程序作用域(scope of a program)之外发出的异常。

    所有标准异常的接口只含一个成员函数:what(),用以获取“型别本身以外的附加信息”,它返回一个以null结束的字符串。除此之外,再没有任何异常提供任何其它成员函数,能够描述异常的种类。

    3.4-3

    C++标准程序库在许多地方采用特殊对象来处理内存配置和寻址,这样的对象称为配置器(allocator)。配置器体现出一种特定的内存模型(memory model),成为一个抽象表征,表现出“内存需求”至“内存低阶调用”的转换。如果运用多个不同的配置器对象,你便可以在同一个程序中采用不同的内存模型。

    --------------------------------------------------------------------------------------------

    4. 通用工具

    4.2-1
    参见博客 为什么需要auto_ptr_ref
    4.3-1
    数值极限
    一般来说,数值型别的极值是一个与平台相关的特性。C++标准程序库通过template numeric_limits提供这些极值,取代传统C语言所采用的预处理常数。C++ Standard规定了各种型别必须保证的最小精度。

    4.4-1

    函数swap用来交换两对象的值。前提,只有当swap()所依赖的copy构造操作和assignment操作行为存在时,这个调用才可能有效。swap()的最大优势在于,透过template specialization(模板特化)或function overloading(函数重载),我们可以为更复杂的型别提供特殊的实作版本。

    4.6-1

    头文件<cstddef>和<cstdlib>和其C对应版本兼容。注意,C语言中的NULL通常定义为(void*)0。在C++中这并不正确,NULL的型别必须是个整数型别,否则你无法将NULL赋值给一个指针。这是因为C++并没有定义从void*到任何其他型别的自动转型操作。

    4.6-2
    函数exit()和abort()可用来在任意地点终止程序运行,无需返回main():
    (1)exit()会销毁所有static对象,将所有缓冲区(buffer)清空(flushes),关闭所有I/O通道(channels),然后终止程序(之前会先调用经由atexit()登录的函数)。如果atexit()登录的函数抛出异常,就会调用terminate()。
    (2)abort()会立刻终止函数,不做任何清理(clean up)工作。
    这两个函数都不会销毁局部对象(local objects),因为堆栈辗转开展动作(stack unwinding)不会被执行起来。为确保所有局部对象的析构函数获得调用,你应该运用异常(exceptions)或正常返回机制,然后再由main()离开。

    --------------------------------------------------------------------------------------------
    5. Standard Template Library(STL),标准模板库
    5.3-1 迭代器
    (1)牢记一点,每一种容器都提供了自己的迭代器(《STL源码剖析》有详细解释)。这些迭代器了解该种容器的内部结构,所以能够知道如何正确行进,事实上,每一种容器都将其迭代器以嵌套(nested)方式定义于内部,因此各种迭代器的接口相同,型别却不同。透过迭代器的协助,我们只需撰写一次算法(注意,算法并非容器类别的成员函数,而是一种搭配迭代器使用的全局函数),就可以将它应用于任意容器之上,这是因为所有容器的迭代器都提供一致的接口(容器--迭代器--算法)。

    (2)Multimaps不允许我们使用subscript(下标)操作符,因为multimaps允许单一索引对应到多个不同元素,而下标操作符却只能处理单一实值。你必须先产生一个“键值/实值”对组,然后再插入multimap。

    5.4-1

    算法 如果某个算法用来处理多个区间,那么当你调用它时,务必确保第二(以及其它)区间所拥有的元素个数,至少和第一区间内的元素个数相同。特别是,执行涂写动作时,务必确保目标区间够大。

    5.6-1

    更易型算法

    (1)算法不能自己移除容器元素,这是STL为了获取灵活性而付出的代价。透过“以迭代器为接口”,STL将数据结构和算法分离开来。然而,迭代器只不过是“容器中某一位置”的抽象概念而已。一般来说,迭代器对自己所属的容器一无所知。任何“以迭代器访问容器元素”的算法,都不得(也无法)透过迭代器调用容器类别所提供的任何成员函数。

    (2)切记:更易型算法(指那些会移除remove、重排resort、修改modify元素的算法)都不得用于关联式容器身上,因为如果更易型算法用于关联式容器身上,会改变某位置上的值,进而破坏其已序(sorted)特性,那也就违反了关联式容器的基本原则:容器内的元素总是根据某个排序准则自动排序。因此,为了保证这个原则,关联式容器的所有迭代器均被声明为指向常量(const iterator)。每一种关联式容器都提供用以移除元素的成员函数。

    (3)有时,针对一个容器,标准库同时提供了STL算法和容器成员函数执行相同操作,如果想要有更高效率,那么使用容器成员函数会是更优选择,因为STL算法针对的是所有容器进行抽象实现。代价是,一旦更换另一种容器,就不得不改动程序代码。

    5.9-1

    仿函数(functors,function objects)

    (1)仿函数是“smart functions”(智能型函数) “行为类似指针”的对象,我们称为“smart pointers”。“行为类似函数” 的对象,我们也可以称为“smart functions”,因为它们的能力可以超越 operator ()。仿函数可以拥有成员函数和成员变量,这意味仿函数拥有状态(state)。事实上,<在同一时间里,由某个仿函数所代表的单一函数>,可能有不同的状态。这在一般函数中是不可能的。另一个好处是,你可以在执行期初始化它们——当然必须在它们被使用(被调用)之前。

    class AddValue
    {
        private:
            int theValue;       // the value to add
        public:
            AddValue(int v) : theValue(v) { }
        void operator () (int& elem) const 
        {
            elem += theValue;
        }
    };
    
    int main()
    {
        list<int> coll;
        for(int i = 1; i <= 9 ; ++i)
        {
            coll.push_back(i);
        }
    
        print_elements(coll, "initialized : ");      // 打印容器元素
        
        for_each(coll.begin(), coll.end(), 
            AddValue(10));      // 执行期才指定数值,通过仿函数的成员变量theValue保存这个状态
    
        print_elements(coll, "after adding 10 : ");      // 打印容器元素
        
        for_each(coll.begin(), coll.end(), 
            AddValue(*coll.begin()));
    
        print_elements(coll, "after adding first element : ");      // 打印容器元素
    }
    
    输出:
    initialized : 1 2 3 4 5 6 7 8 9
    after adding 10 : 11 12 13 14 15 16 17 18 19 
    after adding first element : 22 23 24 25 26 27 28 29 30

    (2)每个仿函数都有自己的型别
    <一般函数,唯有在它们的标记式(signatures)不同时,才算型别不同。而仿函数即使标记式相同,也可以有不同的型别>。事实上,由仿函数定义的每一个函数行为都有其自己的型别。这对于“利用template实现泛型编程”乃是一个卓越贡献,因为如此一来,我们便可以将函数行为当做template参数来运用。这使得不同型别的容器可以使用同类型的仿函数作为排序准则。这可以确保你不会在排序准则不同的群集之间赋值、合并和比较。你甚至可以设计仿函数继承体系,以此完成某些特别事情,例如在一个总体原则下确立某些特殊情况。

    AddValue addx(x);        // function object that adds value x
    AddValue addy(y);        // add y
    
    for_each(coll.begin(), coll.end(), addx);
    ...
    for_each(coll.begin(), coll.end(), addy);
    ...
    for_each(coll.begin(), coll.end(), addx);

    (3)仿函数通常比一般函数速度快
    就template概念而言,由于更多细节在编译期就已确定,所以通常可能进行更好的最佳化。所以,传入一个仿函数(而非一般函数),可能获得更好的性能。

    仿函数相关的更多信息参见《STL源码剖析》

    5.10-1

    Value语意VS. Reference语意

    所有容器都会建立元素副本,并返回该副本。这意味容器内的元素与你放进去的对象“相等(equal)”但非“同一(identical)”。如果你修改容器中的元素,实际改变的是副本而不是原先对象。这意味STL容器所提供的是“value语意”。它们所容纳的是你所安插的对象值,而不是对象本身。然而实用上你也许需要用到“reference语意”,让容器容纳元素的reference。

    STL只支持value语意,不支持reference语意,好处:

    (1)元素的拷贝很简单;

    (2)使用reference时容易导致错误。你必须确保reference所指向的对象仍然健在,并需小心对付偶尔出现的循环引用状态。

    缺点:

    (1)“拷贝元素”可能导致不好的效能;有时甚至无法拷贝;

    (2)无法在数个不同的容器中管理同一份对象。

    实用上你同时需要两种作法。你不但需要一份独立(于原先对象)的拷贝(此乃value语意),也需要一份代表原书记、以能相应改变原值的拷贝(此乃reference语意)。不幸的是,C++标准程序库不支持reference语意。不过我们可以利用value语意来实现reference语意。 一个显而易见的方法是以指针作为元素(C程序员或许很能认可“以指针实现reference语意”的手法。因为在C语言中函数的参数只能passed by value(传值),因此需要通过指针才能实现所谓的call by reference)。为了避免资源泄漏,可以使用智能指针。然而我们不能使用auto_ptr,因为它不符合作为容器元素所需的基本要求。当auto_ptr执行了拷贝(copy)或赋值(assign)动作后,目标对象与原对象并不相等:原来的那个auto_ptr发生了变化,其值并不是被拷贝了,而是被转移了。 你可以使用带有“引用计数”的智能指针实现STL容器的reference语意,但即使这样也很麻烦,举个例子,如果你拥有直接存取元素的能力,你就可以更改元素值,而这在关联式容器中却会打破元素顺序关系。

    《C++标准程序库》6.8节提供了一个实现STL容器reference语意的例子。

    5.11-1

    STL的设计原则是效率优先,安全次之。错误检查相当花时间,所以几乎没有。C++标准程序库指出,对于STL的任何运用,如果违反规则,将会导致未定义行为。 但C++标准程序库还是提供了相应保证,如表6.35.

    注意,所有这些保证都有一个前提:析构函数不得抛出异常。

  • 相关阅读:
    Linux下如何查看哪些进程占用的CPU内存资源最多
    linux查看端口占用情况
    oracle11g用户名密码不区分大小写
    oracle表导入导出
    Oracle的实例占用内存调整
    修改oracle内存
    ORA-04031: 无法分配 共享内存
    OCI_INVALID_HANDLE 什么原因
    Android SDK Manager国内无法更新的解决方案
    sqlite3增删改查简单封装
  • 原文地址:https://www.cnblogs.com/yyxt/p/5014532.html
Copyright © 2011-2022 走看看