一、 运算符重载
运算符重载使得用户自定义的数据以一种更简洁的方式工作。
(一)运算符重载的规则
1.重载运算符的限制
不能重载的算符
. :: .* ?: sizeof
可以重载的运算符
+ - * / % ^ & | ~
! = < > += -= *= /= %
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- ->* ‘ ->
[] () new delete new[] delete[]
a.重载运算符函数可以对运算符作出新的解释,但原有基本语义不变:
Ø不改变运算符的优先级
Ø不改变运算符的结合性
Ø不改变运算符所需要的操作数
Ø不能创建新的运算符
(二)用成员或友元函数重载运算符
Ø 运算符函数可以重载为成员函数或友元函数
1.一元运算符
Object op 或 op Object
Ø 重载为成员函数,解释为:
Object . operatorop ()
操作数由对象Object通过this指针隐含传递
Ø重载为友元函数,解释为:
operator op (Object)
操作数由参数表的参数Object提供
2. 二元运算符
ObjectL opObjectR
Ø 重载为成员函数,解释为:
ObjectL . operatorop ( ObjectR )
左操作数由ObjectL通过this指针传递,右操作数由参数ObjectR传递
Ø重载为友元函数,解释为:
operator op (ObjectL, ObjectR )
左右操作数都由参数传递
3.成员运算符函数的原型在类的内部声明格式如下:
class X {
//…
返回类型operator运算符(形参表);
//…
}
4.在类外定义成员运算符函数的格式如下:
返回类型X::operator运算符(形参表)
{
函数体
}
5.双目运算符重载为成员函数
对双目运算符而言,成员运算符函数的形参表中仅有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含地传递给函数的。
一般而言,如果在类X中采用成员函数重载双目运算符@,成员运算符函数operator@ 所需的一个操作数由对象aa通过this指针隐含地传递,它的另一个操作数bb在参数表中显示,aa和bb是类X的两个对象,则以下两种函数调用方法是等价的:
aa @ bb; // 隐式调用
aa.operator @(bb); // 显式调用
6. 单目运算符重载为成员函数
对单目运算符而言,成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。
【例】
有一个Time类,包含数据成员minute(分)和sec(秒),模拟秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。要求输出分和秒的值。
class Time
{
public:
Time( ){minute=0;sec=0;}
Time(int m,int s):minute(m),sec(s){ }
Time operator++( ); //声明前置自增运算符“++”重载函数
Time operator++(int); //声明后置自增运算符“++”重载函数
private:
int minute;
int sec;
};
Time Time∷operator++( ) //定义前置自增运算符“++”重载函数
{
if(++sec>=60) {
sec-=60; //满60秒进1分钟
++minute;
}
return *this; //返回当前对象值
}
Time Time∷operator++(int) //定义后置自增运算符“++”重载函数
{
Time temp(*this);
sec++;
if(sec>=60) {
sec-=60;
++minute;
}
return temp; //返回的是自加前的对象
}
一般而言,采用成员函数重载单目运算符时,以下两种方法是等价的:
@aa; // 隐式调用
aa.operator@(); // 显式调用
成员运算符函数operator@所需的一个操作数由对象aa通过this指针隐含地传递。因此,在它的参数表中没有参数。
7.用友元函数重载
友元函数重载运算符常用于运算符的左右操作数类型不同的情况。
【例】
class Complex
{ int Real ; int Imag ;
public :
Complex ( int a ) { Real = a ; Imag = 0 ; }
Complex ( int a , int b ) { Real = a ; Imag = b ; }
Complex operator + ( Complex ) ;
…...
} ;
int f ( )
{ Complex z ( 2 , 3 ) , k ( 3 , 4 ) ;
z = z + 27 ;
…...
}
Ø 在第一个参数需要隐式转换的情形下,使用友元函数重载
运算符是正确的选择
Ø 友元函数没有 this 指针,所需操作数都必须在参数表显式
声明,很容易实现类型的隐式转换
ØC++中不能用友元函数重载的运算符有
= () [] -
【例】复数运算
#include
using namespace std;
class Complex
{ public:
Complex( double r =0, double i =0 ) { Real = r ; Image = i ; }
Complex(int a) { Real = a ; Image = 0 ; }
void print() const ;
friend Complex operator+ ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c ) ;
private:
double Real, Image ;
};
Complex operator + ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real + c2.Real ; double i = c1.Image+c2.Image ;
return Complex ( r, i ) ;
}
Complex operator - ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real - c2.Real ; double i = c1.Image - c2.Image ;
return Complex ( r, i ) ;
}
Complex operator- ( const Complex & c )
{ return Complex ( -c.Real, - c.Image ) ; }
void Complex :: print() const
{ cout << '(' << Real << " , " << Image << ')' << endl ; }
8.成员运算符函数与友元运算符函数的比较
(1) 成员运算符函数比友元运算符函数少带一个参数(后置的++、--需要增加一个形参)。
(2) 双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。
(三)几个典型运算符重载
1.数学类中常用的几个运算符重载的特点和应用:
设 A Aobject ;
运算符 ++和 - - 有两种方式:
前置方式: ++Aobject --Aobject
成员函数 重载 A :: A operator++ () ;
解释为: Aobject . operator ++( ) ;
友元函数 重载 friend A operator++ (A &) ;
解释为: operator ++( Aobject ) ;
后置方式: Aobject ++ Aobject –
成员函数 重载 A :: A operator++ (int) ;
解释为: Aobject . operator ++( 0 ) ;——伪参数
友元函数 重载: friend A operator++ (A &, int) ;
解释为: operator++(Aobject, 0)
【例】成员函数重载++
#include
using namespace std;
class Increase
{ public :
Increase ( ) { value=0; }
void display( ) const { cout<
#include
using namespace std;
class Increase
{ public :
Increase ( ) { value=0; }
void display( ) const { cout<
2. 重载赋值运算符
赋值运算符重载用于对象数据的复制
operator= 必须重载为成员函数
重载函数原型为:
类名 & 类名 :: operator= ( 类名 ) ;
【例】定义Name类的重载赋值函数
#include
#include
using namespace std;
class Name
{ public :
Name ( char *pN ) ;
Name( const Name & ) ; //复制构造函数
Name& operator=( const Name& ) ; // 重载赋值运算符
~ Name() ;
protected :
char *pName ;
int size ;
} ;
Name::Name ( char *pN )
{ cout <<" Constructing " << pN << endl ;
pName = new char[ strlen( pN ) + 1 ] ;
if( pName != 0 ) strcpy( pName,pN ) ;
size = strlen( pN ) ;
}
Name::Name( const Name & Obj ) //复制构造函数
{ cout << " Copying " << Obj.pName << " into its own block\n";
pName = new char[strlen( Obj.pName ) + 1 ] ;
if ( pName != 0 ) strcpy( pName, Obj.pName ) ;
size = Obj.size;
}
Name & Name::operator= ( const Name & Obj ) // 重载赋值运算符
{ delete []pName ;
pName = new char[ strlen( Obj.pName ) + 1 ] ;
if ( pName != 0 ) strcpy( pName , Obj.pName ) ;
size = Obj.size ;
return *this ;
}
Name::~ Name()
{ cout << " Destructing " << pName << endl ;
delete []pName ;
size = 0;
}
int main()
{ Name Obj1( "ZhangSan" ) ;
Name Obj2 = Obj1 ; // 调用复制构造函数
Name Obj3( "NoName" ) ;
Obj3 = Obj2 = Obj1 ; // 调用重载赋值运算符函数 (——修改对象时调用重载赋值运算符函数)
}
3.重载运算符[]和()
运算符 [] 和 () 是二元运算符
[] 和 () 只能用成员函数重载,不能用友元函数重载.
(1) 重载下标运算符 []
[] 运算符用于访问数据对象的元素
重载格式 类型 类 ::operator[] ( 类型 ) ;
例
设 x 是类 X 的一个对象,则表达式
x [ y ]
可被解释为
x . operator [ ] ( y)
(2) 重载函数调用符 ()
( )运算符用于函数调用
重载格式 类型 类 ::operator() ( 参数表 ) ;
例
设 x 是类 X 的一个对象,则表达式
x ( arg1, arg2, … )
可被解释为
x . operator () (arg1, arg2, … )
4.重载流插入和流提取运算符
Øistream 和 ostream 是 C++ 的预定义流类
Øcin 是 istream 的对象,cout 是 ostream 的对象
Ø运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
Ø 运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
Ø 用友元函数重载 << 和 >> ,输出和输入用户自定义的数据类型
重载输出运算符“<<”(只能被重载成友元函数,不能重载成成员函数)
定义输出运算符“<<”重载函数的一般格式如下:
ostream&operator<<(ostream& out,class_name&obj)
{
out<
out<
.. .
out<
return out;
}
重载输入运算符“>>” (只能被重载成友元函数)
定义输入运算符函数“>>”重载函数的一般格式如下:
istream&operator>>(istream& in,class_name&obj)
{
in>>obj.item1;
in>>obj.item2;
. . .
in>>obj.itemn;
return in;
}
【例】设计一个集合类,用无符号整数数组表示集合,重载运算符实现集合的基本运算,以及集合元素的输入、输出。
#include
using namespace std;
//集合类
class setType
{ public:
setType( unsigned e=128 ); //构造函数
setType( const setType & B ); //复制构造函数
~setType(); //析构函数
setType operator+= ( unsigned x ); //重载+=,把元素x并入集合
setType operator= ( setType B ); //重载=,集合变量赋值
setType operator() (unsigned x=0); //重载(),集合置元素x,默认置空
setType operator+ ( setType B ); //重载+,求并集
setType operator* ( setType B ); //重载*,求交集
setType operator- ( setType B ); //重载-,求差集
bool operator<= ( setType B ); //重载<=,判集合蕴含
//重载!运算符,判空集。集合空返回false,否则返回true
bool operator ! ();
//重载<,判元素属于集合
friend bool operator< ( unsigned x, setType A );
//重载>>,输入集合元素
friend istream & operator>> ( istream &input, setType &A );
//重载<<,输出集合的全部元素
friend ostream & operator<< ( ostream &output, setType &A );
private:
unsigned *set; //建立动态数组指针
unsigned n; //数组长度
unsigned e; //全集元素个数
};
#include"setTypeHead.h"
int main()
{ setType setA, setB, setC; unsigned x;
cout << "Input the elements of setA, 1-128, until input 0 :\n";
cin >> setA; //输入setA的元素
cout << "Input the elements of setB, 1-128, until input 0 :\n";
cin >> setB; //输入setB的元素
cout << "setA = " << setA << endl; //输出setA的元素
cout << "setB = "<> x;
setA += x; //把元素x并入setA
cout << "Put " << x << " in setA = " << setA << endl;
setC = setA + setB; //求并集
cout << "setC = setA+setB = " << setC << endl;
setC = setA * setB; //求交集
cout << "setC = setA*setB = " << setC << endl;
setC = setA - setB; //求差集
cout << “setC = setA-setB = ” << setC << endl;
//判断setA是否蕴含于setB
if( setA <= setB ) cout << "setA <= setB\n";
else cout << "not setA <= setB\n";
cout << "Input x: "; cin >> x;
//判断元素x是否属于setA
if( x < setA ) cout << x << " in " << setA << "\n";
else cout << x << " not in " << setA << "\n";
setC = setA + setB + setC; //多个集合变量运算
cout << "setC = setA+setB+setC = " << setC << endl;
setC(); //置setC为空集
cout<<"setC = " << setC << endl;
}
二、 C++ 标准模板库STL
(一)STL概述
l STL是C++标准程序库的核心,深刻影响了标准程序库的整体结构
l STL由一些可适应不同需求的集合类(collection class),以及在这些数据集合上操作的算法(algorithm)构成
l STL内的所有组件都由模板(template)构成,其元素可以是任意类型
l STL是所有C++编译器和所有操作系统平台都支持的一种库
1.STL组件
Ø 容器(Container) - 管理某类对象的集合
Ø 迭代器(Iterator) - 在对象集合上进行遍历
Ø 算法(Algorithm) - 处理集合内的元素
Ø 容器适配器(container adaptor)
Ø 函数对象(functor)
2.STL容器类别
Ø 序列式容器-排列次序取决于插入时机和位置
Ø 关联式容器-排列顺序取决于特定准则
3.STL容器的共同能力
Ø 所有容器中存放的都是值而非引用。如果希望存放的不是副本,容器元素只能是指针。
Ø 所有元素都形成一个次序(order),可以按相同的次序一次或多次遍历每个元素
4.STL容器元素的条件
Ø 必须可以通过析构函数完称销毁动作
Ø 序列式容器元素的默认构造函数必须可用
Ø 某些动作必须定义operator ==,例如搜寻操作
Ø 关联式容器必须定义出排序准则,默认情况是重载operator <
对于基本数据类型(int,long,char,double,…)而言,以上条件总是满足
5.STL容器的共同操作
初始化(initialization)
产生一个空容器
std::list
以另一个容器元素为初值完成初始化
std::list
…
std::vector
以数组元素为初值完成初始化
int array[]={2,4,6,1345};
…
std::set
6.STL容器的共同操作
Ø 与大小相关的操作(size operator)
Ø 比较(comparison)
7.STL容器的共同操作
Ø 赋值(assignment)和交换(swap)
swap用于提高赋值操作效率
Ø 与迭代器(iterator)相关的操作
begin()-返回一个迭代器,指向第一个元素
end()-返回一个迭代器,指向最后一个元素之后
rbegin()-返回一个逆向迭代器,指向逆向遍历的第一个元素
rend()-返回一个逆向迭代器,指向逆向遍历的最后一个元素之后
8.容器的共同操作
Ø 元素操作
9.迭代器(iterator)(示例:iterator)
Ø 可遍历STL容器内全部或部分元素的对象
Ø 指出容器中的一个特定位置
Ø 迭代器的基本操作
Ø 所有容器都提供获得迭代器的函数
操作 |
效果 |
begin() |
返回一个迭代器,指向第一个元素 |
end() |
返回一个迭代器,指向最后一个元素 |
² 半开区间[beg, end)的好处: 1.为遍历元素时循环的结束时机提供了简单的判断依据(只要未到达end(),循环就可以继续) 2.不必对空区间采取特殊处理(空区间的begin()就等于end()) Ø 所有容器都提供两种迭代器 container::iterator以“读/写”模式遍历元素 container::const_iterator以“只读”模式遍历元素 Ø 迭代器分类 ü 双向迭代器 可以双向行进,以递增运算前进或以递减运算后退、可以用==和!=比较。 list、set和map提供双向迭代器 ü 随机存取迭代器 除了具备双向迭代器的所有属性,还具备随机访问能力。 可以对迭代器增加或减少一个偏移量、处理迭代器之间的距离或者使用<和>之类的关系运算符比较两个迭代器。 vector、deque和string提供随机存取迭代器 (二)STL容器 1.vector Ø vector模拟动态数组 Ø vector的元素可以是任意类型T,但必须具备赋值和拷贝能力(具有public拷贝构造函数和重载的赋值操作符) Ø 必须包含的头文件#include Ø vector支持随机存取
Ø vector的大小(size)和容量(capacity)
Ø 构造、拷贝和析构 Ø 非变动操作 Ø 赋值操作 |
2.map/multimap
Ø使用平衡二叉树管理元素
Ø元素包含两部分(key,value),key和value可以是任意类型
Ø 必须包含的头文件#include
Ø 根据元素的key自动对元素排序,因此根据元素的key进行定位很快,但根据元素的value定位很慢
Ø不能直接改变元素的key,可以通过operator []直接存取元素值
Ø map中不允许key相同的元素,multimap允许key相同的元素
Ø 构造、拷贝和析构
其中map可以是下列形式
map
map
Ø 赋值
Ø 迭代器相关函数
【例】
比赛又开始了。看见到处都是气球升起,多激动啊!告诉你一个秘密:裁判正在非常开心地猜测哪一题最受欢迎。当比赛结束时,他们统计每种颜色气球的数量就知道结果了。
输入
输入有多组测试例。
对每个测试例,第一个数字是N(0<N<1000),表示气球的数量。接下来N行,每行是一个气球的颜色,由小写字母构成的字符串表示,长度不超过15个。当N=0时,表示输入结束。
输出
对每个测试例输出一行,
是表示最受欢迎的题目的气球颜色。
析:
本题要求输出颜色数最多的气球颜色,题目保证只有一种颜色数最多(答案是唯一的)。
本题比较适合用标准模板库(STL)的容器map(),key为颜色,当同一种颜色重复出现时,让value计数,然后将value值最大的那个key输出即可。
3.set/multiset
Ø 使用平衡二叉树管理元素
Ø集合(Set)是一种包含已排序对象的关联容器。
Ø 必须包含的头文件#include
Ø map容器是键-值对的集合,好比以人名为键的地址和电话号码。相反地,set容器只是单纯的键的集合。当我们想知道某位用户是否存在时,使用set容器是最合适的。
Øset中不允许key相同的元素,multiset允许key相同的元素
4.pair 模板:
pair模板可以用于生成 key-value对。
#include
#include
using namespace std;
main() {
typedef set > double_set;
const int SIZE = 5;
double a[SIZE] = {2.1,4.2,9.5,2.1,3.7 };
double_set doubleSet(a,a+SIZE);
ostream_iterator output(cout," ");
cout << "1) ";
copy(doubleSet.begin(),doubleSet.end(),output);
cout << endl;
pair p;
p = doubleSet.insert(9.5);
if( p.second )
cout << "2) " << * (p.first) << " inserted" << endl;
else
cout << "2) " << * (p.first) << " not inserted" << endl;
}
//insert函数返回值是一个pair对象, 其first是被插入元素的迭代器,second代表是否成功插入了
(三)Algorithm(算法)
Ø 所有算法的前两个参数都是一对iterators:[first,last),用来指出容器内一个范围内的元素。
每个算法的声明中,都表现出它所需要的最低层次的iterator类型。
Ø 大部分算法都可以用functioin object 来更改准则。function object又称functor。
l 泛型演算法大概有70种,不进行一一列举了,需要时可自行查询。简单记录几种常用算法。
² 算法
1)count(计数):
template
size_tcount(InIt first, InIt last, const T& val);
计算[first,last) 中等于val的元素个数
2)count_if(在特定条件下计数):
template
size_tcount_if(InIt first, InIt last, Pred pr);
计算[first,last) 中符合pr(e) == true 的元素 e的个数
3)min_element(最小值所在位置):
template
FwdItmin_element(FwdIt first, FwdIt last);
返回[first,last)中最小元素的迭代器,以“<”作比较器
4)max_element(最大值所在位置):
template
FwdItmax_element(FwdIt first, FwdIt last);
返回[first,last) 中最大(不小)元素的迭代器,以“<”作比较器
5)for_each(对范围内的每一个元素施行某动作):
template
Funfor_each(InIt first, InIt last, Fun f);
对[first,last)中的每个元素 e ,执行 f(e) , 要求 f(e)不能改变e
² 排序和查找算法
1) find(搜寻)
template
InItfind(InIt first, InIt last, const T& val);
返回区间 [first,last) 中的迭代器 i ,使得 * i == val
2) find_if(在特定条件下搜寻):
template
InItfind_if(InIt first, InIt last, Pred pr);
返回区间 [first,last) 中的迭代器 i, 使得 pr(*i) == true
3) binary_search (二元搜寻,折半查找,要求容器已经有序且支持随机访问迭代器,返回是否找到)
template
bool binary_search(FwdIt first, FwdIt last,const T& val);
上面这个版本,比较两个元素x,y 大小时, 看 x < y
template
boolbinary_search(FwdIt first, FwdIt last, const T& val, Pred pr);
上面这个版本,比较两个元素x,y 大小时, 看 pr(x,y)
4) lower_bound,uper_bound, equal_range
lower_bound(下限):
template
FwdItlower_bound(FwdIt first, FwdIt last, const T& val);
要求[first,last)是有序的,
查找大于等于val的最小的位置
upper_bound(上限):
template
FwdItupper_bound(FwdIt first, FwdIt last, const T& val);
要求[first,last)是有序的,
查找大于val的最小位置
equal_range(判断相等与否,传回一个上下限区间范围):
template
pair
要求[first,last)是有序的,
返回值是一个pair, 假设为 p, 则
[first,p.first)中的元素都比 val 小
[p.second,last)中的所有元素都比 val 大
5)sort 快速排序
template
voidsort(RanIt first, RanIt last);
按升序排序。判断x是否应比y靠前,就看 x < y 是否为true
template
voidsort(RanIt first, RanIt last, Pred pr);
按升序排序。判断x是否应比y靠前,就看 pr(x,y) 是否为true
6)改变序列的算法
unique
template
FwdIt unique(FwdIt first, FwdIt last);
用 == 比较是否相等
template
FwdIt unique(FwdIt first, FwdIt last, Pred pr);
用 pr 比较是否等
去除[first,last) 这个升序序列中的重复元素
返回值是迭代器,指向元素删除后的区间的最后一个元素的后面
reverse
template
voidreverse(BidIt first, BidIt last);
颠倒区间[first,last)顺序
一、 心得体会
关于运算符重载,那些运算符本身功能是有限的,比如不能进行复数的加减,但是我们重载以后就可以让它实数与实数相加,虚数与虚数相加,最后结果也是复数;还有字符串string里也有很多重载的地方,像两个字符串也可以相加。重载的主要目的是,简洁易懂,原理上其实也是通过函数实现的,像a+ b用add(a,b) 也行,但是看起来就没那么直观了。虽然道理懂一些,但是具体使用还是有些障碍,还需多加练习。
关于STL, STL是标准模板库,起个简化作用(也就是程序员可以偷懒,直接调用用别人的代码),几乎所有的代码都采用了模板类和模版函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会,使用STL将会简化你所敲出的代码。目前还是没有太懂STL的调用及使用,不知道如何将其嵌入语句,多次练习会有效果,但还是希望老师能深入解释及多多提点,当然,主要还是自己练习。