C++类和对象之特殊数据成员初始化和特殊成员函数

本文深入探讨了C++中四种特殊数据成员:常量成员、引用成员、类对象成员和静态成员的初始化方法,以及静态成员函数和const成员函数的特性和使用方式。

目录:


特殊数据成员的初始化

常量数据成员引用数据成员类对象成员静态数据成员

特殊的成员函数

静态成员函数const 成员函数





特殊数据成员的初始化

在 C++ 的类中,有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)
	{}
	
private:
	const int _ix;
	const int _iy;
	int & _iz;
};

引用数据成员

和常量成员相同,引用成员也必须在构造函数初始化列表中进行初始化引用作为数据成员时候,占用一个指针大小的空间


类对象成员

一个自定义的类型创建的对象作为另外一个自定义类型的数据成员(子对象)的时候,默认会调用子对象的默认(无参)的构造函数,如果我们想初始化,必须要显示调用子对象的相应构造。

即当数据成员本身也是自定义类类型对象时,比如一个直线类 Line 对象中包含两个 Point 类对象,对 Point对象的创建就必须要放在 Line 的构造函数的初始化列表中进行。如

class Line {
public:
	Line(int x1, int y1, int x2, int y2)
	: _pt1(x1, y1)
	, _pt2(x2, y2)
	{
		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 对象。

静态数据成员

C++ 允许使用 static (静态存储)修饰数据成员,这样的成员在编译时就被创建并初始化的(与之相比,对象是在运行时被创建的),且其实例只有一个,被所有该类的对象共享,就像住在同一宿舍里的同学共享一个房间号一样。

静态数据成员 和之前介绍的静态变量一样,当程序执行时,该成员已经存在,一直到程序结束,任何该类对象都可对其进行访问,静态数据成员存储在全局/静态区,并不占据对象的存储空间

下面以 Computer 为例,模拟购买电脑的过程,为了获取总价,我们定义一个静态变量 _totalPrice :

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;
	static double _totalPrice; //全局静态,头文件中
};

因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类对象时被定义的。这意味着它们不是由类的的构造函数初始化的,一般来说,我们不能在类的内部初始化静态数据成员,必须在类的外部定义和初始化静态数据成员,且不再包含 static 关键字,格式如下:

类型 类名::变量名 = 初始化表达式; //普通变量
类型 类名::对象名(构造参数);     //对象变量

Computer 中的静态变量 _totalPrice 的初始化如下:

//静态数据成员必须放到类外部进行初始化
double Computer::_totalPrice = 0; //定义文件中




特殊的成员函数

除了特殊的数据成员以外, C++ 类中还有两种特殊的成员函数:静态成员函数const 成员函数
先看静态成员函数。


静态成员函数

成员函数也可以定义成静态的,静态成员函数的特点:

  1. 静态成员函数内部不能使用非静态的成员变量和非静态的成员函数
  2. 静态成员函数内部只能调用静态数据成员和静态的成员函数
  3. 如果非要 静态成员函数中访问非静态成员,可以传参

原因在于静态成员函数的参数列表中不含有隐含的 this 指针,如下:

class Computer
{
public:
	Computer(const char * brand, double price)
	: _brand(new char[strlen(brand) + 1]())
	, _price(price)
	{
		_totalPrice += _price;
	}
	//... 
	void printTotalPrice()
	{
		cout << "总价:" << _totalPrice << endl;
	}
	//...

private:
	char * _brand;
	double _price;
	static double _totalPrice;
};

对于静态成员函数,还可以直接通过类名进行调用,这也是最常用的方式:

int main(void)
{
	Computer pc1("Huawei MateBook14", 5699);
	pc1.print();
	Computer::printTotalPrice();//通过类名直接调用
	return 0;
}

下面再来看一个有意思的例子:

class NullPointCall
{
public:
	static void test1();
	void test2();
	void test3(int test);
	void test4();

private:
	static int _static;
	int _test;
};

int NullPointCall::_static = 0;

void NullPointCall::test1()
{
	cout << "_static = " << _static << endl;
}
void NullPointCall::test2()
{
	cout << "coding is very cool!" << endl;
}
void NullPointCall::test3(int test)
{
	cout << "test = " << test << endl;
}
void NullPointCall::test4()
{
	cout << "_test = " << _test << endl;
}

int main(void)
{
	NullPointCall * pNull = nullptr;
	pNull->test1();
	pNUll->test2();
	pNull->test3(10);
	pNull->test4();
	return 0;
}

运行结果
在这里插入图片描述

程序中直接通过类名进行调用 text4,在 text4 函数中调用了非静态成员


const 成员函数

之前已经介绍了 const 在函数中的应用,实际上, const 在类成员函数中还有种特殊的用法,把 const 关键字放在函数的参数表和函数体之间(与之前介绍的 const 放在函数前修饰返回值不同), 称为 const 成员函数,其具有以下特点:

  1. 只能读取类数据成员,而不能修改之
  2. 只能调用 const 成员函数,不能调用非 const 成员函数。
  3. const 成员函数与非const成员函数可以 进行重载,且const成员函数不能修改数据成员
  4. 非const对象可以调用两种,但是默认调用非const成员函数
  5. const对象只能调用const成员函数

其格式为:

类型 函数名(参数列表) const
{
	函数体
}

比如当给 Computer 类添加一个 const 的打印函数后,则其实现如下:

class Computer
{
public:
	//... 
	void print() const
	{
		cout << "品牌:" << _brand << endl
			 << "价格:" << _price << endl;
	}

	//也可以是
	//void print() const
    //{
    //    cout << "(" << this->_brand
    //         << ", " << this->_brand
    //         << ")" << endl;
    //}
    
    //非 const
	void print2()
	{
		cout << "品牌:" << _brand << endl
			 << "价格:" << _price << endl;
	}
	
	//...
};

有以下情况:

Computer com1("Thinkpad", 8000);

//默认情况下,非 const对象可以调用非 const版本的成员函数
com1.print2(); 
//非 const对象可以调用 const版本的成员函数
com1.print(); 

const Computer com2("MI", 5000);

//const对象可以调用 const版本的成员函数
com2.print();
//const对象不可以调用非 const版本的成员函数
//com2.print2();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值