重载运算符和STL总结。
7.1运算符重载规则
C++具有简单的运算符重载功能,例如:+、-、*、/等运算对整数、单精度数和双精度数的操作是大不相同的。由于c++已经为基本类型重载了这些运算符,因此在程序中会不知不觉地使用不同的重载版本。
7.1.1重载运算符的限制
1. C++语言中大部分预定义的运算符都可以被重载,当然也有不能进行重载的,这些不能重载的一定要记住,它们是:. .* ?: sizeof
就这四个。
重载运算符函数可以对运算符做出新的解释(即定义用户所需要的各种操作),但是运算符重载后,原有的基本语义不变,包括:
l 不改变运算符的优先级
l 不改变运算符的结合性
l 不改变运算符所需要的操作数。
2. 优先级和结合性主要体现在重载运算符的使用上,而操作数的个数不但体现在重载运算符的使用上,更关系到函数定义的参数设定。(例如:一元运算符不能有两个参数,调用时也不能作用于两个对象。
3. 不能创建新的运算符,只能系统预定义的运算符才能被重载。
7.1.2重载运算符的语法形式
运算符函数是一种特殊函数的成员函数或友元函数,定义形式为:
类型 类名::operatorop(参数)
{ //相对于该类定义的操作
}
“类型”是返回值类型,类名是要重载该运算符的类,“op”表示要重载的运算符。函数名是”operator op”,由关键字operator和被重载的运算符op组成,“参数表”列出该运算符所需要的操作数。
4. 需要注意的是重载函数可以对运算符定义新的操作,甚至编写与原来版本完全不一样的代码,所以程序员将会面临违反习惯逻辑思维的问题。
7.2 用成员或友元函数重载运算符。
1.一元函数。
一元函数无论前置还是后置,都要求只有一个操作数。
例如:object op 或 op object
1>当重载为成员函数时,编译器解释为:object.operator op()
函数operator op 所需的操作数由对象object通过指针隐含传递所以参数表为空。
2>当重载为友元函数时,编译器解释为: operator op(object)
函数operator op所需要的操作数由参数object提供。
2.二元运算符
任何二元运算符都要求有左、右操作数;
ObjectL op ObjectR
1. 当重载为成员函数时,编译器解释为: ObjectL.operator op (ObjectR) 左操作数由对象ObjectL通过this指针传递,右操作数由参数ObjectR传递
2. 重载为友元函数时,编译器解释为:operator op(ObjectL,ObjectR)左右操作数都由参数传递。
不管是成员函数还是友元函数重载,运算符的使用方法都相同。但由于他们传递参数的方法不同,因此导致是实现的代码不同,应用场合也不同。
7.2.1 用成员函数重载运算符
1. 当一元运算符的操作数,或者二元运算符的左操作数是该类的一个对象时,重载运算符函数一般定义为成员函数。
TriCoor operator+(TriCoor t);//普通重载
TriCoor&operator++();
TriCoor&operator=(TriCoor t);//用了一个地址操作符,可以通过改变内存中的地址改变参数。
2. 重载运算符函数像其他函数一样,可以返回其他C++合法类型。在该程序中,重载“+”运算符函数返回类类型Tricoor,重载载“++”和“=”运算符函数返回类类型的引用 Tricore&。“++”和“=”的运算可以作为左值表达式,函数返回类引用既符合运算符原来的语义,又减少了函数返回时对匿名对象数据复制的开销。
7.2.2 用友元函数重载运算符
1.当一个运算符的操作需要修改类对象状态时,应该以成员函数重载。例如,需要左值操作数的运算符(如=、*=、++等)应该用成员函数重载。如果以友元函数重载,则可以使用引用参数修改对象。
2.如果希望运算符的操作数(尤其是第一个操作数)有隐式转换,则重载运算符时必须用友元函数或普通函数。
3.c++中不能用友元函数重载的运算符有:
= () [] ->
7.3几个典型运算符的重载
7.3.1重载++与—
1.自增和自减有前置和后置两种形式。每个重载运算函数都必须有明确的特征,使编译器确定要使用的版本。C++规定,前置形式重载为一元运算符函数,后置形式重载为二元运算符函数。
2.(1)前置自增表达式(设类A的对象 Aobject)
++Aobject 若用成员函数重载,则编译器解释为:
Aobject.operator++()
对应的函数原型为:A&A::operator++(0);
若用友元函数重载,则编译器解释为:operator++(Aobject)
对应的函数原型为:friend A&operator++(A&);
(2)后置自增表达式
Aobject++ 成员函数重载的解释为:Aobject.operator++(0)
对应的函数原型为:A&A::operator++(int);
友元函数重载的解释为:operator++(Aobject,0)
对应的函数原型为:friendA&operator++(A&,int)//在此参数0是一个伪值,用于与前置形式重载相区别。另外,友元函数返回类类型的引用是为了减少函数返回时对象复制的开销,可以根据需要选择是否返回类类型的引用.
3. 函数体不应该使用伪参数,否则会引起调用的二义性。
7.3.2重载赋值运算符
赋值运算符重载用于对象数据的复制,只能用成员函数重载,重载函数类型为: 类名&类名::operator=(类名);
注意:
l 重载赋值运算符函数和复制构造函数的实现十分相似。不同的是,重载函数返回*this,以符合语言版本的原有赋值语义。
l 复制构造函数和重载赋值运算符函数虽然都是实现数据成员函数的复制,但执行时机不同。前者用于对象的初始化,后者用于程序运行时修改对象的数据。
l C++提供系统版本的重载赋值运算,实现数据成员的简单复制。这一点和浅复制的操作一样。所以,对于用指针管理堆的的数据对象,以及绝大多数重要的类,系统赋值运算符操作往往不够,需要程序员自己重载。
l 运算符函数operator=必须重载为成员函数,而且不能被继承。
7.3.3重载运算符[]和()
运算符“[]”和“()”只能用成员函数重载,不能用友元函数重载。
1. 重载下标运算符[]
对象[表达式]
2. 重载函数调用运算符()
形式为: 对象(表达式表) 其中“表达式表”可以为空。
7.3.4重载流插入和流提取运算符
运算符“<<”和”>>”在C++的流类库中重载为插入和提取操作,用于输出和输入标准类型的数据和字符串。程序员也可以重载这两个运算符,通常用于传输用户自定义类型的数据。
1.“<<”插入运算符,只能被重载成友元函数,不能重载为成员函数。
2.“>>”提取运算符,只能被重载成友元函数。
STL
概述:
- STL是C++标准程序库的核心,深刻影响了标准程序库的整体结构
- STL由一些可适应不同需求的集合类(collection class),以及在这些数据集合上操作的算法(algorithm)构成
- STL内的所有组件都由模板(template)构成,其元素可以是任意类型
- STL是所有C++编译器和所有操作系统平台都支持的一种库
STL组件:
- 容器(Container) - 管理某类对象的集合
- 迭代器(Iterator) - 在对象集合上进行遍历
- 算法(Algorithm) - 处理集合内的元素
- 容器适配器(container adaptor)
- 函数对象(functor)
STL组件之间的协作:
容器—>通过迭代器
—>算法—>通过迭代器->容器
容器—>通过迭代器
STL容器类别:
- 序列式容器-排列次序取决于插入时机和位置
- 关联式容器-排列顺序取决于特定准则
STL容器的共同能力:
- 所有容器中存放的都是值而非引用。如果希望存放的不是副本,容器元素只能是指针。
- 所有元素都形成一个次序(order),可以按相同的次序一次或多次遍历每个元素。
STL容器元素的条件:
- 必须能够通过拷贝构造函数进行复制
- 必须可以通过赋值运算符完成赋值操作
- 必须可以通过析构函数完称销毁动作
- 序列式容器元素的默认构造函数必须可用
- 某些动作必须定义operator ==,例如搜寻操作
- 关联式容器必须定义出排序准则,默认情况是重载operator<
对于基本数据类型(int,long,char,double,……)而言,以上条件总是满足。
STL容器的共同操作:
- 初始化
1. 产生一个空容器std::list<int>I;
2. 以另一个容器元素为初始值完成初始化
Std::list<list>I;…
Std::vector<float>c(I.begin(),I.end());
3. 以数组元素为初值完成初始化
Int array[]={2,4,6,1345};…
Std::set<int>c(array,array+sizeof(array)/sizeof(array[0]));
- 与大小相关的操作(size operator)
1. Size()-返回当前容器的元素数量
2. Empty()-判断容器是否为空
3. Max-size()-返回容器能容纳的最大元素数量
- 比较(comparison)
1. ==,!=,<,<=,>=
2. 比较操作两端的容器必须属于同一类型
3. 如果两个容器内的所有元素按序相等,那么这两个容器相等
4. 采用字典式顺序判断某个容器是否小于另一容器
- 赋值和交换
Swap用于提高赋值操作效率
- 与迭代器(iterator)相关的操作
1. begin()-返回一个迭代器,指向第一个元素
2. end()-返回一个迭代器,指向最后一个元素之后
3. rbegin()-返回一个逆向迭代器,指向逆向遍历的第一个元素
4. rend()-返回一个逆向迭代器,指向逆向遍历的最后一个元素之后
迭代器(iterator)(示例:iterator)
a) 可遍历STL容器内全部或部分元素的对象
b) 指出容器中的一个特定位置
c) 迭代器的基本操作
所有容器都提供两种迭代器
1. container::iterator以“读/写”模式遍历元素
2. container::const_iterator以“只读”模式遍历元素
迭代器分类:
1. 双向迭代器:可以双向行进,以递增运算前进或以递减运算后退。List,set,map提供双向迭代器
2. 随机存取迭代器:除了具备双向迭代器的所有属性,还具备随机访问能力。可以对迭代器增加或减少一个偏移量,处理迭代器之间的距离或者使用<和>之类的关系运算符比较两个迭代器。Vector.deque和string提供随机迭代器。
- vector模拟动态数组 vector的元素可以是任意类型T,但必须具备赋值和拷贝能力(具有public拷贝构造函数和重载的赋值操作符)
- 必须包含的头文件#include <vector>
- vector支持随机存取
- vector的大小(size)和容量(capacity)
i. size返回实际元素个数,
ii. capacity返回vector能容纳的元素最大数量。如果插入元素时,元素个数超过capacity,需要重新配置内部存储器。
- 赋值操作
所有的赋值操作都有可能调用元素类型的默认构造函数,拷贝构造函数,赋值操作符和析构函数。
- 元素存取
- 迭代器相关函数
Begin:指向第一个元素。而end指向最后一个元素的后面。也就是说end不指向元素。
- 安插(insert)元素
安插的一系列函数几乎都与insert有关。
- 移除(remove)元素
c.clear()//移除所有元素,清空容器。
Map/muitimap
- 使用平衡二叉树管理元素
- 元素包含两部分(key,value),key和value可以是任意类型
- 必须包含头文件#include<map>
- 根据元素的key自动对元素排序,因此根据元素的key进行定位很快,但根据元素的value定位很慢。
- 不能直接改变元素的key,可以通过operator[]直接存取元素值
- map中不允许key有相同的元素,multimap允许key相同的元素。
- 内部存储结构
- 构造,拷贝和析构 map(beg,end)//以区间[beg,end]内的元素产生一个map。
- 非变动性操作
c.size()//返回元素的个数
c.empty()//判断容器是否为空
c.max_size()//判断元素最大可能数量
- 赋值
- 特殊搜寻操作
find(key) //返回“键值等于key”的第一个元素,找不到返回end。
- 迭代器相关函数(和迭代器部分的用法一致)
Set/multiset
- 使用平衡二叉树管理元素
3. map容器是键-值对的集合,好比以人名为键的地址和电话 号码。相反地,set容器只是单纯的键的集合。当我们想知道某位用户是否存在时,使用set容器是最合适的。
l set中不允许key相同的元素,multiset允许key相同的 元素
例子:begin()//返回指向第一个元素的迭代器
clear()//清除所有元素
equal_range()//返回集合中与给定值相等的上下限的
两个迭代器。
Pair模板:
Pair模板可以用于生成key-value对
Algorithm(算法)
泛型算法通则:
所有算法的前两个参数都是一对iterators:[first,last),用来指出容器内一个范围内的元素。
1. 每个算法的声明中,都表现出它所需要的最低层次的iterator类型。
2. 大部分算法都可以用function object来更改准则。Functionobject又称function
Count计算元素个数。
STL心得:STL据说很好用,当然有前提,就是熟悉它,得会用。目前,对于我来说,说实话,知识点真多,记不住,也不会用,感觉挺难的,正在学习阶段。