想要学号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():
不会做任何清理工作。