重载运算符和STL
第一部分:知识总结
一.运算符重载
1.C++语言中大部分预定义的运算符都可以被重载。以下运算符不可以被重载:
.* :: ?: sizeof
运算符重载后,原有的基本语义不变,包括:
不改变运算符的优先级
不改变运算符的结合性
不改变运算符所需要的操作数
不能创造新的运算符
2.语法形式
运算符函数是一种特殊的成员函数或友元函数。成员函数的语句形式为:
类型 类名::operator op(参数表)
{
//相对于该类定义的操作
}
其中,“类型”是函数的返回类型。“类名”是要重载该运算符的类。“op”表示要重载的运算符。函数名是“operator op”由关键字operator和被重载的运算符op组成。“参数表”列出该运算符所需要的操作数
(1)一元运算符:
Object op 或 op Object
重载为成员函数,解释为:
object .operatorop()
操作数由对象object 通过this指针隐含传递
重载为友元函数,解释为:
operatorop(object)
操作数由参数表的参数object提供
(2)二元运算符
ObjectL op ObjectR
重载为成员函数,解释为:
ObjectL .operator op ( ObjectR )
左操作数由ObjectL通过this指针传递,右操作数由参数ObjectR传递
重载为友元函数,解释为:
operator op (ObjectL, ObjectR )
左右操作数都由参数传递
(3)对双目运算符而言,成员运算符函数的形参表中仅有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含地传递给函数的。
例如以下的例子,重载+运算符
#include<iostream>
using namespacestd;
class Complex
{public:
Complex(){real=0,imag=0;}
Complex(doubler,double i)
{real=r;imag=I;}
Complexoperator+(Complex&c2);
void display();
private:
double real;
double imag;
};
Complex Complex:: operator + (Complex &c2) {
return Complex(real+c2.real,imag+c2.imag);}
void Complex::display( ){
cout<<"("<<real<<","<<imag<<"i)"<<endl;}
int main( ){
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
cout<<"c1+c2="; c3.display( );
return 0;
}
在这个程序中重载了+运算符,利用了传引用的方式,传递了c2的内容
3.用友元函数重载
=有时,运算符的左右操作类型不同,用成员函数重载运算符会碰到麻烦。例如,定义有以下复数类:
ClassComplex
{public:
Complex(inta)
{Real=a;Imag=0;}
Complex(inta,int b)
{Real=a;Imag=b;}
Complexoperator+(Complex);
private:
int Real;
int Imag;
};
int f()
{Complexz(1,2),k(3,4);
z=z+25;
z=25+z;
//……
}
在函数f中,表达式:
z+25
被解释为:z.operator+(25);
它引起调用重载运算符函数是左操作对象z,右操作对象数25通过Complex类型参数调用类的构造函数Complex(int a),建立临时的对象执行函数;
但是表达式:
25+z;
被解释为系统预定义版本时,z无法转换成基本数据类型,而是被解释为重载版本:25.operator+(z)
其中,整型值25不是Complex类对象,无法驱动函数进行运算。在此,用成员函数重载的“+”运算符不具备运算交换性。
如果用友元函数重载运算符,左,右操作数都由参数传递,则C++语言可以通过构造函数实现数据类型隐式转换。
若重载形式为:
friendComplex operator+(Complex ,Complex);
则表达式:25+z
被解释为operator+(25,z)
友元函数不能重载的运算符有:
= () [] ->
成员运算符函数与友元运算符函数的比较
(1)成员运算符函数比友元运算符函数少带一个参数(后置的++,--需要增加一个形参)。
(2)双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。
4.几个典型的运算符重载
(1)运算符++和—有两种方式
前置方式:++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<iostream>
using namespacestd;
class Increase
{ public :
Increase ( ) { value=0; }
void display( ) const {cout<<value<<'\n'; } ;
Increase operator ++ ( ) ; // 前置
Increase operator ++ ( int ) ; // 后置
private: unsigned value ;
};
Increase Increase :: operator ++ ( )
{ value ++ ; return *this ; } //前置的方式
Increase Increase :: operator ++ ( int )
{ Increase temp; temp.value = value ++; return temp; }//后置的方式
int main( )
{ Increase a , b , n ; int i ;
for ( i = 0 ; i < 10 ; i ++ ) a = n ++ ;
cout <<"n= " ; n.display( ) ; cout <<"a= " ; a.display( ) ;
for ( i = 0 ; i < 10 ; i ++ ) b = ++ n ;
cout << "n= " ; n.display( ) ; cout << "b= " ; b.display( ) ;
}
(2)重载=运算符
赋值运算符重载用于对象数据的复制
operator=必须重载为成员函数
重载函数原型为:
类名&类名::operator=(类名)
例如定义Name类的重载
#include<iostream>
#include<string>
using namespacestd;
class Nmae
{public:
Name(char*pN);
Name(constName&);//复制构造函数
Name&operator=(constName&);
~Name();
protected:
char *pName;
int size;
};
int main()
{NameObj1(“zhangsan”);
Name Obj2=Obj1;//调用复制构造函数
Name Obj3(“NoName”);
Obj3=Obj2=Obj1;//调用了重载赋值运算符
}
Name::Name(char*pN)
{cout<<”Constructing”<<pN<<endl;
pName=newchar[strlen(pN)+1];
if(pName!=0)strcpy(pName,pN);//strcpy的功能是把字符串pN复制到一分配好字符串空间pName中
size=strlen(pN);//表示长度
}
Name::Name(constName&Obj)
{cout<<”Copying”<<Obj.pName<<”intoits own block\n”;
pName= newchar[strlen(Obj.pName)+1];
if(pName!=0)strcpy(pName,Obj.pName);
size=Obj.size;
}
Name&Name::operator=(constName&Obj)//重载了=运算符
{delete []pName;
pName=newchar[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;
}
(3)重载运算符[]和()
运算符 [] 和 () 是二元运算符
[] 和 () 只能用成员函数重载,不能用友元函数重载
1.[]运算符用于访问数据对象的元素
重载格式 类型 类::operator [](类型)
例如:
设y是类X的一个对象,则表达式
X【y】
可被解释为
x.operator[](y);
例如以下程序:
#include<iostream>
using namespacestd;
class vector
{public:
vector(intn){v=new int [n];size=n;}
~vector(){delete[]v;size=0;}
int&operator[](int i){return v[i];}
private: int *v;
int size;
};
int main()
{vector a(5);
a[2]=12;
cout<<a[2]<<endl;
}
上面这个程序是什么意思呢,vector(int n){v=new int [n];size=n;}
这一行代码是建立一个动态数组的意思,动态数组会在STL部分总结,那么
vector a(5);
这一句的意思就是申请了一个大小为5的整形数组,然后定义了a[2]为12,其他的就是随机的大小
2.重载函数调用符()
()运算符用于函数调用
重载格式 类型 类::operator()(参数表)
例如
设x是类X的一个对象,则表达式
x(arg1,arg2,…)
可被解释为
x.operator()(arg1,arg2,…)
#include<iostream>
using namespacestd;
class F
{public:
doubleoperator(double x,double y);
};
doubleF::operator()(double x,double y)
{return x*x+y*y;}
int main()
{F f;
cout<<f(5.2,2.5)<<endl;
}
(4)重载流插入和流提取运算符
istream和ostream是C++的预定义流类
cin是istream的对象,cout是ostream 的对象
运算符<<由ostream重载为插入操作,用于输出基本类型数据
运算符>>由istream重载为提取操作,用于输入基本类型数据
用友元函数重载>>和<<,输入和输出用户自定义的数据类型
重载输出运算符“<<”(只能被重载成友元函数,不能重载成成员函数)
定义输出运算符“<<”重载函数的一般格式如下:
ostream&operator<<(ostream&out,class_name&obj)
{out<<obj.item1;
out<<obj.item2;
…
out<<obj.itemn;
return out;
}
重载输入运算符“>>”(只能被重载为友元函数)
定义格式如下:
istream&operator>>(istream&in,classname&obj)
{in>>obj.item1;
in>>obj.item2;
….
in>>onj.itemn;
return in;
}
重载流插入和输出的使用
#include<bits/stdc++.h>
using namespacestd;
class vector
{public:
vector(int size=1);
~vector();
int&operator[](int i);
frinedostream&operator<<(ostream&output,vector&);
friendistream&operator>>(istream&input,vector&);
private:
int *v;int len;
};
int main()
{int k;
cout<<”Inputthe length of vector A:\n”;
cin>>k;
vector A(k);
cout<<”Inputthe elements of vector A:\n”;
cin>>A;
cout<<”Outputthe elements of vector A:\n”;
cout<<A;
}
vector::vector(intsize)
{if(size<=0||size>100)
{cout<<”Thesize of”<<size<<”is null!\n”;
exit(0);
}
v=new int[size];
len=size;
}
vector::~vector(){delete[]v;len=0;}
int&vector::operator[](int i)
{if(i>=0&&i<len)return v[i];
cout<<”Thesubscript”<<i<<”is outside!\n”;
exit(0);
}
ostream&operator<<(ostream&output,vector&ary)
{for(inti=0;i<ary.len;i++)
output<<ary[i]<<”“;
output<<endl;
return output;
}
istream&operator>>(istream&input,vector&ary)
{for(inti=0;i<ary.len;i++)
input>>ary[i];
return input;
}
第二部分:心得总结
关于运算符的重载这一个部分,主要是在使用的时可以更方便一些
大体上分为两种,一种是重载为成员函数,比如=就必须重载成成员函数,另一种是重载成友元函数,比如重载流插入和流输出的时候,就必须重载成友元函数。
在类中定义的时候可以定义成这种形式:
类型 operator op(参数表);
然后在类外定义的时候
是这样的形式
类型 类名::operator op(参数表)
{//所需要的操作
}
STL部分
第一部分:知识总结
1.STL是C++标准程序库的核心,深刻影响了标准程序库的整体结构
2.STL由一些可适应不同需求的集合类,以及在这些数据集合上操作的算法
STL内的所有组件都由模板构成,其元素可以是任意类型
3.STL的组件
容器(Container)管理某类对象的集合
迭代器(iterator)在对象集合上进行遍历
4.容器的类别
序列式容器——排列次序取决于插入时机和位置
关联式容器——排列顺序取决于特定准则
容器的特点:
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。很简单,容器就是保存其它对象的对象,当然这是一个朴素的理解,这种“对象”还包含了一系列处理“其它对象”的方法,因为这些方法在程序的设计上会经常被用到,所以容器也体现了一个好处,就是“容器类是一种对特定代码重用问题的良好的解决方案”。
容器还有另一个特点是容器可以自行扩展。在解决问题时我们常常不知道我们需要存储多少个对象,也就是说我们不知道应该创建多大的内存空间来保存我们的对象。显然,数组在这一方面也力不从心。容器的优势就在这里,它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行您的命令。
序列式容器:vector,deque,list
关联式容器:
和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。
关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)
包括:set,multiset,map,multimap;
STL容器的共同能力:
所有容器中存放的都是值而非引用,如果希望存放的不是副本,容器元素只能是指针。
所有元素都形成一个次序,可以按相同次序一次或者多次遍历每个元素
STL容器元素的条件
必须能够通过拷贝构造函数进行复制
必须可以通过赋值运算符完成复制操作
必须可以通过析构函数完成销毁动作
序列式容器元素的默认构造函数必须可用
某些动作必须定义operator=例如搜寻操作
关联式容器必须定义出排序准则,默认情况是重载operator<
STL容器的共同操作:
初始化:产生一个空容器
std::list<int> l;
以另一个容器元素为初值完成初始化
std::list<int>l;
……
std::vector<float>c(l.begin(),l.end());
以数组元素为初值完成初始化
int array[]={2,4,6,1345}
……
std::set<int>c(arry,arry+sizeof(array)/sizeof(array[0]);
与大小相关的操作
size()—返回当前容器的元素数量
empty()—判断容器是否为空
max_size()—返回容器能够容纳的最大元素数量
比较
==,!=,<,<=,>=
比较操作两端的容器必须属于同一类型
如果两个容器内的所有元素按序相等,那么这两个容器相等
赋值和交换
swap用于提高赋值操作效率
与迭代器相关操作
begin()—返回一个迭代器,指向第一个元素
end()—返回一个迭代器,指向最后一个元素之后
rbegin()-返回一个逆向迭代器,指向逆向遍历的第一个元素
rend()-返回一个逆向迭代器,指向逆向遍历后的最后一个元素之后
元素操作
insert(pos,e)-将元素e的拷贝安插于迭代器pos所指的位置
erase(beg,end)-移除[beg,end]区间内的所有元素
clear()—移除所有元素
5.STL的迭代器
(1):迭代器类似于指针类型,它也提供了对对象的间接访问。
指针是c语言中就有的东西,迭代器是c++中才有的,指针用起来灵活高效,迭代器功能更丰富些。
迭代器提供一个对容器对象或者string对象的访问的方法,并且定义了容器范围。
(2):迭代器的使用
*iter 返回迭代器iter所指元素的引用
iter->men 解引用iter并获得该元素的名为men的成员,相当于(*iter).men
++iter 令iter指示容器的下一个元素
--iter 令iter指示容器的上一个元素
iter1==iter2 如果两个迭代器指示的是同一个元素或者它指向同一个容器的尾后迭代器,则相等.
(3)迭代器的类型
那些拥有迭代器的标准库类型都是使用:iterator和const_iterator来表示迭代器的类型:
vector <int> ::iteratorit; //it能读写vector<int>的元素
vector <int>::const_iteratorit; //it只能读vector<int>的元素,不可以修改vector<int>中的元素
string::iterator s; //s可以读写string对象中的元素
string::const_iterators; //s只可以读string对象中的元素,不可以修改string对象中的元素
vector动态增长的限制:
(1):不能再范围for循环中向vector对象添加元素。
(2):任何一种可能改变vector容量的操作,比如push_back,都会使该vector对象的迭代器失效。
迭代器的运算
iter
+n
//迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置向前移动了,指示可能是容器的一个元素或者是尾部的下一个位置
iter
-n//相反,迭代器指示的位置向后移动了,指示可能是容器的一个元素或者是尾部的下一个位置
iter1
+=n
//等价于iter1+n
iter1
-=n
//等价于iter2-n
iter1
-iter2//两个迭代器的距离,
>,
<,
>=,
<=//位置离begin近的较小
(5).动态数组Vector
vector模拟动态数组,vector的元素可以是任意类型T,但必须具备赋值和拷贝能力(具有public拷贝构造函数和重载的赋值操作符)
必须有头文件#include<vector>,支持随机存取
size返回vector实际元素个数
capacity返回能容纳的元素的最大数量
vector的操作
vector<T>c //产生一个空的vector
vector<T>c1(c2) //产生同类型的vector,并赋值C2的所有元素
vector<T>c(n) //利用类型T的默认构造函数和拷贝构造函数生成一个大小为n的vector
vector<T>c(beg,end) //产生一个vector,已区间[beg,end]为元素初值
~vector<T>() //销毁所有元素并释放内存
非变动操作
c.size() //返回元素的个数
c.empty() //判断容器是否为空
c.max_size() //返回元素最大可能数量
c.capacity //返回重新分配空间前可容纳的最大元素数量
c.reserve(n) //扩大容量为n
vector迭代器相关函数
与常用的相同
vector安插元素
c.insert(pos,e) 在pos位置插入元素e的副本
c.insert(pos,n,e) 在pos位置插入n个元素e的副本
c.insert(pos,beg,end) 在pos位置插入区间[begin,end]内所有元素的副本
c.push_back(e); 在尾部添加一个元素为e的副本
移除操作
c.pop_back() |
移除最后一个元素但不返回最后一个元素 |
c.erase(pos) |
删除pos位置的元素,返回下一个元素的位置 |
c.erase(beg,end) |
删除区间[beg,end]内所有元素,返回下一个元素的位置 |
c.clear() |
移除所有元素,清空容器 |
c.resize(num) |
将元素数量改为num(增加的元素用defalut构造函数产生,多余的元素被删除) |
c.resize(num,e) |
将元素数量改为num(增加的元素是e的副本) |
vector的实例:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
vector<int> a; //定义了一个整形动态数组a
for (int i = 0; i < 5; ++i){
a.push_back(5 - i);//在尾部增添数据(5-i)
}
cout << "Size: " << a.size() << endl;//返回a的现有元素数量
a.pop_back(); //移除最后一个元素,但是不返回
a[0] = 1;
cout << "Size: " << a.size() << endl;
for (int i = 0; i < (int)a.size(); ++i){
cout << a[i] << ", " << endl;
}
cout << endl;
sort(a.begin(), a.end()); //对于a中进行sort排序
cout << "Size: " << a.size() << endl;
for (int i = 0; i < (int)a.size(); ++i){
cout << a[i] << ", " << endl;
}
cout << endl;
a.clear(); //把a的元素全部删除
cout << "Size: " << a.size() << endl;
return 0;
}
(6)map/muitimap的使用
使用平衡二叉树管理元素,可以对元素进行一个快速的定位
元素包括两部分(key,value),key和value可以是任意类型
map中不允许key相同的元素,multimap允许key相同
map的操作和vector差不多,但是map中多了一个排序,
例如下操作
map c(beg,end,op) 以op为排序准则,以区间[beg,end]内的元素产生一个map
map c(op) 以op为排序准则产生一个空的map
这里的排序准则可以按照如下的方式:
struct cmp1
{string str;
cmp1(string s){str=s;}
bool operator()(Record &a)
{ return(a.getStr().find(str)!=string::npos);
}
};
应用示例如下:
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
struct T1{
int v;
bool operator<(const T1 &a)const{
return (v < a.v);//重载小于号运算符,返回其排序方式
}
};
struct T2{
int v;
};
struct cmp{
const bool operator()(const T2 &a, const T2 &b){
return (a.v < b.v);
}
};
int main(){
map<T1, int>mt1; //example for user-defined class//建立mit1
map<T2, int, cmp>mt2; //example for user-defined class(functor)
//以cmp的排序方式建立mt2
map<string, int> m2;
map<string, int>::iterator m2i, p1, p2;//这个地方是建立迭代器
//map<string, int, greater<string> >m2;
//map<string, int, greater<string> >::iterator m2i, p1, p2;
m2["abd"] = 2;
m2["abc"] = 1;
m2["cba"] = 2;
m2.insert(make_pair("aaa", 9));//插入操作std::pair的主要作用是将两个数据组合成一个数据,两个数据可以是同一类型或者不同类型
m2["abf"] = 4;
m2["abe"] = 2;
cout << m2["abc"] << endl;
m2i = m2.find("cba");
if(m2i != m2.end()){
cout << m2i->first << ": " <<m2i->second << endl;//利用迭代器分别指向第一个元素和第二个元素
}else{
cout << "find nothing" << endl;
}
cout << "Iterate" << endl;
for(m2i = m2.begin(); m2i != m2.end(); m2i++){
cout << m2i->first << ": " <<m2i->second << endl;
}
return 0;
}
关于multimap的操作实例
#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
int main(){
multimap<string, int> mm1;
multimap<string, int>::iterator mm1i, p1, p2;
mm1.insert(make_pair("b", 3));
mm1.insert(make_pair("a", 0));
mm1.insert(make_pair("b", 5));
mm1.insert(make_pair("c", 4));
mm1.insert(make_pair("b", 2));
cout << "Size: " << mm1.size() << endl;
for(mm1i = mm1.begin(); mm1i != mm1.end(); mm1i++){
cout << mm1i->first << ": " <<mm1i->second << endl;
}
cout << "COUNT: " << mm1.count("b")<< endl;
cout << "Bound: " << endl;
p1 = mm1.lower_bound("b");//这个地方是特殊搜寻操作,lower_bound(key)返回的是键值大于等于key的第一个元素
p2 = mm1.upper_bound("b");//返回键值大于key的第一个元素
for(mm1i = p1; mm1i != p2; mm1i++){
cout << mm1i->first << ": " <<mm1i->second << endl;
}
return 0;
}
(6)find函数用法
个人觉得这个函数比较好用
例如在vector中的利用
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main()
{
vector<string> m;
m.push_back("hello");
m.push_back("hello2");
m.push_back("hello3");
if (find(m.begin(), m.end(), "hello") == m.end())
cout << "no" << endl;
else
cout << "yes"<< endl;
}
这段程序的意思就是在m中找寻hello
(1)find
template<class InIt,class T>
InIt find(InIt first,InIt last,const T&val)
返回区间[first,last)中的迭代器i,使得*i==val
(2)find_if
template<class InIt,class Perd>
InIt find_if(InIt first,InIt last,Perd pr);
返回区间[first,last)中的迭代器i,使得
pr(*i)==true
(3) binary_search折半查找,要求容器已经有序且支持随机访问迭代器,返回是否找到
template<class Fwdlt,class T>
bool binary_search(FwdIt first,FwdIt last,const T&val);
上面这个版本,比较两个元素x,y大小时,看x<y
心得总结:
关于stl这一部分,我觉得是增加了写程序时的方便性,利用vector可以建立数组,这时就不须考虑数组容量的大小,利用map时查找更为方便,如果vector中的内容发生了改变,map的对象也随之改变,增加或删除都要记得map中也进行相应的操作,还有一点很重要的是不要忘记迭代器,建立相应的指针,便于操作,还有一点就是算法的部分,find,sort的使用,注意建立相应的排序准则。