什么是静态成员
首先了解一下为什么产生了静态成员的需求,有时候需要一些成员直接和类本身相关,而不是和所有对象保持关联。举一个C++ Primer中的例子:
一个银行账户类可能需要一个数据成员来表示当前的基准利率。在此例中,我们希望利率与类关联,而非与类的每个对象关联。从实现效率的角度来看,没必要每个对象都存储利率信息。而且更加重要的是,一旦利率浮动,我们希望所有的对象都能使用新值。
简单来说,静态成员不属于某个对象,而被所有的对象共有,当它被某个对象修改后,所有其他对象都会受到影响。
声明静态成员
在成员前面添加static关键字就可以声明静态成员,静态成员可以是private或public,类型也没有限制。
class Account {
private:
std::string owner;
double amount;
//静态成员
static double interestRate;
static double initRate();
public:
void calculate() {
amount += amount * interestRate;
}
//静态成员
static double rate() {
return interestRate;
}
static void rate(double);
}
如果声明了Account的对象,那么对象持有的数据成员有两个:owner和amount,而另外两个静态成员interestRate和initRate被所有Account对象共享。
使用静态成员
虽然静态成员不属于任何对象,但是也可以像普通成员一样通过对象访问静态成员
Account ac1;
Account *ac2;
//通过对象访问
r = ac1.rate();
//通过指针访问
r = ac2->rate();
或者可以通过作用域运算符直接访问静态成员
double r = Account::rate();
定义静态成员
定义静态成员函数
静态成员可以在类内定义也可以在类外定义。
如果在类外定义,因为类内的声明部分已经加了static关键字,所以定义部分不需要重复添加
class Account {
...
static void rate(double);
...
};
//需要使用作用域运算符指明成员所属的类,不需要重复添加static关键字
void Account::rate (double newRate) {
interestRate = newRate;
}
static成员初始化
>>理解static修饰的作用
因为静态数据成员不属于类的任何一个对象,所以它不是在创建类的对象的时候被定义和初始化的。
-
static修饰类内数据成员时:
表明这个数据成员独立于所有对象之外,所以只能在外部定义和初始化每个静态(数据)成员。并且一个静态数据成员只能定义一次。
static成员变量的内存在所有对象之外,保存在全局数据区,所以它的声明周期等同于整个程序的周期,static数据成员被定义后就一直存在于程序的整个生命周期中。class Test { private: ... static int var ... }; //在类外初始化static数据成员 int Test::var = 1;
如果一定要在类内初始化静态数据成员,那么只能使用初始化为常量类型,而且是字面值类型的constexpr类型:
const和constexpr是有一定区别的,constexpr相比const更强调常量属性,不过大部分情况下(包括这里)二者是等同的,所以关于二者的区别这里就不赘述了。//在类内初始化静态常量 class Test { private: //合法,使用字面值2初始化了静态常量 static constexpr int s = 2; const int a = 1; //非法,只能是字面值常量 static const int b = a; };
静态数据成员可以作为默认实参
class Test { private: int a = 1; static constexpr int b = 2; public: //非法,a是非静态变量,不能作为默认实参 int getVal_1(int val = a) { return val; } //合法 int getVal_2(int val = b) { return val; } };
-
static修饰成员函数时:
访问限制
带有static关键字的类成员函数能访问的变量只有1.全局变量;2.参数;3.类内static数据成员
全局变量是独立于类和函数之外的变量。
//全局变量会被默认初始化 int globalVar; class Test { private: int a = 1; static int b; public: static int func (int val) { //非法,不能访问非static变量 a = 2; //合法,可以访问static变量和参数 b = val; //合法,可以访问全局变量 globalVar += b; //在静态方法内定义局部变量不受限制 int result = globalVar + b; return result; } }; //必须在类外初始化static成员变量 int Test::b = 2;
定义位置:
既然static对于函数的限制只有可访问变量,那么其实staitc函数的定义可以在类内也可以在类外。
根据建议,除了一些简单的内联函数之外,所有的类成员函数最好都把声明和定义分离,即函数的声明在类内,定义在类外。所以static成员函数定义在类外其实跟它是static没关系。class Account { private: std::string owner; double amount; static double interestRate; static constexpr double todayRate = 42.42; static double initRate() { return todayRate; } public: void calculate() { amount += amount * interestRate; } //静态内联函数 static double rate() { return interestRate; } //静态函数的声明 static void rate(double); }; //静态数据成员的定义,必须写在类外 double Account::interestRate = initRate(); //静态函数成员的定义,可以写在类内,但是建议写在类外 void Account::rate(double newRate) { interestRate = newRate; }
static函数成员不能定义为const类型
首先最基础的,把函数定义为const是在参数列表后面加const关键字,从而把函数声明为常量成员函数。
那么常量成员函数有什么性质呢?
-
在其中不能修改对象的普通(即既不是static也不是mutable)数据成员。
-
const成员的this指针是指向常量的指针。
这里涉及到了this指针,简单来说this指针是针对对象设置的,当对象调用成员函数时,程序从对象调用的位置转移到了类内的成员函数中,那么如何记住当初调用它的对象的地址呢?方法就是隐式传递一个this指针,指向调用这个函数的对象。
如果一个函数是常量成员函数,那么这个函数收到的this指针会成为指向常量的指针,代表着这个常量成员函数不会改变它指向的对象的(非静态)数据成员。
既然如此,那么联想一下static成员函数的性质:
- 首先static成员不属于对象,所以它没有this指针;
- 其次static成员不能访问类的成员(非静态)数据成员(不在可访问范围内),所以就无从谈起修改对象的数据成员。
所以把static成员函数声明为const就没有任何意义了。
static函数成员还可以是不完全类型。
不完全类型:只有声明还没有定义,或者还没有定义完全的类。
如果一个类是不完全类型,因为它还没有完全定义,所以不能直接定义它的对象,也不能以不完全类型作为参数或者返回类型。但是可以定义指向它的指针或引用,因为指针指向一个对象不需要它有定义。
//前向声明,因为还没有定义所以是不完全类型 class Student; //非法,不能把不完全类型作为返回类型 Student getStudent() { //非法,不能定义不完全类型的对象 Student s1; //合法,可以定义指向不完全类型的指针 Student* s2; //非法 return s1; }
但是如果把对象声明为static成员,那么这个对象就可以是不完全类型。
//前向声明,因为还没有定义所以是不完全类型 class Student; class Course { public: //非法,Student只有声明没有定义,不能使用不完全类型 Student s1; //非法,Course还没定义完,所以是不完全类型 Course p1; //合法,指针可以是不完全类型,因为指向一个对象不需要它有定义 Course* p2; //合法,静态成员可以是不完全类型 static Student s2; static Course p3; };
-
总结
最后总结一下静态成员和普通成员的区别
- 静态成员不属于对象,被所有对象共享;
- 静态数据成员需要在类内声明,在类外初始化,类外初始化时不需要重复添加static关键字;如果一定要在类内初始化,必须使用constexpr关键字并且用字面值初始化;
- 静态数据成员可以作为默认形参。
- 静态函数成员需要在类内声明为static,定义可以写在类内,但是建议写在类外。
- 静态函数成员只能访问全局变量、参数和静态数据成员,不能访问类内的非静态数据成员。
- 静态函数成员不持有this指针,不能声明为const(常量成员函数)。
- 静态成员可以是不完全类型。