首页 > 代码库 > 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与类[转载]