C++ 静态变量static的使用方法
static概述:
static关键字有三种使用方式,其中前两种只指在C语言中使用,第三种在C++中使用。
静态局部变量(C)
静态全局变量/函数(C)
静态数据成员/成员函数(C++)
静态局部变量
静态局部变量:在函数内部声明的静态变量。即使函数结束,静态局部变量的值也会保留。通常用在需要记住历史信息的情况下。
局部静态变量是在函数内部声明的,但它有一个特别之处:它的生命周期是贯穿整个程序的运行期的,但它只在声明它的函数内部可见。这意味着,虽然它是局部的,但它的值在函数调用之间会保持不变!
特点:
static类型分配在静态存储区,在程序整个运行期间都不释放;
static局部变量在初次运行时进行初始化工作,且只初始化一次;
对于局部静态变量,如果不赋初值,编译期会自动赋初值0或者空
简单实例:
#include <iostream> void function() { static int count = 0; // 这就是一个局部静态变量 count++; std::cout << "Count is: " << count << std::endl; } int main() { function(); // 输出: Count is: 1 function(); // 输出: Count is: 2 function(); // 输出: Count is: 3 return 0; }
在这个例子中,
count
是一个局部静态变量。每次调用function
函数时,count
的值都会递增,并且这个值会在函数调用之间保持。这就是局部静态变量的魅力所在!注意:
- 当我们多次调用 function() 函数时,count 变量的值会累加,而不是每次都从 0 开始;
- “记忆性”是程序运行很重要的一点就是可重复性,而static变量的“记忆性”破坏了可重复性,造成不同时刻同一函数的运行结果不同;
- “生存期”全局性和唯一性。 普通的局部变量在栈上分配空间,因此每次调用函数时,分配的空间都可能不一样,而static具有全局唯一性的特点,每次调用时都指向同一块内存,这就造成一个很重要的问题—不可重入性;
局部静态变量的存储方式:
局部静态变量虽然在函数内部声明,但它们的存储位置可不是在栈上哦!它们被存储在静态存储区,这意味着它们的生命周期会一直持续到程序结束。每次函数调用时,局部静态变量并不会重新初始化,而是会保持上一次函数调用结束时的值。
想象一下,静态存储区就像是一个大仓库,局部静态变量就像是仓库里的一些特殊货物。这些货物在程序运行期间一直存在,不会因为函数的调用和返回而消失。每次你调用函数时,就像是去仓库里查看这些货物的状态,你可以更新它们的值,但它们的存在是持久的。
静态全局变量:
静态全局变量:在函数外部声明的静态变量。它的作用范围局限于声明它的文件内。通常用于在一个文件内共享信息,但防止其他文件访问。
全局静态变量是在全局作用域声明的,但它的链接属性是内部的,这意味着它只能在本文件内访问,其他文件可不知道它的存在。
简单实例:
// file1.cpp #include <iostream> static int globalStaticVar = 10; // 这就是一个全局静态变量 void printGlobalStaticVar() { std::cout << "Global static variable is: " << globalStaticVar << std::endl; } // file2.cpp #include <iostream> // 这里尝试访问file1.cpp中的globalStaticVar会失败哦! // extern int globalStaticVar; // 这行会报错,因为globalStaticVar是静态的,只在file1.cpp中可见 int main() { printGlobalStaticVar(); // 输出: Global static variable is: 10 return 0; }
在这个例子中,
globalStaticVar
是一个全局静态变量,它在file1.cpp
中声明并定义。由于它是静态的,所以它的链接属性是内部的,只能在file1.cpp
中访问。如果你尝试在file2.cpp
中通过extern
关键字来访问它,编译器会毫不留情地给你一个错误提示。全局静态变量常常用来实现文件级别的封装,让某些变量只在本文件中可见和可用,避免与其他文件中的变量发生冲突。这个特性在大型项目中可是非常有用的哦!
注意:
对于外部(全局)变量,不论是否有static限制,它的存储区域都是在静态存储区,生存期都是全局的,此时的static只是起作用域限制作用,限制作用域在本文件内部。
全局静态变量虽然名字里有“全局”两个字,但它其实是个“伪全局”,因为它的作用域被限制在了声明它的那个文件里。这就像是你有个宝藏,虽然它很珍贵,但只有你一个人知道它藏在哪里,其他人都找不到它。
所以,全局静态变量只能在当前文件内部被访问和修改,其他文件是看不到也摸不着的。这个特性在很多情况下都很有用,比如当你想要在某个文件中封装一些变量,但又不想让其他文件访问到它们时,全局静态变量就是你的好帮手啦!
全局静态变量的生命周期
全局静态变量在程序中具有以下生命周期特性:
创建与初始化:全局静态变量在程序开始执行之前就被创建,并且在程序结束时才被销毁。它们的初始化发生在程序启动时的某个阶段,具体取决于编程语言和编译器的实现。未初始化的全局静态变量可能会被赋予一个默认值(如0或NULL)。
持久性:由于全局静态变量在程序的整个生命周期内都存在,因此它们可以在程序的任何地方被访问和修改(当然,这取决于其作用域和访问权限)。这种持久性使得全局静态变量成为在不同函数或模块之间共享数据的理想选择。
单一实例:在程序的整个运行期间,全局静态变量只有一个实例。这意味着无论程序中有多少个函数或模块访问该变量,它们实际上都是在操作同一个内存地址上的数据。
全局静态变量的使用方法
全局静态变量在编程中的使用方法包括以下几个方面:
定义与声明:全局静态变量通常在文件的顶层作用域中声明,使用
static
关键字进行修饰。这表示该变量具有文件作用域,即它只能在定义它的文件内部被访问。// 在C语言中定义全局静态变量 static int globalStaticVar = 0;
访问与修改:由于全局静态变量在程序的任何地方都可见(只要它在定义它的文件内部),因此可以通过直接引用其名称来访问和修改它的值。但是,需要注意的是,过度使用全局静态变量可能会导致代码难以维护和理解。因此,在实际编程中,应尽量避免不必要的全局静态变量使用。
跨文件共享:虽然全局静态变量具有文件作用域,但有时需要在多个文件之间共享数据。为了实现这一点,可以使用外部链接(external linkage)或头文件来声明全局静态变量,并在需要共享数据的文件中包含该头文件。然而,这种方法可能会引入一些复杂性,并且需要谨慎处理以避免命名冲突和数据竞争等问题。
注意事项:
- 由于全局静态变量在程序的整个生命周期内都存在,因此应谨慎处理其内存管理问题,以避免内存泄漏或其他内存相关问题。
- 在多线程环境中使用全局静态变量时,需要特别注意线程安全性问题。可能需要使用互斥锁或其他同步机制来保护对全局静态变量的访问。
全局静态变量的使用实例
// file1.cpp #include <iostream> // 声明一个全局静态变量 static int globalStaticVar = 0; void incrementGlobalStaticVar() { globalStaticVar++; std::cout << "Global static variable is now: " << globalStaticVar << std::endl; } // file2.cpp #include <iostream> // 这里我们声明了一个与file1.cpp中同名的全局静态变量 // 但由于它是静态的,所以这两个变量其实是独立的哦! static int globalStaticVar = 100; void printGlobalStaticVar() { std::cout << "In file2, global static variable is: " << globalStaticVar << std::endl; } int main() { // 调用file1.cpp中的函数 incrementGlobalStaticVar(); // 输出: Global static variable is now: 1 incrementGlobalStaticVar(); // 输出: Global static variable is now: 2 // 调用file2.cpp中的函数 printGlobalStaticVar(); // 输出: In file2, global static variable is: 100 return 0; }
在这个例子中,
file1.cpp
和file2.cpp
都声明了一个名为globalStaticVar
的全局静态变量。但是,由于它们是静态的,所以这两个变量其实是完全独立的,互不影响。在
main
函数中,我们调用了incrementGlobalStaticVar
函数,这个函数会递增file1.cpp
中的globalStaticVar
。然后,我们调用了printGlobalStaticVar
函数,这个函数会打印出file2.cpp
中的globalStaticVar
的值。你看,全局静态变量就是这样,虽然名字听起来像全局的,但实际上它只在自己所在的文件中起作用。这个特性在很多情况下都非常有用哦!
全局静态变量在多线程的使用示例:
在多线程环境中使用全局静态变量时,需要特别注意线程安全性问题。以下是一个使用全局静态变量在多线程中的示例,同时展示了如何使用互斥锁来保护对全局静态变量的访问。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> // 定义全局静态变量和互斥锁 static int globalStaticVar = 0; pthread_mutex_t lock; // 线程函数 void* threadFunc(void* arg) { int threadId = *(int*)arg; free(arg); // 释放动态分配的内存 // 加锁 pthread_mutex_lock(&lock); // 访问和修改全局静态变量 globalStaticVar++; printf("Thread %d incremented globalStaticVar to %d\n", threadId, globalStaticVar); // 解锁 pthread_mutex_unlock(&lock); return NULL; } int main() { const int numThreads = 10; pthread_t threads[numThreads]; // 初始化互斥锁 if (pthread_mutex_init(&lock, NULL) != 0) { fprintf(stderr, "Failed to initialize mutex\n"); return EXIT_FAILURE; } // 创建线程 for (int i = 0; i < numThreads; i++) { int* threadId = malloc(sizeof(int)); // 动态分配内存 *threadId = i + 1; if (pthread_create(&threads[i], NULL, threadFunc, threadId) != 0) { fprintf(stderr, "Failed to create thread %d\n", i + 1); return EXIT_FAILURE; } } // 等待线程完成 for (int i = 0; i < numThreads; i++) { pthread_join(threads[i], NULL); } // 销毁互斥锁 pthread_mutex_destroy(&lock); printf("Final value of globalStaticVar: %d\n", globalStaticVar); return EXIT_SUCCESS; }
代码解释
全局静态变量和互斥锁的定义:
static int globalStaticVar = 0;
:定义了一个全局静态变量globalStaticVar
,并初始化为0。pthread_mutex_t lock;
:定义了一个互斥锁lock
,用于保护对globalStaticVar
的访问。线程函数:
void* threadFunc(void* arg)
:线程函数,接受一个void*
类型的参数。- 在线程函数内部,首先通过
pthread_mutex_lock(&lock);
加锁,然后访问和修改全局静态变量globalStaticVar
,最后通过pthread_mutex_unlock(&lock);
解锁。主函数:
- 初始化互斥锁。
- 创建多个线程,每个线程都执行
threadFunc
函数。- 等待所有线程完成。
- 销毁互斥锁。
- 打印全局静态变量
globalStaticVar
的最终值。注意事项
- 在多线程环境中使用全局静态变量时,务必使用互斥锁或其他同步机制来保护对变量的访问,以避免数据竞争和不确定的行为。
- 动态分配的内存需要在适当的时候释放,以避免内存泄漏。在本例中,线程函数内部释放了动态分配的内存。
静态成员变量:
静态成员变量是编程语言中的一个重要概念,它通常与类(或结构体)相关联,并具有以下定义和特性:
定义
静态成员变量是在类(或结构体)内部使用
static
关键字声明的变量。它属于类本身,而不是类的某个特定对象。因此,静态成员变量在类的所有对象之间共享。特性
共享性:
- 静态成员变量在类的所有对象之间共享。这意味着,无论创建了多少个类的对象,它们都访问同一个静态成员变量。
存储位置:
- 静态成员变量存储在静态存储区,而不是对象的实例存储区。因此,静态成员变量的生命周期贯穿整个程序运行期间。
访问方式:
- 静态成员变量可以通过类名直接访问(使用
类名::静态成员变量
的语法),也可以通过类的对象访问(但这种方式并不常见,因为静态成员变量与特定对象无关)。初始化:
- 静态成员变量必须在类外部进行初始化。这是因为静态成员变量不属于任何对象,因此不能在构造函数中进行初始化。
内存分配:
- 静态成员变量在程序开始执行时就被分配内存,并在程序结束时才被销毁。这意味着静态成员变量在程序的整个生命周期内都存在。
用途:
- 静态成员变量常用于实现类的全局属性或计数器等功能。例如,可以使用静态成员变量来跟踪类的实例数量或存储与类相关的全局配置信息。
示例代码(C++):
#include <iostream> class MyClass { public: // 声明静态成员变量 static int staticVar; // 静态成员函数(用于访问静态成员变量) static void printStaticVar() { std::cout << "Static variable value: " << staticVar << std::endl; } }; // 在类外部初始化静态成员变量 int MyClass::staticVar = 0; int main() { // 通过类名访问静态成员变量 MyClass::staticVar = 42; MyClass::printStaticVar(); // 输出:Static variable value: 42 // 创建类的对象 MyClass obj1, obj2; // 通过对象访问静态成员变量(不推荐,但合法) obj1.staticVar = 100; obj2.printStaticVar(); // 输出:Static variable value: 100 return 0; }
在上述示例中,
MyClass
类具有一个静态成员变量staticVar
和一个静态成员函数printStaticVar
。我们在类外部初始化了staticVar
,并在main
函数中通过类名和对象两种方式访问了它。需要注意的是,尽管可以通过对象访问静态成员变量,但这种做法并不推荐,因为静态成员变量与特定对象无关。
静态成员函数:
定义
静态成员函数是在类(或结构体)内部使用
static
关键字声明的函数。与普通的成员函数不同,静态成员函数不依赖于类的特定对象,因此它们不能访问类的非静态成员变量或非静态成员函数(除非通过对象指针或引用传递)。特性
与对象无关:
- 静态成员函数不与类的任何特定对象相关联。因此,它们不能访问对象的非静态成员(除非显式地传递对象指针或引用)。
访问静态成员:
- 静态成员函数可以访问类的静态成员变量和其他静态成员函数。这是因为静态成员属于类本身,而不是类的某个特定对象。
调用方式:
- 静态成员函数可以通过类名直接调用(使用
类名::静态成员函数名
的语法),也可以通过类的对象调用(但这种方式并不常见,因为静态成员函数与特定对象无关)。隐藏this指针:
- 与普通成员函数不同,静态成员函数不隐式地接收
this
指针。这意味着在静态成员函数中不能使用this
来访问对象的成员。用途:
- 静态成员函数常用于实现与类相关的功能,但不依赖于类的特定对象。例如,可以使用静态成员函数来创建类的实例、管理类的全局状态或提供与类相关的实用功能。
示例代码(C++):
#include <iostream> class MyClass { public: // 声明静态成员变量 static int staticVar; // 声明静态成员函数 static void printStaticVar() { std::cout << "Static variable value: " << staticVar << std::endl; } // 非静态成员函数(用于对比) void printInstanceVar() { std::cout << "Instance variable value: " << instanceVar << std::endl; } private: // 非静态成员变量 int instanceVar; }; // 在类外部初始化静态成员变量 int MyClass::staticVar = 42; int main() { // 通过类名调用静态成员函数 MyClass::printStaticVar(); // 输出:Static variable value: 42 // 创建类的对象 MyClass obj; // 通过对象调用非静态成员函数(需要初始化instanceVar) obj.instanceVar = 100; obj.printInstanceVar(); // 输出:Instance variable value: 100 // 注意:不能通过对象调用静态成员函数(尽管可以编译通过,但不推荐) // obj.printStaticVar(); // 这将正确编译并运行,但不符合静态成员函数的设计意图 return 0; }
在上述示例中,
MyClass
类具有一个静态成员变量staticVar
和一个静态成员函数printStaticVar
。我们还定义了一个非静态成员函数printInstanceVar
用于对比。在main
函数中,我们通过类名调用了静态成员函数,并通过对象调用了非静态成员函数。需要注意的是,尽管可以通过对象调用静态成员函数(这在语法上是允许的),但这种做法并不推荐,因为静态成员函数与特定对象无关。