前言

成员变量就是class中定义的变量,不论是public下还是private下,都是这个类的成员变量
那么静态成员变量,就是加上关键字static


正文


静态成员变量

静态变量的生命周期会随着程序结束才释放,而是脱离{}作用域。
不过c++处理这种可能还是希望放在未命名的命名空间里。

因为static的生命周期要等到程序结束,所以说

  1. 所有类在实例化之后,共享类中的静态成员变量
  2. 而且类在没有实例化的情况下,静态变量已经有了空间,仍然可以访问到
  3. 那么可以说类的静态成员变量并不是完全属于类,就跟成员函数有点相似
1
2
3
4
5
#pragma once
class Box{
public:
static int count;
};

因为类的特殊性,所以静态成员变量也是无法直接初始化的。

你可以选择在外部初始化

1
int Box::count = 100;

但是注意,这里是在Box类这个作用域下的count
如果你是int count=100; 这是两个不相干的变量。


第一个特性

其次是共享静态成员变量。也就是说当你某个实例化对象改变了count的值,其它的实例化对象中的count也会发生改变,因为它们本身就是一个东西。

1
2
Box b1, b2, b3, b4;
b1.count++;

通过改变实例b1,发现b3的count值也改变,有疑虑最直接的就是打印地址

b2和b3的count地址是一样的。足以说明静态成员变量共享内存

其实就是一个没有外部属性的全局变量hh。


第二个特性

说没有实例化时也能访问count,这个其实在刚才外部初始化的时候就说明了这个问题
int Box::count = 100;

那么我们的印证方法也是修改它

1
2
3
Box::count = 200;
std::cout << Box::count << std::endl;
std::cout << b1.count << std::endl;

显然在Box::下可以直接访问,我们在类中再定义一个正常的成员变量

1
2
3
4
5
6
#pragma once
class Box{
public:
static int count;
int hp;
};

报错也说了,非静态成员必须与特定对象相对,就是说要实例化后才能用。


第三个特性

按照常规惯例sizeof查看一个类可以直接算,但是包含静态变量的时候

1
2
3
4
5
6
#pragma once
class Box{
public:
static int count;
int hp;
};

你会发现是个4。

有疑虑注释掉hp变量再看。

这个1前面说过,是为了防止空类多个实例化的时候共用一个地址导致逻辑混乱的问题

不过就足以说明第三个特性,静态成员变量不完全属于类,本质上就是一个没有外部属性的全局变量。只不过给他套上了类的作用域。


新特性初始化静态成员变量

在c++17中我们学过它除了内联函数还可以内联变量。

1
2
3
4
5
6
#pragma once
class Box{
public:
inline static int count{100};
//int hp;
};

注意要将项目属性切到c++17标准

不过毕竟是新特性,有些项目还是遵循老版本,所以不建议乱用。

内联变量的起因就是ODR一次定义原则,一个变量或者实体只能出现在一个编译单元内,除非这个变量用inline修饰。
就是眼下这种场景,一个类中有一个静态成员变量,而且在外部进行了一次初始化,本身是符合ODR一次定义原则。但是多个.cpp源文件同时包含这个头文件的时候,编译器在链接阶段就会发生报错。


套构造和析构

既然静态成员变量共享的,就可以用它判断一共实例化了几个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma once
class Box{
public:
inline static int count{};
//int hp;

Box(){
this->count++;
}

int getCount(){
return this->count;
}
};

看到实例化了4个对象,打印也是4。没啥问题。

但是,没啥问题就是有大问题,我们知道有声明周期这个东西,目前的实例都是在main函数发生的,如果在其他地方也发生过实例化,那么count还是会自增,就不符合逻辑。

1
2
3
void test(){
Box b1, b2, b3, b4;
}

按道理离开了test函数里面的就释放了,但是count没有减去。

当然解决方法就在析构函数上,构造的时候自增了,析构就自减就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
class Box{
public:
inline static int count{};
//int hp;

Box(){
this->count++;
}

int getCount(){
return this->count;
}

~Box(){
this->count--;
}
};

再回过头来看,发现符合预期效果了。


const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
class Box{
public:
const static int count{};
//int hp;

Box(){
this->count++; //error
}

int getCount(){
return this->count;
}

~Box(){
this->count--; //error
}
};

const修饰过之后是可以直接在里面初始化。但是构造和析构就无法改变了,因为是一个常量。

然而在类外初始化const int Box::count{};
也还是要注意上面说的问题,ODR一次定义原则,定义在外面,被多个源文件包含,在链接阶段会产生错误。

解决办法也还是要么在类中就初始化限定了,要么就是使用c++17的特性 inline变量。
inline const static int count{};

虽然很臭屁,但是还是要知道。


静态成员函数

1
2
3
static void test(){

}

模板就这样,多个static关键字修饰。

  1. 不管有没有实例化类,也是都可以访问静态成员函数
  2. 类的静态成员函数不能访问非静态的成员变量
  3. 类的静态成员函数不能是const
  4. 类的静态成员函数不能使用this指针

第一特性

很简单。。就跟之前访问静态成员变量一个操作:

1
2
3
static void test(){
std::cout << "static function" << std::endl;
}

就这样就完事了。


第二特性

因为静态成员函数不需要实例化就能调用,但是hp是只有实例化后能会分配内存空间,才能调用的。
所以静态成员函数只能访问静态成员变量,因为二者都是不需要实例化就能调用,底层来讲就是内存都早早分配了。


第三特性

与其说不能是const,或者应该说用不到const。
我们说const实例化一个对象,只能调用const的函数,但是静态成员函数不需要实例化就能调用,所以即便不是const函数,他也能调用。


第四特性

这个和老二有相似处,因为静态成员函数不需要实例化就能调用,它无法正常的访问一个需要实例化才能调用的成员变量,既然访问不了需要实例化才能访问的,那么this指针也就用不到了,无效了。


结语

内存分配还是绕来绕去