静态成员、友元、常成员、对象成员

今天刚刚结束了C++的第五周课程,老师讲的非常的好,本人收获颇丰,故此记录,以备复习之用。本文主要的内容就是围绕着静态成员,友元,常成员和成员对象展开的。

每一讲课的知识点,老师都是用一个程序为例子来讲解的,所以开篇点题,先把需要用到的类拿出来使用

第一个是CMyDate的类

1
2
3
4
5
6
7
8
9
10
11
class CMyDate {
private:
int nYear;
int nMonth;
int nDay;
public:
CMyDate(int nYear, int nMonth, int nDay) :nYear(nYear),nMonth(nMonth),nDay(nDay){}
void Display() {
cout << nYear <<"-"<< nMonth <<"-"<< nDay;
}
};

第二个是CPlayer的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class CPlayer {
private:
//常数据成员,必须在对象创建时初始化
const int NameSize;//姓名长度
int nId;
char sName[11];
CMyDate birthday;//对象成员,若该成员必须【带参构造】,则必须在对象创建时实施

public:
CPlayer(int nIld = 0, const char* name = NULL);//构造函数
~CPlayer();
//当进入到构造函数{}里面,表示对象构造完毕
//故:常数据成员不能在{}里面初始化
CPlayer(const CPlayer& p):NameSize(11),birthday(2003 ,06 ,14){
this->nId = p.nId;
strcpy(this->sName, p.sName);
this->nObjNum++;//如果不考虑计数,只需要浅拷贝就行了
//若不需要【深拷贝】,则可以直接对象赋值
//*this = p;//this 指向当前对象的指针,*this就是当前对象,省去了上面的步骤
}
private:
//静态成员不属于对象,而是属于类,它必须进行【类外定义】
static int nObjNum;//此处仅仅是个声明,需要去外部定义
public:
//提供访问nObjNum的对外接口
static int getObjNum() {//只读 如果想要可读可写,应该是static int& getObjNum();
return nObjNum;
}
//声明友元函数,则该函数可访问private,protected中的成员
//友元机制破坏了封装机制,一般不要使用
//新兴语言例如java取消了友元机制
//友元时单方面的给予,而不是相互的
friend void showPlayerNum();
};

主程序与函数定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//静态数据成员定义与初始化
int CPlayer::nObjNum = 0;
/*
在外部定义成员函数:
1.域限定: CPlay::
2.不要写参数默认值
*/
CPlayer::CPlayer(int nId, const char* name):NameSize(11), birthday(2003, 06, 14) {//表示NameSize在对象创建时初始化
nObjNum++;
this->nId = nId;
if (name) {
strcpy(sName, name);
}
else {
strcpy(sName, "未命名");
}
}
CPlayer::~CPlayer() {
nObjNum--;
}
void showPlayerNum() {
cout << "玩家人数" << CPlayer::getObjNum() << endl;
}
int main(void) {
CPlayer p1, p2;
CPlayer* p = new CPlayer(10086,"张三");
CPlayer p3(p1);
cout << "玩家人数" << CPlayer::getObjNum() << endl;
delete p;
p = new CPlayer[100];
cout << "玩家人数" << CPlayer::getObjNum() << endl;
delete[] p;
cout << "玩家人数" << CPlayer::getObjNum() << endl;
return 0;
}

其实大部分的知识点都在注释里面了,但是需要剖丝抽茧的把他细细讲解一下,从而可以更加的清楚

静态成员

什么是静态成员:在C++中,我们用static关键字来表示静态成员,例如在Cplayer类中定义了static int nObjNum;

存储位置:与C语言相似,静态成员与类的对象不在同一个存储位置,而是当类中声明静态成员的时候,就会自动为该成员分配一块空间

​ 静态成员存储在全局数据区,这也是一个类不同的对象都共享这一个变量的原因。

静态成员的变量不属于对象,而是属于类,所以他必须进行类外定义

实际上,并不需要像程序中那样,专门的进行**int CPlayer::nObjNum = 0;**的初始化,不赋值则默认的初始化是0

(补充知识点)一个完整程序的内存分布如下

  1. 代码区:存放函数体的二进制代码,由操作系统进行管理的
  2. 全局数据区:存放静态数据(即使是函数内部的静态局部变量)和全局变量,全局数据区的数据不会因为函数的退出而释放空间
  3. 栈区:存放程序内部定义的局部变量,由系统控制,随着程序的退出自动释放空间
  4. 堆区:存放函数内部new出来的动态数据

参考资料

类的静态成员,菜鸟教程

常成员

​ 什么是常成员:用const来修饰的成员,我们称之为常成员。在该程序中我们用const int NameSize来构造了一个常成员的函数用来修饰姓名长度。需要注意的地方时,在构造函数的时候,对于常成员函数的初始化,必须要在对象构造之前初始化,那什么时候是对象构造之前呢?我们知道,每个对象的初始化都是由析构函数来进行构造的,而且析构函数在函数体中, 将我们想要初始化的值,赋值给对象的private变量,那么我们要求在对象构造之前初始化,实际上就是要求在还没有进入构造函数的函数体的时候就初始化,更简单的来说,就是在{}之前进行初始化。所以,对于常成员的初始化,我们可以采用如下的形式:

1
CPlayer::CPlayer(int nId, const char* name):NameSize(11), birthday(2003, 06, 14)

此外,不仅仅是在构造函数中要求常成员提前初始化,事实上,只要涉及到对象中常成员的变化,就必须使得常成员初始化。

说明,对于NameSize(11),就是带参构造,其中括号内11就是给NameSize赋值11的意思。

对象成员

什么是对象成员:还是先看程序,在本程序中,我们首先定义了一个CMyDate的类,然后再CPlayer这个类中定义了一个CMyDate birthday,那么这个成员我们就称之为对象成员。

对象成员的构造:和常成员一样,对象成员必须在对象构造之间带参构造,具体代码和常成员相同。

构造同时赋值

事实上,不仅仅是常成员和对象成员可以在函数构造的同时赋值,对于任何一个成员来说,都可以在函数构造的同时赋值,具体的例子在程序中也有所展示

1
CMyDate(int nYear, int nMonth, int nDay) :nYear(nYear),nMonth(nMonth),nDay(nDay){}

在CMyDate中构造函数就可以实现带参赋值,这里有一点需要注意的是

不可以使用例如this->nYear(nYear)在函数构造同时赋值

原因是:this指针就是指向对象的指针,但是当我们在函数体外边赋值的时候,对象还没有构造完成,此时this指针并没有相应的指向,所以this->nYear会报错!!

友元函数

最后一个是友元函数,其实友元函数并不是一个好的机制,它并不是类的成员函数,要求声明在类中,但是定义要在类的外部。

为什么称它为友元,因为它不是类的成员函数,但是却可以直接访问类的私有成员变量

关键字:friend

友元类

这部分是拓展内容,详情请见

友元类,菜鸟教程