C++ 类中的static成员的使用及单例设计示例


静态成员:被static修饰的成员变量\函数

可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员)

static 静态成员变量

静态成员变量

  • 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存
  • 对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的。
  • 必须初始化,必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离(在实现.cpp中初始化)。
class Foo
{
private:
	//static静态变量无法在类中初始化
	static int ms_age;
	//与普通的全局变量的区别是 作用域 的权限不同,static类成员变量受到类的权限控制
public:
	void test()
	{
		cout << ms_age << endl;
	}
	//提供接口以用来访问与修改静态成员变量
	int& getMS_age()
	{
		return ms_age;
	}
};

int Foo::ms_age = 1;	//在类外使用::作用域解析初始化

int main()
{
	Foo a;
	a.test();
	//两个对象共享同一个static静态对象,static位于全局区,整个程序运行时只存在这一个
	Foo b;
	b.test();

	/*
	mov         dword ptr [Foo::age (07FF768C2C000h)],64h
	mov         dword ptr [Foo::age (07FF768C2C000h)],0C8h
	*/
	//调用的是同一个static静态成员变量
	a.getMS_age() = 100;
	b.getMS_age() = 200;
	a.test();
	b.test();

	return 0;
}

注意点:

  • 在类中用static声明的变量是 静态变量,它存储在数据段(全局区,类似于全局变量),在整个程序运行时期只会有一份内存
  • 我们在一个类对象中修改的static静态成员变量会保持修改,则下一个类打印会发现static变量就不是以前的数据了,因为他类似于全局变量,相当于对全局变量进行修改。

两个类是否真的共用同一个static静态变量?我们来试验一下:

int main()
{
	Foo a;
	Foo b;
	//ms_age为类中的static静态成员变量
	a.ms_age=100;
	b.ms_age=200;
	
	return 0;
}

进入反汇编:

mov         dword ptr [Foo::age (07FF768C2C000h)],64h
mov         dword ptr [Foo::age (07FF768C2C000h)],0C8h

可以看到: 07FF768C2C000h 即是static变量的内存空间,a,b两个类的此变量的地址一样,说明他们共享同一个static变量,更加证明了类中的static变量其实就相当于全局变量,在整个程序运行期间不变且只有唯一的一个


那么? 类中的static静态成员变量和真正的全局变量 又有什么不同呢?

  1. 访问权限不同:全局变量默认public,类中的static受到类的权限限定符的控制
class A{
private:
	static int a;	//位于private中,私有化
public:
	int& getA()
	{
		return a;	//返回此static变量
	}	
}

int abc;	//全局
int main()
{
	cout<<abc <<endl;	//随意访问全局变量
	cout<<A::a<<endl;	//无法直接访问类中的static
	
	//提供一个接口来访问
	getA()=100;	//通过接口就可以访问了
  1. 类中static的成员变量的初始化,注意:不可以直接在类内初始化,要在类外初始化,并且要去掉static修饰符,加上类名作用域::
class A{
private:
	static int a;	//位于private中,私有化
}
int A::a=0;	//类外初始化
  1. 访问类中static变量的方法: 我们要尽量使用类名直接访问static,因为他们本来就相当于全局的,否则容易产生混淆
int main()
{
	Foo* p = new Foo;
	p->ms_age = 100;		//OK:指针访问
	a.ms_age = 200;			//OK:栈对象访问
	Foo::ms_age = 300;		//OK(推荐):类名直接访问
	return 0;
}

static静态成员函数

静态成员函数

  • 内部不能使用this指针(this指针只能用在非静态成员函数内部)
  • 不能是虚函数(虚函数只能是非静态成员函数)
  • 内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数
  • 非静态成员函数内部可以访问静态成员变量\函数
  • 构造函数、析构函数不能是静态
  • 当声明和实现分离时,实现部分不能带static
class Foo
{
public:
	//静态函数内存不位于类中,它在外部定义,与类无关
	static void test()
	{
		//静态成员变量可以随便访问
		cout << "<static> test():" << count << endl;
		//testTemp();	
		//cout << "age:" << age << endl;  
		//非静态成员函数无法访问age属性,因为此age不属于任何对象,也无法访问非静态成员函数,同样它需要一个对象,但是此静态函数位于外部的全局区,它不属于类中,因此没有对象的概念,无法直接使用非静态成员函数。
		Foo a;
		a.age = 50;	//但是可以创建一个临时的对象,利用对象来使用age成员变量
		a.testTemp();

	}
	void testTemp()
	{		
		cout << "testTemp()\n";
	}
	void test3()
	{
		//可以随意访问静态成员函数与变量
		count = 220;
		test();
		testTemp();
	}
private:
	static int count;
	int age = 10;
};

int Foo::count = 0;

Foo g;
int main()
{
	Foo a;
	Foo* b = new Foo;

	//任意类型创建的对象都可以正常访问静态函数
	a.test();
	b->test();
	g.test();

	//可以用类名直接指定此静态函数
	Foo::test();


	return 0;
}

static静态成员函数:相当于外部的全局的函数它虽然在类中,但是和类没有半毛钱关系,它的内存位于全局区。


  1. 无法使用this 指针,并且不能访问非静态成员变量\函数,只能访问静态static成员变量\函数。
class A{
private:
	int a=20;
	static int b;
public:
	static void test(){
		this->a=50;	//ERROR:this指针
		a=100;		//ERROR: 相当于this指针
		A temp;
		temp.a=500;	//OK:通过临时对象来访问temp的变量(闲的没事干)
		
		b=500;		//OK:可以访问类的static静态成员函数,相当于全局变量

		fun();		//ERROR:无法访问非静态成员函数
	}
	void fun();		//一个非静态成员函数
	
	void fun2()
	{
		test();		//OK:可以使用非静态成员函数访问static成员函数
	}
}

int A::b=20;

Error: 因为this指针表示某个类对象把它所在的地址传给了this指针,然后可以通过this访问其所有的变量,但是static静态成员函数位于全局区,所以根据this获取不到某个对象的地址,并且不使用this,想要获取类成员函数也是不可能的(除非你创建一个临时的对象),并且也无法访问非静态成员函数,相反则可以。
但可以访问类的static静态变量,和其他静态函数。
在这里插入图片描述

  1. 静态成员函数不能是虚函数,不能使构造函数,不能是析构函数

在这里插入图片描述 在这里插入图片描述

  1. 声明和实现分离时,实现的时候不要加static修饰符。

单例设计模式

什么是单例设计模式?
在我们的程序运行中只允许存在唯一一个对象,不管我们调用了多少个创建对象,我们始终都是得到唯一一个。

三要素:

  1. 构造函数必须私有化
  2. 必须具有一个唯一的static静态变量成为那个单例
  3. 提供公共的访问对象的接口

我们来一步步实现,我们既然要创建唯一一个对象,则不允许对象随意利用构造函数和析构函数。

注意: 所有的类的函数都是static的静态成员函数,这样我们就可以通过类名直接调用

  1. 把构造和析构设置为私有,并且创建唯一的单例成为静态成员变量
class Res
{
public:
private:
	static Res* p;		//这就是我们的单例对象,是唯一的
	Res(){}
	~Res() {}	//防止在外部使用delete
};
  1. 单例的对象必须由我们亲自初始化: 给他一个构造对象的共有方法:p为空的时候才构造,这就实现了我们如果调用了多次创建对象的函数,但是只能创建一个对象。
static Res* CreatePoint()
{
	if (p == nullptr)
	{
		p = new Res();	//调用构造函数
	}
	return p;
}
  1. 单例的销毁类成员函数:
static void Destroy()
{
	if (p != nullptr)
	{
		delete p;
		p = nullptr;		//注意此处重新置空,这样才能下一次创建对象
	}
}
  1. 测试:
int main()
	{
		//不管你创建多少次指针变量,他们都是同一个对象
		Res* p = Res::CreatePoint();	//只会创建这一个
		p->test();
		
		Res* p1 = Res::CreatePoint();
		p1->test();
		Res* p2 = Res::CreatePoint();
		p2->test();
		Res* p3 = Res::CreatePoint();
		p3->test();

		p->Destroy();		//只会销毁这一次
		
		p1->Destroy();
		p2->Destroy();
		p3->Destroy();

		//delete p;	//析构私有,防止使用delete

		return 0;

	}

单例实现图片资源的封装(很实用)

在我们游戏制作中很好用,比如逆向做一个飞机大战,你可以封装这样一个单例,把所有的资源都加载进map容器中(键–值对),根据类的enum枚举可以选取每一张特定的图片,大家仔细体会:

  1. 头文件
class Res
{
public:
	enum class TYPE { plane, mm, bullet,往左走 };
	static Res* GetRes();	
	static void Draw(int x,int y,TYPE name);
	static void Draw动画(int x, int y, TYPE name, int frame);
	static void Destroy();
private:
	static Res* res;		//单例对象
	static map<TYPE, IMAGE*> image;

	//私有构造函数和析构函数
	Res();
	~Res() {}
};
  1. 实现文件,我们使用了map集合。
/*
注意:首先在类的外部定义类的static成员变量 
*/
map<Res::TYPE, IMAGE*> Res::image{};
Res* Res::res = nullptr;		//初始化单例对象

/*
注意: static成员函数的声明和实现分离时,实现要去掉static的修饰符
*/
Res* Res::GetRes(){
	if (res == nullptr)
	{
		res = new Res;	//创建单例对象
	}
	return res;
}
void Res::Draw(int x,int y,TYPE name){	//绘制静态图片
	putimage(x, y,GetRes()->image[name]);
}

void Res::Draw动画(int x, int y, TYPE name, int frame){	//绘制连环画 :一系列动画的集合  
	/*
	连环画,每个图片有八张连续的动作图片,每一帧换一张图片。即用帧数乘以宽度可以得到下一张图片,
	利用putimage的裁剪图片的功能。
	*/
	putimage(x, y, 400, 300, GetRes()->image[name]+0, frame * 400, 0, SRCAND);		//掩码图
	putimage(x, y, 400, 300, GetRes()->image[name]+1, frame * 400, 0, SRCPAINT);	//原图
}
//单例的销毁
void Res::Destroy(){
	if (res != nullptr)
	{
		delete res;
		res = nullptr;
	}
}

Res::Res(){
	/*
	存储图片资源
	*/
	image[Res::TYPE::plane] = new IMAGE;
	loadimage(image[Res::TYPE::plane], "./Res/plane.jpg");
	image[Res::TYPE::bullet] = new IMAGE;
	loadimage(image[Res::TYPE::bullet], "./Res/bullet.jpg");
	image[Res::TYPE::mm] = new IMAGE;
	loadimage(image[Res::TYPE::mm], "./Res/mm.jpg");
	image[Res::TYPE::往左走] = new IMAGE[2];
	loadimage(image[Res::TYPE::往左走] + 0, "./Res/lefty.bmp");
	loadimage(image[Res::TYPE::往左走] + 1, "./Res/left.bmp");

}
  1. 简单测试:
int main()
{

	initgraph(800, 600,SHOWCONSOLE);
	setbkcolor(WHITE);
	cleardevice();
	//你想画什么,直接Draw即可,在Draw的时候,会创建单例对象,且只创建一次
	Res::Draw(50, 100, Res::TYPE::plane);
	Res::Draw(50, 30, Res::TYPE::bullet);
	Res::Draw(200, 200, Res::TYPE::mm);
	Res::Destroy();	//销毁
	system("pause");
	closegraph();
	return 0;
}

在这里插入图片描述

posted @ 2022-09-27 18:02  hugeYlh  阅读(276)  评论(0编辑  收藏  举报  来源