C/C++ 中的static关键字以及为什么不能在头文件定义static变量

目录

  • 静态成员变量(面向对象)
  • 静态成员函数(面向对象)
  • 静态全局变量(面向过程)
  • 静态局部变量(面向过程)
  • 静态函数(面向过程)

1.(面向对象的)静态成员变量

在类内成员变量的声明前加上关键字static,该数据成员就是类内的静态数据成员。

//Example 5
#include <iostream.h>
class Myclass
{
public:
	Myclass(int a,int b,int c);
	void GetSum();
private:
	int a,b,c;
	static int Sum;//声明静态数据成员
};

int Myclass::Sum=0;    //定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
	this->a=a;
	this->b=b;
	this->c=c;
	Sum+=a+b+c;
}

void Myclass::GetSum()
{
	cout<<"Sum="<<Sum<<endl;
}

void main()
{
	Myclass M(1,2,3);
	M.GetSum();
	Myclass N(4,5,6);
	N.GetSum();
	M.GetSum();
}

静态成员变量有以下特点:

  1. 静态成员变量是该类的所有对象所共有的。对于普通成员变量,每个类对象都有自己的一份拷贝。而静态成员变量一共就一份,无论这个类的对象被定义了多少个,静态成员变量只分配一次内存,由该类的所有对象共享访问。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
  2. 因为静态数据成员在全局数据区分配内存,由本类的所有对象共享,所以,它不属于特定的类对象,不占用对象的内存,而是在所有对象之外开辟内存,在没有产生类对象时其作用域就可见。因此,在没有类的实例存在时,静态成员变量就已经存在,我们就可以操作它;
  3. 静态成员变量存储在全局数据区。static成员变量的内存空间既不是在声明类时分配,也不是在创建对象时分配,而是在初始化时分配。静态成员变量必须初始化,而且只能在类体外进行。否则,编译能通过,链接不能通过。在Example5中,语句intMyclass::Sum=0;是定义并初始化静态成员变量。初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化,一般是0。静态数据区的变量都有默认的初始值,而动态数据区(堆区、栈区)的变量默认是垃圾值。
  4. static 成员变量和普通 static 变量一样,编译时在静态数据区分配内存,到程序结束时才释放。这就意味着,static成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
  5. 静态数据成员初始化与一般数据成员初始化不同。初始化时可以不加 static,但必须要有数据类型。被private、protected、public 修饰的 static成员变量都可以用这种方式初始化。静态数据成员初始化的格式为:<数据类型><类名>::<静态数据成员名>=<值>
  6. 类的静态成员变量访问形式1:<类对象名>.<静态数据成员名>
  7. 类的静态成员变量访问形式2:<类类型名>::<静态数据成员名>,也即,静态成员不需要通过对象就能访问。
  8. 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
  9. 如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;
  10. sizeof 运算符不会计算 静态成员变量。
class CMyclass{
    int n;
    static int s;
};    //则sizeof(CMyclass)等于4

何时采用静态数据成员?

设置静态成员(变量和函数)这种机制的目的是将某些和类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于理解和维护。如果想在同类的多个对象之间实现数据共享,又不要用全局变量,那么就可以使用静态成员变量。也即,静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处:

  1. 不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,节省存储空间。
  2. 一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了。

你也许会问,用全局变量不是也可以达到这个效果吗?

同全局变量相比,使用静态数据成员有两个优势:

  1. 静态成员变量没有进入程序的全局命名空间,因此不存在与程序中其它全局命名冲突的可能。
  2. 可以实现信息隐藏。静态成员变量可以是private成员,而全局变量不能。

2.(面向对象的) 静态成员函数

与静态成员变量类似,我们也可以声明一个静态成员函数。

静态成员函数为类服务而不是为某一个类的具体对象服务。静态成员函数与静态成员变量一样,都是类的内部实现,属于类定义的一部分。普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。

普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体地属于类的某个具体对象的。当函数被调用时,系统会把当前对象的起始地址赋给 this 指针。通常情况下,this是缺省(默认)的。如函数fn()实际上是this->fn()。

与普通函数相比,静态成员函数属于类本身,而不作用于对象,因此它不具有this指针。正因为它没有指向某一个对象,所以它无法访问属于类对象的非静态成员变量和非静态成员函数,它只能调用其余的静态成员函数和静态成员变量。从另一个角度来看,由于静态成员函数和静态成员变量在类实例化之前就已经存在可以访问,而此时非静态成员还是不存在的,因此静态成员不能访问非静态成员。

//Example 6
#include <iostream>
using namespace std;

class Student{
private:
   char *name;
   int age;
   float score;
   static int num;  	//学生人数
   static float total;  //总分
public:
   Student(char *, int, float);
   void say();
   static float getAverage();  //静态成员函数,用来获得平均成绩
};

int Student::num = 0;
float Student::total = 0;

Student::Student(char *name, int age, float score)
{
   this->name = name;
   this->age = age;
   this->score = score;
   num++;
   total += score;
}

void Student::say()
{
   cout<<name<<"的年龄是 "<<age<<",成绩是 "<<score<<"(当前共"<<num<<"名学生)"<<endl;
}

float Student::getAverage()
{
   return total / num;
}

int main()
{
   (new Student("小明", 15, 90))->say();
   (new Student("李磊", 16, 80))->say();
   (new Student("张华", 16, 99))->say();
   (new Student("王康", 14, 60))->say();
   cout<<"平均成绩为 "<<Student::getAverage()<<endl;
   return 0;
}

运行结果:
小明的年龄是 15,成绩是 90(当前共1名学生)
李磊的年龄是 16,成绩是 80(当前共2名学生)
张华的年龄是 16,成绩是 99(当前共3名学生)
王康的年龄是 14,成绩是 60(当前共4名学生)
平均成绩为 82.25

静态成员函数的特点:

  • 出现在类体外的函数定义不能指定关键字static;

  • 静态成员之间可以相互访问,即静态成员函数(仅)可以访问静态成员变量、静态成员函数;

  • 静态成员函数不能访问非静态成员函数和非静态成员变量;

  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;

  • 由于没有this指针的额外开销,静态成员函数与类的全局函数相比速度上会稍快;

  • 调用静态成员函数,两种方式:

  • 通过成员访问操作符(.)和(->),也即通过类对象或指向类对象的指针调用静态成员函数。

  • 直接通过类来调用静态成员函数。<类名>::<静态成员函数名>(<参数表>)。也即,静态成员不需要通过对象就能访问。

拷贝构造函数的问题

在使用包含静态成员的类时,有时候会调用拷贝构造函数生成临时的隐藏的类对象,而这个临时对象在消亡时会调用析构函数有可能会对静态变量做操作(例如total_num–),可是这些对象在生成时却没有执行构造函数中的total_num++的操作。解决方案是为这个类写一个拷贝构造函数,在该拷贝构造函数中完成total_num++的操作。

3. (面向过程的)静态全局变量

(也就是不在类中定义的全局变量)

在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。

//Example 1
#include <iostream.h> 

void fn();
static int n; //定义静态全局变量 

void main()
{
	n=20;
	cout<<n<<endl;
	fn();
} 

void fn()
{
	n++;
	cout<<n<<endl;
} 

静态全局变量有以下特点:

  1. 该变量在全局数据区分配内存;
  2. 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的自动初始化值是随机的);
  3. 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
  4. 静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下:【代码区】【全局数据区】【堆区】【栈区】,一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区,静态数据(即使是函数内部的静态局部变量)存放在全局数据区。自动变量一般会随着函数的退出而释放空间,而全局数据区的数据并不会因为函数的退出而释放空间。

Example 1中的代码中将

static int n; //定义静态全局变量

改为

int n; //定义全局变量

程序照样正常运行。
定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:

  1. 静态全局变量不能被其它文件所用;
  2. 其它文件中可以定义相同名字的变量,不会发生冲突;

将上述示例代码改为如下:

//Example 2
//File1
#include <iostream.h> 

void fn();
static int n; //定义静态全局变量 

void main()
{
	n=20;
	cout<<n<<endl;
	fn();
} 

//File2 
#include <iostream.h> 
extern int n;
void fn()
{
	n++;
	cout<<n<<endl;
} 

编译并运行Example 2,会发现上述代码可以分别通过编译,但运行时出现错误。 这就是因为静态全局变量不能被其它文件所用,即使在其它文件中使用extern 进行声明也不行。
我们将

static int n; //定义静态全局变量

改为

int n; //定义全局变量

再次编译运行程序,程序可正常运行。

因此,在一个文件中,静态全局变量和全局变量功能相同;而在两个文件中,要使用同一个变量,则只能使用全局变量而不能使用静态全局变量。

4. (面向过程的)静态局部变量

在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。

//Example 3
#include <iostream.h>
void fn();
void main()
{
	fn(); //10
	fn(); //11
	fn(); //12
}

void fn()
{
	static n=10;
	cout<<n<<endl;
	n++;
}

通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,这给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。

静态局部变量有以下特点:

  1. 静态局部变量在全局数据区分配内存;
  2. 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
  3. 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
  4. 静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

5. (面向过程的)静态函数

在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

//Example 4
#include <iostream.h>
static void fn();//声明静态函数

void main()
{
	fn();
}

void fn()//定义静态函数
{
	int n=10;
	cout<<n<<endl;
}

定义静态函数的好处:(类似于静态全局变量)

  1. 静态函数不能被其它文件所用;
  2. 其它文件中可以定义相同名字的函数,不会发生冲突;

为什么不能在头文件定义static变量

可以通过编译,但是使用static定义变量证明该变量的作用域范围仅在该源文件内,其他源文件不能访问。如果在头文件中定义static变量,证明包含了所有这个头文件的源文件都定义了该变量,会造成变量多次定义浪费内存,而且不是真正的全局变量。

知乎大佬写的:https://zhuanlan.zhihu.com/p/37439983,受益匪浅

  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是实现上述要求的代码: ```c++ #include <iostream> #include <iomanip> using namespace std; const int COURSE_NUM = 3; // 课程数量 const int MAX_STU_NUM = 100; // 最大学生数量 class Student { private: string name; string id; int scores[COURSE_NUM]; static int totalNum; // 学生总数 static int failNum; // 不及格学生总数 public: static void printStats() { cout << "Total students: " << totalNum << endl; cout << "Total failed students: " << failNum << endl; } Student() { totalNum++; failNum++; } void input() { cout << "Input name: "; cin >> name; cout << "Input ID: "; cin >> id; for (int i = 0; i < COURSE_NUM; i++) { cout << "Input score for course " << i + 1 << ": "; cin >> scores[i]; if (scores[i] >= 60) { failNum--; } } } double getAverageScore() const { double sum = 0; for (int i = 0; i < COURSE_NUM; i++) { sum += scores[i]; } return sum / COURSE_NUM; } void display() const { cout << left << setw(10) << name << left << setw(15) << id; for (int i = 0; i < COURSE_NUM; i++) { cout << left << setw(10) << scores[i]; } cout << left << setw(10) << setprecision(2) << fixed << getAverageScore() << endl; } }; int Student::totalNum = 0; int Student::failNum = 0; int main() { int n; cout << "Input the number of students: "; cin >> n; Student students[MAX_STU_NUM]; for (int i = 0; i < n; i++) { cout << "Input information for student " << i + 1 << ":" << endl; students[i].input(); } cout << left << setw(10) << "Name" << left << setw(15) << "ID"; for (int i = 0; i < COURSE_NUM; i++) { cout << left << setw(10) << "Course " << i + 1; } cout << left << setw(10) << "Average" << endl; for (int i = 0; i < n; i++) { students[i].display(); } Student::printStats(); return 0; } ``` 上述代码,`Student` 有四个私有成员变量,分别是学生姓名、学号和每门课程的成绩。其,课程数量采用常量 `COURSE_NUM` 来表示。同时,`Student` 还有两个静态成员变量,分别是学生总数 `totalNum` 和不及格学生总数 `failNum`。这两个变量通过 `Student` 的构造函数和输入函数自动更新。 `Student` 定义了三个成员函数。`input()` 函数从键盘输入学生信息和成绩,其在输入成绩时,如果成绩及格,则更新 `failNum` 变量。`getAverageScore()` 函数返回学生的平均成绩。`display()` 函数以格式化的形式输出学生的各科成绩和平均成绩。 最后,在 `main()` 函数,首先从键盘输入学生数量,然后定义一个 `Student` 型的对象数组 `students`,大小为 `MAX_STU_NUM`。随后,通过循环输入每个学生的信息和成绩。输入完成后,按照要求输出表头和每个学生的信息和成绩,最后调用静态成员函数 `printStats()` 输出学生总数和不及格学生总数。 以上代码可以满足题目的所有要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值