1、人之所以痛苦,在于追求错误的东西。
2、与其说是别人让你痛苦,不如说自己的修养不够。
3、如果你不给自己烦恼,别人也永远不可能给你烦恼。因为你自己的 内心,你放不下。
4、好好的管教你自己,不要管别人。
5、不宽恕众生,不原谅众生,是苦了你自己。
6、别说别人可怜,自己更可怜,自己修行又如何?自己又懂得人生多少?
7、福报不够的人,就会常常听到是非;福报够的人,从来就没听到过是非。
8、你永远要感谢给你逆境的众生。
9、你永远要宽恕众生,不论他有多坏,甚至他伤害过你,你一定要放下,才能得到真正的快乐。
10、这个世界本来就是痛苦的,没有例外的。
11、当你快乐时,你要想,这快乐不是永恒的。当你痛苦时你要想这痛苦也不是永恒的。
12、认识自己,降伏自己,改变自己,才能改变别人。
13、不要浪费你的生命,在你一定会后悔的地方上。
14、你什么时候放下,什么时候就没有烦恼。
15、每一种创伤,都是一种成熟。
16、当你知道迷惑时,并不可怜, 当你不知道迷惑时,才是最可怜的。
17、狂妄的人有救,自卑的人没有救。
18、你不要一直不满人家,你应该一直检讨自己才对。不满人家,是苦了你自己。
19、你要包容那些意见跟你不同的人,这样子日子比较好过。你要是一直想改变他,那样子你会很痛苦。要学学怎样忍受他才是。你要学学怎样包容他才是。
20、承认自己的伟大,就是认同自己的愚疑。
21、一个人如果不能从内心去原谅别人,那他就永远不会心安理得。
22、心中装满着自己的看法与想法的人,永远听不见别人的心声。
23、毁灭人只要一句话,培植一个人却要千句话,请你多口下留情。
24、当你劝告别人时,若不顾及别人的自尊心,那么再好的言语都没有用的。
25、不要在你的智慧中夹杂着傲慢。不要使你的谦虚心缺乏智慧。
26、根本不必回头去看咒骂你的人是谁?如果有一条疯狗咬你一口,难道你也要趴下去反咬他一口吗?
27、忌妒别人,不会给自己增加任何的好处。忌妒别人,也不可能减少别人的成就。
28、永远不要浪费你的一分一秒,去想任何你不喜欢的人。
29、多少人要离开这个世间时,都会说出同一句话,这世界真是无奈与凄凉啊!
30、恋爱不是慈善事业,不能随便施舍的。感情是没有公式,没有原则,没有道理可循的。可是人们至死都还在执著与追求。
31、请你用慈悲心和温和的态度,把你的不满与委屈说出来,别人就容易接受。
32、创造机会的人是勇者。等待机会的人是愚者。
33、能说不能做,不是真智慧。
34、多用心去倾听别人怎么说,不要急着表达你自己的看法。
35、同样的瓶子,你为什么要装毒药呢?同样的心理,你为什么要充满着烦恼呢?
36、得不到的东西,我们会一直以为他是美好的,那是因为你对他了解太少,没有时间与他相处在一起。当有一天,你深入了解后,你会发现原不是你想像中的那么美好。
37、这个世间只有圆滑,没有圆满的。
38、活着一天,就是有福气,就该珍惜。当我哭泣我没有鞋子穿的时候,我发现有人却没有脚。
39、多一分心力去注意别人,就少一分心力反省自己,你懂吗?
40、欲知世上刀兵劫,但听屠门夜半声。不要光埋怨自己多病,灾祸横生,多看看横死在你刀下的众生又有多少?
41、憎恨别人对自己是一种很大的损失。
42、每一个人都拥有生命,但并非每个人都懂得生命,乃至于珍惜生命。不了解生命的人,生命对他来说,是一种惩罚。
43、自以为拥有财富的人,其实是被财富所拥有。
44、情执是苦恼的原因,放下情执,你才能得到自在。
45、随缘不是得过且过,因循苟且,而是尽人事听天命。
46、不要太肯定自己的看法,这样子比较少后悔。
47、当你对自己诚实的时候,世界上没有人能够欺骗得了你。
48、用伤害别人的手段来掩饰自己缺点的人,是可耻的。
49、内心充满忌妒,心中不坦白,言语不正的人,不能算是一位五官端正的人。
50、多讲点笑话,以幽默的态度处事,这样子日子会好过一点。
51、活在别人的掌声中,是禁不起考验的人。
52、不要刻意去猜测他人的想法,如果你没有智慧与经验的正确判断,通常都会有错误的。
53、要了解一个人,只需要看他的出发点与目的地是否相同,就可以知道他是否真心的。
54、人生的真理,只是藏在平淡无味之中。
55、不洗澡的人,硬擦香水是不会香的。名声与尊贵,是来自于真才实学的。有德自然香。
56、与其你去排斥它已成的事实,你不如去接受它。
57、逆境是成长必经的过程,能勇于接受逆境的人,生命就会日渐的茁壮。
58、你要感谢告诉你缺点的人。
59、能为别人设想的人,永远不寂寞。
60、如果你能像看别人缺点一样,如此准确般的发现自己的缺点,那么你的生命将会不平凡。
61、原谅别人,就是给自己心中留下空间,以便回旋。
62、时间总会过去的,让时间流走你的烦恼吧!
63、你硬要把单纯的事情看得很严重,那样子你会很痛苦。
64、永远扭曲别人善意的人,无药可救。
65、人不是坏的,只是习气罢了,每个人都有习气,只是深浅不同罢了。只要他有向善的心,能原谅的就原谅他,不要把他看做是坏人。
66、说一句谎话,要编造十句谎话来弥补,何苦呢?
67、其实爱美的人,只是与自己谈恋爱罢了。
68、世界上没有一个永远不被毁谤的人,也没有一个永远被赞叹的人。当你话多的时候,别人要批评你,当你话少的时候,别人要批评你,当你沈默的时候,别人还是要批评你。在这个世界上,没有一个人不被批评的。
69、夸奖我们,赞叹我们的,这都不是名师。会讲我们,指示我们的,这才是良师,有了他们我们才会进步。
70、你目前所拥有的都将随着你的死亡而成为他人的,那为何不现在就乐施给真正需要的人呢?
71、白白的过一天,无所事事,就像犯了窃盗罪一样。
72、沈默是毁谤最好的答覆。
73、对人恭敬,就是在庄严你自己。
74、拥有一颗无私的爱心,便拥有了一切。
75、仇恨永远不能化解仇恨,只有宽容才能化解仇恨,这是永恒的至理。
76、你接受比抱怨还要好,对于不可改变的事实,你除了接受以外,没有更好的办法了。
77、不要因为众生的愚疑,而带来了自己的烦恼。不要因为众生的无知,而痛苦了你自己。
78、别人讲我们不好,不用生气、难过。说我们好也不用高兴,这不好中有好,好中有坏,就看你会不会用?
79、当你的错误显露时,可不要发脾气,别以为任性或吵闹,可以隐藏或克服你的缺点。
80、不要常常觉得自己很不幸,世界上比我们痛苦的人还要多。
81、愚痴的人,一直想要别人了解他。有智慧的人,却努力的了解自己。
82、来是偶然的,走是必然的。所以你必须,随缘不变,不变随缘。 83、只要面对现实,你才能超越现实。
84、良心是每一个人最公正的审判官,你骗得了别人,却永远骗不了你自己的良心。
85、不懂得自爱的人,是没有能力去爱别人的。
86、做事就是在学做人而已。
87、有时候我们要冷静问问自已,我们在追求什么?我们活着为了什么?
88、不要因为小小的争执,远离了你至亲的好友,也不要因为小小的怨恨,忘记了别人的大恩。
89、勇于接受别人的批评,正好可以调整自己的缺点。
90、感谢上天我所拥有的,感谢上天我所没有的。
91、说话不要有攻击性,不要有杀伤力,不夸已能,不扬人恶,自然能化敌为友。
92、一个常常看别人缺点的人,自己本身就不够好,因为他没有时间检讨他自己。
93、是非天天有,不听自然无,是非天天有,不听还是有,是非天天有,看你怎么办?
94、如果你真的爱他,那么你必须容忍他部份的缺点。
95、要克服对死亡的恐惧,你必须要接受世上所有的人,都会死去的观念。
96、虽然你讨厌一个人,但却又能发觉他的优点好处,像这样子有修养的人,天下真是太少了。
97、诚实的面对你内心的矛盾和污点,不要欺骗你自己。
98、因果不曾亏欠过我们什么,所以请不要抱怨。
99、我们确实有如是的优点,但也要隐藏几分,这个叫做涵养。
100、大多数的人一辈子只做了三件事;自欺、欺人、被人欺。
101、太过于欣赏自己的人,不会去欣赏别人的优点。
102、心是最大的骗子,别人能骗你一时,而它却会骗你一辈子。
103、当你手中抓住一件东西不放时,你只能拥有这件东西,如果你肯放手,你就有机会选择别的。人的心若死执自己的观念,不肯放下,那么他的智慧也只能达到某种程度而已。
104、人家怕你,并不是一种福,人家欺你,并不是一种辱。
105、不是某人使我烦恼,而是我拿某人的言行来烦恼自己。
106、不要刻意去曲解别人的善意,你应当往好的地方想。
107、世上的事,不如己意者,那是当然的。
108、我的财富并不是因为我拥有很多,而是我要求的很少。
109、吃了就一定要拉,人一定要学会随缘放下,否则就会?便秘。
110、常以为别人在注意你,或希望别人注意你的人,会生活的比较烦恼。
111、我能为你煮东西,但我不能为你吃东西。各人吃饭是各人饱,各人生死是个人了。
112、看轻别人很容易,要摆平自己却很困难。
113、你只管活你自己的,不必去介意别人的扭曲与是非。
114、如果你准备结婚的话,告诉你一句非常重要的哲学名言,你一定要忍耐包容对方的缺点,世界上没有绝对幸福圆满的婚姻,幸福只是来自于无限的容忍与互相尊重。
115、如果你能够平平安安的渡过一天,那就是一种福气了。多少人在今天已经见不到明天的太阳,多少人在今天已经成了残废,多少人在今天已经失去了自由,多少人在今天已经家破人亡。
116、是非和得失,要到最后的结果,才能评定。
117、你不必和因果争吵,因果从来就不会误人。你也不必和命运争吵,命运它是最公平的审判官。
118、你有你的生命观,我有我的生命观,我不干涉你。只要我能,我就感化你。如果不能,那我就认命。
119、你希望掌握永恒,那你必须控制现在。
120、恶口永远不要出自于我们的口中,不管他有多坏,有多恶。你愈骂他,你的心就被污染了,你要想,他就是你的善知识。
121、当你明天开始生活的时候,有人跟你争执,你就让他赢,这个赢跟输,都只是文字的观念罢了。当你让对方赢,你并没有损失什么。所谓的赢,他有赢到什么?得到什么?所谓的输,你又输到什么?失去什么?
122、我们大部份的生命都浪费在文字语言的捉摸上。
123、你不要常常觉得自己很委曲,你应该要想,他对我这样已经很好了,这就是修行的功夫。
124、别人可以违背因果,别人可以害我们,打我们,毁谤我们。可是我们不能因此而憎恨别人,为什么?我们一定要保有一颗完整的 本性和一颗清净的心。
125、与任何人接触时,要常常问自己,我有什么对他有用?使他得益。如果我不能以个人的道德、学问和修持的力量,来使人受益,就等于欠了一份债。
126、如果一个人没有苦难的感受,就不容易对他人给予同情。你要学救苦救难的精神,就得先受苦受难。
127、一般人在遇到对方的权势大,财富大,气力大,在无可奈何的情形之下而忍,这算什么忍耐呢?真正的忍是,就算他欺负了你,对不住你,但他什么都不及你,你有足够的力量对付他,而你却能容忍他,认为他的本性和我一样,只是一时糊涂,或在恶劣的环境中受到熏染罢了,你不必与他计较,能在这样的情况及心境之下容忍那才是真正的忍耐。
128、如果我们放眼从累生历劫去看,那么一切的众生,谁不曾做过我的父母、兄弟姊妹、亲戚眷属?谁不曾做过我的仇敌冤家?如果说有恩,个个与我有恩;如果说有冤,个个与我有冤。这样子我们还有什么恩怨亲疏之别呢?再就智慧愚笨来说,人人有聪明的时候,也有愚痴的时候,聪明的人可能变愚痴,愚痴的人也可能变聪明。最坏的人,也曾做过许多好事,而且不会永远坏;好人也曾做过许多坏事,将来也不一定会好。如此我们反覆思索,所谓的冤亲、贤愚,这许多差别的概念,自然就会渐渐淡了。这绝对不是混沌,也不是不知好坏,而是要将我们无始以来的偏私差别之见,以一视同仁的平等观念罢了!
129、世界原本就不是属于你,因此你用不着抛弃,要抛弃的是一切的执著。万物皆为我所用,但非我所属。
130、宁可自己去原谅别人,莫让别人来原谅你。
131、当你用烦恼心来面对事物时,你会觉得一切都是业障,世界也会变得丑陋可恨。
132、欲为诸佛龙象,先做众生马牛。
133、虽然我们不能改变周遭的世界,我们就只好改变自己,用爱心和智慧来面对这一切.
2、与其说是别人让你痛苦,不如说自己的修养不够。
3、如果你不给自己烦恼,别人也永远不可能给你烦恼。因为你自己的 内心,你放不下。
4、好好的管教你自己,不要管别人。
5、不宽恕众生,不原谅众生,是苦了你自己。
6、别说别人可怜,自己更可怜,自己修行又如何?自己又懂得人生多少?
7、福报不够的人,就会常常听到是非;福报够的人,从来就没听到过是非。
8、你永远要感谢给你逆境的众生。
9、你永远要宽恕众生,不论他有多坏,甚至他伤害过你,你一定要放下,才能得到真正的快乐。
10、这个世界本来就是痛苦的,没有例外的。
11、当你快乐时,你要想,这快乐不是永恒的。当你痛苦时你要想这痛苦也不是永恒的。
12、认识自己,降伏自己,改变自己,才能改变别人。
13、不要浪费你的生命,在你一定会后悔的地方上。
14、你什么时候放下,什么时候就没有烦恼。
15、每一种创伤,都是一种成熟。
16、当你知道迷惑时,并不可怜, 当你不知道迷惑时,才是最可怜的。
17、狂妄的人有救,自卑的人没有救。
18、你不要一直不满人家,你应该一直检讨自己才对。不满人家,是苦了你自己。
19、你要包容那些意见跟你不同的人,这样子日子比较好过。你要是一直想改变他,那样子你会很痛苦。要学学怎样忍受他才是。你要学学怎样包容他才是。
20、承认自己的伟大,就是认同自己的愚疑。
21、一个人如果不能从内心去原谅别人,那他就永远不会心安理得。
22、心中装满着自己的看法与想法的人,永远听不见别人的心声。
23、毁灭人只要一句话,培植一个人却要千句话,请你多口下留情。
24、当你劝告别人时,若不顾及别人的自尊心,那么再好的言语都没有用的。
25、不要在你的智慧中夹杂着傲慢。不要使你的谦虚心缺乏智慧。
26、根本不必回头去看咒骂你的人是谁?如果有一条疯狗咬你一口,难道你也要趴下去反咬他一口吗?
27、忌妒别人,不会给自己增加任何的好处。忌妒别人,也不可能减少别人的成就。
28、永远不要浪费你的一分一秒,去想任何你不喜欢的人。
29、多少人要离开这个世间时,都会说出同一句话,这世界真是无奈与凄凉啊!
30、恋爱不是慈善事业,不能随便施舍的。感情是没有公式,没有原则,没有道理可循的。可是人们至死都还在执著与追求。
31、请你用慈悲心和温和的态度,把你的不满与委屈说出来,别人就容易接受。
32、创造机会的人是勇者。等待机会的人是愚者。
33、能说不能做,不是真智慧。
34、多用心去倾听别人怎么说,不要急着表达你自己的看法。
35、同样的瓶子,你为什么要装毒药呢?同样的心理,你为什么要充满着烦恼呢?
36、得不到的东西,我们会一直以为他是美好的,那是因为你对他了解太少,没有时间与他相处在一起。当有一天,你深入了解后,你会发现原不是你想像中的那么美好。
37、这个世间只有圆滑,没有圆满的。
38、活着一天,就是有福气,就该珍惜。当我哭泣我没有鞋子穿的时候,我发现有人却没有脚。
39、多一分心力去注意别人,就少一分心力反省自己,你懂吗?
40、欲知世上刀兵劫,但听屠门夜半声。不要光埋怨自己多病,灾祸横生,多看看横死在你刀下的众生又有多少?
41、憎恨别人对自己是一种很大的损失。
42、每一个人都拥有生命,但并非每个人都懂得生命,乃至于珍惜生命。不了解生命的人,生命对他来说,是一种惩罚。
43、自以为拥有财富的人,其实是被财富所拥有。
44、情执是苦恼的原因,放下情执,你才能得到自在。
45、随缘不是得过且过,因循苟且,而是尽人事听天命。
46、不要太肯定自己的看法,这样子比较少后悔。
47、当你对自己诚实的时候,世界上没有人能够欺骗得了你。
48、用伤害别人的手段来掩饰自己缺点的人,是可耻的。
49、内心充满忌妒,心中不坦白,言语不正的人,不能算是一位五官端正的人。
50、多讲点笑话,以幽默的态度处事,这样子日子会好过一点。
51、活在别人的掌声中,是禁不起考验的人。
52、不要刻意去猜测他人的想法,如果你没有智慧与经验的正确判断,通常都会有错误的。
53、要了解一个人,只需要看他的出发点与目的地是否相同,就可以知道他是否真心的。
54、人生的真理,只是藏在平淡无味之中。
55、不洗澡的人,硬擦香水是不会香的。名声与尊贵,是来自于真才实学的。有德自然香。
56、与其你去排斥它已成的事实,你不如去接受它。
57、逆境是成长必经的过程,能勇于接受逆境的人,生命就会日渐的茁壮。
58、你要感谢告诉你缺点的人。
59、能为别人设想的人,永远不寂寞。
60、如果你能像看别人缺点一样,如此准确般的发现自己的缺点,那么你的生命将会不平凡。
61、原谅别人,就是给自己心中留下空间,以便回旋。
62、时间总会过去的,让时间流走你的烦恼吧!
63、你硬要把单纯的事情看得很严重,那样子你会很痛苦。
64、永远扭曲别人善意的人,无药可救。
65、人不是坏的,只是习气罢了,每个人都有习气,只是深浅不同罢了。只要他有向善的心,能原谅的就原谅他,不要把他看做是坏人。
66、说一句谎话,要编造十句谎话来弥补,何苦呢?
67、其实爱美的人,只是与自己谈恋爱罢了。
68、世界上没有一个永远不被毁谤的人,也没有一个永远被赞叹的人。当你话多的时候,别人要批评你,当你话少的时候,别人要批评你,当你沈默的时候,别人还是要批评你。在这个世界上,没有一个人不被批评的。
69、夸奖我们,赞叹我们的,这都不是名师。会讲我们,指示我们的,这才是良师,有了他们我们才会进步。
70、你目前所拥有的都将随着你的死亡而成为他人的,那为何不现在就乐施给真正需要的人呢?
71、白白的过一天,无所事事,就像犯了窃盗罪一样。
72、沈默是毁谤最好的答覆。
73、对人恭敬,就是在庄严你自己。
74、拥有一颗无私的爱心,便拥有了一切。
75、仇恨永远不能化解仇恨,只有宽容才能化解仇恨,这是永恒的至理。
76、你接受比抱怨还要好,对于不可改变的事实,你除了接受以外,没有更好的办法了。
77、不要因为众生的愚疑,而带来了自己的烦恼。不要因为众生的无知,而痛苦了你自己。
78、别人讲我们不好,不用生气、难过。说我们好也不用高兴,这不好中有好,好中有坏,就看你会不会用?
79、当你的错误显露时,可不要发脾气,别以为任性或吵闹,可以隐藏或克服你的缺点。
80、不要常常觉得自己很不幸,世界上比我们痛苦的人还要多。
81、愚痴的人,一直想要别人了解他。有智慧的人,却努力的了解自己。
82、来是偶然的,走是必然的。所以你必须,随缘不变,不变随缘。 83、只要面对现实,你才能超越现实。
84、良心是每一个人最公正的审判官,你骗得了别人,却永远骗不了你自己的良心。
85、不懂得自爱的人,是没有能力去爱别人的。
86、做事就是在学做人而已。
87、有时候我们要冷静问问自已,我们在追求什么?我们活着为了什么?
88、不要因为小小的争执,远离了你至亲的好友,也不要因为小小的怨恨,忘记了别人的大恩。
89、勇于接受别人的批评,正好可以调整自己的缺点。
90、感谢上天我所拥有的,感谢上天我所没有的。
91、说话不要有攻击性,不要有杀伤力,不夸已能,不扬人恶,自然能化敌为友。
92、一个常常看别人缺点的人,自己本身就不够好,因为他没有时间检讨他自己。
93、是非天天有,不听自然无,是非天天有,不听还是有,是非天天有,看你怎么办?
94、如果你真的爱他,那么你必须容忍他部份的缺点。
95、要克服对死亡的恐惧,你必须要接受世上所有的人,都会死去的观念。
96、虽然你讨厌一个人,但却又能发觉他的优点好处,像这样子有修养的人,天下真是太少了。
97、诚实的面对你内心的矛盾和污点,不要欺骗你自己。
98、因果不曾亏欠过我们什么,所以请不要抱怨。
99、我们确实有如是的优点,但也要隐藏几分,这个叫做涵养。
100、大多数的人一辈子只做了三件事;自欺、欺人、被人欺。
101、太过于欣赏自己的人,不会去欣赏别人的优点。
102、心是最大的骗子,别人能骗你一时,而它却会骗你一辈子。
103、当你手中抓住一件东西不放时,你只能拥有这件东西,如果你肯放手,你就有机会选择别的。人的心若死执自己的观念,不肯放下,那么他的智慧也只能达到某种程度而已。
104、人家怕你,并不是一种福,人家欺你,并不是一种辱。
105、不是某人使我烦恼,而是我拿某人的言行来烦恼自己。
106、不要刻意去曲解别人的善意,你应当往好的地方想。
107、世上的事,不如己意者,那是当然的。
108、我的财富并不是因为我拥有很多,而是我要求的很少。
109、吃了就一定要拉,人一定要学会随缘放下,否则就会?便秘。
110、常以为别人在注意你,或希望别人注意你的人,会生活的比较烦恼。
111、我能为你煮东西,但我不能为你吃东西。各人吃饭是各人饱,各人生死是个人了。
112、看轻别人很容易,要摆平自己却很困难。
113、你只管活你自己的,不必去介意别人的扭曲与是非。
114、如果你准备结婚的话,告诉你一句非常重要的哲学名言,你一定要忍耐包容对方的缺点,世界上没有绝对幸福圆满的婚姻,幸福只是来自于无限的容忍与互相尊重。
115、如果你能够平平安安的渡过一天,那就是一种福气了。多少人在今天已经见不到明天的太阳,多少人在今天已经成了残废,多少人在今天已经失去了自由,多少人在今天已经家破人亡。
116、是非和得失,要到最后的结果,才能评定。
117、你不必和因果争吵,因果从来就不会误人。你也不必和命运争吵,命运它是最公平的审判官。
118、你有你的生命观,我有我的生命观,我不干涉你。只要我能,我就感化你。如果不能,那我就认命。
119、你希望掌握永恒,那你必须控制现在。
120、恶口永远不要出自于我们的口中,不管他有多坏,有多恶。你愈骂他,你的心就被污染了,你要想,他就是你的善知识。
121、当你明天开始生活的时候,有人跟你争执,你就让他赢,这个赢跟输,都只是文字的观念罢了。当你让对方赢,你并没有损失什么。所谓的赢,他有赢到什么?得到什么?所谓的输,你又输到什么?失去什么?
122、我们大部份的生命都浪费在文字语言的捉摸上。
123、你不要常常觉得自己很委曲,你应该要想,他对我这样已经很好了,这就是修行的功夫。
124、别人可以违背因果,别人可以害我们,打我们,毁谤我们。可是我们不能因此而憎恨别人,为什么?我们一定要保有一颗完整的 本性和一颗清净的心。
125、与任何人接触时,要常常问自己,我有什么对他有用?使他得益。如果我不能以个人的道德、学问和修持的力量,来使人受益,就等于欠了一份债。
126、如果一个人没有苦难的感受,就不容易对他人给予同情。你要学救苦救难的精神,就得先受苦受难。
127、一般人在遇到对方的权势大,财富大,气力大,在无可奈何的情形之下而忍,这算什么忍耐呢?真正的忍是,就算他欺负了你,对不住你,但他什么都不及你,你有足够的力量对付他,而你却能容忍他,认为他的本性和我一样,只是一时糊涂,或在恶劣的环境中受到熏染罢了,你不必与他计较,能在这样的情况及心境之下容忍那才是真正的忍耐。
128、如果我们放眼从累生历劫去看,那么一切的众生,谁不曾做过我的父母、兄弟姊妹、亲戚眷属?谁不曾做过我的仇敌冤家?如果说有恩,个个与我有恩;如果说有冤,个个与我有冤。这样子我们还有什么恩怨亲疏之别呢?再就智慧愚笨来说,人人有聪明的时候,也有愚痴的时候,聪明的人可能变愚痴,愚痴的人也可能变聪明。最坏的人,也曾做过许多好事,而且不会永远坏;好人也曾做过许多坏事,将来也不一定会好。如此我们反覆思索,所谓的冤亲、贤愚,这许多差别的概念,自然就会渐渐淡了。这绝对不是混沌,也不是不知好坏,而是要将我们无始以来的偏私差别之见,以一视同仁的平等观念罢了!
129、世界原本就不是属于你,因此你用不着抛弃,要抛弃的是一切的执著。万物皆为我所用,但非我所属。
130、宁可自己去原谅别人,莫让别人来原谅你。
131、当你用烦恼心来面对事物时,你会觉得一切都是业障,世界也会变得丑陋可恨。
132、欲为诸佛龙象,先做众生马牛。
133、虽然我们不能改变周遭的世界,我们就只好改变自己,用爱心和智慧来面对这一切.
[实践OK]PHP7的session设置问题只在php.ini里设置不生效,得在php-fpm.conf里包含文件里设置,php.ini配置,php session默认存放位置,php.ini 里面的 session.save_path。在PHP7里修改php.ini不行的配置方法。
Php/Js/Shell/Go jackxiang 2007-11-15 18:10
PHP7的session设置问题只在php.ini里设置不生效,得在php-fpm.conf里包含文件里设置:
cat etc/php-fpm.d/www.conf
php_value[session.save_handler] = files
php_value[session.save_path] = /data/tmp/session
来自:https://typecodes.com/web/php7configure.html
===========================================================
php session默认存放位置,php.ini 里面的 session.save_path
采用的是php的缓存。这部分可以自己写,例如把session放到某个cache服务中(redis,memcached)
参考:http://www.php.net/manual/zh/book.session.php
請問各位 php 前輩,有無可能用 include 或 require 整個目錄下的檔案
例:
require(models/survey/SurveyItem.inc');
改成
require(models/survey/');
另外請問一下,同一 php 檔案下是否不能出現 2次以上
相同的 function?
例:
在一php檔裡有 session_start ,而 require的檔案裡 也有 session_start
, 在我local 端的server裡,跑起來會出現
Notice: A session had already been started - ignoring session_start() in C:\xxxxxxxxx.php on line 43
但在虛擬主機上又很正常,為何會如此呢?
感謝~~
在XP系统下,我配置了session
如下:
session.save_path = "C:/temp"
session.auto_start = 1
将安全等级调低,在php.ini中
作者: freelin 发布日期: 2004-5-26
之前我从来没设过安全等级,请问是哪一选项?谢
作者: suse 发布日期: 2004-5-26
php.ini中error_reporting = E_ALL & ~E_NOTICE
或在程序开头加上:
error_reporting(E_ALL & ~E_NOTICE);
还不行还是你配置有问题。。。
cat etc/php-fpm.d/www.conf
php_value[session.save_handler] = files
php_value[session.save_path] = /data/tmp/session
来自:https://typecodes.com/web/php7configure.html
===========================================================
php session默认存放位置,php.ini 里面的 session.save_path
采用的是php的缓存。这部分可以自己写,例如把session放到某个cache服务中(redis,memcached)
参考:http://www.php.net/manual/zh/book.session.php
請問各位 php 前輩,有無可能用 include 或 require 整個目錄下的檔案
例:
require(models/survey/SurveyItem.inc');
改成
require(models/survey/');
另外請問一下,同一 php 檔案下是否不能出現 2次以上
相同的 function?
例:
在一php檔裡有 session_start ,而 require的檔案裡 也有 session_start
, 在我local 端的server裡,跑起來會出現
Notice: A session had already been started - ignoring session_start() in C:\xxxxxxxxx.php on line 43
但在虛擬主機上又很正常,為何會如此呢?
感謝~~
在XP系统下,我配置了session
如下:
session.save_path = "C:/temp"
session.auto_start = 1
将安全等级调低,在php.ini中
作者: freelin 发布日期: 2004-5-26
之前我从来没设过安全等级,请问是哪一选项?谢
作者: suse 发布日期: 2004-5-26
php.ini中error_reporting = E_ALL & ~E_NOTICE
或在程序开头加上:
error_reporting(E_ALL & ~E_NOTICE);
还不行还是你配置有问题。。。
短信息:
总记录数 874
免费短信记录数 300
按主被叫分类
主叫通话 15次 费用合计 4.32元
被叫通话 50次 费用合计 53.25元
按通话地点分类
本地通话 50次 费用合计 32.70元
漫游通话 15次 费用合计 24.87元
结论:被叫电话太多,要免费接听的。。。。
短息条数太多,估计以后会少一些。。。。。下个月继续跟踪!
1.不成熟的男人总是在意女人的姿色;成熟的男人则很会看老婆的脸色。
2.不成熟的男人总是在太太面前轻许承诺;成熟的男人则会在太太面前兜售甜言蜜语。
3.不成熟的男人和太太一起上街时,总是捂紧自己的腰包,那能捂得住吗?而成熟的男人却是闭紧自己的眼睛,正所谓眼不见心不烦。
4.发了奖金以后,不成熟的男人会将钱悄悄塞进衣兜里,一旦被老婆发现就会死得很惨;成熟的男人则 用老婆的名字存在银行里,不被发现当然是好事,万一不小心被发现也不是什么坏事,老婆一定会爱死你。
5.不成熟的男人会满脸笑容地陪老婆逛商场;成熟的男人只有和老婆一起逛菜市的时候才会精神百倍。
6.不成熟的男人一和美女有了眼神的碰撞就会害羞得满脸通红;成熟的男人总会把美女逗得满脸通红。
7.不成熟的男人给太太写完检讨就会把它们全都扔进垃圾桶;成熟的男人则会把老婆检验合格的检讨书寄到杂志社。
8.不成熟的男人会在任何人面前酗酒,包括老婆,一不小心就酒后吐真言,其结局悲惨不堪;成熟的男人无论如何也不会在太太面前喝太多的酒,这样可以守口如瓶,心中的秘密永远也不会见光。
9.不成熟的男人最爱唱情歌;成熟的男人不仅会唱情歌,而且更会唱赞歌。
10.不成熟的男人希望自己的老婆擅长往脸上抹粉(化妆)——看上去养眼,舒服;成熟的男人做梦都想自己的老婆爱揉面粉(做家务)。
2.不成熟的男人总是在太太面前轻许承诺;成熟的男人则会在太太面前兜售甜言蜜语。
3.不成熟的男人和太太一起上街时,总是捂紧自己的腰包,那能捂得住吗?而成熟的男人却是闭紧自己的眼睛,正所谓眼不见心不烦。
4.发了奖金以后,不成熟的男人会将钱悄悄塞进衣兜里,一旦被老婆发现就会死得很惨;成熟的男人则 用老婆的名字存在银行里,不被发现当然是好事,万一不小心被发现也不是什么坏事,老婆一定会爱死你。
5.不成熟的男人会满脸笑容地陪老婆逛商场;成熟的男人只有和老婆一起逛菜市的时候才会精神百倍。
6.不成熟的男人一和美女有了眼神的碰撞就会害羞得满脸通红;成熟的男人总会把美女逗得满脸通红。
7.不成熟的男人给太太写完检讨就会把它们全都扔进垃圾桶;成熟的男人则会把老婆检验合格的检讨书寄到杂志社。
8.不成熟的男人会在任何人面前酗酒,包括老婆,一不小心就酒后吐真言,其结局悲惨不堪;成熟的男人无论如何也不会在太太面前喝太多的酒,这样可以守口如瓶,心中的秘密永远也不会见光。
9.不成熟的男人最爱唱情歌;成熟的男人不仅会唱情歌,而且更会唱赞歌。
10.不成熟的男人希望自己的老婆擅长往脸上抹粉(化妆)——看上去养眼,舒服;成熟的男人做梦都想自己的老婆爱揉面粉(做家务)。
分手后不能做朋友
分手后不能做朋友。我选择做陌生人的理由是:既然分手了,就不要再有暧昧的关系,那样只会让自己伤心,难受,进也不是,退也不是,既然分手了,就彻底的退出,快乐的生活,开心是现在唯一的追求。我只能对她说句对不起,不管是什么原因,分手了就分手了,既然我们不能再相互爱下去,就让我们做陌生人吧
分手后不能做朋友.如果你曾经深深的爱过这个人,她曾经是你生命中不可分割的一部分,那么怎么去转换角色,才能若无其事的把她看成是一个朋友?这对大多数人来说,恐怕都是难以做到的。
分手后不能做朋友。如果说分手后,还会保持联系,这只能说明有一方还没真正放下对方,她还不愿从对方的生活中彻底消失,所以会找各种看似很冠冕堂皇的理由去接近对方。可是,扪心自问,如果你们是真正的爱过一场的话,如果他真的对不起你,真的能做成朋友?NO,因为现实是残酷的,并没有我们想象的那么多姿多彩。
分手后不能做朋友。假设双方分手之后都另结新欢,你却仍以旧爱做朋友,新情人心理怎么想。谁不认为你与旧爱是藕断丝连呢?新欢必然心中不满,更怕在拉拉扯扯中搞出一个对角关系来。虽然也有一些相安无事的例子,但这些故事总不免另人听出一丝尴尬来。当你看到旧情人,你又怎能忘记曾与他同甘共苦一起走过的日子?除非未曾刻骨铭心。
分手后不能做朋友。看着自己以前的恋人,与别人亲亲我我、欢天喜地,你心里真的就那么平衡吗?人都是有尊严的,何必自找苦吃呢?还是早点脱离苦海,把它封在心里,怀念,或者扔掉它,重新过自己的生活这才是明智之举!
分手后不能做朋友。分手了,不做朋友是为了不再给对方希望,不给任何机会,这个人没爱过你或者已经不再爱你,再抱着希望简直是一种酷刑,放手,反而是大家解脱的最好办法,而做为任何一方,既然没有了爱,也就没有必要苦恋不舍了。所以,既然没有缘分一路同行的走下去,那就彻底分开吧,默默的祝福你,但是我的生活中,已经没有你。
分手后不能做朋友。分手之后仍要做一对朋友,岂不是把过去的一切又带回自己的生活中?何苦呢?歌里就这样唱到:“日复一日,年复一年,淡忘了原初的伤疤,只是一回想起来,还是有一丝隐隐的酸楚在脑海里残留着,久久不能散去。曾经挚爱的人转眼间已成陌路,那份爱也已经随风而逝,说是可以忘记,可生活就象表演,不经意间的相遇,看着她远去的背影往昔一样在脑海间漂动,那种伤是难以语表的。慢慢发现,激情与浪漫已渐渐远去,难以再爱。 ”
是啊,有时候感情就像一场游戏,但也并不是谁都愿意这么做。人这一辈子记得最多的应该是最快乐的那段日子,即使有伤害和痛苦,也不会为此后悔过,人不能后悔,后悔就代表否定自己,否定自己那就不可能快乐。也许你的收获仅仅是一些支离破碎的记忆,并不如你想象的那么美好,有一个美满快乐的结局,但起码自己曾经那么的珍惜和努力过,足矣!
分手后不能做朋友。我选择做陌生人的理由是:既然分手了,就不要再有暧昧的关系,那样只会让自己伤心,难受,进也不是,退也不是,既然分手了,就彻底的退出,快乐的生活,开心是现在唯一的追求。我只能对她说句对不起,不管是什么原因,分手了就分手了,既然我们不能再相互爱下去,就让我们做陌生人吧
分手后不能做朋友.如果你曾经深深的爱过这个人,她曾经是你生命中不可分割的一部分,那么怎么去转换角色,才能若无其事的把她看成是一个朋友?这对大多数人来说,恐怕都是难以做到的。
分手后不能做朋友。如果说分手后,还会保持联系,这只能说明有一方还没真正放下对方,她还不愿从对方的生活中彻底消失,所以会找各种看似很冠冕堂皇的理由去接近对方。可是,扪心自问,如果你们是真正的爱过一场的话,如果他真的对不起你,真的能做成朋友?NO,因为现实是残酷的,并没有我们想象的那么多姿多彩。
分手后不能做朋友。假设双方分手之后都另结新欢,你却仍以旧爱做朋友,新情人心理怎么想。谁不认为你与旧爱是藕断丝连呢?新欢必然心中不满,更怕在拉拉扯扯中搞出一个对角关系来。虽然也有一些相安无事的例子,但这些故事总不免另人听出一丝尴尬来。当你看到旧情人,你又怎能忘记曾与他同甘共苦一起走过的日子?除非未曾刻骨铭心。
分手后不能做朋友。看着自己以前的恋人,与别人亲亲我我、欢天喜地,你心里真的就那么平衡吗?人都是有尊严的,何必自找苦吃呢?还是早点脱离苦海,把它封在心里,怀念,或者扔掉它,重新过自己的生活这才是明智之举!
分手后不能做朋友。分手了,不做朋友是为了不再给对方希望,不给任何机会,这个人没爱过你或者已经不再爱你,再抱着希望简直是一种酷刑,放手,反而是大家解脱的最好办法,而做为任何一方,既然没有了爱,也就没有必要苦恋不舍了。所以,既然没有缘分一路同行的走下去,那就彻底分开吧,默默的祝福你,但是我的生活中,已经没有你。
分手后不能做朋友。分手之后仍要做一对朋友,岂不是把过去的一切又带回自己的生活中?何苦呢?歌里就这样唱到:“日复一日,年复一年,淡忘了原初的伤疤,只是一回想起来,还是有一丝隐隐的酸楚在脑海里残留着,久久不能散去。曾经挚爱的人转眼间已成陌路,那份爱也已经随风而逝,说是可以忘记,可生活就象表演,不经意间的相遇,看着她远去的背影往昔一样在脑海间漂动,那种伤是难以语表的。慢慢发现,激情与浪漫已渐渐远去,难以再爱。 ”
是啊,有时候感情就像一场游戏,但也并不是谁都愿意这么做。人这一辈子记得最多的应该是最快乐的那段日子,即使有伤害和痛苦,也不会为此后悔过,人不能后悔,后悔就代表否定自己,否定自己那就不可能快乐。也许你的收获仅仅是一些支离破碎的记忆,并不如你想象的那么美好,有一个美满快乐的结局,但起码自己曾经那么的珍惜和努力过,足矣!
su :切换并取代该用户的身份
执行范例:
1>切换到root
[lammy198@localhost lammy198]$ su - //可以写成su - root
Password: //输入密码
[root@localhost root]#
2>切换到lammy198
由root切换,则不需要输入密码
[root@localhost root]# su - lammy198
[lammy198@localhost lammy198]$
不是由root切换,则需要输入密码
[lammy@localhost lammy]$ su - lammy198 //不可以写成su -
Password: //输入密码
[lammy198@localhost lammy198]$
adduser :新建系统上的用户
[root@localhost root]# adduser -D //显示建立帐号时的默认值
GROUP=100 //所属主组的ID
HOME=/home //用户的根目录
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash //所使用的SHELL
SKEL=/etc/skel //所应用的设置文件
创建lammyt的帐号
[root@localhost root]# adduser -m lammyt
[root@localhost root]# id lammyt //为什么groups=502而不是100?
uid=502(lammyt) gid=502(lammyt) groups=502(lammyt)
userdel :删除帐号
[root@localhost root]# userdel lammyt
[root@localhost root]# id lammyt
id: lammyt: No such user
useradd :新建帐号
[root@localhost root]# useradd lammyt
[root@localhost root]# id lammyt
uid=504(lammyt) gid=504(lammyt) groups=504(lammyt)
who :显示登陆当前登陆用户的信息
[root@localhost root]# who
root :0 Sep 16 18:05
root pts/2 Sep 21 12:16 (:0.0)
id :显示拥护组的ID
[root@localhost root]# id -a //显示用户的帐户信息
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
ps :显示当前系统中由该用户运行的进程列表
[root@localhost root]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:38 ? 00:00:06 init
root 2 1 0 14:38 ? 00:00:00 [keventd]
root 3 1 0 14:38 ? 00:00:00 [kapmd]
root 4 1 0 14:38 ? 00:00:00 [ksoftirqd_CPU0]
root 9 1 0 14:38 ? 00:00:00 [bdflush]
root 5 1 0 14:38 ? 00:00:00 [kswapd]
root 6 1 0 14:38 ? 00:00:00 [kscand/DMA]
free :查看当前系统内存的使用情况
[root@localhost root]# free
total used free shared buffers cached
Mem: 412856 402316 10540 0 35908 218360
-/+ buffers/cache: 148048 264808
Swap: 522104 1612 520492
df :查看文件系统的磁盘空间占用情况
[root@localhost root]# df
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda3 9653708 2690872 6472452 30% /
/dev/sda1 147766 9376 130761 7% /boot
none 206428 0 206428 0% /dev/shm
du :统计目录或文件所占磁盘空间的大小
fdisk :查看磁盘分区情况及对硬盘进行分区管理
[root@localhost root]# fdisk -l
Disk /dev/sda: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Device Boot Start End Blocks Id System
/dev/sda1 * 1 19 152586 83 Linux
/dev/sda2 20 84 522112+ 82 Linux swap
/dev/sda3 85 1305 9807682+ 83 Linux
使用fdisk必须拥有root的权限
[lammy@localhost lammy]$ fdisk -l
-bash: fdisk: command not found
mount :磁盘挂载
[root@localhost root]# mount -l //列出以挂载的设备,文件系统名称和挂载点
/dev/sda3 on / type ext3 (rw) [/]
none on /proc type proc (rw)
usbdevfs on /proc/bus/usb type usbdevfs (rw)
/dev/sda1 on /boot type ext3 (rw) [/boot]
none on /dev/pts type devpts (rw,gid=5,mode=620)
none on /dev/shm type tmpfs (rw)
none on /proc/fs/vmblock/mountPoint type vmblock (rw)
cd :更改工作路径
cd -可以回到前次工作目录
./代表当前目录, ../代表上级目录
ls :列出目录内容
[root@localhost /]# ls //列出目录
bin dev home lib misc opt root soft tmp var
boot etc initrd lost+found mnt proc sbin themes usr
[root@localhost /]# ls -l //列出目录的详细内容
总用量 201
drwxr-xr-x 2 root root 4096 9月 5 23:19 bin
drwxr-xr-x 4 root root 1024 9月 5 23:07 boot
drwxr-xr-x 20 root root 118784 9月 16 18:05 dev
drwxr-xr-x 56 root root 4096 9月 26 21:41 etc
drwxr-xr-x 8 root root 4096 9月 26 21:40 home
drwxr-xr-x 2 root root 4096 2003-01-25 initrd 后面的没有贴出来
mkdir :创建目录
[root@localhost root]# mkdir -p ./test/test //-p设置路径
[root@localhost root]# ls
gcc_programe minicom.log other test vmware-tools-distrib
[root@localhost root]# cd test
[root@localhost test]# ls
test
cat :连接并显示指定的一个和多个文件的有关信息
-n 由第一行开始对所有输出的行数编号
-b 和-n相似,只不过对于空白行不编号
cp :将给出的文件或目录复制到另一个文件或目录中
[root@localhost root]# mkdir -p ./test/lammy
[root@localhost root]# cp -a ./test/lammy ./
[root@localhost root]# ls
gcc_programe lammy minicom.log other test vmware-tools-distrib
[root@localhost root]# ls ./test
lammy test
mv :为文件或目录改名或将文件由一个目录移入到另一个目录中
[root@localhost root]# mkdir -p ./test/lammyt
[root@localhost root]# ls ./test
lammy lammyt test
[root@localhost root]# mv -i ./test/lammyt ./
[root@localhost root]# ls
gcc_programe lammy lammyt minicom.log other test vmware-tools-distrib
[root@localhost root]# ls ./test
lammy test
该实例把./test下的lammyt移到./下
[root@localhost root]# mkdir ./lammyt/lammyt
[root@localhost root]# ls ./lammyt
lammyt
[root@localhost root]# mv ./lammyt/lammyt ./lammyt/lammy198
[root@localhost root]# ls ./lammyt
lammy198
重命名实验
rm :删除一个目录中的一个或多个文件
[root@localhost root]# rm -i lammy
rm:是否删除目录‘lammy’? y
rm: 无法删除目录‘lammy’: 是一个目录
[root@localhost root]# rm -r lammy //删除单个目录
rm:是否删除目录‘lammy’? y
[root@localhost root]# ls
gcc_programe lammyt minicom.log other test vmware-tools-distrib
该实例说明如果煤油使用-r,则rm不会删除目录;
[root@localhost root]# rm -r test //删除多个目录
rm:是否进入目录‘test’? y
rm:是否删除目录‘test/test’? y
rm:是否删除目录‘test/lammy’? y
rm:是否删除目录‘test’? y
执行范例:
1>切换到root
[lammy198@localhost lammy198]$ su - //可以写成su - root
Password: //输入密码
[root@localhost root]#
2>切换到lammy198
由root切换,则不需要输入密码
[root@localhost root]# su - lammy198
[lammy198@localhost lammy198]$
不是由root切换,则需要输入密码
[lammy@localhost lammy]$ su - lammy198 //不可以写成su -
Password: //输入密码
[lammy198@localhost lammy198]$
adduser :新建系统上的用户
[root@localhost root]# adduser -D //显示建立帐号时的默认值
GROUP=100 //所属主组的ID
HOME=/home //用户的根目录
INACTIVE=-1
EXPIRE=
SHELL=/bin/bash //所使用的SHELL
SKEL=/etc/skel //所应用的设置文件
创建lammyt的帐号
[root@localhost root]# adduser -m lammyt
[root@localhost root]# id lammyt //为什么groups=502而不是100?
uid=502(lammyt) gid=502(lammyt) groups=502(lammyt)
userdel :删除帐号
[root@localhost root]# userdel lammyt
[root@localhost root]# id lammyt
id: lammyt: No such user
useradd :新建帐号
[root@localhost root]# useradd lammyt
[root@localhost root]# id lammyt
uid=504(lammyt) gid=504(lammyt) groups=504(lammyt)
who :显示登陆当前登陆用户的信息
[root@localhost root]# who
root :0 Sep 16 18:05
root pts/2 Sep 21 12:16 (:0.0)
id :显示拥护组的ID
[root@localhost root]# id -a //显示用户的帐户信息
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
ps :显示当前系统中由该用户运行的进程列表
[root@localhost root]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 14:38 ? 00:00:06 init
root 2 1 0 14:38 ? 00:00:00 [keventd]
root 3 1 0 14:38 ? 00:00:00 [kapmd]
root 4 1 0 14:38 ? 00:00:00 [ksoftirqd_CPU0]
root 9 1 0 14:38 ? 00:00:00 [bdflush]
root 5 1 0 14:38 ? 00:00:00 [kswapd]
root 6 1 0 14:38 ? 00:00:00 [kscand/DMA]
free :查看当前系统内存的使用情况
[root@localhost root]# free
total used free shared buffers cached
Mem: 412856 402316 10540 0 35908 218360
-/+ buffers/cache: 148048 264808
Swap: 522104 1612 520492
df :查看文件系统的磁盘空间占用情况
[root@localhost root]# df
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda3 9653708 2690872 6472452 30% /
/dev/sda1 147766 9376 130761 7% /boot
none 206428 0 206428 0% /dev/shm
du :统计目录或文件所占磁盘空间的大小
fdisk :查看磁盘分区情况及对硬盘进行分区管理
[root@localhost root]# fdisk -l
Disk /dev/sda: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Device Boot Start End Blocks Id System
/dev/sda1 * 1 19 152586 83 Linux
/dev/sda2 20 84 522112+ 82 Linux swap
/dev/sda3 85 1305 9807682+ 83 Linux
使用fdisk必须拥有root的权限
[lammy@localhost lammy]$ fdisk -l
-bash: fdisk: command not found
mount :磁盘挂载
[root@localhost root]# mount -l //列出以挂载的设备,文件系统名称和挂载点
/dev/sda3 on / type ext3 (rw) [/]
none on /proc type proc (rw)
usbdevfs on /proc/bus/usb type usbdevfs (rw)
/dev/sda1 on /boot type ext3 (rw) [/boot]
none on /dev/pts type devpts (rw,gid=5,mode=620)
none on /dev/shm type tmpfs (rw)
none on /proc/fs/vmblock/mountPoint type vmblock (rw)
cd :更改工作路径
cd -可以回到前次工作目录
./代表当前目录, ../代表上级目录
ls :列出目录内容
[root@localhost /]# ls //列出目录
bin dev home lib misc opt root soft tmp var
boot etc initrd lost+found mnt proc sbin themes usr
[root@localhost /]# ls -l //列出目录的详细内容
总用量 201
drwxr-xr-x 2 root root 4096 9月 5 23:19 bin
drwxr-xr-x 4 root root 1024 9月 5 23:07 boot
drwxr-xr-x 20 root root 118784 9月 16 18:05 dev
drwxr-xr-x 56 root root 4096 9月 26 21:41 etc
drwxr-xr-x 8 root root 4096 9月 26 21:40 home
drwxr-xr-x 2 root root 4096 2003-01-25 initrd 后面的没有贴出来
mkdir :创建目录
[root@localhost root]# mkdir -p ./test/test //-p设置路径
[root@localhost root]# ls
gcc_programe minicom.log other test vmware-tools-distrib
[root@localhost root]# cd test
[root@localhost test]# ls
test
cat :连接并显示指定的一个和多个文件的有关信息
-n 由第一行开始对所有输出的行数编号
-b 和-n相似,只不过对于空白行不编号
cp :将给出的文件或目录复制到另一个文件或目录中
[root@localhost root]# mkdir -p ./test/lammy
[root@localhost root]# cp -a ./test/lammy ./
[root@localhost root]# ls
gcc_programe lammy minicom.log other test vmware-tools-distrib
[root@localhost root]# ls ./test
lammy test
mv :为文件或目录改名或将文件由一个目录移入到另一个目录中
[root@localhost root]# mkdir -p ./test/lammyt
[root@localhost root]# ls ./test
lammy lammyt test
[root@localhost root]# mv -i ./test/lammyt ./
[root@localhost root]# ls
gcc_programe lammy lammyt minicom.log other test vmware-tools-distrib
[root@localhost root]# ls ./test
lammy test
该实例把./test下的lammyt移到./下
[root@localhost root]# mkdir ./lammyt/lammyt
[root@localhost root]# ls ./lammyt
lammyt
[root@localhost root]# mv ./lammyt/lammyt ./lammyt/lammy198
[root@localhost root]# ls ./lammyt
lammy198
重命名实验
rm :删除一个目录中的一个或多个文件
[root@localhost root]# rm -i lammy
rm:是否删除目录‘lammy’? y
rm: 无法删除目录‘lammy’: 是一个目录
[root@localhost root]# rm -r lammy //删除单个目录
rm:是否删除目录‘lammy’? y
[root@localhost root]# ls
gcc_programe lammyt minicom.log other test vmware-tools-distrib
该实例说明如果煤油使用-r,则rm不会删除目录;
[root@localhost root]# rm -r test //删除多个目录
rm:是否进入目录‘test’? y
rm:是否删除目录‘test/test’? y
rm:是否删除目录‘test/lammy’? y
rm:是否删除目录‘test’? y
简单示例:
gdb server
b server.c 10 //在server.c的第10行断点
n
p
bt //堆栈查看
p cfg->type //查看结构体某个字段的值
得到的函数列表:
info functions
启动 GDB 调试工具
$ gdb --quiet
info 命令列出程序信息
(gdb) info proc
list 命令
(gdb) list main
disassemble 命令
(gdb) disass main
========================================
刚刚学了下gdb调试器的简单使用,感觉还不错,趁热打铁,把过程讲述下,自己也增强下,呵呵,废话少说,Begin!!!
[root@localhost hello]# vim test.c //新建一个test.c的源文件
在test.c中键入如下代码,很简单的程序:
/*test.c*/
#include
int sum(int m);
int main(void)
{
int i,m =0;
sum(50);
for(i=50; i!=0; i--)m+=i;
printf("The sum of 1~50 is %d \n",m);
}
int sum(int m)
{
int i,n =0;
// sum(50);
for(i=m; i!=0; i--)n+=i;
printf("The sum of 1~m is %d \n",n);
}
完了后保存它,返回终端
[root@localhost hello]# gcc -g test.c -o test //记得一定要加 -g,这样编译出的可执行代码才包含调试的信息,否则gdb是无法载入的
[root@localhost hello]# gdb test //test为带有调试信息的目标文件
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) //以上的信息就是gdb的启动画面,指定了gdb的版本号等信息
//然后键入 l 查看加载的文件,l表示list,b表示breakpoint,p表示print
(gdb) l //一次加载10行总[个人总结],
1 /*test.c*/
2 #include
3 int sum(int m);
4
5 int main(void)
6 {
7 int i,m =0;
8 sum(50);
9 for(i=50; i!=0; i--)m+=i;
10 printf("The sum of 1~50 is %d \n",m);
(gdb)
(gdb) l //继续通过参数 l 加载,加载接下来的10行
11 }
12
13 int sum(int m)
14 {
15 int i,n =0;
16 // sum(50);
17 for(i=m; i!=0; i--)n+=i;
18 printf("The sum of 1~m is %d \n",n);
19 }
(gdb) l //加载完毕,再加载显然是不会在加了哈
Line number 20 out of range; test.c has 19 lines.
//首先来看怎么设断点
(gdb) b 8 //b表示设断点,后面表示行号,就是加载时显示的行号
Breakpoint 1 at 0x804833f: file test.c, line 8.
(gdb) b 9
Breakpoint 2 at 0x804834c: file test.c, line 9.
(gdb) info b //我设了两个断点,通过info 来查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804833f in main at test.c:8
2 breakpoint keep y 0x0804834c in main at test.c:9
(gdb)
(gdb) r //r表示run,运行至断点1,如果没设断点,则运行至结尾
Starting program: /root/hello/test
Breakpoint 1, main () at test.c:8
8 sum(50);
(gdb) c //c表示continue,继续运行
Continuing.
The sum of 1~m is 1275
Breakpoint 2, main () at test.c:9
9 for(i=50; i!=0; i--)m+=i;
(gdb) //呵呵,上面几个操作看明白了吧
//怎么来看变量值呢
(gdb) l //先用l来看看源码
4
5 int main(void)
6 {
7 int i,m =0;
8 sum(50);
9 for(i=50; i!=0; i--)m+=i;
10 printf("The sum of 1~50 is %d \n",m);
11 }
12
13 int sum(int m)
(gdb) p m //p表示print,打印m的值
$1 = 0
(gdb) p i //打印i的值,i为什么这么大,应该不用我介绍了吧
$2 = 1073828704
//变量知道怎么看了,那么单步运行呢
(gdb) n //n表示单步运行,这表示一次运行一行,所以它不会进入调用的函数
10 printf("The sum of 1~50 is %d \n",m);
(gdb) n //n的运行机制通过这俩个n应该要看出个门道来喽,我用颜色强调了下,明白了没
The sum of 1~50 is 1275
11 }
//那么如果碰到调用函数怎么进入呢,不急,有办法
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/hello/test
Breakpoint 1, main () at test.c:8
8 sum(50); //前面设的断点,表示下一步将运行这断点所在的行
(gdb) s //用s来进入函数,也就说s也是单步运行的意思,但是它碰到函数时会进入函数运行; 而n不会,直接跳过
sum (m=50) at test.c:15 //仔细看看,是不是进入了sum函数哈
15 int i,n =0;
(gdb) s
17 for(i=m; i!=0; i--)n+=i;
(gdb) s
18 printf("The sum of 1~m is %d \n",n);
(gdb)
//这样在windows下的编译器的调试方法在gdb中都有相应的命令对应,并且更加灵活,哦忘了说怎么取消断点拉,呵呵简单
(gdb) info b //首先查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804833f in main at test.c:8
breakpoint already hit 1 time
2 breakpoint keep y 0x0804834c in main at test.c:9
(gdb) delet 1 //用delet取消断点,后面的数字应该明白吧
//还有其它命令可以通过输入help来查看,或者help command[需要查看的命令]来查看
//退出gdb,返回终端是通过q来实现的
(gdb) q
The program is running. Exit anyway? (y or n) y
[root@localhost hello]#
还有其它很多命令,我就不一一介绍拉。
gdb server
b server.c 10 //在server.c的第10行断点
n
p
bt //堆栈查看
p cfg->type //查看结构体某个字段的值
得到的函数列表:
info functions
启动 GDB 调试工具
$ gdb --quiet
info 命令列出程序信息
(gdb) info proc
list 命令
(gdb) list main
disassemble 命令
(gdb) disass main
========================================
刚刚学了下gdb调试器的简单使用,感觉还不错,趁热打铁,把过程讲述下,自己也增强下,呵呵,废话少说,Begin!!!
[root@localhost hello]# vim test.c //新建一个test.c的源文件
在test.c中键入如下代码,很简单的程序:
/*test.c*/
#include
int sum(int m);
int main(void)
{
int i,m =0;
sum(50);
for(i=50; i!=0; i--)m+=i;
printf("The sum of 1~50 is %d \n",m);
}
int sum(int m)
{
int i,n =0;
// sum(50);
for(i=m; i!=0; i--)n+=i;
printf("The sum of 1~m is %d \n",n);
}
完了后保存它,返回终端
[root@localhost hello]# gcc -g test.c -o test //记得一定要加 -g,这样编译出的可执行代码才包含调试的信息,否则gdb是无法载入的
[root@localhost hello]# gdb test //test为带有调试信息的目标文件
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
(gdb) //以上的信息就是gdb的启动画面,指定了gdb的版本号等信息
//然后键入 l 查看加载的文件,l表示list,b表示breakpoint,p表示print
(gdb) l //一次加载10行总[个人总结],
1 /*test.c*/
2 #include
3 int sum(int m);
4
5 int main(void)
6 {
7 int i,m =0;
8 sum(50);
9 for(i=50; i!=0; i--)m+=i;
10 printf("The sum of 1~50 is %d \n",m);
(gdb)
(gdb) l //继续通过参数 l 加载,加载接下来的10行
11 }
12
13 int sum(int m)
14 {
15 int i,n =0;
16 // sum(50);
17 for(i=m; i!=0; i--)n+=i;
18 printf("The sum of 1~m is %d \n",n);
19 }
(gdb) l //加载完毕,再加载显然是不会在加了哈
Line number 20 out of range; test.c has 19 lines.
//首先来看怎么设断点
(gdb) b 8 //b表示设断点,后面表示行号,就是加载时显示的行号
Breakpoint 1 at 0x804833f: file test.c, line 8.
(gdb) b 9
Breakpoint 2 at 0x804834c: file test.c, line 9.
(gdb) info b //我设了两个断点,通过info 来查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804833f in main at test.c:8
2 breakpoint keep y 0x0804834c in main at test.c:9
(gdb)
(gdb) r //r表示run,运行至断点1,如果没设断点,则运行至结尾
Starting program: /root/hello/test
Breakpoint 1, main () at test.c:8
8 sum(50);
(gdb) c //c表示continue,继续运行
Continuing.
The sum of 1~m is 1275
Breakpoint 2, main () at test.c:9
9 for(i=50; i!=0; i--)m+=i;
(gdb) //呵呵,上面几个操作看明白了吧
//怎么来看变量值呢
(gdb) l //先用l来看看源码
4
5 int main(void)
6 {
7 int i,m =0;
8 sum(50);
9 for(i=50; i!=0; i--)m+=i;
10 printf("The sum of 1~50 is %d \n",m);
11 }
12
13 int sum(int m)
(gdb) p m //p表示print,打印m的值
$1 = 0
(gdb) p i //打印i的值,i为什么这么大,应该不用我介绍了吧
$2 = 1073828704
//变量知道怎么看了,那么单步运行呢
(gdb) n //n表示单步运行,这表示一次运行一行,所以它不会进入调用的函数
10 printf("The sum of 1~50 is %d \n",m);
(gdb) n //n的运行机制通过这俩个n应该要看出个门道来喽,我用颜色强调了下,明白了没
The sum of 1~50 is 1275
11 }
//那么如果碰到调用函数怎么进入呢,不急,有办法
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/hello/test
Breakpoint 1, main () at test.c:8
8 sum(50); //前面设的断点,表示下一步将运行这断点所在的行
(gdb) s //用s来进入函数,也就说s也是单步运行的意思,但是它碰到函数时会进入函数运行; 而n不会,直接跳过
sum (m=50) at test.c:15 //仔细看看,是不是进入了sum函数哈
15 int i,n =0;
(gdb) s
17 for(i=m; i!=0; i--)n+=i;
(gdb) s
18 printf("The sum of 1~m is %d \n",n);
(gdb)
//这样在windows下的编译器的调试方法在gdb中都有相应的命令对应,并且更加灵活,哦忘了说怎么取消断点拉,呵呵简单
(gdb) info b //首先查看断点信息
Num Type Disp Enb Address What
1 breakpoint keep y 0x0804833f in main at test.c:8
breakpoint already hit 1 time
2 breakpoint keep y 0x0804834c in main at test.c:9
(gdb) delet 1 //用delet取消断点,后面的数字应该明白吧
//还有其它命令可以通过输入help来查看,或者help command[需要查看的命令]来查看
//退出gdb,返回终端是通过q来实现的
(gdb) q
The program is running. Exit anyway? (y or n) y
[root@localhost hello]#
还有其它很多命令,我就不一一介绍拉。
1.语言中变量的实质
要理解C指针,我认为一定要理解C中“变量”的存储实质,所以我就从“变量”这个东西开始讲起吧!
先来理解理解内存空间吧!请看下图:
内存地址→ 6 7 8 9 10 11 12 13
-----------------------------------------------------------------
··· | | | | | | | |··
-----------------------------------------------------------------
如图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样。每个座位都要编号,我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧!所以内存也要象座位一样进行编号了,这就是我们所说的内存编址。座位可以是按一个座位一个号码的从一号开始编号,内存则是按一个字节一个字节进行编址,如上图所示。每个字节都有个编号,我们称之为内存地址。好了,我说了这么多,现在你能理解内存空间这个概念吗?
我们继续看看以下的C、C++语言变量申明:
int I;
char a;
每次我们要使用某变量时都要事先这样申明它,它其实是内存中申请了一个名为i的整型变量宽度的空间(DOS下的16位编程中其宽度为二个字节),和一个名为a的字符型变量宽度的空间(占一个字节)。
我们又如何来理解变量是如何存在的呢。当我们如下申明变量时:
int I;
char a;
内存中的映象可能如下图:
内存地址→ 6 7 8 9 10 11 12 13
------------------------------------------------------------------
···| | | | | | | |··
------------------------------------------------------------------
变量名|→i ←|→a ←|
图中可看出,i在内存起始地址为6上申请了两个字节的空间(我这里假设了int的宽度为16位,不同系统中int的宽度是可能不一样的),并命名为i。 a在内存地址为8上申请了一字节的空间,并命名为a。这样我们就有两个不同类型的变量了。
2.赋值给变量
再看下面赋值:
i=30
a=’t’
你当然知道个两个语句是将30存入i变量的内存空间中,将’t’字符存入a变量的内存空间中。我们可以这样的形象理解啦:
内存地址→ 6 7 8 9 10 11 12 13
-----------------------------------------------------------------------
··· | 30 | ‘t’ | | | | |··
-----------------------------------------------------------------------
|→i ←|→a ←|
3.变量在哪里?(即我想知道变量的地址)
好了,接下来我们来看看&i是什么意思?
是取i变量所在的地址编号嘛!我们可以这样读它:返回i变量的地址编号。你记住了吗?
我要在屏幕上显示变量的地址值的话,可以写如下代码:
printf(“%d”,&i);
以上图的内存映象所例,屏幕上显示的不是i值30,而是显示i的内存地址编号6了。当然实际你操作的时,i变量的地址值不会是这个数了。
这就是我认为作为初学者们所应想象的变量存储实质了。请这样理解吧!
最后总结代码如下:
int main()
{
int i=39;
printf(“%d\n”,i); //①
printf(“%d\n”, &i); //②
}
现在你可知道①、②两个printf分别在屏幕上输出的是i的什么东西啊?
好啦!下面我们就开始真正进入指针的学习了。Come on !(待续…)
二.指针是什么东西<想说弄懂你不容易啊!我们许多初学指针的人都要这样的感慨。我常常在思索它,为什么呢?其实生活中处处都有指针。我们也处处在使用它。有了它我们的生活才更加方便了。没有指针,那生活才不方便。不信?你看下面的例子。
这是一个生活中的例子:比如说你要我借给你一本书,我到了你宿舍,但是你人不在宿舍,于是我把书放在你的2层3号的书架上,并写了一张纸条放在你的桌上。纸条上写着:你要的书在第2层3号的书架上。当你回来时,看到这张纸条。你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是书的地址,你通过纸条这个指针找到了我借给你的本书。
那么我们C,C++中的指针又是什么呢?请继续跟我来吧,看下面看一个申明一整型指针变量的语句如下:
int * pi;
pi是一个指针,当然我们知道啦,但是这样说,你就以为pi一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。不信你看下面图。
内存地址→6 7 8 9 10 11 12 13 14
--------------------------------------------------------------
···| 30 | ‘t’ | | | | | | |...
--------------------------------------------------------------
变量 |→i ←|→a ←| |→ pi ←|
(说明:这里我假设了指针只占2个字节宽度,实际上在32位系统中,指针的宽度是4个字节宽的,即32位。)由图示中可以看出,我们使用int *Pi申明指针变量; 其实是在内存的某处申明一个一定宽度的内存空间,并把它命名为Pi。你能在图中看出pi与前面的i,a 变量有什么本质区别吗,没有,当然没有!pi也只不过是一个变量而已嘛!那么它又为什么会被称为指针?关键是我们要让这个变量所存储的内容是什么。现在我要让pi成为真正有意义上的指针。请接着看下面语句:
pi=&i;
你应该知道 &i是什么意思吧!再次提醒你啦:这是返回i变量的地址编号。整句的意思就是把i地址的编号赋值给pi,也就是你在pi上写上i的地址编号。结果如下图所示:
内存地址→6 7 8 9 10 11 12 13 14
------------------------------------------------------------------
···| 30 | ‘t’ | | | 6 | | |...
------------------------------------------------------------------
变量 |→i ←|→a ←| |→ pi ←|
你看,执行完pi=&i;后,在图示中的系统中,pi的值是6。这个6就是i变量的地址编号,这样pi就指向了变量i了。你看,pi与那张纸条有什么区别?pi不就是那张纸条嘛!上面写着i的地址,而i就是那个本书。你现在看懂了吗?因此,我们就把pi称为指针。所以你要记住,指针变量所存的内容就是内存的地址编号!好了,现在我们就可以通过这个指针pi来访问到i这个变量了,不是吗?。看下面语句:
printf(“%d”,*pi);
那么*pi什么意思呢?你只要这样读它:pi内容所指的地址的内容(嘻嘻,看上去好像在绕口令了),就pi这张“纸条”上所写的位置上的那本 “书”---i 。你看,Pi内容是6,也就是说pi指向内存编号为6的地址。*pi嘛!就是它所指地址的内容,即地址编号6上的内容了。当然就是30的值了。所以这条语句会在屏幕上显示30。也就是说printf(“%d”,*pi);语句等价于printf( “%d”, i ) ,请结合上图好好体会吧!各位还有什么疑问,可以发Email:yyf977@163.com。
到此为止,你掌握了类似&i , *pi写法的含义和相关操作吗。总的一句话,我们的纸条就是我们的指针,同样我们的pi也就是我们的纸条!剩下的就是我们如何应用这张纸条了。最后我给你一道题:程序如下。
char a,*pa
a=10
pa=&a
*pa=20
printf( “%d”, a)
你能直接看出输出的结果是什么吗?如果你能,我想本篇的目的就达到了。好了,就说到这了。Happy to Study!在下篇中我将谈谈“指针的指针”即对int * * ppa;中ppa 的理解。
《彻底搞定C指针》第3篇--指针与数组名
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:76
1.数组元素
看下面代码
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, a[i] );
}
很显然,它是显示a 数组的各元素值。
我们还可以这样访问元素,如下
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, *(a+i) );
}
它的结果和作用完全一样
2. 通过指针访问数组元素
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a ;//请注意数组名a直接赋值给指针pa
for (i=0;i<=9;i++)
{
printf ( “%d”, pa[i] );
}
很显然,它也是显示a 数组的各元素值。
另外与数组名一样也可如下:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *(pa+i) );
}
看pa=a即数组名赋值给指针,以及通过数组名、指针对元素的访问形式看,它们并没有什么区别,从这里可以看出数组名其实也就是指针。难道它们没有任何区别?有,请继续。
3. 数组名与指针变量的区别
请看下面的代码:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意这里,指针值被修改
}
可以看出,这段代码也是将数组各元素值输出。不过,你把{}中的pa改成a试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而数组名只是一个指针常量。这个代码与上面的代码不同的是,指针pa在整个循环中,其值是不断递增的,即指针值被修改了。数组名是指针常量,其值是不能修改的,因此不能类似这样操作:a++。前面4,5节中pa[i],*(pa+i)处,指针pa的值是使终没有改变。所以变量指针pa与数组名a可以互换。
4. 申明指针常量
再请看下面的代码:
int i, a[]={3,4,5,6,7,3,7,4,4,6};
int * const pa=a;//注意const的位置:不是const int * pa,
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意这里,指针值被修改
}
这时候的代码能成功编译吗?不能。因为pa指针被定义为常量指针了。这时与数组名a已经没有不同。这更说明了数组名就是常量指针。但是…
int * const a={3,4,5,6,7,3,7,4,4,6};//不行
int a[]={3,4,5,6,7,3,7,4,4,6};//可以,所以初始化数组时必定要这样。
以上都是在VC6.0上实验。
《彻底搞定C指针》第4篇const int * pi/int * const pi的区别
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:69
1 int i 说起
你知道我们申明一个变量时象这样int i ;这个i是可能在它处重新变赋值的。如下:
int i=0;
//…
i=20;//这里重新赋值了
不过有一天我的程序可能需要这样一个变量(暂且称它变量),在申明时就赋一个初始值。之后我的程序在其它任何处都不会再去重新对它赋值。那我又应该怎么办呢?用const 。
//**************
const int ic =20;
//…
ic=40;//这样是不可以的,编译时是无法通过,因为我们不能对const 修饰的ic重新赋值的。
//这样我们的程序就会更早更容易发现问题了。
//**************
有了const修饰的ic 我们不称它为变量,而称符号常量,代表着20这个数。这就是const 的作用。ic是不能在它处重新赋新值了。
认识了const 作用之后,另外,我们还要知道格式的写法。有两种:const int ic=20;与int const ic=20;。它们是完全相同的。这一点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。有了这个概念后,我们来看这两个家伙:const int * pi与int const * pi ,按你的逻辑看,它们的语义有不同吗?呵呵,你只要记住一点,int 与const 哪个放前哪个放后都是一样的,就好比const int ic;与int const ic;一样。也就是说,它们是相同的。
好了,我们现在已经搞定一个“双包胎”的问题。那么int * const pi与前两个式子又有什么不同呢?我下面就来具体分析它们的格式与语义吧!
2 const int * pi的语义
我先来说说const int * pi是什么作用 (当然int const * pi也是一样的,前面我们说过,它们实际是一样的)。看下面的例子:
//*************代码开始***************
int i1=30;
int i2=40;
const int * pi=&i1;
pi=&i2; //4.注意这里,pi可以在任意时候重新赋值一个新内存地址
i2=80; //5.想想看:这里能用*pi=80;来代替吗?当然不能
printf( “%d”, *pi ) ; //6.输出是80
//*************代码结束***************
语义分析:
看出来了没有啊,pi的值是可以被修改的。即它可以重新指向另一个地址的,但是,不能通过*pi来修改i2的值。这个规则符合我们前面所讲的逻辑吗?当然符合了!
首先const 修饰的是整个*pi(注意,我写的是*pi而不是pi)。所以*pi是常量,是不能被赋值的(虽然pi所指的i2是变量,不是常量)。
其次,pi前并没有用const 修饰,所以pi是指针变量,能被赋值重新指向另一内存地址的。你可能会疑问:那我又如何用const 来修饰pi呢?其实,你注意到int * const pi中const 的位置就大概可以明白了。请记住,通过格式看语义。哈哈,你可能已经看出了规律吧?那下面的一节也就没必要看下去了。不过我还得继续我的战斗!
3 再看int * const pi
确实,int * const pi与前面的int const * pi会很容易给混淆的。注意:前面一句的const 是写在pi前和*号后的,而不是写在*pi前的。很显然,它是修饰限定pi的。我先让你看例子:
//*************代码开始***************
int i1=30;
int i2=40;
int * const pi=&i1;
//pi=&i2; 4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。
//所以我已经注释了它。
i1=80; //5.想想看:这里能用*pi=80;来代替吗?可以,这里可以通过*pi修改i1的值。
//请自行与前面一个例子比较。
printf( “%d”, *pi ) ; //6.输出是80
//***************代码结束*********************
语义分析:
看了这段代码,你明白了什么?有没有发现pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。相反,这次你可以通过*pi来修改i1的值了。与前一个例子对照一下吧!看以下的两点分析
1). pi因为有了const 的修饰,所以只是一个指针常量:也就是说pi值是不可修改的(即pi不可以重新指向i2这个变量了)(看第4行)。
2). 整个*pi的前面没有const 的修饰。也就是说,*pi是变量而不是常量,所以我们可以通过*pi来修改它所指内存i1的值(看5行的注释)
总之一句话,这次的pi是一个指向int变量类型数据的指针常量。
我最后总结两句:
1).如果const 修饰在*pi前则不能改的是*pi(即不能类似这样:*pi=50;赋值)而不是指pi。
2).如果const 是直接写在pi前则pi不能改(即不能类似这样:pi=&i;赋值)。
请你务必先记住这两点,相信你一定不会再被它们给搞糊了。现在再看这两个申明语句int const *pi和int * const pi时,呵呵,你会头昏脑胀还是很轻松惬意?它们各自申明的pi分别能修改什么,不能修改什么?再问问自己,把你的理解告诉我吧,可以发帖也可以发到我的邮箱(我的邮箱yyf977@163.com)!我一定会答复的。
3.补充三种情况。
这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单提一下吧!
情况一:int * pi指针指向const int i常量的情况
//**********begin*****************
const int i1=40;
int *pi;
pi=&i1; //这样可以吗?不行,VC下是编译错。
//const int 类型的i1的地址是不能赋值给指向int 类型地址的指针pi的。否则pi岂不是能修改i1的值了吗!
pi=(int* ) &i1; // 这样可以吗?强制类型转换可是C所支持的。
//VC下编译通过,但是仍不能通过*pi=80来修改i1的值。去试试吧!看看具体的怎样。
//***********end***************
情况二:const int * pi指针指向const int i1的情况
//*********begin****************
const int i1=40;
const int * pi;
pi=&i1;//两个类型相同,可以这样赋值。很显然,i1的值无论是通过pi还是i1都不能修改的。
//*********end*****************
情况三:用const int * const pi申明的指针
//***********begin****************
int i
const int * const pi=&i;//你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改i的值。因为不管是*pi还是pi都是const的。
//************end****************
下篇预告:函数参数的指针传递,值传递,引用传递 迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。
彻底搞定C指针——第5篇:函数参数的传递
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:99
一. 三道考题 开讲之前,我先请你做三道题目。(嘿嘿,得先把你的头脑搞昏才行……唉呀,谁扔我鸡蛋?)
1. 考题一:程序代码如下:
void Exchg1(int x, int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d\n”,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(“a=%d,b=%d\n”,a,b)
}
输出的结果:
x=____, y=____
a=____, b=____
问下划线的部分应是什么,请完成。
2. 考题二:代码如下。
Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d\n”,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2( &a,&b);
Print(“a=%d,b=%d\n”, a, b);
}
输出的结果为:
*px=____, *py=____
a=____, b=____
问下划线的部分应是什么,请完成。
3. 考题三:
Exchg2(int &x, int &y)
{
int tmp=x;
x=y;
y=tmp;
print(“x=%d,y=%d\n”,x,y);
}
main()
{
int a=4;
int b=6;
Exchg2(a,b);
Print(“a=%d,b=%d\n”, a, b);
}
输出的结果:
x=____, y=____
a=____, b=____
问下划线的部分输出的应是什么,请完成。
你不在机子上试,能作出来吗?你对你写出的答案有多大的把握?
正确的答案,想知道吗?(呵呵,让我慢慢地告诉你吧!)
好,废话少说,继续我们的探索之旅了。
我们都知道:C语言中函数参数的传递有:值传递,地址传递,引用传递这三种形式。题一为值传递,题二为地址传递,题三为引用传递。不过,正是这几种参数传递的形式,曾把我给搞得晕头转向。我相信也有很多人与我有同感吧?
下面请让我逐个地谈谈这三种传递形式。
二. 函数参数传递方式之一:值传递
1. 值传递的一个错误认识
先看题一中Exchg1函数的定义:
void Exchg1(int x, int y) //定义中的x,y变量被称为Exchg1函数的形式参数
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d\n”,x,y)
}
问:你认为这个函数是在做什么呀?
答:好像是对参数x,y的值对调吧?
请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:
void main()
{
int a=4,b=6;
Exchg1 (a,b) //a,b变量为Exchg1函数的实际参数。
/ printf(“a=%d,b=%d\n”,a,b)
}
我问:Exchg1 ()里头的 printf(“x=%d,y=%d\n”,x,y)语句会输出什么啊?
我再问:Exchg1 ()后的 printf(“a=%d,b=%d\n”,a,b)语句输出的是什么?
程序输出的结果是:
x=6 , y=4
a=4 , b=6 //为什么不是a=6,b=4呢?
奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本就不知实参a,b与形参x,y的关系了。
2. 一个预备的常识
为了说明这个问题,我先给出一个代码:
int a=4;
int x;
x=a;
x=x+3;
看好了没,现在我问你:最终a值是多少,x值是多少?
(怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4 x==7嘛!)
在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重要的认识喔。
3. 理解值传递的形式
看调用Exch1函数的代码:
main()
{
int a=4,b=6;
Exchg1(a,b) //这里调用了Exchg1函数
printf(“a=%d,b=%d”,a,b)
}
Exchg1(a,b)时所完成的操作代码如下所示。
int x=a;//←
int y=b;//←注意这里,头两行是调用函数时的隐含操作
int tmp;
tmp=x;
x=y;
y=tmp;
请注意在调用执行Exchg1函数的操作中我人为地加上了头两句:
int x=a;
int y=b;
这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是a,b变量或者只是x,y变量呢?)
原来 ,其实函数在调用时是隐含地把实参a,b 的值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x,y变量。并不是a,b。当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。
哈哈,终于明白了,正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。
第6篇 指向另一指针的指针
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:119
一.针概念:
早在本系列第二篇中我就对指针的实质进行了阐述。今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧!
当我们程序如下申明变量:
short int i;
char a;
short int * pi;
程序会在内存某地址空间上为各变量开辟空间,如下图所示。
内存地址→6 7 8 9 10 11 12 13 14 15
-------------------------------------------------------------------------------------
… | | | | | | | | | |
-------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
图中所示中可看出:
i 变量在内存地址5的位置,占两个字节。
a变量在内存地址7的位置,占一个字节。
pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节)
接下来如下赋值:
i=50;
pi=&i;
经过上在两句的赋值,变量的内存映象如下:
内存地址→6 7 8 9 10 11 12 13 14 15
--------------------------------------------------------------------------------------
… | 50 | | | 6 | | | |
--------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:
*pi=5; //就是等价于I=5;
你可以回看本系列的第二篇,那里有更加详细的解说。
二. 指针的地址与指向另一指针地址的指针
在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个地址。
看下面代码:
short int * * ppi; //这是一个指向指针的指针,注意有两个*号
ppi=π
第一句:short int * * ppi;——申明了一个指针变量ppi,这个ppi是用来存储(或称指向)一个short int * 类型指针变量的地址。
第二句:&pi那就是取pi的地址,ppi=π就是把pi的地址赋给了ppi。即将地址值10赋值给ppi。如下图:
内存地址→6 7 8 9 10 11 12 13 14 15
------------------------------------------------------------------------------------
… | 50 | | | 6 | 10 | |
------------------------------------------------------------------------------------
|short int i|char a| |short int * pi|short int ** ppi|
从图中看出,指针变量ppi的内容就是指针变量pi的起始地址。于是……
ppi的值是多少呢?——10。
*ppi的值是多少呢?——6,即pi的值。
**ppi的值是多少呢?——50,即I的值,也是*pi的值。
呵呵!不用我说太多了,我相信你应明白这种指针了吧!
三. 一个应用实例
1. 设计一个函数:void find1(char array[], char search, char * pi)
要求:这个函数参数中的数组array是以0值为结束的字符串,要求在字符串array中查找字符是参数search里的字符。如果找到,函数通过第三个参数(pa)返回值为array字符串中第一个找到的字符的地址。如果没找到,则为pa为0。
设计:依题意,实现代码如下。
void find1(char [] array, char search, char * pa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
}
你觉得这个函数能实现所要求的功能吗?
调试:
我下面调用这个函数试试。
void main()
{
char str[]={“afsdfsdfdf\0”}; //待查找的字符串
char a=’d’; //设置要查找的字符
char * p=0; //如果查找到后指针p将指向字符串中查找到的第一个字符的地址。
find1(str,a,p); //调用函数以实现所要操作。
if (0==p )
{
printf (“没找到!\n”);//1.如果没找到则输出此句
}
else
{
printf(“找到了,p=%d”,p); //如果找到则输出此句
}
}
分析:
上面代码,你认为会是输出什么呢?
运行试试。
唉!怎么输出的是:没有找到!
而不是:找到了,……。
明明a值为’d’,而str字符串的第四个字符是’d’,应该找得到呀!
再看函数定义处:void find1(char [] array, char search, char * pa)
看调用处:find1(str,a,p);
依我在第五篇的分析方法,函数调用时会对每一个参数进行一个隐含的赋值操作。
整个调用如下:
array=str;
search=a;
pa=p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
哦!参数pa与参数search的传递并没有什么不同,都是值传递嘛(小语:地址传递其实就是地址值传递嘛)!所以对形参变量pa值(当然值是一个地址值)的修改并不会改变实参变量p值,因此p的值并没有改变(即p的指向并没有被改变)。
(如果还有疑问,再看一看《第五篇:函数参数的传递》了。)
修正:
void find2(char [] array, char search, char ** ppa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
}
主函数的调用处改如下:
find2(str,a,&p); //调用函数以实现所要操作。
再分析:
这样调用函数时的整个操作变成如下:
array=str;
search=a;
ppa=&p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
看明白了吗?
ppa指向指针p的地址。
对*ppa的修改就是对p值的修改。
你自行去调试。
经过修改后的程序就可以完成所要的功能了。
看懂了这个例子,也就达到了本篇所要求的目的。
第7篇 函数名与函数指针
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:94
一 数调用
一个通常的函数调用的例子:
//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
return 0;
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:
MyFun(10);
我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。
直到——
学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢?
(不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)
二 函数指针变量的申明
就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法:
void (*FunP)(int) ; //也可写成void (*FunP)(int x);
你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)
三 通过函数指针变量调用函数
有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的:
//自行包含头文件
void MyFun(int x); //这个申明也可写成:void MyFun( int );
void (*FunP)(int ); //也可申明成void(*FunP)(int x),但习惯上一般不这样。
int main(int argc, char* argv[])
{
MyFun(10); //这是直接调用MyFun函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针变量FunP来调用MyFun函数的。
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
请看黑体字部分的代码及注释。
运行看看。嗯,不错,程序运行得很好。
哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。
int i,*pi;
pi=&i; //与FunP=&MyFun比较。
(你的感觉呢?)
呵呵,其实不然——
四 调用函数的其它书写格式
函数指针也可如下使用,来完成同样的事情:
//自行包含头文件
void MyFun(int x);
void (*FunP)(int ); //申明一个用以指向同样参数,返回值函数的指针变量。
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
我改了黑体字部分(请自行与之前的代码比较一下)。
运行试试,啊!一样地成功。
咦?
FunP=MyFun;
可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)
看来与之前的代码有点矛盾了,是吧!所以我说嘛!
请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):
代码之三:
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
代码之四:
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
真的是可以这样的噢!
(哇!真是要晕倒了!)
还有呐!看——
int main(int argc, char* argv[])
{
(*MyFun)(10); //看,函数名MyFun也可以有这样的调用格式
return 0;
}
你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)
那么,这些又说明了什么呢?
呵呵!依据以往的知识和经验来推理本篇的“新发现”,我想就连“福尔摩斯”也必定会由此分析并推断出以下的结论:
1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。
上述代码的写法,随便你爱怎么着!
请这样理解吧!这可是有助于你对函数指针的应用喽!
最后——
补充说明一点:在函数的申明处:
void MyFun(int ); //不能写成void (*MyFun)(int )。
void (*FunP)(int ); //不能写成void FunP(int )。
(请看注释)这一点是要注意的。
五 定义某一函数的指针类型:
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
我先给你一个自定义数据类型的例子。
typedef int* PINT; //为int* 类型定义了一个PINT的别名
int main()
{
int x;
PINT px=&x; //与int * px=&x;是等价的。PINT类型其实就是int * 类型
*px=10; //px就是int*类型的变量
return 0;
}
根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
下面我们来看一下函数指针类型的定义及使用:(请与上对照!)
//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int ); //这样只是定义一个函数指针类型
FunType FunP; //然后用FunType类型来申明全局FunP变量
int main(int argc, char* argv[])
{
//FunType FunP; //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
MyFun(10);
FunP=&MyFun;
(*FunP)(20);
return 0;
}
void MyFun(int x)
{
printf(“%d\n”,x);
}
看黑体部分:
首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。
然后,FunType FunP; 这句就如PINT px;一样地申明一个FunP变量。
其它相同。整个程序完成了相同的事。
这样做法的好处是:
有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下:
FunType FunP2;
FunType FunP3;
//……
六 函数指针作为某个函数的参数
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
实现:代码如下:
//自行包含头文件
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
void CallMyFun(FunType fp,int x);
int main(int argc, char* argv[])
{
CallMyFun(MyFun1,10); //⑤. 通过CallMyFun函数分别调用三个不同的函数
CallMyFun(MyFun2,20);
CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
{
fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
}
void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
{
printf(“函数MyFun1中输出:%d\n”,x);
}
void MyFun2(int x)
{
printf(“函数MyFun2中输出:%d\n”,x);
}
void MyFun3(int x)
{
printf(“函数MyFun3中输出:%d\n”,x);
}
输出结果:略
分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)
要理解C指针,我认为一定要理解C中“变量”的存储实质,所以我就从“变量”这个东西开始讲起吧!
先来理解理解内存空间吧!请看下图:
内存地址→ 6 7 8 9 10 11 12 13
-----------------------------------------------------------------
··· | | | | | | | |··
-----------------------------------------------------------------
如图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样。每个座位都要编号,我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧!所以内存也要象座位一样进行编号了,这就是我们所说的内存编址。座位可以是按一个座位一个号码的从一号开始编号,内存则是按一个字节一个字节进行编址,如上图所示。每个字节都有个编号,我们称之为内存地址。好了,我说了这么多,现在你能理解内存空间这个概念吗?
我们继续看看以下的C、C++语言变量申明:
int I;
char a;
每次我们要使用某变量时都要事先这样申明它,它其实是内存中申请了一个名为i的整型变量宽度的空间(DOS下的16位编程中其宽度为二个字节),和一个名为a的字符型变量宽度的空间(占一个字节)。
我们又如何来理解变量是如何存在的呢。当我们如下申明变量时:
int I;
char a;
内存中的映象可能如下图:
内存地址→ 6 7 8 9 10 11 12 13
------------------------------------------------------------------
···| | | | | | | |··
------------------------------------------------------------------
变量名|→i ←|→a ←|
图中可看出,i在内存起始地址为6上申请了两个字节的空间(我这里假设了int的宽度为16位,不同系统中int的宽度是可能不一样的),并命名为i。 a在内存地址为8上申请了一字节的空间,并命名为a。这样我们就有两个不同类型的变量了。
2.赋值给变量
再看下面赋值:
i=30
a=’t’
你当然知道个两个语句是将30存入i变量的内存空间中,将’t’字符存入a变量的内存空间中。我们可以这样的形象理解啦:
内存地址→ 6 7 8 9 10 11 12 13
-----------------------------------------------------------------------
··· | 30 | ‘t’ | | | | |··
-----------------------------------------------------------------------
|→i ←|→a ←|
3.变量在哪里?(即我想知道变量的地址)
好了,接下来我们来看看&i是什么意思?
是取i变量所在的地址编号嘛!我们可以这样读它:返回i变量的地址编号。你记住了吗?
我要在屏幕上显示变量的地址值的话,可以写如下代码:
printf(“%d”,&i);
以上图的内存映象所例,屏幕上显示的不是i值30,而是显示i的内存地址编号6了。当然实际你操作的时,i变量的地址值不会是这个数了。
这就是我认为作为初学者们所应想象的变量存储实质了。请这样理解吧!
最后总结代码如下:
int main()
{
int i=39;
printf(“%d\n”,i); //①
printf(“%d\n”, &i); //②
}
现在你可知道①、②两个printf分别在屏幕上输出的是i的什么东西啊?
好啦!下面我们就开始真正进入指针的学习了。Come on !(待续…)
二.指针是什么东西<想说弄懂你不容易啊!我们许多初学指针的人都要这样的感慨。我常常在思索它,为什么呢?其实生活中处处都有指针。我们也处处在使用它。有了它我们的生活才更加方便了。没有指针,那生活才不方便。不信?你看下面的例子。
这是一个生活中的例子:比如说你要我借给你一本书,我到了你宿舍,但是你人不在宿舍,于是我把书放在你的2层3号的书架上,并写了一张纸条放在你的桌上。纸条上写着:你要的书在第2层3号的书架上。当你回来时,看到这张纸条。你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是书的地址,你通过纸条这个指针找到了我借给你的本书。
那么我们C,C++中的指针又是什么呢?请继续跟我来吧,看下面看一个申明一整型指针变量的语句如下:
int * pi;
pi是一个指针,当然我们知道啦,但是这样说,你就以为pi一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。不信你看下面图。
内存地址→6 7 8 9 10 11 12 13 14
--------------------------------------------------------------
···| 30 | ‘t’ | | | | | | |...
--------------------------------------------------------------
变量 |→i ←|→a ←| |→ pi ←|
(说明:这里我假设了指针只占2个字节宽度,实际上在32位系统中,指针的宽度是4个字节宽的,即32位。)由图示中可以看出,我们使用int *Pi申明指针变量; 其实是在内存的某处申明一个一定宽度的内存空间,并把它命名为Pi。你能在图中看出pi与前面的i,a 变量有什么本质区别吗,没有,当然没有!pi也只不过是一个变量而已嘛!那么它又为什么会被称为指针?关键是我们要让这个变量所存储的内容是什么。现在我要让pi成为真正有意义上的指针。请接着看下面语句:
pi=&i;
你应该知道 &i是什么意思吧!再次提醒你啦:这是返回i变量的地址编号。整句的意思就是把i地址的编号赋值给pi,也就是你在pi上写上i的地址编号。结果如下图所示:
内存地址→6 7 8 9 10 11 12 13 14
------------------------------------------------------------------
···| 30 | ‘t’ | | | 6 | | |...
------------------------------------------------------------------
变量 |→i ←|→a ←| |→ pi ←|
你看,执行完pi=&i;后,在图示中的系统中,pi的值是6。这个6就是i变量的地址编号,这样pi就指向了变量i了。你看,pi与那张纸条有什么区别?pi不就是那张纸条嘛!上面写着i的地址,而i就是那个本书。你现在看懂了吗?因此,我们就把pi称为指针。所以你要记住,指针变量所存的内容就是内存的地址编号!好了,现在我们就可以通过这个指针pi来访问到i这个变量了,不是吗?。看下面语句:
printf(“%d”,*pi);
那么*pi什么意思呢?你只要这样读它:pi内容所指的地址的内容(嘻嘻,看上去好像在绕口令了),就pi这张“纸条”上所写的位置上的那本 “书”---i 。你看,Pi内容是6,也就是说pi指向内存编号为6的地址。*pi嘛!就是它所指地址的内容,即地址编号6上的内容了。当然就是30的值了。所以这条语句会在屏幕上显示30。也就是说printf(“%d”,*pi);语句等价于printf( “%d”, i ) ,请结合上图好好体会吧!各位还有什么疑问,可以发Email:yyf977@163.com。
到此为止,你掌握了类似&i , *pi写法的含义和相关操作吗。总的一句话,我们的纸条就是我们的指针,同样我们的pi也就是我们的纸条!剩下的就是我们如何应用这张纸条了。最后我给你一道题:程序如下。
char a,*pa
a=10
pa=&a
*pa=20
printf( “%d”, a)
你能直接看出输出的结果是什么吗?如果你能,我想本篇的目的就达到了。好了,就说到这了。Happy to Study!在下篇中我将谈谈“指针的指针”即对int * * ppa;中ppa 的理解。
《彻底搞定C指针》第3篇--指针与数组名
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:76
1.数组元素
看下面代码
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, a[i] );
}
很显然,它是显示a 数组的各元素值。
我们还可以这样访问元素,如下
int i,a[]={3,4,5,6,7,3,7,4,4,6};
for (i=0;i<=9;i++)
{
printf ( “%d”, *(a+i) );
}
它的结果和作用完全一样
2. 通过指针访问数组元素
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a ;//请注意数组名a直接赋值给指针pa
for (i=0;i<=9;i++)
{
printf ( “%d”, pa[i] );
}
很显然,它也是显示a 数组的各元素值。
另外与数组名一样也可如下:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *(pa+i) );
}
看pa=a即数组名赋值给指针,以及通过数组名、指针对元素的访问形式看,它们并没有什么区别,从这里可以看出数组名其实也就是指针。难道它们没有任何区别?有,请继续。
3. 数组名与指针变量的区别
请看下面的代码:
int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
pa =a;
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意这里,指针值被修改
}
可以看出,这段代码也是将数组各元素值输出。不过,你把{}中的pa改成a试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而数组名只是一个指针常量。这个代码与上面的代码不同的是,指针pa在整个循环中,其值是不断递增的,即指针值被修改了。数组名是指针常量,其值是不能修改的,因此不能类似这样操作:a++。前面4,5节中pa[i],*(pa+i)处,指针pa的值是使终没有改变。所以变量指针pa与数组名a可以互换。
4. 申明指针常量
再请看下面的代码:
int i, a[]={3,4,5,6,7,3,7,4,4,6};
int * const pa=a;//注意const的位置:不是const int * pa,
for (i=0;i<=9;i++)
{
printf ( “%d”, *pa );
pa++ ; //注意这里,指针值被修改
}
这时候的代码能成功编译吗?不能。因为pa指针被定义为常量指针了。这时与数组名a已经没有不同。这更说明了数组名就是常量指针。但是…
int * const a={3,4,5,6,7,3,7,4,4,6};//不行
int a[]={3,4,5,6,7,3,7,4,4,6};//可以,所以初始化数组时必定要这样。
以上都是在VC6.0上实验。
《彻底搞定C指针》第4篇const int * pi/int * const pi的区别
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:69
1 int i 说起
你知道我们申明一个变量时象这样int i ;这个i是可能在它处重新变赋值的。如下:
int i=0;
//…
i=20;//这里重新赋值了
不过有一天我的程序可能需要这样一个变量(暂且称它变量),在申明时就赋一个初始值。之后我的程序在其它任何处都不会再去重新对它赋值。那我又应该怎么办呢?用const 。
//**************
const int ic =20;
//…
ic=40;//这样是不可以的,编译时是无法通过,因为我们不能对const 修饰的ic重新赋值的。
//这样我们的程序就会更早更容易发现问题了。
//**************
有了const修饰的ic 我们不称它为变量,而称符号常量,代表着20这个数。这就是const 的作用。ic是不能在它处重新赋新值了。
认识了const 作用之后,另外,我们还要知道格式的写法。有两种:const int ic=20;与int const ic=20;。它们是完全相同的。这一点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。有了这个概念后,我们来看这两个家伙:const int * pi与int const * pi ,按你的逻辑看,它们的语义有不同吗?呵呵,你只要记住一点,int 与const 哪个放前哪个放后都是一样的,就好比const int ic;与int const ic;一样。也就是说,它们是相同的。
好了,我们现在已经搞定一个“双包胎”的问题。那么int * const pi与前两个式子又有什么不同呢?我下面就来具体分析它们的格式与语义吧!
2 const int * pi的语义
我先来说说const int * pi是什么作用 (当然int const * pi也是一样的,前面我们说过,它们实际是一样的)。看下面的例子:
//*************代码开始***************
int i1=30;
int i2=40;
const int * pi=&i1;
pi=&i2; //4.注意这里,pi可以在任意时候重新赋值一个新内存地址
i2=80; //5.想想看:这里能用*pi=80;来代替吗?当然不能
printf( “%d”, *pi ) ; //6.输出是80
//*************代码结束***************
语义分析:
看出来了没有啊,pi的值是可以被修改的。即它可以重新指向另一个地址的,但是,不能通过*pi来修改i2的值。这个规则符合我们前面所讲的逻辑吗?当然符合了!
首先const 修饰的是整个*pi(注意,我写的是*pi而不是pi)。所以*pi是常量,是不能被赋值的(虽然pi所指的i2是变量,不是常量)。
其次,pi前并没有用const 修饰,所以pi是指针变量,能被赋值重新指向另一内存地址的。你可能会疑问:那我又如何用const 来修饰pi呢?其实,你注意到int * const pi中const 的位置就大概可以明白了。请记住,通过格式看语义。哈哈,你可能已经看出了规律吧?那下面的一节也就没必要看下去了。不过我还得继续我的战斗!
3 再看int * const pi
确实,int * const pi与前面的int const * pi会很容易给混淆的。注意:前面一句的const 是写在pi前和*号后的,而不是写在*pi前的。很显然,它是修饰限定pi的。我先让你看例子:
//*************代码开始***************
int i1=30;
int i2=40;
int * const pi=&i1;
//pi=&i2; 4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。
//所以我已经注释了它。
i1=80; //5.想想看:这里能用*pi=80;来代替吗?可以,这里可以通过*pi修改i1的值。
//请自行与前面一个例子比较。
printf( “%d”, *pi ) ; //6.输出是80
//***************代码结束*********************
语义分析:
看了这段代码,你明白了什么?有没有发现pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。相反,这次你可以通过*pi来修改i1的值了。与前一个例子对照一下吧!看以下的两点分析
1). pi因为有了const 的修饰,所以只是一个指针常量:也就是说pi值是不可修改的(即pi不可以重新指向i2这个变量了)(看第4行)。
2). 整个*pi的前面没有const 的修饰。也就是说,*pi是变量而不是常量,所以我们可以通过*pi来修改它所指内存i1的值(看5行的注释)
总之一句话,这次的pi是一个指向int变量类型数据的指针常量。
我最后总结两句:
1).如果const 修饰在*pi前则不能改的是*pi(即不能类似这样:*pi=50;赋值)而不是指pi。
2).如果const 是直接写在pi前则pi不能改(即不能类似这样:pi=&i;赋值)。
请你务必先记住这两点,相信你一定不会再被它们给搞糊了。现在再看这两个申明语句int const *pi和int * const pi时,呵呵,你会头昏脑胀还是很轻松惬意?它们各自申明的pi分别能修改什么,不能修改什么?再问问自己,把你的理解告诉我吧,可以发帖也可以发到我的邮箱(我的邮箱yyf977@163.com)!我一定会答复的。
3.补充三种情况。
这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单提一下吧!
情况一:int * pi指针指向const int i常量的情况
//**********begin*****************
const int i1=40;
int *pi;
pi=&i1; //这样可以吗?不行,VC下是编译错。
//const int 类型的i1的地址是不能赋值给指向int 类型地址的指针pi的。否则pi岂不是能修改i1的值了吗!
pi=(int* ) &i1; // 这样可以吗?强制类型转换可是C所支持的。
//VC下编译通过,但是仍不能通过*pi=80来修改i1的值。去试试吧!看看具体的怎样。
//***********end***************
情况二:const int * pi指针指向const int i1的情况
//*********begin****************
const int i1=40;
const int * pi;
pi=&i1;//两个类型相同,可以这样赋值。很显然,i1的值无论是通过pi还是i1都不能修改的。
//*********end*****************
情况三:用const int * const pi申明的指针
//***********begin****************
int i
const int * const pi=&i;//你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改i的值。因为不管是*pi还是pi都是const的。
//************end****************
下篇预告:函数参数的指针传递,值传递,引用传递 迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。
彻底搞定C指针——第5篇:函数参数的传递
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:99
一. 三道考题 开讲之前,我先请你做三道题目。(嘿嘿,得先把你的头脑搞昏才行……唉呀,谁扔我鸡蛋?)
1. 考题一:程序代码如下:
void Exchg1(int x, int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d\n”,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(“a=%d,b=%d\n”,a,b)
}
输出的结果:
x=____, y=____
a=____, b=____
问下划线的部分应是什么,请完成。
2. 考题二:代码如下。
Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d\n”,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2( &a,&b);
Print(“a=%d,b=%d\n”, a, b);
}
输出的结果为:
*px=____, *py=____
a=____, b=____
问下划线的部分应是什么,请完成。
3. 考题三:
Exchg2(int &x, int &y)
{
int tmp=x;
x=y;
y=tmp;
print(“x=%d,y=%d\n”,x,y);
}
main()
{
int a=4;
int b=6;
Exchg2(a,b);
Print(“a=%d,b=%d\n”, a, b);
}
输出的结果:
x=____, y=____
a=____, b=____
问下划线的部分输出的应是什么,请完成。
你不在机子上试,能作出来吗?你对你写出的答案有多大的把握?
正确的答案,想知道吗?(呵呵,让我慢慢地告诉你吧!)
好,废话少说,继续我们的探索之旅了。
我们都知道:C语言中函数参数的传递有:值传递,地址传递,引用传递这三种形式。题一为值传递,题二为地址传递,题三为引用传递。不过,正是这几种参数传递的形式,曾把我给搞得晕头转向。我相信也有很多人与我有同感吧?
下面请让我逐个地谈谈这三种传递形式。
二. 函数参数传递方式之一:值传递
1. 值传递的一个错误认识
先看题一中Exchg1函数的定义:
void Exchg1(int x, int y) //定义中的x,y变量被称为Exchg1函数的形式参数
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d\n”,x,y)
}
问:你认为这个函数是在做什么呀?
答:好像是对参数x,y的值对调吧?
请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:
void main()
{
int a=4,b=6;
Exchg1 (a,b) //a,b变量为Exchg1函数的实际参数。
/ printf(“a=%d,b=%d\n”,a,b)
}
我问:Exchg1 ()里头的 printf(“x=%d,y=%d\n”,x,y)语句会输出什么啊?
我再问:Exchg1 ()后的 printf(“a=%d,b=%d\n”,a,b)语句输出的是什么?
程序输出的结果是:
x=6 , y=4
a=4 , b=6 //为什么不是a=6,b=4呢?
奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本就不知实参a,b与形参x,y的关系了。
2. 一个预备的常识
为了说明这个问题,我先给出一个代码:
int a=4;
int x;
x=a;
x=x+3;
看好了没,现在我问你:最终a值是多少,x值是多少?
(怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4 x==7嘛!)
在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重要的认识喔。
3. 理解值传递的形式
看调用Exch1函数的代码:
main()
{
int a=4,b=6;
Exchg1(a,b) //这里调用了Exchg1函数
printf(“a=%d,b=%d”,a,b)
}
Exchg1(a,b)时所完成的操作代码如下所示。
int x=a;//←
int y=b;//←注意这里,头两行是调用函数时的隐含操作
int tmp;
tmp=x;
x=y;
y=tmp;
请注意在调用执行Exchg1函数的操作中我人为地加上了头两句:
int x=a;
int y=b;
这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是a,b变量或者只是x,y变量呢?)
原来 ,其实函数在调用时是隐含地把实参a,b 的值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x,y变量。并不是a,b。当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。
哈哈,终于明白了,正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。
第6篇 指向另一指针的指针
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:119
一.针概念:
早在本系列第二篇中我就对指针的实质进行了阐述。今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧!
当我们程序如下申明变量:
short int i;
char a;
short int * pi;
程序会在内存某地址空间上为各变量开辟空间,如下图所示。
内存地址→6 7 8 9 10 11 12 13 14 15
-------------------------------------------------------------------------------------
… | | | | | | | | | |
-------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
图中所示中可看出:
i 变量在内存地址5的位置,占两个字节。
a变量在内存地址7的位置,占一个字节。
pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节)
接下来如下赋值:
i=50;
pi=&i;
经过上在两句的赋值,变量的内存映象如下:
内存地址→6 7 8 9 10 11 12 13 14 15
--------------------------------------------------------------------------------------
… | 50 | | | 6 | | | |
--------------------------------------------------------------------------------------
|short int i |char a| |short int * pi|
看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:
*pi=5; //就是等价于I=5;
你可以回看本系列的第二篇,那里有更加详细的解说。
二. 指针的地址与指向另一指针地址的指针
在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个地址。
看下面代码:
short int * * ppi; //这是一个指向指针的指针,注意有两个*号
ppi=π
第一句:short int * * ppi;——申明了一个指针变量ppi,这个ppi是用来存储(或称指向)一个short int * 类型指针变量的地址。
第二句:&pi那就是取pi的地址,ppi=π就是把pi的地址赋给了ppi。即将地址值10赋值给ppi。如下图:
内存地址→6 7 8 9 10 11 12 13 14 15
------------------------------------------------------------------------------------
… | 50 | | | 6 | 10 | |
------------------------------------------------------------------------------------
|short int i|char a| |short int * pi|short int ** ppi|
从图中看出,指针变量ppi的内容就是指针变量pi的起始地址。于是……
ppi的值是多少呢?——10。
*ppi的值是多少呢?——6,即pi的值。
**ppi的值是多少呢?——50,即I的值,也是*pi的值。
呵呵!不用我说太多了,我相信你应明白这种指针了吧!
三. 一个应用实例
1. 设计一个函数:void find1(char array[], char search, char * pi)
要求:这个函数参数中的数组array是以0值为结束的字符串,要求在字符串array中查找字符是参数search里的字符。如果找到,函数通过第三个参数(pa)返回值为array字符串中第一个找到的字符的地址。如果没找到,则为pa为0。
设计:依题意,实现代码如下。
void find1(char [] array, char search, char * pa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
}
你觉得这个函数能实现所要求的功能吗?
调试:
我下面调用这个函数试试。
void main()
{
char str[]={“afsdfsdfdf\0”}; //待查找的字符串
char a=’d’; //设置要查找的字符
char * p=0; //如果查找到后指针p将指向字符串中查找到的第一个字符的地址。
find1(str,a,p); //调用函数以实现所要操作。
if (0==p )
{
printf (“没找到!\n”);//1.如果没找到则输出此句
}
else
{
printf(“找到了,p=%d”,p); //如果找到则输出此句
}
}
分析:
上面代码,你认为会是输出什么呢?
运行试试。
唉!怎么输出的是:没有找到!
而不是:找到了,……。
明明a值为’d’,而str字符串的第四个字符是’d’,应该找得到呀!
再看函数定义处:void find1(char [] array, char search, char * pa)
看调用处:find1(str,a,p);
依我在第五篇的分析方法,函数调用时会对每一个参数进行一个隐含的赋值操作。
整个调用如下:
array=str;
search=a;
pa=p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
pa=array+i
break;
}
else if (*(array+i)==0)
{
pa=0;
break;
}
}
哦!参数pa与参数search的传递并没有什么不同,都是值传递嘛(小语:地址传递其实就是地址值传递嘛)!所以对形参变量pa值(当然值是一个地址值)的修改并不会改变实参变量p值,因此p的值并没有改变(即p的指向并没有被改变)。
(如果还有疑问,再看一看《第五篇:函数参数的传递》了。)
修正:
void find2(char [] array, char search, char ** ppa)
{
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
}
主函数的调用处改如下:
find2(str,a,&p); //调用函数以实现所要操作。
再分析:
这样调用函数时的整个操作变成如下:
array=str;
search=a;
ppa=&p; //请注意:以上三句是调用时隐含的动作。
int i;
for (i=0;*(array+i)!=0;i++)
{
if (*(array+i)==search)
{
*ppa=array+i
break;
}
else if (*(array+i)==0)
{
*ppa=0;
break;
}
}
看明白了吗?
ppa指向指针p的地址。
对*ppa的修改就是对p值的修改。
你自行去调试。
经过修改后的程序就可以完成所要的功能了。
看懂了这个例子,也就达到了本篇所要求的目的。
第7篇 函数名与函数指针
build:2007-1-30 17:58:50 author:白云小飞 editor:beyond update:2006-11-26 browse:94
一 数调用
一个通常的函数调用的例子:
//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
return 0;
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:
MyFun(10);
我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。
直到——
学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢?
(不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)
二 函数指针变量的申明
就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法:
void (*FunP)(int) ; //也可写成void (*FunP)(int x);
你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)
三 通过函数指针变量调用函数
有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的:
//自行包含头文件
void MyFun(int x); //这个申明也可写成:void MyFun( int );
void (*FunP)(int ); //也可申明成void(*FunP)(int x),但习惯上一般不这样。
int main(int argc, char* argv[])
{
MyFun(10); //这是直接调用MyFun函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针变量FunP来调用MyFun函数的。
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
请看黑体字部分的代码及注释。
运行看看。嗯,不错,程序运行得很好。
哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。
int i,*pi;
pi=&i; //与FunP=&MyFun比较。
(你的感觉呢?)
呵呵,其实不然——
四 调用函数的其它书写格式
函数指针也可如下使用,来完成同样的事情:
//自行包含头文件
void MyFun(int x);
void (*FunP)(int ); //申明一个用以指向同样参数,返回值函数的指针变量。
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
void MyFun(int x) //这里定义一个MyFun函数
{
printf(“%d\n”,x);
}
我改了黑体字部分(请自行与之前的代码比较一下)。
运行试试,啊!一样地成功。
咦?
FunP=MyFun;
可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)
看来与之前的代码有点矛盾了,是吧!所以我说嘛!
请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):
代码之三:
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=&MyFun; //将MyFun函数的地址赋给FunP变量
FunP(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
代码之四:
int main(int argc, char* argv[])
{
MyFun(10); //这里是调用MyFun(10);函数
FunP=MyFun; //将MyFun函数的地址赋给FunP变量
(*FunP)(20); //这是通过函数指针变量来调用MyFun函数的。
return 0;
}
真的是可以这样的噢!
(哇!真是要晕倒了!)
还有呐!看——
int main(int argc, char* argv[])
{
(*MyFun)(10); //看,函数名MyFun也可以有这样的调用格式
return 0;
}
你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)
那么,这些又说明了什么呢?
呵呵!依据以往的知识和经验来推理本篇的“新发现”,我想就连“福尔摩斯”也必定会由此分析并推断出以下的结论:
1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。
上述代码的写法,随便你爱怎么着!
请这样理解吧!这可是有助于你对函数指针的应用喽!
最后——
补充说明一点:在函数的申明处:
void MyFun(int ); //不能写成void (*MyFun)(int )。
void (*FunP)(int ); //不能写成void FunP(int )。
(请看注释)这一点是要注意的。
五 定义某一函数的指针类型:
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
我先给你一个自定义数据类型的例子。
typedef int* PINT; //为int* 类型定义了一个PINT的别名
int main()
{
int x;
PINT px=&x; //与int * px=&x;是等价的。PINT类型其实就是int * 类型
*px=10; //px就是int*类型的变量
return 0;
}
根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
下面我们来看一下函数指针类型的定义及使用:(请与上对照!)
//自行包含头文件
void MyFun(int x); //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int ); //这样只是定义一个函数指针类型
FunType FunP; //然后用FunType类型来申明全局FunP变量
int main(int argc, char* argv[])
{
//FunType FunP; //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
MyFun(10);
FunP=&MyFun;
(*FunP)(20);
return 0;
}
void MyFun(int x)
{
printf(“%d\n”,x);
}
看黑体部分:
首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。
然后,FunType FunP; 这句就如PINT px;一样地申明一个FunP变量。
其它相同。整个程序完成了相同的事。
这样做法的好处是:
有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下:
FunType FunP2;
FunType FunP3;
//……
六 函数指针作为某个函数的参数
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
实现:代码如下:
//自行包含头文件
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
void CallMyFun(FunType fp,int x);
int main(int argc, char* argv[])
{
CallMyFun(MyFun1,10); //⑤. 通过CallMyFun函数分别调用三个不同的函数
CallMyFun(MyFun2,20);
CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
{
fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
}
void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
{
printf(“函数MyFun1中输出:%d\n”,x);
}
void MyFun2(int x)
{
printf(“函数MyFun2中输出:%d\n”,x);
}
void MyFun3(int x)
{
printf(“函数MyFun3中输出:%d\n”,x);
}
输出结果:略
分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)
在 Unix 上写过程序的人一般都遇到过 Makefile,尤其是用 C 来开发程序的人。用 make 来开发和编译程序的确很方便,可是要写出一个MakeFile就不那么简单了。偏偏介绍 Makefile 的文件不多,GNU Make 那份印出来要几百页的文件,光看完 Overview 自己就快要先Over了,难怪许多人闻 Unix色变。本文将介绍如何利用 GNU Autoconf 及 Automake 这两套软件来帮助『自动』产生 Makefile 文件,并且让开发出来的的软件可以象 Apache, MySQL 和常见的 GNU 软件一样,只要会 ``./configure'', ``make'', ``make install'' 就可以把程序安装到系统中。如果您有心开发 Open Source 的软件,或只是想在 Unix 系统下写写程序。希望这份介绍文件能帮助您轻松的进入 Unix Programming 的殿堂。
1. 简介
Makefile 基本上就是『目标』(target), 『关联』(dependencies) 和『动作』三者所组成的一系列规则。而 make 就会根据 Makefile 的规则来决定如何编译 (compile) 和连接 (link) 程式。实际上,make 可做的不只是编译和连接程序,例如 FreeBSD 的 port collection 中,Makefile还可以做到自动下载远程程序,解压缩 (extract) , 打补丁 (patch),设定,然后编译,安装到系统中。
Makefile 基本结构虽然很简单,但是妥善运用这些规则就可以变换出许多不同的花样。却也因为这样,许多刚刚开始学习写Makefile 时会觉得没有规范可以遵循,每个人写出来的Makefile都不大一样,不知道从哪里下手,而且常常会受到自己的开发环境的限制,只要环境参数不同或者路径更改,可能 Makefile 就得跟着修改修改。虽然有 GNU Makefile Conventions (GNU Makefile惯例例)订出一些使用 GNU 程式设计时撰写 Makefile 的一些标准和规范,但是内容很长而且很复杂,并且经常作一些调整,为了减轻程序开发人员维护Makefile 的负担,因此出现了Automake。
程序设计者只需要写一些预先定义好的宏 (macro),提交给Automake处理后会产生一个可以供 Autoconf 使用的 Makefile.in文件。再配合利用 Autoconf产生的自动培植设置文件 configure 即可产生一份符合符合 GNU Makefile 惯例的 Makeifle 了。
2. 上路之前
在开始使用 Automake 之前,首先确认你的系统安装有如下软件:
1.
GNU Automake
2.
GNU Autoconf
3.
GNU m4
4.
perl
5.
GNU Libtool (如果你需要产生 shared library)
建议最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的工具程序来作为开发的环境,这些工具都是属于 Open Source Software 不但免费而且功能强大。如果你是使用 Red Hat Linux 可以找到所有上述软件的 rpm 文件,FreeBSD 也有现成的 package 可以直接安装,或也可以自行下载这些软件的源代码回来安装。下面的示例是在Red Hat Linux 5.2 + CLE2 的环境下所完成的。
3. 一个简单的例子
Automake 所产生的 Makefile 除了可以做到程式的编译和连接,也已经把如何产生程序文件 (如 manual page, info 文件及 dvi 文件) 的动作,还有把源码文件包装起来以供发布都考虑进去了,所以程序源代码所存放的目录结构最好符合GNU 的标准惯例,接下来就用一个hello.c 来做为例子。
在工作目录下建立一个新的子目录"devel"',再在 devel 下建立一个"hello"' 的子目录,这个目录将作为存放 hello这个程序及其相关文件的地方:
% mkdir devel
% cd devel
% mkdir hello
% cd hello
用编辑器写一个hello.c文件,
#include
int main(int argc, char** argv) {
printf(``Hello, GNU!\n'');
return 0;
}
接下来就要用 Autoconf 及 Automake 来产生 Makefile 文件了,
1.
用 autoscan 产生一个 configure.in 的原型,执行autoscan 后会产生一个configure.scan 的文件,可以用它作为 configure.in文件的蓝本。
% autoscan
% ls
configure.scan hello.c
2.
编辑 configure.scan文件,如下所示,并且改名为configure.in
dnl Process this file with autoconf to produce a configure script. AC_INIT(hello.c) AM_INIT_AUTOMAKE(hello, 1.0)
dnl Checks for programs.
AC_PROG_CC
dnl Checks for libraries.
dnl Checks for header files.
dnl Checks for typedefs, structures, and compiler characteristics.
dnl Checks for library functions.
AC_OUTPUT(Makefile)
3.
执行 aclocal 和 autoconf ,分别会产生 aclocal.m4 及 configure 两个文件
% aclocal
% autoconf
% ls
aclocal.m4 configure configure.in hello.c
4.
编辑 Makefile.am 文件,内容如下
AUTOMAKE_OPTIONS= foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c
5.
执行 automake --add-missing ,Automake 会根据Makefile.am 文件产生一些文件,包含最重要的 Makefile.in
% automake --add-missing automake: configure.in: installing `./install-sh' automake: configure.in: installing `./mkinstalldirs' automake: configure.in: installing `./missing'
6.
最后执行 ./configure ,
% ./configure creating cache ./config.cache checking for a BSD compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking whether make sets $... yes checking for working aclocal... found checking for working autoconf... found checking for working automake... found checking for working autoheader... found checking for working makeinfo... found checking for gcc... gcc checking whether the C compiler (gcc ) works... yes checking whether the C compiler (gcc ) is a cross-compiler... no checking whether we are using GNU C... yes checking whether gcc accepts -g... yes updating cache ./config.cache creating ./config.status creating Makefile
现在你的目录下已经产生了一个 Makefile 档,下个 ``make'' 指令就可以开始编译 hello.c 成执行档,执行 ./hello 和 GNU 打声招呼吧!
% make gcc -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -c hello.c gcc -g -O2 -o hello hello.o % ./hello Hello! GNU!
你还可以试试 ``make clean'',''make install'',''make dist'' 看看会有什麼结果。你也可以把产生出来的 Makefile 秀给你的老板,让他从此对你刮目相看 :-)
4. 追根问底
上述产生Makefile 的过程和以往自行编写的方式非常不一样,舍弃传统自定义make 的规则,使用 Automake 只需用到一些已经定义好的宏就可以了。我们把宏及目标 (target)写在Makefile.am 文件内,Automake 读入 Makefile.am 文件后会把这一串已经定义好的宏展开并产生相对应的 Makefile.in 文件,然后再由 configure这个 shell script 根据 Makefile.in 产生合适的Makefile。
利用 autoconf 及 automake产生Makefile 的流程
上图表示在上一范例中要使用的文件档案及产生出来的文件,有星号 (*) 者代表可执行文件。在此示例中可由 Autoconf 及 Automake 工具所产生的额外文件有 configure.scan、aclocal.m4、configure、Makefile.in,需要自行加入设置的有configure.in 及 Makefile.am。
4.1 编辑 configure.in 文件
Autoconf 是用来产生 'configure'文件的工具。'configure' 是一个 shell script,它可以自动设定原始程序以符合各种不同平台上Unix 系统的特性,并且根据系统参数及环境产生合适的Makefile文件或C 的头文件(header file),让原始程式可以很方便地在不同的平台上进行编译。Autoconf会读取 configure.in 文件然后产生'configure' 这个 shell script。
configure.in 文件内容是一系列GNU m4 的宏,这些宏经autoconf处理后会变成检查系统特性的shell scripts。 configure.in 内宏的顺序并没有特别的规定,但是每一个configure.in 文件必须在所有宏前加入 AC_INIT 宏,然后在所有宏的最后加上 AC_OUTPUT宏。可先用 autoscan 扫描原始文件以产生一个 configure.scan 文件,再对 configure.scan 做些修改成 configure.in 文件。在范例中所用到的宏如下:
dnl
这个宏后面的字不会被处理,可以视为注释
AC_INIT(FILE)
该宏用来检查源代码所在路径,autoscan 会自动产生,一般无须修改它。
AM_INIT_AUTOMAKE(PACKAGE,VERSION)
这个是使用 Automake 所必备的宏,PACKAGE 是所要产生软件套件的名称,VERSION 是版本编号。
AC_PROG_CC
检查系统可用的C编译器,若源代码是用C写的就需要这个宏。
AC_OUTPUT(FILE)
设置 configure 所要产生的文件,若是Makefile ,configure 便会把它检查出来的结果带入 Makefile.in 文件后产生合适的 Makefile。
实际上,这里使用 Automake 时,还需要一些其他的宏,这些额外的宏我们用 aclocal来帮助产生。执行 aclocal会产生aclocal.m4 文件,如果无特别的用途,可以不需要修改它,用 aclocal 所产生的宏会告诉 Automake如何动作。
有了 configure.in 及 aclocal.m4两个文件以后,便可以执行 autoconf来产生 configure 文件了。
4.2 编辑Makefile.am 文件
接下来要编辑Makefile.am 文件,Automake 会根据 configure.in 中的宏把Makefile.am 转成 Makefile.in 文件。 Makefile.am 文件定义所要产生的目标:
AUTOMAKE_OPTIONS
设置 automake 的选项。Automake 主要是帮助开发 GNU 软件的人员来维护软件,所以在执行 automake 时,会检查目录下是否存在标准 GNU 软件中应具备的文件,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件。设置 foreign 时,automake 会改用一般软件的标准来检查。
bin_PROGRAMS
定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空白符隔开。
hello_SOURCES
定义 'hello' 这个执行程序所需要的原始文件。如果 'hello'这个程序是由多个原始文件所产生,必须把它所用到的所有原始文件都列出来,以空白符隔开。假设 'hello' 还需要 'hello.c'、'main.c'、'hello.h' 三个文件的话,则定义
hello_SOURCES= hello.c main.c hello.h
如果定义多个执行文件,则对每个执行程序都要定义相对的filename_SOURCES。
编辑好 Makefile.am 文件,就可以用 automake --add-missing来产生 Makefile.in。加上 --add-missing 选项来告诉 automake顺便假如包装一个软件所必须的文件。Automake产生生出来的 Makefile.in 文件是完全符合 GNU Makefile 的惯例,只要执行 configure这个shell script 便可以产生合适的 Makefile 文件了。
4.3 使用 Makefile
利用 configure 所产生的 Makefile文件有几个预先设定的目标可供使用,这里只用几个简述如下:
make all
产生设定的目标,既次范例中的执行文件。只敲入make 也可以,此时会开始编译源代码,然后连接并产生执行文件。
make clean
清除之前所编译的执行文件及目标文件(object file, *.o)。
make distclean
除了清除执行文件和目的文件以外,也把 configure 所产生的 Makefile 清除掉。
make install
将程序安装到系统中,若源码编译成功,且执行结果正确,便可以把程序安装到系统预先设定的执行文件存放路径中,若用 bin_PROGRAMS 宏的话,程序会被安装到 /usr/local/bin下。
make dist
将程序和相关的文档包装为一个压缩文档以供发布 (distribution) 。执行完在目录下会产生一个以PACKAGE-VERSION.tar.gz 为名称的文件。PACKAGE 和 VERSION 这两个参数是根据 configure.in 文件中 AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生 'hello-1.0.tar.gz' 的文件。
make distcheck
和 make dist 类似,但是加入检查包装以后的压缩文件是否正常,这个目标除了把程序和相关文档包装成 tar.gz 文件外,还会自动把这个压缩文件解开,执行 configure,并执行 make all ,确认编译无错误以后,户显示这个 tar.gz 文件已经准备好可以发布了。这个检查非常有用,检查过关的套件,基本上可以给任何具备 GNU 开发环境的人去重新编译成功。就 hello-1.tar.gz 这个范例而言,除了在Red Hat Linux 上,在 FreeBSD 2.2.x 也可以正确编译。
要注意的是,利用 Autoconf 及 Automake 所产生出来的软件套件是可以在没有安装 Autoconf 及 Automake 的环境使用的,因为 configure 是一个 shell script,它己被设计为可以在一般 Unix 的 sh 这个 shell 下执行。但是如果要修改 configure.in 及 Makefile.am 文件再产生新的 configure 及 Makefile.in 文件时就一定要有 Autoconf 及 Automake 了。
5. 相关资料
Autoconf 和 Automake 功能十分强大,可以从它们附带的 info 稳当4中找到详细的使用方法说明。你也可以从许多现有的GNU 软件或 Open Source 软件中找到相关的 configure.in 或 Makefile.am 文件,他们是学习 Autoconf 及 Automake 更多技巧的最佳范例。
这个简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入 Open Source 软件开发的行列,希望这篇文章可以帮助你对产生 Makefile 有个简单的了解。其它有关开发 GNU 程式或 C 程序设计及 Makefile 的详细运用及技巧,建议从 GNU Coding Standards (GNU 编码规定) 读起,里面包含了 GNU Makefile 惯例,及开发 GNU 软件的标准程序和惯例。这些 GNU 软件的在线说明文件可以在 http://www.gnu.org/ 上找到。
6. 结束语
利用 Autoconf 及 Automake,产生一个 Makefile 似乎不再象以前那么困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix 之间发布及便宜程序变的简单,这对于在Unix 系统上程序开发员来说减轻了许多负担。妥善运用这些 GNU 的工具软件,可以帮助我们更容易的去开发程序,而且更容易维护源代码。
1. 简介
Makefile 基本上就是『目标』(target), 『关联』(dependencies) 和『动作』三者所组成的一系列规则。而 make 就会根据 Makefile 的规则来决定如何编译 (compile) 和连接 (link) 程式。实际上,make 可做的不只是编译和连接程序,例如 FreeBSD 的 port collection 中,Makefile还可以做到自动下载远程程序,解压缩 (extract) , 打补丁 (patch),设定,然后编译,安装到系统中。
Makefile 基本结构虽然很简单,但是妥善运用这些规则就可以变换出许多不同的花样。却也因为这样,许多刚刚开始学习写Makefile 时会觉得没有规范可以遵循,每个人写出来的Makefile都不大一样,不知道从哪里下手,而且常常会受到自己的开发环境的限制,只要环境参数不同或者路径更改,可能 Makefile 就得跟着修改修改。虽然有 GNU Makefile Conventions (GNU Makefile惯例例)订出一些使用 GNU 程式设计时撰写 Makefile 的一些标准和规范,但是内容很长而且很复杂,并且经常作一些调整,为了减轻程序开发人员维护Makefile 的负担,因此出现了Automake。
程序设计者只需要写一些预先定义好的宏 (macro),提交给Automake处理后会产生一个可以供 Autoconf 使用的 Makefile.in文件。再配合利用 Autoconf产生的自动培植设置文件 configure 即可产生一份符合符合 GNU Makefile 惯例的 Makeifle 了。
2. 上路之前
在开始使用 Automake 之前,首先确认你的系统安装有如下软件:
1.
GNU Automake
2.
GNU Autoconf
3.
GNU m4
4.
perl
5.
GNU Libtool (如果你需要产生 shared library)
建议最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的工具程序来作为开发的环境,这些工具都是属于 Open Source Software 不但免费而且功能强大。如果你是使用 Red Hat Linux 可以找到所有上述软件的 rpm 文件,FreeBSD 也有现成的 package 可以直接安装,或也可以自行下载这些软件的源代码回来安装。下面的示例是在Red Hat Linux 5.2 + CLE2 的环境下所完成的。
3. 一个简单的例子
Automake 所产生的 Makefile 除了可以做到程式的编译和连接,也已经把如何产生程序文件 (如 manual page, info 文件及 dvi 文件) 的动作,还有把源码文件包装起来以供发布都考虑进去了,所以程序源代码所存放的目录结构最好符合GNU 的标准惯例,接下来就用一个hello.c 来做为例子。
在工作目录下建立一个新的子目录"devel"',再在 devel 下建立一个"hello"' 的子目录,这个目录将作为存放 hello这个程序及其相关文件的地方:
% mkdir devel
% cd devel
% mkdir hello
% cd hello
用编辑器写一个hello.c文件,
#include
int main(int argc, char** argv) {
printf(``Hello, GNU!\n'');
return 0;
}
接下来就要用 Autoconf 及 Automake 来产生 Makefile 文件了,
1.
用 autoscan 产生一个 configure.in 的原型,执行autoscan 后会产生一个configure.scan 的文件,可以用它作为 configure.in文件的蓝本。
% autoscan
% ls
configure.scan hello.c
2.
编辑 configure.scan文件,如下所示,并且改名为configure.in
dnl Process this file with autoconf to produce a configure script. AC_INIT(hello.c) AM_INIT_AUTOMAKE(hello, 1.0)
dnl Checks for programs.
AC_PROG_CC
dnl Checks for libraries.
dnl Checks for header files.
dnl Checks for typedefs, structures, and compiler characteristics.
dnl Checks for library functions.
AC_OUTPUT(Makefile)
3.
执行 aclocal 和 autoconf ,分别会产生 aclocal.m4 及 configure 两个文件
% aclocal
% autoconf
% ls
aclocal.m4 configure configure.in hello.c
4.
编辑 Makefile.am 文件,内容如下
AUTOMAKE_OPTIONS= foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c
5.
执行 automake --add-missing ,Automake 会根据Makefile.am 文件产生一些文件,包含最重要的 Makefile.in
% automake --add-missing automake: configure.in: installing `./install-sh' automake: configure.in: installing `./mkinstalldirs' automake: configure.in: installing `./missing'
6.
最后执行 ./configure ,
% ./configure creating cache ./config.cache checking for a BSD compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking whether make sets $... yes checking for working aclocal... found checking for working autoconf... found checking for working automake... found checking for working autoheader... found checking for working makeinfo... found checking for gcc... gcc checking whether the C compiler (gcc ) works... yes checking whether the C compiler (gcc ) is a cross-compiler... no checking whether we are using GNU C... yes checking whether gcc accepts -g... yes updating cache ./config.cache creating ./config.status creating Makefile
现在你的目录下已经产生了一个 Makefile 档,下个 ``make'' 指令就可以开始编译 hello.c 成执行档,执行 ./hello 和 GNU 打声招呼吧!
% make gcc -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -c hello.c gcc -g -O2 -o hello hello.o % ./hello Hello! GNU!
你还可以试试 ``make clean'',''make install'',''make dist'' 看看会有什麼结果。你也可以把产生出来的 Makefile 秀给你的老板,让他从此对你刮目相看 :-)
4. 追根问底
上述产生Makefile 的过程和以往自行编写的方式非常不一样,舍弃传统自定义make 的规则,使用 Automake 只需用到一些已经定义好的宏就可以了。我们把宏及目标 (target)写在Makefile.am 文件内,Automake 读入 Makefile.am 文件后会把这一串已经定义好的宏展开并产生相对应的 Makefile.in 文件,然后再由 configure这个 shell script 根据 Makefile.in 产生合适的Makefile。
利用 autoconf 及 automake产生Makefile 的流程
上图表示在上一范例中要使用的文件档案及产生出来的文件,有星号 (*) 者代表可执行文件。在此示例中可由 Autoconf 及 Automake 工具所产生的额外文件有 configure.scan、aclocal.m4、configure、Makefile.in,需要自行加入设置的有configure.in 及 Makefile.am。
4.1 编辑 configure.in 文件
Autoconf 是用来产生 'configure'文件的工具。'configure' 是一个 shell script,它可以自动设定原始程序以符合各种不同平台上Unix 系统的特性,并且根据系统参数及环境产生合适的Makefile文件或C 的头文件(header file),让原始程式可以很方便地在不同的平台上进行编译。Autoconf会读取 configure.in 文件然后产生'configure' 这个 shell script。
configure.in 文件内容是一系列GNU m4 的宏,这些宏经autoconf处理后会变成检查系统特性的shell scripts。 configure.in 内宏的顺序并没有特别的规定,但是每一个configure.in 文件必须在所有宏前加入 AC_INIT 宏,然后在所有宏的最后加上 AC_OUTPUT宏。可先用 autoscan 扫描原始文件以产生一个 configure.scan 文件,再对 configure.scan 做些修改成 configure.in 文件。在范例中所用到的宏如下:
dnl
这个宏后面的字不会被处理,可以视为注释
AC_INIT(FILE)
该宏用来检查源代码所在路径,autoscan 会自动产生,一般无须修改它。
AM_INIT_AUTOMAKE(PACKAGE,VERSION)
这个是使用 Automake 所必备的宏,PACKAGE 是所要产生软件套件的名称,VERSION 是版本编号。
AC_PROG_CC
检查系统可用的C编译器,若源代码是用C写的就需要这个宏。
AC_OUTPUT(FILE)
设置 configure 所要产生的文件,若是Makefile ,configure 便会把它检查出来的结果带入 Makefile.in 文件后产生合适的 Makefile。
实际上,这里使用 Automake 时,还需要一些其他的宏,这些额外的宏我们用 aclocal来帮助产生。执行 aclocal会产生aclocal.m4 文件,如果无特别的用途,可以不需要修改它,用 aclocal 所产生的宏会告诉 Automake如何动作。
有了 configure.in 及 aclocal.m4两个文件以后,便可以执行 autoconf来产生 configure 文件了。
4.2 编辑Makefile.am 文件
接下来要编辑Makefile.am 文件,Automake 会根据 configure.in 中的宏把Makefile.am 转成 Makefile.in 文件。 Makefile.am 文件定义所要产生的目标:
AUTOMAKE_OPTIONS
设置 automake 的选项。Automake 主要是帮助开发 GNU 软件的人员来维护软件,所以在执行 automake 时,会检查目录下是否存在标准 GNU 软件中应具备的文件,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件。设置 foreign 时,automake 会改用一般软件的标准来检查。
bin_PROGRAMS
定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空白符隔开。
hello_SOURCES
定义 'hello' 这个执行程序所需要的原始文件。如果 'hello'这个程序是由多个原始文件所产生,必须把它所用到的所有原始文件都列出来,以空白符隔开。假设 'hello' 还需要 'hello.c'、'main.c'、'hello.h' 三个文件的话,则定义
hello_SOURCES= hello.c main.c hello.h
如果定义多个执行文件,则对每个执行程序都要定义相对的filename_SOURCES。
编辑好 Makefile.am 文件,就可以用 automake --add-missing来产生 Makefile.in。加上 --add-missing 选项来告诉 automake顺便假如包装一个软件所必须的文件。Automake产生生出来的 Makefile.in 文件是完全符合 GNU Makefile 的惯例,只要执行 configure这个shell script 便可以产生合适的 Makefile 文件了。
4.3 使用 Makefile
利用 configure 所产生的 Makefile文件有几个预先设定的目标可供使用,这里只用几个简述如下:
make all
产生设定的目标,既次范例中的执行文件。只敲入make 也可以,此时会开始编译源代码,然后连接并产生执行文件。
make clean
清除之前所编译的执行文件及目标文件(object file, *.o)。
make distclean
除了清除执行文件和目的文件以外,也把 configure 所产生的 Makefile 清除掉。
make install
将程序安装到系统中,若源码编译成功,且执行结果正确,便可以把程序安装到系统预先设定的执行文件存放路径中,若用 bin_PROGRAMS 宏的话,程序会被安装到 /usr/local/bin下。
make dist
将程序和相关的文档包装为一个压缩文档以供发布 (distribution) 。执行完在目录下会产生一个以PACKAGE-VERSION.tar.gz 为名称的文件。PACKAGE 和 VERSION 这两个参数是根据 configure.in 文件中 AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生 'hello-1.0.tar.gz' 的文件。
make distcheck
和 make dist 类似,但是加入检查包装以后的压缩文件是否正常,这个目标除了把程序和相关文档包装成 tar.gz 文件外,还会自动把这个压缩文件解开,执行 configure,并执行 make all ,确认编译无错误以后,户显示这个 tar.gz 文件已经准备好可以发布了。这个检查非常有用,检查过关的套件,基本上可以给任何具备 GNU 开发环境的人去重新编译成功。就 hello-1.tar.gz 这个范例而言,除了在Red Hat Linux 上,在 FreeBSD 2.2.x 也可以正确编译。
要注意的是,利用 Autoconf 及 Automake 所产生出来的软件套件是可以在没有安装 Autoconf 及 Automake 的环境使用的,因为 configure 是一个 shell script,它己被设计为可以在一般 Unix 的 sh 这个 shell 下执行。但是如果要修改 configure.in 及 Makefile.am 文件再产生新的 configure 及 Makefile.in 文件时就一定要有 Autoconf 及 Automake 了。
5. 相关资料
Autoconf 和 Automake 功能十分强大,可以从它们附带的 info 稳当4中找到详细的使用方法说明。你也可以从许多现有的GNU 软件或 Open Source 软件中找到相关的 configure.in 或 Makefile.am 文件,他们是学习 Autoconf 及 Automake 更多技巧的最佳范例。
这个简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入 Open Source 软件开发的行列,希望这篇文章可以帮助你对产生 Makefile 有个简单的了解。其它有关开发 GNU 程式或 C 程序设计及 Makefile 的详细运用及技巧,建议从 GNU Coding Standards (GNU 编码规定) 读起,里面包含了 GNU Makefile 惯例,及开发 GNU 软件的标准程序和惯例。这些 GNU 软件的在线说明文件可以在 http://www.gnu.org/ 上找到。
6. 结束语
利用 Autoconf 及 Automake,产生一个 Makefile 似乎不再象以前那么困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix 之间发布及便宜程序变的简单,这对于在Unix 系统上程序开发员来说减轻了许多负担。妥善运用这些 GNU 的工具软件,可以帮助我们更容易的去开发程序,而且更容易维护源代码。
1. 男人是社会的主体,不管你信或不信。所以男人应该有种责任感。
2. 25岁之前,请记得,爱情通常是假的,或者不是你所想象的那样纯洁和永远。如果你过了25岁,那么你也许会懂得这个道理。
3. 吃饭7成饱最舒服。对待女友最多也请你保持在7成。
4. 30岁之前请爱惜自己的身体,前30年你找病,后30年病找你。如果你过了30岁,你自然也会懂得这个道理。
5. 事业远比爱情重要。如果说事业都不能永恒,那么爱情只能算是昙花一现。
6. 不要轻易接受追求你的女孩。女追男隔层纱。如果你很容易就陷进去,你会发现你会错过很多东西,失去很多东西。
7. 请你相信,能用钱解决的问题,都不是问题。如果你认为钱索王道,有钱有女人,没钱没女人,那么。女人不是问题。
8 . 请永远积极向上。每个男人都有他可爱的地方,但是不可爱的地方只有不积极面对生活。
9. 不要连续2次让同一个女人伤害。好马不吃回头草,是有他道理的。如果认真考虑过该分手,那么请不要做任何舍不得的行动。
10. 如果你和你前女友能做朋友,那么你要问自己:为什么?如果分手后还是朋友,那么只有2个可能:。你们当初都只是玩玩而已,没付出彼此最真的感情。或者:必定有个人是在默默的付出无怨无悔!
11. 永远不要太相信女人在恋爱时的甜言蜜语。都说女人爱听甜言蜜语,其实,男人更喜欢。
12. 请不要为自己的相貌或者身高过分担心和自卑。人是动物,但是区别于动物。先天条件并不是阻挡你好好生活的借口。人的心灵远胜于相貌,请相信这点。如果有人以相貌取人,那么你也没必要太在意。因为他从某种意义来讲,只是只动物。你会跟动物怄气吗?
13. 失恋时,只有2种可能,要么你爱她她不爱你,或者相反。那么,当你爱的人不再爱你,或者从来没爱过你时。你没有遗憾,因为你失去的只是一个不爱你的人。
14. 请不要欺骗善良的女孩。这个世界上,善良的女孩太少。
15. 不能偏激的认为金钱万能,至少,金钱治不好艾滋病。
16. 请一定要有自信。你就是一道风景,没必要在别人风景里面仰视。
17. 受到再大的打击,只要生命还在,请相信每天的太阳都是新的。
18. 爱情永远不可能是天平。你想在爱情里幸福就要舍得伤心。
19. 如果你喜欢一个认为别人应该对她好的mm,请尽早放弃。直接血缘关系的亲人以外,没有一个人理所当然要对另一个人好。如果她不明白这个道理,也就是她根本不懂得珍惜。
20. 不要因为寂寞而找gf,寂寞男人请要学会品味寂寞。请记住:即使寂寞,远方黑暗的夜空下,一定有人和你一样,寂寞的人不同,仰望的星空却是唯一。
也许有些人的真心将长时间的寂寞,也许有些不寂寞的人最终要走向寂寞。不管你相不相信这句话,或愿不愿意成为有些人中的一员。
21. 任何事没有永远。也别问怎样才能永远。生活有很多无奈。请尽量充实自己,充实生活。请善待生活。
2. 25岁之前,请记得,爱情通常是假的,或者不是你所想象的那样纯洁和永远。如果你过了25岁,那么你也许会懂得这个道理。
3. 吃饭7成饱最舒服。对待女友最多也请你保持在7成。
4. 30岁之前请爱惜自己的身体,前30年你找病,后30年病找你。如果你过了30岁,你自然也会懂得这个道理。
5. 事业远比爱情重要。如果说事业都不能永恒,那么爱情只能算是昙花一现。
6. 不要轻易接受追求你的女孩。女追男隔层纱。如果你很容易就陷进去,你会发现你会错过很多东西,失去很多东西。
7. 请你相信,能用钱解决的问题,都不是问题。如果你认为钱索王道,有钱有女人,没钱没女人,那么。女人不是问题。
8 . 请永远积极向上。每个男人都有他可爱的地方,但是不可爱的地方只有不积极面对生活。
9. 不要连续2次让同一个女人伤害。好马不吃回头草,是有他道理的。如果认真考虑过该分手,那么请不要做任何舍不得的行动。
10. 如果你和你前女友能做朋友,那么你要问自己:为什么?如果分手后还是朋友,那么只有2个可能:。你们当初都只是玩玩而已,没付出彼此最真的感情。或者:必定有个人是在默默的付出无怨无悔!
11. 永远不要太相信女人在恋爱时的甜言蜜语。都说女人爱听甜言蜜语,其实,男人更喜欢。
12. 请不要为自己的相貌或者身高过分担心和自卑。人是动物,但是区别于动物。先天条件并不是阻挡你好好生活的借口。人的心灵远胜于相貌,请相信这点。如果有人以相貌取人,那么你也没必要太在意。因为他从某种意义来讲,只是只动物。你会跟动物怄气吗?
13. 失恋时,只有2种可能,要么你爱她她不爱你,或者相反。那么,当你爱的人不再爱你,或者从来没爱过你时。你没有遗憾,因为你失去的只是一个不爱你的人。
14. 请不要欺骗善良的女孩。这个世界上,善良的女孩太少。
15. 不能偏激的认为金钱万能,至少,金钱治不好艾滋病。
16. 请一定要有自信。你就是一道风景,没必要在别人风景里面仰视。
17. 受到再大的打击,只要生命还在,请相信每天的太阳都是新的。
18. 爱情永远不可能是天平。你想在爱情里幸福就要舍得伤心。
19. 如果你喜欢一个认为别人应该对她好的mm,请尽早放弃。直接血缘关系的亲人以外,没有一个人理所当然要对另一个人好。如果她不明白这个道理,也就是她根本不懂得珍惜。
20. 不要因为寂寞而找gf,寂寞男人请要学会品味寂寞。请记住:即使寂寞,远方黑暗的夜空下,一定有人和你一样,寂寞的人不同,仰望的星空却是唯一。
也许有些人的真心将长时间的寂寞,也许有些不寂寞的人最终要走向寂寞。不管你相不相信这句话,或愿不愿意成为有些人中的一员。
21. 任何事没有永远。也别问怎样才能永远。生活有很多无奈。请尽量充实自己,充实生活。请善待生活。
匹配正则:test是文件名
awk '/^(no|yes)/' test
几个实例
$ awk '/^(no|so)/' test-----打印所有以模式no或so开头的行。
$ awk '/^[ns]/{print $1}' test-----如果记录以n或s开头,就打印这个记录。
$ awk '$1 ~/[0-9][0-9]$/(print $1}' test-----如果第一个域以两个数字结束就打印这个记录。
$ awk '$1 == 100 || $2 < 50' test-----如果第一个或等于100或者第二个域小于50,则打印该行。
$ awk '$1 != 10' test-----如果第一个域不等于10就打印该行。
$ awk '/test/{print $1 + 10}' test-----如果记录包含正则表达式test,则第一个域加10并打印出来。
$ awk '{print ($1 > 5 ? "ok "$1: "error"$1)}' test-----如果第一个域大于5则打印问号后面的表达式值,否则打印冒号后面的表达式值。
$ awk '/^root/,/^mysql/' test----打印以正则表达式root开头的记录到以正则表达式mysql开头的记录范围内的所有记录。如果找到一个新的正则表达式root开头的记录,则继续打印直到下一个以正则表达式mysql开头的记录为止,或到文件末尾。
awk 用法:awk ‘ pattern {action} ‘
变量名 含义
ARGC 命令行变元个数
ARGV 命令行变元数组
FILENAME 当前输入文件名
FNR 当前文件中的记录号
FS 输入域分隔符,默认为一个空格
RS 输入记录分隔符
NF 当前记录里域个数
NR 到目前为止记录数
OFS 输出域分隔符
ORS 输出记录分隔符
1、awk ‘/101/’ file 显示文件file中包含101的匹配行。
awk ‘/101/,/105/’ file
awk ‘$1 == 5′ file
awk ‘$1 == “CT”‘ file 注意必须带双引号
awk ‘$1 * $2 >100 ‘ file
awk ‘$2 >5 && $21000000 ‘ 通过管道符获得输入,如:显示第4个域满足条件的行。
4、awk -F “|” ‘{print $1}’ file 按照新的分隔符“|”进行操作。
awk ‘BEGIN { FS=”[: \t|]” }
{print $1,$2,$3}’ file 通过设置输入分隔符(FS=”[: \t|]”)修改输入分隔符。
Sep=”|”
awk -F $Sep ‘{print $1}’ file 按照环境变量Sep的值做为分隔符。
awk -F ‘[ :\t|]’ ‘{print $1}’ file 按照正则表达式的值做为分隔符,这里代表空格、:、TAB、|同时做为分隔符。
awk -F ‘[][]’ ‘{print $1}’ file 按照正则表达式的值做为分隔符,这里代表[、]
5、awk -f awkfile file 通过文件awkfile的内容依次进行控制。
cat awkfile
/101/{print “47 Hello! 47″} –遇到匹配行以后打印 ‘ Hello! ‘.47代表单引号。
{print $1,$2} –因为没有模式控制,打印每一行的前两个域。
6、awk ‘$1 ~ /101/ {print $1}’ file 显示文件中第一个域匹配101的行(记录)。
7、awk ‘BEGIN { OFS=”%”}
{print $1,$2}’ file 通过设置输出分隔符(OFS=”%”)修改输出格式。
8、awk ‘BEGIN { max=100 ;print “max=” max} BEGIN 表示在处理任意行之前进行的操作。
{max=($1 >max ?$1:max); print $1,”Now max is “max}’ file 取得文件第一个域的最大值。
(表达式1?表达式2:表达式3 相当于:
if (表达式1)
表达式2
else
表达式3
awk ‘{print ($1>4 ? “high “$1: “low “$1)}’ file
9、awk ‘$1 * $2 >100 {print $1}’ file 显示文件中第一个域匹配101的行(记录)。
10、awk ‘{$1 == ‘Chi’ {$3 = ‘China’; print}’ file 找到匹配行后先将第3个域替换后再显示该行(记录)。
awk ‘{$7 %= 3; print $7}’ file 将第7域被3除,并将余数赋给第7域再打印。
11、awk ‘/tom/ {wage=$2+$3; printf wage}’ file 找到匹配行后为变量wage赋值并打印该变量。
12、awk ‘/tom/ {count++;}
END {print “tom was found “count” times”}’ file END表示在所有输入行处理完后进行处理。
13、awk ‘gsub(/\$/,”");gsub(/,/,”"); cost+=$4;
END {print “The total is $” cost>”filename”}’ file gsub函数用空串替换$和,再将结果输出到filename中。
1 2 3 $1,200.00
1 2 3 $2,300.00
1 2 3 $4,000.00
awk ‘{gsub(/\$/,”");gsub(/,/,”");
if ($4>1000&&$42000&&$43000&&$43000&&$43000) next;
else c4+=$4; }
END {printf “c4=[%d]\n”,c4}”‘ file
通过next在某条件时跳过该行,对下一行执行操作。
14、awk ‘{ print FILENAME,$0 }’ file1 file2 file3>fileall 把file1、file2、file3的文件内容全部写到fileall中,格式为
打印文件并前置文件名。
15、awk ‘ $1!=previous { close(previous); previous=$1 }
{print substr($0,index($0,” “) +1)>$1}’ fileall 把合并后的文件重新分拆为3个文件。并与原文件一致。
16、awk ‘BEGIN {”date”|getline d; print d}’ 通过管道把date的执行结果送给getline,并赋给变量d,然后打印。
17、awk ‘BEGIN {system(”echo \”Input your name:\\c\”"); getline d;print “\nYour name is”,d,”\b!\n”}’
通过getline命令交互输入name,并显示出来。
awk ‘BEGIN {FS=”:”; while(getline0) { if($1~”050[0-9]_”) print $1}}’
打印/etc/passwd文件中用户名包含050x_的用户名。
18、awk ‘{ i=1;while(i28) flag=1;
if ((j==4||j==6||j==9||j==11)&&i>30) flag=1;
if (flag==0) {printf “%02d%02d “,j,i}
}
}
}’
19、在awk中调用系统变量必须用单引号,如果是双引号,则表示字符串
Flag=abcd
awk ‘{print ‘$Flag’}’ 结果为abcd
awk ‘{print “$Flag”}’ 结果为$Flag
awk '/^(no|yes)/' test
几个实例
$ awk '/^(no|so)/' test-----打印所有以模式no或so开头的行。
$ awk '/^[ns]/{print $1}' test-----如果记录以n或s开头,就打印这个记录。
$ awk '$1 ~/[0-9][0-9]$/(print $1}' test-----如果第一个域以两个数字结束就打印这个记录。
$ awk '$1 == 100 || $2 < 50' test-----如果第一个或等于100或者第二个域小于50,则打印该行。
$ awk '$1 != 10' test-----如果第一个域不等于10就打印该行。
$ awk '/test/{print $1 + 10}' test-----如果记录包含正则表达式test,则第一个域加10并打印出来。
$ awk '{print ($1 > 5 ? "ok "$1: "error"$1)}' test-----如果第一个域大于5则打印问号后面的表达式值,否则打印冒号后面的表达式值。
$ awk '/^root/,/^mysql/' test----打印以正则表达式root开头的记录到以正则表达式mysql开头的记录范围内的所有记录。如果找到一个新的正则表达式root开头的记录,则继续打印直到下一个以正则表达式mysql开头的记录为止,或到文件末尾。
awk 用法:awk ‘ pattern {action} ‘
变量名 含义
ARGC 命令行变元个数
ARGV 命令行变元数组
FILENAME 当前输入文件名
FNR 当前文件中的记录号
FS 输入域分隔符,默认为一个空格
RS 输入记录分隔符
NF 当前记录里域个数
NR 到目前为止记录数
OFS 输出域分隔符
ORS 输出记录分隔符
1、awk ‘/101/’ file 显示文件file中包含101的匹配行。
awk ‘/101/,/105/’ file
awk ‘$1 == 5′ file
awk ‘$1 == “CT”‘ file 注意必须带双引号
awk ‘$1 * $2 >100 ‘ file
awk ‘$2 >5 && $21000000 ‘ 通过管道符获得输入,如:显示第4个域满足条件的行。
4、awk -F “|” ‘{print $1}’ file 按照新的分隔符“|”进行操作。
awk ‘BEGIN { FS=”[: \t|]” }
{print $1,$2,$3}’ file 通过设置输入分隔符(FS=”[: \t|]”)修改输入分隔符。
Sep=”|”
awk -F $Sep ‘{print $1}’ file 按照环境变量Sep的值做为分隔符。
awk -F ‘[ :\t|]’ ‘{print $1}’ file 按照正则表达式的值做为分隔符,这里代表空格、:、TAB、|同时做为分隔符。
awk -F ‘[][]’ ‘{print $1}’ file 按照正则表达式的值做为分隔符,这里代表[、]
5、awk -f awkfile file 通过文件awkfile的内容依次进行控制。
cat awkfile
/101/{print “47 Hello! 47″} –遇到匹配行以后打印 ‘ Hello! ‘.47代表单引号。
{print $1,$2} –因为没有模式控制,打印每一行的前两个域。
6、awk ‘$1 ~ /101/ {print $1}’ file 显示文件中第一个域匹配101的行(记录)。
7、awk ‘BEGIN { OFS=”%”}
{print $1,$2}’ file 通过设置输出分隔符(OFS=”%”)修改输出格式。
8、awk ‘BEGIN { max=100 ;print “max=” max} BEGIN 表示在处理任意行之前进行的操作。
{max=($1 >max ?$1:max); print $1,”Now max is “max}’ file 取得文件第一个域的最大值。
(表达式1?表达式2:表达式3 相当于:
if (表达式1)
表达式2
else
表达式3
awk ‘{print ($1>4 ? “high “$1: “low “$1)}’ file
9、awk ‘$1 * $2 >100 {print $1}’ file 显示文件中第一个域匹配101的行(记录)。
10、awk ‘{$1 == ‘Chi’ {$3 = ‘China’; print}’ file 找到匹配行后先将第3个域替换后再显示该行(记录)。
awk ‘{$7 %= 3; print $7}’ file 将第7域被3除,并将余数赋给第7域再打印。
11、awk ‘/tom/ {wage=$2+$3; printf wage}’ file 找到匹配行后为变量wage赋值并打印该变量。
12、awk ‘/tom/ {count++;}
END {print “tom was found “count” times”}’ file END表示在所有输入行处理完后进行处理。
13、awk ‘gsub(/\$/,”");gsub(/,/,”"); cost+=$4;
END {print “The total is $” cost>”filename”}’ file gsub函数用空串替换$和,再将结果输出到filename中。
1 2 3 $1,200.00
1 2 3 $2,300.00
1 2 3 $4,000.00
awk ‘{gsub(/\$/,”");gsub(/,/,”");
if ($4>1000&&$42000&&$43000&&$43000&&$43000) next;
else c4+=$4; }
END {printf “c4=[%d]\n”,c4}”‘ file
通过next在某条件时跳过该行,对下一行执行操作。
14、awk ‘{ print FILENAME,$0 }’ file1 file2 file3>fileall 把file1、file2、file3的文件内容全部写到fileall中,格式为
打印文件并前置文件名。
15、awk ‘ $1!=previous { close(previous); previous=$1 }
{print substr($0,index($0,” “) +1)>$1}’ fileall 把合并后的文件重新分拆为3个文件。并与原文件一致。
16、awk ‘BEGIN {”date”|getline d; print d}’ 通过管道把date的执行结果送给getline,并赋给变量d,然后打印。
17、awk ‘BEGIN {system(”echo \”Input your name:\\c\”"); getline d;print “\nYour name is”,d,”\b!\n”}’
通过getline命令交互输入name,并显示出来。
awk ‘BEGIN {FS=”:”; while(getline0) { if($1~”050[0-9]_”) print $1}}’
打印/etc/passwd文件中用户名包含050x_的用户名。
18、awk ‘{ i=1;while(i28) flag=1;
if ((j==4||j==6||j==9||j==11)&&i>30) flag=1;
if (flag==0) {printf “%02d%02d “,j,i}
}
}
}’
19、在awk中调用系统变量必须用单引号,如果是双引号,则表示字符串
Flag=abcd
awk ‘{print ‘$Flag’}’ 结果为abcd
awk ‘{print “$Flag”}’ 结果为$Flag
早上在签到的时候,看到玩得好的正看一道人家答的面试题,其中一道是C语言的折半查找,呵呵。。。
来个 原理:
以升序为例
1:第一各中间值是 全部元素的个数/2(或者(元素的序数+1)/2 )
2:判断你所要的值和这个中间值的大小
如果大,那么就是 (第一次中间值序数+1 + 末尾元素序数)/2
如果小,那么就是 (第一次中间值序数-1 + 首元素序数(通常是0))/2
这样逐步缩小范围
3:而后如果出现
比中间值小(这一轮的中间值),但是比上一步中间值大(上一轮的中间值)
那么, 新的中间值序数=((上轮中间值序数)+(这轮中间值序数))/2
如果是降序,则反之
这个折半查找法的思想 和 微积分中间的中值定理的思维有点像
来个 原理:
以升序为例
1:第一各中间值是 全部元素的个数/2(或者(元素的序数+1)/2 )
2:判断你所要的值和这个中间值的大小
如果大,那么就是 (第一次中间值序数+1 + 末尾元素序数)/2
如果小,那么就是 (第一次中间值序数-1 + 首元素序数(通常是0))/2
这样逐步缩小范围
3:而后如果出现
比中间值小(这一轮的中间值),但是比上一步中间值大(上一轮的中间值)
那么, 新的中间值序数=((上轮中间值序数)+(这轮中间值序数))/2
如果是降序,则反之
这个折半查找法的思想 和 微积分中间的中值定理的思维有点像
指针是C/C++的精华,也是最难的部分。——所有学习C/C++的人都明白这点,当年我初学的时候也是这样。但是,现在再回想指针,我却很难回忆它究竟难在哪儿。应该说这就叫“难者不会,会者不难”吧。“饱汉不知饿汉饥”是有一定的道理的,即使饱汉曾经饿过。
本书中规中矩地讲解了指针的概念、定义与初始化、操作等。正如上面提到的“饱汉不知饿汉饥”,我似乎很健忘,以至于不记得指针的难点在哪儿了。
指针的灵活性可以把大量的工作化繁为易,前提是必须首很把足够繁的指针弄懂。听起来有点像绕口令,事实就是这样,你现在把难懂的东西弄懂了,日后可以把难事化简,大事化小。
从VB过来的人一定会熟悉“值传递”和“地址传递”这两个概念,实际上,“地址传递”这种说法正是为了弥补VB没有指针却有类似的需要才发明的。我认为C/C++程序员要想深入理解指针,首先要抛弃这个概念。在C/C++程序中,即使在函数调用中传递指针,也不能说“地址传递”,还应该说是值传递,只不过这次传递的值有点特殊,特殊在于借用这个值,可以找到其它值。就好像我给你一把钥匙一样,你通过钥匙可以间接获得更多,但是我给你的只不过是钥匙。
我前阵子曾写过一篇关于指针的文章,之所以写那篇文章,是因为看到一大堆初学者在论坛上提问。通过对他们提的问题的分析,我总结了几点。下面,首先就先引用我自己写的《关于指针》中的片段吧(完整的文章请到我的个人主页查找):
一、指针就是变量:
虽然申明指针的时候也提类型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数(将来的64位系统中可能有变化)。
二、指针的加减运算很特殊:
p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。
比如:
char *型指针,每次加减的改变量都是1;
float *型的指针,每次加减的改变量都是4;
void *型指针无法加减。
还要注意的是:指针不能相加,指针相减的差为int型。
正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。
三、用指针做参数传递的是指针值,不是指针本身:
要理解参数传递,首先必须把“形参”与“实参”弄明白。
函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。
以上示例中,函数A中的变量C称为“实参”,函数B中的变量C被称为“形参”,调用函数时,会在B函数体内建立一个形参,该形参的值与实参的值是相同的,但是形参的改变不影响实参,函数结束时,形参被销毁,实参依然没有发生变化。
指针也是一个变量,所以它也符合以上的规定,但是,指针存放的不仅仅是一个值,而是一个内存地址。B函数对这个地址进行了改动,改动的并不是形参,而是形参所指的内存。由于形参的值与实参的值完全相同,所以,实参所指的内存也被修改。函数结束时,虽然这个形参会被销毁,指针的变化无法影响实参,但此前对它所指的内存的修改会持续有效。所以,把指针作为参数可以在被调函数(B)中改变主调函数(A)中的变量,好像形参影响了实参一样。
注意:是“好像”。在这过程中,函数B影响的不是参数,而是内存。
下面再来看刚才的例子:“int abc(char *p)”和“a = abc(p);”。为什么申请中要用*号,因为函数必须知道这是指针;为什么调用时不加*号,因为传递的是“指针值”,而不是“指针所指内存的值”。
四、指向指针的指针:
正因为指针也是一个变量,它一样要尊守形参与实参的规定。所以,虽然指针做参数可以将函数内对变量的修改带到函数外,但是,函数体内对指针本身作任何修都将被丢弃。如果要让指针本身被修改而且要影响函数外,那么,被调函数就应该知道“该指针所在的内存地址”。这时,指针不再是指针,而是“普通变量”。作为参数传递的不是这个“普通变量”,而是指向这个“普通变量”的指针。即“指向指针的指针”。
如果p是一个指向指针的指针,那么*p就是一个指针,我们不妨就把它看成q。要访问q指针所指的内存,只要*q就是了。用初中数学的“等量代换”一换就知道,*q就是**p。
五、指针数组。
之所以要把“指针数组”单独提出来,是因为数组本身就与指针有着千丝万缕的关系。即使你不想用指针,只要你使用了数组,实际就在与指针打交道了。
只要理解了指针本身就是变量,就不难理解“指针数组”,我们可以暂且把它当成普通数组来处理,a[0]、a[1]、a[2]……就是数组的元素,只是,a[0]是一个指针,a[1]、a[2]也是一个指针。那a呢?当然也是指针,但这是两码事。你可以完全无视a的存在,只去管a[0]等元素。*a[0]与*p没有什么本质的区别。
还有一个东西不得不提一下,它比较重要:
指针的定义有两个可取的方式,它们各有优缺点:“int *p;”和“int* p;”是完全等价的,后者的好处是让人体会到p是一个“指向int的”指针,前者会让人误解为*p是一个int型变量(这里没有定义int型变量);但是前者的好处是不会产生混淆,如“int *p, *q;”让人一眼就看出定义了两个指针,而“int* p,q;”会让人误解成定义了两个指针(实际上q不是指针)。
本书中规中矩地讲解了指针的概念、定义与初始化、操作等。正如上面提到的“饱汉不知饿汉饥”,我似乎很健忘,以至于不记得指针的难点在哪儿了。
指针的灵活性可以把大量的工作化繁为易,前提是必须首很把足够繁的指针弄懂。听起来有点像绕口令,事实就是这样,你现在把难懂的东西弄懂了,日后可以把难事化简,大事化小。
从VB过来的人一定会熟悉“值传递”和“地址传递”这两个概念,实际上,“地址传递”这种说法正是为了弥补VB没有指针却有类似的需要才发明的。我认为C/C++程序员要想深入理解指针,首先要抛弃这个概念。在C/C++程序中,即使在函数调用中传递指针,也不能说“地址传递”,还应该说是值传递,只不过这次传递的值有点特殊,特殊在于借用这个值,可以找到其它值。就好像我给你一把钥匙一样,你通过钥匙可以间接获得更多,但是我给你的只不过是钥匙。
我前阵子曾写过一篇关于指针的文章,之所以写那篇文章,是因为看到一大堆初学者在论坛上提问。通过对他们提的问题的分析,我总结了几点。下面,首先就先引用我自己写的《关于指针》中的片段吧(完整的文章请到我的个人主页查找):
一、指针就是变量:
虽然申明指针的时候也提类型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数(将来的64位系统中可能有变化)。
二、指针的加减运算很特殊:
p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。
比如:
char *型指针,每次加减的改变量都是1;
float *型的指针,每次加减的改变量都是4;
void *型指针无法加减。
还要注意的是:指针不能相加,指针相减的差为int型。
正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。
三、用指针做参数传递的是指针值,不是指针本身:
要理解参数传递,首先必须把“形参”与“实参”弄明白。
函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。
以上示例中,函数A中的变量C称为“实参”,函数B中的变量C被称为“形参”,调用函数时,会在B函数体内建立一个形参,该形参的值与实参的值是相同的,但是形参的改变不影响实参,函数结束时,形参被销毁,实参依然没有发生变化。
指针也是一个变量,所以它也符合以上的规定,但是,指针存放的不仅仅是一个值,而是一个内存地址。B函数对这个地址进行了改动,改动的并不是形参,而是形参所指的内存。由于形参的值与实参的值完全相同,所以,实参所指的内存也被修改。函数结束时,虽然这个形参会被销毁,指针的变化无法影响实参,但此前对它所指的内存的修改会持续有效。所以,把指针作为参数可以在被调函数(B)中改变主调函数(A)中的变量,好像形参影响了实参一样。
注意:是“好像”。在这过程中,函数B影响的不是参数,而是内存。
下面再来看刚才的例子:“int abc(char *p)”和“a = abc(p);”。为什么申请中要用*号,因为函数必须知道这是指针;为什么调用时不加*号,因为传递的是“指针值”,而不是“指针所指内存的值”。
四、指向指针的指针:
正因为指针也是一个变量,它一样要尊守形参与实参的规定。所以,虽然指针做参数可以将函数内对变量的修改带到函数外,但是,函数体内对指针本身作任何修都将被丢弃。如果要让指针本身被修改而且要影响函数外,那么,被调函数就应该知道“该指针所在的内存地址”。这时,指针不再是指针,而是“普通变量”。作为参数传递的不是这个“普通变量”,而是指向这个“普通变量”的指针。即“指向指针的指针”。
如果p是一个指向指针的指针,那么*p就是一个指针,我们不妨就把它看成q。要访问q指针所指的内存,只要*q就是了。用初中数学的“等量代换”一换就知道,*q就是**p。
五、指针数组。
之所以要把“指针数组”单独提出来,是因为数组本身就与指针有着千丝万缕的关系。即使你不想用指针,只要你使用了数组,实际就在与指针打交道了。
只要理解了指针本身就是变量,就不难理解“指针数组”,我们可以暂且把它当成普通数组来处理,a[0]、a[1]、a[2]……就是数组的元素,只是,a[0]是一个指针,a[1]、a[2]也是一个指针。那a呢?当然也是指针,但这是两码事。你可以完全无视a的存在,只去管a[0]等元素。*a[0]与*p没有什么本质的区别。
还有一个东西不得不提一下,它比较重要:
指针的定义有两个可取的方式,它们各有优缺点:“int *p;”和“int* p;”是完全等价的,后者的好处是让人体会到p是一个“指向int的”指针,前者会让人误解为*p是一个int型变量(这里没有定义int型变量);但是前者的好处是不会产生混淆,如“int *p, *q;”让人一眼就看出定义了两个指针,而“int* p,q;”会让人误解成定义了两个指针(实际上q不是指针)。
Here are two sample files that we will use in numerous examples to
illustrate the output of `diff' and how various options can change it.
This is the file `lao':
The Way that can be told of is not the eternal Way;
The name that can be named is not the eternal name.
The Nameless is the origin of Heaven and Earth;
The Named is the mother of all things.
Therefore let there always be non-being,
so we may see their subtlety,
And let there always be being,
so we may see their outcome.
The two are the same,
But after they are produced,
they have different names.
This is the file `tzu':
The Nameless is the origin of Heaven and Earth;
The named is the mother of all things.
Therefore let there always be non-being,
so we may see their subtlety,
And let there always be being,
so we may see their outcome.
The two are the same,
But after they are produced,
they have different names.
They both may be called deep and profound.
Deeper and more profound,
The door of all subtleties!
看来国外程序员对中国的禅和道有兴趣的不少啊, python的一个East Egg(import this)题目就叫The Zen of Python, 除了The Zen of Programming 和 The Tao of Prgramming之外,那本大名鼎鼎的“The Art of Unix Programming”原版封面就是一个老和尚和一个小和尚, 最近一本畅销书也叫“The Zen of CSS” 。。
illustrate the output of `diff' and how various options can change it.
This is the file `lao':
The Way that can be told of is not the eternal Way;
The name that can be named is not the eternal name.
The Nameless is the origin of Heaven and Earth;
The Named is the mother of all things.
Therefore let there always be non-being,
so we may see their subtlety,
And let there always be being,
so we may see their outcome.
The two are the same,
But after they are produced,
they have different names.
This is the file `tzu':
The Nameless is the origin of Heaven and Earth;
The named is the mother of all things.
Therefore let there always be non-being,
so we may see their subtlety,
And let there always be being,
so we may see their outcome.
The two are the same,
But after they are produced,
they have different names.
They both may be called deep and profound.
Deeper and more profound,
The door of all subtleties!
看来国外程序员对中国的禅和道有兴趣的不少啊, python的一个East Egg(import this)题目就叫The Zen of Python, 除了The Zen of Programming 和 The Tao of Prgramming之外,那本大名鼎鼎的“The Art of Unix Programming”原版封面就是一个老和尚和一个小和尚, 最近一本畅销书也叫“The Zen of CSS” 。。