C++类中的常成员和静态成员
常变量、常对象、常引用、指向常对象或常变量的指针等在定义时都使用了const关键字,这是C++语言引入的一种数据保护机制,称为const数据保护机制。例如通过const关键字主动地将被调函数形参进行限定,限定被调函数不能修改主调函数传递过来的数据。
下面通过一个出租车类(Taxi),更好的理解常成员和常函数成员:
class Taxi //定义出租车类 { private: int price; //出租车里程单价 int fare; //出租车收费总额 public: void SetPrice(int p) { price = p; } //设置里程单价 int GetPrice() { return price; } //获取里程单价 void SetFare(int f) { fare = f; } //设置收费总额 int GetFare() { return fare; } //获取收费总额 void AddFare(int f) { fare += f; } //累计收费总额 void Order() //执行一个打车订单 { int x = GetARand(5,100); //通过随机数获得一次订单里程 AddFare(x*price); //计算一次订单金额,加到收费总额里 } Taxi(int p =0,int f=0) //带默认形参值的构造函数 { price = p; fare = f; } }
一、常成员
在定义类时,使用const关键字进行限定的成员称为常成员。数据成员和函数成员均可定义为常成员。
1、常数据成员
如果一个数据成员所保存的数值在初始化以后不会改变,那么可以将这个数据成员定义成常数据成员。换句话说,常数据成员只能在对象初始化时赋值,初始化后不得再修改。常数据成员和常变量的不同在于常数据成员在类中声明时不得初始化,类中任何数据成员的初始化必须经过构造函数完成。
常数据成员的语法细则:
1)常数据成员定义:const 数据类型 常数据成员名;
2)初始化列表:为构造函数添加初始化列表是对常数据成员进行初始化的唯一途径。语法形式如下:
构造函数名(形参列表):常数据成员名1(形参1),常数据成员名2(形参2),...
{
//函数体中对其他数据成员初始化
}
以出租车里程单价price为常数据成员为例:
const int price;
Taxi(int p=0;int f=0):price(p)
{
fare=f;
}
在构造函数头后面添加初始化列表“:price(p)”是唯一能够设置常数据成员price初始值的地方,其他任何地方都不得对price再次赋值。
3) 定义对象时初始化。定义含常数据成员类的对象时需要初始化,重点给出常数据成员的初始值。
程序员在设计类的时候,如果认为某个数据成员所保存的数值在初始化以后不能在被修改,可以主动将其定义为常数据成员。
2、常函数成员
如果某个函数成员只需要读取类中的数据成员(注意:不是常数据成员)的数值,而不会修改它们,那么可以将其定义为常函数成员。
常函数成员定义语法形式:
1)内联函数。在类声明部分直接定义的函数被当作内联函数处理,在函数头后面加const关键字就可以将其定义为常函数成员。
示例:int GetPrice() const { return price; }
2) 非内联函数。此时需要在声明和定义语句的函数头后面分别加上const关键字。
示例:
类声明中:int GetPrice() const;
类实现中:int Taxi::GetPrice() const { return price; }
常函数成员语法细则:
1)声明、定义常函数成员须在函数头后面加关键字const进行限定。
2)常函数成员只能读取类中数据成员的数值,不能修改它们。
3)常函数成员只能调用其他常函数成员,以防止常函数通过其他函数间接修改数据成员。
4)通过常对像只能调用其常函数成员,以防止函数间接修改常对象的数据成员。
5)除了形参的个数和类型之外,还可以用关键字const区分类中的重载函数。
3、关于const重载类成员函数的问题
1)如何调用重载后的两个函数?
const修饰的对象调用的是使用const修饰的方法,非const对象调用的是非const的方法。
2)重载是如何实现的?
经查资料,这些函数的参数中其实还有一个隐式的this指针,在调用的时候传入。因为在非const修饰的函数中,this指针也是非const的。在const修饰的函数中,this指针是const修饰的。所以非const对象调用函数时,this指针是非const的,调用非const函数。const对象调用函数时,this指针是const的,调用的是const的函数。
注意:如果一个函数用const修饰了,但是这个函数没有实现重载,那么非const对象和const对象都能调用这个函数。
特别注意的是,const修饰的对象只能调用const修饰的函数。
二、静态成员
面向对象的程序设计希望用类管理所有程序代码,使得程序中没有游离在类外的全局变量和外部函数。对于一些共用的全局变量或外部函数,可以将它们规划到某个具有关联关系的类中,与类中的其他成员一起进行统一管理。定义类时,使用关键字static进行限定的成员称为静态成员。数据成员和函数成员都可以定义为静态成员。与静态全局变量、静态局部变量和静态函数一样静态成员的定义与作用域密不可分。
1、静态数据成员
静态数据成员属于一个类整体,不属于某个对象。作用上相当于全局变量,使用起来类似静态局部变量(静态局部变量作用域是某个函数,生存期是程序全局)。
静态数据成员的语法形式:
类声明:static 数据类型 静态数据成员名;//注意这里只是对静态数据成员的声明,未继进行定义。
初始化:数据类型 类名::静态数据成员名 = 某值;//注意,静态数据成员的定义只能在类实现中,即类实现部分定义静态数据成员。定义时可以初始化。(那么在类定义时初始化的静态数据成员如何分配的内存空间哪?又如何将该初始化值传递给调用该类的其他程序哪?)
示例:
类声明:static int totalFare;
初始化:int Taxi::totalFare = 0;
静态数据成员可以是私有的(只能被该类的成员函数访问),也可以是公有的(可以被类外其他函数访问修改)。
静态数据成员的语法细则:
1)关键字static。在类中声明静态数据成员需使用关键字static进行限定,声明时不能初始化。
2)定义和初始化。必须在类声明的大括号后面对静态数据成员进行定义,定义时不能加static关键字。定义时可以初始化。
3)在类的函数成员中访问。类中的函数成员直接使用成员名访问静态数据成员,访问时不受权限约束。这一点和普通数据成员一样。
4)在类外其他函数中访问。在类外其他函数中访问静态数据成员需以“类名::静态数据成员名”的形式访问,或通过任何一个该类对象以"对象名.静态数据成员"的形式访问,或通过任何一个该类对象指针以“对象指针名->静态数据成员名”的形式访问。
私有静态数据成员具有类作用域,只能在类内访问。公有静态数据成员具有文件作用域,可以被本文件的任何函数访问,并且可通过类声明将其作用域扩展到任何程序文件。
5)内存分配。和全局变量一样,静态数据成员也是静态分配,被分配在静态存储区。静态数据成员在程序加载后立即分配内存,直到程序执行结束退出时才被释放。这一点与类中的其他普通数据成员是完全不同的。类定义多个对象,每个对象的普通数据成员都各自分配内存单元,但所有对象的静态数据成员会共用一个内存单元。
2、静态函数成员
可以将外部函数划归到某个具体关联关系的类中,作为类的静态函数成员进行管理。
静态函数成员的语法形式:
类声明:static 返回值类型 静态函数成员名(形参列表);
类实现:返回值类型::静态函数成员名(形参列表) { 函数体 }
示例:
类声明:static void AddTotal(int f);
类实现: void Taxi::AddToal(int f) {totalFare += f; }
静态函数成员的语法细则:
1)声明时使用关键字static进行限定,定义时不能在使用关键字static。
2)类中的其他函数成员可以调用静态函数成员。调用时直接使用函数名,不受访问控制权限约束。
3)在类外调用静态函数成员需以“类名::静态函数成员名”的形式访问,或通过任何一个该类对象以"对象名.静态函数成员"的形式访问,或通过任何一个该类对象指针以“对象指针名->静态函数成员名”的形式访问。
类外调用静态函数成员受访权限约束,私有静态函数成员具有类作用域,只能在类内调用。公有静态函数成员具有文件作用域,可以被本文件的任何函数调用,并且可通过类声明将其作用域扩展到任何程序文件。
4)静态函数成员只能访问类中的静态数据成员,也就是不能访问类中的非静态数据成员。因为静态函数成员可以在没有对象定义的情况下被调用,此时非静态数据成员还没有分配内存空间,不能访问。同样,静态函数成员只能调用类中的静态函数成员,不能调用非静态函数成员。
5)静态函数成员不能是内联函数,因为编译器在编译时会调整内联函数,可能会用到静态数据成员中的数据,但此时静态数据成员还未初始化,所访问的数据是错误的。
3、静态成员的应用
1)以类的形式管理全局变量和外部函数
本质上静态数据成员就是一个全局变量,静态函数成员就是一个外部函数。将它们改为静态成员好处便于分类组织和管理程序代码。
2)将具有相同属性值的成员定义成静态数据成员
利用静态数据成员公用内存单元的特点,可以将具有想用属性的成员定义成静态数据成员,这样就有效减少内存占用。
例如:每个出租车对象都有price里程单价这一数据成员,现实中相同的出租车里程单价一样,这样为每个出租车都设置一个price就显得有些内存浪费,设置一个静态数据成员price表示全部出租车类的里程单价,就会节省很多内存资源。