[C++] static静态成员变量/函数的用法

简介: [C++] static静态成员变量/函数的用法

一. 回顾成员变量

普通成员变量的特点:

  1. 通过对象名能够访问中的public成员变量。
  2. 每个对象的成员变量都是专属的,即使是同个的不同对象之间也不能共享该的成员变量。

这么来看,如果多个对象想操作同一个数据,从目前来看只能使用全局变量(每个对象都可以操作全局作用域的全局变量),但是这样存在安全隐患,在现代软件开发中不推荐这种方式。那么,有没有其它更好的方式呢?


二. 静态成员概念的引入

  1. 统计在程序运行期间某个类的对象数目。
  2. 保证程序的安全性 ==> 不能使用全局变量( 由于全局变量在程序的任何地方都可能被修改,因此不推荐使用全局变量)。
  3. 随时可以获取当前对象的数目。

针对上面新的需求,我们就引入了类的静态成员变量的概念。


三. 静态成员变量

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个不妥之处:

  1. 类名或对象名能直接访问成员变量(因为是public类型),也就是说成员变量能直接被外界修改。
    修改: 应将静态变量定义为private类型。
  2. 这里我们使用了一个成员函数来获取当前的对象个数,看似没问题,但是必须要定义对象test t3,通过对象去调用成员函数,即t3.getValue()。但有时候我们不想定义对象,也能直接使用类中的成员函数,这就是我们要说的类的静态成员函数
    等价写法:将成员函数getValue()定义为静态类型的,就可以通过test::getValue()直接使用。

5. 小结

静态成员变量的优势如下:

1)安全地实现共享数据

全局变量的值也可以为一个程序中的多个函数所共享,但是全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶然失误,全局变量的值就被修改,导致程序的失败。因此在实际开发中很少使用全局变量

因为同类不同对象之间的所占的内存空间是不一样的,这也导致各个对象之间成员变量的数据没法互用。而静态成员变量恰恰解决了这个问题,它可以在同类的多个对象之间实现数据共享,也可以实现信息隐藏,静态数据成员可以是private成员,而全局变量不能(当程序想要使用全局变量的时候应该先考虑使用 static)。

2)节省内存,效率高

因为静态变量所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。


四. 静态成员函数

1. 定义:和静态成员变量一样,静态成员函数以关键字static开头,在C++中,定义静态成员函数主要目的处理类的静态成员变量

2. 静态成员函数的特点

  1. 普通成员函数静态成员函数之间有一个主要的区别。那就是静态成员函数没有this指针(静态成员函数属于整个所有)
  2. 静态成员函数只能直接访问静态成员变量和静态成员函数不可以调用或操纵非静态成员
  3. 可以通过类名直接访问类的公有静态成员函数
  4. 可以通过对象名访问类的公有静态成员函数
  5. 为了调用方便,静态成员函数的调用不需要通过类的实例化,通过作用域::就可以被调用。

特点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

这样我们就直接能通过类名::静态成员函数的方式去访问静态成员函数,获取对象个数,而不需要任何类的实例化对象来实现调用。

静态成员函数的其他用法:

  1. 可以实现某些特殊的设计模式:如单例模式—Singleton
  2. 可以封装某些算法,比如数学函数,如lnsintan等等,这些函数本就没必要属于任何一个对象,所以从类上调用感觉更好,比如定义一个数学函数类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

最重要的是要记住:静态函数不需要实例化就可以被调用,不会也不可以调用或操纵非静态成员。


下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。


目录
相关文章
|
28天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
157 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
C语言 C++
实现两个变量值的互换[C语言和C++的区别]
实现两个变量值的互换[C语言和C++的区别]
19 0
|
7天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
33 4
|
8天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
27 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4