详解静态成员 什么是静态成员,静态成员和普通成员的区别

什么是静态成员

首先了解一下为什么产生了静态成员的需求,有时候需要一些成员直接和类本身相关,而不是和所有对象保持关联。举一个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;
    };
    

总结

最后总结一下静态成员和普通成员的区别

  1. 静态成员不属于对象,被所有对象共享;
  2. 静态数据成员需要在类内声明,在类外初始化,类外初始化时不需要重复添加static关键字;如果一定要在类内初始化,必须使用constexpr关键字并且用字面值初始化;
  3. 静态数据成员可以作为默认形参。
  4. 静态函数成员需要在类内声明为static,定义可以写在类内,但是建议写在类外。
  5. 静态函数成员只能访问全局变量、参数和静态数据成员,不能访问类内的非静态数据成员。
  6. 静态函数成员不持有this指针,不能声明为const(常量成员函数)。
  7. 静态成员可以是不完全类型。

你可能感兴趣的:(C++,1024程序员节,c++,数据结构,static,静态成员)