Win32开拓入门(1) 关于C++的几个要点
副标题#e#
我不知道列位,一提起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++中指针对应的对象呢?有,多得 是:
指南针。
手表。
电流表/电压表。
汽车上用来显示剩余汽油的表。
……
看看,这些物体都有什么配合特点?是不是都有一根可能多根指针?好比,下面图 片中的山寨手表。
#p#副标题#e#
此刻,我要你从山寨手表中读出其指示的时间,那么,你想一想 ,你会怎么看出来的
这个应该会看吧,小学生城市用了。我们会先看一下时针所指的偏向在哪 个时刻范畴内,如10-11之间,所以我们确定了是10点钟;然后,我们看到分针所指的是第二个刻度, 我们读出2,所以一组合,就是10点2。
是不是这样读,没读错吧?
像电流表也是这样, 我们就是通过指针所指的偏向找到对应的刻度,就知道当前电流是几多安/毫安,家用的应该是以毫安 为单元。
我们知道,措施的运行是存在内存中的,因此,我们在代码中声明的所有变量都是存 放在内存中的某块区域中,所以,在C语言中,指针用来汇报我们,某个变量在内存中的首地点在哪。 留意,是首地点,因为变量的长度不必然就是一个字节,有大概N多个字节,它在内存中是分列在一段 持续的区域块中。
#p#分页标题#e#
好比,某中学的解说楼,每个年级利用一栋楼,月朔年级在A栋,初二在B栋 ,初三在C栋。某学生在校其间,由于多次非礼女同学,老师说要见家长,于是,家长K君来到了某学校 ,但学校那么大,怎么找到K君的儿子地址的讲堂呢?这时候,保安人员汇报K君,初二年级在B栋,并 用手指着东北偏向(指针指向的内存地点块的位置)。
于是,K君顺着保安人员手指的偏向找到 了B栋,他知道他儿子在3班,而楼上的讲堂都是按顺序的,1班在第一个讲堂,2班在第二个讲堂,以此 类推。所以,K君很快就找到他儿子,然后把他教诲了两顿(会见或处理惩罚指针所指向内存中的数据), 然后,K君很郁闷地下楼,分开了学校(产生析构,清理内存)。
因此,我们可以对指针这样宝 义:
通过指针中存放的首地点,应用措施顺利地找到某个变量。就仿佛我最近认识了一位伴侣 ,他叫我有空去他家坐坐,然后,他留下了地点。某个周末我正闲着,突然想起这位伴侣,于是,我就 按照他留的地点去找他,功效,当我来到傻B街230号出租房时,内里走出一个我不认识的人,于是,我 问他我这位伴侣去哪了,生疏人说,我刚租了这屋子,你找的大概是前一位租户吧。
所以,指 针所指向的地点,有大概是变量B,也有大概是变量F,可能变量S,指针是房东,可以把屋子租给B,C ,或F,它可以动态为变量分派内存,也可以把变量销毁(delete),交不起房租就滚开(析构函数) 。
从上面的故事中,我们看到指针的两个用途:索引内存和分派内存。
看看下面这个例 子。
#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的首地点。
再看 看下面的代码:
#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;
为什么指针可以建设数组?前面我提到过,指针是指向首地点的,那么你想想,我们的数组如 果在堆上分派了内存,它们是不是也按必然序次存放在一块持续的内存地点中,整个数组同样组成了一 段内存块。
二、取地点标记&
许多书和教程都把这个标记叫引用,但我不喜欢翻译为引用,因为引用欠好领略,假如叫取地点符 ,那我预计你就会大白了,它就是返回一个变量的首地点。
#p#分页标题#e#
看看例子:
#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的地点。
那么,这样做有啥长处呢?我们 把上面的例子再扩展一下,酿成这样:
#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砖家只是想出一大堆与指针相关的莫名其妙的考 题,但很让人找不到指针在实际应用到底醒目什么,我预计那些砖家本身也不常识吧。所以,我们的考 试最大的失败,就是让学生不常识学了有什么用。
上面先容了指针可以存首地点,可以分派内 存,可以建设数组,还说了取地点符&,那么,这些对象有什么用呢?你必定会问,我直接声明一 个变量也是要占用内存的,那我为什么要吃饱了没事干还要用指针来存放首地点呢?
好,我先 不答复,我们再说说函数的参数通报。看看下面这样的例子。
#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的值照旧稳定呢 ?不消急,我们再把代码改一下:
#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)指针法。把参数改为指针范例 。
#include <stdio.h> void fn(int* x) { *x += 100; } void main() { int a = 20; fn(&a);//用取地点符来通报,因为指针是生存地点的 printf(" a : %d\n", a); getchar(); }
这里要留意,把变量传给指针范例的参数,要利用取地点符&。
那么,这次运行正确吗 ?
好了,终于看到想要的功效了。
(2)引用法,就是把参数改为& 通报的。
#include <stdio.h> void fn(int& x) { x += 100; } void main() { int a = 20; fn(a);//直接传变量名就行了 printf(" a : %d\n", a); getchar(); }
可以看到,这样的运行功效也是正确的。
四、指针与工具
#p#分页标题#e#
不管是类照旧布局(其实布局是一种非凡的类),它们在建设时照旧要建设内存的,可是,建设类 的工具也有两种方法,直接声明和用指针来分派新实例。
#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.。
void Func2() { Test* pt = new Test; pt -> Do("Func2"); delete pt; }
此刻看看,是不是在两个函数返回时,都可以或许销毁工具。
此刻你大白了吧?
由此,可以得出一个结论:指针只认真为工具分派和清理内存,并 不与内存中的工具实例有直接干系。
增补:
我只是为了说明,指针其实是一个数字,它 动态分派内存后,内里存的就是它指向的内存的首地点,可是,指针变量的生命周期终结后,并没有去 清理它所指向的内存,而需要显示利用delete。动态new完后,不要忘了delete,不要误解了,与 const* 不要紧。