MENU

C++:静态成员与友元

• March 5, 2021 • Read: 287 • 高级语言,学习笔记

静态成员

静态成员包含静态数据成员和静态成员函数,那么为什么会存在静态成员?

我们在开发中会发现,有一些属性是所有对象所共有的,比如:学生总人数,链表头指针,尾指针等,这些数据成员没有必要和对象进行关联,它们更需要被所有对象所共享,而不是在每个对象中都对其重复进行操作。如果将其放在全局变量中,那么 C++ 的封装性将受到影响,静态成员案例见以下程序片段

class student{
    public:
        student(string nName = "null");
        ~student(){};
    private:
        static int stuNum;//学生总人数
        string name;
        //..
};

静态数据成员

上面的程序片段定义了静态数据成员 stuNum,它将用于记录学生总人数,它将如何被合理使用呢?我们可以依靠构造函数和析构函数来对其进行操作

student::student(string nName){ //初始化一个新对象时stuNum+1
    cout<<"create one student"<<endl;
    name=nName;
    stuNum++;
    cout<<stuNum<<endl;
}
student::~student(){ //析构一个对象时stuNum-1
    cout<<"delete one student"<<endl;
    stuNum--;
    cout<<stuNum<<endl;
}

数据成员 stuNum 不属于任何一个对象的一部分,无论有多少个对象,stuNum 值都只会产生一个,并且所有属于 student 类的对象均能够访问静态数据成员 stuNum,且共享。静态数据成员的空间也不会随着构造函数执行而分配,也不会因为析构函数的空间释放而释放。它的空间分配有三种可能的情况:

  1. 作为类的外部接口的头文件
  2. 类定义的内部实现
  3. 程序 main()函数前的全局变量声明与定义处

因为静态数据成员需要实际地分配空间,那么它显然不能在类的声明中定义,因为类的声明并不存在空间的分配,所以不能在类的声明中写入以下语句

static int stuNum = 0;//error

它也不能在头文件的类声明外部定义,这样会造成在多个使用该类的源文件中会重复定义此静态数据成员。

静态数据成员是类的一部分,那么将其放在类的内部实现部分来定义就非常合适,重用该类时,简单包含其头文件即可,如以下程序片段

//student.h
#ifndef _STUDENT_H_
#define _STUDENT_H_
class student{
    public:
        student(string nName="null");
        ~student();
    private:
        static int stuNum;
        string name;
};
#endif

//student.cpp
#include "student.h"
//#include ...
int student::stuNum = 0;//在此处分配静态数据程序空间,并将其初始化为0
student::student(string nName){
    //..
    stuNum++;
}
student::~student(){
    //..
    stuNum--;
}

然后在 C++ 工程文件中包含 student.cpp 和 main()函数所在 cpp 文件即可,这是静态数据成员的标准使用方法。

之前说到,静态数据成员不与任何对象关联,它被所有对象所共享,那么在类的外部应该怎样去访问静态数据成员呢?

其形式可以是(通常使用第二种,它将更规范):

  1. 任一对象名.静态数据成员(s1.stuNum;)
  2. 类名::静态数据成员(student::stuNum;)

静态成员属于类,不属于任何一个对象。

静态成员函数

同样的,静态成员函数也属于类,不与类的任何一个对象相联系,所以在调用静态成员函数时,并不需要使用对象来进行引导,标准方法为:类名::静态成员函数名();

下面程序片段演示了静态成员函数的调用方法

class student{
    public:
        static int stuNumber(){
            return stuNum;
}
    //..
    private:
        static int stuNum;
        string name;
};
int student::stuNum = 0;//初始化静态数据成员
int main(){
    student s1;//初始化对象s1
    cout<<s1.stuNumber()<<endl;//合法,使用对象引用静态成员函数
    cout<<student::stuNumber()<<endl;//合法,使用类名引导静态成员函数(标准用法)
    return 0;
}

静态成员函数属于类,不与该类的任何一个对象相联系,那显然也不能对非静态成员进行默认访问,因为非静态成员与对象相关联。但静态成员函数可以通过对象指针等方法访问对应对象中的非静态成员。

以下程序片段的实现方法不合法

class student{
    public:
        static char* sName(){
            cout<<stuNum<<endl;
            return name;//error 此name为哪一个对象中的name?
}
    private:
        static int stuNum;
        string name;
};

友元

为什么需要友元?在我之前的 OA 项目中,就遇到了该问题:

有时候一个普通的函数也需要直接访问一个类中的 private 或者 protected 数据成员,如果没有友元,那么我们只能将需要访问的数据成员归类于 public,这样一来,任何一个函数都能轻易访问该数据成员,非常影响数据安全。

如何创建友元?

仅需要在类中定义一个普通函数,在前方表上关键字 friend,它就成为了该类的友元,可以访问该类中的任何一个成员,以下程序片段演示了友元的使用方法

class Vector{
    public:
        //..
        friend Vector Multiply(Matrix& m, Vector& v);//友元
    private:
        //..
};
class Matrix{
    public:
        //..
        friend Vector Multiply(Matrix& m, Vector& v);//友元
    privat:
        //..
};

经过以上操作后,Multiply 函数将可以访问 Vector 和 Matrix 类中的任何成员。

友元的使用

像以上程序片段那样创建友元函数后,就可以通过该函数访问多个类中的任何成员了,需要注意的是,虽然友元函数写在了类的声明中,但它并不是一个成员函数,它仍然只是一个普通的函数,只是有访问部分类中任意成员的权限,可以理解为它是类的一个朋友。

其次,一个类的成员函数可以是另一个类的友元,如何操作?见以下程序片段

class teacher{
    public:
        void assignGrades(student& s);
    private:
        //..
};
class student{
    public:
        //..
        friend void teacher::assignGrades(student& s);//teacher类中的成员函数成为了student类中的友元
    private:
        //..
};

此时,teacher 类中的成员函数 assignGrades 就可以访问 student 类中的任何成员了。

友类

整个类也可以是另一个类的友元,这类友元被称为友类。如何使用,见以下程序片段

class teacher{
    public:
        //..
    private:
        //..
};
class student{
    public:
        friend class teacher;//类teacher成为了student类的友类
    private:
        //..
};

友类的每个成员函数均能访问另一个类中的 private 或 protected 数据成员,使用时应注意安全。


编辑:Henry 2021-03-05 未授权禁止转载


Archives QR Code
QR Code for this page
Tipping QR Code