特殊的数据成员——初始化
4类比较特殊的数据成员 常量成员、引用成员、类对象成员和静态成员,其初始化与普通数据成员有所不同。
常量数据成员 ( const ) 和引用数据成员( & )
const数据成员具有“只读属性”,经初始化后,在程序中无法修改其值。
因此必须在初始化列表中初始化常量数据成员,不能使用赋值表达式 (=)。
和常量成员相同,引用成员也必须在初始化列表中进行初始化。
class Point{
public:
//错误写法
Point(int ix = 0, int iy = 0)
{
_ix = ix;//error, const成员不能使用赋值语句修改/初始化
_iy = iy;//error
_iz = _ix;
}
//正确写法
Point(int ix = 0, int iy = 0){
: _ix(ix), _iy(iy), _iz(_ix) //初始化列表中initialize
}
private:
const int _ix;
const int _iy;
int & _iz; //
};
可以用
sizeof(Point)
测出类又多长,可以看出占据类空间的只有数据成员,没有成员函数。
一个空类占1个字节
(1 bit)
,sizeof(Empty) = 1;
类对象数据成员
当类中的数据成员本身也是类对象时,对象数据成员的创建及初始化必须放在类中构造函数的初始化列表中。
默认情况下,类对象数据成员会在初始化列表中进行初始化,此时只会调用子对象的默认构造函数,如果默认构造函数没有,会报错,所以为了符合要求,最好在初始化列表中进行显示初始化。
如一个直线类Line对象中包含两个Point类对象,对Point对象的创建就必须要放在Line的构造函数的初始化列表中进行。
//Line线类
class Line{
public:
//构造函数
Line(int x1, int y1, int x2, int y2)
: _pt1(x1, y1), _pt2(x2, y2) //类对象成员要在初始化列表中initialize
{
cout << "Line(int,int,int,int)" << endl;
}
//打印函数
void printLine(){
_pt1.print();
cout << "---->";
_pt2.print();
cout << endl;
}
private:
Point _pt1;
Point _pt2;
};
//当Line的构造函数没有在其初始化列表中初始化对象_pt1和_pt2时,
//系统也会自动调用Point类的默认构造函数,此时就会与预期的构造不一致。
//因此需要显式在Line的构造函数初始化列表中初始化_pt1和_pt2对象。
//Point点类
class Point{
public:
//构造函数
Point(int x = 0, int y = 0):_ix(x),_iy(y){
cout << "Point(int = 0, int = 0)" << endl;
}
//打印函数
void print(){
cout << "(" << this -> _ix
<< "," << this -> _iy
<< ")" << endl;
}
//数据成员
private:
int _ix;
int _iy;
}
//主函数
int main{
Line line(1, 2, 3, 4);
cout << "line = " ;
line.printLine();
return 0;
}
静态数据成员 ( static )
静态数据成员不能在初始化列表中进行初始化,要放在全局静态的位置进行初始化。
- 静态数据成员和静态变量一样,是在编译时就被创建并初始化的(,当程序执行时,该成员已经存在,一直到程序结束。
- 静态数据成员存储在全局/静态区,任何该类对象都可对其进行访问,不占据对象的存储空间。
- 静态数据成员的实例只有一个,被所有该类的对象共享。因此静态数据成员不占用类的大小(即类对象的空间,因为静态数据成员数被类创建的所有对象共享的)。
- 对于头文件与实现文件分开书写的形式,静态数据成员的初始化要放在实现文件中进行,否则会出现重定义问题。
//举例代码:
class Computer
{
public:
//构造函数
Computer(const char *brand, double price)
: _brand(new char[strlen(brand) + 1]()), _price(price)
{
_totalPrice += _price;
}
//打印函数
void print()
{
cout << "品牌:" << _brand << endl
<< "价格:" << _price << endl
<< "总价:" << _totalPrice << endl;
}
//...
private:
char * _brand;
double _price;
//定义静态变量_totalPrice获取总价
static double _totalPrice;
};
因为static数据成员不属于类的任何一个对象,所以它们并不是在创建类对象时被定义的。
即它们不是由类的的构造函数初始化的。
一般来说,不能在类内部初始化静态数据成员,必须在类的外部定义和初始化静态数据成员,且不再包含static关键字,格式如下:
数据类型 类名::变量名 = 初始化表达式; //普通变量
数据类型 类名::对象名(构造参数); //对象变量
//举例代码:
//Computer中的静态变量_totalPrice的初始化如下:
double Computer::_totalPrice = 0;
特殊成员函数
C++类中还有两种特殊的成员函数:静态成员函数和 const 成员函数。
静态成员函数
成员函数也可以定义成静态的,静态成员函数的特点:
- 静态的成员函数的第一个参数位置没有this指针,因此有以下情况。
- 静态成员函数只能访问静态数据成员和静态成员函数
- 静态成员函数不能访问非静态数据成员和非静态成员函数
- 非静态的成员函数能访问静态的数据成员和静态的成员函数
- 由于静态成员函数由所有对象共享,所以可以使用
类名::函数名
的形式调用静态成员函数(特殊用法)。
//举例代码:
class Computer
{
public:
//构造函数
Computer(const char * brand, double price)
: _brand(new char[strlen(brand) + 1]()), _price(price)
{
_totalPrice += _price;
}
//...
static void printTotalPrice()
{
cout << "总价:" << _totalPrice << endl;
/* this -> _price = 10; //error,静态成员函数无this指针。 */
/* printf( "this : %p\n", this); //error,静态成员函数无this指针 */
/* print(); //error,不能调用非静态成员函数。 */
}
~Computer(){
cout << "~Computer()" << endl;
_totalPrice -= _price;
}
//...
private:
char * _brand;
double _price;
static double _totalPrice;
};
double Computer::_totalPrice = 0.0; //全局位置初始化静态数据成员
int main(void)
{
cout << "初始情况下,总价为:" << endl;
Computer::printTotalPrice();//不用对象,直接通过类名调用静态成员函数
//创建pc1对象
Computer pc1("Lenovo YOGA 14s", 6500);
pc1.print();
pc1.printTotalPrice();
//创建pc2对象
Computer pc2("Huawei MateBook14", 7000);
pc2.print();
//以下三种效果相同,输出结果相同
pc2.printTotalPrice();
pc1.printTotalPrice();//pc1 和 pc2 共享 printTotalPrice()
Computer::printTotalPrice();//通过类名直接调用
/* Computer::print() //error,不能通过类名直接调用非静态成员函数 */
return 0;
}
const成员函数
const
放在函数的参数表和函数体之间(与之前介绍的const放在函数前修饰返回值不同),
特点:
- 只能读取类数据成员,而不能修改数据成员。
- onst版本的成员函数具有只读特性,不能进行写操作,所以若想实现不修改数据成员,可以将成员函数用const修饰。
- const版本的成员函数与非const版本的成员函数可以重载( 参数列表中this指针const属性不同 ),一般先写const版本的。
- const对象只能调用const版本额成员函数,不能调用非const版本的成员函数;
- 非const对象,既可以调用const版本的成员函数,可以调用非const版本的成员函数。
- 对于非const对象,若屏蔽掉非const版本的成员函数,则调用const版本的成员函数,但若屏蔽掉const版本的成员函数,则const对象调用时会报错。
- 默认情况下,非const对象调用非const版本的成员函数。
格式:
函数返回类型 函数名(参数列表) const {
//函数体
}
举例代码:
class Computer
{
public:
//...
//原本this指针的形式:单const,*const,可以修改所指内容,不能修改指针指向
void print(/* Computer *const this */) {
/* this -> price = 100; //OK,可以修改所指内容 */
cout << "void print()" << endl;
cout << "品牌:" << _brand << endl
<< "价格:" << _price << endl;
}
//加const后的this指针的形式:双const,const * const,不能修改所指内容和指针指向
//故参数列表不同,实现了函数重载
void print(/* const Computer *const this */) const {
/* this -> price = 100; //error,不可以修改所指内容 */
cout << "void print()" << endl;
cout << "品牌:" << _brand << endl
<< "价格:" << _price << endl;
}
//...
};
void test(){
Computer pc1("Lenovo YOGA 14s", 6500); //非const对象
cout << "pc1 = " << endl;
//调用非const版本(默认)或const版本的print成员函数均可
pc1.print();
const Computer pc2("Lenovo YOGA 14s", 6500); //const对象
cout << "pc2 = " << endl;
//只能调用const版本的print成员函数
pc2.print();
}
r pc1(“Lenovo YOGA 14s”, 6500); //非const对象
cout << "pc1 = " << endl;
//调用非const版本(默认)或const版本的print成员函数均可
pc1.print();
const Computer pc2("Lenovo YOGA 14s", 6500); //const对象
cout << "pc2 = " << endl;
//只能调用const版本的print成员函数
pc2.print();
}