C++那些细节--static关键字

本文探讨了在面向过程和面向对象编程中static关键字的作用。在面向过程中,static用于静态全局变量、静态局部变量和静态函数,静态局部变量只初始化一次,函数结束后不会销毁。在面向对象编程中,static关键字用于静态数据成员和静态成员函数,静态成员函数无法访问非静态成员,但可以通过类名直接调用。

static也是我们经常用到的关键字,关于static有很多用法,而且在面向过程和面向对象编程中,static有着不同的意义。之前总是记不住,于是,本人强迫症又发作了,一定要搞懂它!!!


一.面向过程编程中的static关键字

1.静态全局变量

静态全局变量:
// C++Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;

static int s_test;
int nons_test;

int _tmain(int argc, _TCHAR* argv[])
{
	
	//静态全局变量默认初始化为0
	cout<<"static :"<<s_test<<endl;
	//非静态全局变量默认初始化也是0,但如果是局部非静态变量,在VS中直接报错,不能通过编译
	cout<<"non-static: "<<nons_test<<endl;

	system("pause");
	return 0;
}

结果:
static :0
non-static: 0
请按任意键继续. . .

我们在运行程序时,内存分为代码区,全局数据区,堆区,栈区。正常的临时变量auto等都在栈区,生命周期是函数结束,而new出来的对象一般都在堆区,声明周期由我们控制,new时开始,delete时结束。而全局数据区则保存全局变量以及静态变量,他们的生命周期是整个程序的运行周期。

使用静态全局变量和使用普通全局变量的差别:
1)如果全局变量在头文件中,或者使用和定义都在同一个文件中,静态全局变量和普通全局变量是相同的。
//StaticTest.h头文件中分别定义静态全局变量和普通全局变量
static int static_num = 20;
int nonstatic_num = 30;

// C++Test.cpp : 定义控制台应用程序的入口点。
//全局变量在头文件或者本文件中两者没有什么区别

#include "stdafx.h"
#include <iostream>
#include <string>
#include "StaticTest.h"
#include "StaticTest1.h"
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	
	//如果静态变量在头文件中或者本文件中,可以访问静态全局变量
	cout<<"static :"<<static_num<<endl;
	//如果静态变量在头文件中或者本文件中,可以访问非静态全局变量
	cout<<"non-static: "<<nonstatic_num<<endl;

	system("pause");
	return 0;
}

结果:
static :20
non-static: 30
请按任意键继续. . .


2)如果全局变量在.cpp文件中,普通全局变量是全局可见的,即其他文件也可见,但是要使用时,就要在其他文件中添加extern关键字。而且如果不添加的话,在这个文件再声明同名的变量,是会报错的。但是使用静态全局变量就可以解决这个问题,静态全局变量在其他文件中是不可见的,我们不需要关注其他文件中的全局变量,也不会出现不能使用同名全局变量的问题啦!

//.h文件
#ifndef __STATICTEST1_H_
#define __STATICTEST1_H_
#pragma once
class StaticTest1
{
public:
	StaticTest1(void);
	virtual ~StaticTest1(void);
};
#endif

//.cpp文件
#include "stdafx.h"
#include "StaticTest1.h"

//在.cpp文件中定义全局变量
static int static_test = 10;
int nonstatic_test = 20;

StaticTest1::StaticTest1(void)
{
}


StaticTest1::~StaticTest1(void)
{
}
main函数文件:
// C++Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include "StaticTest1.h"
using namespace std;

extern int nonstatic_test;
extern int static_test;

int _tmain(int argc, _TCHAR* argv[])
{
	
	//如果静态变量在其他.cpp文件中,不可以访问静态全局变量,即使在本文件使用extern声明也不行
	//cout<<"static :"<<static_test<<endl;
	//如果静态变量在其他.cpp文件中,可以访问非静态全局变量,但是要在本文件使用extern声明
	cout<<"non-static: "<<nonstatic_test<<endl;

	system("pause");
	return 0;
}

不添加extern时,静态全局变量为未定义,而即使添加extern声明也会报错:
C++Test2.obj : error LNK2001: 无法解析的外部符号 "int static_test" (?static_test@@3HA)
1>: fatal error LNK1120: 1 个无法解析的外部命令


关于静态全局变量,有下面三点要注意:
1)静态全局变量如果未初始化,会被默认初始化为0,而非静态全局变量,注意是全局变量,也是初值为0。(非静态非全局变量如果未初始化在VS中会直接报错的)
2)静态全局变量在全局数据区非配内存,所以变量的生存期是整个程序的运行周期。(局部静态变量的生存期也是整个程序运行周期)
3)静态全局变量在变量声明的文件是可见的,但是在其他文件中是不可见的。

关于全局变量:
如果我们将全局变量直接放在头文件中,变量会随着头文件的引入,引入到各个文件中。有时候这是我们不想看到的,所以另一种方法是将全局变量放在.cpp文件中,这样,全局变量就不会随着.h文件到处引入,不是全局可见。但是,这样这个变量仍然是全局变量,要想在另外的文件中使用这个变量,就要在这个文件中使用extern关键字声明一下。如果不声明,就会出现变量未声明的情况。如果直接在这个变量再定义一个同名的变量,就会出现冲定义的情况,这也是我们不想看到的,如果全局变量仅仅在本文件中有用,那么不如直接使用静态全局变量。


2.静态局部变量

局部变量,即存储在栈空间的变量,我们在调用函数时,变量初始化,而函数调用结束时,局部变量的生存期就结束了,进而被销毁。而有时候我们需要对两次调用函数之间的变量进行保存,最简单的想法就是使用一个全局变量,但是这个变量就已经不属于函数本身,而是全局可见,不符合局部性原理,给维护带来了不便。而静态局部变量刚好可以解决这个问题。静态局部变量存储在全局数据区,不会因函数结束而销毁,并且在其他函数看来,静态局部变量是不可见的,刚好满足了我们的需求!

下面看一个例子:

// C++Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
using namespace std;


void TestFunc()
{
	//定义一个静态局部变量,但是这个变量仅仅在第一次的时候初始化,再次调用时不会再初始化
	static int static_num = 10;
	//但是普通局部变量,每次都会初始化
	int nonstatic_num = 10;
	static_num++;
	nonstatic_num++;
	cout<<"non_static: "<<nonstatic_num<<"  static :"<<static_num<<endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	//调用10次该函数
	for(int i = 0; i < 10; i++)
		TestFunc();

	system("pause");
	return 0;
}

结果:

non_static: 11  static :11
non_static: 11  static :12
non_static: 11  static :13
non_static: 11  static :14
non_static: 11  static :15
non_static: 11  static :16
non_static: 11  static :17
non_static: 11  static :18
non_static: 11  static :19
non_static: 11  static :20
请按任意键继续. . .


从上面的结果我们看出,函数被调用了10次,非静态变量每次都被初始化,因而结果没变。而静态变量却只被初始化了一次,因而每次结果都比上一次大1。


关于局部静态变量要注意的几点:

1)局部静态变量也在全局数据区分配内存,不会因为函数调用结束而销毁。

2)局部静态变量在首次调用到该变量的时候进行初始化,之后再次调用时不会再进行初始化。并且局部静态变量一般就在声明处初始化,如果未显示初始化,则默认初始化为0

3)局部静态变量的生命周期为声明时到程序结束,但是它的作用域却是局部的,仅仅在该函数内,不会破坏局部性原理。


3.静态函数

函数默认是全局可见的,而如果我们希望某个函数只在本文件中可见,那么可以将它声明为静态函数。这一点与静态全局变量差不多。
一个例子:
//StaticTest.h
#include <iostream>
using namespace std;


void NormalFuncTest();

static void StaticFuncTest();

//StaticTest.cpp
#include "stdafx.h"
#include "StaticTest.h"


void NormalFuncTest()
{
	cout<<"Normal Func!"<<endl;	
}

static void StaticFuncTest()
{
	cout<<"Static Func!"<<endl;
}

// C++Test.cpp : 定义控制台应用程序的入口点。
//main函数

#include "stdafx.h"
#include <iostream>
#include "StaticTest.h"
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	NormalFuncTest();
	StaticFuncTest();

	system("pause");
	return 0;
}
这个例子中有两个函数,都定义在另一个文件中,一个为普通函数,另一个为static函数。编译一下,会出现如下错误:

\vs2012\vs12\vc\include\xlocnum(155): error C2129: 静态函数“void StaticFuncTest(void)”已声明但未定义

就是说,编译器只看到了.h文件中的声明,但是看不到.cpp文件中的实现。这样,static的目的就达到啦!但是,如果将函数的实现放到了.h中,那么,不管是不是静态的,都是可以使用的。


二.面向对象编程中的Static关键字

1.静态数据成员

在类中,成员也可以声明为静态数据成员,而在面向对象编程中,static关键字又有了新的功能。
先来一个例子:
// C++Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
using namespace std;


class StaticTest
{
private:
	//此处为声明,声明一个静态成员变量
	static int count;
	int id;
public:
	StaticTest(int i): id(i)
	{
		count++;
	}

	void Show()
	{
		cout<<"ID: "<<id<<endl;
	}

	static void ShowCount()
	{
		cout<<"Count: "<<count<<endl;
	}
};

//在类外定义并初始化静态成员变量,由于定义时需要分配空间,所以不能在类声明中定义
int StaticTest::count = 0;


int _tmain(int argc, _TCHAR* argv[])
{

	StaticTest test1(1);
	StaticTest test2(2);
	test1.Show();
	test2.Show();
	StaticTest::ShowCount();

	system("pause");
	return 0;
}

结果:
ID: 1
ID: 2
Count: 2
请按任意键继续. . .

关于静态成员变量有下面几点:
1)静态成员变量在一个类中只有一份,由该类所有的对象所共享,静态数据成员只分配一次内存。而非静态成员变量则是每个对象都有一份自己的拷贝。
2)静态成员变量遵循public,private,protected的访问规则
3)静态数据成员在全局数据区分配内存,属于本类所有对象共享,但是它不属于任何一个对象,所以即使没有产生对象时也可以使用静态成员变量。
4)静态成员变量的初始化比较特殊,不能直接像普通成员变量那样在类中给初值,更不能像非成员变量那样直接初始化,静态成员变量需要在类的定义中声明,然后再类外面,用<类型><类名>::<变量名> = <值>的格式来初始化。如上面的例子中:int StaticTest::count = 0;
5)访问静态成员变量的方式也有两种,一种是与普通成员变量的访问方式一样,<对象名>.<静态数据成员>。另一种是静态成员变量特有的方式<类名>::<静态数据成员>



2.静态成员函数

还是上面的那个例子,加了一部分功能:

// C++Test.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
using namespace std;


class StaticTest
{
private:
	//此处为声明,声明一个静态成员变量
	static int count;
	int id;
public:
	StaticTest(int i): id(i)
	{
		count++;
	}

	void Show()
	{
		//但是非静态成员函数可以访问静态成员函数和变量
		cout<<"ID: "<<id<<"  Count: "<<count<<endl;
	}

	static void ShowCount()
	{
		//静态成员函数不能访问非静态的成员,非静态成员函数也不行!
		//cout<<"ID: "<<id<<endl;     error C2597: 对非静态成员“StaticTest::id”的非法引用
		//Show();:                    error C2352: “StaticTest::Show”: 非静态成员函数的非法调用
		cout<<"Count: "<<count<<endl;
	}
};

//在类外定义并初始化静态成员变量,由于定义时需要分配空间,所以不能在类声明中定义
int StaticTest::count = 0;


int _tmain(int argc, _TCHAR* argv[])
{

	StaticTest test1(1);
	StaticTest test2(2);
	test1.Show();
	test2.Show();
	StaticTest::ShowCount();

	system("pause");
	return 0;
}

结果:

ID: 1  Count: 2
ID: 2  Count: 2
Count: 2
请按任意键继续. . .


关于静态成员函数注意的地方:

1)静态成员函数可以访问静态成员函数和静态成员变量。但是静态成员函数不能访问普通成员变量和普通成员函数。(因为静态成员函数没有this指针,属于共用)

2)非静态成员函数可以访问静态成员变量和静态成员函数。

3)定义在类外的静态成员函数不能加static,声明时加个static即可,定义时和普通成员函数相同。

4)静态成员函数与静态成员变量的调用规则一致,都不需要有对象就能调用。可以使用正常方法,也可以使用类名::调用。






### C++ 中 `static` 关键字的使用场景及作用 #### 1. **静态局部变量** 当在函数内部定义了一个局部变量并用 `static` 修饰时,该变量会成为静态局部变量。这种情况下,变量会在第一次进入函数时初始化,并在整个程序生命周期中保持存在[^2]。 静态局部变量的特点是: - 它仅在其所在函数的作用域内可见。 - 即使函数执行完毕,变量也不会被销毁,其值会被保留下来供下次调用时继续使用。 示例代码如下: ```cpp void func() { static int count = 0; // 静态局部变量 count++; std::cout << "Count: " << count << std::endl; } ``` 每次调用 `func()` 函数时,`count` 的值都会累加而不是重新初始化为零[^4]。 --- #### 2. **全局静态变量** 如果在一个源文件中定义了一个全局变量并用 `static` 修饰,则此变量的作用域将局限于当前源文件。这意味着即使其他源文件通过头文件包含了该变量的声明,也无法访问它。这种方式常用于避免命名冲突以及控制变量的可见性。 示例代码如下: ```cpp // file1.cpp #include <iostream> static int globalVar = 10; void printGlobalVar() { std::cout << "globalVar: " << globalVar << std::endl; } // file2.cpp (无法访问file1中的globalVar) ``` 在这里,`globalVar` 被限定于 `file1.cpp` 文件范围内[^5]。 --- #### 3. **静态函数** 当一个函数被声明为 `static` 后,它的作用域也被限制在定义它的源文件中。这使得该函数不能被其他源文件直接调用,从而减少了潜在的名字污染风险。 示例代码如下: ```cpp // file1.cpp static void helperFunction() { // 静态函数 std::cout << "This is a static function." << std::endl; } void callHelper() { helperFunction(); } // file2.cpp (无法直接调用helperFunction()) ``` 上述例子展示了如何利用 `static` 来隐藏某些辅助功能的具体实现细节--- #### 4. **类中的静态成员** 在面向对象编程中,`static` 还可用于修饰类的成员变量或成员函数。对于这些静态成员来说,它们不属于任何特定的对象实例,而是由整个类共享。 ##### 静态成员变量 - 类的所有对象都共享同一个静态成员变量副本。 - 必须在类外显式地初始化静态成员变量。 示例代码如下: ```cpp class MyClass { public: static int sharedValue; // 声明静态成员变量 }; int MyClass::sharedValue = 42; // 初始化静态成员变量 int main() { MyClass obj1, obj2; obj1.sharedValue = 100; std::cout << "obj2.sharedValue: " << obj2.sharedValue << std::endl; // 输出100 return 0; } ``` ##### 静态成员函数 - 静态成员函数可以直接操作静态成员变量,但无法访问非静态成员(因为没有隐式的 `this` 指针)。 - 可以通过类名直接调用静态成员函数。 示例代码如下: ```cpp class MyClass { public: static void display(); // 声明静态成员函数 private: static const char* message; // 声明静态私有字符串 }; const char* MyClass::message = "Hello from static member!"; // 初始化静态成员 void MyClass::display() { // 实现静态成员函数 std::cout << message << std::endl; } int main() { MyClass::display(); // 不需要创建对象即可调用 return 0; } ``` --- #### 总结 `static` 是一个多用途的关键字,在不同的上下文中具有不同的含义和效果。它可以用来延长变量的生命期、缩小变量或函数的作用域、或者让类的部分特性独立于具体的对象而存在[^1][^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值