1.同一类内数据的共享
在面向过程的语言中,我们知道,两个函数之间如果想要“互通有无”发送点数据,其无外乎两种方式,要么是通过一个函数调用另一个函数,调用时传递参数即可;或者是直接大家共享一个全局变量,那么便都可以对此变量进行访问和修改了。C++是一门面对对象的语言,将函数和数据等封装在了类内,其目的是为了解决客观世界的问题而设计。我们知道,类的私有数据成员private只允许其自身的成员函数来访问和修改。因此,如果我实例化类的一个对象,其对象本身是不能直接使用"."访问符访问其对象内的私有数据成员的,更不用说同一类的其他实例化的对象了。
比如,我们有一个Student类,其类内声明了私有数据成员身高height、体重weight等,我们对Student类实例化两个对象ZhangSan、LiSi,那么ZhangSan有张三的数据成员,LiSi有李四的数据成员,他们俩人的数据成员当然是不互通的,肯定是不能共享同一个数据成员的。但如果张三和李四他们两个人之间想要“互通有无”发送点数据呢?比如我们想要统计下学生总数Count,这个数据放在什么地方才可以让同一类的不同对象都可以访问和修改呢?这就是同一类内不同对象之间的数据的共享,也就是我们接下来说的静态数据成员和静态成员函数的应用。
1.1 静态数据成员
同一类内,不同对象之间的数据成员是在每一个对象中拥有一个复本,这个属性正是每个对象区别于其他对象的特征,现在我们要做的是让这个属性是同一类内所有的对象成员都拥有,大家都可以访问和修改。这就需要采用一个关键字:static,在数据成员的类型定义符之前加上static关键字后,那么此数据成员便成为了同一类的所有对象所共同拥有和维护的属性,从而实现了我们的目的:想要在同一类中不同对象之间实现数据共享。
具体用法为:
静态数据成员声明: static 数据类型 变量名;如:static int Count ;
和一般的数据成员不同的是,静态数据成员在类内声明后,还必须要成员函数一样在类外进行定义(或者说实现)。
具体用法为:
静态数据成员实现: 数据类型 类名::变量名 = 初始化值;如:int Student::Count = 0;
这里需要指出的是,如果定义不给出初值,则默认初值为0 。
简单来说其声明和定义的特殊性是:
1.类内声明(前面加static)
2.类外定义实现(前面无static)
3.不允许通过构造函数初始化列表的形式初始化
另外,其还有一些之外的特殊属性:
1.静态数据成员被类的所有对象共享,包括该类的派生类对象,基类对象和派生类对象共享基类的静态数据成员。也就是说,静态数据成员不属于任何对象,类的静态数据成员的存在不依赖于任何类对象的存在,静态数据成员是由类的所有对象共享的。
2.静态数据成员可以作为成员函数的默认形参,而普通数据成员则不可以。如下例:
class Student
{
public:
//静态数据成员
static int Count;
int number;
void showCount(int i = Count);//正确
void showCount(int i = number);//报错
};
3.静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为所属类类型的指针或引用。如下例:
class Student
{
public:
//静态数据成员
static Student count;//正确
Test number;//报错
Test *pStudent;//正确
Test &m_Student;//正确
static Student*pStaticObject;//正确
};
4.后面我们会讲到,常成员函数const是不能修改目标对象数据成员的,但是在static静态数据成员这里就有所特殊,即静态数据成员在const函数中可以修改,而普通数据成员则是不能修改的。
总结完以上其特殊性,我们通过以上方法,便得到了一个类似于在该类内是全局变量的特殊数据成员—静态数据成员。该数据成员在类内实例化的每个对象都可访问和修改,但是如果想访问,我们必须得有个该类实例化后的对象,再通过该对象的某个成员函数访问此数据成员,这就给我们造成了不变,由于Count是整个类共有的,不属于任何对象的,因此我们自然不希望再实例化一个对象后再访问Count,我们能不能直接通过类名来引用类内某一函数来访问此成员呢?故在设计中引入了接下来的静态成员函数的概念。
1.2 静态成员函数
所谓静态成员函数,和前面的静态数据成员类似,是加了static关键字的成员函数。那么,类内的静态成员函数和类内的普通成员函数有什么不一样的呢?
首先其声明形式:
静态成员函数的声明: static 返回值类型 函数名(形参);如:static void showCount();
其实现形式:
静态成员函数的实现: 返回值类型 函数名(形参);如:void showCount();
其声明和实现形式的特殊性还是总结如下:
简单来说其声明和定义的特殊性是:
1.类内声明(前面加static)
2.类内类外都可定义实现(类外前面无static,不同于静态数据成员,其可在类内以内联函数的形式实现)
其属性的特殊性是:
1.静态成员函数不能调用非静态成员函数,但是反过来是可以的。
2.静态成员函数没有this指针,也就是说静态成员函数不能使用修饰符(也就是函数后面的const关键字)。
3.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。
通过以上方法,我们便可直接通过类名::静态成员函数名来直接调用静态成员函数了,不必再进行对象的实例化。如我们可:Student::showCount();基于此语句,我们没对对象实例化,便访问或者输出了静态数据成员count。
至此,依靠静态数据成员和静态成员函数,我们便实现了同一类内不同对象之间信息传递。
2.不同类或对象之间数据的共享
解决了同一类内不同对象之间数据的共享问题,我们接下来要解决的就是类和对象的成员函数之间、不同类与类之间数据的共享问题。不同的类别群体之间共享一些信息在客观世界中也是经常存在的,比如现在有两个类:ZhangSan张三类、LiSi李四类。张三和李四是邻居,张三有几条狗小三红,小三绿,小三蓝(成员函数),李四也有几条狗小四红,小四绿,小四蓝(成员函数),大家各自都有好几盆珍贵的狗粮(私有数据成员),以前呢,张三和李四只能是自家狗吃自家的狗粮(对应各自类内成员函数访问私有数据成员),后来张三和李四成为了好朋友,他们的狗可以互相吃对方的狗粮,这就是两个类之间信息的共享。C++作为一门面向对象的语言,当然也是有相应的操作来抽象和处理这种状况。
2.1 不同"类"和"对象的成员函数"之间数据的共享
不同类和对象的成员函数之间数据的共享是依靠友元函数来实现的,所谓友元函数,其就是在类的定义中用关键词“friend”来修饰声明的一种非(该类的)成员函数。友元函数可以是一种普通的函数,也可以是其他类的成员函数。
还是张三和李四的例子,某天张三出门遇到大蟒蛇,是李四的狗小四蓝和大蟒蛇搏斗救了张三,张三便认定了李四的狗小四蓝是他的好朋友,允许小四蓝可以随便吃他家的狗粮,这就是张三类声明了李四类的成员函数是其友元函数,那么友元函数就可以像其类自身的成员函数一样通过对象名来访问类的私有和保护成员了。
其声明形式如下:
友元函数声明:friend 函数返回值类型 函数名(形参表); 如:friend void dogFourBlue();
要注意的是:
1.友元函数的声明:因友元函数因为不是类内部的函数,因此其声明不受访问限制符的影响。这句话的意思是:友元函数只需要在类内声明即可,而不需要考虑在public内还是private内声明。
2.友元函数的定义(实现):因为不是类内的成员函数,其实现也理所应当的在类外实现,准确点说,其实现与类根本无关。
通过引用友元函数fried,我们便解决了类和其他函数之间信息共享的问题,其friend性质也符合客观实际,很好理解。友元函数的关键所在,即友元函数可以通过对象名直接访问类内的私有数据成员。
2.2 不同"类"之间数据的共享
上面我们解决了不同类和对象的成员函数之间信息的共享,那么不同类和类之间信息的共享如何实现呢?同友元函数一样,我们只需要在类内声明哪个类是我的友元类即可。
其声明形式如下:
友元类声明:friend class 类名; 如:friend class LiSi;
如上所示,某天张三又出门遇到了大蟒蛇,这次是李四救了他,因此他声明:LiSi类为ZhangSan类的友元类friend,那么此时:LiSi类的所有成员函数(所有狗)都是ZhangSan类的友元函数,都可以访问ZhangSan类的私有和保护成员(都可以吃狗粮)。
为类声明了友元类,其便建立了类与类之间的联系,这是实现类之间数据共享的一种途径。
2.3 友元关系的说明
上面我们介绍了两种友元关系:友元函数和友元类。关于友元关系,还有几点需要注意:
1.友元关系是不能传播的,比如李四是张三的朋友,王五是李四的朋友,如果没有声明,不能认定张三和王五是朋友,也就不能进行数据的共享。
2.友元关系是单向的,这个很好理解,张三因为李四救了他,一厢情愿认为李四是他张三的好朋友,可以让李四的所有狗都可以吃他的狗粮。但李四可不一定这么认为,李四如果不认为张三是其好朋友,那么张三的狗就不能吃李四家的狗粮,而只能李四家的狗可以吃张三家的狗粮,即单向关系。
3.友元关系也不能继承,张三认为李四是其朋友,允许李四家的狗吃其狗粮,但不见得认为李四的孩子也是其朋友,没有声明,当然不会允许李四其孩子家的狗也来吃狗粮。
3.共享数据的保护
前面我们讲的是数据的共享在类或对象中的体现,我们知道,共享数据其实是在一定程度上破坏了数据的安全性,本来类封装私有数据成员只能其自身的成员函数访问和修改,现在其他人也可以了,这本身是对数据隐蔽性的一种破坏。那可不可以我允许和你共享,但是不允许你修改呢?一个常见的例子就是去图书馆借书,允许你看,不允许你更改涂抹。在C++中,为了有效的保护数据,因此也引入了类的常对象、类的常成员(包括常成员函数和常数据成员)、常引用这三个概念。
3.1 常对象
常对象顾名思义就是在实例化一个新对象时,加上关键字const,设置其为常对象。
其形式为:
常对象语法形式:const 类型说明符 对象名(形参); 如,const Student A(3,4);
这样,整个对象A就变为了常对象。
总结常对象有以下特性:
1.常对象必须在刚开始实例化时就初始化。因为其数据成员在对象的整个生存期间内都是不允许修改的,故我们必须在刚开始就对其进行初始化,之后就固定不变了无法更新。
2.常对象无法调用普通成员函数。因为调用普通成员函数是可以修改私有数据成员的,这就违背了常对象设计的初衷,因此,直接禁止常对象调用普通成员函数便保证了数据成员不被更改。事实上,常对象只能调用常成员函数,如下面所述。
3.2 常成员函数
常成员函数并非有多么特殊,顾名思义,其首先是类的成员函数,其次才是类的常成员函数,因此其和普通的成员函数一样:即可在类内声明,类外定义实现;也可在类内直接定义实现。不同的是,其声明和实现都需要在后面加上一个关键字const。
声明形式:
常成员函数声明形式:返回值类型说明符 函数名(形参表)const;如:void showCount() const;
我们前面说了,常对象不允许调用普通成员函数,因为普通成员函数可以修改数据成员,而无法实现我们想要的对数据进行保护,但常对象可以访问常成员函数,怎么保证常成员函数不修改数据成员呢?
因此,我们直接规定了:
常成员函数可以访问但不能更新目的对象数据成员,从而保证了数据成员不被修改。
但有一个例外需要记住:类内的static静态数据成员,允许常成员函数更改。
3.2 常数据成员
有了常对象、常成员函数,当然也要有常数据成员。常数据成员就是用const关键字修饰的类的数据成员变量,仅此而已,并没有什么特殊。
我们知道对于普通的变量如int,double我们也可以加上const使之变为符号常量,在定义时候就必须进行初始化,其后便不允许更改了,常数据成员也是这样,只不过其初始化方式是通过构造函数的初始化列表罢了。
其声明形式:
常数据成员声明:const 类型说明符 变量名;如:const int A;
其初始化方式:
常数据成员初始化:通过构造函数初始化列表初始化赋值。如:Student::Student(int number):A(number){}
#对比静态数据成员和常数据成员:
在此文里,针对数据成员,我们讲了两种方式:
1:加static关键字变为静态数据成员,其必须类内声明,必须类外定义,且不允许通过初始化列表进行初始化。
2:加const关键字变为常数据成员,其是在类内声明,无需额外定义,且必须通过初始化列表的方式进行初始化。
static静态数据成员相当于是类内的一个全局变量,使得同一类的不同对象之间可以共享同一个数据成员,相当于是提升了其“作用域”。对比发现,const限定的只是限定常数据成员其运行期间不允许被修改,而对其“作用域”没任何作用。事实上,两者根本不冲突也不是一回事,一个相当于属性限定类内大家都可以访问了,一个是属性限定大家都不可以修改,因此常常组合使用,如下所示:
静态 常 数据成员声明:static const int B; (类内声明,加static关键字)
静态 常 数据成员实现:const int B = 10; (类外定义,无需static关键字)
静态常数据成员的意义:允许类内不同对象都可以访问,但一旦初始化后便不再允许修改。