C++ 静态属性和静态方法
为什么需要静态属性和静态方法
静态成员是类级别的,也就是它和类的地位等同,而普通成员是对象(实例)级别的.类级别的成员,应先于该类任何对象的存在而存在,所以类的静态成员会被该类所有的对象共享,因此不能在静态方法里面访问非静态元素但非静态方法可以访问类的静态成员及非静态成员
当我们需要在每次创建对象时进行实例化对象数量的计数,使用普通变量的话,由于普通变量为类中的局部变量,也就是说,每建立一次对象,对象中的局部变量都会被初始化并重新写入,而有了类中的静态变量,由于静态变量是类中的全局变量且不可被类外的对象访问,全局变量值在每次对象构建时不会被初始化,而是在原值的基础上进行累计,使用静态属性和静态方法,保证了重要参数的安全性
所以静态数据成员的用途之一是统计有多少个对象实际存在
面对对象编程技术的一个重要特征是用一个对象把数据和对数据处理的方法封装在一起,在前面的例子里我们一直是在使用对象(也可以说是某个类的实例)来调用方法,每个方法只处理调用它的那个对象所包含的数据,所有的数据都属于同一个对象,这就引发了一个问题:如果我们所需要的功能或者数据不属于某个特定的对象,而是属于整个类的,该怎么办?
举例:
比如现在需要统计一下有多少只活的动物,那么我们需要一个计数器数量:每诞生一只宠物,就给宠物计数器加上1,每死掉一只,就减去1,此时我们首先想到的是创建一个全局变量来充当这个计数器,但这么做的后果是程序中的任何代码都可以修改这个计数器,稍不小心就会在程序里留下一个难以查堵的漏洞
所以坚决不建议在非必要的时候声明全局变量,我们真正需要的是一个只有在创建或删除对象时候才允许访问的计数器
这个问题必须使用C++的静态属性和静态函数才能完美地得到解决,C++允许我们把一个或多个成员声明为属于某个类,而不是仅属于该类实例化出的某个对象这么做的好处在于程序员可以在没有创建任何对象的情况下调用有关的方法 而且 有关数据仍能在该类的所有对象(静态和非静态皆可)之间共享
静态数据成员
静态数据成员的声明
静态数据成员实际上是类域中的全局变量,且类的静态数据成员在类内只能声明,定义和初始化必须在类外
因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的,所以静态成员不能在类内初始化
在C++中,类的静态成员(static member
)必须在类内声明,在类外初始化,像下面这样 :
class A
{
private:
static int count ; //类内声明一个static类的静态数据成员
};
int A::count = 0 ; //🌟类外初始化静态数据,注意,类外定义时不必再加static关键字说明其的静态属性!
👽类外定义和初始化是保证static成员变量只被定义一次的好方法,其保证了类中static成员A的静态属性
注意: 在C++中,类的静态常量成员(static const member
)可以在类内声明并初始化,像下面这样 :
class A
{
private:
static const int count = 0;//🌟静态常量成员可以在类内初始化
};
const数据成员不能在类内定义时初始化,在类外初始化也不行,其通常通过构造函数初始化.
static数据成员不能在类内初始化,其需要在类外初始化,因为static成员不属于任何一个对象.
static const数据成员可以在类内声明且同时进行初始化,但一般仅仅限于初始化整形参数.
静态数据的定义
所有的静态数据成员(const
,static
,static const
)都必须在.cpp
文件中定义 否则就会出现无法识别外部符号的错误
静态数据成员的作用域
静态数据成员被类的所有对象共享,包括该类的派生类对象,所以静态数据成员可以想象为类内的全局变量
静态数据成员可以成为成员函数的可选参数,而普通数据成员不可以,举例如下
class cla{
public :
static int i1; //静态数据成员
int i2; //普通数据成员
void f1(int i = i1);//OK 允许作为此类内的成员函数的参数进行值传递(因为静态数据成员可看作全局变量)
void f2(int i = i2);//错误 不允许
};
静态数据成员的类型可以为本类的类型,而普通数据成员不可以,举例如下
class Pet
{
public:
static Pet A;//todo静态数据成员的类型,可以是本类的类型,而普通数据成员则不可以
Pet A;//错误,普通数据成员的类型不能是本类的类型
🌟注意: 指针和引用数据成员的类型,也可以是本类的类型,而普通数据成员则不可以
Pet * a;//正确
Pet& b;//正确
}
静态成员函数
类的静态成员函数可以在没有定义任何对象前使用,即无须创建任何对象实例就可以使用此成员函数,举例如下:
class A
{
public:
static void B(); //建立一个静态成员函数
}
A::B();//正确访问静态成员函数的方式 格式为classname::funcname();
静态成员函数不可调用类的非静态成员,且静态成员函数不包含this指针,非静态成员必须与特定对象相对
类内声明静态变量及静态成员函数
class Pet
{
public:
static int getcount();//类内声明静态成员函数 正确
protected:
string thename;
private:
static int count;//类内声明静态变量 正确
static int count=10;//类内声明静态变量 错误 声明时不可进行赋值操作(此时未分配内存 变量赋值需要分配内存)
};
类外声明静态变量成员
静态成员的值对所有的对象是一样的.静态成员可以被初始化,但只能在类体外进行初始化
静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的,你在一个对象里给它赋值,其他对象里的该成员也会发生变化,为了避免混乱C++规定不可在类体内进行赋值,真正要为它们分配内存并进行初始化的时候,需要在类外进行声明 ,格式如下:
在类外声明静态变量的一般形式为 : 数据类型类名::静态数据成员名=初值
int Pet::count = 0;类外定义并初始化静态数据成员 (要使用静态变量和静态成员函数 必须分配内存和初始化)
类外实现静态成员函数
静态成员函数在类外实现时候无须加static
关键字,否则将会报错,若在类外实现上述的那个静态成员函数,是不能加上static关键字的,需要这样写:
int Pet::getcount()
{
return count;
}
注意:🎯
1.static成员的所有者是类本身和对象,但是对象拥有一样的静态成员,从而在定义对象时不能通过构造函数对其进行初始化
2.静态成员不能在类定义里边初始化,只能在class body外初始化
3.静态成员仍然 遵循public,private,protected
访问准则
4.静态成员函数, 没有this指针,它不能返回非静态成员,因为除了对象会调用他之外,类本身也可以调用,静态成员函数可以直接访问该类的静态数据和函数成员,而访问非静态数据成员必须通过参数传递的方式,得到一个对象名,然后通过对象名来访问,
总结:🎯
1.非静态成员函数可以任意地访问静态成员函数和静态数据成员,静态成员函数不能访问非静态成员函数和非静态数据成员。
2.调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,当同一类的所有对象使用一个量时,对于这个共用的量,可以用静态数据成员变量,这个变量对于同一类的所有的对象都取相同的值。静态成员变量只能被静态成员函数调用。静态成员函数也是由同一类中的所有对象共用。只能调用静态成员变量和静态成员函数
3.一个静态成员函数不与任何对象相联系,故不能对非静态成员进行默认访问,它们的根本区别在于静态成员函数没有this指针,而非静态成员函数有一个指向当前对象地址的指针this(也就是说静态成员不被对象包含)
如何创建一个静态属性和静态方法:
只需要在它的声明前加上static保留字即可
class Class
{
public:
static int a;//建立一个静态属性
static void b(int a);//建立一个静态方法
};
程序案例:
利用static静态属性和静态方法的特性,编写一个不用建立对象便可进行 宠物 这个类中的 宠物信息登记程序 的调用
要求 用户不可访问宠物的名字和宠物数量 且宠物数量为全局变量
程序如下
#include <iostream>
#include <string>
using namespace std;
class Pet
{
public:
Pet(string name);//构造函数
~Pet();//析构函数
static int getcount();//todo访问权限为public的对象计数器函数(接口函数 对象可以访问 并可以不建立对象进行全局访问)
//todo定义类的静态成员函数
//测试 与项目无关----------------------------
//static int para;
//void fun(int i = para); //todo静态数据成员可以成为成员函数的可选参数,而普通成员不可以
//static Pet type;//todo静态数据成员的类型可以是本类的类型,而普通数据成员则不可以
//Pet * a;//正确
//Pet& b;//正确
//---------------------------------------------------
protected:
string thename;//todo建立存放宠物名的参数thename 访问权限为protected(对象不可访问) 目的是防止用户不慎修改该参数 所以这个参数的赋值发生在本类的结构体中(即每次有对象建立便类内部进行赋值)
private:
static int count;//todo访问权限为private的计数值 只能本类中访问(对象不可访问)目的是防止用户不慎修改该参数 所以这个参数的赋值发生在本类的结构体中(即每次有对象建立便类内部进行赋值)
//todo定义类的静态成员变量
};
class Dog :public Pet
{
public:
Dog(string name);
};
class Cat :public Pet
{
public:
Cat(string name);
};
int Pet::count = 0;//todo类中的静态变量count 可以全局使用
Pet::Pet(string name)//todo构造函数 用于宠物数量的计数和宠物信息的显示 每次为类建立实例后都将被调用一次
{
thename = name;
count++;
cout << "一只小宠物出生啦!名字叫做:" << thename << endl;
}
Pet::~Pet()//todo析构函数 在程序尾部或对象寿命结束时被调用
{
count--;
cout << thename << "挂掉了" << endl;
}
Dog::Dog(string name) :Pet(name)
{
}
Cat::Cat(string name) : Pet(name)
{
}
int Pet::getcount()
{
return count;
}
int main()
{
Dog dog("wangcai");
Cat cat("miaomiao");
cout << "\n已经诞生了" << Pet::getcount() << "只宠物\n\n";
//todo以上这个静态函数就可以在未声明任何对象的情况下 使用classname::staticfunname的格式进行 类内函数的调用
//最大程度上规避了用户不小心修改count这个宠物数量变量的漏洞 因为这个静态函数中的count变量为private私有 仅能被Pet类内函数调用
//todo大括号建立区域
{
Dog dog2("tom");
Cat cat2("jerry");
cout << "\n现在呢,已经诞生了" << Pet::getcount() << "只宠物!\n\n";
}//todo大括号建立了区域后dog2和cat2的作用域仅存在于括号内 出了括号则进行析构也就是进行~pet() 从而导致宠物死亡
cout << "\n现在还有" << Pet::getcount() << "只宠物\n";
}
结果
关于花括号对 对象的生存周期的影响:
如果一个类的实例在花括号内被声明和定义 那么当程序运行到花括号外后 花括号内的对象将被视为已经结束使用,将被析构函数进行析构处理.
当上方程序为进行或括号包含后的运行结果
int main()
{
Dog dog("wangcai");
Cat cat("miaomiao");
cout << "\n已经诞生了" << Pet::getcount() << "只宠物\n\n";
//todo以上这个静态函数就可以在未声明任何对象的情况下 使用classname::staticfunname的格式进行 类内函数的调用
//最大程度上规避了用户不小心修改count这个宠物数量变量的漏洞 因为这个静态函数中的count变量为private私有 仅能被Pet类内函数调用
Dog dog2("tom");
Cat cat2("jerry");
cout << "\n现在呢,已经诞生了" << Pet::getcount() << "只宠物!\n\n";
cout << "\n现在还有" << Pet::getcount() << "只宠物\n";
}
结果
来自:
🔍
类的静态数据成员声明、定义、初始化🔍