http://blog.csdn.net/tcjiaan/article/details/8493072

目录(?)[-]

  1. 一指针真的那么恐怖吗
  2. 二取地址符号
  3. 三参数的传递方式
  4. 四指针与对象

我不知道各位,一提起C++,第一感觉是什么?而据俺的观察,许多人几乎成了“谈C色变”。不管是C还是C++,一直以来都被很多人视为相当难学的玩意儿,幸好只是一个C++,没有C--,C**和C//,不然,那还得了?曾记得,某年某月某日,在某论坛上看到有牛人说“C++++”,当时我猜想这是啥玩意儿,后来经过一番顺虅摸瓜,深入调查发现,原来有人作了这么个有趣的等式:C# == C++++。

显然,这个等式也不太正确,C#不仅继承了C++一些特性,也继承了Delphi中的和VB中的一些优点。

好了,这个等式意义不大,咱们不扯它了。前面我写了许多和移动开发的文章,估计现在移动市场泡沫也差不多膨胀起来了,你说这泡沫,泡到什么程度呢?据说连压根连程序都没写过的人,也嚷着说:移动开发,我要(幸好不是官人,不然动机不纯)。

这很容易让人联想到“全民炒股”的创世纪大笑话,中国人貌似很喜欢这样,一曰跟风,二曰盲从。这二者合并起来,正好为市场本质上的“自发性,盲目性”等特征作了相当有力的诠释,难怪罗斯福总统说必要时还得宏观调控。在1932年如果还不调控的话,估计到了1945年,在太平洋战场上完蛋的不是零式战斗机了,该是地狱猫战斗机了,呵呵。

不管是移动互联网,还是云计算,各位还是理性地考虑一下吧,认为有需要才进行投资,目前来说,移动市场绝大部分还是在娱乐上,要说真要和商业模式融合,估计现在的手机和平板电脑还达不到这个指标,未来几年有可能开始和商业平台对接,今年的话,不太可能,如果你计划把你的商业应用向移动平台扩展(我这里用扩展,千万不要转移,不然会丢失原来的市场),那么,你现在可能要考虑你现有的应用程序框架到底有多少可以进行扩展了。

这扩展一事说起来容易,做起来可不轻松,记得去年我在F公司工作,尝把ERP的功能,从小的模块开始,向Web/电子商务平台整合,技术上是没问题的,但业务逻辑上有可能会一败涂地。所以,有时候,咱们做开发的,学一学市场营销、财务会计、企业管理、HR,甚至是文学艺术,对我们的成长还是有好处的,你只会写程序,有时候很容易“当局者迷”,金庸老先生在小说里常常把这个称为“走火入X”,不知道欧阳锋大哥算不算。

一、指针,真的那么恐怖吗

很多人学C语言,就是败在她的“石榴裙”下的,指针(Pointer),这里我为什么要把英文原名写出来了,我目的想让你思考一下,我们常叫它指针,但是,这个翻译到底合不合理?

在学习C和C++时,很多人会被指针给弄得“六神无主”,虽然大家在许多书上都看到,指针就是用来保存地址的,是吧。然很多人就是无法理解。我这里告诉大家一个技巧,凡是遇到抽象的东西,你就不妨尝试着在客观存在的事物中去寻找与其相似的东西,例如从我们日常生活中入手。我们要明白一个道理,所有抽象的东西,追根到底,都是从客观事物中提取出来的,也就是说,任何抽象的东西,都会在客观世界中找到它的原形。

那么,在我们的日常生活中,有没有与C/C++中指针对应的东西呢?有,多得是:

指南针。

手表。

电流表/电压表。

汽车上用来显示剩余汽油的表。

……

看看,这些物体都有什么共同特点?是不是都有一根或者多根指针?比如,下面图片中的山寨手表。

现在,我要你从山寨手表中读出其指示的时间,那么,你想一想,你会怎么看出来的

这个应该会看吧,小学生都会用了。我们会先看一下时针所指的方向在哪个时刻范围内,如10-11之间,所以我们确定了是10点钟;然后,我们看到分针所指的是第二个刻度,我们读出2,所以一组合,就是10点2。

是不是这样读,没读错吧?

像电流表也是这样,我们就是通过指针所指的方向找到对应的刻度,就知道当前电流是多少安/毫安,家用的应该是以毫安为单位。

我们知道,程序的运行是存在内存中的,因此,我们在代码中声明的所有变量都是存放在内存中的某块区域中,所以,在C语言中,指针用来告诉我们,某个变量在内存中的首地址在哪。注意,是首地址,因为变量的长度不一定就是一个字节,有可能N多个字节,它在内存中是排列在一段连续的区域块中。

比如,某中学的教学楼,每个年级使用一栋楼,初一年级在A栋,初二在B栋,初三在C栋。某学生在校其间,由于多次非礼女同学,老师说要见家长,于是,家长K君来到了某学校,但学校那么大,怎么找到K君的儿子所在的教室呢?这时候,保安人员告诉K君,初二年级在B栋,并用手指着东北方向(指针指向的内存地址块的位置)。

于是,K君顺着保安人员手指的方向找到了B栋,他知道他儿子在3班,而楼上的教室都是按顺序的,1班在第一个教室,2班在第二个教室,以此类推。所以,K君很快就找到他儿子,然后把他教育了两顿(访问或处理指针所指向内存中的数据),然后,K君很郁闷地下楼,离开了学校(发生析构,清理内存)。

因此,我们可以对指针这样宝义:

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

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

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

看看下面这个例子。

[cpp] view plaincopyprint?
  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. }

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

我们看到了,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的首地址。

再看看下面的代码:

[cpp] view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* arrint = new int[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. }

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

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

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

int* pint = new int(100);

/****/

delete pint;

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

二、取地址符号&

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

看看例子:

[cpp] view plaincopyprint?
  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. }

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

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

[cpp] view plaincopyprint?
  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. }

先预览一下结果。

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

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

三、参数的传递方式

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

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

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

[cpp] view plaincopyprint?
  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. }

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

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

[cpp] view plaincopyprint?
  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. }

运行结果如下:

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

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

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

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

[cpp] view plaincopyprint?
  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. }

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

那么,这次运行正确吗?

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

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

[cpp] view plaincopyprint?
  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. }

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

四、指针与对象

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

[cpp] view plaincopyprint?
  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. }

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

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

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

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

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

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

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

现在你明白了吧?

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

补充:

我只是为了说明,指针其实是一个数字,它动态分配内存后,里面存的就是它指向的内存的首地址,但是,指针变量的生命周期终结后,并没有去清理它所指向的内存,而需要显示使用delete。动态new完后,不要忘了delete,不要误解了,与const* 没关系。

目录(?)[-]

  1. 一指针真的那么恐怖吗
  2. 二取地址符号
  3. 三参数的传递方式
  4. 四指针与对象

我不知道各位,一提起C++,第一感觉是什么?而据俺的观察,许多人几乎成了“谈C色变”。不管是C还是C++,一直以来都被很多人视为相当难学的玩意儿,幸好只是一个C++,没有C--,C**和C//,不然,那还得了?曾记得,某年某月某日,在某论坛上看到有牛人说“C++++”,当时我猜想这是啥玩意儿,后来经过一番顺虅摸瓜,深入调查发现,原来有人作了这么个有趣的等式:C# == C++++。

显然,这个等式也不太正确,C#不仅继承了C++一些特性,也继承了Delphi中的和VB中的一些优点。

好了,这个等式意义不大,咱们不扯它了。前面我写了许多和移动开发的文章,估计现在移动市场泡沫也差不多膨胀起来了,你说这泡沫,泡到什么程度呢?据说连压根连程序都没写过的人,也嚷着说:移动开发,我要(幸好不是官人,不然动机不纯)。

这很容易让人联想到“全民炒股”的创世纪大笑话,中国人貌似很喜欢这样,一曰跟风,二曰盲从。这二者合并起来,正好为市场本质上的“自发性,盲目性”等特征作了相当有力的诠释,难怪罗斯福总统说必要时还得宏观调控。在1932年如果还不调控的话,估计到了1945年,在太平洋战场上完蛋的不是零式战斗机了,该是地狱猫战斗机了,呵呵。

不管是移动互联网,还是云计算,各位还是理性地考虑一下吧,认为有需要才进行投资,目前来说,移动市场绝大部分还是在娱乐上,要说真要和商业模式融合,估计现在的手机和平板电脑还达不到这个指标,未来几年有可能开始和商业平台对接,今年的话,不太可能,如果你计划把你的商业应用向移动平台扩展(我这里用扩展,千万不要转移,不然会丢失原来的市场),那么,你现在可能要考虑你现有的应用程序框架到底有多少可以进行扩展了。

这扩展一事说起来容易,做起来可不轻松,记得去年我在F公司工作,尝把ERP的功能,从小的模块开始,向Web/电子商务平台整合,技术上是没问题的,但业务逻辑上有可能会一败涂地。所以,有时候,咱们做开发的,学一学市场营销、财务会计、企业管理、HR,甚至是文学艺术,对我们的成长还是有好处的,你只会写程序,有时候很容易“当局者迷”,金庸老先生在小说里常常把这个称为“走火入X”,不知道欧阳锋大哥算不算。

一、指针,真的那么恐怖吗

很多人学C语言,就是败在她的“石榴裙”下的,指针(Pointer),这里我为什么要把英文原名写出来了,我目的想让你思考一下,我们常叫它指针,但是,这个翻译到底合不合理?

在学习C和C++时,很多人会被指针给弄得“六神无主”,虽然大家在许多书上都看到,指针就是用来保存地址的,是吧。然很多人就是无法理解。我这里告诉大家一个技巧,凡是遇到抽象的东西,你就不妨尝试着在客观存在的事物中去寻找与其相似的东西,例如从我们日常生活中入手。我们要明白一个道理,所有抽象的东西,追根到底,都是从客观事物中提取出来的,也就是说,任何抽象的东西,都会在客观世界中找到它的原形。

那么,在我们的日常生活中,有没有与C/C++中指针对应的东西呢?有,多得是:

指南针。

手表。

电流表/电压表。

汽车上用来显示剩余汽油的表。

……

看看,这些物体都有什么共同特点?是不是都有一根或者多根指针?比如,下面图片中的山寨手表。

现在,我要你从山寨手表中读出其指示的时间,那么,你想一想,你会怎么看出来的

这个应该会看吧,小学生都会用了。我们会先看一下时针所指的方向在哪个时刻范围内,如10-11之间,所以我们确定了是10点钟;然后,我们看到分针所指的是第二个刻度,我们读出2,所以一组合,就是10点2。

是不是这样读,没读错吧?

像电流表也是这样,我们就是通过指针所指的方向找到对应的刻度,就知道当前电流是多少安/毫安,家用的应该是以毫安为单位。

我们知道,程序的运行是存在内存中的,因此,我们在代码中声明的所有变量都是存放在内存中的某块区域中,所以,在C语言中,指针用来告诉我们,某个变量在内存中的首地址在哪。注意,是首地址,因为变量的长度不一定就是一个字节,有可能N多个字节,它在内存中是排列在一段连续的区域块中。

比如,某中学的教学楼,每个年级使用一栋楼,初一年级在A栋,初二在B栋,初三在C栋。某学生在校其间,由于多次非礼女同学,老师说要见家长,于是,家长K君来到了某学校,但学校那么大,怎么找到K君的儿子所在的教室呢?这时候,保安人员告诉K君,初二年级在B栋,并用手指着东北方向(指针指向的内存地址块的位置)。

于是,K君顺着保安人员手指的方向找到了B栋,他知道他儿子在3班,而楼上的教室都是按顺序的,1班在第一个教室,2班在第二个教室,以此类推。所以,K君很快就找到他儿子,然后把他教育了两顿(访问或处理指针所指向内存中的数据),然后,K君很郁闷地下楼,离开了学校(发生析构,清理内存)。

因此,我们可以对指针这样宝义:

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

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

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

看看下面这个例子。

[cpp] view plaincopyprint?
  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. }

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

我们看到了,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的首地址。

再看看下面的代码:

[cpp] view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* arrint = new int[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. }

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

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

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

int* pint = new int(100);

/****/

delete pint;

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

二、取地址符号&

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

看看例子:

[cpp] view plaincopyprint?
  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. }

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

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

[cpp] view plaincopyprint?
  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. }

先预览一下结果。

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

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

三、参数的传递方式

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

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

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

[cpp] view plaincopyprint?
  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. }

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

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

[cpp] view plaincopyprint?
  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. }

运行结果如下:

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

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

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

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

[cpp] view plaincopyprint?
  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. }

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

那么,这次运行正确吗?

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

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

[cpp] view plaincopyprint?
  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. }

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

四、指针与对象

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

[cpp] view plaincopyprint?
  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. }

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

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

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

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

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

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

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

现在你明白了吧?

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

补充:

我只是为了说明,指针其实是一个数字,它动态分配内存后,里面存的就是它指向的内存的首地址,但是,指针变量的生命周期终结后,并没有去清理它所指向的内存,而需要显示使用delete。动态new完后,不要忘了delete,不要误解了,与const* 没关系。

跟我一起玩Win32开发(1):关于C++的几个要点相关推荐

  1. 跟我一起玩Win32开发

    跟我一起玩Win32开发(1):关于C++的几个要点 我不知道各位,一提起 C++ ,第一感觉是什么?而据俺的观察,许多人几乎成了 " 谈 C 色变 " .不管是 C 还是 C++ ...

  2. 跟我一起玩Win32开发(4):创建菜单

    跟我一起玩Win32开发(4):创建菜单 也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长, ...

  3. 跟我一起玩Win32开发(5):具有单选标记的菜单

    跟我一起玩Win32开发(5):具有单选标记的菜单 帅哥们,美女们,下午好,我又来误人子弟,请做好准备. 今天,我们的目的是,想要实现下图中的这种菜单效果. 就是一种类似单选按钮的菜单,多个菜单项中, ...

  4. 跟我一起玩Win32开发(25):监视剪贴板

    跟我一起玩Win32开发(25):监视剪贴板 自从郭大侠和蓉儿离开桃花岛后,最近岛比较寂静,有一种"门前冷落鞍马稀"的感觉.于是,老邪就拿出<九阴真经>认真阅读,同时用 ...

  5. 跟我一起玩Win32开发(2):完整的开发流程

    http://blog.csdn.net/tcjiaan/article/details/8497535 目录(?)[-] 一WinMain入口点 二设计与注册窗口类 三创建和显示窗口 四更新窗口可选 ...

  6. 跟我一起玩Win32开发(20):浏览文件夹

    最近忙于一些相当无聊的事情,还没忙完,不过,博客还是要写的,不然我头顶上会多了几块砖头. 在上一篇博文中,我们浏览了文件,今天我们也浏览一下目录,如何? 浏览目录我们同样有两个规矩,用托管类库的我就不 ...

  7. 跟我一起玩Win32开发(6):创建右键菜单

    快捷菜单,说得容易理解一点,就是右键菜单,当我们在某个区域内单击鼠标右键,会弹出一些菜单项.这种类型的菜单,是随处可见的,我们在桌面上右击一下,也会弹出一个菜单. 右键菜单的好处就是方便,它经常和我们 ...

  8. GCC for Win32开发环境介绍

    GCC for Win32开发环境介绍(1) 第一章 在视窗操作系统下的GCC 第一节GCC家族概览 GCC是一个原本用于Unix-like系统下编程的编译器.不过,现在GCC也有了许多Win32下的 ...

  9. win32开发(对话框启动)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 有过mfc开发经验的朋友都知道,在mfc下面有三种app开发的方式,它分别是mdi开发.sdi开 ...

最新文章

  1. 并发、并行、串行、同步、异步、阻塞、非阻塞
  2. java cglib jar包_Java面试题|反射必看的4道面试题
  3. Field 'id' doesn't have a default value
  4. Stanford UFLDL教程 线性解码器
  5. 工业级以太网交换机与普通商用交换机相比,在性能上有哪些优势?
  6. 《FPGA全程进阶---实战演练》第二十一章 电源常用类型:LDO和 DCDC
  7. Qt笔记-使用正则表达式匹配URL及获取Get请求后面的参数(QRegExp)
  8. 制度化规范化标准化精细化_管理技巧:为什么说企业制度化管理势在必行?好处太多了...
  9. wait()和sleep()区别(常见面试题)
  10. 2021-2027全球与中国数控龙门镗铣床市场现状及未来发展趋势
  11. 入行GIS圈N年,看看资深GISer如何进行场景绘制?
  12. Win制作苹果IOS证书
  13. ISO18000-6C超高频RFID蓝牙写卡器HX9816UBT上位机命令数据块
  14. fai 自动安装debian 7.4
  15. ChatGpt:OpenAI 最近推出了一款聊天AI ——ChatGPT
  16. 如何实现图片转化为文字
  17. 神经网络入门(个人理解)
  18. 复选框checkbox自定义样式
  19. 第七章 项目成本管理
  20. linux执行lsof命令_Linux操作系统上lsof命令详解

热门文章

  1. 国内被广泛模仿的12个国外网站
  2. 虾皮电商选品时必须注意哪些是违禁品
  3. Python 协议攻击脚本(六): STP攻击
  4. 行走的Offer收割机,首次公布Java10W字面经,Github访问量破百万
  5. python替换word中的图片_python如何提取word内的图片
  6. John--解密工具
  7. 大数据元数据管理系统有哪些功能
  8. Q_INVOKABLE与invokeMethod用法详解
  9. iOS APP 启动页面的使用
  10. Javescript第二周学习