C++静态数据成员/静态函数成员总结

本文详细探讨了C++中静态成员的概念与应用,包括静态数据成员的定义与初始化方法、特性,以及静态成员函数的特点与限制。并通过多个示例代码展示了如何在实际编程中正确使用静态成员。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总结

     类中的静态成员真是个让人爱恨交加的特性。我曾经在面试时,被主考官抓住这个问题一阵穷追猛打,直把我问的面红耳赤,败下阵来。所以回来之后,我痛定思痛,决定好好总结一下静态类成员的知识点,以便自己在以后面试中,在此类问题上不在被动。
静态类成员包括静态数据成员和静态函数成员两部分。

一 静态数据成员:

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时,静态数据成员还具有以下特点:

1.静态数据成员的定义。
静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。
其定义方式与全局变量相同。举例如下:

xxx.h文件
class base{
private:
static const int _i;//声明,标准c++支持有序类型在类体中初始化,但vc6不支持。
};

xxx.cpp文件
const int base::_i=10;//定义(初始化)时不受private和protected访问限制.

注:不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。

2.静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。举例如下:
class base{
public :
static int _num;//声明
};
int base::_num=0;//静态数据成员的真正定义

class derived:public base{
};

main()
{
base a;
derived b;
a._num++;
cout<<"base class static data number _num is"<<a._num<<endl;
b._num++;
cout<<"derived class static data number _num is"<<b._num<<endl;
}
// 结果为1,2;可见派生类与基类共用一个静态数据成员。

3.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以。举例如下:
class base{
public :
static int _staticVar;
int _var;
void foo1(int i=_staticVar);//正确,_staticVar为静态数据成员
void foo2(int i=_var);//错误,_var为普通数据成员
};

4.★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用。举例如下:

class base{
public :
static base _object1;//正确,静态数据成员
base _object2;//错误
base *pObject;//正确,指针
base &mObject;//正确,引用
};

//neiloid 注: 这个原因应该是因为指针,引用都有固定大小的空间,所以可以这样分配。而静态成员为什么也可以呢??这个还需要查一下!

5.★这个特性,我不知道是属于标准c++中的特性,还是vc6自己的特性。
静态数据成员的值在const成员函数中可以被合法的改变。举例如下:

class base{
public:
base(){_i=0;_val=0;}

mutable int _i;
static int _staticVal;
int _val;
void test() const{//const 成员函数

_i++;//正确,mutable数据成员
_staticVal++;//正确,static数据成员
_val++;//错误

}
};
int base::_staticVal=0;

二,静态成员函数
静态成员函数没有什么太多好讲的。

1.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。举例如下:
class base{
static int func1();
int func2();
};

int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针


2.静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针。

3.静态成员函数不可以同时声明为 virtual、const、volatile函数。举例如下:
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};


最后要说的一点是,静态成员是可以独立访问的,也就是说,无须创建任何对象实例就可以访问

总结二

一、静态数据成员
1.几种错误用法总结
class A
{
    static int a;
    void test()
    {
        int b = a;//错误,因为没有定义a,会提示找不到a
    }
}
class A
{
    static int a = 2;//错误,非const静态数据成员不能在类体中初始化
}
class A
{
    static const int a = 2;
    void test()
    {
        int b = a;//错误,虽然static const成员变量可以再类体中初始化,但仍需
要在类外定义,且不能再次初始化
    }
}
const A::a[4];
class A
{
    static const char a[4] = "err";//错误,只有有序的const静态数据成员可在类
体中初始化
}
2.正确用法例子:
int A::a = 2;
const int A::b;
const int A::c("err");
class A
{
    static int a;
    static const int b = 2;
    static const char c[5];
}
3.可以通过const方法修改静态数据成员的值。
如果是public静态数据成员,也可以通过对象或者类域修改。
4.静态数据成员所有类共享,包括子类
5.不要在头文件中定义静态数据成员,否则可能会出现多次定义情况。
6.静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以
7.静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员
的只能声明为所属类类型的指针或引用
class A
{
    static A a;//可以
    A b;//不可以
}
二、静态函数成员
1.只能访问静态数据成员
2.静态成员函数不能同时声明成const, virtual,volatile类型。
3.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用类成员函数
指针来储存。
class A
{
    static int func1();
    int func2();
}
int (*psf)() = &A::func1;
int (A::*pf)() = &A::func2;
 
 

为什么虚函数必须是非静态成员函数 构造函数能为static吗?


用static声明的函数是静态函数。静态函数可以分为全局静态函数和类的静态成员函数。

Static关键字
在类中,用static声明的成员变量为静态成员变量,它为该类的公用变量,在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份。
用static声明的方法是静态方法,在调用该方法时,不会将对象的引用传递给它,所以在static方法中不可访问非static的成员。
静态方法不再是针对于某个对象调用,所以不能访问非静态成员。
可以通过对象引用或类名(不需要实例化)访问静态成员

C++类静态数据成员与类静态成员函数
函数调用的结果不会访问或者修改任何对象(非static)数据成员,这样的成员声明为静态成员函数比较好。且如果static int func(....)不是出现在类中,则它不是一个静态成员函数,只是一个普通的全局函数,只不过由于static的限制,它只能在文件所在的编译单位内使用,不能在其它编译单位内使用。
静态成员函数的声明除了在类体的函数声明前加上关键字static,以及不能声明为const或者volatile之外,与非静态成员函数相同。出现在类体之外的函数定义不能制定关键字static。
静态成员函数没有this指针。

在没有讲述本章内容之前如果我们想要在一个范围内共享某一个数据,那么我们会设立全局对象,但面向对象的程序是由对象构成的,我们如何才能在类范围内共享数据呢?

这个问题便是本章的重点:声明为static的类成员或者成员函数便能在类的范围内共同享,我们把这样的成员称做静态成员和静态成员函数。

下面我们用几个实例来说明这个问题,类的成员需要保护,通常情况下为了不违背类的封装特性,我们是把类成员设置为protected(保护状态)的,但是我们为了简化代码,使要说明的问题更为直观,更容易理解,我们在此处都设置为public。

以下程序我们来做一个模拟访问的例子,在程序中,每建立一个对象我们设置的类静态成员变自动加一,代码如下:

#include <iostream>
using namespace std;

class Internet
{
public:
     Internet(char *name,char *address)
     {
         strcpy(Internet::name,name);
         strcpy(Internet::address,address);
         count++;
     }

     static void Internet::Sc()//静态成员函数
     {
         cout<<count<<endl;
     }

     Internet &Rq();

public:
     char name[20];
     char address[20];
     static int count;//这里如果写成static int count=0;就是错误的
};

Internet& Internet::Rq()//返回引用的成员函数
{
     return *this;
}

int Internet::count = 0;//静态成员的初始化

void vist()
{
     Internet a1("中国软件开发实验室","www.cndev-lab.com");
     Internet a2("中国软件开发实验室","www.cndev-lab.com");
}

void fn(Internet &s)
{
     cout<<s.Rq().count;
}

void main()
{
     cout<<Internet::count<<endl;//静态成员值的输出
     vist();
     Internet::Sc();//静态成员函数的调用
     Internet b("中国软件开发实验室","www.cndev-lab.com");
     Internet::Sc();
     fn(b);
     cin.get();
}

上面代码我们用了几种常用的方式建立对象,当建立新对象并调用其构造函数的时候,静态成员cout便运行加1操作,静态成员的初始化应该在主函数调用之前,并且不能在类的声明中出现,通过运行过程的观察我们发现,静态成员count的状态并不会随着一个新的对象的新建而重新定义,尽而我们了解到类的静态成员是属于类的而不是属于哪一个对象的,所以静态成员的使用应该是类名称加域区分符加成员名称的,在上面的代码中就是Internet::count,虽然我们仍然可以使用对象名加点操作符号加成员名称的方式使用,但是不推荐的,静态类成员的特性就是属于类而不专属于某一个对象。

静态成员函数的特性类似于静态成员的使用,同样与对象无关,调用方法为类名称加域区分符加成员函数名称,在上面的代码中就是Internet::Sc();静态成员函数由于与对象无关系,所以在其中是不能对类的普通成员进行直接操作的。

如果上面的 static void Internet::Sc()修改成为:
static void Internet::Sc()//静态成员函数
{
    cout<<name<<endl;//错误
     cout<<count<<endl;
}

静态成员函数与普通成员函数的差别就在于缺少this指针,没有这个this指针自然也就无从知道name是哪一个对象的成员了。

根据类静态成员的特性我们可以简单归纳出几点,静态成员的使用范围:
1.用来保存对象的个数。
2.作为一个标记,标记一些动作是否发生,比如:文件的打开状态,打印机的使用状态,等等。
3.存储链表的第一个或者最后一个成员的内存地址。

为了做一些必要的练习,深入的掌握静态对象的存在的意义,我们以前面的结构体的教程为基础,用类的方式描述一个线性链表,用于存储若干学生的姓名,代码如下:

#include <iostream>
using namespace std;

class Student
{
public:
     Student (char *name);
     ~Student();

public:
     char name[30];
     Student *next;
     static Student *point;
};

Student::Student(char *name)
{
     strcpy(Student::name,name);
     this->next=point;
     point=this;
}

Student::~Student ()//析构过程就是节点的脱离过程
{
     cout<<"析构:"<<name<<endl;

     if(point==this)
     {
         point=this->next;
         cin.get();
         return;
     }

     for(Student *ps=point;ps;ps=ps->next)
     {
         if(ps->next==this)
         {
         cout<<ps->next<<""<<this->next<<endl;
         ps->next=next;//=next也可以写成this->next;
         cin.get();
         return;
         }
     }

     cin.get();
}

Student* Student::point=NULL;
void main()
{
     Student *c = new Student("marry");
     Student a("colin");
     Student b("jamesji");
     delete c;
     Student *fp=Student::point;
     while(fp!=NULL)
     {
         cout<<fp->name<<endl;
         fp=fp->next;
     }

     cin.get();
}

从上面的代码来看,原来单纯结构化编程需要的一个链表进入全局指针在这里被类的静态成员指针所替代(类的静态成员完全可以替代全局变量),这个例子的理解重点主要是要注意观察类成员的析构顺序,通过对析构顺序的理解,使用析构函数来进行节点的脱链操作。

为什么虚函数必须是非静态成员函数
如果定义为虚函数,那么它就是动态绑定的,也就是在派生类中可以被覆盖的,这与静态成员函数的定义本身就是相矛盾的。

==
主要有两个作用:
     1、管理静态数据成员;
     2、提供类范围的功能,即不需要对象来实现的功能。
比如Symbian中的NewL/LC方法就是static的

==
使用static关键字声明的函数成员是静态的,静态成员函数同样也属于整个类,由同一个类的所有对象共同维护,为这些对象所共享。

作为成员函数,它的访问属性可以受到类的严格控制,对于公有的静态函数成员函数,可以通过类名或对象名来调用,但一般情况下建议用对象名来引用静态函数成员(真的吗?)。注意,一般的成员函数只能通过对象名来调用。
由于一个类的静态成员函数只有一个拷贝,因此它访问对象的数据何函数使受到了限制。静态成员函数可以直接访问该类的静态数据成员。而访问非静态数据成员,必须通过参数传递方式得到对象名,然后通过对象名来访问。可以看到,通过静态函数成员访问非静态成员使相当麻烦的,一般的使用中,它主要用来访问全局变量或同一个类中的静态数据成员,特别是和后者一起使用,达到对同一个类中对象之间共享的数据进行维护的目的。
构造函数不可以定义为static,看了上面,应该可以理解原因。

注意,由于static不是函数类型中的一部分,所以在类定义之外定义静态成员函数时不使用static,在类中定义的静态成员函数是内联的。

一般来说,通过成员名限定比使用对象名访问静态成员要好。因为静态成员不是对象的成员。

静态成员可以被继承,这时,基类对象和派生类的对象共享该静态成员,除此之外,在类等级中对静态成员的其他特性(例如,静态成员在派生类中的访问权限,在派生类中重载成员函数等)的分析与一般成员类似。

静态成员函数不能被申明为虚函数,静态成员具有外部连接属性,static仅有的含义是使该成员为该类的所有对象共享。

类中的任何成员函数都可以访问静态成员,但静态成员函数只能通过对象名(或指向对象的指针)访问该对象的非静态成员,因为静态成员函数没有this 指针

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值