点击链接加入群【C语言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG


    

上一小节,介绍了一些基础的运算符重载,那么,关于string 这个类型的 函数,大家可以百度一下,"stl string常用函数",然后找一些博客仔细研究一下,如果你对字符串的常用操作都能理解啦,那么你的是非常厉害滴,但是这些操作我们并不会常用,我简单说一下大家要达到什么样的标准,就是遇到字符串了,如果需要进行一些特殊的处理,大家应该可以百度或MSDN 找相关的函数说明,能快速的解决问题,这才是公司需要的员工,而不是我遇到问题了,一直想啊想,想到最后自己也解决不了问题,公司最怕的就是这种员工,技术经理需要的是你解决问题的能力。

所以第一个小任务: 百度 上面说的内容,将每一个函数都试一下,大家不需要做笔记,也不需要做整理,因为这些东西只有在特定的场合下会用到,我们只需要一个IDE和MSDN就可以了。


好,我们今天就来研究一下向量,如果你学过C语言(不是看我的系列哦),那么你可能写过动态数组,可能会遇到各种各样的问题,而这些在C++中都已经得到了很好的解决啦。

vector 就是我们的主角,它其实也是一个类模板,接收参数类型来实例化一个模板类,再用模板类实例化类对象。

#include <vector>

using namespace std;

int main()

{

int size = sizeof( vector<int> );

size = sizeof( vector<char> );

size = sizeof( vector<double> );

return 0;

}

通过代码测试,我们发现 不管理 实例化出来的模板类是什么类型,其大小都是 16B,所以,我们就有必要用 ALT+F12 ,再一浏览一下 vector里面的数据啦。

我们会发现它有四个保护成员,其中一个是 分配器,另3个 迭代器 分别是 _First , _Last , _End  而迭代器类型是

typedef _Tptr iterator; 而_Tptr类型是

typedef _A::pointer _Tptr; 可以看到,是分配器类型中定义的指针类型,而通过下面的声明可以看出,这个分配器类型与实例化用的类型是一样的

template<class _Ty, class _A = allocator<_Ty> >

class vector {

};

也就是说, vector<int> 这个类型中的 迭代器类型是 int * ,而 vector<string> 这个类型中的迭代器类型是 string * ,怎么样,好理解吧。


也就是说 vector 通过 3个指针和一个分配器 及相关函数来完成向量所提供的功能。

那么三个指针分别是什么意义呢? 三个值默认为0,还记得在讲C中的 realloc时的思想吗,后面的空间够用,就不用再重新申请,只需要扩展就行,如果后面的空间不足了,就要重新申请,向量也是基于这种思想的。

#include <vector>

using namespace std;

int main()

{

vector<int> vec;

for (int i = 0 ;i<17;i++)

{

vec.push_back(i);  //这里下断点

}

return 0;  //这里下断点

}

我们调试程序,然后再局部变量窗口中 将 vec展开,观察里面的值,每执行一次push_back 就观察一次,并且观察内存地址为 _First的数据,

第1次 _First指向的数据:

00381000 00 00 00 00 ....

00381004 FD FD FD FD   //  _Last 和_End指向这里 ,为了方便,我以后就写 last 和 end

第2次 我们发现原空间不够用,重新申请,地址变化了

00381048 00 00 00 00 ....

0038104C 01 00 00 00

就这样,大家一步一步的观察,多观察几次,会发现一些现象,下面我就来就总结一下:

_First   始终指向数据区的首地址

_Last   指向当前数据区中的第一个CD(未初始化的数据)的位置,如果空间已经占满,则指向下面的 FD(界限哦)

_End    指向下面的FD

想像一下,我们现在有哪些信息:数据类型 int ,数据首地址 , 数据结束地址 ,有这些信息,我们就可以通过指针进行各种访问了。只要前面的知识你都学好了,这些东东你自己都能理解了。

常用操作也就显得那么容易理解啦:

explicit vector(const A& al = A());    //构造空的向量对象

explicit vector(size_type n, const T& v = T(), const A& al = A());  //用n个值为v的来构造向量对象

vector(const vector& x);  //用另一个向量来构造向量对象,这个还有个专有名字,还记得吗,拷贝构造哦,,你要忘记啦,就是你滴错啦

vector(const_iterator first, const_iterator last,const A& al = A()); //用某一范围里的值来构造向量


size_type capacity() const;//这个函数返回当前容量,就是 _First至_End之间可以存储多少个元素哦

这个函数后面有个const 关键字,干什么用滴呢,它的作用是用来限制XXXX滴,什么不懂?当然啦,XXXX = this,这个时候你懂了吗? const vector<int> * this; 表示的是什么意思呢?意思就是函数内部如果修改成员变量的值是不行滴,就这么简单,比如我们现在看的这个函数,只是想取一下容量 ,如果将_Last的值给掉了,这是很不合乎情理滴,加上这个const就可以起到一个限制作用啦。


size_type size() const; //这个返回的是元素的个数,要理解好 它与上面函数的区别哦

void push_back(const T& x); //将一个值为x的数据插入到数据区的最后

void pop_back();  //将最后的元素移出去

reference operator[](size_type pos);  //得到下标为pos的元素的引用

reference front();  //得到最前面的元素的引用

const_reference back() const;  //得到最后一个元素的 const 引用


iterator insert(iterator it, const T& x = T());

void insert(iterator it, size_type n, const T& x);

void insert(iterator it, const_iterator first, const_iterator last);

将数据(x,或 n 个x, 或 first到last这个范围内的所有元素)插入到某个位置(it),注意这里面又用到了迭代器哦。

iterator erase(iterator first, iterator last);    //移除范围内的所有元素
其它的函数大家自己再研究研究吧,一定要记住它的成员是什么样的,就用了三个地址,所以嘛,其它很多函数都很简单啦。

下面我们来分析一下迭代器吧,这个重要的东东,我们要研究研究:

我们已经看到在string 和 vector中 迭代器都是 普通的指针类型,在复杂的结构中,尤其是当数据在内存中的存储不是连续的时候,这种指针就不行啦,而需要使用类来定义,并重载它的 ++ 方法等。我们来看一个例子,还记得 在讲c的时候 ,说到堆区通过双链表的数据结构来维护的,现在我们就来看一下双链表,list
其实也没有什么好讲的滴,我们之前已经分析过它啦,大家只需要打开MSDN,查找一下 list 主题依旧选择C/C++的,大家会看到和 vector是如此的类似,而且也提供了各种函数,大家现在看很多函数基本上都可以理解啦。而且大家应该也可以想像出来数据在内存中的存储哦,list 本身只是一个对象(12B,分配器4B,一个指针4B,一个元素个数4B),而其它的数据都是散乱滴分布在内存中,它们不像向量一样,向量的数据都是连续的,但是它们是不连续的,我们还说过链表适用于经常插入和删除的操作,因为效率高,很多东西大家都可以自己测试了,这里,我要讨论的是list里面的迭代器。
大家现在自己看看 list 这个文件的源码,也大差不差的 能看懂一些了,虽然不是很明白,但事实上我们也没有必要完全弄明白,我们现在的目的是看看迭代器是怎么动作的,
class iterator : public const_iterator   迭代器在这里面被设计成了类,

iterator(){}
iterator(_Nodeptr _P): const_iterator(_P) {}
reference operator*() const  {    return (_Acc::_Value(_Ptr));     }



iterator& operator++(){   _Ptr = _Acc::_Next(_Ptr);   return (*this);   }
iterator operator++(int){    iterator _Tmp = *this;   ++*this  ;  return (_Tmp);   }
iterator& operator--(){   _Ptr = _Acc::_Prev(_Ptr);   return (*this);    }
iterator operator--(int){  iterator _Tmp = *this;   --*this;   return (_Tmp);   }
bool operator==(const iterator& _X) const{   return (_Ptr == _X._Ptr);   }
bool operator!=(const iterator& _X) const{   return (!(*this == _X));   }

可以看到这个类重载了好多的运算符,如 *  ->  ==  != ++  ++(int)   前面两个一个是前++一个后++,(int)主要是用来区分哪个前哪个后,没有其它用处。
好高级哦,看不懂哦,其实只是大家不愿意研究罢了,我曾经把每一步都分析过了,不过我不会带着大家一步一步的分析这些STL 源码,我来讲我对它的理解以及我们之前讲过的知识与这里的融合:
数据都是内存中存在的,而且都是有关系滴,所以,我们完全可以利用这些有用的信息 用迭代器来进行指针的偏移,总之这里的解释不是很好,大家要知道我们有许多信息,比如一个节点(NODE,list中有这个定义)的大小,每个节点的内存地址及数据,我们就可以通过重载运算符让迭代器执行++操作就偏移到内存中的下一个节点的位置,这也是整个迭代器中的一个较为重要的思想,其实我的建议是大家自己研究一下整个LIST中的源码,某些函数实现不用研究,这一个研究透了,其它的也就通了,由于篇幅量太大,再加上书面形式不好表现,我就不再细讲,但是思想大家一定要有,还是指针+1的思想,类型是36个字节,则+1就偏移36个位置,类型是N个字节,则+1偏移N个位置,只不过是我们现在的类型封装在了LIST里面,叫NODE,指针也封装在LIST里面,叫迭代器,重载迭代器的各种运算符,就可以实现遍历操作啦。下在我来演示一下遍历操作:

#include <list>
using namespace std;
int main()
{
	list<int>  lst;
	lst.push_back(1);
	lst.push_back(2);
	lst.push_back(3);
	lst.push_back(4);
	list<int>::iterator iter = lst.begin();    //获取第一个元素对应的迭代器
	for (;iter != lst.end();++iter)    // 只要迭代器没有偏移到最后一个元素的后面一个位置,就让它向后偏移
	{
		*iter = 5;    //将元素的值修改为5
	}
	return 0;
}

有的人呢,还有会有一些问题,就是如果换成类就不会用了,我再举一个类的例子
#include <list>
#include <string>
#include <iostream>
using namespace std;
class CPerson
{
public:
	int		m_age;
	string  m_name;
	CPerson(int age,const string & name )
	{
		m_age = age;
		m_name = name;
	}
};
int main()
{
	list<CPerson>  lst;
	lst.push_back(CPerson(1,"周润发"));
	lst.push_back(CPerson(2,"周星驰"));
	lst.push_back(CPerson(3,"张国荣"));
	lst.push_back(CPerson(4,"谭咏麟"));
	list<CPerson>::iterator iter = lst.begin();

	for (;iter!=lst.end();++iter)
	{
		cout<< iter->m_age <<iter->m_name<<endl;
	}

	for (iter = lst.begin();iter != lst.end();++iter)
	{
		iter->m_age *=5;	// =  (*iter).m_age *=5;
		iter->m_name = "大明星";	// = (*iter).m_name ="大明星";		
	}
	//这里我们再说明一下,iter就像是指针一样,++iter就是让指针指向下一个元素
	//用iter访问元素 用 箭头  iter->
	//要用对象自己访问就要用 (*iter).
	for (iter = lst.begin();iter!=lst.end();++iter)
	{
		cout<< (*iter).m_age <<iter->m_name<<endl;
	}
	return 0;
}

个人也感觉到没有讲好这一小节,因为迭代器里面还有许多设计思想在里面,不是一句两句就可以讲明白滴,大家有兴趣可以研究研究,比如
大家可以在 MSDN 索引中 查找   iterator header file  ,看看这个迭代器的设计

template<class C, class T, class Dist = ptrdiff_t>
    struct iterator {
    typedef C iterator_category;    //迭代器子集类型
    typedef T value_type;        //值类型
    typedef Dist distance_type;    //这就是距离类型,就是 ptrdiff_t 就是两个指针的差,意思是字节数
    };

下面还有五个空定义的结构体,C++中的结构体其实是 访问权限全为PUBLIC的类,可以拥有方法也可以继承,与C中的结构不同哦,它们主要是起一个标志作用
struct input_iterator_tag {
    };
struct output_iterator_tag {
    };
struct forward_iterator_tag
    : public input_iterator_tag {
    };
struct bidirectional_iterator_tag
    : public forward_iterator_tag {
    };
struct random_access_iterator_tag
    : public bidirectional_iterator_tag {
    };

    如
    template<class U, class E=char, class Tr=char_traits<E> >
    class ostream_iterator
        : public iterator<output_iterator_tag, void, void> {        //这里就使用了output_iterator_tag 这个标志,其实整个系统还是相当庞大滴
public:
    typedef U value_type;
    typedef E char_type;
    typedef T traits_type;
    typedef basic_ostream<E, T> ostream_type;
    ostream_iterator(ostream_type& os);
    ostream_iterator(ostream_type& os, const E *delim);
    ostream_iterator<U, E, T>& operator=(const U& val);
    ostream_iterator<U, E, T>& operator*();
    ostream_iterator<U, E, T>& operator++();
    ostream_iterator<U, E, T> operator++(int);
    };

 不过我想说的是,大家能理解好迭代器是对指针的一种封装就OK了, 会用就可以了,而且我们在做项目的时候,一般是不会去用这些东西滴,这些主要是为了让大家开拓眼界罢了,现在大家的首要任务是研究好 vector和list 这两个类模板中的方法怎么使用,大部分都很简单,遇到有不会的先留着,因为我们后面还有知识点没讲。
 
 好了,大家在学习这些东西的时候一定要抓住重点,我始终讲的是思想,不是让大家去研究源码,因为我已经研究过了,在这里就是直接说它的思想,更多的是我们要学会怎么用。这一小节过后,我要休息一下啦,应该停几天再更新哦。别看C++中,我们现在只有五小节,但是信息量是非常大滴。大家慢慢吸收吸收,切匆浮沙筑高台,更不可抄之过急,循序渐近.......................