C++的友元函数和友元类 与 类的杂散问题

一、什么是友元函数

1、外部函数访问类内成员

(1)写一个Person类,内部有private、protected、public的三类访问权限的成员
(2)写一个外部函数disp_info来打印这三类成员
(3)代码实战

#include <iostream>

using namespace std;

class person
{
private:
    int age;
protected:
    int height;
public:
    string name;

    person(){};//由于使用了自己的构造函数,编译器不会再为我们提供默认构造函数
    person(int age, int height, string name);//自定义构造函数
};

person::person(int age, int height, string name)
{
    this->age = age;
    this->height = height;
    this->name = name;
}
void disp_info(person& p)
{
    cout << "My name is " << p.name << endl;
    //cout << "I am " << p.age << "years old." << endl;
    //cout << "My height is " << p.height << endl;
}
int main(int argc, char *argv[])
{
    person p1(10, 155, "Jack");
    person &p2 = p1;

    disp_info(p2);

    return 0;
}

(4)总结:可以访问public的,但是protected和private的无法访问
在这里插入图片描述
(5)想办法:除非把disp_info挪到Person类内部成为成员函数,否则没办法

2、友元函数的引入

(1)将外部函数disp_info声明为Person类的友元函数即可解决

(2)代码实战验证

#include <iostream>
using namespace std;
class person
{
private:
    int age;
protected:
    int height;
public:
    string name;

    person(){};//由于使用了自己的构造函数,编译器不会再为我们提供默认构造函数
    person(int age, int height, string name);//自定义构造函数

    friend void disp_info(person& p);//友元函数
};
person::person(int age, int height, string name)
{
    this->age = age;
    this->height = height;
    this->name = name;
}
void disp_info(person& p)
{
    cout << "My name is " << p.name << endl;
    cout << "I am " << p.age << "years old." << endl;
    cout << "My height is " << p.height << endl;
}

int main(int argc, char *argv[])
{
    person p1(10, 155, "Jack");
    person &p2 = p1;
    disp_info(p2);
    return 0;
}

3、总结

(1)友元函数不是本类的成员函数,而是一个外部函数

(2)友元函数的标志就是在类内部加friend关键字来声明

(3)友元函数声明的位置不要求,写在private或者protected或者public内都可以,反正只要有声明就行

(4)一个外部函数成为类的友元后,访问权限被扩展了,等同于类的内部成员函数了

(5)友元函数是单向的,反过来是不行的,一个类可以有多个友元函数

(6)友元函数就好像在类的封装和访问权限保护上打了个“洞”,所以是对面向对象的一种破坏,所以不能滥用

二、友元函数的2种实现

1、友元函数的2种实现

(1)友元函数为外部函数
(2)友元函数为另一个类中的成员函数(也叫友元成员,友元成员方法,友元成员函数)
(3)第2种实现的代码实战

#include <iostream>

using namespace std;

class person;

class animal//如果将person类与animal类调换位置,声明animal类,是不可以的,编译报错
{
public:
    //person p1;//编译不通过
    person *p1;
    void eat(person& p);//类的前置声明不包括类的详细信息
};

class person
{
private:
    int age;
protected:
    int height;
public:
    string name;

    person(){};//由于使用了自己的构造函数,编译器不会再为我们提供默认构造函数
    person(int age, int height, string name);//自定义构造函数

    friend void disp_info(person& p);
    friend void animal::eat(person& p);
};
person::person(int age, int height, string name)
{
    this->age = age;
    this->height = height;
    this->name = name;
}

void animal::eat(person& p)//注意该函数的位置,不能放在person类之前,负责编译无法通过
{
    cout << "This animal wants to eat a person.His message is as follows." << endl;
    cout << "My name is " << p.name << endl;
    cout << "I am " << p.age << "years old." << endl;
    cout << "My height is " << p.height << endl;    
}
void disp_info(person& p)
{
    cout << "My name is " << p.name << endl;
    cout << "I am " << p.age << "years old." << endl;
    cout << "My height is " << p.height << endl;
}

int main(int argc, char *argv[])
{
    person p1(10, 155, "Jack");
    person &p2 = p1;

    disp_info(p2);

    animal a;
    a.eat(p2);

    return 0;
}

2、类的前置声明

(1)两个类要互相引用,就会出现“未定义”尴尬,此时可以用前置声明来解决

(2)前置声明不包括类的详细信息,所以编译器无法得到前置声明类的size,成员等详细信息

(3)不能试图通过前置声明解决类成员的调用。

(4)不能试图通过前置声明来定义类的对象,只能改为定义类对象的指针。

3、总结

(1)理解编译器的工作原理和“脾气”,很多事自然就很简单了
(2)设计多个类的体系时,尽量设计好层次关系成单向的,尽量避免互相引用的情况

三、友元类

1、友元类的概念和使用

(1)将类A声明为B中的friend class后,则A中所有成员函数都成为类B的友元函数了
(2)友元类的定义和使用友元类是单向的
(3)友元类是单向的和上边的友元函数一样

2、互为友元类

(1)2个类可以互为友元类

(2)互为友元类要注意互相引用的细节规则

#include <iostream>

using namespace std;

class people;

class monkey
{
public:
    int age;
    monkey(){};
    monkey(int mage, string mfood):age(mage),food(mfood){}
    void test_friend_monkey(people& s1);
private:
    string food;
    friend class people;
};

class people
{
public:
    int male;
    people(){};
    people(int mmale, string mname):male(mmale),name(mname){}
    void test_friend_people(monkey& s2);    
private:
    string name;
    friend class monkey;
};

void monkey::test_friend_monkey(people& s1)
{
    cout << "test_friend_monkey function" << endl;
    cout << "class people male = " << s1.male << endl;
    cout << "class people name = " << s1.name << endl;
}


void people::test_friend_people(monkey& s2)
{
    cout << "test_friend_people function" << endl;
    cout << "class monkey age = " << s2.age << endl;
    cout << "class monkey food = " << s2.food << endl;
}

int main(int argc, char *argv[])
{
    monkey a(11, "banana");
    monkey& a1 = a;
    people b(1, "Jack");
    people b1 = b;

    a.test_friend_monkey(b1);
    b.test_friend_people(a1);

    return 0;   
}

3、友元类总结

(1)友元类其实就是批量制造友元函数

(2)友元类中所有全部成员都成为了友元函数,相当于一次打了很多洞,极大破坏了面向对象

(3)除非确实有必要,否则建议按需定义友元函数,尽量维护面向对象,让代码更安全健壮

四、为什么会有友元函数

1、使用友元函数的优缺点

(1)缺点:破坏了封装机制,尽量不使用友元函数,不得已才使用友元函数

(2)优点:在实现类之间数据共享时,减少系统开销,提高效率。

2、使用友元函数的两种情况

(1)运算符重载的某些场合需要使用友元

(2)两个类要共享数据的时候

3、运算符重载中使用友元回顾

(1)使用方法参考C++的运算符重载(2)
(2)并非所有运算符重载都可用友元函数,有四个运算符 =, ->, [], ()就不可以

(3)详解可参考:https://www.jb51.net/article/40143.htm

(4)总结:C++的语法特性确实大而细,正确的方法是去理解而不是死记硬背。

4、两个类如何共享数据

(1)类内的数据,其实就是类的成员变量

(2)2个类共享数据方法1:将共享数据访问权限设置为public。

(3)2个类共享数据方法2:通过第三个专门封装数据的类,和2个类中带参数的成员函数来传参共享

(4)2个类共享数据方法3:通过友元函数打洞

5、友元函数和类的成员函数的区别

(1)成员函数有this指针,而友元函数没有this指针。为什么?因为友元只是朋友,并不是类内“自家人”

(2)友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。

(3)友元关系不具有传递性。类B是类A的友元,类C是B的友元,类C不一定是类A的友元,要看类中是否有相应的声明

6、共有友元函数

(1)1个函数同时成为2个类的友元函数
(2)共有友元函数可以是外部函数,也可以是某个(第3个)类的成员函数
(3)共有友元函数内可同时访问2个类的受保护成员,间接将2个完全无关的类的数据打通了

五、嵌套类和局部类

1、嵌套类

(1)在一个类(叫外围类)的内部定义一个类(叫内部类)。

(2)嵌套类技术也是一种类的组合技术,和前面讲的继承、组合有类似。

(3)嵌套类主要是限定了内部类的作用域

(4)嵌套类的内部类和外围类各自有各自的访问权限限定符,且遵守传统权限规则

(5)嵌套类中的成员函数可以在它的类体外定义,但是要附加类名的作用域限定说明

(6)嵌套类的内部类中声明的友元,并不是外围类的友元

(7)定义嵌套类的目的在于隐藏类名,减少全局标识符,限制用户使用该类建立对象。以提高类的抽象能力,强调两个类(外围类和嵌套类)之间的主从关系。

#include <iostream>
using namespace std;

class People
{
public:
    int age;
    string name;

    class Animal
    {
    public:
        string food;
        int count;

        void print(void);   
    };
    void print(void);
};

void People::print(void)
{
    cout << "People print function" << endl;
    cout << "age is " << this->age << endl;
    cout << "name is " << this->name << endl;
}

void People::Animal::print(void)
{
    cout << "Animal print function" << endl;
    cout << "food is " << this->food << endl;
    cout << "count is " << this->count << endl;
}

int main(int argc, char *argv[])
{
    People s1;
    People::Animal s2;

    s1.age = 10;
    s1.name = "wqewqe";

    s2.count = 100;
    s2.food = "rice";

    s1.print();
    s2.print();

    return 0;
}

2、局部类

(1)定义在函数内部的类,叫做局部类,只在定义他的作用域内可见,也是一种类型隐藏技术

(2)局部类除作用域外其他和正常类一样

(3)局部类一般不需要访问权限限定,因为本身作用域就很小了

(4)局部类内还可以再做嵌套类,如果有需要的话

(5)C++允许在函数内定义类,但是不允许在函数内定义函数,所以没有局部函数一说

3、总结

(1)不管是嵌套类还是局部类,都是为了隐藏类型,将没必要给外部看的类型隐藏在实现内部
(2)没必要纠结嵌套类和局部类的各种访问细节,真的需要用时写代码验证让编译器告诉你即可
(3)不要求会写这些,不写框架是用不到的,只需要知道,见了能认识即可。
(4)模板中会用到嵌套类,讲到模板时再说

六、数值与对象互转

1、数值与对象概念

(1)数值是简单类型,如int,float,double等,是C++从C继承而来的

(2)数值类型是源生类型,数值类型定义的是变量,非面向对象概念

(3)纯正的面向对象中是没有数值类型和变量的,会提供类库来替代数值类型,用数值对象来替代变量

2、C++中数值与对象互转

(1)数值转对象,实际是调用形参类型相匹配的构造函数来实现。
(2)对象转数值,不能默认转,必须调用对象的相应转换函数来实现。

#include <iostream>

using namespace std;

class Int
{
private:
    int a;
public:
    Int()
    {
        this->a = 0;
        cout << "Int()" << endl;
    }
    Int(int a)
    {
        this->a = a;
        cout << "Int(int a)" << endl;
    }
    int toint()
    {
        return this->a;
    }

};

int main(int argc, char *argv[])
{
    Int a;
    a = 7;//这句会先把数值7内部隐式转成一个临时Int对象,
          //然后再将临时对象(调用Int类的默认的oprator=函数)赋值给a

    int b;
    b = a.toint();

    return 0;
}

3、对象数组

(1)就是一次定义多个对象

(2)对象数组的访问和普通变量数组没区别

(3)要注意如果是用new来分配的对象数组,则销毁时要用delete[] xx;

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小嵌同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值