C++那些细节--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++,c,静态,编程技巧,static)