C++学习笔记:如何理解静态成员?

213 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

C++学习笔记.png

概述

这一章我们来看看静态成员。

什么是静态成员?

在理解静态成员之前,我们先来回忆一下,什么是类? 类不是一个占有内存的实体,而是一种类型,是现实世界的抽象概括。 其中,每个类的对象都是该类数据成员的拷贝。

但有时我们的需求是:所有对象在类的范围内共享某个成员,也就是说,该成员的属性是类的所有对象共有的。 这时我们将这个成员声明为static即可。这种声明为static的类的成员叫做类的静态成员。


如何理解静态成员?

打个比方,我们有一个学生类Student,其数据成员,可以是name,Gender,id,score,numStu(人数)等等。

在这里,我们就可以将学生的人数,即数据成员numStu声明为static,因为无论学生类的对象有多少, 数据成员numStu(学生的总人数)都只有一个,所有Student类的对象都能共享它,并且能够访问它。


静态成员会在哪里进行空间分配?

将我们数据成员numStu声明为static后,再创建一个Student的对象stu

此时stu对象并没有随之创建静态数据成员numStu的内存空间。这是为什么?

事实上,静态成员的内存分配并不会随着类对象的创建而创建,也不会随着对象的消失而消失。也就是说: 静态成员的内存分配,并不在Student类的构造函数中进行,其空间回收也不在析构函数中完成。

那么,回到问题上来,静态成员会在哪里进行空间分配?

有三种可能:

  1. 程序main()函数所在的源文件中(全局数据处)
  2. 类的声明文件(作为类的外部接口的头文件)
  3. 类的定义文件(类的内部实现细节)

我们来分析一下:


  • 问:可以在main()函数之前的全局数据定义处,定义静态成员吗?
  • 答:不可以! 这样会导致每个重用该类的应用程序在包含该类的头文件后,都不得不在应用程序中再定义一下该类的静态成员。这不便于类的重用。

  • 问:可以在类的声明文件里分配吗?
  • 答:不可以!类的声明文件是不能进行实际的空间分配的(只能进行成员的声明,并不能定义),也就是说,在类的声明文件中写:static int numStu = 0; 是不对的。

注意:

在头文件中,类声明的外部,进行静态成员的定义,也是不行的,因为这样会导致,在多个使用该类的源文件中,对其进行重复定义。


  • 问:可以在类定义的文件中吗?
  • 答:可以。我们应该在类定义文件的内部实现中,定义静态成员。定义时要用类名引导。这样在重用该类时,只需导入类的头文件即可。

例子如下:

//----------------------------------------
//              Student.h
//----------------------------------------
class Student {
private:
    int s_id;
    float score;
    static int numStu;  	//静态成员
public:
    Student();   			//构造函数
    Student(int id, float s);
   ~Student();              //析构函数
};
//----------------------------------------



//----------------------------------------
//              Student.cpp
//----------------------------------------
# include "Student.h"
# include <iostream>
# include <string>
using namespace std;

int Student::numStu = 0;    //静态成员在这里初始化,内存分配

Student::Student() {
    cout << "\n无参构造函数调用了..." << endl;
    s_id = 1000;
	score = 0;
    numStu++;       //创建一个对象伴随着学生人数加1
    cout << "现在的总人数为:" << numStu << endl;
}

Student::Student(int id, float s) {
    cout << "\n有参构造函数调用了..." << endl;
    s_id = id;
	score = s;
    numStu++;       //创建一个对象伴随着学生人数加1
    cout << "现在的总人数为:" << numStu << endl;
}

Student::~Student() {
    cout << "\n析构函数调用了..." << endl;
    numStu--;       //析构对象伴随着人数减1
    cout << "现在的总人数为:" << numStu << endl;
}
//----------------------------------------


//----------------------------------------
//              main.cpp
//----------------------------------------
# include "Student.h"
# include <iostream>
using namespace std;
int main() {
    Student t1;
    Student t2(1001,98);
    Student t3(1002,98);
    return 0;
}
//----------------------------------------

运行结果如下:

//----------------------------------------

无参构造函数调用了...
现在的总人数为:1

有参构造函数调用了...
现在的总人数为:2

有参构造函数调用了...
现在的总人数为:3

析构函数调用了...
现在的总人数为:2

析构函数调用了...
现在的总人数为:1

析构函数调用了...
现在的总人数为:0
//----------------------------------------

静态成员有哪些分类?

我们知道,类可以分为数据成员和成员函数,同样的,静态成员的分类为:

  • 静态数据成员
  • 静态成员函数

静态数据成员

在类的外部,访问静态数据成员可以是:t1.numStu或者是t2.numStu,都是可以的,这表示任意对象都能共享该静态成员。但是通常我们会用Student::numStu来访问静态成员,其表明该静态数据成员不属于任意一个特定对象,而是属于Student类的。

静态数据成员的使用场景:

  • 用来保存流动变化的对象个数。
  • 作为一个标志,指示一个特定动作是否发生。
  • 一个指向链表第一个成员或最后一个成员的指针。

静态成员函数

静态成员函数的定义位置与一般成员函数一样,属于类定义的一部分。

与静态数据成员一样,静态成员函数也和类相联系,并不与类的对象相联系,所以访问静态成员函数时,不需要对象引导。但可以用对象来引用静态成员函数。

比如:

int main() {
    Student s;
    s.number();     //用对象引用静态成员函数
    Student::number();  //用类名来引导静态成员函数
}

需要注意的是:

一个静态成员函数不与任何对象相联系,因此不能对非静态成员进行默认访问。

比如:

class Student{
private:
    static int numStu;
    char name[40];
public:
    //静态成员函数,是所有对象共享的
    static char* sName() {
        cout<<numStu<<endl;
        return name;  //错误的操作
    }
};

在上述例子中,静态成员函数sName()只认类型,不认对象。对于静态成员函数来说,任何访问非静态成员的操作都是非法的。

另外,静态成员函数和非静态成员函数的根本区别在于:静态成员函数没有this指针。而非静态成员函数有一个指向当前对象的指针this

写在最后

好了,静态成员就复习到这里,欢迎大家到评论区一起讨论!