STL学习笔记(一)

想要学号C++,掌握STL是必不可少的。在网上也查询了一些STL的推荐书籍,决定按照主流的学习路线循序渐进。学习顺序:
1. 《C++标准程序库:自修教程与参考手册》
2. 《STL源码剖析》
那么现在开始记录我的《C++标准程序库:自修教程与参考手册》学习过程啦!

基本型别的显式初始化
如果使用不含参数的、明确的构造函数调用方式,基本型别就会被初始化为零,具体方法:

template<typename T>
void f()
{
    T x = T();
    ...
}

上面这个函数就会保证 x 被初始化为零。

关键字explicit
explicit作用:禁止单参构造函数被用于自动型别转换,实例:

class Stack{
    explicit Stack(int size);
    ...
};

Stack s1(40);       //1.ok
Stack s2 = 40;      //2.error

如果我们不加explicit关键字的话,1、2两行都是正确的,第二行会把40转换为有40个元素的stack,并赋值给s2,这显然不是我们想要的结果。
而我们加了关键字expicit之后,第二行就会编译出错啦。

template constructor
template consturctor 是模板成员的一种特殊情况,通常用于“在赋值对象时实现隐式型别转换”,但是当赋值的形参型别完全符合时,还是会调用拷贝构造函数,实例如下:

template<class T>
class MyClass{
public:
    template<class U>
    MyClass(const MyClass<U>& u);
};

void func()
{
    MyClass<double> x1;
    MyClass<double> x2(x1);   //calls copy constuctor
    MyClass<int> x3(x1);      //calls template constuctor
}

algorithm头文件
algorithm 是c++特有的STL模板的算法头文件,包含了一些特定的算法函数,而且是全局的。

空间配置器:allocator
配置器使得诸如共享内存、垃圾回收、面向对象数据库等特定的内存模型保持一致的接口。而我们之所以叫空间配置器是因为,不仅只有内存操作,也包括磁盘操作等空间。
allocator 是c++标准库中定义的一个缺省配置器:

namespace std{
    template<class T>
    class allocator;
}

通用工具

1. pairs
对组pairs是c++用来管理,将两个值视为一个单元。例如,map和multimap中的key/valude就是通过pairs管理的。
注意:pairs的定义是一个struct而不是class,所以它的成员都是public的,外部都可以使用。
make_pair:
make_pair是一种便捷函数,使我们无需输入型别就可以定义pair,它的代码实现是这样的:

template<class T, class W>
pair<T,W> make_pair(const T& x, const W& y){
    return pair<T,W>(x,y);
}

这样我们在定义pair的时候可以方便的:

make_pair(40, 'c');

而不用麻烦的:

pair<int,char>(40, 'c');

但是make_pair也有局限,比如我们输入一个浮点数的时候,他会默认认为是double类型的,但是有时候我们使用 copy constructor 的时候型别是非常严格的,如果我们定义的是float,而传进去的是 double 那就会调用 template constructor了。
注意:c++中,凡是提到必须返回两个值函数,一般都用pairs

2. Class auto_ptr
auto_ptr是一种智能类型的指针,它的出现是为了防止“被抛出异常的时候发生资源泄露”。
我们在使用普通指针的时候,异常发生时,程序直接退出,不会调用delete,从而造成资源泄露,我们一般通过 try()…catch()来解决:

void func(){
    MyClass* p = new MyClass;
    try(){
        ...
    }
    catch(){
        delete p;
        throw;
    }
    delete p;
}

这样做显然很麻烦,而auto_ptr的出现就解决了这个问题,它是对象的拥有者,所以auto_ptr结束的时候,它的对象也会销毁,资源自然也就回收了。无论是正常还是异常退出,只要函数退出,它就会被销毁。我们看看它的使用:

#include<memory>    //这个是auto_ptr的头文件

void func()
{
    auto_ptr<MyClass> p(new MyClass);
}

但是我们也要注意auto_ptr的定义方式:

auto_ptr<MyClass> p(new MyClass);   //ok
auto_ptr<MyClass> p = new MyClass;  //error

auto_ptr同样也可以使用迭代器operator*来指向对象,operator->指向成员,但是诸如operator++之类的指针的算数操作都是没有定义的。
我们前面说过auto_ptr销毁的时候,它所指向的对象也会跟着销毁,这就要求了:一个对象同一时刻只能被一个auto_ptr所拥有。而这个拥有权也是可以转移的,就是在拷贝的时候:

auto_ptr<MyClass> p(new MyClass);
auto_ptr<MyClass> p1;
p1 = new MyClass;   //error
p1 = p;             //ok

p1=p; 这行中,p会将拥有权转移给p1,并且如果p1之前有拥有权的话,会先销毁之前的对象。而p会指向NULL。
auto_ptr也可以作为参数传递给函数,那么它的拥有权也就转移给了函数,当函数体执行完毕就会销毁对象,所以如果想要防止这种情况发生,就:

const auto_ptr<MyClass> p(new MyClass);

这样我们在传递p指针的时候就会发生编译错误。这里的const并不意味着不能改变对象,而是不能改变拥有权。
注意这时的 p 相当于:T* const p,而不是const T* p。

auto_ptr作为成员?
有一个知识点需要注意:析构函数只有在构造函数全部执行完毕之后,才能调用。所以如果构造函数中有两次new,但只执行一个之后就抛出异常了,那么不会调用析构,从而就会发生泄漏。解决方法就是:使用auto_ptr成员,因为拷贝构造函数的存在,auto_ptr应设为const(形参中也要是const,否则编译错误)

3. 数值极限

#include<iostream>
#include<string>
#include<limits>   //头文件
using namespace std;


int main()
{
    cout<<"numeric_limits<int>::min()= "<<numeric_limits<int>::min()<<endl;     //int的最小值
    cout<<"numeric_limits<int>::max()= "<<numeric_limits<int>::max()<<endl;     //int的最大值
    cout<<"numeric_limits<short>::min()= "<<numeric_limits<short>::min()<<endl;     //short
    cout<<"numeric_limits<short>::max()= "<<numeric_limits<short>::max()<<endl;
    cout<<"numeric_limits<double>::min()= "<<numeric_limits<double>::min()<<endl;   //double
    cout<<"numeric_limits<double>::max()= "<<numeric_limits<double>::max()<<endl;

    cout<<"numeric_limits<int>::is_signed()= "<<numeric_limits<int>::is_signed<<endl;    //是否有正负号
    cout<<"numeric_limits<string>::is_specialized()= "<<numeric_limits<string>::is_specialized<<endl;    //是否定义了数值极限
    system("pause");
    return 0;
}

结果:
这里写图片描述

4. 辅助函数
STL算法程序库(“algoithm”)中有三个辅助函数:min(),max(),swap()分别是找到两值中较小、较大、和两个值的交换。
其中,值得强调的是在对象的成员需要进行交换时,使用swap()函数会非常的方便,不需要反复的复制操作,下面我们看一下实例:

#include <iostream>
using namespace std;

class CAA
{
public:
    CAA(char* str, int num){
        m_str = new char[strlen(str)+1];
        strcpy(this->m_str, str);
        this->m_num = num;
    }
    ~CAA(){
        delete[] m_str;
        m_str = NULL;
    }
    void Swap(CAA& newObject){
        swap(this->m_str, newObject.m_str);
        swap(this->m_num, newObject.m_num);
    }
    void Show(){
        cout << this->m_str << " " << this->m_num << endl;
    }
private:
    char* m_str;
    int m_num;
};

inline void Swap(CAA& object_A, CAA& object_B)
{
    object_A.Swap(object_B);
}

int main()
{
    CAA object_A("hello", 1);
    CAA object_B("world", 2);
    object_A.Show();
    object_B.Show();

    Swap(object_A, object_B);
    cout << "-----------swap()-----------" << endl;
    object_A.Show();
    object_B.Show();

    system("pause");
    return 0;
}

输出结果:
这里写图片描述

5. 比较操作符
有四个模板函数定义了:!=, >, >=, <= ,他们都是利用操作符==和<实现的。(这里思考一下是如何实现的),他们都包含在 utility 头文件中,我们只要先定义==和<,再加上rel_ops命名空间,他们就会获得定义了。实例:

#include <utility>

class CAA{
public:
    bool operator == (const CAA& a)const;
    bool operator < (const CAA& a)const;
};

using namespace std::rel_ops;

注意:因为rel_ops是std的子空间,所以我们直接 using namespace std;也是可以的。

6. exit() 和 abort()
共同点
1. 可以在任何位置终止程序而不需要返回main()函数;
2. 他们都不会销毁局部变量,所以为了保证局部对象的析构函数能够调用,应该使用异常(exception)或正常返回机制。
不同点
1. exit():
首先销毁所有 static 对象,然后清空所有 buffer, 关闭所有 I/O 通道(channels),然后终止程序。
2. abort():
不会做任何清理工作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值