C++就看这篇--->类和对象之static成员、友元、内部类、匿名对象
代码语言:javascriptAI代码解释。
一、static成员
背景:如果我们要实现一个类,这个类可以计算它总共实例化出多少个对象?
该咋实现呢?相信以现阶段的知识而言肯定会说,建立一个全局变量,再类的构造函数和拷贝构造函数当中进行++(解释:类要实例化出对象,就必然要调用构造函数或者是拷贝构造函数),如下所示:
代码语言:javascript
AI代码解释
int n = 0;
class A
{
public:
A()
{
++n;
}
A(const A& t)
{
++n;
}
private:
};
但这里会遇到一个问题:如果其它地方把n改了呢?这是不是违背了我们类的封装性的特性啊,你n随便改能行?所以static成员来了。
✨1.1 概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
如何用呢?
代码语言:javascript
AI代码解释
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
static int GetACount() { return _scount; }//当然这个成员函数也可以被static修饰,
//但是这样做了在函数体内就不能对非静态成员进行访问,即没有this指针
private:
static int _scount;//这里只是声明,这样写_scount即属于这个类,又属于每一个对象
};
int A::_scount = 0;//这里才是定义
void TestA()
{
cout << A::GetACount() << endl;//当然如果定义成static成员,想要获取就需要写一个成员函数
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
//如果将私有限定符改成公有的下面是都可以访问的
/*cout << a1._secount << endl;
cout << a2._secount << endl;
cout << A::_secount << endl;*/
}
int main()
{
TestA();
return 0;
}
✨1.2 特性
- 1️⃣静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 2️⃣静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 3️⃣ 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 4️⃣ 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 5️⃣静态成员也是类的成员,受public、protected、private 访问限定符的限制
学了以上知识看两个小问题: 1. 静态成员函数可以调用非静态成员函数吗? 2. 非静态成员函数可以调用类的静态成员函数吗? 答: 1. 显然是不可以的,静态成员函数是没有this指针的,要调用非静态成员函数必须将对象地址传给非静态成员函数(这一步是隐藏的) 2. 显然是可以的,无需解释。
总结一下:
代码语言:javascript
AI代码解释
class A
{
public:
static int GetACount() { return _scount; }//当然这个成员函数也可以被static修饰,
private:
static int _scount;//这里只是声明,这样写_scount即属于这个类,又属于每一个对象
};
int A::_scount = 0;//这里才是定义
void TestA()
{
cout << A::GetACount() << endl;//当然如果定义成static成员,想要获取就需要写一个成员函数
cout << a1.GetACount() << endl;
}
1. 如果想要访问私有static成员必须要写一个成员函数来获取
2. 如果是static成员函数上述A::GetACount()和a1.GetACount()都可以使用,但如果是普通成员函数就必须使用特定对象调用的方式a1.GetACount()
📕二、友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。
友元分为:友元函数和友元类
✨2.1 友元函数
背景:
如上面Time类的几个成员变量是在私有限定符下
这就导致了在类外面是不能访问的,但是如果我必须要访问怎么办?
这就违背了限定符的作用,那怎么办?取个折中的方法,你可以访问但是前提你必须是我的朋友
所以友元函数就是解决上述问题的
友元函数定义,见下:
代码语言:javascript
AI代码解释
class Time
{
friend void f(Time& t);
private:
int _hour = 0;
int _minute = 0;
int _second = 0;
};
void f(Time& t)
{
t._hour = 10;
cout << t._hour << t._minute << t._second << endl;
}
有没有感觉莫名奇妙的韵味,你既然要访问,还弄这杂七杂八的干嘛,直接定义成成员函数不就行了,确实是这个样,但是友元函数也确实有它的应用场景,见下:
前面的学习过程中,我们打印都是写一个print函数,但是我们能不能像cout那样直接打印呢?
代码语言:javascript
AI代码解释
cout << t;
这样肯定是不行的,就像前面学习的运算符重载那样,直接用是不行的
所以要想使用上面的这种形式就必须重载
代码语言:javascript
AI代码解释
void operator<<(ostream& out)//ostream是iostream中cout对象类型
{
out << _hour << _minute << _second << endl;
}
但你会发现还是编不过,原因就是讲述运算符重载时说过,左操作数是重载函数的第一个参数(即隐含的this指针)右操作数是重载函数的显式形参,但你现在所表达的是cout是左操作数,t是右操作数,因而不行
代码语言:javascript
AI代码解释
t << cout;//所以要用只能这种形式,但这不符合常规啊,可读性不好,那怎么办?
this指针是隐含的而且我们知道它必须是第一个参数,变不了,因此友元函数的作用来了
代码语言:javascript
AI代码解释
class Time
{
public:
friend void operator<<(ostream& out, const Time& t1);
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
, _minute(minute)
,_second(second)
{}
private:
int _hour = 0;
int _minute = 0;
int _second = 0;
};
void operator<<(ostream& out, const Time& t1)//ostream是iostream中cout对象类型
{
out << t1._hour << t1._minute << t1._second << endl;
}
int main()
{
Time t(20, 14, 50);
//我们将重载函数定义到类外面
cout << t;
return 0;
}
补充知识: ostream是iostream中cout对象类型 istram是iostream中cin对象类型
cout我们一般还有这样操作过
代码语言:javascript
AI代码解释
cout << i << j << endl;
连续的输出,但是我们上面所实现的并不能完成上面功能 ,需要如下做:
代码语言:javascript
AI代码解释
ostream& operator<<(ostream& out, const Time& t1)//ostream是iostream中cout对象类型
{
out << t1._hour << t1._minute << t1._second << endl;
return out;
}

我们还有输入cin,原理和上面类似,连续输入的实现如下:
代码语言:javascript
AI代码解释
istream& operator>>(istream& in, Time& t1)//istream是iostream中cin对象类型
{
in >> t1._hour >> t1._minute >> t1._second ;
return in;
}
注意: 友元函数可访问类的私有和保护成员,但不是类的成员函数 友元函数不能用const修饰 友元函数可以在类定义的任何地方声明,不受类访问限定符限制 一个函数可以是多个类的友元函数 友元函数的调用与普通函数的调用原理相同
总结一下: 友元函数在 C++ 中的主要应用场景是当某个函数需要直接访问一个类的私有或保护成员,但该函数逻辑上又不应该(或不能)是这个类的成员函数时。它打破了严格的封装边界,但提供了必要的灵活性。
更多推荐



所有评论(0)