新标准C++程序设计读书笔记_运算符重载

形式

返回值类型 operator 运算符(形参表)
{
    ……
}

运算符重载

(1)运算符重载的实质是函数重载
(2)可以重载为普通函数,也可以重载为成员函数

 1 class Complex
 2 {
 3 public:
 4     double real,imag;
 5     Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { }
 6     Complex operator-(const Complex & c);
 7 };
 8
 9 Complex operator+( const Complex & a, const Complex & b)
10 {
11     return Complex( a.real + b.real, a.imag + b.imag); //返回一个临时对象
12 }
13
14 Complex Complex::operator-(const Complex & c)
15 {
16     return Complex(real - c.real, imag - c.imag); //返回一个临时对象
17 }
18
19 int main()
20 {
21     Complex a(4,4),b(1,1),c;
22
23     //等价于c=operator+(a,b);
24     c = a + b;
25     cout << c.real << "," << c.imag << endl;
26
27     //a-b等价于a.operator-(b)
28     cout << (a - b).real << "," << (a - b).imag << endl;
29     return 0;
30 }

(3)把含运算符的表达式转换成对运算符函数的调用
(4)把运算符的操作数转换成运算符函数的参数
(5)运算符被多次重载时,根据实参的类型决定调用哪个运算符函数
(6)重载为成员函数时, 参数个数为运算符目数减一;重载为普通函数时, 参数个数为运算符目数

赋值运算符 ‘ =’重载

赋值运算符“ =”只能重载为成员函数

 1 #include <iostream>
 2
 3 using namespace std;
 4
 5 class String
 6 {
 7 private:
 8     char * str;
 9 public:
10     String ():str(new char[1]) { str[0] = 0;}
11     const char * c_str() { return str; };
12     String & operator = (const char * s);
13     String::~String( ) { delete [] str; }
14 };
15
16 String & String::operator = (const char * s)
17 {
18     //重载“=”以使得 obj = “hello”能够成立
19     delete [] str;
20     str = new char[strlen(s)+1];
21     strcpy( str, s);
22     return * this;
23 }
24
25 int main()
26 {
27     String s;
28     s = "Good Luck," ; //等价于 s.operator=("Good Luck,");
29     cout << s.c_str() << endl;
30
31     /* 这条语句要是不注释掉就会出错,因为这是一个初始化语句而并不是赋值语句 */
32     // String s2 = "hello!";
33
34     s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");
35     cout << s.c_str() << endl;
36     return 0;
37 }

浅拷贝和深拷贝

考察下面的代码

1 String S1, S2;
2 S1 = “this”;
3 S2 = “that”;
4 S1 = S2;

如果还用上面的运算符重载,那就会出现问题

(1)如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str指向同一地方。
(2)如果S1对象消亡,析构函数将释放S1.str指向的空间,再访问S2的时候就好玩儿了。
(3)另外,如果执行 S1 = "other";会导致S2.str指向的地方被delete

因此,要做如下修改:

 1 String & operator = (const String & s)
 2 {
 3     if( this == & s)
 4         return * this;
 5
 6     delete [] str;
 7     str = new char[strlen(s.str)+1];
 8     strcpy( str,s.str);
 9     return * this;
10 }

ps:详细完成一个完整的String类,后续再补充

运算符重载为友元函数

一般情况下,将运算符重载为类的成员函数,是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。

 1 class Complex
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r, double i):real(r),imag(i){ };
 6     Complex operator+( double r );
 7 };
 8
 9 Complex Complex::operator+( double r )
10 {
11     //能解释 c+5
12     return Complex(real + r,imag);
13 }

上面的类中重载的加号有局限性

Complex c ;
c = c + 5; //有定义,相当于 c = c.operator +(5);
但是:
c = 5 + c; //编译出错

为了使得上述的表达式能成立,需要将 + 重载为普通函数。但是普通函数又不能访问私有成员,所以,需要将运算符 + 重载为友元。

 1 class Complex
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r, double i):real(r),imag(i){ };
 6     Complex operator+( double r );
 7     friend Complex operator + (double r,const Complex & c);
 8 };
 9
10 Complex Complex::operator+( double r )
11 {
12     //能解释 c+5
13     return Complex(real + r,imag);
14 }
15
16 Complex operator+ (double r,const Complex & c)
17 {
18     //能解释 5+c
19     return Complex( c.real + r, c.imag);
20 }

设计一个变长数组类

想要达到下面目的

 1 int main()
 2 {
 3     CArray a; //开始里的数组是空的
 4     /*
 5         要用动态分配的内存来存放数组元素,需要一个指针成员变量
 6      */
 7     for( int i = 0;i < 5;++i)
 8         a.push_back(i);
 9
10     CArray a2,a3;
11     /*
12         要重载“=”
13      */
14     a2 = a;
15     for( int i = 0; i < a.length(); ++i )
16         /*
17             要重载“[ ]”
18          */
19         cout << a2[i] << " " ;
20
21     a2 = a3; //a2是空的
22     for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0
23         cout << a2[i] << " ";
24     cout << endl;
25
26     a[3] = 100;
27     /*
28         要自己写复制构造函数
29      */
30     CArray a4(a);
31     for( int i = 0; i < a4.length(); ++i )
32         cout << a4[i] << " ";
33
34     return 0;
35 }

类的设计如下:

 1 class CArray {
 2     int size; //数组元素的个数
 3     int *ptr; //指向动态分配的数组
 4 public:
 5     CArray(int s = 0); //s代表数组元素的个数
 6     CArray(CArray & a);
 7     ~CArray();
 8     void push_back(int v); //用于在数组尾部添加一个元素v
 9     CArray & operator=( const CArray & a);
10     //用于数组对象间的赋值
11     int length() { return size; } //返回数组元素个数
12     int & CArray::operator[](int i) //返回值为 int 不行!不支持 a[i] = 4
13     {
14         //用以支持根据下标访问数组元素,
15         // 如n = a[i] 和a[i] = 4; 这样的语句
16         return ptr[i];
17     }
18 };
19
20 CArray::CArray(int s):size(s)
21 {
22     if( s == 0)
23         ptr = NULL;
24     else
25         ptr = new int[s];
26 }
27
28 CArray::CArray(CArray & a)
29 {
30     if( !a.ptr) {
31         ptr = NULL;
32         size = 0;
33         return;
34     }
35     ptr = new int[a.size];
36     memcpy( ptr, a.ptr, sizeof(int ) * a.size);
37     size = a.size;
38 }
39
40 CArray::~CArray()
41 {
42     if( ptr) delete [] ptr;
43 }
44
45 CArray & CArray::operator=( const CArray & a)
46 {
47     //赋值号的作用是使“=”左边对象里存放的数组,大小和内容都和右边的对象一样
48     if( ptr == a.ptr) //防止a=a这样的赋值导致出错
49         return * this;
50
51     if( a.ptr == NULL) { //如果a里面的数组是空的
52         if( ptr )
53             delete [] ptr;
54         ptr = NULL;
55         size = 0;
56         return * this;
57     }
58
59     if( size < a.size) { //如果原有空间够大,就不用分配新的空间
60         if(ptr)
61             delete [] ptr;
62         ptr = new int[a.size];
63     }
64
65     memcpy( ptr, a.ptr, sizeof(int) * a.size);
66     size = a.size;
67     return * this;
68 }
69
70 void CArray::push_back(int v)
71 {
72     //在数组尾部添加一个元素
73     if( ptr) {
74         int * tmpPtr = new int[size + 1]; //重新分配空间
75         memcpy(tmpPtr, ptr, sizeof(int) * size); //拷贝原数组内容
76         delete [] ptr;
77         ptr = tmpPtr;
78     }
79     else //数组本来是空的
80         ptr = new int[1];
81
82     ptr[size++] = v; //加入新的数组元素
83 }

流运算符的重载

假定c是Complex复数类的对象,现在希望写“ cout << c;”,就能以“ a+bi”的形式输出c的值,写“ cin>>c;”,就能从键盘接受“ a+bi”形式的输入,并且使得c.real = a,c.imag = b。

 1 class Complex
 2 {
 3     double real,imag;
 4 public:
 5     Complex( double r=0, double i=0):real(r),imag(i){ };
 6     friend ostream & operator<<( ostream & os, const Complex & c);
 7     friend istream & operator>>( istream & is,Complex & c);
 8 };
 9
10 ostream & operator<<( ostream & os,const Complex & c)
11 {
12     os << c.real << "+" << c.imag << "i"; //以"a+bi"的形式输出
13     return os;
14 }
15
16 istream & operator>>( istream & is,Complex & c)
17 {
18     string s;
19     is >> s; //将"a+bi"作为字符串读入, “a+bi” 中间不能有空格
20     int pos = s.find("+", 0);
21     string sTmp = s.substr(0, pos); //分离出代表实部的字符串
22     c.real = atof(sTmp.c_str()); //atof库函数能将const char*指针指向的内容转换成 float
23     sTmp = s.substr(pos+1, s.length()-pos-2); //分离出代表虚部的字符串
24     c.imag = atof(sTmp.c_str());
25     return is;
26 }

类型转换运算符

类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型

 1 #include <iostream>
 2 using namespace std;
 3 class Complex
 4 {
 5     double real,imag;
 6 public:
 7     Complex(double r=0,double i=0):real(r),imag(i) { };
 8     //重载强制类型转换运算符 double
 9     operator double () { return real; }
10
11 };
12
13 int main()
14 {
15     Complex c(1.2,3.4);
16     cout << (double)c << endl; //输出 1.2
17     double n = 2 + c; //等价于 double n=2+c.operator double()
18     cout << n; //输出 3.2
19 }

自增,自减运算符的重载

1、前置运算符作为一元运算符重载
(1)重载为成员函数:
T & operator++();
T & operator--();
(2)重载为全局函数:
T1 & operator++(T2);
T1 & operator—(T2);

2、后置运算符作为二元运算符重载,多写一个没用的参数
(1)重载为成员函数:
T operator++(int);
T operator--(int);
(2)重载为全局函数:
T1 operator++(T2,int );
T1 operator—( T2,int);

 1 class CDemo
 2 {
 3 private:
 4     int n;
 5 public:
 6     CDemo(int i=0):n(i) { }
 7     CDemo & operator++(); //用于前置形式
 8     CDemo operator++( int ); //用于后置形式
 9     operator int ( ) { return n; }
10     friend CDemo & operator--(CDemo & );
11     friend CDemo operator--(CDemo & ,int);
12 };
13
14 CDemo & CDemo::operator++()
15 {
16     //前置 ++
17     n++;
18     return * this;
19 } // ++s即为: s.operator++();
20
21 CDemo CDemo::operator++( int k )
22 {
23     //后置 ++
24     CDemo tmp(* this); //记录修改前的对象
25     n++;
26     return tmp; //返回修改前的对象
27 } // s++即为: s.operator++(0);
28
29 CDemo & operator--(CDemo & d)
30 {
31     //前置--
32     d.n--;
33     return d;
34 } //--s即为: operator--(s);
35
36 CDemo operator--(CDemo & d,int)
37 {
38     //后置--
39     CDemo tmp(d);
40     d.n--;
41     return tmp;
42 } //s--即为: operator--(s, 0);
43
44 int main()
45 {
46     CDemo d(5);
47     cout << (d++ ) << ","; //等价于 d.operator++(0);
48     cout << d << ",";
49     cout << (++d) << ","; //等价于 d.operator++();
50     cout << d << endl;
51     cout << (d-- ) << ","; //等价于 operator--(d,0);
52     cout << d << ",";
53     cout << (--d) << ","; //等价于 operator--(d);
54     cout << d << endl;
55     return 0;
56 }

运算符重载的注意事项
(1)C++不允许定义新的运算符 ;
(2)重载后运算符的含义应该符合日常习惯;
complex_a + complex_b
word_a > word_b
date_b = date_a + n
(3)运算符重载不改变运算符的优先级;
(4)以下运算符不能被重载:“.” 、“.*” 、“::” 、“?:” 、 sizeof;
(5)重载运算符()、 []、 ->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数。

时间: 2024-10-25 07:37:39

新标准C++程序设计读书笔记_运算符重载的相关文章

新标准C++程序设计读书笔记_类和对象

面向对象的程序设计方法 抽象:将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性):将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构. 封装:通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“ 类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系. 从客观事物抽象出类 写一个程序,输入矩形的长和宽,输出面积和周长. 比如对于“矩形”这种东西,要用一个类来表示,该如何做“抽象”呢?矩形的属

新标准C++程序设计读书笔记_继承和多态

简单继承的例子: 1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class CStudent 6 { 7 private: 8 string name; 9 string id; //学号 10 char gender; //性别,'F'代表女, 'M'代表男 11 int age; 12 public: 13 void PrintInfo(); 14 void SetInfo( const

汇编语言程序设计读书笔记(3)- 程序范例

主要描述三方面的内容:第一是汇编语言的程序模版,以及模版涉及到的一些知识点:第二是如何调试汇编语言:第三是如何在汇编语言中调用C库函数. 1. 汇编语言的组成 汇编语言由段(section)组成,一个程序中执行的代码,叫文本段(text),程序还可能有定义变量,有付给初始值的变量放在数据段(data)中,没有赋初值或者付给零初值的放在bss段中.text段一定是要有的,data和bss可以没有. 2. 段的定义 用.section语法定义段.比如: .section .text定义文本段, .s

《循序渐进DB2.DBA系统管理、运维与应用案例》(牛新庄著)读书笔记2

<循序渐进DB2.DBA系统管理.运维与应用案例>(牛新庄著)读书笔记2                                              Windows下的DB2向导安装 现在的DB2已经支持了广泛的IT基础设施,可以在众多平台上运行.Windows.Linux.UNIX这三种平台统称为luw平台.对于自学DB2的人而言,由于不可能自己购买大型机或高端的服务器,所以更多的是把DB2装在luw平台上进行练习.而如果想要尽可能的贴近真实的生产环境,为日后工作打基础,最好是

C++ Primer笔记12_运算符重载_递增递减运算符_成员访问运算符

1.递增递减运算符 C++语言并不要求递增递减运算符必须是类的成员.但是因为他们改变的正好是所操作对象的状态,所以建议设定为成员函数. 对于递增与递减运算符来说,有前置与后置两个版本,因此,我们应该为类定义两个版本的递增与递减运算符. 问题来了,程序是如何区分前置和后置呢?因为都是++和-- 为了解决这个问题,后置版本的递增递减运算符接受一个额外的(不被使用)int类型的形参.当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参.这个形参唯一的作用就是区分前置和后置运算符函数. 因为不会

C++ Primer笔记13_运算符重载_总结

总结: 1.不能重载的运算符: . 和 .* 和 ?: 和 ::  和 sizeof 和 typeid 2.重载运算符有两种基本选择: 类的成员函数或者友元函数, 建议规则如下: 运算符 建议使用 所有一元运算符 成员函数 = () [] -> 必须是成员函数 += -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了. 成员函数 所有其它二元运算符, 例如: –,+,*,/ 友元函数 3.前几篇中的实例,现在汇总Person类的程序:

C++旧源码向新标准移植陷井(一)_局部栈变量的生命周期

之前在VC++6.0上面写了下面这样的代码: int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; //不重要的部分略过if (argc>1) {if (strcmp(argv[1],"createpcsstep") == 0) { CDlgCreateStepPcs dlg; dlg.DoModal(); break; } else if (strcmp(argv[1],"clea

C++ Primer笔记10_运算符重载_赋值运算符_输入/输出运算符

我们致力于推广Scriptcase,以产品销售.技术支持.培训服务.外包开发为核心,将Scriptcase这一简单易用的工具真正在国内予以推广.我们搭建了Scriptcase的示例网站,通过该网站可以看到在几乎不需要任何编程的情况下,Scriptcase可以做到如何快速和先进的开发. 敬请访问 www.phpscriptcase.com C++ Primer笔记10_运算符重载_赋值运算符_输入/输出运算符

多态实现的原理------新标准c++程序设计

"多态"的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定.例子: #include<iostream> using namespace std; class A{ public: int i; virtual void func(){}; virtual void func2(){}; //如果为只有一个去掉 virtual 关键字即virtual void func2(){};变为 void func2(){}; 输