=====================================================================
算数操作符:+-*/%
关系操作符:><==
逻辑操作符:&& || !
位操作符:&、|、~、^
下标操作符:[]
取地址操作符:&
解引用操作符:*
插入操作符:<<
提取操作符:>>
取内存大小操作符:sizeof()
一、操作符与操作符函数
1. 将一个操作符标记#应用于一个或多个类类型的操作数时,编译器将调用这个操作符标记相关联的操作符函数operator#()。
实例:
= -> operator=()
+ -> operator+()
>> -> operator>> ()
[] -> operator[] ()
...
2. 操作符函数的重载定义通常包括全局函数和成员函数两种形式。
二、输入输出操作符
1. 一般而言,如果一个输入输出操作#已经按照全局函数的方式被重载定义了,那么表达式
L#R;
将被编译器解释为如下函数调用:
operator# (L, R);
可见,按照全局函数方式被重载定义的输入输出操作符函数,应
该有两个参数,分别代表左操作数和右操作数;
1>
ostream& operator<<(ostream& os,const X&x){
os<<x.xxx;
...
return os;
}
istream& operator>>(istream& is,X&x){
is>>x.xxx;
...
return is;
}
- friend ostream& <<operator(ostream&os,const Test&test){}
- friend istream& >>operator(istream&is,Test&test){}
2> 为了能够在全局操作符函数中直接访问类的私有成员,可以将该 操作符函数声明为类的友元;
3> 友元声明可以出现在一个类的任何部分(公,保,私),它允许被声明者直接访问该类的所有数据成员和成员函数,无论其访问属性如何;
三、双目操作符
1.L#R->L.operator#(R)
2.左右操作数在操作过程中不发生变化,返回的新对象,如+/-
X X::operator#(const X&x)const{
...
return X(...);
}
3.右操作数不变,但左操作数改变,返回的是左操作数本身,而非仅
是其值;
X& X::operator#(const X&x){
...
return *this;
}
四、单目操作符
1.#O->O.operator#();
操作数不变,返回新对象;
X X::operator#(void)const{
...
return X(...);
}
2.自增减操作符
1>操作数会发生变化;
2>前缀表达式的值是改变以后的值,后缀表达式的值是改变以前的值;
3>前缀表达式的值是操作数本身(引用)可以连用;后缀运算不能连用,
会出现编译错误;
4>区分前后缀;-----哑元
++c; //c.operator()
c++; //c.operator(0) //利用重载区分前后缀;
金典实例:
- #include <iostream>
- using namespace std;
- class Complex {
- public:
- Complex (double fReal = 0.0, double fImag = 0.0) : m_fReal (fReal), m_fImag (fImag) {}
- Complex operator+ (const Complex& c) const {
- double fReal = m_fReal + c.m_fReal;
- double fImag = m_fImag + c.m_fImag;
- Complex sum (fReal, fImag);
- return sum;
- }
- Complex operator+ (double fReal) const {
- return Complex (m_fReal + fReal, m_fImag);
- }
- Complex operator- (const Complex& c) const {
- return Complex (m_fReal - c.m_fReal, m_fImag - c.m_fImag);
- }
- Complex& operator+= (const Complex& c) {
- m_fReal += c.m_fReal;
- m_fImag += c.m_fImag;
- return *this;
- }
- Complex& operator-= (const Complex& c) {
- m_fReal -= c.m_fReal;
- m_fImag -= c.m_fImag;
- return *this;
- }
- Complex operator- (void) const {
- return Complex (-m_fReal, -m_fImag);
- }
- // 前缀
- Complex& operator++ (void) {
- m_fReal++;
- m_fImag++;
- return *this;
- }
- Complex& operator-- (void) {
- m_fReal--;
- m_fImag--;
- return *this;
- }
- // 后缀
- const Complex operator++ (int) {
- Complex c = *this;
- m_fReal++;
- m_fImag++;
- return c;
- }
- const Complex operator-- (int) {
- Complex c = *this;
- m_fReal--;
- m_fImag--;
- return c;
- }
- private:
- double m_fReal;
- double m_fImag;
- friend ostream& operator<< (ostream&, const Complex&);
- friend istream& operator>> (istream&, Complex&);
- };
- ostream& operator<< (ostream& os, const Complex& c) {
- return os << "(" << c.m_fReal << "+" << c.m_fImag << "i)";
- }
- istream& operator>> (istream& is, Complex& c) {
- return is >> c.m_fReal >> c.m_fImag;
- }
- int main (void) {
- Complex c1 (1, 2);
- cout << c1 << endl; // operator<< (operator<< (cout, c1), endl);
- // Complex c2;
- // cin >> c2; // operator>> (cin, c2);
- // cout << c2 << endl;
- Complex c3 (3, 4);
- Complex c4 = c1 + c3; // c1.operator+ (c3);
- cout << c1 << "+" << c3 << "=" << c4 << endl;
- cout << c4 << "-" << c3 << "=" << c4-c3<<endl;
- // int n1 = 10, n2 = 20;
- // cout << (n1 += n2) << endl;
- // cout << n1 << endl;
- // n1 += n2 += 30; // n1 += (n2 += 30);
- // cout << n1 << ", " << n2 << endl;
- // (n1 += n2) += 30;
- // cout << n1 << ", " << n2 << endl; // 60, 20
- Complex c2 (5, 6);
- // c1 += c2 += c3; // c1.operator+=(c2.operator+=(c3));
- (c1 += c2) += c3; // c1.operator+=(c2).operator+= (c3);
- cout << c1 << ", " << c2 << endl;
- cout << (c1-=c3) << endl;
- cout << -c1 << endl;
- /*
- int n = 100;
- cout << n++ << endl; // 100
- cout << n << endl; // 101
- n = 100;
- cout << ++n << endl; // 101
- cout << n << endl; // 101
- n = 100;
- ++++n; // ++(++n)
- cout << n; // 102
- // n++++; // 后自增不能连用
- */
- cout << endl;
- cout << c2 << endl;
- cout << ++c2 << endl;
- cout << c2 << endl;
- ++++c2;
- cout << c2 << endl;
- cout << endl;
- cout << c2++ << endl;
- cout << c2 << endl;
- // c2++++;
- cout << c2+100 << endl; // c2.operator+(100)
- return 0;
- }
五、成员和友元
当双目操作符的两个操作数不是同一类型时,往往需要把操作符函数定义为调用对象(左操作数)类型的成员,同时也是参数对象(右操作数)类型的友元。
C++中提供三种友元关系的实现方式,友元函数、友元成员函数、友元类。
先谈谈废话: C++ 程序的设计一切是为了运行效率
1、我们已知道类具备封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程式中的其他函数是无法访问私有成员的。非成员函数能够访问类中的公有成员,但是假如将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程式的运行效率。
2、为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但他需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的私有成员。友元的作用在于提高程式的运行效率,但是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。
3、怎么使用友元函数:
友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
1、 要访问非static成员时,需要对象做参数;–常用(友元函数常含有参数)
2、 要访问static成员或全局变量时,则不需要对象做参数
3、 如果做参数的对象是全局对象,则不需要对象做参数
友元函数的位置:
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
友元函数的调用:
可以直接调用友元函数,不需要通过对象或指针
4、运算符重载时需要友元 、关联两个或者两个以上类的内部数据成员需要友元
5、友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
说明:
1、友元函数
友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,他和普通函数相同,即在定义上和调用上和普通函数相同
friend 函数类型 函数名(形参列表) {......函数体..}
实例:
- <span style="color:#000000;">/*友元*/
- #include <iostream>
- using namespace std;
- class Rect{
- public:
- Rect(int width=0,int length=0):m_width(width),m_length(length){}
- private:
- int m_width;
- int m_length;
- friend int Area(Rect&rect);
- };
- int Area(Rect&rect)
- {
- return rect.m_width*rect.m_length;
- }
- int main(void)
- {
- Rect rect(10,50);
- cout<<Area(rect)<<endl;
- return 0;
- }
- </span>
2、友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
friend class 类名;
其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。
例如,以下语句说明类B是类A的友元类:
class A
{
…
public:
friend class B;
…
};
经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。
使用友元类时注意:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
注意事项:
1.友元可以访问类的私有成员。
2.只能出现在类定义内部,友元声明可以在类中的任何地方,一般放在类定义的开始或结尾。
3.友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。
4.类必须将重载函数集中每一个希望设为友元的函数都声明为友元。
5.友元关系不能继承,基类的友元对派生类的成员没有特殊的访问权限。如果基类被授予友元关系,则只有基类具有特殊的访问权限。该基类的派生类不能访问授予友元关系的类。
实例:
- /* 友元类 */
- #include <iostream>
- using namespace std;
- class B;
- class A{
- public:
- A(int n=0,double f=0.0,char ch='A'):m_n(n),m_f(f),m_ch(ch){}
- int m_n;
- protected:
- double m_f;
- private:
- char m_ch;
- friend class B;
- };
- class B{
- public:
- B(){}
- void B_f1(A&a){
- a.m_n++;
- a.m_ch++;
- }
- void B_print(A&a){
- cout<<a.m_n<<","<<a.m_f<<","<<a.m_ch<<endl;
- }
- };
- int main(void)
- {
- A a(10,3.14,'B');
- B b;
- b.B_print(a);
- b.B_f1(a);
- b.B_print(a);
- return 0;
- }
3、友元成员函数:类外其他类的成员函数为友元函数
建议:将友元类放在前面
实例:
- /*友元成员函数*/
- #include <iostream>
- using namespace std;
- class A;
- class B{
- public:
- B(){}
- void B_foo(A&a);
- };
- class A{
- public:
- A(int n,double f,char ch='A'):m_n(n),m_f(f),m_ch(ch){}
- int m_n;
- protected:
- double m_f;
- private:
- char m_ch;
- friend void B::B_foo(A&a);
- };
- void B::B_foo(A&a)
- {
- a.m_n += 10;
- a.m_ch++;
- cout<<a.m_n<<","<<a.m_f<<","<<a.m_ch<<endl;
- }
- int main(void)
- {
- A a(100,3.14,'D');
- B b;
- b.B_foo(a);
- return 0;
- }
4、在模板类中使用友元 operator<< (对 << 运算符的重载)
a)使用方法
在模板类中声明:
friend ostream& operator<< <>(ostream& cout,const MGraph<VexType,ArcType>& G);
在模板类中定义:
template<class VexType,class ArcType>
ostream& operator<<(ostream& cout,const MGraph<VexType,ArcType>& G)
{
//函数定义
}
b)注意:
把函数声明非模板函数:
friend ostream& operator<< (ostream& cout,const MGraph& G);
把函数声明为模板函数:
friend ostream& operator<< <>(ostream& cout,const MGraph<VexType,ArcType>& G); 或:
friend ostream& operator<< <VexType,ArcType>(ostream& cout,const MGraph<VexType,ArcType>& G);
说明:
在函数声明中加入operator<< <>:是将operator<<函数定义为函数模板,将函数模板申明为类模板的友员时,是一对一绑定的
实际的声明函数:这里模板参数可以省略,但是尖括号不可以省略
friend ostream& operator<< <VexType,ArcType>(ostream& cout,const MGraph<VexType,ArcType>& G);
六、不是所有的操作符都能重载
eg:
::
.*/->*
.
?:
七、类型转换操作符和自定义类型转换
A a;
B b (a);
B b = a;
B b = static_cast<B> (a);
1. 可以通过为目标类型提供具有类型转换功能的构造函数完成自定义类型转换,也可以通过为源类型提供类型转换操作符达到同样的目的。
2. 通过使用explicit关键字可以强制通过构造函数完成的类型转换必须显式完成。
3. 类型转换操作符函数
operator 目标类型 (void) {...}
当隐式类型转换与类型转换运算符同时存在时,优先选择类型转换运算符;
实例:
- /*类型转换*/
- #include <iostream>
- using namespace std;
- class Point3D;
- class Point2D{
- public:
- Point2D(int x,int y):m_x(x),m_y(y){}
- operator Point3D(void);
- private:
- int m_x;
- int m_y;
- friend class Point3D;
- };
- class Point3D{
- public:
- // 构造函数
- Point3D(int x,int y,int z):m_x(x),m_y(y),m_z(z){} // 使用 Point3D pt3(pt2); 结果 (10,100,400)
- Point3D(const Point2D&pt):m_x(pt.m_x),m_y(pt.m_y),m_z(0){} // 使用 Point3D pt3 = pt2; 结果 (10,100,0)
- void Print(void){
- cout<<"("<<m_x<<","<<m_y<<","<<m_z<<")"<<endl;
- }
- private:
- int m_x;
- int m_y;
- int m_z;
- };
- Point2D::operator Point3D(void){
- return Point3D(m_x,m_y,400);
- }
- int main(void)
- {
- Point2D pt2(10,100);
- Point3D pt3(30,20,10);
- pt3 = pt2; // 隐式类型转换
- pt3.Print();
- return 0;
- }
结果: (10,100,400)
2. 通过使用explicit关键字可以强制通过构造函数完成的类型转换必须显式完成。
- /*类型转换*/
- #include <iostream>
- using namespace std;
- class Point3D;
- class Point2D{
- public:
- Point2D(int x,int y):m_x(x),m_y(y){}
- operator Point3D(void);
- private:
- int m_x;
- int m_y;
- friend class Point3D;
- };
- class Point3D{
- public:
- // 构造函数
- Point3D(int x,int y,int z):m_x(x),m_y(y),m_z(z){}
- explicit Point3D(const Point2D&pt):m_x(pt.m_x),m_y(pt.m_y),m_z(0){}
- void Print(void){
- cout<<"("<<m_x<<","<<m_y<<","<<m_z<<")"<<endl;
- }
- private:
- int m_x;
- int m_y;
- int m_z;
- };
- Point2D::operator Point3D(void){
- return Point3D(m_x,m_y,400);
- }
- int main(void)
- {
- Point2D pt2(10,100);
- Point3D pt3(30,20,10);
- // pt3 = pt2;
- pt3 = static_cast<Point3D>(pt2); // 显示类型转换
- pt3.Print();
- return 0;
- }
结果:(10,100,0)
五种显示类型的转换
A.通用类型转换: 目标类型(标识符)
eg: char c = 'a';
int n = char(c);
c = int(n);
B.静态类型转换: static_cast<目标类型>(标识符)
eg: char c = 'a';
int n = static_cast<int>(c);
c = static_cast<char>(n);
备注:在目标类型和源类型之间只要有一个方向上可以做隐式类型转换,那 么在两个方向上就都可以做静态类型转换;
主要用于基本类型之间和具有继承关系的类型之间的转换。
这种转换一般会更改变量的内部表示方式,因此,static_cast应用于指针类型转换没有太大意义。
//基本类型转换
int i=0;
double d = static_cast<double>(i); //相当于 double d = (double)i;
//转换继承类的对象为基类对象
class Base{};
class Derived : public Base{};
Derived d;
Base b = static_cast<Base>(d); //相当于 Base b = (Base)d;
C.动态类型转换: dynamic_cast<目标类型>(标识符)
它与static_cast相对,是动态转换。这种转换是在运行时进行转换分析的,并非在编译时进行。
只能在继承类对象的指针之间或引用之间进行类型转换。进行转换时,会根据当前运行时类型信息,判断类型对象之间的转换是否合法。dynamic_cast的指针转换失败,可通过是否为null检测,引用转换失败则抛出一个bad_cast异常。
例:
class Base{};
class Derived : public Base{};
//派生类指针转换为基类指针
Derived *pd = new Derived;
Base *pb = dynamic_cast<Base*>(pd);
if (!pb)
cout << "类型转换失败" << endl;
//没有继承关系,但被转换类有虚函数
class A{
virtual ~A(); //有虚函数
};
class B{}:
A* pa = new A;
B* pb = dynamic_cast<B*>(pa);
如果对无继承关系或者没有虚函数的对象指针进行转换、基本类型指针转换以及基类指针转换为派生类指针,都不能通过编译。
D.常量类型转换: const_cast<目标类型>(标识符)
eg: int*p;
const int* pc = const_cast<int*>(p);
p = const_cast<int*>(pc);
备注:试图通过常量类型转换修改常量的值可能导致不可预知的后果; 必须是同类型之间相互转换
一、用于去除指针变量的常量属性,将它转换为一个对应指针类型的普通变量。反过来,也可以将一个非常量的指针变量转换为一个常指针变量。
这种转换是在编译期间做出的类型更改。
int a = 10;
const int* pci = &a;
int* pk = const_cast<int*>(pci); //相当于int* pk = (int*)pci;
//*pci = 100;此时是会出现编译错误的(assignment of read-only location),因为pci被定义为指向常量的指针,因//此不能通过它来修改指向变量的值
*pk = 100; //此时是正确的,即a的值被修改为100。即便前面a的定义被修改//为const int a = 10,a同样被修改。
cout<<a<<","<<*pni<<","<<endl; // 100,100 修改成功
const A* pca = new A;
A* pa = const_cast<A*>(pca); //相当于A* pa = (A*)pca;
二、出于安全性考虑,const_cast无法将非指针的常量转换为普通变量。即括号中的变量只能是指针变量,且< >中的写的必须是某种类型变量的指针,即int *, double *等等。
像下面的代码是错误的。 (错误:在类型 ‘int’ 上使用 const_cast 无效,因为它既不是指针,也不是引用,也不是数据成员指针)
const int b = 200;
int d = const_cast<int>(b);
E.重解类型转换: reinterpret_cast<目标类型>(标识符)
说明:
reinterpret_cast<指针int */引用int &/void */其他变量类型int>
一、 将一个类型的指针转换为另一个类型的指针。这种转换不用修改指针变量值存放格式(不改变指针变量值),只需在编译时重新解释指针的类型就可做到.
1>基本类型指针的类型转换
eg: void* pv = ...;
int* pn = reinterpret_cast<int*>(pv);
pv = reinterpret_cast<void*>(pn);
eg:
double d=9.2;
double* pd = &d;
int *pi = reinterpret_cast<int*>(pd); //相当于int *pi = (int*)pd;
2>不相关的类的指针的类型转换
class A{};
class B{};
A* pa = new A;
B* pb = reinterpret_cast<B*>(pa); //相当于B* pb = (B*)pa;
二、reinterpret_cast 可以将指针值转换为一个整型数,但不能用于非指针类型的转换。
//指针转换为整数
long l = reinterpret_cast<long>(pi); //相当于long l = (long)pi;
三、但是reinterpret_cast只能用于指针转换,即最后的括号中的变量只能是指针才行。像下面的代码在编译时将通不过。
double f2 = 12.36;
int of = reinterpret_cast<int>(f2);//因为f2不是指针,而是double类型的普通变量。
八、函数操作符
以平方为例:
- /*函数操作符*/
- #include <iostream>
- using namespace std;
- class Square{
- public:
- int operator()(int n)const{
- return n*n;
- }
- };
- int main(void)
- {
- Square sq;
- cout<<sq(2)<<endl;
- return 0;
- }