zoukankan      html  css  js  c++  java
  • C++ 基础 3:类和对象

    1 类和对象

    1.1 类定义

    类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

    class Box
    {
       public:
          double length;   // 盒子的长度
          double breadth;  // 盒子的宽度
          double height;   // 盒子的高度
    
          // 成员函数声明
          double getVolume(void);// 返回体积
    };
    

    1.2 C++ 对象

    对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:

    Box Box1;          // 声明 Box1,类型为 Box
    Box Box2;          // 声明 Box2,类型为 Box
    

    1.3 成员变量和成员函数

    成员变量:BOX 类中的 length,breadth,height。

    成员函数:getVolume();

    成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。

    1. 在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。如下所示:
    class Box
    {
       public:
          double length;      // 长度
          double breadth;     // 宽度
          double height;      // 高度
       
          double getVolume(void)
          {
             return length * breadth * height;
          }
    };
    
    1. 也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:
    double Box::getVolume(void)
    {
        return length * breadth * height;
    }
    

    2 对象的构造和析构

    2.1 构造函数

    C++ 中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数。

    class 类名
    {
        类名(形参)
        {
            构造体
        }
    }
    

    2.2 析构函数

    C++ 中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数。

    class 类名
    {
        ~类名(形参)
        {
            析构体
        }
    }
    

    2.3 构造函数的分类

    2.3.1 无参构造函数

    代码示例:

    // strucFunc1.cpp,无参构造函数
    
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    	public:
    
    		// 无参构造函数
    		Test()
    		{
    			a = 0;
    			b = 0;
    
    			cout << "Test() 无参构造函数执行" << endl;
    		}
    
    	private:
    
    		int a;
    		int b;
    };
    
    int main()
    {
    	Test t; // 调用无参构造函数
    
    	getchar();
    
    	return 0;
    }
    

    运行结果:

    2.3.2 有参构造函数

    代码示例:

    // strucFunc2.cpp,有参构造函数
    
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    	private:
    
    		int a;
    
    	public:
    
    		// 有参构造函数
    		Test(int a)
    		{
    			cout << "a = " << a << endl;
    		}
    
    		// 有参构造函数
    		Test(int a,int b)
    		{
    			cout << "a = " << a << ", b = "<< b << endl;
    		}
    };
    
    int main()
    {
    	Test t1(1); // 调用有参构造函数Test(int a)
    	Test t2(2,3); // 调用有参构造函数Test(int a,int b)
    
    	getchar();
    
    	return 0;
    }
    

    运行结果:

    2.3.3 拷贝构造函数

    本小节内容参考 AlanTu:C++ 拷贝构造函数详解

    对于普通类型的对象来说,他们之间的复制是很简单的,如:

    int a = 1;
    int b = a;
    

    而类对象与普通对象不同,类对象内部结构存在各种成员变量。因此需要使用拷贝构造函数。

    拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量

    拷贝构造函数的最常见形式如下:

    class 类名
    {
          类名(const 类名 & another)
          {
                拷贝构造体
          }
    }
    

    2.3.3.1 拷贝构造函数被调用的场景

    1. 通过使用另一个同类型的对象来初始化新创建的对象
    // CopyConstructorTest1.cpp,拷贝构造函数被调用的场景: 1. 通过使用另一个同类型的对象来初始化新创建的对象
    
    #include <iostream>
    
    using namespace std;
    
    class CopyConstructorTest1
    {
    	private:
    
    		int a;
    
    	public:
    
    		// 构造函数
    		CopyConstructorTest1(int temp)
    		{
    			a = temp;
    
    			cout << "构造函数被调用" << endl;
    		}
    
    		// 拷贝构造函数
    		CopyConstructorTest1(const CopyConstructorTest1 & tempClass)
    		{
    			a = tempClass.a;
    
    			cout << "拷贝构造函数被调用" << endl;
    		}
    
    		// 析构函数
    		~CopyConstructorTest1()
    		{
    			cout << "析构函数被调用" << endl;
    		}
    
    		void show()
    		{
    			cout << "a = " << a << endl;
    		}
    };
    
    int main()
    {
    	CopyConstructorTest1 A(1); 
    	CopyConstructorTest1 B = A;  // 1 通过使用另一个同类型的对象来初始化新创建的对象。
    
    	B.show();
    
    	return 0;
    }
    

    运行结果:

    2. 当函数的参数为类的对象时
    // CopyConstructorTest2.cpp,拷贝构造函数被调用的场景: 2. 当函数的参数为类的对象时
    
    #include <iostream>
    
    using namespace std;
    
    class CopyConstructorTest1
    {
    	private:
    
    		int a;
    
    	public:
    
    		// 构造函数
    		CopyConstructorTest1(int temp)
    		{
    			a = temp;
    
    			cout << "构造函数被调用" << endl;
    		}
    
    		// 拷贝构造函数
    		CopyConstructorTest1(const CopyConstructorTest1 & tempClass)
    		{
    			a = tempClass.a;
    
    			cout << "拷贝构造函数被调用" << endl;
    		}
    
    		// 析构函数
    		~CopyConstructorTest1()
    		{
    			cout << "析构函数被调用" << endl;
    		}
    
    		void show()
    		{
    			cout << "a = " << a << endl;
    		}
    };
    
    void funtionTest(CopyConstructorTest1 tempClassCopyConstructorTest1)
    {
    	cout << "funtionTest 函数被调用" << endl;
    
    	tempClassCopyConstructorTest1.show();
    }
    
    int main()
    {
    	CopyConstructorTest1 A(1); 
    	
    	funtionTest(A); // 2. 当函数的参数为类的对象时,会调用拷贝构造函数
    
    	return 0;
    }
    

    运行结果:

    funtionTest() 函数执行时,会执行以下步骤:

    1. A 对象传入 funtionTest() 函数时,会产生形参 tempClassCopyConstructorTest1

    2. 调用拷贝构造函数把 A 对象的值给 tempClassCopyConstructorTest1

    1.2 步 总和起来类似于 CopyConstructorTest1 tempClassCopyConstructorTest1 = A;

    1. funtionTest() 函数执行完后,析构掉 tempClassCopyConstructorTest1
    3. 函数的返回值是类的对象
    // CopyConstructorTest3.cpp,拷贝构造函数被调用的场景: 3. 函数的返回值是类的对象
    
    #include <iostream>
    
    using namespace std;
    
    class CopyConstructorTest1
    {
    	private:
    
    		int a;
    
    	public:
    
    		// 构造函数
    		CopyConstructorTest1(int temp)
    		{
    			a = temp;
    
    			cout << "构造函数被调用" << endl;
    		}
    
    		// 拷贝构造函数
    		CopyConstructorTest1(const CopyConstructorTest1 & tempClass)
    		{
    			a = tempClass.a;
    
    			cout << "拷贝构造函数被调用" << endl;
    		}
    
    		// 析构函数
    		~CopyConstructorTest1()
    		{
    			cout << "析构函数被调用" << endl;
    		}
    
    		void show()
    		{
    			cout << "a = " << a << endl;
    		}
    };
    
    CopyConstructorTest1 funtionTest()
    {
    	cout << "funtionTest 函数被调用" << endl;
    
    	CopyConstructorTest1 A(1);
    
    	A.show();
    
    	return A;
    }
    
    int main()
    {
    	funtionTest(); // 3. 函数的返回值是类的对象
    
    	return 0;
    }
    

    运行结果:

    funtionTest()函数执行时,会执行以下步骤:

    1. 调用构造函数创建 A 对象

    2. 编译器内部产生一个临时变量 X(看不见)

    3. 调用拷贝构造函数把 对象 A 的值给 对象 X

    4. 析构 A

    5. 析构 X

    2.4 浅拷贝与深拷贝

    本小节内容参考 AlanTu:C++ 拷贝构造函数详解

    2.4.1 默认拷贝构造函数

    很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有以下形式:

    Rect::Rect(const Rect& r)
    {
        width = r.width;
        height = r.height;
    }
    

    以上代码不用我们编写,编译器会为我们自动生成。但是如果认为这样就可以解决对象的复制问题,那就错了,让我们来考虑以下一段代码:

    // CopyTest1.cpp,默认构造函数的局限性
    
    #include <iostream>
    
    using namespace std;
    
    class Rect
    {
    	public:
    
    		Rect()
    		{
    			count++;
    		}
    
    		~Rect()
    		{
    			count--;
    		}
    
    		static int getCount()
    		{
    			return count;
    		}
    
    private:
    
        int width;
        int height;
    
        static int count;
    };
    
    int Rect::count = 0;
    
    int main()
    {
        Rect rect1;
    
        cout << "The count of Rect:" << Rect::getCount() << endl;
    
        Rect rect2(rect1);
    
        cout << "The count of Rect:" << Rect::getCount() << endl;
    
        return 0;
    }
    

    运行结果:

    这段代码对前面的类,加入了一个静态成员,目的是进行计数。在主函数中,首先创建对象rect1,输出此时的对象个数,然后使用rect1复制出对象rect2,再输出此时的对象个数,按照理解,此时应该有两个对象存在,但实际程序运行时,输出的都是1,反应出只有1个对象。此外,在销毁对象时,由于会调用销毁两个对象,类的析构函数会调用两次,此时的计数器将变为负数。

    说白了,就是拷贝构造函数没有处理静态数据成员。

    2.4.2 浅拷贝

    所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码:

    #include <iostream>
    #include <assert.h>
    
    using namespace std;
    
    class Rect
    {
        public:
    
            Rect()
            {
                 p = new int(100);
            }
       
            ~Rect()
            {
                assert(p != NULL);
                delete p;
            }
    
        private:
            int width;
            int height;
            int *p;
    };
    
    int main()
    {
        Rect rect1;
    
        Rect rect2(rect1);
    
        return 0;
    }
    

    在这段代码运行结束之前,会出现一个运行错误。原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。我们来分析一下:

    在运行定义 rect1 对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:

    在使用 rect1 复制 rect2 时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,如下图所示:

    当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

    2.4.3 深拷贝

    在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

    #include <iostream>
    #include <assert.h>
    
    using namespace std;
    
    class Rect
    {
        public:
            
            Rect()
            {
                 p = new int(100);
            }
        
            Rect(const Rect& r)
            {
                width = r.width;
                height = r.height;
    
                p = new int(100);
    
                *p = *(r.p);
            }
         
            ~Rect()
            {
                assert(p != NULL);
                delete p;
            }
    
    private:
    
            int width;
            int height;
            int *p;
    };
    
    int main()
    {
        Rect rect1;
        Rect rect2(rect1);
    
        return 0;
    }
    

    此时,在完成对象的复制后,内存的一个大致情况如下:

    此时 rect1 的 p 和 rect2 的 p 各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

    2.4.4 总结

    拷贝有两种:深拷贝,浅拷贝。

    当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

    深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

    浅拷贝只拷贝栈上的变量,深拷贝同时要拷贝堆上面的内存。

    3 对象动态建立 new 和 释放 delete

    了解动态内存在 C++ 中是如何工作的是成为一名合格的 C++ 程序员必不可少的。C++ 程序中的内存分为两个部分:

    • 栈:在函数内部声明的所有变量都将占用栈内存。

    • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。

    很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。

    在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。

    如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。

    C++ 提供了运算符 new 和 delete 来取代 malloc 和 free 函数。

    下面是使用 new 运算符来为任意的数据类型动态分配内存的通用语法:

    new data-type;

    例如,我们可以定义一个指向 double 类型的指针,然后请求内存,该内存在执行时被分配。我们可以按照下面的语句使用 new 运算符来完成这点:

    double* pvalue  = NULL;
    if( !(pvalue  = new double ))
    {
       cout << "Error: out of memory." <<endl;
       exit(1);
     
    }
    

    在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用 delete 操作符释放它所占用的内存,如下所示:

    delete pvalue; // 释放 pvalue 所指向的内存

    4 静态成员变量和成员函数

    类的静态成员,属于类,也属于对象,但终归属于类。

    4.1 静态成员变量

    // 声明
    static 数据类型 成员变量; // 在类的内部
    
    // 初始化
    数据类型 类名::静态数据成员 = 初值; // 在类的外部
    
    // 调用
    类名:: 静态数据成员
    类对象.静态数据成员
    
    1. static 成员变量实现了同类对象间信息共享

    2. static 成员类外存储,求类大小并不包含在内

    3. static 成员是命名空间属于类的全局变量,存储在 data 区

    4. static 成员只能类外初始化

    5. static 成员 可以通过类名访问,也可以通过对象访问

    4.2 静态成员函数

    // 声明
    static 函数声明
    
    // 调用
    类名::函数调用
    类对象.函数调用
    
    1. 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

    2. 静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时 this 指针被当做参数传进,而静态成员函数属于类,不属于对象,没有 this 指针。

    5 友元

    类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员

    尽管友元函数的原型有在类的定义中出现过,但是 友元函数并不是成员函数

    友元可以是一个函数,该函数被称为 友元函数;友元也可以是一个类,该类被称为 友元类,在这种情况下,整个类及其所有成员都是友元。

    5.1 友元函数

    友元函数是可以直接访问类的私有成员的非成员函数。它是 定义在类外部的普通函数,它不属于任何类,但需要在类的定义中声明,声明时只需要在友元函数的名称前加上关键字 friend,格式如下:

    friend 类型 函数名(形参);

    一个函数可以是多个类的友元函数,只需要在各个类中分别声明即可。

    5.1.1 全局函数作友元函数

    // friendTest1.cpp,全局函数作友元函数
    
    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    class Point
    {
    	private:
    
    		double x,y;
    
    	public:
    
    		Point(double xx,double yy)
    		{
    			x = xx;
    			y = yy;
    		}
    
    		void printXY();
    
    		friend double getDistance(Point &point1,Point &point2);
    };
    
    void Point::printXY()
    {
    	cout << "(" << x << "," << y << ")" << endl;
    }
    
    double getDistance(Point &point1,Point &point2)
    {
    	double x = point1.x - point2.x;
    	double y = point1.y - point2.y;
    
    	return sqrt(x*x + y*y);
    }
    
    int main(void)
    {
    	Point p1(3.0,4.0),p2(6.0,8.0);
    
    	p1.printXY();
    	p2.printXY();
    
    	double distance = getDistance(p1,p2);
    
    	cout << "Distance is " << distance << endl;
    
    	return 0;
    }
    

    运行结果:

    5.1.2 类成员函数作友元函数

    // friendTest2.cpp,类成员函数作友元函数
    
    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    // 前向声明,是一种不完全型声明,即只需要提供类名,无需提供类实现即可。
    class Point;
    
    class ManagerPoint
    {
    	public:
    		double getDistance(Point &point1,Point &point2);
    };
    
    class Point
    {
    	private:
    
    		double x,y;
    
    	public:
    
    		Point(double xx,double yy)
    		{
    			x = xx;
    			y = yy;
    		}
    
    		void printXY();
    
    		friend double ManagerPoint::getDistance(Point &point1,Point &point2);
    };
    
    void Point::printXY()
    {
    	cout << "(" << x << "," << y << ")" << endl;
    }
    
    double ManagerPoint::getDistance(Point &point1,Point &point2)
    {
    	double x = point1.x - point2.x;
    	double y = point1.y - point2.y;
    
    	return sqrt(x*x + y*y);
    }
    
    int main(void)
    {
    	Point p1(3.0,4.0),p2(6.0,8.0);
    
    	p1.printXY();
    	p2.printXY();
    
    	ManagerPoint mp;
    
    	double distance = mp.getDistance(p1,p2);
    
    	cout << "Distance is " << distance << endl;
    
    	return 0;
    }
    

    运行结果:

    5.2 友元类

    友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。

    定义友元类的格式如下:

    friend class 类名;
    其中:friend 和 class 是关键字,类名必须是程序中的一个已定义过的类。
    
    例如,以下语句说明 类 B 是 类 A 的 友元类
    
    class A
    {
          ...
    
          public:
          
                friend class B;
    
          ...
    };
    

    示例代码如下:

    // friendTest3.cpp,友元类。类 B 是 类 A 的友元类。
    
    #include <iostream>
    
    using namespace std;
    
    class A
    {
    	private:
    
    		double x;
    
    
    	public:
    
    		friend class B;
    
    		void printX()
    		{
    			cout << "A is " << x << endl;
    		}
    };
    
    class B
    {
    	public:
    
    		void setA(double temp, A &a);
    };
    
    
    void B::setA(double temp, A &a)
    {
    	a.x = temp;
    }
    
    int main()
    {
    	B b;
    	A a;
    
    	b.setA(666.0,a);
    
    	a.printX();
    
    	return 0;
    }
    

    运行结果:

    5.3 友元总结

    5.3.1 声明位置

    友元声明以关键字 friend 开始,它只能出现在类定义中。因为友元不是授权类的成员,所以它不受其所在类的声明区域,如 public,private 和 protected 的影响。通常选择把友元声明放在类头之后。

    5.3.2 友元的利弊

    友元不是类成员,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

    不过,类的访问权限确实在某些应用场合显得有些呆板,从而容忍了友元这一特别语法 现象。

    5.3.3 注意事项

    1. 友元关系不能被继承

    2. 友元关系是单向的,不具有交换性。若类 B 是 类 A 的友元,类 A 不一定是 类 B 的友元,要看在类中是否有相应的声明。

    3. 友元关系不具有传递性。若 类 B 是 类 A 的友元,类 C 是 类 B 的友元,类 C 不一定是 类 A 的友元,同样要看在类中是否有相应的声明。

    6 运算符重载

    重载,就是重新赋予新的含义。

    函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。因此,一个函数名就可以用来代表不同功能的函数。

    函数重载:在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。、

    运算符也可以重载。实际上,我们已经在不知不觉之中使用了运算符重载。例如“<<”是 C++ 的位运算中的位移运算符(左移),但在输出操作中又是与流对象 cout 配合使用的流插入运算符,这就是运算符重载。C++ 系统对 "<<"进行了重载,用户在不同的场合下使用它们时,作用是不同的。对“<<”的重载处理是放在头文件 stream 中。

    重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

    运算符重载 的一般格式如下:

    函数类型 operator运算符名称(形参)
    {
          重载实体;
    }
    

    6.1 运算符重载的方式

    1. 运算符重载函数作为类的成员函数

    2. 运算符重载函数作为类的友元函数

    3. 运算符重载函数作为全局函数

    6.1.1 运算符重载函数作为类的成员函数

    //overloadOperatorTest1.cpp,运算符重载函数作为类的成员函数
    
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    	private:
    
    		float _x;
    		float _y;
    
    	public:
    
    		Test(float x = 0,float y = 0):_x(x),_y(y)
    		{
    		}
    
    		void display()
    		{
    			cout << "(" << _x << "," << _y << ")" << endl;
    		}
    
    		Test operator+(Test &another);
    };
    
    Test Test::operator+(Test &another)
    {
    	cout << "运算符重载函数作为类的成员函数" << endl;
    
    	return Test(this->_x + another._x, this->_y + another._y);
    }
    
    int main(void)
    {
    	Test t1(1,2);
    	Test t2(3,4);
    
    	t1.display();
    	t2.display();
    
    	// 运算符重载函数作为类的成员函数
    	Test t3 = t1 + t2; // 或  Test t3 = operator+(t1,t2);
    
    	t3.display();
    }
    

    运行结果:

    6.1.2 运算符重载函数作为类的友元函数

    //overloadOperatorTest2.cpp,运算符重载函数作为类的友元函数
    
    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    	private:
    
    		float _x;
    		float _y;
    
    	public:
    
    		Test(float x = 0,float y = 0):_x(x),_y(y)
    		{
    		}
    
    		void display()
    		{
    			cout << "(" << _x << "," << _y << ")" << endl;
    		}
    
    		friend Test operator+(Test &t1,Test &t2);
    };
    
    Test operator+(Test &t1,Test &t2)
    {
    	cout << "运算符重载函数作为类的友元函数" << endl;
    
    	return Test(t1._x + t2._x, t1._y + t2._y);
    }
    
    int main(void)
    {
    	Test t1(1,2);
    	Test t2(3,4);
    
    	t1.display();
    	t2.display();
    
    	// 运算符重载函数作为类的友元函数
    	Test t3 = t1 + t2; // 或  Test t3 = operator+(t1,t2);
    
    	t3.display();
    }
    

    运行结果:

    6.2 重载规则

    1. C++ 不允许用户自己定义新的运算符,只能对已有的 C++ 运算符进行重载

    2. C++ 允许重载的运算符

    • 双目运算符:+(加),-(减),*(乘),/(除),%(取模)

    • 关系运算符:==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)

    • 逻辑运算符:||(逻辑或),&&(逻辑与),!(逻辑非)

    • 单目运算符:+ (正),-(负),*(指针),&(取地址)

    • 自增自减运算符:++(自增),--(自减)

    • 位运算符:(按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)

    • 赋值运算符:=, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=

    • 空间申请与运算符:new, delete, new[ ] , delete[]

    • 其他运算符:()(函数调用),->(成员访问),,(逗号),[](下标)

    1. C++ 不能重载的运算符
    • .: 成员访问运算符
    • .*, ->*:成员指针访问运算符
    • :::域运算符
    • sizeof:长度运算符
    • ?::条件运算符
    • #: 预处理符号
    1. 重载不能改变运算符运算对象(即操作数)的个数

    2. 重载不能改变运算符的优先级别

    3. 重载不能改变运算符的结合性

    4. 重载运算符的函数不能有默认的参数

    重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。

    也就是说,参数不能全部是 C++的标准类型,以防止用户修改用于标准类型数据成员的运算符的性质,如下面这样的写法是不对的:

    int operator + (int a,int b) 
    { 
          return (a-b); 
    } 
    

    原来运算符 + 的作用是对两个数相加,现在企图通过重载使它的作用改为两个数相减。如果允许这样重载的话。如果有表达式 4+3。它的结果是 7 还是 1 呢?显然,这样的做法是绝对要禁止的。

    6.3 双目运算符重载

    下面的实例演示了如何重载加运算符( + )。

    #include <iostream>
    using namespace std;
     
    class Box
    {
       double length;      // 长度
       double breadth;     // 宽度
       double height;      // 高度
    public:
     
       double getVolume(void)
       {
          return length * breadth * height;
       }
       void setLength( double len )
       {
           length = len;
       }
     
       void setBreadth( double bre )
       {
           breadth = bre;
       }
     
       void setHeight( double hei )
       {
           height = hei;
       }
       // 重载 + 运算符,用于把两个 Box 对象相加
       Box operator+(const Box& b)
       {
          Box box;
          box.length = this->length + b.length;
          box.breadth = this->breadth + b.breadth;
          box.height = this->height + b.height;
          return box;
       }
    };
    // 程序的主函数
    int main( )
    {
       Box Box1;                // 声明 Box1,类型为 Box
       Box Box2;                // 声明 Box2,类型为 Box
       Box Box3;                // 声明 Box3,类型为 Box
       double volume = 0.0;     // 把体积存储在该变量中
     
       // Box1 详述
       Box1.setLength(6.0); 
       Box1.setBreadth(7.0); 
       Box1.setHeight(5.0);
     
       // Box2 详述
       Box2.setLength(12.0); 
       Box2.setBreadth(13.0); 
       Box2.setHeight(10.0);
     
       // Box1 的体积
       volume = Box1.getVolume();
       cout << "Volume of Box1 : " << volume <<endl;
     
       // Box2 的体积
       volume = Box2.getVolume();
       cout << "Volume of Box2 : " << volume <<endl;
     
       // 把两个对象相加,得到 Box3
       Box3 = Box1 + Box2;
     
       // Box3 的体积
       volume = Box3.getVolume();
       cout << "Volume of Box3 : " << volume <<endl;
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Volume of Box1 : 210
    Volume of Box2 : 1560
    Volume of Box3 : 5400
    

    6.4 单目运算符重载

    下面的实例演示了如何重载一元减运算符( - )。

    #include <iostream>
    using namespace std;
     
    class Distance
    {
       private:
          int feet;             // 0 到无穷
          int inches;           // 0 到 12
       public:
          // 所需的构造函数
          Distance(){
             feet = 0;
             inches = 0;
          }
          Distance(int f, int i){
             feet = f;
             inches = i;
          }
          // 显示距离的方法
          void displayDistance()
          {
             cout << "F: " << feet << " I:" << inches <<endl;
          }
          // 重载负运算符( - )
          Distance operator- ()  
          {
             feet = -feet;
             inches = -inches;
             return Distance(feet, inches);
          }
    };
    int main()
    {
       Distance D1(11, 10), D2(-5, 11);
     
       -D1;                     // 取相反数
       D1.displayDistance();    // 距离 D1
     
       -D2;                     // 取相反数
       D2.displayDistance();    // 距离 D2
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    F: -11 I:-10
    F: 5 I:-11
    

    6.5 输入/输出运算符重载

    下面的实例演示了如何重载提取运算符 >> 和插入运算符 <<。

    #include <iostream>
    using namespace std;
     
    class Distance
    {
       private:
          int feet;             // 0 到无穷
          int inches;           // 0 到 12
       public:
          // 所需的构造函数
          Distance(){
             feet = 0;
             inches = 0;
          }
          Distance(int f, int i){
             feet = f;
             inches = i;
          }
          friend ostream &operator<<( ostream &output, 
                                           const Distance &D )
          { 
             output << "F : " << D.feet << " I : " << D.inches;
             return output;            
          }
     
          friend istream &operator>>( istream  &input, Distance &D )
          { 
             input >> D.feet >> D.inches;
             return input;            
          }
    };
    int main()
    {
       Distance D1(11, 10), D2(5, 11), D3;
     
       cout << "Enter the value of object : " << endl;
       cin >> D3;
       cout << "First Distance : " << D1 << endl;
       cout << "Second Distance :" << D2 << endl;
       cout << "Third Distance :" << D3 << endl;
     
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Enter the value of object :
    70
    10
    First Distance : F : 11 I : 10
    Second Distance :F : 5 I : 11
    Third Distance :F : 70 I : 10
    

    6.6 友元还是成员

    1. 一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函
      数定义为谁的友元,谁的成员问题。

    2. 一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左
      操作数)。

    3. 一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右
      操作数)。

    6.7 运算符重载提高

    6.7.1 赋值运算符重载(operator=)

    下面的实例演示了如何重载赋值运算符。

    #include <iostream>
    using namespace std;
     
    class Distance
    {
       private:
          int feet;             // 0 到无穷
          int inches;           // 0 到 12
       public:
          // 所需的构造函数
          Distance(){
             feet = 0;
             inches = 0;
          }
          Distance(int f, int i){
             feet = f;
             inches = i;
          }
          void operator=(const Distance &D )
          { 
             feet = D.feet;
             inches = D.inches;
          }
          // 显示距离的方法
          void displayDistance()
          {
             cout << "F: " << feet <<  " I:" <<  inches << endl;
          }
          
    };
    
    int main()
    {
       Distance D1(11, 10), D2(5, 11);
     
       cout << "First Distance : "; 
       D1.displayDistance();
       cout << "Second Distance :"; 
       D2.displayDistance();
     
       // 使用赋值运算符
       D1 = D2;
       cout << "First Distance :"; 
       D1.displayDistance();
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    First Distance : F: 11 I:10
    Second Distance :F: 5 I:11
    First Distance :F: 5 I:11
    

    6.7.2 数组下标运算符(operator[])

    下面的实例演示了如何重载数组下标运算符 []。

    #include <iostream>
    using namespace std;
    const int SIZE = 10;
     
    class safearay
    {
       private:
          int arr[SIZE];
       public:
          safearay() 
          {
             register int i;
             for(i = 0; i < SIZE; i++)
             {
               arr[i] = i;
             }
          }
          int& operator[](int i)
          {
              if( i > SIZE )
              {
                  cout << "索引超过最大值" <<endl; 
                  // 返回第一个元素
                  return arr[0];
              }
              return arr[i];
          }
    };
    int main()
    {
       safearay A;
     
       cout << "A[2] 的值为 : " << A[2] <<endl;
       cout << "A[5] 的值为 : " << A[5]<<endl;
       cout << "A[12] 的值为 : " << A[12]<<endl;
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    A[2] 的值为 : 2
    A[5] 的值为 : 5
    A[12] 的值为 : 索引超过最大值
    0
    

    6.7.3 函数调用运算符(operator ())

    函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。

    下面的实例演示了如何重载函数调用运算符 ()。

    #include <iostream>
    using namespace std;
     
    class Distance
    {
       private:
          int feet;             // 0 到无穷
          int inches;           // 0 到 12
       public:
          // 所需的构造函数
          Distance(){
             feet = 0;
             inches = 0;
          }
          Distance(int f, int i){
             feet = f;
             inches = i;
          }
          // 重载函数调用运算符
          Distance operator()(int a, int b, int c)
          {
             Distance D;
             // 进行随机计算
             D.feet = a + c + 10;
             D.inches = b + c + 100 ;
             return D;
          }
          // 显示距离的方法
          void displayDistance()
          {
             cout << "F: " << feet <<  " I:" <<  inches << endl;
          }
          
    };
    int main()
    {
       Distance D1(11, 10), D2;
    
       cout << "First Distance : "; 
       D1.displayDistance();
    
       D2 = D1(10, 10, 10); // invoke operator()
       cout << "Second Distance :"; 
       D2.displayDistance();
    
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    First Distance : F: 11 I:10
    Second Distance :F: 30 I:120
    
  • 相关阅读:
    windows下搭建solr 6.2.1服务器一
    redis 持久化
    weblogic 启动报错java.net.UnknownHostException
    Tomcat 容器lib下添加 wlfullclient.jar 包引起项目中javax servlet 的冲突
    java.lang.NoSuchMethodError: javax.servlet.ServletContext.getContextPath()Ljava/lang/String;
    spring mvc控制框架的流程及原理1: 总概及源码分析
    CentOS7安装iptables防火墙
    【Intellij IDEA】eclipse项目导入
    weblogic11g 安装参考地址
    解决“只能通过Chrome网上应用商店安装该程序”的方法
  • 原文地址:https://www.cnblogs.com/PikapBai/p/13205132.html
Copyright © 2011-2022 走看看