C++ 中 Static 关键字的作用


Static 能够被用在一个类或者结构体的内部或者外部。

如果用在外部的话,意味着被你声明成 static 的变量只能作用于该定义的 Translation Unit.

如果用在内部的话,则意味着该 static 变量将会共享该类所有实例的内存空间。即在所有该类的实例中,该变量将只会有一个实例。如果某一个类的实例改变了该变量的值,则所有的类实例中该变量的值都将被改变。

Static 用在类或者结构体的外部

下面我将用例子来说明:

首先,在两个 cpp 文件 “static.cpp” 和 “main.cpp” 中分别定义同一个变量,其中一个定义为static。

//static.cpp
 static int s_Valuable = 5;
//main.cpp
#include <iostream>

int s_Valuable = 10;

int main()
{
	std::cout << s_Valuable << std::endl;	
	std::cin.get();
}

上述代码运行起来不会有任何问题,且输出为 10。

但是如果现在把 “static.cpp” 中变量的 static 去掉呢?

//static.cpp
int s_Valuable = 5;
Error	LNK1169	one or more multiply defined symbols found
Error	LNK2005	"int s_Valuable" (?s_Valuable@@3HA) already defined in Main.obj	

则这时会有 Link 错误产生: 变量 “s_Valuable” 被重复定义。
这是为什么呢?

如上述所说 ,static 定义在类或者结构体外部的变量或者函数只会存在于该定义的 Translation unit 中。 可以狭义的理解为 “static int s_Valuable” 只定义在 “static.cpp” 中, 就像该文件的私有变量一样。所以程序运行时,linker 不会将这两个相同的变量链接起来,因此不会产生 link 错误。

解决这个问题,我们可以在 mian.cpp 中的变量 “s_Valuable” 前面加上关键字 “extern”。

//main.cpp
#include <iostream>

extern int s_Valuable = 10;

int main()
{
	std::cout << s_Valuable << std::endl;	
	std::cin.get();
}

这时程序运行成功,并且输出结果为5. “extern” 关键字的作用为在外部的 Translation Unit 中寻找该变量 “s_Valuable” 的定义。所以输出结果为 “static.cpp” 中该变量的值。这种行为被称作 “external linking” 或者 “external linkage”。

如果这时,在 “static.cpp” 中再加上关键字 “static” 呢?

//static.cpp
 static int s_Valuable = 5;
Error	LNK2001	unresolved external symbol "int s_Valuable" (?s_Valuable@@3HA)

你又会看到 link error了。但是这次不同的是,Linker 在全局空间里找不到该变量的定义。

当然,上述规则也同样适用于函数。如果你在函数前面加上 static, 跟上述变量的结果是一样的。

另外,如果在一个 “.h” 文件中定义一个 static 函数或者变量呢?

假设你在不同的文件里 include 了这个 “.h” 文件,那么对于每一个文件而言,都相当于把 “.h” 文件中的代码复制到自己的内容里,所以相当于你直接复制了该 static 变量或者函数的定义,因此也不会有问题。

最后总结: 如果你不想要你定义的这个变量是一个全局的变量的话,即可以被该 solution 中所有的文件可见 ,只想要对当前文件或者当前 Translation Unit 可见的话,请将其声明为 static。

Static 用在类或者结构体的内部

还是举例说明:

//main.cpp
#include <iostream>

struct Entity
{
	int x, y;
	
	void print()
	{
		std::cout << x << ", " << y << std::endl;
	}
};

int main()
{
	Entity e;
	e.x = 2;
	e.y = 3;

	Entity e1 = {5, 8};

	e.print();
	e1.print();
}

这是一个非常简单的程序,“e” 和 “e1” 是结构体 “Entity” 的两个实例,然后分别被初始化为2,3 和 5,8。 很显然,该程序的输出为 2,3 和 5,8.

如果这时,将 “Entity” 中的变量定义为 static 呢?

struct Entity
{
	static int x, y;
	
	void print()
	{
		std::cout << x << ", " << y << std::endl;
	}
};

那么首先初始化语句 “Entity e1 = {5, 8}” 会失败,原因是变量 x, y 被声明成 static 之后就不再是类成员变量了。所以我们首先要重新初始化。

int main()
{
	Entity e;
	e.x = 2;
	e.y = 3;

	Entity e1;
	e1.x = 5; 
	e1.y = 8;

	e.print();
	e1.print();
}

运行代码,会出现以下的 Link error.

Error	LNK2001	unresolved external symbol "public: static int Entity::y" (?y@Entity@@2HA)	
Error	LNK2001	unresolved external symbol "public: static int Entity::x" (?x@Entity@@2HA)	
Error	LNK1120	2 unresolved externals	Practice	

这些 Error 是告诉我们 x 和 y 必须要被声明。

#include <iostream>

struct Entity
{
	static int x, y;

	void print()
	{
		std::cout << x << ", " << y << std::endl;
	}
};

int Entity::x;
int Entity::y;

int main()
{
	Entity e;
	e.x = 2;
	e.y = 3;

	Entity e1;
	e1.x = 5; 
	e1.y = 8;

	e.print();
	e1.print();
}

运行结果为: 5,8 和 5,8.

这是因为如上述所说,x,y 如果被声明为 static, 那么变量 x, y将只会有一个实例,所有的 Entity的实例都将共享 x, y的实例。也就是实际上,实例 e 和 e1 的 x, y 指向的是同一块内存空间。 所以即使上上述的代码可以等同于下面:

int main()
{
	Entity e;
	Entity::x = 2;
	Entity::y= 3;

	Entity e1;
	Entity::x = 5;
	Entity::y = 8;

	e.print();
	e1.print();
}

实际上,可以视为 变量 x, y 被声明在一个 Entity 的命名空间 (namespace)中,它们并不真的属于类或者结构体 。当然,它们依然具有部分类成员的属性,可以被声明为 public 或者 private,但是在该类或者结构体被实例化的时候,static 变量并不会随之开辟新的内存空间。

这个方法在你想要创建一个可以跨越不同的类的变量是非常有用的。当然,你也可以通过创建一个全局变量来达到这个效果,但是不同的是,static的类变量并不会跨越 translation unit.

对于函数来讲,和变量的效果是一样的。

#include <iostream>

struct Entity
{
	static int x, y;

	static void print()
	{
		std::cout << x << ", " << y << std::endl;
	}
};

int Entity::x;
int Entity::y;

int main()
{
	Entity e;
	Entity::x = 2;
	Entity::y= 3;

	Entity e1;
	Entity::x = 5;
	Entity::y = 8;

	e.print();
	e1.print();
}

将函数 print() 改成 static 之后,程序没有任何问题。因为 print() 函数中涉及到的变量 x, y 也都是 static 变量。

同时,该方法可以被这样调用:

	Entity::print();
	Entity::print();

那鉴于 Entity 中所有的变量以及函数都是 static 的,所以我们根本不需要实例化。 那代码可以修改为:

int main()
{
	Entity::x = 2;
	Entity::y= 3;

	Entity::x = 5;
	Entity::y = 8;

    Entity::print();
}

如果这时我们将变量 x, y 改成非 static, 但依然将print函数声明为 static 呢?

#include <iostream>

struct Entity
{
	int x, y;

	static void print()
	{
		std::cout << x << ", " << y << std::endl;
	}
};


int main()
{
	Entity e;
	e.x = 2;
	e.y= 3;

	Entity e1;
	e1.x = 5;
	e1.y = 8;

	Entity::print();
	Entity::print();
}
Error	C2597	illegal reference to non-static member 'Entity::x'	

很显然,static 方法是不能调用非 static 的变量的。 该Error 为非法调用, 因为static函数是没有类实例的。即当 Entity 被实例化的时候,x, y 会随之被实例化,开辟新的内存空间,但是 static 方法 print 并不会被实例化,即可以狭义理解为该方法并不存在于类实例中 (并不一定完全正确,只是帮助理解用)。实际上每一个非static的类成员函数都会获取一个当前类的实例作为一个参数,可视为一个隐形的参数,但是static的方法并不能获得这个参数。

类中的static 方法等同于在类外面的函数。如果将print类写在 Entity 的外面,则 print 函数根本不知道 x,y 是什么。

struct Entity
{
	int x, y;
};

static void print()
{
	std::cout << x << ", " << y << std::endl;
}
Error (active)	E0020	identifier "y" is undefined		
Error (active)	E0020	identifier "x" is undefined	

如果我们给 print 函数一个类实例参数:

struct Entity
{
	int x, y;
};

static void print(Entity e)
{
	std::cout << e.x << ", " << e.y << std::endl;
}

int main()
{
	Entity e;
	e.x = 2;
	e.y= 3;

	Entity e1;
	e1.x = 5;
	e1.y = 8;

	print(e);
	print(e1);
}

这时代码便能够正常运行了。

对于类中的方法来说,实际上也会有一个隐形类实例参数, 如上述所示。而对类中的 static 方法而言,它并不能获取该参数,所以不能识别类中的非 static 的变量。

Static 作用于局部(Local)

首先,定义一个变量我们可以规定了它的作用范围以及生命周期。作用范围意味着哪里可以访问该变量,而生命周期则指该变量会在内存空间中保留多久。Local Static 即表示创建一个生命周期为整个程序,但是作用范围为当前声明该变量的函数,其他的函数并不能访问它 (作用范围可以自己定义)。

依然用例子说明:

#include <iostream>

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

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		Function();
	}
	std::cin.get();
}

该函数的输出结果很显然为 1,1,1,1,1。

但是如果将 i 定义为 static 呢?

#include <iostream>

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

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		Function();
	}
	std::cin.get();
}

输出结果为 1,2,3,4,5. 这时我们想要的结果。

当然,可以将 i 声明为全局变量来达到相同的效果。

#include <iostream>

int i = 0;

void Function()
{
	i++;
	std::cout << i << std::endl;
}

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		Function();
	}
	std::cin.get();
}

但此时该变量可以随时在外部被改变。

#include <iostream>

int i = 0;

void Function()
{
	i++;
	std::cout << i << std::endl;
}

int main()
{
	Function();
	i = 10;
	for (size_t i = 1; i < 5; i++)
	{
		Function();
	}
	std::cin.get();
}

输出结果此时为1,11,12,13,14.

如果你并不想要变量 i 的值能够在任意地方被修改,则可以将 i 在函数内部声明为static。

Reference:https://www.youtube.com/watch?v=f7mtWD9GdJ4&list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb&index=23

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值