一丶类的静态成员
I. 引入
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
II.特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,静态成员存在在静态区中。
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中所写只是声明;即静态成员变量遵循“类内声明,类外定义”的原则。
- 类静态成员即可用“类名::静态成员"或者“对象.静态成员”两种方式来访问静态成员。
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
- 静态成员也是类的成员,受public丶protected丶private访问限定符的限制。
此外,讲述一下静态成员和非静态成员直接的权限关系。
对于静态成员函数,不能调用非静态成员函数,也不能访问非静态成员变量。
对于非静态成员函数,可以调用静态成员函数,也可以访问静态成员变量。
#include <iostream>
using namespace std;
class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount()
{
//Print(); //不行
//_a = 1; //不行
return _scount;
}
void Print()
{
GetACount(); //可行
_scount = 0; //可行
cout << _scount << endl;
}
private:
static int _scount;
int _a = 0;
};
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
a3.Print();
}
二丶友元
友元分为友元函数和友元类。
I.友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
现今我们借助友元来实现流插入<<操作符重载和流提取>>操作符重载。
#include <iostream>
using namespace std;
class Date
{
//让这两个重载成为Date类的友元函数
//使得这两个函数可以访问Date类中的所有成员
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
//流插入运算符重载
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
//流提取运算符重载
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
//隐式调用
cin >> d;
//显式调用
operator>>(cin, d);
//隐式调用
cout << d << endl;
//显式调用
operator<<(cout, d) << endl;
return 0;
}
这里的流插入和流提取重载,都是在类外的全局式重载,再借助friend成为类的友元,使得函数们可以访问类中的所有成员。
那么为什么不让这两个函数在类内作为成员函数进行重载呢?
我们不妨试一下:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 4, 16);
d1 << cout; //相当于是这样的:d1.operator<<(&d1, cout);
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
//可以看出这是不符合常规调用的
return 0;
}
这里作为类内成员函数进行重载时,调用的时候会发现不符合常规的调用形式,在形式上反了过来。
这是因为成员函数的参数列表所导致的。
因为成员函数的第一个默认的隐式参数是this,这就导致了在隐式调用重载函数好似,必须要将对象在重载操作符的前面。
在操作符重载那节,我们知道成员函数的特征标(参数列表)中的参数的先后次序,就决定了出现在重载操作符的位置次序。
综上,我们要知道,若想重载流插入和流提取运算符,必须要以全局式重载实现。
此时friend关键字的作用也十分清晰,friend关键字用于将普通函数变成类中的友元函数。当成为友元函数后,该函数便可以访问类内的成员们。
注意:friend 函数声明
friend友元函数声明写在类内,它可以写在类内任意的位置,不受权限的影响。一般的话都是放在类内最上面一行。
总结一下友元函数的特性:
- 1.友元函数可以访问类的私有和保护成员,但不是类的成员函数。
- 2.友元函数不能用const修饰。
- 3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- 4.一个函数可以是多个类的友元函数。
- 5.友元函数的调用与普通函数的调用原理相同。
II.友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
- 友元关系不能传递。
- 友元关系不能继承。
#include <iostream>
using namespace std;
class Time
{
friend class Date;
//声明我Date类是你Time类的友元
//那么在Date类中就可以访问Time类的私有成员
//此时Time类并不能访问Date类的私有成员 注意
//注意:friend给予的权限是单向的
public:
Time(int hour = 1, int minute = 1, int second = 1)
:_hour(hour)
,_minute(minute)
,_second(second)
{
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
//直接访问Time类的私有成员
_t._hour++;
}
void SetTimeDate(int hour, int minute, int second)
{
//直接访问Time类的私有成员
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year = 1970;
int _month = 1;
int _day = 1;
Time _t;
};
我们在Time类中声明Date类是Time类的友元。
这样使得Date类中所有的成员函数,都成为了Time类的友元函数。
那么在Date类的所有成员函数中,都可以访问Time类的私有成员。
注意:
这里要特别强调一下友元类的特性1:友元关系是单向的,不具有交换性。
我们拿上面的例子来说,此时Date类作为Time类的友元,但是由于只声明了Date类是Time类的友元,并没有在Date类中声明Time类是Date类的友元,所以此时Time类并不是Date类的友元。因为友元关系是单向的权限,不是相互的。
无论是友元函数还是友元类,我们发现通过实现友元,那么原本为private权限的类成员,将可以被直接访问。
从某种程度上讲,这破坏了" 封装 "的特性。因此不建议过多使用友元,除非没有其他方法。
三丶内部类
I. 引入
定义:如果一个类定义在另一个类的内部,这个内部的类就叫做内部类。
说明:内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:
内部类就是外部类的友元,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类的所有成员。但是外部类不是内部类的友元。
#include <iostream>
using namespace std;
class A
{
private:
static int k;
int h;
public:
void func()
{
//无法访问内部类B的成员
}
//内部类B
//此类为独立的类 只不过放到A里面
//仅仅受到类域的作用域限制
//且B天生是A的友元 类B可以访问类A的私有成员
class B
{
public:
void foo(A& a)
{
//可以访问外部类A的所有成员
cout << a.h << endl;
a.func();
}
private:
int _b;
};
};
II.特性
内部类的特性:
- 1.内部类可以定义在外部类的public丶protecte丶private都是可以的,在不同的权限下将影响着内部类是否能通过外部类作用域访问到。
- 2.内部类可以直接访问外部类中的static成员,不需要外部类的对象或类名。
- 3.sizeof(外部类)=外部类本身的大小,和内部类没有关系,外部类和内部类是两两独立的。
#include <iostream>
using namespace std;
class A
{
private:
static int k;
int h = 1;
public:
void func()
{
//无法访问内部类B的成员
}
class B
{
public:
void foo(A& a)
{
//可以访问外部类A的所有成员
cout << a.h << endl;
//内部类可以直接访问外部类中的static成员,不需要外部类的对象或类名。
cout << k << endl;
a.func();
}
private:
int _b = 0;
};
};
int A::k = 0;
int main()
{
A a1;
cout << sizeof(a1) << endl; //大小为4
//外被类A和内部类B两两独立
//在空间存在上也是
A::B b1;
//内部类B处于public权限
//因此可以通过类作用域符搜查到
//B b2; //不允许
return 0;
}
简单说明一下上面的代码:
“sizeof(a1)”;由于内部类A和外部类B的空间独立,此时只是外部类A类被创建了出来,并计算其大小,因此为4;
“A::B b1; ”创建内部类对象,内部类B不可直接创建,比如“B b1”这样;因为类B作为内部类,受作用域限制,它无法在全局被直接检索到,这意味着向“B b1”这样的代码是无法通过的,必须要借助作用域限定符来找到内部类B存在的作用域。
我们知道内部类B在外部类A的作用域中,因此通过外部类A的作用域限定符找到了内部类B的作用域,便可以访问和创建内部类B对象。
本博客仅供个人参考,如有错误请多多包含。
Aruinsches-C++日志-4/20/2024