目录
前言:
本篇博客我们继续对类和对象的其它琐碎的知识点进行学习,本篇博客是类和对象的收篇之作,从下一篇我们将开始新阶段的学习,希望大家跟住步伐,征服C++指日可待!这篇博客我们将了解static成员、友元函数与友元类、内部类、匿名对象等,希望大家有所收获!
📕一、static成员
背景:如果我们要实现一个类,这个类可以计算它总共实例化出多少个对象?
该咋实现呢?相信以现阶段的知识而言肯定会说,建立一个全局变量,再类的构造函数和拷贝构造函数当中进行++(解释:类要实例化出对象,就必然要调用构造函数或者是拷贝构造函数),如下所示:
int n = 0;
class A
{
public:
A()
{
++n;
}
A(const A& t)
{
++n;
}
private:
};
但这里会遇到一个问题:如果其它地方把n改了呢?这是不是违背了我们类的封装性的特性啊,你n随便改能行?所以static成员来了。
✨1.1 概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
如何用呢?
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. 显然是可以的,无需解释。
总结一下:
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类的几个成员变量是在私有限定符下
这就导致了在类外面是不能访问的,但是如果我必须要访问怎么办?
这就违背了限定符的作用,那怎么办?取个折中的方法,你可以访问但是前提你必须是我的朋友
所以友元函数就是解决上述问题的
友元函数定义,见下:
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那样直接打印呢?
cout << t;
这样肯定是不行的,就像前面学习的运算符重载那样,直接用是不行的
所以要想使用上面的这种形式就必须重载
void operator<<(ostream& out)//ostream是iostream中cout对象类型
{
out << _hour << _minute << _second << endl;
}
但你会发现还是编不过,原因就是讲述运算符重载时说过,左操作数是重载函数的第一个参数(即隐含的this指针)右操作数是重载函数的显式形参,但你现在所表达的是cout是左操作数,t是右操作数,因而不行
t << cout;//所以要用只能这种形式,但这不符合常规啊,可读性不好,那怎么办?
this指针是隐含的而且我们知道它必须是第一个参数,变不了,因此友元函数的作用来了
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我们一般还有这样操作过
cout << i << j << endl;
连续的输出,但是我们上面所实现的并不能完成上面功能 ,需要如下做:
ostream& operator<<(ostream& out, const Time& t1)//ostream是iostream中cout对象类型
{
out << t1._hour << t1._minute << t1._second << endl;
return out;
}
我们还有输入cin,原理和上面类似,连续输入的实现如下:
istream& operator>>(istream& in, Time& t1)//istream是iostream中cin对象类型
{
in >> t1._hour >> t1._minute >> t1._second ;
return in;
}
注意:
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
总结一下:
友元函数在 C++ 中的主要应用场景是当某个函数需要直接访问一个类的私有或保护成员,但该函数逻辑上又不应该(或不能)是这个类的成员函数时。它打破了严格的封装边界,但提供了必要的灵活性。
典型的应用场景和例子:
- 运算符重载(特别是需要对称性的运算符)
- 需要访问多个不同类私有成员的函数
慎用: 友元破坏了封装性,这是面向对象设计的核心原则之一。过度使用友元会使代码耦合度增高,更难维护和理解。优先考虑通过公有成员函数(getter/setter, 尽管它们也可能破坏封装)或设计更清晰的接口来解决问题。
✨2.2 友元类
原理和友元函数没有什么区别,直接看个例子即可,如下,本质原因就是Date类里想访问Time类里的私有变量,做法就是将Date类变为Time的友元类
class Time
{
friend class Date;// 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
📕三、内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
- 1️⃣内部类可以定义在外部类的public、protected、private都是可以的。
- 2️⃣注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- 3️⃣sizeof(外部类)=外部类,和内部类没有任何关系。
见个例子:
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;//需要注意访问细节
b.foo(A());
return 0;
}
📕四、匿名对象
匿名对象(又称临时对象)是 C++ 中一种没有名称的对象,它在表达式结束时自动销毁。这类对象由编译器隐式创建或通过显式调用构造函数生成。
核心特性
-
无标识符:没有变量名绑定
-
短暂生命周期:通常在完整表达式结束时销毁
-
右值性质:属于右值(可绑定到右值引用
&&
或const
左值引用) -
编译器优化:常被返回值优化 (RVO/NRVO) 消除
见个例子:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}
📕五、总结
本篇博客我们了解了 C++ 中的 static 成员、友元、内部类和匿名对象各有特点。static 成员为类所有对象共享,需类外定义;友元包括友元函数和类,可突破封装访问私有成员,但也增加耦合度;内部类定义在外部类内部,但独立于外部类,可访问外部类 static 成员;匿名对象无名称,表达式结束时自动销毁。这些概念是 C++ 面向对象编程的重要组成部分,针对不同场景有各自的应用和限制。