C++基础 | 类和对象----友元和重载

友元

友元的作用是提高了程 序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装 性和隐藏性,使得非成员函 数可以访问类的私有成员。

友元函数是可以直接访问类的私有成员的非成员函数。需要在类的定义中加以声明,声明时只需在友元 的名称前加上 关键字 friend
即可以全局函数作友元函数,也可以类成员函数作友元函数

全局函数作友元函数:
class Point {
    public:
        Point(double xx, double yy)
        {
            x = xx;
            y = yy; 
        }
        void Getxy();
        friend double Distance(Point &a, Point &b);  
     private:
        double x, y;
  };

double Distance(Point &a, Point &b)
{
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
}

类成员函数作友元函数:
class Point;
//前向声明,是一种不完全型声明,即只需提供类名( 需提供类实现)即可。仅可用于声明指针和引用 。
class ManagerPoint
{
    public:
        double Distance(Point &a, Point &b);
};

class Point
{
public:
    Point(double xx, double yy)
    {
        x = xx;
        y = yy; 
    }
      void Getxy();
       friend double ManagerPoint::Distance(Point &a, Point &b);
private:
    double x, y;
};

double ManagerPoint::Distance(Point &a, Point &b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
}

友元对象

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

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

注意事项
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一 定 是类 A 的友元,同样要看类中是否有相应的声明。

重载

C++中const用于函数重载

先来回忆下常成员函数:
声明:<类型标志符>函数名(参数表)const;

说明:

(1)const是函数类型的一部分,在实现部分也要带该关键字。
(2)const关键字可以用于对重载函数的区分。
(3)常成员函数不能更新类的成员变量,也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。
(4)非常量对象也可以调用常成员函数,但是如果有重载的非常成员函数则会调用非常成员函数。
(来自:https://www.cnblogs.com/qingergege/p/7609533.html

bool operator <(const student &a ) const{     
        if(score!=a.score) return score<a.score;
        else if(name.compare(a.name)!=0) return name<a.name;
        else if(age!=a.age)return age<a.age;
        else
            return false;
    }

加const是因为:
我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。函数加上const后缀的作用是表明函数本身不会修改类成员变量。
加上const,对于const的和非const的实参,函数就能接受;如果不加,就只能接受非const的实参。

用&是因为:
这样可以避免在函数调用时对实参的一次拷贝,提高了效率。

如下:

#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(float x=0, float y=0) :_x(x),_y(y){}
    const void dis() {
        cout<<"("<<_x<<","<<_y<<")"<<endl;
}
//    friend  Complex operator+ ( Complex &c1, Complex &c2);

    const Complex operator+(const Complex &another) const{     //可以c3=c1+c2+c2; c1+c2后返回的是匿名对象
        Complex temp(this->_x+another._x,this->_y+another._y);
        return temp;
    }

private:
    float _x;
    float _y; 
};
/*
Complex operator+ ( Complex &c1, Complex &c2) {    //不可以c3=c1+c2+c2;只可以二元
    Complex temp(c1._x - c2._x,c1._y - c2._y);
    return temp;
}
*/

int main() {
    Complex c1(1,2);
    Complex c2(3,4);
    Complex c3(4,5);
    c1.dis();
    c2.dis();
    Complex c4;
    c4= c1+c2+c2;
    //Complex c3 = operator+(c1,c2);
c4.dis();
return 0; 
}
重载规则

(1)C++不允许用户自己定义新的运算符,只能对已有的 C++运算符进行重载。
(2)重载不能改变运算符运算对象(即操作数)的个数。
(3)重载不能改变运算符的优先级别。
(4)重载不能改变运算符的结合性。
如,复制运算符”=“是右结合性(自右至左),重载后仍为右结合性。
(5)重载运算符的函数不能有默认的参数
否则就改变了运算符参数的个数,与前面第(3)点矛盾。
(6)重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有 一 个是类对象(或类对象的引用)。

也就是说,参数不能全部是 C++的标准类型,以防止用户修改用于标准类型数据 成 员的运算符的性质,如下面这样是不对的:
复制代码 代码如下:
int operator + (int a,int b) { return(a-b); }
原来运算符+的作用是对两个数相加,现在企图通过重载使它的作用改为两个数 相 减。如果允许这样重载的话,如果有表达式 4+3,它的结果是 7 还是 1 呢?显然,这
是 绝对要禁止的。

(7)用于类对象的运算符一般必须重载,但有两个例外,运算符”=“和运算 符”&“不 必用户重载。
(8)应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现 的功 能。
(9)运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非 类 的成员函数也不是友元函数的普通函数
(10)不能重载的操作符:
. 成员选择符 .*成员对象选择 ::域解析操作符 ?:三目运算符

.* 例如:
class A{
char p;
}
A a
a.
p===*(a.p) 基本不用,不管也行

双目运算符重载
//使 : L#R 
operator#(L,R);
 //全局函数 L.operator#(R); 
//成员函数

class Complex
{
public:
    Complex(float x=0, float y=0) :_x(x),_y(y){}
void dis() {
        cout<<"("<<_x<<","<<_y<<")"<<endl;
    }
    Complex& operator+=(const Complex &c)
    {
        this->_x += c._x; 
        this->_y += c._y;
        return * this;
    }
//  friend Complex& operator+=(Complex &c1,const Complex &c2)
private:
        float _x;
        float _y;
};
/*
   Complex& operator+=(Complex &c1,const Complex &c2)
  {
      c1._x=c1._x+c2._x;
      c1._y=c1._y+c2._y;
      return c1;
  }
*/
单目运算符

用++a(前加加,可以++++a)和a++(后加加,不可以a++++),用a++来举例

//使用: #M 或者 M#
operator#(M); //全局函数 
M.operator#() //成员函数

class Complex
{
    public:
        Complex(float x=0, float y=0):_x(x),_y(y){}
void dis() {
        cout<<"("<<_x<<","<<_y<<")"<<endl;
    }
#if 0
    const Complex operator++(int)
    {
        Complex t = *this; _x++;
        _y++;
        return t;
} #endif
    friend const Complex operator++(Complex &c,int); 
 //这里用占位符来区分a++和++a,加了占位符的是a++;
                                                              
private:
    float _x;
    float _y;
 };
const Complex operator++(Complex &c,int)
{
    Complex t(c._x,c._y); 
    c._x++;
    c._y++;
    return t;
}
左移右移操作符重载

<<和>>只能写在全局,不能够写在成员方法中,否则调用的顺序会变反,c1<<out

istream & operator>>(istream &, 定义类&);
ostream & operator<<(ostream &, 定义类&);

class Complex {
public:
    Complex(float x=0, float y=0)
        :_x(x),_y(y){}
    void dis() {
        cout<<"("<<_x<<","<<_y<<")"<<endl;
    }
    friend ostream & operator<<(ostream &os, const Complex & c);
    friend istream & operator>>(istream &is, Complex &c);
private:
    float _x;
    float _y;
 };

ostream & operator<<(ostream &os, const Complex & c)
{
    os<<"("<<c._x<<","<<c._y<<")";
    return os;
}
istream & operator>>(istream &is, Complex &c)
{
   is>>c._x>>c._y;
    return is;
}

结论:
1,一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函 数定义为谁的友元,谁的成员问题。
2,一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左 操作数)。
3,一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右 操作数)。

赋值运算符重载 (operator=)

用一个己有对象,给另外一个己有对象赋值。两个对象均己创建结束后,发生的赋值行为。

class A {
A& operator=(const A& another)
{
//函数体
return *this;
}
};

规则
1 系统提供默认的赋值运算符重载,一经实现,不复存在。
2 系统提供的也是等位拷贝,也就浅拷贝,一个内存泄漏,重析构。 3 要实再深深的赋值,必须自定义。
4 自定义面临的问题有三个:
1,自赋值
2,内存泄漏 (将原有堆释放,再重新new)
3,重析构。(即浅拷贝引发的问题)
(所以上面函数体內要注意到这三个方面)
5 返回引用,且不能用 const 修饰。其目的是实现连等式。

成员函数
Student& operator=(const Student &another)
{
if(this==&another){ //防止自身赋值
return *this;
}
//先将自身的额外空间回收
if(this->name!=NULL){
delete[] this->name;
this->name=NULL;
this->id=0;
}
//执行深拷贝
this->id=another.id;
int len=strlen(another.name);
this->name=new char[len+1];
strcpy(this->name,another.name);
return *this;
}

数组下标运算符 (operator[]) 自定义数组
class vector
{
  public:
      vector(int n){ 
          v=new int[n];
          size=n;
      }
       ~vector() { delect[] v;size=0;}
      int & operator[] (int i) {return v[i];}
  private:
    int *v;
    int size;
};
函数调用符号 (operator () ) 仿函数

把类对象像函数名一样使用。
仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类 中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。

class 类名 {
返值类型 operator()(参数类型) 函数体
}

#include <iostream>
using namespace std;
class Sqr
{
    public:
        int operator()(int i)
        {
};
return i*i; }
double operator ()(double d)
{
return d*d; }
int main() {
    Sqr sqr;
    int i = sqr(4);
    double d = sqr(5.5);
    cout<<i<<endl;
    cout<<d<<endl;
return 0; }
不建议重载&&和||操作符
#include <iostream>
using namespace std;
class Test
{
public:
    Test(int i = 0)
    {
this->i = i; }
    Test operator+ (const Test& obj)
    {
cout<<"执行+号重载函数"<<endl; Test ret;
ret.i = i + obj.i;
return ret;
}
    bool operator&&(const Test& obj)
    {
cout<<"执 &&重载函数"<<endl;
        return i && obj.i;
    }
private:
    int i;
};
int main() {
    int a1 = 0;
    int a2 = 1;
cout<<"注意:&&操作符的结合顺序是从左向右"<<endl;
    if( a1 && (a1 + a2) )
    {
cout<<"有一个是假,则不在执 下 个表达式的计算"<<endl; }
    Test t1(0);
    Test t2(1);
    if ( t1 && (t1 + t2) )
    {
    //t1 && t1.operator(t2)
    // t1.operator&&( t1.operator+(t2) ) 
    cout<<"两个函数都被执行了, 且是先执行了+"<<endl;
    }
  return 0;
}

C++如果重载&&或者|| 将无法实现短路规则

new和delete操作符的重载
#include <iostream>
using namespace std;
class A{
public:
    A(){
        cout<<"A()..."<<endl;
    }
    A(int a){
        cout<<"A(int)"<<endl;
        this->a=a;
    }
    //重载了new操作符,依然会触发对象的构造函数
    void* operator new (size_t size)   //void*是万能指针,和void完全没关系
    {
        cout<<"重载了new操作符"<<endl;
        return malloc(size);
    }

    void operator delete(void *p){
        cout<<"重载了delete操作符"<<endl;
        if(p!=NULL){
            free(p);
            p=NULL;
        }
    }

    void *operator new[](size_t size){
        cout<<"重载了new[]操作符"<<endl;
        return malloc(size);
    }

    void operator delete[](void *p){
        cout<<"重载了delete[]操作符"<<endl;
        if(p!=NULL){
            free(p);
            p=NULL;
        }
    }
    ~A(){
        cout<<"~A()..."<<endl;
    }

private:
    int a;
};


int main() {

    A *ap=new A(10);
    //ap->operator new(sizeof(A));
    A *ap2=new A[20];
    //ap2->operator new[](sizeof(A[20]));
    delete ap;
    delete[] ap2;
    return 0; 
}

智能指针和自定义指针

智能指针

常规意义上讲,new 或是 malloc 出来的堆上的空间,都需要手动 delete 和 free 的。但在其它高级语言中,只需申请无需释放的功能是存在的。
c++中也提供了这样的机制。我们先来探究一下实现原理。

#include <memory>
class A
{ public:
    A() {
        cout<<"A constructor"<<endl;
           } 
    ~A() {
        cout<<"A destructor"<<endl;
    }
    void dis() {
        cout <<"class A's dis() " <<endl;
    }
 };
int main() {
//使用智能指针 auto_ptr 
auto_ptr<A> p (new A);
p->dis();
return 0; 
}
自定义智能指针
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A() {
        cout<<"A constructor"<<endl;
    }
~A() {
        cout<<"A destructor"<<endl;
    }
void dis() {
        cout<<"in class A's dis"<<endl;
    }
};
class PMA    //自定义A的智能指针
{
public:
    PMA(A *p) :_p(p){}
    ~PMA() {
      if(_p!=NULL){
      delete _p;  //触发A的析构
      _p=NULL;
      }
     }

    A& operator*()  //重载了*
    {
          return *_p; 
    }
    A* operator->()   //重载了->
    {
          return _p; 
    }
private:
    A * _p;
};

int main(){
  PMA p(new A);
  p->dis();    //p._p->func()
  (*p).dis();    //*_p.dis();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值