C++封装思想之二:友元机制和运算符重载(1W字详解)

目录

友元机制和运算符重载

友元机制

友元函数

友元的作用

友元类

前置声明 

友元类的注意事项 

友元成员函数(类的某个成员函数 作为另一个类的友元)

运算符重载

运算符重载的作用

运算符重载的注意事项

运算符重载的实现

成员函数重载

友元函数重载

运算符重载规则 

重载“=”运算符

重载"<"、">"运算符

重载"++"运算符

先自加再使用

先使用后自加

重载"+"运算符

重载"[ ]"运算符

重载"->"运算符

流运算符重载

重载函数调用运算符"()"(了解)

类型转换运算符(详解)

以bool类型转换作为引入

类型转换运算符详解

补充(内置类)

实例(内置类实现C++链表封装)


友元机制和运算符重载

友元机制

友元函数

比如下面这种情况:在func中每次打印num都需要调用get_num函数,函数的调用和销毁浪费了大量的运行时间(即函数不是类的一部分,但又需要频繁地访问类的数据成员)

因此C++提出了友元的概念

什么样的函数为友元函数?

函数不是类的一部分,但又需要频繁地访问类的数据成员

友元的作用

友元的作用:类的非成员函数可以直接访问类的非公有成员变量,省去函数的调用和访回过程。从而提高了程序的运行效率(即减少了类型和安全性检查及调用的时间开销

友元类

前置声明 

前置声明:只是单纯声明有这个类,但是不知道这个类的具体构造;

只可以利用类型名声明指针和引用变量,不能实例并访问类的内部构造;

若需要利用指针或引用调用前置类型的接口,必须按照声明和实现分离的方式进行编码。(后置实现)

注意:注意类声明的顺序;

友元类的注意事项 

 

友元成员函数(类的某个成员函数 作为另一个类的友元)

友元成员函数:即让当前类的某个函数成为另一个类的友元函数,这样就可以在当前类的这个函数中访问另一个类的私有成员

注意事项:通过前置声明使用类的方法必须在前置类定义之后初始化,不能互为友元成员函数

 class Room;//向前声明 只能说明类名称
 class goodGay
 {
     public:
     void visiting01(Room &room);
     void visiting02(Room &room);
 };

 class Room
 {
     friend void goodGay::visiting02(Room &room);
     private:
         string bedRoom;//卧室
     public:
         string setingRoom;//客厅
     public:
     Room(string bedRoom, string setingRoom)
     {
         this‐>bedRoom = bedRoom;
         this‐>setingRoom = setingRoom;
     }
 };
int main(int argc, char *argv[])
 {
     Room room("吴维的卧室","吴维的客厅");
     goodGay ob;
     ob.visiting01(room);
     ob.visiting02(room);
     return 0;
 }

 void goodGay::visiting01(Room &room)
 {
     cout<<"翰文访问了"<<room.setingRoom<<endl;
     //cout<<"翰文访问了"<<room.bedRoom<<endl;
 }

 void goodGay::visiting02(Room &room)
 {
     cout<<"好基友张三访问了"<<room.setingRoom<<endl;
     cout<<"好基友张三访问了"<<room.bedRoom<<endl;
 }

运算符重载

可以通过函数参数的不同可以实现一个运算符任意方法的重载

运算符重载的作用

  • 可以提高程序的可读性
  • 体现了C++的可扩充性
  • 运算符重载仅仅只是语法上的方便,它是另一种函数调用的方式
  • 运算符重载,本质上是函数重载

运算符重载的注意事项

不要滥用重载、因为它只是语法上的方便,所以只有在涉及的代码更容易写、尤其是更易读时才有必要重载

运算符重载的实现

成员函数重载

友元函数重载

运算符重载规则 

  • 运算符重载不允许发明新的运算符
  • 不能改变运算符操作对象的个数
  • 运算符被重载后,其优先级和结合性不会改变
  • 不能重载的运算符

  • 成员函数重载和友元函数重载的选择
    • 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。
    • 以下一些双目运算符不能重载为类的友元函数:=、()、[ ]、->
    • 类型转换运算符只能以成员函数方式重载
    • 流运算符只能以友元的方式重载

重载“=”运算符

MyString &MyString::operator=(MyString ob)
{
    //str2 = str1;
    if(this->str != NULL)
    {
        delete [] this->str;
        this->str = NULL;
    }

    this->size = ob.size;
    this->str = new char[this->size+1];
    memset(this->str, 0, this->size+1);
    strcpy(this->str, ob.str);

    return *this;
}

MyString &MyString::operator=(char *str)
{
    //str2 = str1;
    if(this->str != NULL)
    {
        delete [] this->str;
        this->str = NULL;
    }

    this->size = strlen(str);
    this->str = new char[this->size+1];
    memset(this->str, 0, this->size+1);
    strcpy(this->str, str);

    return *this;
}

 类的赋值运算符 “=” 只能重载为成员函数,而不能把它重载为友元函数,具体原因见:赋值运算符"="的重载https://blog.csdn.net/aaqian1/article/details/86423858

重载"<"、">"运算符

bool MyString::operator>(MyString ob)
{
    if(str==NULL || ob.str == NULL)
    {
        exit(-1);
    }
    if(strcmp(this->str, ob.str) > 0)
    {
        return true;
    }
    return false;
}

bool MyString::operator>(char *str)
{
    if(this->str==NULL || str == NULL)
    {
        exit(-1);
    }
    if(strcmp(this->str, str) > 0)
    {
        return true;
    }
    return false;
}

bool MyString::operator<(char *str)
{
    if(this->str==NULL || str == NULL)
    {
        _Exit(-1);
    }
    if(strcmp(this->str,str)<0)
    {
        return true;
    }
    return false;
}

bool operator<(const MyString &ob1, const MyString &ob2)
{
    if(ob1.str==NULL || ob2.str == NULL)
    {
        _Exit(-1);
    }
    if(strcmp(ob1.str,ob2.str)<0)
    {
        return true;
    }
    return false;
}

运行结果:

重载"++"运算符

实例:Integer类(友元和成员函数重载方式)

先自加再使用

成员函数重载

友元函数重载

先使用后自加

成员函数重载

友元函数重载

重载"+"运算符

MyString operator+(const MyString &ob1,const MyString &ob2)
{
    MyString tmp;
    tmp.size = ob1.size+ob2.size;
    tmp.str = new char[tmp.size+1];
    memset(tmp.str, 0, tmp.size+1);

    strcpy(tmp.str, ob1.str);
    strcat(tmp.str, ob2.str);

    return tmp;
}

MyString MyString::operator+(char *str)
{
    MyString tmp;
    tmp.size = size+strlen(str);
    tmp.str = new char[tmp.size+1];
    memset(tmp.str, 0, tmp.size+1);

    strcpy(tmp.str, this->str);
    strcat(tmp.str, str);

    return tmp;
}

重载"[ ]"运算符

char& MyString::operator[](int pos)
{
    if(pos<0 || pos>=size)
    {
        cout<<"元素位置不合法"<<endl;
        exit(-1);
    }

    return str[pos];
}

重载"->"运算符

->重载实现访问类中内置成员类里的某个成员

如下所示LinkIterator类和Node类同属于Link类的内置类,在main函数中实例化一个Link类对象指针,如果没有重载"->",对象指针将无法访问Node类中的私有成员m_num和next。但是由于Link类中有一个Node类的成员对象指针m_p。因此通过在Link类中重载"->",返回m_p,那么在main函数中的Link类对象指针就可以通过"->"访问到m_p中的m_num和next。

流运算符重载

如果采用成员函数的方法,由于是cout的成员,因此不能当前在类中实现重载。所以只能采用第二种方法。

流运算符>为内部类,不能通过类内成员函数重载的方法来实现,只能通过友元函数的方法来实现。

//全局函数实现 <<重载
ostream& operator<<(ostream &out, MyString ob)
{
    out<<ob.str;
    return out;
}
//全局函数实现 >>重载
istream& operator>>(istream &in, MyString &ob)
{
    char buf[1024]="";
    cin>>buf;

    if(ob.str != NULL)//ob已经有字符串
    {
        delete [] ob.str;
        ob.str = NULL;
    }

    ob.size = strlen(buf);
    ob.str = new char[ob.size+1];
    memset(ob.str, 0,ob.size+1);
    strcpy(ob.str, buf);

    return in;
}

重载函数调用运算符"()"(了解)

 重载()运算符 一般用于 为算法 提供策略。

以重载()用于输出信息为例:

类型转换运算符(详解)

以bool类型转换作为引入

在MyString类中定义了bool类型转换函数,在main函数中实例化MyString对象的时候会隐式地将MyString类型转到bool的类型。

PS:类型的true还是false由bool类型转换构造函数中的具体返回值来确定

如果str1中的str不为空,则打印str中的内容:

输出结果如下:

类型转换运算符详解

类型转换运算符(conversion operator)是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式是:

operator type() const;

        其中,type表示转换目标类型,除了void、数组或函数类型,但允许转换成指针(包括数组指针及函数指针)。类型转换运算符既没有显式的返回类型,也没有形参,而且必须定义为类的成员函数。类型转换运算符通常不应该改变待转换对象的内容,因此,类型转换运算符一般被定义成const成员。

        转换构造函数和类型转换运算符,有时也被称作用户定义的类型转换(use-defined conversions)。

        定义含有类型转换运算符的类

        举一个简单的例子,令其表示0到255之间的一个整数:

class SmallInt
{
public:
    SmallInt(int i = 0) :val(i)
    {
        if (i < 0 || i>255)
        {
            throw out_of_range("Bad SmallInt value");
        }
    }
 
    operator int() const {return val;}
 
private:
    size_t val;
};

这里的SmallInt类既定义了向类类型的转换,也定义了从类类型向其他类型的转换。其中,构造函数将算术类型的值转换成了SmallInt对象,而类型转换运算符将SmallInt对象转换成int:

SmallInt si;
si = 4;          // 首先将4隐式地转换成SmallInt,然后调用SmallInt::operator=
si + 3;          // 首先将si隐式地转换成int,然后执行整数的加法

尽管编译器一次只能执行一次用户定义的类型转换,但是隐式的用户定义类型转换可以置于一个标准(内置)类型转换之前或之后,并与其一起使用。因此,我们可以将任何算术类型传递给SmallInt的构造函数。类似的,我们也能使用类型转换运算符将一个SmallInt对象转换成int,然后再将所得的int转换成任何其他的算术类型:

// 内置类型转换将double实参转换成int
SamllInt si = 3.14;         // 调用SmallInt(int)构造函数
 
// SmallInt的类型转换运算符将si转换成int
si + 3.14;                 // 内置类型转换将所得的int继续转换成double

在实践中,类很少提供类型转换运算符。在大多数情况下,如果类型转换自动发生,用户可能会感觉比较意外,而不是感觉受到了帮助。而这条经验法则存在一种例外的情况:对于类来说,定义向bool类型转换还是比较普遍的现象。

显示的类型转换运算符

C++11新标准引入了显示的类型转换运算符(explicit conversion operator):

class SmallInt
{
public:
    // 编译器不会自动执行这一类型转换
    explicit operator int() const {return val;}
    // 其他成员与之前的版本一致
};

和显式的构造函数一样,编译器通常也不会将一个显式的类型转换运算符用于隐式类型转换:

SmallInt si = 3;             // 正确:SmallInt的构造函数不是显式的
si + 3;                      // 错误:此处需要隐式的类型转换,但类的运算符是显式的
static_cast<int>(si) + 3;    // 正确:显式地请求类型转换

类型转换参考如下

C++重载运算与类型转换https://blog.csdn.net/weixin_43918519/article/details/123933954?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-2-123933954-blog-100067470.235%5Ev38%5Epc_relevant_anti_t3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromBaidu~Rate-2-123933954-blog-100067470.235%5Ev38%5Epc_relevant_anti_t3&utm_relevant_index=3

关于operator bool () 和bool operator ==()https://blog.csdn.net/znzxc/article/details/80385995

C++中Operator类型强制转换成员函数解析https://blog.csdn.net/zhangzheng_1986/article/details/81080407

补充(内置类)

必须使用内置类的封装才能接管++、--运算符,当传统类型已经不足够进行操作的时候,必须要内置类进行封装

实例(内置类实现C++链表封装)
#include <iostream>

using namespace std;

class Link
{
public:
    class Node
    {
    public:
        Node(int num) : m_num(num), next(nullptr)
        {
        }
        int m_num;
        Node *next;

        friend ostream& operator<<(ostream &out, const Link::Node node);
    };

    class LinkIterator;

    typedef LinkIterator iterator;
    // typedef Node * iterator;

    class LinkIterator
    {
    public:
        LinkIterator(Node *p) : m_p(p)
        {
        }

        LinkIterator operator++()
        {
            m_p = m_p->next;
            return *this;
        }

        LinkIterator operator++(int num)
        {
            LinkIterator temp(this->m_p);
            this->m_p = m_p->next;
            return temp;
        }

        Node *operator->()
        {
            return m_p;
        }

        bool operator!=(const LinkIterator &other)
        {
            return m_p != other.m_p;
        }

        bool operator==(const LinkIterator &other)
        {
            return this->m_p == other.m_p;
        }

        LinkIterator & operator+(int index)
        {
            for(int i = 0; i < index; i++)
            {
                m_p = m_p->next;
            }

            return *this;
        }

        Node operator*()
        {
            return *m_p;
        }

    private:
        Node *m_p;
    };

    iterator begin()
    {
        return LinkIterator(m_head);
    }

    iterator end()
    {
        return LinkIterator(nullptr);
    }

    Link(Node *head = nullptr) : m_head(head)
    {
    }

    ~Link()
    {
        Node *temp = m_head;
        while (m_head != nullptr)
        {
            temp = m_head;
            m_head = m_head->next;
            delete temp;
        }

        m_head = nullptr;
    }

    void insert_head(Node *newnode)
    {
        newnode->next = m_head;
        m_head = newnode;
    }

    void insert_tail(Node *newnode)
    {
        Node *temp = m_head;
        if (temp == nullptr)
        {
            newnode->next = nullptr;
            m_head = newnode;
        }
        else
        {
            while (temp->next != nullptr)
            {
                temp = temp->next;
            }

            temp->next = newnode;
            newnode->next = nullptr;
        }
    }

    bool insert_mid(Node *newnode, int index)
    {
        Node *temp = m_head;

        if (temp == nullptr)
        {
            return false;
        }

        while (temp != nullptr)
        {
            if (temp->m_num == index)
            {
                newnode->next = temp->next;
                temp->next = newnode;
                return true;
            }

            temp = temp->next;
        }

        return false;
    }

    void insert_mid(iterator it, Node *newnode)
    {
        newnode->next = it->next;
        it->next = newnode;
    }

    iterator find(int index)
    {
        for (auto it = this->begin(); it != this->end(); ++it)
        {
            if (it->m_num == index)
            {
                return it;
            }
        }

        return this->end();
    }

    bool delete_node(Node *node)
    {
        Node *temp = m_head;

        if (temp == nullptr)
        {
            return false;
        }

        if (m_head->m_num == node->m_num)
        {
            m_head = m_head->next;
            free(temp);
            temp = nullptr;
            return true;
        }
        else
        {
            Node *p = temp;
            temp = temp->next;
            while (temp != nullptr)
            {
                if (temp->m_num == node->m_num)
                {
                    p->next = temp->next;
                    free(temp);
                    temp = nullptr;
                    return true;
                }

                p = temp;
                temp = temp->next;
            }

            return false;
        }
    }

    void display()
    {
        Node *temp = m_head;
        while (temp != nullptr)
        {
            cout << temp->m_num << " ";
            temp = temp->next;
        }
        cout << "\n";
    }

private:
    Node *m_head;
};

ostream& operator<<(ostream &out, const Link::Node node)
{
    out << node.m_num;
    return out;
}

int main(int argc, char **argv)
{
    Link::Node p(5);

    Link link;

    for (int i = 0; i < 5; i++)
    {
        link.insert_tail(new Link::Node(i));
    }

    link.insert_mid(new Link::Node(5), 3);
    link.insert_head(new Link::Node(6));
    link.delete_node(new Link::Node(4));

   // link.display();

    for (auto it = link.begin(); it != link.end(); ++it)
    {
        //cout << it->m_num << endl;
        cout << *it << endl;
    }

    auto it = link.find(6);
    if (it == link.end())
    {
        cout << "not find" << endl;
    }
    else
    {
        cout << it->m_num << endl;
    }

    it = it + 3;
    cout << it->m_num << endl;
    
    string s1 = "hello world";
    for(auto it = s1.rbegin(); it != s1.rend(); it++)
    {
        cout << *it << endl;
    }
    
    return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

竹烟淮雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值