C++的友元函数和友元类

前言


c++文章连载:
1.C++基础
  1.C++基础
  2.C++新增和有变化的关键字
  3.C++的内存管理
2.面向对象
  1.C++的封装和访问权限
  2.C++继承和多态特性
  3.C++的运算符重载
  4.C++静态类和静态成员
  5.C++的友元函数和友元类
3.模板编程和STL
  1.C++模板编程入门
  2.STL的容器类和迭代器
  3.STL的泛型算法
  4.模板特化与类型萃取
  5.STL的其他容器讲解
  6.智能指针与STL查漏补缺
4.杂项
  1.c++各种流操作
  2.依赖,关联,聚合,组合,继承
  3.一些技巧性的代码设计
  
  


1.什么是友元函数

1.1、外部函数访问类内成员
(1)写一个Person类,内部有private、protected、public的三类访问权限的成员
(2)写一个外部函数disp_info来打印这三类成员
(3)代码实战
(4)总结:可以访问public的,但是protected和private的无法访问
(5)想办法:除非把disp_info挪到Person类内部成为成员函数,否则没办法
1.2、友元函数的引入
(1)将外部函数disp_info声明为Person类的友元函数即可解决
(2)代码实战验证
1.3、总结
(1)友元函数不是本类的成员函数,而是一个外部函数
(2)友元函数的标志就是在类内部加friend关键字来声明
(3)友元函数声明的位置不要求,写在private或者protected或者public内都可以,反正只要有声明就行
(4)一个外部函数成为类的友元后,访问权限被扩展了,等同于类的内部成员函数了
(5)友元函数是单向的,反过来是不行的
(6)友元函数就好像在类的封装和访问权限保护上打了个“洞”,所以是对面向对象的一种破坏,所以不能滥用

2.友元函数的2种实现

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

#include <iostream>

using namespace std;

class Person;					// Person类的前置声明
//class Animal;
//Person可以前置声明给Animal用,但是Animal不可以给Person用,因为Animal中只是用了Person类型的引用
//,或者指针,并没有用他的成员,编译器是从前往后编译的,类的前置声明只是声明了有这样一个名字的类,他的成员的
//具体情况是不知道的,所以不可以使用
class Animal
{
public:
	void eat(Person& pn);
	Person &ppx;
	//Person p2;	//不能定义对象,只能改为指针
};

class Person
{
private:
	int age;				// 年龄
	
protected:
	int height;			// 身高
	
public:
	string name;		// 姓名
	
	// 构造函数来初始化对象,给三个属性赋值
	Person(){};
	Person(int age, int height, string name);
	
	friend void Animal::eat(Person& pn);
//	friend void eat(Person& pn);
};




Person::Person(int age, int height, string name)
{
	this->age = age;
	this->height = height;
	this->name = name;
}

void Animal::eat(Person& pn)
{
	cout << "Animal eat a person named " << pn.name << endl;		// 可以直接访问
	cout << "Animal eat a person age = " << pn.age << endl;
	cout << "Animal eat a person height =  " << pn.height << endl;
}


int main(void)
{
	Person p1(10, 155, "Jim");
	Animal a1;
	
	a1.eat(p1);


	return 0;
}

2.2、类的前置声明
(1)两个类要互相引用,就会出现“未定义”尴尬,此时可以用前置声明来解决
(2)前置声明不包括类的详细信息,所以编译器无法得到前置声明类的size,成员等详细信息
(3)不能试图通过前置声明解决类成员的调用。
(4)不能试图通过前置声明来定义类的对象,只能改为定义类对象的指针。

在代码中Person可以前置声明给Animal用,但是Animal不可以给Person用,因为Animal中只是用了Person类型的引用,或者指针,并没有用他的成员,编译器是从前往后编译的,类的前置声明只是声明了有这样一个名字的类,他的成员的具体情况是不知道的,所以不可以使用
2.3、总结
(1)设计多个类的体系时,尽量设计好层次关系成单向的,尽量避免互相引用的情况

3.友元类

3.1、友元类的概念和使用
(1)将类A声明为B中的friend class后,则A中所有成员函数都成为类B的友元函数了
(2)代码实战:友元类的定义和使用友元类是单向的
(3)友元类是单向的,代码实战验证

#include <iostream>

using namespace std;

class Person;					// Person类的前置声明
//class Animal;

class Animal
{
private:
	int size;
public:

	Animal(){};
	Animal(int size);

	void eat(Person& pn);
	void love(Person& pn);
	
//	friend void Person::measure(Animal& a);
	friend class Person;	
};

class Person
{
private:
	int age;				// 年龄
	
protected:
	int height;			// 身高
	
public:
	string name;		// 姓名
	
	// 构造函数来初始化对象,给三个属性赋值
	Person(){};
	Person(int age, int height, string name);
	
	void measure(Animal& a);
	
//	friend void Animal::eat(Person& pn);
	friend class Animal;			// 将Animal整个类声明为Person的友元类
};



Person::Person(int age, int height, string name)
{
	this->age = age;
	this->height = height;
	this->name = name;
}

void Person::measure(Animal& a)
{
	cout << "person meusure animal size = " << a.size << endl;
}

Animal::Animal(int size)
{
	this->size = size;
}

void Animal::eat(Person& pn)
{
	cout << "Animal eat a person named " << pn.name << endl;		// 可以直接访问
	cout << "Animal eat a person age = " << pn.age << endl;
	cout << "Animal eat a person height =  " << pn.height << endl;
}

void Animal::love(Person& pn)
{
	cout << "Animal love a person named " << pn.name << endl;		// 可以直接访问
	cout << "Animal love a person age = " << pn.age << endl;
	cout << "Animal love a person height =  " << pn.height << endl;	
}


int main(void)
{
	Person p1(10, 155, "Jim");
	Animal a1(33);
	
	p1.measure(a1);
	
//	a1.eat(p1);
//	a1.love(p1);


	return 0;
}

3.2、互为友元类
(1)2个类可以互为友元类,代码实战验证
(2)互为友元类要注意互相引用的细节规则
3.3、友元类总结
(1)友元类其实就是批量制造友元函数
(2)友元类中所有全部成员都成为了友元函数,相当于一次打了很多洞,极大破坏了面向对象
(3)除非确实有必要,否则建议按需定义友元函数,尽量维护面向对象,让代码更安全健壮

4.为什么会有友元函数

4.1、使用友元函数的优缺点
(1)缺点:破坏了封装机制,尽量不使用友元函数,不得已才使用友元函数
(2)优点:在实现类之间数据共享时,减少系统开销,提高效率。
4.2、使用友元函数的两种情况
(1)运算符重载的某些场合需要使用友元
(2)两个类要共享数据的时候
4.3、运算符重载中使用友元回顾
(1)使用方法参考《2.4.11.两种运算符重载方法》
(2)并非所有运算符重载都可用友元函数,有四个运算符 =, ->, [], ()就不可以
(3)详解可参考:https://www.jb51.net/article/40143.htm

4.4、两个类如何共享数据
(1)类内的数据,其实就是类的成员变量
(2)2个类共享数据方法1:将共享数据访问权限设置为public。
(3)2个类共享数据方法2:通过第三个专门封装数据的类,和2个类中带参数的成员函数来传参共享
(4)2个类共享数据方法3:通过友元函数打洞
4.5、友元函数和类的成员函数的区别
(1)成员函数有this指针,而友元函数没有this指针。为什么?因为友元只是朋友,并不是类内“自家人”
(2)友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友。
(3)友元关系不具有传递性。类 B是类A的友元,类C是B的友元,类C不一定是类A的友元,要看类中是否有相应的声明
4.6、共有友元函数
(1)1个函数同时成为2个类的友元函数
(2)共有友元函数可以是外部函数,也可以是某个(第3个)类的成员函数
(3)共有友元函数内可同时访问2个类的受保护成员,间接将2个完全无关的类的数据打通了

5.嵌套类和局部类

#include <iostream>

using namespace std;


class Person
{
private:
	int age;				// 年龄
	
protected:
	int height;			// 身高
	
public:
	string name;		// 姓名
	
	
	class Dog
	{
	private:
		int x;
	public:
		void func(void);
	
	};
	
};

Person::Dog::func();

void Person::Dog::func(void)
{
	
}

int main(void)
{


	return 0;
}

5.1、嵌套类
(1)在一个类(叫外围类)的内部定义一个类(叫内部类)。代码演示
(2)嵌套类技术也是一种类的组合技术,和前面讲的继承、组合有类似。
(3)嵌套类主要是限定了内部类的作用域
(4)嵌套类的内部类和外围类各自有各自的访问权限限定符,且遵守传统权限规则
(5)嵌套类中的成员函数可以在它的类体外定义,但是要附加类名的作用域限定说明
(6)嵌套类的内部类中声明的友元,并不是外围类的友元
(7)定义嵌套类的目的在于隐藏类名,减少全局标识符,限制用户使用该类建立对象。以提高类的抽象能力,强调两个类(外围类和嵌套类)之间的主从关系。
5.2、局部类
(1)定义在函数内部的类,叫做局部类,只在定义他的作用域内可见,也是一种类型隐藏技术
(2)局部类除作用域外其他和正常类一样
(3)局部类一般不需要访问权限限定,因为本身作用域就很小了
(4)局部类内还可以再做嵌套类,如果有需要的话
(5)C++允许在函数内定义类,但是不允许在函数内定义函数,所以没有局部函数一说
5.3、总结
(1)不管是嵌套类还是局部类,都是为了隐藏类型,将没必要给外部看的类型隐藏在实现内部
(2)没必要纠结嵌套类和局部类的各种访问细节,真的需要用时写代码验证让编译器告诉你即可
(3)不要求会写这些,不写框架是用不到的,只需要知道,见了能认识即可。
(4)模板中会用到嵌套类,讲到模板时再说

6.数值与对象互转

#include <iostream>

using namespace std;

// 一般是语言类库来提供,Int类就是我们语言提供的int类型的面向对象版本
class Int
{
private:
	int a;				// 真正用来存储数据的,编译器会给分配内存的变量

public:
	Int();
	Int(int a);
	Int(float a);
	Int(double a);
	
	int toint();

};

Int::Int()
{
	this->a = 0;
	cout << "Int()" << endl;
};

Int::Int(int a)
{
	this->a = a;
	cout << "Int(int a)" << endl;
};

Int::Int(float a)
{
	this->a = (int)a;
	cout << "Int(float a)" << endl;
};

Int::Int(double a)
{
	this->a = (int)a;
	cout << "Int(double a)" << endl;
};

int Int::toint()
{
	return this->a;
}



int main(void)
{
	Int a;		// 定义了一个Int类型的对象a,它内部的值其实是0
//	a = 7;		// 这句会先把数值7内部隐式转成一个临时Int对象,然后再将临时对象(调用Int类的oprator=)赋值给a
	a = 7.7f;
	
	int b;
	b = a.toint();
	cout << "b = " << b << endl;
	
	Int a[4];
	Int *p = new Int[5];

	return 0;
}

6.1、数值与对象概念
(1)数值是简单类型,如int,float,double等,是C++从C继承而来的
(2)数值类型是源生类型,数值类型定义的是变量,非面向对象概念
(3)纯正的面向对象中是没有数值类型和变量的,会提供类库来替代数值类型,用数值对象来替代变量
6.2、C++中数值与对象互转
(1)**数值转对象,实际是调用形参类型相匹配的构造函数来实现。**代码验证
(2)**对象转数值,不能默认转,必须调用对象的相应转换函数来实现。**代码演示。
6.3、对象数组
(1)就是一次定义多个对象
(2)对象数组的访问和普通变量数组没区别
(3)要注意如果是用new来分配的对象数组,则销毁时要用delete[] xx;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值