欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

C++ - 类和对象(5):静态成员、好友、内部类、匿名对象

最编程 2024-07-15 11:10:13
...

4. static成员

        在类中可以使用static来修饰成员变量或者成员函数,使之成为静态成员变量或者静态成员函数。由static修饰的静态成员不再属于某一个对象,而是属于类的,为该类的所有对象共享

        静态成员变量在类中声明,在类外定义(定义时注意类的域限定符)。静态成员函数没有this指针,所以可以不通过实例化后的对象进行调用,但是只能访问类中的静态成员。静态成员依旧收到访问限定符的限制。

class A
{
private:
	//声明
	static int n; //静态成员不属于任何一个对象,而是属于整个类,为所以类对象共享
public:
	static void func()	//静态成员函数没有this指针,所以只能访问静态成员(变量或函数)
	{
		cout << n << endl;
	}
};
//定义
int A::n = 4;	//静态成员变量在类中声明,在类外定义,定义时不加static
int main()
{
	A a;
	//cout << a.n;	//静态成员亦受访问限定符限制
	a.func();
	A::func();
}

5. 友元

        我们在写C++代码时可能会遇到需要在类外访问类中private成员的情况,如果为了可以被访问就将访问限定符改为public这就会违背类和对象“封装”的初心。所以为了应对这种特殊需求,C++为我们提供了在类外访问私有成员的方法:友元。

5.1 友元函数

        友元函数本身是一个类外的普通函数,由于有访问私有成员的需求,所以加上了关键字为friend的友元声明,这时这个函数就变成了友元函数就可以访问类内部的私有成员了。友元函数不受访问限定符的限制,一个函数也可以是多个类的友元函数。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	friend void func(A& a);
private:
	int _a;
};
void func(A& a)
{
	cout << a._a << endl;
}
int main()
{
	A a(13);
	func(a);
}

5.1.1 流插入、流提取操作符重载

        在掌握了友元函数的使用方法后,在往期所实现的日期类中,我们还可以重载流插入和流提取运算符,来支持日期的输入和输出。

        或许存在这样的疑问:为什么这两个运算符重载要使用友元呢?那么请看如下代码。

class Date
{
private:
	int _year;
public:
	Date(int year)
	{
		_year = year;
	}
	//重载<<,cout实际上是ostream类的一个对象,所以使用ostream&的方式作为参数
	void operator<<(ostream& out)
	{
		out << _year << endl;
	}
};
int main()
{
	//想要使用cout<<d1这样的方式进行快捷打印d1对象,因此需要对流插入运算符<<进行重载
	Date d1(2024);
	Date d2(2000);

	//cout << d1; 
    //发现cout<<d1没有办法正确调用
	//这是因为<<有两个操作数,当重载为成员函数后,默认左操作数传给this指针,右操作数传给显式写出的参数,也就是ostream&
	//这就导致了cout<<d1因为参数位置问题而调用失败了
	//根据其参数位置,我们可以采取以下方法正确调用,但是发现其顺序不合逻辑,将cout控制台流插入到d1,用起来非常别扭
	d1.operator<<(cout);
	d1 << cout;

}

        注释已经进行了一定的说明了,这里再额外解释一下。因为在类内重载运算符时,如果运算符有两个参数那么就会默认重载函数的第一个参数为左操作数,第二个为右操作数。而成员函数的第一个操作数是固定为this指针的,所以这就意味着我们重载后的运算符左操作数必须是Date类对象。显然流插入操作符的左操作数肯定是以cout为例的ostream对象,所以这就意味着重载后的使用形式与我们的正常使用逻辑完全相反。

        为了解决这一问题,我们只能想办法改变两个参数位置,那就只能在类外进行运算符重载了。但是此时又面临无法访问类私有成员的问题,于是友元就显得很有价值了。

class Date
{
private:
	int _year;
public:
	Date(int year)
	{
		_year = year;
	}
	//写法1
	//重载<<,cout实际上是ostream类的一个对象,所以使用ostream&的方式作为参数
	void operator<<(ostream& out)
	{
		out << _year << endl;
	}

	//友元声明,可以在类外访问类的私有成员
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
};
	
//写法2
//流插入对于内置类型在库中已经实现,自定义类型则需要我们自己重载
//重载<<,定义在类外,这样就可以自主规定参数顺序
//返回ostream&,可以支持连续调用
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year ;
	return out;
}

//流提取
istream& operator>>(istream& in, Date& d)
{
	in >> d._year;
	return in;
}

int main()
{
	//想要使用cout<<d1这样的方式进行快捷打印d1对象,因此需要对流插入运算符<<进行重载
	Date d1(2024);
	Date d2(2000);

	cin >> d2;

	//采用了写法1,发现cout<<d1没有办法正确调用
	//这是因为<<有两个操作数,当重载为成员函数后,默认左操作数传给this指针,右操作数传给显式写出的参数,也就是ostream&
	//这就导致了cout<<d1因为参数位置问题而调用失败了
	//cout << d1;
	//根据其参数位置,我们可以采取以下方法正确调用,但是发现其顺序不合逻辑,将cout控制台流插入到d1,用起来非常别扭
	d1.operator<<(cout);
	d1 << cout;

	//采用了写法2,可以按正常逻辑进行调用
	cout << d1 << '\n' << d2 << endl;

}

        在类外的运算符重载可以“肆意”规定参数顺序以满足我们的需求,注意的是和赋值运算符重载相似,如果想支持连续流插入、流提取,需要返回对应的Ostream和Istream对象。

5.2 友元类

        友元类和友元函数概念类似,当在某一个类中声明了友元类后,就支持在这个类中访问这个“某一个类”的所有私有成员了,同时“某一个类”没有权限访问这个类,即友元关系是单向的。

        感觉有点绕?没关系,友元函数不绕吧,那么就记住一句话,声明了一个友元类相当于声明了这个友元类的所有成员函数为友元函数

        以下给出测试代码,提供理解参考。

void func()
{}
class Date
{
	//声明友元类
	friend class Time;	//声明Time类为Date类的友元类,因此可以在Time类中可以访问Date类的私有成员
	//friend void Print();	//声明了友元类,等同于声明了这个类中的所有成员函数
	//声明友元函数,作为与友元类对比
	friend void func();	//声明func函数为Date类的友元类,因此可以在func函数中访问Date类的私有成员
private:
	int _month;
	int _day;
};
class Time
{
public:
	//friend class Date;//声明Date类为Time类的友元类,支持在Date类中访问Time类的私有成员,但是在Time类中无法访问Date类,这说明友元关系是单向的
	void Print()
	{
		cout << d._month << '/' << d._day << '/' << _hour << ':' << _minute << endl;
		//由于声明了Time类是Date类的友元类,所以可以正常访问Date中的私有成员_month和_day
	}
private:
	int _hour;
	int _minute;
	Date d;
};
int main()
{
	return 0;
}

6. 内部类

        内部类就如同字面意思,内部类就是定义在一个类中的类。因为内部类定义在类中,自然而然的收到类域和访问限定符的限制。C++规定内部类天生就是外部类的友元类,所以内部类可以访问到外部类的私有成员。注意区分内部类和类对象的区别,在sizeof计算类的大小时,认为与内部类无关。

//内部类
class A
{
public:
	class B	//B的访问限定符是public
	{
	public:
		void funcb(A& a)
		{
			a._a = 1;	//内部类天生就是外部类的友元类,所以可以访问外部类的私有成员
		}
	private:
		int _b;
	};
private:
	class C	//C的访问限定符是private
	{};
private:
	int _a;
};

int main()
{
	A a;
	//内部类受类域和访问限定符的限制
	A::B b;	
	//A::C c;	//error,C是private
	cout << sizeof(A) << endl;	//4,注意:内部类和类对象的成员变量是两回事
	return 0;
}

7. 匿名对象

        匿名对象,就是没有给名字的对象。这样的对象由于没有名称,所以只有在定义时我们有机会访问到它,一旦过了定义,因为没有名字就难以访问了。所以很自然的就规定匿名对象的生命周期只有一条语句。如此看来,匿名对象如此短的生命周期难以承担起一个对象变量的存储数据等正常职责,只是代表了一个对象。所以匿名对象的创建一般是为了作为媒介快捷访问类中的相关内容,是一个完全的“工具对象”。

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A(int)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	void func()
	{}
private:
	int _a;
};
int main()
{
	//有名对象
	A a1(1);
	//匿名对象---声明周期只有一行
	A(2);
	//匿名对象一般使用场景是对对象并无创建需求,但是需要一个对象作为媒介来访问类中的函数,那么匿名对象就可以充当这个媒介,进行便捷访问
	return 0;
}

推荐阅读