1.静态成员函数和静态成员变量的引入
(1)我们通过以下面的这个例子逐步引出静态的成员变量和成员函数:
我们自己定义一个类,使用这个类创建对象,我们应该如何判断在这个程序执行的过程中,创建了多少个对象,有几个对象是正在使用的。
(2)上面的这个代码就可以进行判断正在使用的对象的个数和创建的对象的个数,我们是定义了一个n用来记录创建的对象的个数,我们定义了一个m用来记录正在使用的对象个数;
(3)我们在这个程序里面是写了4个函数,3个是默认的函数,1个就是我们自己定义的func函数,当执行默认构造函数和拷贝函数的时候,就让nm都加加,当执行析构函数的时候,就让m--,说明已经有一个对象被销毁了;最后再打印输出nm的数值;
(4)在这个过程中,我们有3组打印输出的结果,我们知道 m打印的结果一直都是2,说明正在使用的对象就是2个,n打印的结果在变化,说明一直有对象被创建;
(5)第一次的打印结果两个2很容易理解, 创建2个对象,执行默认构造函数,正在使用的对象个数是2,所以打印结果是两个2;
(6)A()这个我们之前提到过,这个就是一个匿名的对象,我们只需要记住匿名对象在进行创建的时候,在外观上面和默认的构造函数长的是一样的,所以这个时候创建的对象的个数除了前面的a1,a2还有我们现在的匿名对象A(),就是创建了3个对象,但是匿名对象的生命周期就在创建的那一行,所以在执行cout的时候,这个匿名对象的生命周期已经结束了,并不是正在使用的,所以m的值还是2;
(7)当我们调用func函数的时候,打印的结果显示n=5说明这个时候创建了5个对象,为什么会比第二次打印时候的3又多了2个呢?因为我们调用了func这个函数,a1这个实参传递给aa这个形参执行拷贝构造函数,这个时候需要创建对象,而且返回的时候,因为是传值返回,我们需要创建一个对象作为临时变量,所以又要创建一个对象,综上所述,一共是创建了5个对象;
(8)如果在func函数里面,我们是传引用返回,这个时候就不会生成这个临时的对象,这个时候的n就是4;如果我们使用传引用返回而且形参也是引用的,这个时候就不会执行拷贝构造函数,这个时候n=3;当然因为这个地方的aa这个返回值是出了作用域就会销毁的,并不符合传引用返回的条件,我们在这里只是说明问题,传引用返回实际上是不规范的;
这个过程我们好像并没有使用到静态的成员变量和成员函数,下面我们们将在这个题目的基础上面引入
2.为什么会存在静态的成员变量和成员函数
(1)上面的写法是有缺陷的,什么缺陷呢?
我们首先要理解的就是C++的一个很重要的特点就是封装性,但是这个里面我们定义的n和m都是全局的变量,很容易被修改破坏,因此我们可以把这两个变量放到类的里面去;
(2)放到类的里面之后,我们就要知道这个nm都是对象的,就是我们创建的任何一个对象都有n和m,现在我们想要这个n和m属于这个类,就要在前面加上static;
这个时候,我们在nm这两个成员变量的前面加上static之后,这两个成员变量就是静态成员变量,属于整个类了,而不是某一个单独的对象,这个时候我们会发现在等号的位置有报错;
这个就说明静态成员变量就不可以给缺省值了,为什么会这样?
因为我们在这个地方给的缺省值是传递给了初始化列表的,初始化列表是对对象进行初始化的,但是我们这里的静态成员变量是属于这个类的,不会走初始化列表的,所以就不应该给缺省值;
(3) 我们在类里面声明静态的成员变量,我们还是要进行定义的,我们可以在外面定义进行初始化的操作;因为这个时候静态的成员变量属于整个类域,所以我们在类外面定义的时候加上访问操作符;
(4)声明和定义完成之后,我们就可以进行打印输出成员变量的值,例如这里的A::m,a1.m都是可以访问到这些公有的成员变量的;但是我们不能直接打印nm这种,因为现在这个n和m已经在类域里面了,默认情况下我们找的时候只会在全局找,不会跑到类域里面去找的;
这个就相当于是有一个围墙,我们在打印的时候,就要看在全局里面是否存在这个变量,A::这种方式相当于是突破了围墙的限制,允许编译器到类域的里面去找,a1.这个也是可以突破围墙的,这个直接打印就不行;
(5) 我们上面还有一个实例,就是定义了一个A类的指针并且初始化为nullptr,我们使用ptr->n这样的方式也是可以突破围墙的限制,访问到静态的成员变量,并不会报错;
(6)上面的所有讨论都是建立在我们的静态成员变量都是public的,如果他们是private的,就都会报错,我们要想访问private这个里面的静态的成员变量,就需要使用和静态的成员变量相互对应的静态的成员函数;
(7)在private情况下面,我们可以在类里面定义getm这样的函数让函数的返回值是我们想要的n和m,但是如果我们是定义的匿名对象呢?
我们也可以使用A().print()的方式进行匿名对象函数的调用,但是这个会影响我们的判断,因为这个匿名对象调用print函数会创建一个新的对象,但是这个对象的创建是没有必要的;
(8)这个时候我们就可以在函数的前面加上static这样的话函数就成为了静态的成员函数;
静态的成员函数的特点是没有this指针,我们之前的那些普通函数有this指针(就是我们使用A这个类创建一个对象a1,我们使用a1.print()就可以调用这个函数,这个函数里面是有this指针接受这个传递过来的对象的);
现在的静态的成员函数,我们可以直接使用A::print()进行匿名对象的函数的调用,但是匿名函数里面不可以调用非静态的变量,因为非静态的变量的调用需要这个函数有this指针,但是静态的成员函数没有this指针。
(9)静态成员函数的限制就是不能访问非静态的成员变量,为什么非静态的需要this指针呢?
因为我们知道静态的成员变量是在类里面的,属于类域,但是非静态的就是属于某个对象的,我们要想使用这个非静态的变量,我们就要适用对象进行使用,但是有对象就有this指针 ,相当于我们的非静态的变量需要this但是静态成员函数没this所以不可在静态函数里面使用非静态的变量。