首页 > 代码库 > C++stl与类[转载]

C++stl与类[转载]

原文地址: http://www.cnblogs.com/jianxinzhou/p/3982771.html

**********************作者是不是转的我不清楚呀

OOP之类和对象

1. this指针的引入

每个成员函数都有一个额外的隐含的形参,这个参数就是this指针,它指向调用对象的地址。默认情况下,this的类型是指向类类型非常量版本的常量指针。可以表示成如下伪代码形式:

/* 假设现在有一个类Sales_data,以及其非常量Sales_data类型对象,则该隐式的this指针可以写成如下伪代码形式 */Sales_data *const this = &total;

this指针一般用于解决重名问题和返回自身的值或者引用。例如:

struct A{    int a;    void test(int a){        this->a = a;    }};

test函数的形参a和类成员a成名,根据就近原则,直接使用a,调用的是形参a,那么如何使用被屏蔽的成员a呢,这里就是采用this指针。

2. const成员函数

紧随参数列表之后的const关键字作用为:修改隐式this指针所指向的对象的类型,如下:

/* 假设现在有一个类Sales_data,以及Sales_data类型对象,则在const成员函数中隐式的this指针可以写成如下伪代码形式 */const Sales_data *const this = &total;

这里加const的含义是,这个函数不能修改本对象,其实就是函数体内不得对类的成员进行修改。const主要起到保护的作用。

注意以下几点:

a)非const对象可以调用const成员函数,也可以调用非const成员函数,但是const对象只能调用const成员函数。并且,非const对象优先调用非const成员函数。

b)const成员函数只可以返回本对象的常量引用,如下写法会报错:

Student &print(ostream &os) const{    os << id_ << " " << name_ << " " << age_ << endl;    return *this;}

报错提示:

clang下:error: binding of reference to type ‘Student‘ to a value of type ‘const Student‘ drops qualifiers
            return *this;

g++下:error: invalid initialization of reference of type ‘Student&’ from e
            return *this;

最后记住:构造函数不能为const。如果为const,怎么完成初始化工作?!

3. const成员函数和非const成员函数可以构成重载。

到此为止,构成函数重载的要素有:类的名称、函数名、函数形参表以及成员函数的const属性。事实上,函数签名就是由这几个部分构成。

在这里我们解释一个问题: 为什么C语言里面没有函数重载? 因为在编译器编译C程序时会维护一张符号表,C语言在记载函数的时候就是简单的记录函数的名字,所以函数名就是C函数的唯一标识。当我们试图定义两个名字相同的函数时,就发生了重定义。

C++是怎么做的呢? 很显然,对于普通函数,它的符号(唯一标识)是根据函数名和参数列表生成的,对于类的成员函数,还要加上类名和const属性,所以我们进行函数重载的时候,这些函数在符号表中的标识是不相同的。 C++正是通过这种机制实现了函数的重载

注意:C++编译器生成函数符号的时候没有考虑返回值,这也是函数重载和返回值无关的原因。

4. 构造函数之构造函数初始值列表(constructor initialize list)

构造函数有一个特殊的地方,就是它可以包含一个构造函数初始化列表,如下:

Person(int id, const string &name, int age)         :_id(id), _name(name), _age(age){}

虽然以下形式,也完全可以达到目的:

Person(int id, const string &name, int age){        _id = id;        _name = name;        _age = age;}

但两者是不同的。第一种形式带构造函数初始值列表,执行的是真正的初始化工作;而第二种形式,进行的是赋值操作。

注意,即使构造函数没有构造函数初始值列表(更确切的说是构造函数初始值列表为空),那么类中的成员变量将会执行默认初始化。因此在以下情况我们必须使用构造函数默认初始化列表:

a)const内置类型变量以及没有显示定义默认构造函数的const类类型变量(可以参考该博文合成的默认构造函数定义为delete的一种情况)

b)引用类型成员

c)没有默认构造函数的类类型变量

其本质是因为,const内置类型变量和引用类型必须初始化;而对于类类型对象,可以通过默认构造函数进行默认初始化(非const类类型对象只要有默认构造函数就可以默认初始化,而const类类型对象必须有显示定义的默认构造函数才可以执行默认初始化)

5. 类成员初始化的顺序是它们在类中声明的顺序,而不是初始化列表中列出的顺序

考虑下面的类:

class X {    int i;    int j;public:    X(int val) :    j(val), i(j) {    }};

我们的设想是这样的,用val初始化j,用j的值初始化i,然而这里初始化的次序是先i然后j。

记住:类成员初始化的顺序是它们在类中声明的顺序,而不是初始化列表中列出的顺序!

6. 析构函数

与构造函数一样,析构函数也是一种特殊的函数。构造函数在对象被创建时调用,析构函数则是在对象被销毁时被调用。构造函数与构造函数一样,同样没有返回值,并且析构函数没有任何参数。如下:

~Person(){        }

需要引起注意的是:

a)对于类类型对象foo的析构函数只是在它生命期的最后一刻的回调罢了,管不了foo自己所占的内存,就像自己没法给自己收尸一样。

b)对于堆上的类类型对象:free 干的事情是释放内存。delete 干的事情是调用析构函数,然后释放内存,注意是delete释放的内存空间,而不是析构函数释放的。对于栈上的类类型对象,退出作用域时会自动调用析构函数,然后释放内存。

总结:对于栈上的类类型对象其实和内置类型变量一样,退出作用域后都是由系统自动释放内存的。实际上无论是栈空间,还是堆空间,内置类型对象和类类型对象销毁时的区别,在于类对象会在销毁前调用析构函数。

7. static成员

不用于普通的数据成员,static 数据成员独立于该类的任何一个对象而存在,每个static数据成员是与类关联,并不与该类的对象相关联。

正如类可以定义共享的 static 数据成员一样,类也可以定义 static 成员函数。static 成员函数没有 this 形参(因为static成员不属于任何一个对象),它可以直接访问所属类的 static 成员,但不能直接使用非 static 成员(因为没有this指针)。当我们在类的外部定义 static成员时,无须重复指定 static 保留字,该保留字只出现在类定义体内部的声明处即可。

小结:

a)static 成员是类的组成部分但不是任何对象的组成部分,因此,static 成员函数没有 this 指针

b)因为 static 成员不是任何对象的组成部分,所以 static 成员函数不能是const成员函数。因为,将成员函数声明为 const 就是承诺不会修改该函数所属的对象,而 static 成员不是任何对象的组成部分。

c)static 函数只能使用 static 成员,而不能直接调用普通成员(方法+数据成员),当然如果这样写,static void print(Test &t) 谁也挡不住其调用对象t的普通成员。

d)static 成员一般在类内声明,类外定义。注意,当我们在类的外部定义 static 成员时,无须重复指定 static 保留字,该保留字只出现在类定义体内部的声明处即可。

8. 友元

1. 必须先定义包含成员函数的类,才能将这个类的成员函数设置为另外一个类的友元。

2. 不必预先声明类和非成员函数来将它们设为友元。

#include <iostream>#include <string>#include <vector>using namespace std;class Test{    public:        friend class Other;                //声明某个类是Test的朋友        friend void bar(const Test &t);     //声明某个函数是Test的朋友    private:        int x_;        int y_;};class Other{    public:        void foo(Test &t)        {            t.x_ = 10;            t.y_ = 20;        }};void bar(const Test &t){    cout << t.x_ << endl;}int main(int argc, const char *argv[]){    Test t;    return 0;}

注意:友元关系是单向的,以上例子中Test并不是Other的朋友,因此Test不能访问Other的private成员。(tmd,这不就是在告诉我们,你的是我的,我的还是我的)。顺便黑一下C++:

C++ is a modern language where your parent can‘t touch your privates but your friends can.

多么痛的领悟。

STL之顺序容器

1. 顺序容器的初始化

顺序容器主要是vector和list,他们的初始化方式有以下五种:

1. 直接初始化一个空的容器

2. 用一个容器去初始化另一个容器

3. 指定容器的初始大小

4. 指定容器的初始大小和初始值

5. 用一对迭代器范围去初始化容器

第2种和第5种初始化方式的区别在于:第2种不仅要求容器类型相同,还要求容器元素类型完全一致,而第5种不要求容器相同,对于容器元素,要求能相互兼容即可。

指针可以当做迭代器,所以可以这样做:

#include <iostream>#include <string>#include <vector>using namespace std;int main(int argc, char **argv) {    
    const size_t MAX_SIZE = 3;    string arr[MAX_SIZE] = { "hello", "world", "foobar" };    vector<string> vec(arr, arr + MAX_SIZE);    return 0;}

注意,凡是传入迭代器作为指定范围的参数,可以使用指针代替。

2. 容器元素的类型约束

凡是放入vector中的元素,必须具备复制和赋值的能力,因为放入vector中的元素只是一份拷贝。下例会报错。

#include <iostream>#include <string>#include <vector>using namespace std;//Test不支持复制和赋值。所以不能放入vectorclass Test{    public:        Test() {}    private:        //设为私有,禁用了Test的复制和赋值能力         Test(const Test &);           //用于复制(拷贝构造函数)        void operator=(const Test &); //用于赋值(赋值运算符)};int main(int argc, const char *argv[]){    vector<Test> vec;    Test t;    vec.push_back(t);    return 0;}

3. 特殊的迭代器成员 begin和end

有四个特殊的迭代器:

c.begin()   //指向容器C的第一个元素

C.end()     //指向最后一个元素的下一个位置

C.rbegin() //返回一个逆序迭代器,指向容器c的最后一个元素

C.rend()   //返回一个逆序迭代器,指向容器c的第一个元素的前面的位置

分别去顺序迭代和逆序迭代容器,例如:

#include <iostream>#include <string>#include <vector>#include <list>using namespace std;int main(int argc, char **argv) {    vector<string> vec;    vec.push_back("beijing");    vec.push_back("shanghai");    vec.push_back("guangzhou");    vec.push_back("shenzhen");    for (vector<string>::iterator iter = vec.begin(); iter != vec.end();            ++iter) {        cout << *iter << endl;    }    for (vector<string>::reverse_iterator iter = vec.rbegin();            iter != vec.rend(); ++iter) {        cout << *iter << endl;    }    return 0;}/*output:beijingshanghaiguangzhoushenzhenshenzhenguangzhoushanghaibeijing*/

4. 顺序容器的插入操作

1. vector没有push_front(vectoe内部实现是数组)。list有push_front。

2. 针对List

a)可以使用insert(p, t)   在指定位置元素之前添加元素,其中p是迭代器,t时元素的值

b)Insert(p, n, t)          在迭代器p指向的位置之前插入n个元素,初始值为t

c)Insert(p, b, e)          在迭代器p指向的位置之前插入迭代器b和迭代器e之间的元素

d)可是使用push_front   头插

5. 顺序容器的删除操作

1. 删第一个或最后一个元素

类似与插入元素,pop_front或者pop_back可以删除第一个或者最后一个元素

2. 删除容器的一个元素

与insert对应,删除采用的是erase操作,该操作有两个版本:删除由一个迭代器指向的元素,或者删除由一对迭代器标记的一段元素。删除元素需要接收返回值,防止迭代器失效,最好使用while循环。

6. 容器大小的操作

vector与容量有关的函数:

a)size         元素数目,类似于会议室中人的数目

b)resize      调整元素数目,类似于调整函数

c)capacity   可容纳数目,类似于会议室中的座位数量

d)reserve   告诉vector容器应该预留多少个元素的存储空间

7. 迭代器的失效问题

任何insert或者push操作都可能导致迭代器失效。当编写循环将元素插入到vector或list容器中时,程序必须确保迭代器在每次循环后都得到更新。

vector迭代器持续有效,除非:

1. 使用者在较小的索引位置插入或者删除元素。

2. 由于容量的变化引起的内存重新分配。

list迭代器持续有效,除非:

将it指向的元素删除,那么it则失效(list内部实现是链表,it指向的元素删了就是没有了,再用it访问直接段错误。vector也有可能失效,只不过后面的元素会往前移,再用it访问可能不会产生段错误)。

删除元素需要接收返回值,最好使用while循环。例如删除下例删除偶数:

vector<int>::iterator it = vec.begin();
while(it != vec.end()){    if(*it % 2 == 0)        //vec.erase(it);        it = vec.erase(it);    else        ++it;}

8. vector的for_each方法

遍历vector方法:

1. 下标

2. 迭代器

3. for_each

#include <iostream>#include <string>#include <vector>#include <algorithm>using namespace std;void print(int i){    cout << i << endl;}int main(int argc, const char *argv[]){    vector<int> vec;    vec.push_back(12);    vec.push_back(23);    vec.push_back(45);    vec.push_back(56);    vec.push_back(221);    vec.push_back(35);    vec.push_back(129);    for_each(vec.begin(), vec.end(), print);    return 0;}/*output:1223455622135129*/

9. vector和list的区别

a) vector采用数组实现,list采用链表。

b) vector支持随机访问,list不提供下标。

c) 大量增加删除的操作适合使用list。

10. string之截取子串substr

例子如下:

#include <iostream>#include <string>#include <vector>using namespace std;int main(int argc, const char *argv[]){    string s = "helloworldfoo";    string s2 = s.substr(1, 4); //ello    cout << s2 << endl;    return 0;}

注意,迭代器一般是取基地址到尾后地址的一段范围。而下标操作,通常是基地址+长度。

11. stack

#include <iostream>#include <string>#include <vector>#include <stack>using namespace std;int main(int argc, const char *argv[]){    stack<int> s;    s.push(10);    s.push(22);    s.push(23);    s.push(1);    s.push(8);    s.push(99);    s.push(14);    while(!s.empty())    {        cout << s.top() << endl;        s.pop();    }    return 0;}/* 输出如下:149981232210*/

12. queue

#include <iostream>#include <string>#include <vector>#include <queue>using namespace std;int main(int argc, const char *argv[]){    queue<int> q;    q.push(12);    q.push(23);    q.push(4);    q.push(5);    q.push(7);    while(!q.empty())    {            cout << q.front() << endl;        q.pop();    }    return 0;}/*输出:1223457*/

13. 优先级队列(用堆实现)

例1:

#include <iostream>#include <string>#include <vector>#include <queue>using namespace std;int main(int argc, const char *argv[]){    priority_queue<int> q;    q.push(12);    q.push(99);    q.push(23);    q.push(123);    while(!q.empty())    {        cout << q.top() << endl;                q.pop();    }    return 0;}/*output:123992312*/

例2:

#include <iostream>#include <string>#include <vector>#include <queue>using namespace std;int main(int argc, const char *argv[]){    priority_queue<int, vector<int>, greater<int> > q;        q.push(12);    q.push(99);    q.push(23);    q.push(123);    while(!q.empty())    {        cout << q.top() << endl;                q.pop();    }    return 0;}/*output:122399123*/
#include <iostream>#include <string>#include <vector>#include <queue>using namespace std;int main(int argc, const char *argv[]){    priority_queue<int, vector<int>, less<int> > q;    q.push(12);    q.push(99);    q.push(23);    q.push(123);    while(!q.empty())    {        cout << q.top() << endl;                q.pop();    }    return 0;}/*output:123992312*/

例3:传入函数对象

#include <iostream>#include <string>#include <vector>#include <queue>using namespace std;struct Score{    int score_;    string name_;    Score(int score, const string name)        :score_(score), name_(name)    { }};class Cmp{    public:        bool operator() (const Score &s1, const Score &s2)        {            return s1.score_ < s2.score_;        }};// Cmp p;// p(s1, s2)int main(int argc, const char *argv[]){    priority_queue<Score, vector<Score>, Cmp> q;        q.push(Score(67, "zhangsan"));    q.push(Score(88, "lisi"));    q.push(Score(34, "wangwu"));    q.push(Score(99, "foo"));    q.push(Score(0, "bar"));    while(!q.empty())    {        cout << q.top().name_ << " : " << q.top().score_ << endl;        q.pop();    }    return 0;}/*output:foo : 99lisi : 88zhangsan : 67wangwu : 34bar : 0*/

STL之关联容器

1. Pair类型

Pair是一种简单的关联类型。注意:pair不是容器,而是代表一个key-value键值对。

示例1:

#include <iostream>#include <string>#include <utility>using namespace std;int main(int argc, const char *argv[]){    pair<int, int> p1;    p1.first = 10;    p1.second = 12;        pair<int, string> p2;    p2.first = 12;    p2.second = "hello";    pair<string, string> p3;}
示例2:
#include <iostream>#include <string>#include <vector>using namespace std;//生成pair对象的三种方法int main(int argc, const char *argv[]){    vector<pair<string, int> > vec;    pair<string, int> word;    word.first = "hello";    word.second = 12;    vec.push_back(word);    pair<string, int> word2("world", 12);    vec.push_back(word2);        vec.push_back(make_pair("foo", 3));}

示例3:vector中装入pair,实现统计词频:

#include <iostream>#include <string>#include <vector>#include <utility>using namespace std;typedef vector<pair<string, int> > Dict;void makeDict(Dict &dict, const vector<string> &words);void addWordToDict(Dict &dict, const string &word);int main(int argc, const char *argv[]){    vector<string> words;    string word;    while(cin >> word)        words.push_back(word);    Dict dict;    makeDict(dict, words);        for(const pair<string, int> &p : dict)    {        cout << p.first << " : " << p.second << endl;    }    return 0;}void makeDict(Dict &dict, const vector<string> &words){    dict.clear();    for(vector<string>::const_iterator it = words.begin();        it != words.end();        ++it)    {        addWordToDict(dict, *it);    }}void addWordToDict(Dict &dict, const string &word){    Dict::iterator it;    for(it = dict.begin();            it != dict.end();            ++it)    {        if(it->first == word)        {            ++it->second;            break;        }    }        if(it == dict.end())        dict.push_back(make_pair(word, 1));}

2. map

map可以看做是一种存储pair类型的容器,内部采用二叉树实现(编译器实现为红黑树)。

1. pair不是容器,而是代表一个key-value键值对;而map则是一个容器,里面存储了pair对象,只是存储的方式与vector<pair>这种连续存储,有所不同,map采用的是二叉排序树存储pair,一般而言是红黑树,因此内部是有序的

2. 当map使用下标访问时,如果key不存在,那么会在map中添加一个新的pair,value为默认值

示例1:

#include <iostream>#include <string>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, int> m;    m["beijing"] = 2000;    m["shenzhen"] = 1000;    m["shanghai"] = 1500;    m["hongkong"] = 500;    m["hangzhou"] = 880;    for(map<string, int>::const_iterator it = m.begin();        it != m.end();        ++it)    {        //*it pair        cout << it->first << " : " << it->second << endl;    }    return 0;}/*output:beijing : 2000hangzhou : 880hongkong : 500shanghai : 1500shenzhen : 1000*/
// 由于key是string类型,因此输出按字典序。

示例2:

#include <iostream>#include <string>#include <vector>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, int> m;    m["beijing"] = 40;    m["shenzhen"] = 30;    m["guangzhou"] = 37;    cout << m.size() << endl; //3    cout << m["shanghai"] << endl;    cout << m.size() << endl;    return 0;}/*output:304*/

3. map 的 key 必须具有小于操作符 operator <

以下为错误代码:

#include <iostream>#include <map>using namespace std;struct Test{    int a;};int main(int argc, const char *argv[]){    map<Test, int> m;      Test t;    m[t] = 1;}/* 编译报错,因为Test对象在次数为key-value对中的key,但其并没有定义 operator< 运算符,红黑树无法进行排序 */

4. map查找元素的效率是lgn,因为树的高度不超过O(lgN)

示例:使用map,实现统计词频,如下:

#include <iostream>#include <string>#include <vector>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, int> words;    string word;        /* 如果key(word)存在,则value++; 如果word不存在,此处会在map(words)中添加一个新的pair,value为默认值(此处为0),然后value++ */    while(cin >> word)        words[word]++;    for(const pair<string, int> &p : words)        cout << p.first << " : " << p.second << endl;    return 0;}

5. 在map中添加元素

刚才我们看到,采用下标的方式,可以给map添加元素,但更好的做法时采用insert插入一个pair对象。

这里值得注意的是insert的返回值,其返回了一个pair对象,第一个元素是指向该key所在的那个pair对象的的迭代器,第二个则表示插入是否成功。使用insert插入map元素时,如果失败,则不会更新原来的值。看下面例子:

#include <iostream>#include <string>#include <vector>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, int> m;    m.insert(make_pair("hello", 1));    m.insert(make_pair("foo", 1));    m.insert(make_pair("bar", 1));    m.insert(make_pair("hello", 1));    cout << "size : " << m.size() << endl;    /* insert的返回值:指向key所在pair的迭代器,以及表示插入是否成功的布尔值 */    pair<map<string, int>::iterator, bool> ret;        // 之前没有这个key,插入成功    ret = m.insert(make_pair("fwfgwfg", 23));    cout << "ret = " << ret.second << endl;        // 之前已有的key,插入失败。插入失败的话,不会更新原来的value值    ret = m.insert(make_pair("hello", 25425));    cout << "ret = " << ret.second << endl;    cout << ret.first->second << endl;    return 0;}/*output:size : 3 ret = 1       ret = 01*/

下面的程序仍然是实现统计词频:

#include <iostream>#include <string>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, int> words;    string word;    pair<map<string, int>::iterator, bool> ret;    while(cin >> word)    {        ret = words.insert(make_pair(word, 1));        if(ret.second == false) //word已经存在            ++ret.first->second;    }    for(const pair<string, int> &p : words)        cout << p.first << " : " << p.second << endl;    return 0;}

综上,在本章中我们已经使用三种方式,去统计词频了,分别是:vector中使用pair, map的下标访问方式以及map的insert方式。

6. 在map中查找元素

刚才看到可以利用下标获取value的值,但是这样存在一个弊端,如果下标访问的是不存在的元素,那么会自动给map增加一个键值对,这显然不是我们所预期的。

我们可以采用 count 和 find 来解决问题,其中 count 仅仅能得出该元素是否存在,而 find 能够返回该元素的迭代器。

示例1:

#include <iostream>#include <string>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, string> m;    m["beijing"] = "bad";    m["shanghai"] = "just soso";    m["shenzhen"] = "well";    m["hangzhou"] = "good";    cout << m.count("hangzhou") << endl;    cout << m.count("HK") << endl;    return 0;}/*output:10*/

示例2:

#include <iostream>#include <string>#include <map>using namespace std;int main(int argc, const char *argv[]){    map<string, string> m;    m["beijing"] = "bad";    m["shanghai"] = "just soso";    m["shenzhen"] = "well";    m["hangzhou"] = "good";        // find的返回值    map<string, string>::iterator it = m.find("HK");        if(it == m.end())        cout << "不存在" << endl;    else        cout << it->first << " : " << it->second << endl;        return 0;}/*output:不存在*/

3. set

Set类似于数学上的集合,仅仅表示某个元素在集合中是否存在,而不必关心它的具体位置。同样,set中的元素互异,也就是无法两次插入相同的元素。set 底层采用红黑树实现,按照值进行排序,map则按照key进行排序。使用方式和map类似,但是简单很多。

示例1:

#include <iostream>#include <string>#include <set>using namespace std;int main(int argc, const char *argv[]){    set<int> s;        // set不会插入重复的元素    for(int i = 0; i < 20 ; ++i)    {        s.insert(i);        s.insert(i);    }    cout << "size : " << s.size() << endl;    return 0;}/*output:size : 20*/

示例2:

#include <iostream>#include <string>#include <vector>#include <set>#include <stdlib.h>using namespace std;int main(int argc, const char *argv[]){    srand(10000);    set<int> s;        for(int i = 0; i < 40; ++i)    {        s.insert(rand() % 100);    }        // 注意是有序的    for(int i : s)    {        cout << i << " ";    }    cout << endl;    return 0;}/*output:4 5 8 12 13 15 16 20 21 22 24 25 27 32 38 39 42 43 46 50 54 57 59 63 66 72 78 82 85 93 94 96 98*/

4. 小结

map 中key 的值是唯一的,set 中的元素都是唯一的。

1. map和set比较:

a) 二者均使用红黑树实现

b) key需要支持<操作

c) map侧重于key-value的快速查找

d) set侧重于查看元素是否存在

2. 用户无法对map和set中的元素进行排序,不然会干扰map和set本身的实现。

最后注意:map 的 key 值是不可更改的。而 set 中的 value 虽然可以更改,但不建议这样做,真有需求,直接删除即可。

C++stl与类[转载]