声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用
static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
一、静态成员变量
1)特性
- 所有静态成员为所有类对象所共享 ,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义在类内声明,定义时不添加static关键字,在类中声明时加static关键字
- 类静态成员可用 类名::静态成员名 或者 对象.静态成员名 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
2)使用场景
现在我们有一个要求,需要统计现存对象以及累计创建对象的个数,所以我们依赖一个在没有实例存在的时候仍能存在的变量,而且该变量要和该类强相关,此时我们便可以利用到静态变量的特性,创建两个静态变量在public中(公共区中),并在类的外面定义他们,初始值为零,在每次调用构造函数或者拷贝构造函数时都使n加一,m也加一,每次调用析构函数时,m就减一,如此,n的数值就是累计创建的个数,m的值就是现存对象的个数,它们和类直接关联,而且在所有实例被清除后它们仍能存在着去记录数据。
总结:静态成员变量的作用就是突破类域
3)缺点:可能会在意外调用后被修改
但是这样使用静态变量也有一定的弊端,当有使用者(非用户,而是使用该类的其他人)在改变m和n的值时,将会影响我们的判断。
解决办法就是将这种变量全都放在private中,这样只有我们自己能使用它,它的适用范围变为了类的内部,我们称他为"类全局"。当然,如果有友元函数声明或者友元类声明也能调用他们。
4)注意
定义在类中的全局变量不走初始化列表
空指针、匿名对象都能访问他,因为他放在整个类中(限制于公有的前提)
二、静态成员函数
1)特性
1.静态成员一大特征就是无this指针,因此!!它没办法调用非静态的成员函数,毕竟非静态成员函数里面可都是有this指针的,所以一般和静态成员变量配套使用;
2.它可直接被调用而无需创建对应的"实例",类似这样:
cpp
YourClass:: YourStaticFunction();
3.它只能访问静态变量,这里的静态变量指同一类中的静态变量。如果是子类或者友元类的静态成员函数也可以访问该类中的静态成员变量。
三、实例观察现象
一下是作者编写的用于观察现象的小实例:
cpp
#include<iostream>
using namespace std;
//我们创建一个用于演示的"A"类
class A
{
public:
A();//此处采用定义和声明分离的方法
A(const A& other);
~A();
static void Print();//声明给静态,定义不用加static
A& operator= (const A aa);
private:
static int _Creat;//静态成员变量_Creat声明
static int _NumNow;//静态成员变量_NumNow
int _num;
};
//现在是在类外
//直接使用类名加冒号加变量名的方式直接给该类变量定义
//而且可以在这里个静态成员变量赋初识值
int A::_Creat = 0;
int A::_NumNow = 0;
//声明一个非静态成员函数f2
A f2(void);
void func1(void);
//构造函数
A::A()
:_num(0)
{
cout << "A( )" << endl;
//在构造函数中我们可以调用该类中的静态成员变量
//这样我们就能统计存在和累计创建实例的个数了
_Creat++;
_NumNow++;
}
//拷贝构造函数
A::A(const A& other)
{
_num = other._num;
cout << "A(const A& other)" << endl;
}
//析构函数
A::~A()
{
cout << "~A( )" << endl;
_NumNow--;
}
//输出两个变量的函数
void A::Print()
{
cout << "现存数量:" << _NumNow << " , " << "累计创建:" << _Creat << endl;
}
//赋值运算符重载
A& A::operator= (const A aa)
{
_num = aa._num;
return *this;
}
using namespace std;//命名空间展开
//定义一个非静态成员函数f2
A f2(void)
{
A a; //生成一个实例后传值返回
return a;
}
//定义一个非静态成员函数f1用于演示
void func1(void)
{
cout << "/********生成一个实例********/" << endl;
A aa0; //生成一个实例
A::Print(); //输出变量
cout << "/******创建一个匿名对象******/" << endl;
A(); //创建一个匿名对象
A::Print(); //输出变量
cout << "/**************************/" << endl;
cout << " 将f2返回值传值返回给实例temp1" << endl;
A temp1 = f2(); //将f2返回值传值返回给实例temp1
A::Print(); //输出变量
cout << "/****先创建后使用运算符赋值**/" << endl;
A temp2;
temp2 = f2();
A::Print(); //输出变量
cout << "/****f2返回值赋给匿名对象****/" << endl;
A() = f2(); //f2返回值赋给匿名对象
cout << "/**************************/" << endl;
}
//主函数
int main(void)
{
func1();
//函数结束后再观察一次变量
std::cout << "func1 done" << endl;
A::Print();
return 0;
}
通过观察显现我们还可以看到编译器存在将多次构造再拷贝构造直接优化为一次构造的情况,例如第三组的输出情况。
四、遇到的问题:
遇到了现存数出现负数的问题:
赋值运算符重载返回值不是传引用返回导致多次调用析构的问题,在刚开始的现象中出现了现存数量为-1的情况,原因是析构函数多调用了一次,刚开始认为是赋值给匿名对象的原因,于是注释掉后重试发现想象仍然存在,有两组涉及到赋值运算符的组,编译器优化掉赋值的一组没有问题,最后发现问题在没有优化的有这组里,那么就可以确定是赋值运算符重载的问题了
然后发现是赋值运算符重载采用了传值返回,将其改为传引用返回后可以正常运行,我们平成使用赋值运算符重载也要使用传引用返回,这样可以减少不必要的拷贝,并且传引用返回可以链式赋值,而传值返回则不行,
然后尝试性得给赋值运算符重载的返回值从传值返回改为传引用返回,然后就得到了正确的结果
cpp
void func1(void)
{
cout << "/******************/" << endl;
A() = f2();
A::Print(); //输出变量
cout << "/******************/" << endl;
}
那么为什么赋值运算符重载传值返回会出现多次析构的问题呢?
经过在代码中添加输出,得到了以下结果:
赋值运算符重载调用了拷贝构造,如果是传引用返回则不会调用这一次拷贝构造,
所以我猜测是传值返回本质是将一个临时的自定义类型对象的值拷贝给另外一个对象的过程,但是不知道为什么这个过程中创建临时变量的构造没有触发,或者理解为没有显示调用但是析构却显示调用了,导致计数出现问题,应该是编译器部分优化的问题。
如果说怎样规避这样的问题的话那就是尽可能减少这类隐式的转化。
以下是问题代码:
cpp
#include<iostream>
using namespace std;
//我们创建一个用于演示的"A"类
class A
{
public:
A();//此处采用定义和声明分离的方法
A(const A& other);
~A();
static void Print();//声明给静态,定义不用加static
A operator= (const A aa);
private:
static int _Creat;//静态成员变量_Creat声明
static int _NumNow;//静态成员变量_NumNow
int _num;
};
//现在是在类外
//直接使用类名加冒号加变量名的方式直接给该类变量定义
//而且可以在这里个静态成员变量赋初识值
int A::_Creat = 0;
int A::_NumNow = 0;
//声明一个非静态成员函数f2
A f2(void);
void func1(void);
//构造函数
A::A()
:_num(0)
{
cout << "A( )" << endl;
//在构造函数中我们可以调用该类中的静态成员变量
//这样我们就能统计存在和累计创建实例的个数了
_Creat++;
_NumNow++;
}
//拷贝构造函数
A::A(const A& other)
{
_num = other._num;
cout << "A(const A& other)" << endl;
}
//析构函数
A::~A()
{
cout << "~A( )" << endl;
_NumNow--;
}
//输出两个变量的函数
void A::Print()
{
cout << "现存数量:" << _NumNow << " , " << "累计创建:" << _Creat << endl;
}
//赋值运算符重载
A A::operator= (const A aa)
{
_num = aa._num;
return *this;
}
using namespace std;//命名空间展开
//定义一个非静态成员函数f2
A f2(void)
{
A a; //生成一个实例后传值返回
return a;
}
//定义一个非静态成员函数f1用于演示
void func1(void)
{
cout << "/******************/" << endl;
A aa0; //生成一个实例
A::Print(); //输出变量
cout << "/******************/" << endl;
A(); //创建一个匿名对象
A::Print(); //输出变量
cout << "/******************/" << endl;
A temp1 = f2(); //将f2返回值给实例temp1
A::Print(); //输出变量
cout << "/******************/" << endl;
//A() = f2(); //f2返回值赋给匿名对象
//这个行为很危险,具体逻辑可能需要看到汇编层才能判断
//如果这样赋值会出现多次析构的情况
//我们尝试另一个例子和上一组形成对照
A temp2;
temp2 = f2();
A::Print(); //输出变量
cout << "/******************/" << endl;
}
//主函数
int main(void)
{
func1();
//函数结束后再观察一次变量
std::cout << "func1 done" << endl;
A::Print();
return 0;
}
多次析构情况如下: