一. 回顾成员变量
普通成员变量的特点:
- 通过类的对象名能够访问类中的
public
成员变量。 - 每个类对象的成员变量都是专属的,即使是同个类的不同对象之间也不能共享该类的成员变量。
这么来看,如果多个对象想操作同一个数据,从目前来看只能使用全局变量(每个对象都可以操作全局作用域的全局变量),但是这样存在安全隐患,在现代软件开发中不推荐这种方式。那么,有没有其它更好的方式呢?
二. 静态成员概念的引入
- 统计在程序运行期间某个类的对象数目。
- 保证程序的安全性 ==> 不能使用全局变量( 由于全局变量在程序的任何地方都可能被修改,因此不推荐使用全局变量)。
- 随时可以获取当前对象的数目。
针对上面新的需求,我们就引入了类的静态成员变量的概念。
三. 静态成员变量
1. 定义:静态成员变量以关键字static
开头,是一种特殊的类成员变量。
2. 语法
C++
中,静态成员变量与静态成员函数的使用方法如下:
#include <iostream> #include <string> using namespace std; //静态成员变量在类内定义,类外初始化 class test { private: static int m_value; //定义类的静态成员变量 public: static int getValue(){ //定义类的静态成员函数 return m_value; } }; int test::m_value = 12; //类的静态成员变量需要在类外分配内存空间,可以不给初值,那么系统默认给0
定义完静态成员变量之后,通过类对象来调用静态成员变量/函数:
int main() { test t; cout << t.getValue() << endl; system("pause"); }
注意:静态成员变量在类内定义的时候由于还没有被分配内存空间,所以不能被直接赋初值。需要在类外(头文件的结尾或者源文件的开头)赋初值分配内存空间,这样就能够保证在调用任何函数之前静态成员已经被成功初始化。
3. 静态成员变量的特点
1)静态成员变量隶属于整个类所有,所有类的对象共享其静态成员变量
这就意味着,即使创建多个对象,也只为静态分配一份内存,所有对象使用的都是这份内存中的数据,我们一旦在某个对象中修改了这个成员变量的值,在其他对象中能够直接看到修改的结果。
2)静态成员变量的生命周期不依赖于任何对象:原因是其在全局(静态)存储区内分配空间,所以它生命周期是从程序开始到程序结束。
- 全局变量,静态局部变量,静态全局变量保存在全局(静态)存储区,生命周期是从程序开始到程序结束。
- 局部变量保存在栈,其数据则随着函数等的作用域结束导致出栈而销毁。
可见,静态局部变量的效果已经跟全局变量有一拼了,而且因为位于函数体内部,所以更有利于程序的模块化了。
Tips:全局变量作用区间是在各个类中实现数据共用的方法——extern
//类A.cpp: int g_abc = 15; //类B.cpp: extern int g_abc; cout << g_abc << endl; //输出15
3)可以通过类名直接访问公有静态成员变量。
语法:类名 :: 静态成员变量名(Class_name
:: static_member
)
4)可以通过对象名访问公有静态成员变量。
5)静态成员变量在类内定义,类外初始化:
因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
4. 静态变量的使用
针对静态成员变量的以上的几个特点,我们把上边的代码修改如下,用于统计当前对象的个数:
#include <iostream> #include <string> using namespace std; class test { private: static int m_value1; //定义私有类的静态成员变量 public: static int m_value2; //定义公用类的静态成员变量(公有的问题在结论) public: test(){ m_value1++; m_value2++; } int getValue(){ //定义类的成员函数(和静态成员函数的用法区别) return m_value1; } }; int test::m_value1 = 0; //类的静态成员变量需要在类外分配内存空间 int test::m_value2 = 0; //类的静态成员变量需要在类外分配内存空间
int main() { test t1, t2, t3; cout << "test::m_value2 = " << test::m_value2 << endl; //通过类名直接调用公有静态成员变量,获取对象个数 cout << "t3.m_value2 = " << t3.m_value2 << endl; //通过对象名名直接调用公有静态成员变量,获取对象个数 cout << "t3.getValue() = " << t3.getValue() << endl; //通过对象名调用普通函数获取对象个数 }
编译输出:
test::m_value2 = 3 t3.m_value2 = 3 t3.getValue() = 3
从输出,貌似得到我们想要的效果,但是C++
中讲究的是封装性,以上代码,有2个
不妥之处:
- 类名或对象名能直接访问成员变量(因为是
public
类型),也就是说成员变量能直接被外界修改。
修改: 应将静态变量定义为private
类型。 - 这里我们使用了一个成员函数来获取当前的对象个数,看似没问题,但是必须要定义对象
test t3
,通过对象去调用成员函数,即t3.getValue()
。但有时候我们不想定义对象,也能直接使用类中的成员函数,这就是我们要说的类的静态成员函数。
等价写法:将成员函数getValue()
定义为静态类型的,就可以通过test::getValue()
直接使用。
5. 小结
静态成员变量的优势如下:
1)安全地实现共享数据
全局变量的值也可以为一个程序中的多个函数所共享,但是全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶然失误,全局变量的值就被修改,导致程序的失败。因此在实际开发中很少使用全局变量。
因为同类不同对象之间的所占的内存空间是不一样的,这也导致各个对象之间成员变量的数据没法互用。而静态成员变量恰恰解决了这个问题,它可以在同类的多个对象之间实现数据共享,也可以实现信息隐藏,静态数据成员可以是private成员,而全局变量不能(当程序想要使用全局变量的时候应该先考虑使用 static)。
2)节省内存,效率高
因为静态变量所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
四. 静态成员函数
1. 定义:和静态成员变量一样,静态成员函数以关键字static
开头,在C++
中,定义静态成员函数的主要目的是处理类的静态成员变量。
2. 静态成员函数的特点
- 普通成员函数与静态成员函数之间有一个主要的区别。那就是静态成员函数没有
this
指针(静态成员函数属于整个类所有) - 静态成员函数只能直接访问静态成员变量和静态成员函数,不可以调用或操纵非静态成员。
- 可以通过类名直接访问类的公有静态成员函数。
- 可以通过对象名访问类的公有静态成员函数。
- 为了调用方便,静态成员函数的调用不需要通过类的实例化,通过作用域
::
就可以被调用。
特点2的错误示范:
class test { int number; static void picker() { number = 6; //不被允许,静态成员函数只能访问静态成员变量/静态成员函数 } }
3. 静态成员函数的用法
针对上述特点,对上面的代码进行成员函数修改成静态成员函数并调用:
#include <iostream> #include <string> using namespace std; class test { private: static int m_value; //定义私有类的静态成员变量 public: test(){ m_value++; } static int getValue(){ //定义类的静态成员函数 return m_value; } }; int test::m_value = 0; //类的静态成员变量需要在类外分配内存空间
int main() { test t1,t2,t3; cout << "test::getValue() = " << test::getValue() << endl; //通过类名直接调用公有静态成员函数,获取对象个数 cout << "t3.getValue() = " << t3.getValue() << endl; //通过对象名调用静态成员函数获取对象个数 system("pause"); }
编译输出:
test::m_value2 = 3 t3.getValue() = 3
这样我们就直接能通过类名::静态成员函数的方式去访问静态成员函数,获取对象个数,而不需要任何类的实例化对象来实现调用。
静态成员函数的其他用法:
- 可以实现某些特殊的设计模式:如单例模式—
Singleton
; - 可以封装某些算法,比如数学函数,如
ln
,sin
,tan
等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类Math
,调用Math::sin(3.14)
;如果非要用非静态函数,那就必须:Math math
;math.sin(3.14)
; 行是行,只是不爽。
4. 静态成员函数与普通成员函数的对比
静态成员函数 | 普通成员函数 | |
所有对象共享 | Yes | Yes |
隐含this指针 | No | Yes |
访问普通成员变量(函数) | No | Yes |
访问普通静态变量(函数) | Yes | Yes |
通过类名直接调用 | Yes | No |
通过对象名直接调用 | Yes | Yes |
最重要的是要记住:静态函数不需要实例化就可以被调用,不会也不可以调用或操纵非静态成员。
下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。 |