静态成员

当用关键字static说明一个类成员时,该成员为静态成员,包括静态数据成员,静态成员函数
静态数据成员,如:

class ABCD{int value;static int s_value;};

对静态成员除了在类内声明外还必须在类声明体外进行定义和初始化(不应该在构造函数中初始化,并且大部分环境也不支持这样做)

int ABCD:😒_value=n;

注意初始化时还要指定数据类型。如果没有给定初始化值,那么默认初始化为0(此时分配在在bss段)。

同c中的静态数据类型相似,类中的静态成员在所有类对象中共享一个存储空间,如:

ABCD A,B,C,D;

其中A,B,C,D四个对象具有value和s_value两种数据成员,但value属性在每个对象中都有一份存储空间,而类的所有对象共享类的静态数据成员,无论建立多少个该类的对象,静态数据成员都只有一份拷贝,因此静态数据成员属于类,而不属于具体某个对象

  • 具体来说,静态成员变量不占用对象的内存,静态成员变量和普通的静态变量类似,都在内存分区中的静态存储区分配内存
  • 创建的时间也比较特殊,不是在声明类时分配,也不是在创建对象时分配,而是在类外初始化时分配(这也许就是为什么静态变量必须在类外声明一次的原因,哪怕没有初始化)

静态数据成员也有public和private之分,在类外只能访问public属性的静态数据成员,在类内可以访问所有属性的静态数据成员
由于静态数据成员属于类,因此可以在类外以如下格式访问public静态数据成员:

类名::静态数据成员;
对象名.静态数据成员;
对象指针->静态数据成员;

特别的,在对象不存在时,也可以访问静态数据成员

静态成员函数,如:

class ABCD{static void ShowStaticValue();}

静态成员函数由于只属于一个类,因此没有this指针,同静态数据成员一样,静态成员函数也可以声明成public或private访问权限,调用形式同静态数据成员
静态成员函数只能访问静态数据成员、静态成员函数和类以外的函数和数据,不能访问非静态数据成员,反之静态成员函数或静态数据成员可由任意访问许可的函数访问
一是因为静态成员是整个类的成员,因而具体对象的情况对其是未定义的,静态成员代表的是整个类共有的性质,是独立存在的;二是在于静态成员函数没有this指针,也找不到对象的成员数据

静态成员示例程序1:统计合格时钟,不合格时钟,生产的总时钟数

#include<iostream>
#include<cstring>
using namespace std;
class Clock
{
    static int qualified,inqualified,total;
    bool isqualified;
public:
    Clock(bool i);
    void ShowProduct();
};
int Clock::qualified=0;
int Clock::inqualified=0;
int Clock::total=0;
Clock::Clock(bool i):isqualified(i)
{
    if(i==0)inqualified++;
    if(i!=0)qualified++;
    total++;
}
void Clock::ShowProduct()
{
    cout<<qualified<<' '<<inqualified<<' '<<total<<endl;
}
int main()
{
    Clock c1(false),c2(true),c3(c1);
    c3.ShowProduct();
}

构造函数一般不能作为私有类型,因为这样的话用该类创建对象时编译会出错,但是通过使用静态数据类型和成员函数就可以在不创建对象的情况下对该类进行操作
静态成员示例程序2:单例设计模式,程序运行中该类永远只有一个实例

#include<iostream>
#include<cstring>
using namespace std;
class Simpledesign
{
private:
    static Simpledesign *instance;                   //声明一个Simpledesign类型的指针,用于存储唯一实例的首地址,并且不对外开放
    Simpledesign(){}                                 //将构造函数声明为私有
public:
    static Simpledesign *Getinstance()               //定义一个静态成员函数,创建单例
    {
        if(instance==0)instance=new Simpledesign();  //当instance为NULL时,new一个Simpledesign实例
        return instance;                             //返回instance,实际上每次调用返回的都是第一次的Simpledesign对象首地址
    }
};
Simpledesign Simpledesign::*instance=NULL;           //将instance初始化为NULL
int main()
{
    Simpledesign *s1,*s2;
    s1=Simpledesign::Getinstance();                  //类外直接调用Simpledesign类的静态成员函数
    s2=Simpledesign::Getinstance();
} 

常对象,常成员

如果某个对象不允许被修改,则称该对象为常对象,c++使用const来定义常对象,const也可以用来限制类的数据成员和成员函数,分别称为类的常数据成员和常成员函数
常对象和常成员明确规定了程序中各种对象的变与不变的界限,从而进一步增强了c++程序的安全性和可控性

常对象

常对象的值不能被修改,常对象不能访问非常成员函数(访问非常成员函数的话就有修改数据的权限了),只能调用常成员函数,如:

const Clock c1(9,9,9);Clock c2(10,10,10);

此时类似c1=c2,c1.ShowTime() 编译器都会报错

常对象使用场合通常是:

  • 函数返回值
  • 函数形参(常引用)

当对象作为参数传递进入函数时,如果不希望对象在函数内部被修改时,就可以定义为常对象的形式将其传入函数

常数据成员

常数据成员声明的基本格式:

const 类型名 数据成员名 (或者const 放到类型名后面)

注意,一般的常数据成员只能通过初始化列表的形式获得初值,静态常数据成员还是一样在类声明体外进行初始化,例:

class A
{
private:
    const int &r,a;
    static const int b;
public:
    A(int i):a(i),r(a){}         //因为a,r已经被声明为const不能赋值,使用形如A(int i){a=i,r=a}的形式编译器会报错
    void Display()               //所以只能通过表达式表的形式初始化
    {
        cout<<a<<' '<<r<<endl;
    }
};
const int A::b=n;                //与一般的静态数据成员一样,在类外定义

常成员函数

常成员函数不能修改对象的非静态数据成员的值(大概是因为this指针是const对象指针类型的吧),也不能调用该类中没有用const修饰的成员函数,其基本格式为:

返回类型 成员函数名(参数表) const; 表示这个函数对类不会进行任何修改

可以理解为常成员函数是供常对象操作的(不过非常对象和成员函数也是可以调用常成员函数的)

注意,const修饰成员函数时,放在函数名前面和后面所起的作用是不一样的:

  • const放在函数名后,表明这个函数是常成员函数,不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变
    • 并且限定常对象只能调用这类常成员函数
  • const放在函数名前面,表示函数的返回类型是常量,不能修改
  • const可以参与重载函数的区分,即可以将成员函数定义为常成员来进行重载,const重载主要是通过能否对传入的参数进行修改判断的
    • 注意是要定义函数成员为常成员才算重载,如果是修饰参数的话则与非类成员函数的一般重载规则是一致的

补充:const修饰指针的情况

先确定一个规则:const默认与左边结合,左边没有东西则与右边结合,在这个规则下进行分析。

  • const int*a
    • const与int结合,因此变量a是一个指向常量整型的指针。
  • int const*a
    • const与int结合,因此变量a同const int*。
  • int*const a
    • const与*结合,因此变量a是一个指向整型的常量指针。
  • const int*const a
    • 第1个const与int结合,第2个const与*结合,因此变量a是一个指向常量整型的常量指针。
  • int const*const a
    • 第1个const与int结合,第2个const与*结合,因此变量a与上同。
  • int const* const*a
    • 第1个const与int结合,第2个const与左边的* 结合,而变量a前还有1个多出来的* ,因此变量a是一个二级指针,即指向const int* const或int const*中常量指针的指针。
  • int const* const*const a
    • 第1个const与int结合,第2个const与左边的* 结合,第3个const也与左边的* 结合,因此变量a是一个常量二级指针,即指向const int* const或int const*中常量指针的常量指针。