深入学习C++——21~23静态

11 篇文章 0 订阅

深入学习C++——21~23静态

static在C++中有两种含义,分为类和结构体外的静态类和结构体内的静态

21.类和结构体外的静态

类和结构体外部的static,意味着你声明为static的符号只在当前文件内部链接,它只对它所在的翻译单元可见。有关翻译单元请看深入学习C++——6~7编译器和链接器

新建一个源文件命名为CStatic.cpp,在main.cppCstatic.cpp中同时定义一个变量和一个函数,编译器在链接阶段会报错multiple definition of XXX,这是因为这个变量/函数已经在另一个编译单元中定义了。所以我们不能有两个同名的全局变量/函数。其中一种修改方式是使用extern关键字,extern意味着它会在外部翻译单元中寻找这个变量/函数。我们把main.cpp中的定义加上static,再次编译无报错。

/*CStatic.cpp*/
int Variable = 5;
void Function()
{
	std::cout << "Function in CStatic" << std::endl;
}

/*main.cpp*/
//int Variable = 5; //multiple definition of Variable
//void Function() //multiple definition of `Function()'
//{
//	std::cout << "Variable" << std::endl;
//}
extern int Variable;
extern void Function(); //函数默认都是extern,这里删掉extern也可以编译通过
int main()
{
	Function();
	std::cout << Variable << std::endl;
}

另一种解决方法是使用static关键字。static的意思是这个变量/函数只会在这个翻译单元内部链接。这有点像在类中定义私有变量/函数,其他所有的翻译单元都不能看到这个变量/函数。链接器在全局作用域下将不会看到这个变量/函数。把CStatic.cpp中的变量与函数均改成静态的,编译会报错,因为他们在main中不可见,跨翻译单元是找不到他的。将main.cpp中的变量和函数修改为全局的,此时编译通过,且输出的是main中定义的变量值和函数。

/*CStatic.cpp*/
static int Variable = 5;
static void Function()
{
	std::cout << "Function in CStatic" << std::endl;
}

/*main.cpp*/
//extern int Variable; //undefined reference to `Function()'
//extern void Function(); //undefined reference to `Variable'
int Variable = 10;
void Function()
{
	std::cout << "Function in main" << std::endl;
}
int main()
{
	Function();
	std::cout << Variable << std::endl;
}

在类和结构体外使用静态,意味着当你声明静态函数和静态变量时,它只会在它被声明的C++文件中被“看到”。如果在一个头文件中声明静态变量并将该头文件包含在两个不同的C++文件中,就相当于在两个翻译单元中都声明了那个变量为静态变量。(包含头文件时,编译器会复制所有头文件中的内容到C++文件中)

为什么要用static?可以类比为什么要在类中用private。当不需要变量是全局变量时,尽可能地用静态变量。因为一旦在全局作用域下声明东西的时候,编译器会跨编译单元进行链接,这个变量在任何地方都可以被使用,可能会导致一些很严重的bug。

综上,尽可能地标记函数或变量为静态的,除非你真的需要他们跨翻译单元链接。

22.类和结构体内的静态

在类和结构体内部定义的静态变量,在类的所有实例中这个变量只有一个实例,这意味着该变量实际上将与类的所有实例共享内存。对于静态函数来说,没有实例会传递给一个静态函数。

下面用结构体举例子(类也一样,只是因为结构体默认是public的)

struct Entity
{
	int num;
	void Print()
	{
		std::cout << num << std::endl;
	}
};

int main()
{
	Entity e1;
	e1.num = 2;

	Entity e2;
	e2.num = 5;

	e1.Print();
	e2.Print();
}

很显然,这样会输出2和5。如果将结构体内的x和y变成静态的,会报错undefined reference to ‘Entity::num’,因为x和y要在某个地方被定义,加上int Entity::num;,程序修改为:

struct Entity
{
	static int num;
	void Print()
	{
		std::cout << num << std::endl;
	}
};

int Entity::num;

int main()
{
	Entity e1;
	e1.num = 2;

	Entity e2;
	e2.num = 5;

	e1.Print();
	e2.Print();
}

这时运行会输出5和5.这是因为num变量在Entity类的所有实例中只有一个实例,这意味着e1.num和e2.num指向的是相同的内存,所以e1.nume2.num这样写是没有意义的。可以直接写成Entity::num = 5。这就像是在一个名为Entity的命名空间中创建了一个变量,他们实际上并不属于类,但是他们可以是private的也可以是public的,所以他们仍是类的一部分。但是在应用上来说他们其实和在命名空间中一样。

静态方法跟静态变量一样,如果将Print也改为静态的,调用时同样需要使用Entity::Print();

但静态方法不能访问非静态变量,将num改为非静态变量,Print保持为静态方法,这时编译会报错error: invalid use of member 'Entity::num' in static member function,原因是静态方法没有类实例。每个非静态方法总是会获得当前类的一个实例作为参数,这我们是看不见的,在底层通过隐藏参数发挥作用,而静态方法不会得到那个隐藏参数。类中的静态方法拿到类外面在编译的时候实际上是这个样子的,实际上传进去了一个实例参数,这样就不会报错:

struct Entity
{
	int num;
};
static void Print(Entity e)
{
	std::cout << e.num << std::endl;
}

int main()
{
	Entity e;
	e.num = 5;
	Print(e);
}

综上所述,当你需要跨类使用变量时,类内静态变量将会派上用场。那这么说创建一个全局变量或者静态变量不也一样吗?NO!如果你有一条消息,想要在所有实例之间共享数据,把这个消息变量放在类中是有意义的,因为它在逻辑上跟这个类有关。要想组织好你的代码,最好在类中创建一个静态变量,而不是将全局或者静态变量到处乱写。

23.局部静态

局部静态允许我们创建一个变量,它的生存周期基本相当于整个程序的生存期,但是作用范围被限制在这个域中。来看一段程序:

void Function()
{
	int i = 0;
	i++;
	std::cout << i << std::endl;
}
int main()
{
	Function();
	Function();
	Function();
	Function();
	Function();
}

显而易见输出为11111。要是想将i每次递增实现输出12345,你的第一反应可能是将i改为全局变量。但是这样做会使每个人都能访问这个变量,如果要避免这个问题,可以在局部作用域下将i声明为static。这样程序也可以输出12345,与全局变量效果相同,但是此时i只是函数作用域下的局部变量。

使用局部静态的主要作用是可以使代码更干净,我们来看另一个例子。创建一个单例类(单例类是只存在一个实例的类),如果不使用静态局部作用域来创建单例类,就需要创建静态的单例实例,可能是一个指针,并返回一个引用,即为创建的实例:

class Singleton
{
private:
	static Singleton* s_Instance;
public:
	static Singleton& Get()
	{
		return *s_Instance;
	};

	void Hello()
	{
		std::cout << "Hello" << std::endl;
	}
};

Singleton* Singleton::s_Instance = nullptr;

int main()
{
	Singleton::Get().Hello();
}

如果使用局部静态来创建,代码会变得干净很多:

class Singleton
{
public:
	static Singleton& Get()
	{
		static Singleton instance;
		return instance;
	};
	void Hello()
	{
		std::cout << "Hello" << std::endl;
	}
};

int main()
{
	Singleton::Get().Hello();
}

这段代码里如果没有static,当代码运行到函数右花括号处,即函数作用域结束时,instance就会被销毁。通过添加static,它的生存周期被延长到永远,在第一次调用Get的时候,实际上会构造一个单例实例,接下来它只会返回这个已存在的实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范子琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值