【C++】类和对象(下):友元、static成员、内部类、explicit 和 匿名对象

文章目录


前言

一、友元(友元函数 和 友元类)
二、static成员(类中被static修饰的成员变量 和 成员函数)
三、内部类( 如果⼀个类定义在另⼀个类的内部,这个类就叫做内部类)
四、隐式类型转换(加explicit可以阻止隐式类型转换)
五、匿名对象


一、友元

友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。

友元函数的特点:

• 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
• 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
• 一个函数可以是多个类的友元函数。

友元类的特点:

• 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
• 友元类的关系是单向的,不具有交换性,比如声明了A类是B类的友元,但是B类不是A类的友元。
• 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。

友元有时能为我们提供便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

(1)报错的示例(外部函数无法直接访问类对象的private部分):

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
private:
	int _a1 = 5;
};
class B
{
private:
	int _b1 = 3;
};

void func(const A& aa, const B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}

int main()
{
	A aa;
	B bb;
	func(aa, bb);
	return 0;
}

那么外部函数要如何才能访问类对象的private部分,有一个办法就是在类中声明这个外部函数是它的友元函数,外部友元函数可以访问类对象的私有和保护成员,如下:

cpp 复制代码
#include<iostream>
using namespace std;
class B;// 前置声明,防止A中友元函数声明时,编译器不认识B

class A
{
	// 友元函数声明
	friend void func(const A& aa, const B& bb);
private:
	int _a1 = 5;
};

class B
{
	// 友元函数声明
	friend void func(const A& aa, const B& bb);
private:
	int _b1 = 3;
};

void func(const A& aa, const B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}

int main()
{
	A aa;
	B bb;
	func(aa, bb);
	return 0;
}


(2)示例(友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。):

cpp 复制代码
#include<iostream>
using namespace std;

class A
{
	friend class B;// 友元类声明
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	void func1(const A& aa)
	{
		cout << aa._a1 << endl;
		cout << _b1 << endl;
	}
	void func2(const A& aa)
	{
		cout << aa._a2 << endl;
		cout << _b2 << endl;
	}
private:
	int _b1 = 3;
	int _b2 = 4;
};

int main()
{
	A aa;
	B bb;
	bb.func1(aa);
	bb.func2(aa);
	return 0;
}

二、static成员

static成员指的是类中被static修饰的成员变量 和 成员函数。

static修饰的成员变量:

• 用static修饰的成员变量,称之为静态成员变量。静态成员变量⼀定要在类外进行初始化,且其初始化时不受访问限定符的限制。
• 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
• 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

static修饰的成员函数:

• 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
• 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的成员,因为没有this指针。

• 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
• 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
• 突破类域就可以访问静态成员(前提是静态成员被public限定),可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。

(1)示例(static修饰的成员变量):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	
private:
	static int _scount;// 类里⾯声明
};
// 静态成员变量⼀定要在类外进行初始化,且其初始化时不受访问限定符的限制
int A::_scount = 0;

int main()
{
	A a;
	cout << sizeof(a) << endl;
	// A类对象大小为1,证明静态成员变量不存在对象中
	return 0;
}


(2)示例(static修饰的成员函数;突破类域就可以访问静态成员):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	static int GetACount()
	{
		return _scount++;// 静态成员函数中可以访问其他的静态成员
	}
private:
	static int _scount;// 类⾥⾯声明
};
// 静态成员变量⼀定要在类外进行初始化,且其初始化时不受访问限定符的限制
int A::_scount = 0;

int main()
{
	cout << A::GetACount() << endl;
	A a1;
	cout << a1.GetACount() << endl;
	// 突破类域就可以访问静态成员(前提是静态成员被public限定),
	// 可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
	return 0;
}


(3)示例(非静态的成员函数,可以访问任意的静态成员变量和静态成员函数):

cpp 复制代码
// 实现⼀个类,能计算程序中创建出了多少个类对象?
#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		++_scount;// 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数
	}
	static int GetACount()
	{
		return _scount;
	}
private:
	static int _scount;
};
int A::_scount = 0;

int main()
{
	cout << A::GetACount() << endl;
	A a1;
	cout << a1.GetACount() << endl;
	A a2, a3;
	cout << a2.GetACount() << endl;
	return 0;
}

三、内部类

• 如果⼀个类定义在另⼀个类的内部,这个类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
• 内部类默认是外部类的友元类。
• 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
private:
	static int _k;
	int _h = 5;

public:
	class B // 内部类默认是外部类的友元类,所以B默认就是A的友元
	{       
	public:
		void fun(const A& a)
		{
			cout << _k << endl; 
			cout << a._h << endl; 
			cout << _b << endl;
		}
	private:
		int _b = 1;
	};
};
int A::_k = 10;

int main()
{
	A a;
	// A类对象大小大小为4,所以外部类定义的对象中不包含内部类
	cout << "A类对象大小:" << sizeof(a) << endl;
	// 内部类受到外部类的类域和访问限定符限制,定义内部类对象要指定类域
	A::B b;
	b.fun(a);
	return 0;
}

四、隐式类型转换(加explicit可以阻止隐式类型转换)

• C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
• 类类型的对象之间也可以隐式转换,需要相应的构造函数支持
• 构造函数前面加explicit就不再支持隐式类型转换。

(1)示例(只要有相关内置类型为参数的构造函数,内置类型就可以隐式类型转换为类类型对象):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a1)// A类的单参数构造函数
		:_a1(a1)
	{}

	A(int a1, int a2)// A类的双参数构造函数
		:_a1(a1)
		, _a2(a2)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1 = 10;
	int _a2 = 5;
};

int main()
{
	// 1 被传给A类的单参数构造函数,来构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa1
	A aa1 = 1;
	aa1.Print();
	
	// { 2,2 } 被传给A类的双参数构造函数,来构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa2
	A aa2 = { 2,2 };
	aa2.Print();

	// 由于 1 构造的是⼀个A的临时对象,而临时对象是具有常属性的,
	// 如果想要引用这个临时对象的话,必须使用const引用,使用普通引用会报错
	const A& aa3 = 1;
	return 0;
}

(2)示例(类类型的对象之间也可以隐式转换,但需要相应的构造函数支持):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a1)
		:_a1(a1)
	{}
	int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 10;
	int _a2 = 5;
};

class B
{
public:
	B(const A& a)// B类的构造函数
		:_b(a.Get())
	{}
	void Print()
	{
		cout << _b << endl;
	}
private:
	int _b = 0;
};

int main()
{
	A aa1 = 2;

	// aa1 被传给B类的构造函数,来构造⼀个B的临时对象,再⽤这个临时对象拷⻉构造bb1
	B bb1 = aa1;
	bb1.Print();
	return 0;
}

(3)报错的示例(构造函数前面加explicit就不再支持隐式类型转换):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	// 构造函数前加explicit就不再⽀持隐式类型转换
	explicit A(int a1)
		:_a1(a1)
	{}
	int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 10;
	int _a2 = 5;
};

class B
{
public:
	// 构造函数前加explicit就不再⽀持隐式类型转换
	explicit B(const A& a)
		:_b(a.Get())
	{}
	void Print()
	{
		cout << _b << endl;
	}
private:
	int _b = 0;
};

int main()
{
	A aa1 = 2;// A类构造函数不再支持这种隐式类型转换,直接报错
	B bb1 = aa1;// B类构造函数不再支持这种隐式类型转换,直接报错
	bb1.Print();
	return 0;
}

explicit 关键字只阻止隐式类型转换,但不阻止显式类型转换。我们使用显式类型转换可以突破explicit 的限制,之前的那些转换过程照常进行:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	// 构造函数前加explicit就不再⽀持隐式类型转换
	explicit A(int a1)
		:_a1(a1)
	{}
	int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 10;
	int _a2 = 5;
};

class B
{
public:
	// 构造函数前加explicit就不再⽀持隐式类型转换
	explicit B(const A& a)
		:_b(a.Get())
	{}
	void Print()
	{
		cout << _b << endl;
	}
private:
	int _b = 0;
};

int main()
{
	// 在 C++ 中,当一个构造函数被声明为 explicit,它阻止了编译器调用它进行隐式类型转换。
	// 这里使用了显式类型转换 (A) 和 2 来创建A类的临时对象,再去拷贝构造aa1,这是合法的,
	// 因为 explicit 关键字只阻止隐式类型转换,但不阻止显式类型转换.
	A aa1 = (A)2;
	B bb1 = (B)aa1;
	bb1.Print();
	return 0;
}

五、匿名对象

• 用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象
• 匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

(1)示例(匿名对象的定义及其生命周期):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	// A aa1(); 不能这么定义对象,因为编译器⽆法识别这是⼀个函数声明,还是对象定义
	// 但是我们可以这么定义匿名对象,匿名对象的特点是不⽤取名字,
	// 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
	A();
	A(1);
	A(2);
	return 0;
}

(2)示例(引用匿名对象可以延长它的生命周期):

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	// 匿名对象的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
	A();
	// 我们可以引用匿名对象,不过必须要使用const引用,
	// 因为匿名对象和临时变量类似,都具有常属性。
	// 匿名对象被引用后,⽣命周期不再只有⼀⾏,它的生命周期跟引用其的aa一致
	const A& aa = A(1);
	A(2);
	return 0;
}


(3)示例(匿名对象在创建的同时可以调用成员函数):

cpp 复制代码
#include<iostream>
using namespace std;
class Solution 
{
public:
	int Sum_Solution(int n) 
	{
		cout << n << endl;
		return n;
	}
};

int main()
{
	Solution s1;
	s1.Sum_Solution(10);

	// 匿名对象在创建的同时可以调用成员函数
	Solution().Sum_Solution(10);
	return 0;
}

相关推荐
C++小厨神12 分钟前
Go语言的数据库交互
开发语言·后端·golang
毒丐32 分钟前
GCC使用说明
linux·c语言·c++
强大的RGG40 分钟前
从源码编译Qt5
开发语言·c++·qt
(❁´◡`❁)Jimmy(❁´◡`❁)1 小时前
3103: 【基础】既生瑜,何生亮!
c++
s.feng1 小时前
00_basic_gemm
c++
Channing Lewis1 小时前
python实现,outlook每接收一封邮件运行检查逻辑,然后发送一封邮件给指定邮箱
开发语言·python·outlook
编程小筑1 小时前
TypeScript语言的软件工程
开发语言·后端·golang
꧁坚持很酷꧂1 小时前
Qt天气预报系统鼠标拖动窗口
开发语言·qt·计算机外设
2401_898410691 小时前
CSS语言的软件工程
开发语言·后端·golang
Akzeptieren2 小时前
Python字符串的格式化
开发语言·python