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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • C++基础2:类与对象 1. 认识类与对象 什么是类(class)?类(class)是类型(type),是用户自定...
    jdzhangxin阅读 2,254评论 0 7
  • C++运算符重载-下篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 1,436评论 0 49
  • C++运算符重载-上篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 2,292评论 0 51
  • 3. 类设计者工具 3.1 拷贝控制 五种函数拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数拷贝和移...
    王侦阅读 1,797评论 0 1
  • 著名的教育学家苏格拉底曾经说过说:“当我们为奢侈的生活而疲于奔波的时候,幸福和生活已经离我们越来越远了。做人要知...
    修文039王玉会阅读 260评论 0 0