今天我们要说说静态成员变量和静态成员函数。 那里面的类 它的成员有静态成员,所以说,什么叫静态成员呢? 就是我们在定义成员的时候前面加了static关键字, 那这个成员就成为静态成员。 怎样说,来我们看这个CRectangle这个类, w代表这个宽度,h代表高度,然后有一个TotalArea。 我们看TotalArea前面加了一个static关键字,那这个TotalArea就成为静态成员变量。 那这个TotalNumber也一样。 这个TotalArea呢是用来记录所有矩形对象的总面积的。 TotalNumber呢是来记录所有矩形对象的总数的。 那么静态成员可以有两种:一种是静态成员变量,另外一种就是静态成员函数。 像这个PrintTotal前面呢加了static关键字,于是PrintTotal就成了这个静态成员函数。 那这个普通成员变量 和静态成员变量到底有什么差别呢?除了形式上差一个static以外呢, 其他它们的差别就是普通的成员变量 每一个对象有各自的一份。 而静态的成员变量一共就一份,被所有的对象所共享。 就是说这个像这个w,h的这样的普通成员变量每个CRectangle对象都有各自的一份。 你打的对象a,e的w那对象a,r的w不受影响,对吧? 而这一种静态的成员变量TotalArea和TotalNumber呢是被所有的CRectangle它们对象所共享的。 你改了a,e的nTotalArea,那a,r的 这个nTotalArea也跟着被修改了。这些静态成员变量是对所有对象所共享的。 这个是。 另外还有一个差别呢就是sizeof运算符,它不会计算 静态成员变量。sizeof我们知道可以用来计算一个对象和在用的自点数,对吧。 那我们这里有一个CMyclass,那它有一个普通的成员变量n,还有一个静态的成为变量s。 那sizeof CMyclass到底是多少呢?不会把这个s算进去, 所以sizeof CMyclass等于四。 为什么,因为这个静态成员变量它实上不是放在一个对象内部了。 它是放在所有的对象外面,被所有的对象所共享了。 静态成员变量一共就那么一份。 那刚刚说了静态成员变量和普通成员变量的差别。 那静态成员函数和普通成员函数的差别 又在哪儿呢?就是这个普通成员函数呢,它必须具体作用于某一个对象。 我们前面看到怎么调用函数,得通过对象名、 点儿、程序函数名,或者是指针、箭号、程序函数名对吧, 或者是引用、点儿、程序函数名。总之,这些程序函数它都一定是作用在某一个对象上面的。 那对于静态的程序函数呢,它并不具体作用于某一个对象。 调用普通程序函数你需要交代普通程序函数作用在哪个对象上面。 调用静态程序函数你就不要交代这一点了,你也没法交代,因为它并不具体作用于某个对象。 那么我们 说了这个静态的成员变量,静态的成员不管是成员变量也好还是静态 成员函数也好,它们都不是属于某一个对象的,也不是具体作于某一个对象的, 所以这个静态成员它不需要通过对象就能访问。 不管是静态成员变量还是静态成员函数,都是不需要通过对象就能访问了。 我们看具体的例子。怎么访问静态成员呢? 有各种方法,第一种方法是通过类名成员名。 比如说CRectangle PrintTotal,这样就直接调用PrintTotal这个 成员函数。你说它具体作用哪一个对象呢?说不出来吧。它不具体作用于哪个对象, 所以我们通过类名成员名的办法可以直接访问 类的静态成员。这里类名成员是访问一个类的静态成员函数 是要你把这个PrintTotal换成一个静态成员变量的名字, 也是没有问题的。 那第二种访问静态成员的方法就跟传统的方法是一样的,可以通过 对象名成员名的办法。那这个CRectangle r.PrintTotal。 那要注意了,虽然写的是r.PrintTotal,但PrintTotal并不是作用在r这个对象上面呢。 这只是一种形式而已,它并不以为着PrintTotal作用在r这个对象上面。 静态成员函数和静态成员变量它都不具体属于某一个对象,也不是具体作用于某一个 对象的。当然我们也可以通过指针建好成员名的办法来访问静态的成员函数或者是静态的成员变量。 虽然形是如此,实上这个PrintTotal也并不是作用在p所指向的那一个对象上了。 然后我们还可以通过引用点成员名的方法来访问 静态成员变量,比如说这个nTotalNumber在CRectangle里面是静态成员变量对吧, 我们可以 如此来访问它。但这个时候呢,我们 能说nTotalNumber它就是属于ref所引用的那一个对象的吗? 它实上并不是指属于,ref所引用的对象r它是被所有的 CRectangle对象所共享呢,所以你也不好说它是特定地属于这个。 r的。那 最重要的一点,大家需要搞清楚的就是, 静态成员变量本质上它是全局变量, 哪怕一个对象都不存在,一个类的静态成员变量也存在。 因为它是全局变量。那普通的成员变量, 它必须得属于某一个对象,有了对象以后这个对象的成员,它是一个普通的成员变量。 然后这个静态成员函数它本质上呢就是一个全局的函数。 那这个全局的函数它不需要作用在某一个对象。 普通的成员函数它必须具体地作用在某一个对象上。 那竟然这个 静态成员变量是全局变量,然后静态函数又是全局函数,那我们还为什么要引入 这样一种东西呢?我们直接就写全局变量,直接就写全局函数不就行了吗? 那我们设置静态成员的这种机制 它的目的呢实上是把和某些紧密相关的全局变量 和全局函数写到类里面,这样看上去呢就成为一个整体,易于理解和维护。 我们去看一个具体的例子。 就是我们考录一个需要随时知道矩形总数和总面积的 图形处理程序。 那当然我们可以用全局变量来记录总数和总面积, 对吧?我们肯定不需要在每一个矩形对象里面来记录一个总面积, 也不需要在每一个矩形对象里面都记录一个总数。那我们用全局变量 来记录总数和总面积。 那这样做的话呢实上是有一个不好的地方,就是你没法 很直观地看出这两个的全局变量跟那个矩形这个类之间的这个关系。 而且实上这两个全局变量它只跟 矩形类有关系,那你把它写成一个 全局变量的话呢,那看来就其他的类的全局函数 也能够来访问它。这就不太好了,对吧,因为它是让直跟矩形类有关系。 那竟然这两个变成这样,直跟具矩形类有关系,那我们把它作为静态成员写到这个矩形类里面, 不就很充分地体现了它跟矩形类的关系,对吧。而且当我们把这两个全局变量作为静态成员写到矩形类里面以后, 那别的类就没有办法来访问这两个变量。 这样的话我们就很容易理解和维护这两个全局变量。 那具体地说刚才已经提到了,我们写一个CRectangle的这个类, 那这个每一个CRectangle呢有 w和h这两个成员变量代表矩形的 宽和高,这个是普通的成员变量。每一个矩形变量有各自的一份,每一个矩形对象都代表一个矩形。 然后呢,我们有两个静态成员变量,用来记录所有矩形对象的总面积 和所有矩形对象的这个总数,这就是矩形对象的总数。 那这两个变量我们当然可以用全局变量来完成,对吧,但是由于你写成员变量全局变量的话就没办法 体现它跟CRectangle类的紧密关系。 它可能被其他类所访问,就不容易维护,所以我们把它写到这个 CRectangle类里面成为 静态的成员变量,这就赢不了,这两个东西是指跟CRectangle这个 类有关的。那好了,我们还会需要一些 函数能够把这两个 直给打出来。那能打出这两个直的函数我们名义为 PrintTotal。那PrintTotal当然如果你nTotalArea跟nTotalNumber 都是全局变量的话,那我们PrintTotal也就写成全局函数就好了,对吧。但现在呢,这两个 变量都是静态成员变量,那相应地我们也可以把PrintTotal写成静态的这个 成员函数。那也赢不了这个PrintTotal 只跟CRectangle有关,跟其他东西是没有关的。 那现在我们来看看我们 如何用这个TotalNumber、TotalArea来进入这个 在运行的时候,我们要随时用这两个变量来进入 当前的矩形对象的总数和矩形对象的总面积,怎么做到这一点呢? 啊,一个很直接的想法就是我们在 矩形的CRectangle类的构造函数里面要增加TotalNumber, 增加TotalArea对吧?这个很明显,因为只要有矩形对象生成 就一定会引发构造函数的调用,那我们在 构造函数里面就可以增加矩形的总数和矩形的总面积,对吧。 那当然,那矩形对象有可能消亡,对吧?比如说 一个局部的矩形对象变量,在出了这个包含它的函数以后就消亡了,那这时候矩形的总数就减少了,总面积也减少了, 所以我们在CRegtangle类的析构函数里面呢 要减少TotalNumber,减少TotalArea,这是很直观的对吧, 然后呢,我们在PrintTotal里面可以打印出 TotalNumber和TotalArea,啊,在这个PrintTotal里面,访问这两个静态成员变量把它给打出来, 但是要注意一点,就是 在C++里面啊,静态成员变量你必须拿到 外面,就是所有的函数外面来单独的给它声明一下,像这样啊, 这两个CRegtangle的静态成员变量,我们要单独拿出来生声明一下,像这种形式, 啊,类型,类名,然后跟这个变量名。 那声明的同时呢,你可以对它进行初始化,也可以不对它进行初始化。 但具体到这个例子,我们需要用这两个变量来计入矩形的总数和总面积,对吧, 那我们当然要给它初始化成0,啊一开始没有矩形,那总面积和总数都是0, 然后只要有矩形对象生成,构造函数就会增加TotalNumber和TotalArea, 然后只要有矩形对象消亡,析构函数就会减少TotalNumber,减少TotalArea, 哎,看上去很美,是吧, 呃,下面我们就来看看这些静态成员函数 或者静态成员变量是怎么起作用的啊,在main里面定义了两个矩形对象, 然后我们试图输出TotalNumber, 那么呢这条语句这样写的话就会编译过不了,为什么呢? 因为这个跟TotalNumber它是私有的,啊, 虽然这个表达式没问题,但你在这里在main里面 试图访问CRectangle类的私有成员,啊,这个时候当然就会出错, 哪怕它是静态成员,也是不行的。在接下来的这个 CRectangel啊,PrintTotal这样就打出了 这个此时的总面积和总数。 那这个句型总数是多少呢?一共生成了两个矩形对象,所以总数当然就是2,总面积是多少呢? 这个r1的面积是9嘛,r2的面积是4,所以总数和总面积就都是分布是2 和这个13,当然这里输出了两行2和13是因为下面还有一个r1.PrintTotal, r1.PrintTotal跟上面这个CRectangel::PrintTotal它完全是等价的,啊, 虽然这么写,但并不意味着PrintTotal作用在r1上面, 喝点水 接着, 那我们一定要注意一点啊, 就是在静态成员函数中,不能够访问非静态成员函数, 呃,呃,不能访问非静态成员变量,啊,要注意,那我们一定要注意一点啊,就是在非静态成员函数中, 那我们一定要注意一点啊,就是在非静态成员函数中,不能访问非静态成员变量, 也不能调用非静态成员函数。啊。比方说我们下面这个PrintTotal, 如果我想把w给它数出来,这行不行呢? 唉这个是不行的,因为这是一个静态成员函数,在这里面你试图访问非静态的成员变量 是不可以的。那为什么不可以呢?那就是因为这个静态成员函数啊,它不是 作用在某一个对象上面的。那如果你执行到这的时候要访问这个w, 这PrintTotal又不是具体作用在某一个对象上面的,那你这里来一个w, 这个w是属于谁的啊,解释不通,对吧。很明显,我可以用这样一个办法来调用PrintTotal, 那这样的办法来调用PrintTotal,进到这个PrintTotal里面,你说这个w是属于哪个对象的啊? 说不清楚,所以这样做是不允许的。 那为什么静态成员函数里面也不能调用非静态成员函数呢? 哎,那这个结果也挺明显,啊,那因为你在这个 你在一个非静态的成员函数里面是有可能访问到一个非静态的成员变量,对吧, 啊,所以同理,如果静态成员函数调用了 非静态成员函数,然后那个非静态成员函数又访问到了 非静态的成员变量,那最后就会发生, 被访问的那个成员变量是属于哪个对象的呢?说不清楚。 所以这也是不行的。 那下面我们再回到刚才这个 CRectangle这个类里面,啊,这个类呢我们说了, 要用TotalNumber计入当前系统里面矩形的总数,用TotalArea计入 当前所有矩形的总面积,然后我们在构造函数里面增加这些值,在析构函数里面减少这些值,看上去很对,对吧? 实际上这个CRectangle类的写法,它是有严重的缺陷的。 它会导致在某一种时刻,哎,这个TotalNumber和TotalArea的值是不正确的。 那这个缺陷到底是什么产生的呢? 我们看,就说我们这个时候 忽略掉了复制构造函数。啊。我们前面写了这个,在这个 构造函数里面,对TotalNumber和TotalArea进行增加,这是没问题的对吧, 但问题是,是否搜有 CRectangle对象都会用这个构造函数来初始化呢? 啊,这个问题就来了,我们知道不是的。 有一些CRectangle的对象它可能不会用这个构造函数来初始化。 那用哪个构造函数来初始化啊?啊,那我们看编译器会缺省,会自动生成什么样的构造函数啊? 现在我们自己写了这个构造函数了,编译器就不会自动生成无参构造函数了, 但是呢,编译器它会自动生成复制构造函数。因为我们没有写复制构造函数对吧? 那编译器自动生成的那个复制构造函数,它会去修改这两个变量的值吗? 它当然不会,对吧?而,而我们知道, 我们在使用这个CRectangle类的过程中,就有可能有一些CRectangle的对象 它是用复制构造函数来初始化的,这种情况时有可能发生的,对吧? 什么情况下啊? 比方说如果我们调用了一个以CRectangle对象作为 形参的函数的时候,啊这个形参对象就是由复制构造函数初始化的。 或者我们调用了一个以CRectangle对象作为 返回值的函数的时候,那这个返回值对象 它也是用复制构造函数初始化的。那这样的对象它们生成的时候 并没有走这里,对吧?也就是并没有增加TotalNumber和TotalArea, 那这样的对象呢,它们在消亡的时候,却一定要 走析构函数,就会减掉TotalNumber和TotalArea, 所以这个时候问题就产生了,会产生什么样的问题呢?啊就是 如果有这样用复制构造对象,啊,复制构造函数初始化对象生成的话,就会发生 总数和这个总面积比实际的情况 要多还是少啊,大家说? 当然是变少的情况,对吧,因为这样这些对象在生成的时候 并没有增加总数,可他们消亡的时候却减掉了总数,那当然总数就会变少,比实际的情况要少。 这就出问题了。啊,就像这种还有些临时对象啊,它在生成的时候,消亡的时候也会调用析构函数, 什么是临时对象啊?呃,这个,如果一个函数的返回值是一个 对象的话,那这个函数的返回值就是一个临时对象。 那怎么来解决这个问题呢? 啊,解决的办法我们就是用 为复制,呃,为这个CRectangle类编写一个复制构造函数, 啊,怎么解决这个问题呢?那当然问题是出在复制构造函数上面, 那我们就要用复制构造函数来解决。啊。 如果我们为CRectangle类编写一个复制构造函数,这个问题就迎刃而解啦。 我们自己写一个复制构造函数,在这个复制构造函数里面,除了我们做复制的工作以外,还要对这个 两个总数进行修改,啊,那么这个时候就不会发生 对象生成没有,没有增加总数,对象消亡却减掉了总数这个怪异的现象, 那当然就能确保TotalNumber和TotalArea总是正确的了。