c++友元函数及运算符重载

前言:

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

c++友元函数

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

友元函数例子

#include 

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<int main(){
    Obj obj(10,'h');

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

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

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

#include 

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<private:
    int a;
    char b;
};


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

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

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

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

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

c++友元类

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

#include 

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 <int main(){
    A obj(10,20);

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

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

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

c++运算符重载

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

如:

#include 

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" <return out;
    }
};

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

    Complex c3 = c1 + c2;

    cout << "c3:" << c3 <cout << "c1:" << c1 <cout << "++c1:" << c1 <cout << "c2:" << c2 <cout << "c2++:" << c2 <cout << "c4:" << c4 <

结果:

c++友元函数及运算符重载_第1张图片

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

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

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

说明:

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

你可能感兴趣的:(C/C++)