2016-04-10 21:39:53 ydxlt 阅读数 2191
  • 华为工程师,带你实战C++视频精讲

    这是一套全面而系统的C++学习:1、C++对C的全面提高(类型增强,函数重载,默认参数,引用,new/delete 内联函数,类型强转,命名空间,系统string类;2、封装,类与对象,友元,运算符重载;3、继承与派生,多态,UML,设计模式;5、文件IO流,模板,STL,,异常机制。

    58728 人正在学习 去看看 王桂林

前言:

 c++的类和java的类机制着实不大一样,不仅仅是语法,还包括一些特殊的东西,如c++用友元函数来破坏类的封装性,使得外界(友元函数体)可以访问类的私有属性,而java呢,java则可以通过反射机制类在类的外部访问类的私有属性,从而破坏类的封装性,而不仅这点,java中没有什么运算符重载,而c++中提供了运算符重载技术使得我们自定义的类型(类)也可以想基本数据类型一样进行的运算符(+,-,*,/,%,…)运算。

c++友元函数

 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

友元函数例子

#include <iostream>

using namespace std;

class Obj{
public:
    Obj(int a,char b){
        this->a = a;
        this->b = b;
    }

    // 友元函数,通过friend关键字声明
    friend void print(Obj obj);
private:
    int a;
    char b;
};

void print(Obj obj){
    cout << "a = " << obj.a << "  c=" << obj.b<<endl;
}

int main(){
    Obj obj(10,'h');

    // 类外部直接通过类名访问类属性,报错 error:'int obj::a' is private
    // cout << "a = " << obj.a << "  c=" << obj.b<<endl;

    // 类外部通过友元函数访问类私有成员
    print(obj);
}

上面的例子也可以这样写:

#include <iostream>

using namespace std;

class Obj{
public:
    Obj(int a,char b){
        this->a = a;
        this->b = b;
    }

    // 友元函数,通过friend关键字声明
    friend void print(Obj obj){
        cout << "a = " << obj.a << "  c=" << obj.b<<endl;
    }
private:
    int a;
    char b;
};


int main(){
    Obj obj(10,'h');

    // 类外部直接通过类名访问类属性,报错 error:'int obj::a' is private
    // cout << "a = " << obj.a << "  c=" << obj.b<<endl;

    // 类外部通过友元函数访问类私有成员
    print(obj);
}

即友元函数体也放在类里面,这里调用友元函数方式不变

这说明了友元函数虽然定义在类内部,但那只是表示这个函数是这个类的友元,这个函数还是一个全局函数,而不是类的成员函数。

c++友元类

 c++友元类和友元函数一样都是类的友元,定义形式也是通过friend关键字,如:

#include <iostream>

using namespace std;

class A{
public:
    A(int a,int b){
        this->a = a;
        this->b = b;
    }

    // 友元类,通过friend关键字声明
    friend class B;
private:
    int a;
    int b;
};

class B{
public:
    void printA(A obj){
        cout << "a = " << obj.a << "  b = " << obj.b <<endl;
    }
};


int main(){
    A obj(10,20);

    // 类外部通过友元访问类私有属性
    B b;
    b.printA(obj);

    // 类外部直接通过类名访问类属性,报错 error:'int obj::a' is private
    // cout << "a = " << obj.a << "  b=" << obj.b<<endl;
}

友元类和友元函数声明语句放在类中的那个位置都无所谓,也可以放在private修饰的域下面。

c++运算符重载

 C++提供运算符重载机制让我们重载 C++ 内置的运算符。这样,我们就可以使用自定义类型的运算符。重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

如:

#include <iostream>

using namespace std;

class Complex{
public:
    int real;
    int virt;

    Complex(int real,int virt){
        this->real = real;
        this->virt = virt;
    }

    Complex operator+(Complex const &c){
        return Complex(this->real+c.real,this->virt+c.virt);
    }

    // 前置++
    Complex& operator++(){
        this->real++;
        this->virt++;
        return *this;
    }

    // 后置++,用参数占位符区别于前置++
    Complex& operator++(int){
        Complex tmp(this->real,this->virt);
        this->real++;
        this->virt++;
        return tmp;
    }

    // << >>运算符必须用友元函数才能重载
    friend ostream& operator<<(ostream &out,Complex const &c){
        out << c.real << " + " << c.virt << "j" <<endl;
        return out;
    }
};

int main(){
    Complex c1(1,2),c2(3,5);

    Complex c3 = c1 + c2;

    cout << "c3:" << c3 <<endl;

    cout << "c1:" << c1 <<endl;

    ++c1;

    cout << "++c1:" << c1 <<endl;

    cout << "c2:" << c2 <<endl;

    Complex c4 = c2++;

    cout << "c2++:" << c2 <<endl;

    cout << "c4:" << c4 <<endl;

}

结果:

这里写图片描述

可以看到我们的自定义类型-复数类型通过运算符重载机制实现了基本的运算符运算,使得我们自定义类型可以按照我们自己规定的运算规则进行运算。

这里总结了对自定义类型进行运算符重载的思路:

  1. 首先我们要知道我们要让我们自定义类型进行什么运算符运算,如要实现+法运算;
  2. 直接写出要运算的表达式,如c3 = c1 + c2
  3. 分析这是一个二目运算符,所以方法中需要两个参数,但是由于在类中有this指针,所以我们就省去了一个参数,这里就只需要例外一个参数了,所以写出方法头为Complex operator+(Complex const &c),这里为什么确定返回值为一个Complex也是根据我们的需要而定的(因为我们需要支持连加,但加得到的结果是一个新的Complex,所以不需要再返回值上加引用&)
  4. 确定好函数头就写函数体了,函数体就是要按照我们定义的规则进行代码实现即可。

说明:

  1. 实现运算符重载的函数也可以像其他函数一样函数体和函数声明分离,即函数体写在类外部或者一个cpp文件里面。
  2. 对于<<和>>运算符,要实现重载,只能通过友元函数实现,同时,这也是友元函数的真正的运用场景。对于每必要使用友元函数的地方就不要使用友元函数,这样可以提高程序的效率,也能避免一些意想不到的错误(滥用友元函数)。
  3. 对于前置++和后置++运算符,后置++的参数要使用参数占位符来和前置++相区别。
  4. 大部分的运算符都支持运算符重载,也有一些运算符不可以重载(::,?:,.*)。
2015-07-10 11:27:53 GarfieldEr007 阅读数 2897
  • 华为工程师,带你实战C++视频精讲

    这是一套全面而系统的C++学习:1、C++对C的全面提高(类型增强,函数重载,默认参数,引用,new/delete 内联函数,类型强转,命名空间,系统string类;2、封装,类与对象,友元,运算符重载;3、继承与派生,多态,UML,设计模式;5、文件IO流,模板,STL,,异常机制。

    58728 人正在学习 去看看 王桂林

我们知道,C++中的运算符重载有两种形式:①重载为类的成员函数(见C++运算符重载(成员函数方式)),②重载为类的友元函数。

当重载友元函数时,将没有隐含的参数this指针。这样,对双目运算符,友元函数有2个参数,对单目运算符,友元函数有一个参数。但是,有些运行符不能重载为友元函数,它们是:=,(),[]和->。

重载为友元函数的运算符重载函数的定义格式如下:

friend 函数类型 operator 运算符(形参表) 
{ 
	函数体; 
} 

一、程序实例

//运算符重载:友元函数方式
#include <iostream.h>

class complex //复数类
{
public:
	complex(){ real = imag = 0;}
	complex(double r, double i)
	{
		real = r;
		imag = i;
	}
	friend complex operator + (const complex &c1, const complex &c2); //相比于成员函数方式,友元函数前面加friend,形参多一个,去掉类域
	friend complex operator - (const complex &c1, const complex &c2); //成员函数方式有隐含参数,友元函数方式无隐含参数
	friend complex operator * (const complex &c1, const complex &c2);
	friend complex operator / (const complex &c1, const complex &c2);

	friend void print(const complex &c); //友元函数

private:
	double real; //实部
	double imag; //虚部

};

complex operator + (const complex &c1, const complex &c2) 
{
	return complex(c1.real + c2.real, c1.imag + c2.imag);
}

complex operator - (const complex &c1, const complex &c2)
{
	return complex(c1.real - c2.real, c1.imag - c2.imag);
}

complex operator * (const complex &c1, const complex &c2)
{
	return complex(c1.real * c2.real - c1.imag * c2.imag, c1.real * c2.real + c1.imag * c2.imag);
}

complex operator / (const complex &c1, const complex &c2)
{
	return complex( (c1.real * c2.real + c1.imag * c2. imag) / (c2.real * c2.real + c2.imag * c2.imag), 
		(c1.imag * c2.real - c1.real * c2.imag) / (c2.real * c2.real + c2.imag * c2.imag) );
}

void print(const complex &c) 
{
	if(c.imag < 0)
		cout<<c.real<<c.imag<<'i'<<endl;
	else
		cout<<c.real<<'+'<<c.imag<<'i'<<endl;
}

int main()
{	
	complex c1(2.0, 3.5), c2(6.7, 9.8), c3;
	c3 = c1 + c2;
	cout<<"c1 + c2 = ";
	print(c3); //友元函数不是成员函数,只能采用普通函数调用方式,不能通过类的对象调用

	c3 = c1 - c2;
	cout<<"c1 - c2 = ";
	print(c3);

	c3 = c1 * c2;
	cout<<"c1 * c2 = ";
	print(c3);

	c3 = c1 / c2;
	cout<<"c1 / c2 = ";
	print(c3);
	return 0;
}

二、程序运行结果


从运行结果上我们就可以看出来,无论是通过成员函数方式还是采用友元函数方式,其实现的功能都是一样的,都是重载运算符,扩充其功能,使之能够应用于用户定义类型的计算中。

三、两种重载方式(成员函数方式与友元函数方式)的比较

一般说来,单目运算符最好被重载为成员;对双目运算符最好被重载为友元函数,双目运算符重载为友元函数比重载为成员函数更方便此,但是,有的双目运算符还是重载为成员函数为好,例如,赋值运算符。因为,它如果被重载为友元函数,将会出现与赋值语义不一致的地方。

2017-12-26 13:39:23 ayangya 阅读数 6392
  • 华为工程师,带你实战C++视频精讲

    这是一套全面而系统的C++学习:1、C++对C的全面提高(类型增强,函数重载,默认参数,引用,new/delete 内联函数,类型强转,命名空间,系统string类;2、封装,类与对象,友元,运算符重载;3、继承与派生,多态,UML,设计模式;5、文件IO流,模板,STL,,异常机制。

    58728 人正在学习 去看看 王桂林

1、对双目运算符而言,成员运算符重载函数参数列表中含有一个参数,而友元运算符重载函数参数列表含有两个参数;对单目运算符而言,成员运算符重载函数参数列表中没有参数,而友元运算符重载函数参数列表含有一个参数。
2、双目运算符一班可以被重载为友元运算符和成员函数运算符,但是当一个整数与一个复数相加时,必须使用友元函数。
例:友元运算符重载函数实现一个复数与一个整数相加

#include<iostream>
using namespace std;
class complex
{
    public:
        complex(int r=0,int i=0);
        friend complex operator+(complex com,int a)//定义友元运算符重载函数,+左侧是类对象,右侧是函数 
        {
            return complex(com.real+a,com.imag);
        }
        friend complex operator+(int a,complex com)//定义友元运算符重载函数,+左侧是函数,右侧是类对象
        {
            return complex(a+com.real,com.imag);
        }
        void show();
        private:
            int real,imag;
};

void complex::show()
{
    cout<<"real="<<real<<" imag="<<imag<<endl;
}

complex::complex(int r,int i)
{
    real=r;
    imag=i;
}
int main()
{
    complex com1(12,34),com2,com3;
    com2=com1+27;
    com2.show();
    com3=12+com1;
    com3.show();
    return 0;
}

结果:
real=39 imag=34
real=24 imag=34
如果有成员函数运算符,整数不是类的对象
com2=com1+27,则编译为com2=com1.operator(100)
com2=12+com1,则编译为com2=100.operator(com1),错误,100无法调用成员运算符重载函数。
所以,当一个整数与一个复数相加时,只能使用友元运算符重载函数

运算符函数调用形式
习惯调用形式 友元 成员函数
a+b operator+(a,b) a.operator+(b)
-a operator-(a) a.operator-()
a++ operator++(a,0) a.operator++(0)
–a operator–(a) a.operator–();

使用成员函数以前缀和后缀方式重载运算符–

#include<iostream>
using namespace std;
class three
{
    public:
        three(int a=0,int b=0,int c=0);
        void print();
        three operator--();//声明自减运算符--重载成员函数(前缀方式) 
        three operator--(int);//声明自减运算符--重载成员函数(后缀方式)
        private:
        int x,y,z; 
};

three::three(int a,int b,int c)
{
    x=a;
    y=b;
    z=c;
}

void three::print()
{
    cout<<"x="<<x<<" y="<<y<<" z="<<z<<endl;
}

three three::operator--()
{
    --x;
    --y;
    --z;
    return *this;//返回自减后的当前对象 
 } 
three three::operator--(int)
{
    three temp(*this); 
    x--;
    y--;
    z--;
    return temp;
}

int main()
{
    three obj1(4,5,6),obj2,obj3(7,8,9),obj4;
    obj1.print();
    --obj1;
    obj1.print();
    obj2=obj1--;//obj2保存的是执行obj1--前的obj1值 
    obj2.print();
    obj1.print();
    cout<<endl;
    obj3.print();
    obj3.operator--();
    obj3.print();
    obj4=obj3.operator--(0);
    obj4.print();
    obj3.print();
    return 0;
}

结果:
x=4 y=5 z=6
x=3 y=4 z=5
x=3 y=4 z=5
x=2 y=3 z=4

x=7 y=8 z=9
x=6 y=7 z=8
x=6 y=7 z=8
x=5 y=6 z=7

重载后缀自增运算符时,多了一个int型参数,这个参数只是为了与前缀++运算符重载函数有所区别,定义时可以只写int

使用友元函数以前缀方式和后缀方式重载运算符++

#include<iostream>
using namespace std;
class three
{
    public:
        three(int a=0,int b=0,int c=0);
        void print();
        friend three operator++(three &op);//声明自加运算符++重载友元函数(前缀方式) 
        friend three operator++(three &op,int);//声明自加运算符++重载友元函数(后缀方式) 
        private:
            int x,y,z;
 };

 three::three(int a,int b,int c)
 {
    x=a;
    y=b;
    z=c;
 }

 void three::print()
 {
    cout<<"x="<<x<<" y="<<y<<" z="<<z<<endl;
 }

three operator++(three &op)
{
    ++op.x;
    ++op.y;
    ++op.z;
    return op;
}

three operator++(three &op,int)
{
    op.x++;
    op.y++;
    op.z++;
    return op;
}
int main()
{
    three obj1(4,5,6),obj2(7,8,9);
    obj1.print();
    ++obj1;
    obj1.print();
    obj1++;
    obj1.print();
    cout<<endl;
    obj2.print();
    operator++(obj2);//显示调用 
    obj2.print();
    operator++(obj2,0);
    obj2.print();
    return 0;

}

结果:
x=4 y=5 z=6
x=5 y=6 z=7
x=6 y=7 z=8

x=7 y=8 z=9
x=8 y=9 z=10
x=9 y=10 z=11

友元运算符重载函数没有this指针,所以不能用this指针所指的对象,应采用对象引用参数传递数据

2019-04-28 20:28:17 pix_csdn 阅读数 36
  • 华为工程师,带你实战C++视频精讲

    这是一套全面而系统的C++学习:1、C++对C的全面提高(类型增强,函数重载,默认参数,引用,new/delete 内联函数,类型强转,命名空间,系统string类;2、封装,类与对象,友元,运算符重载;3、继承与派生,多态,UML,设计模式;5、文件IO流,模板,STL,,异常机制。

    58728 人正在学习 去看看 王桂林

主要介绍以下几点:

  • 类友元函数的创建方式;
  • 运算符重载的限制;
  • 如何重载运算符(以 + 为例);
  • 运算符重载与友元函数;
  • ( << )运算符的重载;

类的友元函数

通常来说,外部实现对类私有成员的访问,只能通过类的公有方法,然而友元提供了另一用方法。

  • 友元函数

  • 友元类

  • 友元成员函数

    创建类的友元

class A
{
	private:
		...
	public:
		...
		friend void func(...);
		//注意,和类的const成员函数不一样,
		//友元函数属于正常的函数,不能声明成const型
		//friend void func(...) const;
};

//func不是类的成员,定义时不需要A::func()
//func定义时无需加friend前缀
	void func(...){
		...
	}

定义友元函数时需要注意以下两点:

 - 无需(类名::函数名)的形式去定义,因为友元不是类的成员;
 - 定义时无需加friend前缀;

所以,也不能用成员运算符调用友元函数

那么问题就来了,都有类的公有方法了,为什么多此一举搞一个友元函数?
在运算符重载那一部分,会详细介绍友元函数的具体用法。

重载运算符的限制

  • 重载后的运算符,必须要有一个操作数是用户定义的类型(防止用户为标准类型重载运算符);
  • 重载运算符不能违反运算符原有的语法(不能将 + 重载成 - ,将 * 重载成 /);
  • 不能创建新的运算符;
  • 一些特殊运算符不能重载;

不能重载的运算符如下:

	sizeof			:	计算空间运算
	.				:	成员运算
	.*				:	成员指针运算
	::				:	作用域运算
	?:				:	条件运算
	typeid			:	RTTI运算符
	四个强制类型转换运算符

仅能通过成员函数进行重载的运算符(重载的函数必须是类的成员函数)

	=			:	赋值运算
	()			:	函数调用运算
	[]			:	下标运算
	->			:	通过指针访问类成员运算

运算符重载

假设我有一个类Vector,类Vector表示xoy平面的向量(Vector仅有两个私有变量x和y)

class Vector{
privatr:
	double x;
	double y;
public:
	//构造函数与析构函数,具体内容就不写了
	Vector();
	Vector(double xx, double yy);
	~Vector();
};

Vector a = {1.0, 2.0}, b = {1.5, 2.5};

上面用Vector创建了两个向量a和b,现在我想做向量的加法,使:

Vector c = a + b;

此时就需要重载“+”运算符。
具体重载方法如下:

class Vector{
...
public:
	Vector & operator+(const Vector v) const;
}

Vector & Vector::operator+(const Vector v) const {
	Vector c;
	c.x = x + v.x;
	c.y = y + v.y;
	return c;
}

此时需要注意,c = a + b重载运算符“+”,使用的是运算符函数,其实际形式是

 // c = a + b与下面的形式等价:
  c = a.operator+( b );

所以该函数,隐式的使用a(因为调用了它的方法),显式的使用对象b(被作为参数传递);
同理,乘法运算 c = a * 2.0也可以实现,只需将参数的形式变化一下即可。
那么,如果想要算 c = 2.0 * a 呢? 2.0只是一个double常量,它怎么调用“它的方法”呢?
这里就需要使用上面的友元函数了。
因为友元函数不是类的成员函数,非成员函数不是由对象调用的,它所有的参数都是显式参数,所以, c = 2.0 * a如果调用非成员函数的运算符重载,则编程下面这种形式:

c = operator* (2.0, a);

所以重载“*”运算需要定义两种重载函数:

class Vector{
	private:
	...
	public:
		...
		Vector & operator*(const Vector v) const;
		friend Vector & operator*(double n, const Vector v);
		//或者将友元换成下面这种成员函数也可以
		Vector & operator*(double n, const Vector v) const {
			//此处实际是调用第一个函数
			return v * n;
		}
}

重载 <<

如果我们用下面这个成员函数重载<<,运行下列指令会发生什么?

void operator<<(std::ostream & os, const Vector &v);
...
Vector a = {1.0, 2.0};
std::cout << a << "a" << a << "a" << std::endl;

很明显,cout << a后返回的是void,再执行 void << "a"会报错。此时,我们需要按照下面这种方式重载<<.

std::ostream & operator<<(std::ostream & os, const Vector &v);
...
Vector a = {1.0, 2.0};
std::cout << a << "a" << a << "a" << std::endl;

这样,cout << a后返回的还是一个cout,再执行 cout << “a” ……

2018-05-26 01:17:34 sdau20171755 阅读数 444
  • 华为工程师,带你实战C++视频精讲

    这是一套全面而系统的C++学习:1、C++对C的全面提高(类型增强,函数重载,默认参数,引用,new/delete 内联函数,类型强转,命名空间,系统string类;2、封装,类与对象,友元,运算符重载;3、继承与派生,多态,UML,设计模式;5、文件IO流,模板,STL,,异常机制。

    58728 人正在学习 去看看 王桂林

C++函数之运算符重载

一、运算符重载的几个概念

1、运算符重载使得用户自定义的数据以一种更简洁的方式工作。

2、C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。

3、运算符函数可以重载为成员函数或友元函数

二、运算符重载结构

<返回类型说明符> operator <运算符符号>(<参数表>)  
	{  
	  
	     <函数体>  
	  
	}  

三、运算符重载的规则

1、重载后的运算符的操作对象必须至少有一个是用户定义的类型
2、重载后的运算符的操作对象必须至少有一个是用户定义的类型
3、不能创建一个新的运算符

四 、重载运算符的限制

1、不能重载的算符             

.        ::       .*      ?:      sizeof

2、可以重载的运算符

+       -        *       /       %      ^       &      |       ~

!        =       <       >       +=     -=      *=     /=     %

^=     &=    |=     <<     >>     >>=  <<=  ==     !=

<=     >=     &&   ||     ++     --       ->*   ‘        ->

[]      ()       new delete      new[]       delete[] 

五、双目运算符的重载

      对双目运算符而言,成员运算符函数的形参表中仅有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含地传递给函数的。

#include <iostream.h>
class Complex
{
public:
Complex( ) {real=0,imag=0;}
Complex(double r,double i) {real=r; imag=i;}
Complex operator + (Complex &c2);
void display( );

六、单目运算符的重载

      对单目运算符而言,成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。

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;
};




c++ 运算符重载之成员函数重载

博文 来自: Sico2Sico
没有更多推荐了,返回首页