c++中"指针"终结理解

我们可以对指针这样定义:

通过指针中存放的首地址,应用程序顺利地找到某个变量。就好像我最近认识了一位朋友,他叫我有空去他家坐坐,然后,他留下了地址。某个周末我正闲着,忽然想起这位朋友,于是,我就根据他留的地址去找他,结果,当我来到傻B街230号出租房时,里面走出一个我不认识的人,于是,我问他我这位朋友去哪了,陌生人说,我刚租了这房子,你找的可能是前一位租户吧。

所以,指针所指向的地址,有可能是变量B,也有可能是变量F,或者变量S,指针是房东,可以把房子租给B,C,或F,它可以动态为变量分配内存,也可以把变量销毁(delete),交不起房租就滚蛋(析构函数)。

从上面的故事中,我们看到指针的两个用途:索引内存和分配内存

看看下面这个例子。

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* pint = new int(100);
  5. printf(" *pint的值:%d\n", *pint);
  6. printf(" pint的值:0x %x\n", pint);
  7. getchar();
  8. }
#include <stdio.h>

void main()
{
	int* pint = new int(100);

	printf("  *pint的值:%d\n", *pint);
	printf("  pint的值:0x %x\n", pint);

	getchar();
}

你猜猜,它运行后会出现什么?

我们看到了,pint里面存的就是整型100的首地址,因为它是int*,是指向int的指针,所以指针知道,找到首地址后,我只关注从首地址开始,连续的4个字节,后面的我不管了,因为我只知道int有四个字节。上面的例子,我们看到pint的值就是0x10f1968,这就是整型100在内存中的首地址,所以,100所拥有的内存块可能是:

0x10f1968 , 0x10f1969, 0x10f196A, 0x10f196b

总之是连续的内存块来保存这4个字节。

new int(100),表示指针pint在首地址为0x10f1968的内存区域创建了一个4个字节的区域,里面保存的值就是整型100,所以,pint取得的就是100的首地址,而加上*号就不同了,看看上面的例子,*pint的值就是100了。这样一来,我们又得到一个技巧:

利用指针标识符 * 放在指针变量前即可获得指针所指地址中存储的实际值。


我都大家一个很简单的技巧。看看下面两行代码。

int *p = new int(200);

int p = 200;

因为 * 放在类型后或放在变量名前面都是可以的,即int* pint和int *pint是一个道理。这样一来,我们不妨把int *pint 看作int (*pint),将整个*pint看作一个整体,这样看上去是不是和下面的声明很像?

int a = 30;

所以,int* p = new int(30)中,*p返回的值就是int的本值30,而p则只是返回30的首地址。

再看看下面的代码:

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* arrint = newint[3];
  5. arrint[0] = 20;
  6. arrint[1] = 21;
  7. arrint[2] = 22;
  8. for(int i =0; i < 3; i++)
  9. {
  10. printf(" 数组[%d] = %d\n", i, arrint[i]);
  11. }
  12. delete [] arrint; // 清理内存
  13. getchar();
  14. }
#include <stdio.h>
void main()
{
	int* arrint = new int[3];
	arrint[0] = 20;
	arrint[1] = 21;
	arrint[2] = 22;
	for(int i =0; i < 3; i++)
	{
		printf("  数组[%d] = %d\n", i, arrint[i]);
	}
	delete [] arrint; // 清理内存
	getchar();
}

现在你可以猜猜它的运行结果是什么。


从上面的代码我们又看到了指针的第三个功能:创建数组

上例中,我创建了有三个元素的数组。在使用完成后,要使用delete来删除已分配的内存,所以,我们的第一个例子中,其实不完善,我们没有做内存清理。

int* pint = new int(100);

/****/

delete pint;

为什么指针可以创建数组?前面我提到过,指针是指向首地址的,那么你想想,我们的数组如果在堆上分配了内存,它们是不是也按一定次序存放在一块连续的内存地址中,整个数组同样构成了一段内存块。

二、取地址符号&

很多书和教程都把这个符号叫引用,但我不喜欢翻译为引用,因为引用不好理解,如果叫取地址符,那我估计你就会明白了,它就是返回一个变量的首地址。

看看例子:

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int a = 50;
  5. int* p = &a;
  6. printf(" a的值:%d\n", a);
  7. printf(" p的值:0x_%x\n", p);
  8. getchar();
  9. }
#include <stdio.h>
void main()
{
	int a = 50;
	int* p = &a;
	printf("  a的值:%d\n", a);
	printf("  p的值:0x_%x\n", p);
	getchar();
}

我们不能直接对指针变量赋值,要把变量的地址传给指针,就要用取地址符&。上面的代码中我们声明了int类型的变量a,值为50,通过&符号把变量a的地址存到p指针中,这样,p指向的就是变量a的首地址了,故:a的值的50,而p的值就应该是a的地址。

那么,这样做有啥好处呢?我们把上面的例子再扩展一下,变成这样:

  1. #include <stdio.h>
  2. void main()
  3. {
  4. int a = 50;
  5. int* p = &a;
  6. printf(" a的值:%d\n", a);
  7. printf(" p的值:0x_%x\n", p);
  8. /* 改变指针所指向的地址块中的值,就等于改变了变量的值 */
  9. *p = 250;
  10. printf(" a的新值:%d\n", a);
  11. getchar();
  12. }
#include <stdio.h>
void main()
{
	int a = 50;
	int* p = &a;
	printf("  a的值:%d\n", a);
	printf("  p的值:0x_%x\n", p);
	/* 改变指针所指向的地址块中的值,就等于改变了变量的值 */
	*p = 250;
	printf("  a的新值:%d\n", a);
	getchar();
}

先预览一下结果。



不知道大家在这个例子中发现了什么?

我们定义了变量a,值为50,然后指针p指向了a的首地址,但注意,后面我只是改变了p所指向的那块内存中的值,我并没有修改a的值,但是,你看看最后a的值也变为了250,想一想,这是为什么?

三、参数的传递方式

很多书,包括一些计算机二级的考试内容,那些傻S砖家只是想出一大堆与指针相关的莫名其妙的考题,但很让人找不到指针在实际应用到底能干什么,我估计那些砖家自己也不知识吧。所以,我们的考试最大的失败,就是让学生不知识学了有什么用。

上面介绍了指针可以存首地址,可以分配内存,可以创建数组,还说了取地址符&,那么,这些东西有什么用呢?你肯定会问,我直接声明一个变量也是要占用内存的,那我为什么要吃饱了没事干还要用指针来存放首地址呢?

好,我先不回答,我们再说说函数的参数传递。看看下面这样的例子。

  1. #include <stdio.h>
  2. void fn(int x)
  3. {
  4. x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(a);
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>

void fn(int x)
{
	x += 100;
}

void main()
{
	int a = 20;
	fn(a);
	printf("  a : %d\n", a);
	getchar();
}


我们希望,在调用函数fn后,变量a的值会加上100,现在我们运行一下,看看结果:

我们可能会很失望,为什么会这样?我明明是把20传进了fn函数的,为什么a的值还是不变呢?不用急,我们再把代码改一下:

  1. #include <stdio.h>
  2. void fn(int x)
  3. {
  4. printf(" 参数的地址:0x_%d\n", &x);
  5. x += 100;
  6. }
  7. void main()
  8. {
  9. int a = 20;
  10. fn(a);
  11. printf(" a : %d\n", a);
  12. printf(" a的地址:0x_%x\n", &a);
  13. getchar();
  14. }
#include <stdio.h>

void fn(int x)
{
	printf("  参数的地址:0x_%d\n", &x);
	x += 100;
}

void main()
{
	int a = 20;
	fn(a);
	printf("  a : %d\n", a);
	printf("  a的地址:0x_%x\n", &a);
	getchar();
}

运行结果如下:


看到了吗?变量a和fn函数的参数x的地址是不一样的,这意味着什么呢?这说明,变量a的值虽然传给了参数x,但实际上是声明了一个新变量x,而x的值为20罢了,最后加上100,x的中的值是120,但a的值没有变,因为在函数内被+100的根本不是变量a,而是变量x(参数)。

这样,就解释了为什么么函数调用后a的值仍然不变的原因。

那么,如何让函数调用后对变量a作修改,让它变成120呢?这里有两个方法:

(1)指针法。把参数改为指针类型。

  1. #include <stdio.h>
  2. void fn(int* x)
  3. {
  4. *x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(&a);//用取地址符来传递,因为指针是保存地址的
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>

void fn(int* x)
{
	*x += 100;
}

void main()
{
	int a = 20;
	fn(&a);//用取地址符来传递,因为指针是保存地址的
	printf("  a : %d\n", a);
	getchar();
}

这里要注意,把变量传给指针类型的参数,要使用取地址符&。

那么,这次运行正确吗?


好了,终于看到想要的结果了。

(2)引用法,就是把参数改为&传递的。

  1. #include <stdio.h>
  2. void fn(int& x)
  3. {
  4. x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(a);//直接传变量名就行了
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>

void fn(int& x)
{
	x += 100;
}

void main()
{
	int a = 20;
	fn(a);//直接传变量名就行了
	printf("  a : %d\n", a);
	getchar();
}

可以看到,这样的运行结果也是正确的。


四、指针与对象

不管是类还是结构(其实结构是一种特殊的类),它们在创建时还是要创建内存的,但是,创建类的对象也有两种方式,直接声明和用指针来分配新实例。

  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. Test();
  7. ~Test();
  8. void Do(char* c);
  9. };
  10. Test::Test()
  11. {
  12. cout << "Test对象被创建。" << endl;
  13. }
  14. Test::~Test()
  15. {
  16. cout << "Test对象被销毁。" << endl;
  17. }
  18. void Test::Do(char* c)
  19. {
  20. cout << "在" << c << "中调用了Do方法。" << endl;
  21. }
  22. void Func1()
  23. {
  24. Test t;
  25. t.Do("Func1");
  26. /*
  27. 当函数执行完了,t的生命周期结束,发生析构。
  28. */
  29. }
  30. void Func2()
  31. {
  32. Test* pt = new Test;
  33. pt -> Do("Func2");
  34. /*
  35. 用指针创建的对象,就算指针变量的生命周期结束,但内存中的对象没有被销毁。
  36. 因此,析构函数没有被调用。
  37. */
  38. }
  39. int main()
  40. {
  41. Func1();
  42. cout << "---------------------" << endl;
  43. Func2();
  44. getchar();
  45. return 0;
  46. }
#include <iostream>
using namespace std;

class Test
{
public:
	Test();
	~Test();
	void Do(char* c);
};

Test::Test()
{
	cout << "Test对象被创建。" << endl;
}
Test::~Test()
{
	cout << "Test对象被销毁。" << endl;
}
void Test::Do(char* c)
{
	cout << "在" << c << "中调用了Do方法。" << endl;
}

void Func1()
{
	Test t;
	t.Do("Func1");
	/*
	当函数执行完了,t的生命周期结束,发生析构。
	*/
}

void Func2()
{
	Test* pt = new Test;
	pt -> Do("Func2");
	/*
	用指针创建的对象,就算指针变量的生命周期结束,但内存中的对象没有被销毁。
	因此,析构函数没有被调用。
	*/
}

int main()
{
	Func1();
	cout << "---------------------" << endl;
	Func2();
	getchar();
	return 0;
}


我们来看看这个例子,首先定义了一个类Test,在类的构造函数中输出对象被创建的个息,在发生析构时输出对象被销毁。

接着, 我们分别在两个函数中创建Test类的对象,因为对象是在函数内部定义的,根据其生命周期原理,在函数返回时,对象会释放,在内存中的数据会被销毁。理论上是这样的,那么,程序实际运行后会如何呢?

这时候我们发现一个有趣的现象,在第一个函数直接以变量形式创建的对象在函数执行完后被销毁,因为析构函数被调用;可是,我们看到第二个函数中并没有发生这样的事,用指针创建的对象,在函数完成时居然没有调用析构函数。

直接创建对象,变量直接与类实例关联,这样一来,当变量的生命周期结束时,自然会被处理掉,而用指针创建的实例,指针变量本身并不存储该实例的数据,它仅仅是存了对象实例的首地址罢了,指针并没有与实例直接有联系,所以,在第二个函数执行完后,被销毁的是Test*,而不是Test的对象,仅仅是保存首地址的指针被释放了而已,而Test对象依然存在于内存中,因此,在第二个函数完成后,Test的析构函数不会调用,因为它还没死呢。

那么,如何让第二个函数在返回时也销毁对象实例呢?还记得吗,我前文中提过。对,用delete.。

  1. void Func2()
  2. {
  3. Test* pt = new Test;
  4. pt -> Do("Func2");
  5. delete pt;
  6. }
void Func2()
{
	Test* pt = new Test;
	pt -> Do("Func2");
	delete pt;
}


现在看看,是不是在两个函数返回时,都能够销毁对象。

现在你明白了吧?

由此,可以得出一个结论:指针只负责为对象分配和清理内存,并不与内存中的对象实例有直接关系。

 

原文出处: http://blog.csdn.net/tcjiaan/article/details/8493072
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值