1.7 静态类成员
类的成员一般通过对象来访问,不可以通过类名直接访问。但是如果将类成员定义为静态类成员,则允许使用类名直接访问。静态类成员是在类成员定义前使用static关键字进行标识。
class CBook
{
public:
static unsigned int m_Price; // 定义一个静态数据成员
};
在定义静态数据成员时,通常需要在类外部对静态数据成员进行初始化。例如:
unsigned int CBook ::m_Price = 10;
对于静态类成员,则不仅可以使用对象访问,也可以使用类名直接访问。
int main()
{
CBook book;
printf("%d\n", CBook::m_Price);
printf("%d\n", book.m_Price);
return 0;
}
在一个类中,静态数据成员是被所有的类的对象所共享的,这就意味着无论定义多少个类对象,静态数据成员只有一份,同时,如果某个类对象改变了该静态数据成员,其他对象的静态数据成员也将会改变,因为是同一个静态数据成员(静态数据成员同时属于全世界~)。
class CBook
{
public:
static unsigned int m_Price;
};
unsigned int CBook ::m_Price = 10;
int main()
{
CBook book ,vbook;
printf("%d\n", CBook::m_Price);
printf("%d\n", book.m_Price);
vbook.m_Price = 100;
printf("%d\n", CBook::m_Price);
printf("%d\n", book.m_Price);
return 0;
}
输出为:
对于静态类的数据成员,还需要注意以下几点;
(1) 静态数据成员可以是当前类的类型,而其他数据成员只能是当前类的指针或者引用类型。
在定义类的成员时,对于静态数据成员,其类型可以是当前类的类型,而非静态数据成员则是不可以的,除非数据成员为当前类的指针或者引用类型。
(2) 静态数据成员可以作为成员函数的默认参数。
在定义类的成员函数是,可以为成员函数指定默认参数,其参数的默认值也可以是类的静态数据成员,但是普通的数据成员则不能作为成员函数的默认参数。
1.8 嵌套类和局部类
嵌套类 : 一个A类中定义另外一个类B,B称之为嵌套类。
嵌套类B对于外围类A,A通常是不能访问嵌套类B的私有成员,只有在定义嵌套类B时,将类A定义为类B的友元类,这就可以访问嵌套类的私有成员了。
局部类 : 将类的定义放置在函数中,这样的类称之为局部类。
局部类中函数之外是不能被访问的,被局部封装在函数的局部作用域中,在局部类中,用户也可以再定义一个嵌套类。
1.9 类模板的定义和应用
在介绍类模板之前,先来设计一个简单的单项链表。在链表的功能包括向尾节点添加数据,遍历链表中的节点、在链表结束时释放所有的节点。代码如下:
#include "stdafx.h"
class CNode //定义一个节点类
{
public:
CNode *m_pNext; //定义一个节点指针,指向下一个节点
int m_Data; //定义节点的数据
CNode() //定义节点类的构造函数
{
m_pNext = NULL; //将m_pNext设置为空
}
};
class CList //定义链表类CList类
{
private:
CNode *m_pHeader; //定义头节点
int m_NodeSum; //节点数量
public:
CList() //定义链表的构造函数
{
m_pHeader = NULL; //初始化m_pHeader
m_NodeSum = 0; //初始化m_NodeSum
}
CNode* MoveTrail() //移动到尾节点
{
CNode* pTmp = m_pHeader; //定义一个临时节点,将其指向头节点
for (int i = 1; i<m_NodeSum; i++) //遍历节点
{
pTmp = pTmp->m_pNext; //获取下一个节点
}
return pTmp; //返回尾节点
}
void AddNode(CNode *pNode) //添加节点
{
if (m_NodeSum == 0) //判断链表是否为空
{
m_pHeader = pNode; //将节点添加到头节点中
}
else //链表不为空
{
CNode* pTrail = MoveTrail(); //搜索尾节点
pTrail->m_pNext = pNode; //在尾节点处添加节点
}
m_NodeSum++; //使链表节点数量加1
}
void PassList() //遍历链表
{
if (m_NodeSum > 0) //判断链表是否为空
{
CNode* pTmp = m_pHeader; //定义一个临时节点,将其指向头节点
printf("%4d", pTmp->m_Data); //输出节点数据
for (int i = 1; i<m_NodeSum; i++) //遍历其他节点
{
pTmp = pTmp->m_pNext; //获取下一个节点
printf("%4d", pTmp->m_Data); //输出节点数据
}
}
}
~CList() //定义链表析构函数
{
if (m_NodeSum > 0) //链表不为空
{
CNode *pDelete = m_pHeader; //定义一个临时节点,指向头节点
CNode *pTmp = NULL; //定义一个临时节点
for (int i = 0; i< m_NodeSum; i++) //遍历节点
{
pTmp = pDelete->m_pNext; //获取下一个节点
delete pDelete; //释放当前节点
pDelete = pTmp; //将下一个节点设置为当前节点
}
m_NodeSum = 0; //将m_NodeSum设置为0
pDelete = NULL; //将pDelete置为空
pTmp = NULL; //将pTmp置为空
}
m_pHeader = NULL; //将m_pHeader置为空
}
};
下面顶一个链表对象,向其中添加节点,然后遍历链表节点。
int main(int argc, char* argv[])
{
CList list; //定义链表对象
for (int i = 0; i<5; i++) //利用循环向链表中添加5个节点
{
CNode *pNode = new CNode(); //构造节点对象
pNode->m_Data = i; //设置节点数据
list.AddNode(pNode); //添加节点到链表
}
list.PassList(); //遍历节点
printf("\n"); //输出换行
return 0;
}
分析上述代码中顶一个链表类CList,一个最大的缺陷就是不够灵活,其节点只能是CNode类型,为了让CList能够适应各种类型的节点,一个最简单的方法,就是使用类模板。类模板的定义与函数模板类似,以关键字template开始,其后由尖括号<>构成的模板参数。
下面重新定义CList,以类模板的形式进行改写。
template <class Type> //定义类模板
// ClassTemplate.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
template <class Type> //定义类模板
class CList //定义CList类
{
private:
Type *m_pHeader; //定义头节点
int m_NodeSum; //节点数量
public:
CList() //定义构造函数
{
m_pHeader = NULL; //将m_pHeader置为空
m_NodeSum = 0; //将m_NodeSum置为0
}
Type* MoveTrail() //获取尾节点
{
Type *pTmp = m_pHeader; //定义一个临时节点,将其指向头节点
for (int i=1;i<m_NodeSum;i++) //遍历链表
{
pTmp = pTmp->m_pNext; //将下一个节点指向当前节点
}
return pTmp; //返回尾节点
}
void AddNode(Type *pNode) //添加节点
{
if (m_NodeSum == 0) //判断链表是否为空
{
m_pHeader = pNode; //在头节点处添加节点
}
else //链表不为空
{
Type* pTrail = MoveTrail(); //获取尾节点
pTrail->m_pNext = pNode; //在尾节点处添加节点
}
m_NodeSum++; //使节点数量加1
}
void PassList() //遍历链表
{
if (m_NodeSum > 0) //判断链表是否为空
{
Type* pTmp = m_pHeader; //定义一个临时节点,将其指向头节点
printf("%4d",pTmp->m_Data); //输出头节点数据
for (int i=1;i<m_NodeSum;i++) //利用循环访问节点
{
pTmp = pTmp->m_pNext; //获取下一个节点
printf("%4d",pTmp->m_Data); //输出节点数据
}
}
}
~CList() //定义析构函数
{
if (m_NodeSum > 0) //判断链表是否为空
{
Type *pDelete = m_pHeader; //定义一个临时节点,将其指向头节点
Type *pTmp = NULL; //定义一个临时节点
for(int i=0; i< m_NodeSum; i++) //利用循环遍历所有节点
{
pTmp = pDelete->m_pNext; //将下一个节点指向当前节点
delete pDelete; //释放当前节点
pDelete = pTmp; //将当前节点指向下一个节点
}
m_NodeSum = 0; //设置节点数量为0
pDelete = NULL; //将pDelete置为空
pTmp = NULL; //将pTmp置为空
}
m_pHeader = NULL; //将m_pHeader置为空
}
};
class CNode //定义一个节点类
{
public:
CNode *m_pNext; //定义一个节点指针,指向下一个节点
int m_Data; //定义节点的数据
CNode() //定义节点类的构造函数
{
m_pNext = NULL; //将m_pNext设置为空
}
};
每个模板参数必须由Class或者typename标识,不能够利用一个class或者typename关键字定义多个模板参数。
上述代码利用类模板对链表类CList进行了修改,实际上是在原来链表的基础上将链表中出现CNode类型的地方替换为模板参数type。下面可以再定义一个CNet类,演示类模板CList是如何食用不同节点类型的。
class CNet //定义一个节点类
{
public:
CNet *m_pNext; //定义一个节点类指针
char m_Data; //定义节点类的数据成员
CNet() //定义构造函数
{
m_pNext = NULL; //将m_pNext置为空
}
};
int main(int argc, char* argv[])
{
CList<CNode> nodelist; //构造一个类模板实例
for(int n=0; n<5; n++) //利用循环向链表中添加节点
{
CNode *pNode = new CNode(); //创建节点对象
pNode->m_Data = n; //设置节点数据
nodelist.AddNode(pNode); //向链表中添加节点
}
nodelist.PassList(); //遍历链表
printf("\n"); //输出换行
CList<CNet> netlist; //构造一个类模板实例
for(int i=0; i<5; i++) //利用循环向链表中添加节点
{
CNet *pNode = new CNet(); //创建节点对象
pNode->m_Data = 97+i; //设置节点数据
netlist.AddNode(pNode); //向链表中添加节点
}
netlist.PassList(); //遍历链表
printf("\n"); //输出换行
return 0;
}
输出如下: