C++ 对象指针

1.对象指针的一般概念

和基本类型的变量一样,每一个对象在初始化之后都会在内存中占有一定的空间。因此,既可以通过对象名,也可以通过对象地址来访问一个对象。虽然对象同时包含了数据和函数两种成员,与一般变量略有不同,但是对象所占据的内存空间只是用于存放数据成员的,函数成员不在每一个对象中存在副本。对象指针就是用于存放对象地址的变量。 对象指针遵循一般变量指针的各种规则。

(1)声明对象指针

声明对象指针的一般语法形式为:

类名*对象指针名;

例如:

Point p1;//声明Point类的对象p1
Point *pointPtr;//声明Point类的对象指针变量PointPtr
PointPtr=&p1;//将对象p1的地址赋给PointPtr,使PointPtr指向p1

(2)使用对象指针访问对象的成员

就像通过对象名来访问对象的成员一样,使用对象指针一样可以方便地访问对象的成员,语法形式为:

对象指针名->成员名;

这种形式与“(*对象指针名).成员名”的访问形式是等价的。

【例1】使用指针来访问Point类的成员。

class Point//定义Point类
{
public://外部接口
	Point(int x=0,int y=0):x(x),y(y){}//构造函数
	int getX() { return x; }//返回x
	int getY() { return y; }//返回y
private://私有数据
	int x, y;
};

int main()
{
	Point a(4, 5);//定义并初始化对象a
	Point* p1 = &a;//定义对象指针p1,用a的地址将其初始化
	cout << p1->getX() << endl;//利用指针访问对象成员
	cout << (*p1).getX() << endl;//利用指针访问对象成员
	cout << a.getX() << endl;//利用对象名访问对象成员
	return 0;
}

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

对象指针在使用之前一定要先进行初始化,让它指向一个已经声明过的对象,然后再使用。通过对象指针可以访问到对象的公有成员。

【例2】将下面的错误代码改写为正确

错误代码:

class A;
class B
{
	A x;
};
class A
{
	B y;
};

错误原因:
在类A的定义尚不完善的时候B类不可以使用A类的对象。

修改后的代码;

class A;
class B
{
	A* x;
};
class A
{
	B y;
};
int main()
{
	return 0;
}

这里在类B中声明一个类A的指针,这是允许的,而类A的定义位于类B的完整定义之后,当然就可以声明B类的对象作为数据成员。

2.this指针

this指针是隐含于每一个类的非静态成员函数中的特殊指针(包括构造函数和析构函数),它用于指向当前正在被成员函数操作的对象。

【注意】this指针实际上是类成员函数的一个隐含参数。在调用类的成员函数时,当前被该成员函数所操作的对象的地址会自动作为该参数的值,传递给被调用的成员函数,这样被调函数就能通过this指针来访问当前正在被操作的对象的数据成员。对于常成员函数来说,这个隐含的参数是常指针类型的。

每次对成员函数的调用,都存在一个当前正在被成员函数操作的对象,this指针就是指向当前正在被操作的这个对象的指针。
例如:

class Point//定义Point类
{
public://外部接口
	Point(int x=0,int y=0):x(x),y(y){}//构造函数
	int getX() { return x; }//返回x
	int getY() { return y; }//返回y
private://私有数据
	int x, y;
};

int main()
{
	Point a(4, 5);//定义并初始化对象a
	Point* p1 = &a;//定义对象指针p1,用a的地址将其初始化
	cout << p1->getX() << endl;//利用指针访问对象成员
	cout << (*p1).getX() << endl;//利用指针访问对象成员
	cout << a.getX() << endl;//利用对象名访问对象成员
	return 0;
}

以上代码中的成员函数:

int getX()
{
	return x;
}

中的return x;,系统需要区分每次执行这条语句时被赋值的数据成员到底属于哪一个对象,使用的就是this指针,对系统来讲,每次调用都相当于执行的是:

return this->x;

this指针明确地指出了成员函数当前所操作的数据所属的对象。实际过程是,this指针是成员函数的一个隐含的形参,当通过一个对象调用成员函数时,系统就先将该对象的地址通过参数传递给成员函数,成员函数再对该对象的数据成员进行操作,隐含地使用了this指针。

this指针是一个指针常量,对于常成员函数来说,this同时又是一个指向常量的指针。在成员函数中,可以使用*this来标识正在调用该函数的对象。

【注意】当局部作用域中声明了与类成员同名的标识符时,对该标识符的直接引用代表的是局部作用域中所声明的标识符,这时为了访问该类成员,就可以通过this指针。

3.指向类的非静态成员的指针

类的成员自身也有一些变量、函数或者对象等,因此也可以直接将它们的地址存放到一个指针变量中,这样,就可以使指针直接指向对象的成员,进而可以通过这些指针访问对象的成员。

指向对象成员的指针使用前也要先声明,再赋值,然后引用。因此首先要声明指向该对象所在类的成员的指针。

(1)声明指针语句一般形式为:

类型说明符 类名::*指针名;//声明指向数据成员的指针
类型说明符 (类名::*指针名)(参数表)//声明指向函数成员的指针

声明了指向成员的指针之后,需要对其进行赋值,也就是要确定指向类的哪一个成员。

(2)对数据成员指针赋值的语法形式一般为:

指针名=&类名::数据成员名;

(3)使用成员函数指针访问成员函数

【注意】对类成员取地址时,也要遵守访问权限的约定,也就是说,在一个类的作用域之外不能对它的私有成员取地址。

对一个普通变量,用“&”运算符就可以得到它的地址,将相应的地址赋值给一个指针就可以通过指针访问变量。但是对于类的成员来说,问题就比较复杂。

类的定义只确定了各个数据成员的类型,所占内存大小以及它们的相对位置,在定义时并不为数据成员分配相应的地址。因此上述赋值之后,只说明了被赋值的指针是专门用于指向哪个数据成员的,同时指针中存放该数据成员在类中的相对位置(即相对于起始地址的地址偏移量),当然通过这样的指针现在并不能访问什么。

由于类是通过对象而实例化的,在声明类的对象时才会为具体的对象分配内存空间,这时只要将对象在内存中的起始地址与成员指针中存放的相对偏移结合起来就可以访问到对象的数据成员了。访问数据成员时,这种结合可通过以下两种语法形式实现:

对象名.*类成员指针名;

对象指针名->*类成员指针名;

【例】访问对象的公有成员的不同方式

class Point//定义Point类
{
public://外部接口
	Point(int x = 0, int y = 0) :x(x), y(y) {}//构造函数
	int getX() { return x; }//返回x
	int getY() { return y; }//返回y
private://私有数据
	int x, y;
};

int main()
{
	Point a(4, 5);//定义并初始化对象a
	Point* p1 = &a;//定义对象指针p1,用a的地址将其初始化
	int(Point:: * fun)() = &Point::getX;//定义成员函数指针并初始化

	cout << (a.*fun)() << endl;//使用成员函数指针和对象名访问成员函数
	cout << (p1->*fun)() << endl;//使用成员函数指针和对象指针访问成员函数
	cout << a.getX() << endl;//使用对象名访问成员函数
	cout << p1->getX() << endl;//使用对象指针访问成员函数
	return 0;
}

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

(4)对成员函数指针赋值
成员函数指针在声明之后要用以下形式的语句对其赋值:

指针名=&类名::函数成员名;

【注意】常成员函数与普通成员函数具有不同的类型,因此能够被常成员函数赋值的指针,需要在声明时明确写上const关键字。

(5)使用成员函数指针调用成员函数

一个普通函数的函数名就表示它的起始地址,将起始地址赋给指针,就可以通过指针调用函数。类的成员函数虽然并不在每个对象中复制一份副本,但是由于需要确定this指针,因而必须通过对象来调用非静态成员函数。因此,上述对成员函数指针赋值以后,也还不能用指针直接调用成员函数,而是需要先声明类的对象,然后用以下的形式语句利用指针调用成员函数:

(对象名.*类成员函数指针名)(参数表)

(对象指针名->*类成员函数指针名)(参数表)

成员函数指针的声明、赋值和使用过程中的返回值类型、函数参数一定要互相匹配。

3.指向类的静态成员的指针

对类的静态成员的访问是不依赖于对象的,因此可以通过普通的指针来指向和访问静态成员。

【例1】通过指针访问类的静态数据成员。

class Point//定义Point类
{
public://外部接口
	static int count;//声明静态数据成员,用于记录点的个数
	Point(int x = 0, int y = 0) :x(x), y(y) //构造函数
	{
		count++;
	}
	Point(const Point& p) :x(p.x), y(p.y)
	{
		count++;
	}
	~Point()
	{
		count--;
	}
	int getX() { return x; }//返回x
	int getY() { return y; }//返回y

private://私有数据
	int x, y;
};

int Point::count = 0;//静态数据成员定义和初始化,使用类名限定

int main()
{
	int* p = &Point::count;//定义一个int型指针,指向类的静态成员
	Point a(4, 5);//定义并初始化对象a
	cout << "点A为:(" << a.getX() << "," << a.getY() << ")  ";
	cout << "对象的个数=" << *p << endl;//直接通过指针访问静态数据成员
	Point b(a);//定义对象b,并用a对象对其进行初始化
	cout << "点B为:(" << b.getX() << "," << b.getY() << ")  ";
	cout << "对象的个数=" << *p << endl;//直接通过指针访问静态数据成员
	
	return 0;
}

运行结果:
在这里插入图片描述
【例2】通过指针访问类的静态函数成员。

class Point//定义Point类
{
public://外部接口
	Point(int x = 0, int y = 0) :x(x), y(y) //构造函数
	{
		count++;
	}
	Point(const Point& p) :x(p.x), y(p.y)
	{
		count++;
	}
	~Point()
	{
		count--;
	}
	int getX() { return x; }//返回x
	int getY() { return y; }//返回y

	static void ShowCount()//输出静态数据成员
	{
		cout << "  对象的个数=" << count << endl;
	}

private://私有数据
	int x, y;
	static int count;//静态数据成员声明,用于记录点的个数
};

int Point::count = 0;//静态数据成员定义和初始化,使用类名限定

int main()
{
	void(*fun)() = Point::ShowCount;//定义一个指向函数的指针,指向类的静态成员函数
	
	Point a(4, 5);//定义并初始化对象a
	cout << "点A为:(" << a.getX() << "," << a.getY() << ")  ";
	fun();//输出对象个数,直接通过指针访问静态函数成员
	Point b(a);//定义对象b,并用a对象对其进行初始化
	cout << "点B为:(" << b.getX() << "," << b.getY() << ")  ";
	fun();//输出对象个数,直接通过指针访问静态函数成员

	return 0;
}

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