﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-faintbear-随笔分类-C/C++ </title><link>http://www.blogjava.net/faintbear/category/981.html</link><description>小风嗖嗖的刮着......</description><language>zh-cn</language><lastBuildDate>Wed, 06 Jan 2010 03:57:20 GMT</lastBuildDate><pubDate>Wed, 06 Jan 2010 03:57:20 GMT</pubDate><ttl>60</ttl><item><title>静态局部变量</title><link>http://www.blogjava.net/faintbear/archive/2010/01/06/308408.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 06 Jan 2010 03:37:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2010/01/06/308408.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/308408.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2010/01/06/308408.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/308408.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/308408.html</trackback:ping><description><![CDATA[在局部变量前加上&#8220;static&#8221;关键字，就成了静态局部变量。静态局部变量存放在内存的全局数据区。函数结束时，静态局部变量不会消失，每次该函数调用
时，也不会为其重新分配空间。它始终驻留在全局数据区，直到程序运行结束。静态局部变量的初始化与全局变量类似．如果不为其显式初始化，则C++自动为其
初始化为0。<br />
静态局部变量与全局变量共享全局数据区，但静态局部变量只在定义它的函数中可见。静态局部变量与局部变量在存储位置上不同，使得其存在的时限也不同，导致对这两者操作 的运行结果也不同。<br />
例如，下面的程序定义了全局变量、静态局部变量和局部变量：<br />
<span class="ff0000"><font color="#ff0000">　//*********************<br />
//**　　 ch5_2.cpp　 **<br />
//********************* </font></span>
<p class="ff0000"><font color="#ff0000">　　　　#include &lt;iostream.h&gt;</font></p>
<p class="ff0000"><font color="#ff0000">　　　　void func();<br />
int n=1; //全局变量</font></p>
<p class="ff0000"><font color="#ff0000">　　　　void main()<br />
{<br />
<em><strong>static</strong></em> int a; // 静态局部变量<br />
int b= -10; // 局部变量<br />
cout &lt;&lt;"a:" &lt;&lt;a<br />
&lt;&lt;" b:" &lt;&lt;b<br />
&lt;&lt;" n:" &lt;&lt;n &lt;&lt;endl;<br />
b+=4;<br />
func();<br />
cout &lt;&lt;"a:" &lt;&lt;a<br />
&lt;&lt;" b:" &lt;&lt;b<br />
&lt;&lt;" n:" &lt;&lt;n &lt;&lt;endl;<br />
n+=10;<br />
func();<br />
}</font></p>
<p class="ff0000"><font color="#ff0000">　　　　void func()<br />
{<br />
<em><strong>static</strong></em> int a=2; // 静态局部变量<br />
int b=5; // 局部变量<br />
a+=2;<br />
n+=12;<br />
b+=5;<br />
cout &lt;&lt;"a:" &lt;&lt;a<br />
&lt;&lt;" b:" &lt;&lt;b<br />
&lt;&lt;" n:" &lt;&lt;n &lt;&lt;endl;<br />
}</font></p>
<p class="p14">　　运行结果为：<br />
<span class="ff0000">　　　<font color="#ff0000">　a:0 b:-10 n:l<br />
a:4 b:10 n:13<br />
a:0 b:-6 n:13<br />
a:6 b:10 n:35</font></span><br />
程序中主函数main()两次调用了func()函数，从运行结果可以看出，程序控制每次进入func()函数时，局部变量b都被初始化。而静态局部
变量a仅在第一次调用时被初始化，第二次进入该函数时，不再进行初始化，这时它的值是第一次调用后的结果值4。
main()函数中的变量a和b与func()函数中的变量a和b空间位置是不一样的，所以相应的值也不一样。关于变量作用域和可见性的进一步讨论见第6
章。<br />
静态局部变量的用途有许多：可以使用它确定某函数是否被调用过。使用它保留多次调用的值。</p>
<p class="p14"><strong><font size="5">对静态局部变量的说明：</font></strong> <br />
(1) 静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量（即动态局部变量）属于动态存储类别，存储在动态存储区空间(而不是静态存储区空间)，函数调用结束后即释放。 <br />
(2)
为静态局部变量赋初值是在编译时进行值的，即只赋初值一次，在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的
值。而为自动变量赋初值，不是在编译时进行的，而是在函数调用时进行，每调用一次函数重新给一次初值，相当于执行一次赋值语句。 <br />
<br />
(3)
如果在定义局部变量时不赋初值的话，对静态局部变量来说，编译时自动赋初值0(对数值型变量)或空字符(对字符型变量)。而对自动变量来说，如果不赋初
值，则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放，下次调用时又重新另分配存储单元，而所分配的单元中的值是不确定的。 <br />
(4) 虽然静态局部变量在函数调用结束后仍然存在，但其他函数是不能引用它的，也就是说，在其他函数中它是&#8220;不可见&#8221;的。</p>
<img src ="http://www.blogjava.net/faintbear/aggbug/308408.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2010-01-06 11:37 <a href="http://www.blogjava.net/faintbear/archive/2010/01/06/308408.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++ 程序设计员应聘常见面试试题深入剖析 </title><link>http://www.blogjava.net/faintbear/archive/2006/07/27/60321.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Thu, 27 Jul 2006 06:08:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2006/07/27/60321.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/60321.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2006/07/27/60321.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/60321.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/60321.html</trackback:ping><description><![CDATA[
		<table class="Caption_Big" width="95%" align="center" border="0">
				<tbody>
						<tr>
								<td height="30">
										<font style="FONT-SIZE: 15px">
												<b>C/C++ 程序设计员应聘常见面试试题深入剖析 </b>
										</font>
								</td>
						</tr>
				</tbody>
		</table>
		<table class="Border_Blue" cellpadding="5" width="95%" align="center" bgcolor="#f2f9ff" border="0">
				<tbody>
						<tr>
								<td>时间：2006-5-30 17:06:00</td>
								<td>
										<a href="http://edu.cnzz.cn/NewsComment/28170.aspx">
										</a>
								</td>
								<td> </td>
						</tr>
				</tbody>
		</table>
		<div class="Content_T">.引言 <br /><br />　　本文的写作目的并不在于提供C/C++程序员求职面试指导，而旨在从技术上分析面试题的内涵。文中的大多数面试题来自各大论坛，部分试题解答也参考了网友的意见。<br /><br />　　许多面试题看似简单，却需要深厚的基本功才能给出完美的解答。企业要求面试者写一个最简单的strcpy函数都可看出面试者在技术上究竟达到了怎样的程度，我们能真正写好一个strcpy函数吗？我们都觉得自己能，可是我们写出的strcpy很可能只能拿到10分中的2分。读者可从本文看到strcpy 函数从2分到10分解答的例子，看看自己属于什么样的层次。此外，还有一些面试题考查面试者敏捷的思维能力。 <br /><br />　　分析这些面试题，本身包含很强的趣味性；而作为一名研发人员，通过对这些面试题的深入剖析则可进一步增强自身的内功。<br /><br />　　2.找错题<br /><br />　　试题1：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void test1()<br />{<br />　char string[10];<br />　char* str1 = "0123456789";<br />　strcpy( string, str1 );<br />}</td></tr></tbody></table><br />　　试题2：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void test2()<br />{<br />　char string[10], str1[10];<br />　int i;<br />　for(i=0; i&lt;10; i++)<br />　{<br />　　str1[i] = 'a';<br />　}<br />　strcpy( string, str1 );<br />}</td></tr></tbody></table><br />　　试题3：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void test3(char* str1)<br />{<br />　char string[10];<br />　if( strlen( str1 ) &lt;= 10 )<br />　{<br />　　strcpy( string, str1 );<br />　}<br />}</td></tr></tbody></table><br />　　解答：<br /><br />　　试题1字符串str1需要11个字节才能存放下（包括末尾的’\0’），而string只有10个字节的空间，strcpy会导致数组越界；<br /><br />　　对试题2，如果面试者指出字符数组str1不能在数组内结束可以给3分；如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分，在此基础上指出库函数strcpy工作方式的给10 分；<br /><br />　　对试题3，if(strlen(str1) &lt;= 10)应改为if(strlen(str1) &lt; 10)，因为strlen的结果未统计’\0’所占用的1个字节。<br /><br />　　剖析：<br /><br />　　考查对基本功的掌握：<br /><br />　　(1)字符串以’\0’结尾；<br /><br />　　(2)对数组越界把握的敏感度；<br /><br />　　(3)库函数strcpy的工作方式，如果编写一个标准strcpy函数的总分值为10，下面给出几个不同得分的答案：<br /><br />　　2分<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void strcpy( char *strDest, char *strSrc )<br />{<br />　 while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</td></tr></tbody></table><br />　　4分<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void strcpy( char *strDest, const char *strSrc ) <br />//将源字符串加const，表明其为输入参数，加2分<br />{<br />　 while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</td></tr></tbody></table><br />　　7分<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void strcpy(char *strDest, const char *strSrc) <br />{<br />　//对源地址和目的地址加非0断言，加3分<br />　assert( (strDest != NULL) &amp;&amp; (strSrc != NULL) );<br />　while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</td></tr></tbody></table><br />　　10分<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>//为了实现链式操作，将目的地址返回，加3分！<br /><br />char * strcpy( char *strDest, const char *strSrc ) <br />{<br />　assert( (strDest != NULL) &amp;&amp; (strSrc != NULL) );<br />　char *address = strDest; <br />　while( (*strDest++ = * strSrc++) != ‘\0’ ); <br />　　return address;<br />}</td></tr></tbody></table><br />　　从2分到10分的几个答案我们可以清楚的看到，小小的strcpy竟然暗藏着这么多玄机，真不是盖的！需要多么扎实的基本功才能写一个完美的strcpy啊！<br /><br />　　(4)对strlen的掌握，它没有包括字符串末尾的'\0'。<br /><br />　　读者看了不同分值的strcpy版本，应该也可以写出一个10分的strlen函数了，完美的版本为： int strlen( const char *str ) //输入参数const<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>{<br />　assert( strt != NULL ); //断言字符串地址非0<br />　int len;<br />　while( (*str++) != '\0' ) <br />　{ <br />　　len++; <br />　} <br />　return len;<br />}</td></tr></tbody></table><br />　　试题4：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void GetMemory( char *p )<br />{<br />　p = (char *) malloc( 100 );<br />}<br /><br />void Test( void ) <br />{<br />　char *str = NULL;<br />　GetMemory( str ); <br />　strcpy( str, "hello world" );<br />　printf( str );<br />}</td></tr></tbody></table><br />　　试题5：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>char *GetMemory( void )<br />{ <br />　char p[] = "hello world"; <br />　return p; <br />}<br /><br />void Test( void )<br />{ <br />　char *str = NULL; <br />　str = GetMemory(); <br />　printf( str ); <br />}</td></tr></tbody></table><br />　　试题6：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void GetMemory( char **p, int num )<br />{<br />　*p = (char *) malloc( num );<br />}<br /><br />void Test( void )<br />{<br />　char *str = NULL;<br />　GetMemory( &amp;str, 100 );<br />　strcpy( str, "hello" ); <br />　printf( str ); <br />}</td></tr></tbody></table><br />　　试题7：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>void Test( void )<br />{<br />　char *str = (char *) malloc( 100 );<br />　strcpy( str, "hello" );<br />　free( str ); <br />　... //省略的其它语句<br />}</td></tr></tbody></table><br />　　解答：<br /><br />　　试题4传入中GetMemory( char *p )函数的形参为字符串指针，在函数内部修改形参并不能真正的改变传入形参的值，执行完<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>char *str = NULL;<br />GetMemory( str ); </td></tr></tbody></table><br />　　后的str仍然为NULL；<br /><br />　　试题5中<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>char p[] = "hello world"; <br />return p; </td></tr></tbody></table><br />　　的p[]数组为函数内的局部自动变量，在函数返回后，内存已经被释放。这是许多程序员常犯的错误，其根源在于不理解变量的生存期。<br /><br />　　试题6的GetMemory避免了试题4的问题，传入GetMemory的参数为字符串指针的指针，但是在GetMemory中执行申请内存及赋值语句<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>*p = (char *) malloc( num );</td></tr></tbody></table><br />　　后未判断内存是否申请成功，应加上：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>if ( *p == NULL )<br />{<br />　...//进行申请内存失败处理<br />}</td></tr></tbody></table><br />　　试题7存在与试题6同样的问题，在执行<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>char *str = (char *) malloc(100);</td></tr></tbody></table><br />　　后未进行内存是否申请成功的判断；另外，在free(str)后未置str为空，导致可能变成一个“野”指针，应加上：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>str = NULL;</td></tr></tbody></table><br />　　试题6的Test函数中也未对malloc的内存进行释放。<br /><br />　　剖析：<br /><br />　　试题4～7考查面试者对内存操作的理解程度，基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确，却也绝非易事。<br /><br />　　对内存操作的考查主要集中在：<br /><br />　　（1）指针的理解；<br /><br />　　（2）变量的生存期及作用范围；<br /><br />　　（3）良好的动态内存申请和释放习惯。<br /><br />　　再看看下面的一段程序有什么错误：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>swap( int* p1,int* p2 )<br />{<br />　int *p;<br />　*p = *p1;<br />　*p1 = *p2;<br />　*p2 = *p;<br />}</td></tr></tbody></table><br />　　在swap函数中，p是一个“野”指针，有可能指向系统区，导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>swap( int* p1,int* p2 )<br />{<br />　int p;<br />　p = *p1;<br />　*p1 = *p2;<br />　*p2 = p;<br />}</td></tr></tbody></table><br />　3.内功题<br /><br />　　试题1：分别给出BOOL，int，float，指针变量 与“零值”比较的 if 语句（假设变量名为var）<br /><br />　　解答：<br /><br />　　　BOOL型变量：if(!var)<br /><br />　　　int型变量： if(var==0)<br /><br />　　　float型变量：<br /><br />　　　const float EPSINON = 0.00001;<br /><br />　　　if ((x &gt;= - EPSINON) &amp;&amp; (x &lt;= EPSINON)<br /><br />　　　指针变量：　　if(var==NULL)<br /><br />　　剖析：<br /><br />　　考查对0值判断的“内功”，BOOL型变量的0判断完全可以写成if(var==0)，而int型变量也可以写成if(!var)，指针变量的判断也可以写成if(!var)，上述写法虽然程序都能正确运行，但是未能清晰地表达程序的意思。 
<table cellspacing="0" cellpadding="0" align="left" border="0"><tbody><tr><td valign="top"><table cellspacing="4" cellpadding="0" width="350" align="left" border="0"><tbody><tr><td><iframe marginwidth="0" marginheight="0" src="http://interbjafa.allyes.com/main/adfshow?user=Interbjafa%7Csohu_dual_core2%7CDigital_inner_page_PIP_350_250&amp;db=interbjafa&amp;border=0&amp;local=yes" frameborder="0" width="350" scrolling="no" height="250">
&amp;ltnoscript&amp;gt&amp;lta
HREF="http://interbjafa.allyes.com/main/adfclick?user=Interbjafa|sohu_dual_core2|Digital_inner_page_PIP_350_250&amp;ampdb=interbjafa"&gt;ltimg
SRC="/newsfile/28000-28999/28170/060530170836850.gif" WIDTH=350
HEIGHT=250 BORDER=0&gt;lt/a&gt;lt/noscript&gt;</iframe></td></tr></tbody></table></td></tr><tr><td></td></tr></tbody></table><br /><br />　　const关键字至少有下列n个作用：<br /><br />　　（1）欲阻止一个变量被改变，可以使用const关键字。在定义该const变量时，通常需要对它进行初始化，因为以后就没有机会再去改变它了；<br /><br />　　（2）对指针来说，可以指定指针本身为const，也可以指定指针所指的数据为const，或二者同时指定为const；<br /><br />　　（3）在一个函数声明中，const可以修饰形参，表明它是一个输入参数，在函数内部不能改变其值；<br /><br />　　（4）对于类的成员函数，若指定其为const类型，则表明其是一个常函数，不能修改类的成员变量；<br /><br />　　（5）对于类的成员函数，有时候必须指定其返回值为const类型，以使得其返回值不为“左值”。例如：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>const classA operator*(const classA&amp; a1,const classA&amp; a2);</td></tr></tbody></table><br />　　operator*的返回结果必须是一个const对象。如果不是，这样的变态代码也不会编译出错：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>classA a, b, c;<br />(a * b) = c; // 对a*b的结果赋值</td></tr></tbody></table><br />　　操作(a * b) = c显然不符合编程者的初衷，也没有任何意义。<br /><br />　　剖析：<br /><br />　　惊讶吗？小小的static和const居然有这么多功能，我们能回答几个？如果只能回答1~2个，那还真得闭关再好好修炼修炼。<br /><br />　　这个题可以考查面试者对程序设计知识的掌握程度是初级、中级还是比较深入，没有一定的知识广度和深度，不可能对这个问题给出全面的解答。大多数人只能回答出static和const关键字的部分功能。<br /><br />　　4.技巧题<br /><br />　　试题1：请写一个C函数，若处理器是Big_endian的，则返回0；若是Little_endian的，则返回1<br /><br />　　解答：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>int checkCPU()<br />{<br />　{<br />　　union w<br />　　{ <br />　　　int a;<br />　　　char b;<br />　　} c;<br />　　c.a = 1;<br />　　return (c.b == 1);<br />　}<br />}</td></tr></tbody></table><br />　　剖析：<br /><br />　　嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节，而Big-endian模式对操作数的存放方式是从高字节到低字节。例如，16bit宽的数0x1234在Little- endian模式CPU内存中的存放方式（假设从地址0x4000开始存放）为：<br /><br /><table cellspacing="0" cellpadding="0" width="72%" align="center" border="1"><tbody><tr><td>内存地址</td><td>存放内容</td></tr><tr><td>0x4000</td><td>0x34</td></tr><tr><td>0x4001</td><td>0x12</td></tr></tbody></table><br />　　而在Big-endian模式CPU内存中的存放方式则为：<br /><br /><table cellspacing="0" cellpadding="0" width="72%" align="center" border="1"><tbody><tr><td>内存地址</td><td>存放内容</td></tr><tr><td>0x4000</td><td>0x12</td></tr><tr><td>0x4001</td><td>0x34</td></tr></tbody></table><br />　　32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式（假设从地址0x4000开始存放）为：<br /><br /><table cellspacing="0" cellpadding="0" width="72%" align="center" border="1"><tbody><tr><td>内存地址</td><td>存放内容</td></tr><tr><td>0x4000</td><td>0x78</td></tr><tr><td>0x4001</td><td>0x56</td></tr><tr><td>0x4002</td><td>0x34</td></tr><tr><td>0x4003</td><td>0x12</td></tr></tbody></table><br />　　而在Big-endian模式CPU内存中的存放方式则为：<br /><br /><table cellspacing="0" cellpadding="0" width="72%" align="center" border="1"><tbody><tr><td>内存地址</td><td>存放内容</td></tr><tr><td>0x4000</td><td>0x12</td></tr><tr><td>0x4001</td><td>0x34</td></tr><tr><td>0x4002</td><td>0x56</td></tr><tr><td>0x4003</td><td>0x78</td></tr></tbody></table><br />　　联合体union的存放顺序是所有成员都从低地址开始存放，面试者的解答利用该特性，轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答，那简直就是一个天才的程序员。<br /><br />　　试题2：写一个函数返回1+2+3+…+n的值（假定结果不会超过长整型变量的范围） <br /><br />　　解答：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>int Sum( int n )<br />{ <br />　return ( (long)1 + n) * n / 2;　　//或return (1l + n) * n / 2;<br />}</td></tr></tbody></table><br />　　剖析：<br />　<br />　　对于这个题，只能说，也许最简单的答案就是最好的答案。下面的解答，或者基于下面的解答思路去优化，不管怎么“折腾”，其效率也不可能与直接return ( 1 l + n ) * n / 2相比！ <br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>int Sum( int n )<br />{<br />　long sum = 0;<br />　for( int i=1; i&lt;=n; i++ )<br />　{<br />　　sum += i;<br />　}<br />　return sum;<br />} </td></tr></tbody></table><br />　　所以程序员们需要敏感地将数学等知识用在程序设计中。 </div>
<img src ="http://www.blogjava.net/faintbear/aggbug/60321.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2006-07-27 14:08 <a href="http://www.blogjava.net/faintbear/archive/2006/07/27/60321.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> c 语言难点分析整理 (转)</title><link>http://www.blogjava.net/faintbear/archive/2006/07/18/58871.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Tue, 18 Jul 2006 15:29:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2006/07/18/58871.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/58871.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2006/07/18/58871.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/58871.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/58871.html</trackback:ping><description><![CDATA[
		<table height="100%" cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td style="PADDING-RIGHT: 10px" width="74">
								</td>
								<td valign="top" width="100%">
										<table height="100%" cellspacing="0" cellpadding="0" width="100%" border="0">
												<tbody>
														<tr>
																<td class="artitle" valign="top" colspan="2">c 语言难点分析整理</td>
														</tr>
														<tr class="text" valign="top">
																<td align="left">原创：imy </td>
																<td class="text" valign="top" align="right">2003年9月10日 </td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
						<tr>
								<td class="arcontent" colspan="3">
										<br />
								</td>
						</tr>
				</tbody>
				<style type="text/css">
						<!--
.text {
	padding: 7px;
}
-->
				</style>
这篇文章主要是介绍一些在复习C语言的过程中笔者个人认为比较重点的地方，较好的掌握这些重点会使对C的运用更加得心应手。此外会包括一些细节、易错的地方。涉及的主要内容包括：变量的作用域和存储类别、函数、数组、字符串、指针、文件、链表等。一些最基本的概念在此就不多作解释了，仅希望能有只言片语给同是C语言初学者的学习和上机过程提供一点点的帮助。<br /><p><strong>变量作用域和存储类别：</strong></p><p>了解了基本的变量类型后，我们要进一步了解它的存储类别和变量作用域问题。</p><table cellspacing="1" width="100%" bgcolor="#000000" border="0"><tbody><tr class="text"><td class="text" width="21%" bgcolor="#cccccc" height="22">变量类别</td><td class="text" width="79%" bgcolor="#cccccc">子类别</td></tr><tr class="text"><td class="text" bgcolor="#e0e0e0" rowspan="3">局部变量</td><td class="text" bgcolor="#ffffff">静态变量（离开函数，变量值仍保留）</td></tr><tr bordercolor="#000000" bgcolor="#e0e0e0"><td class="text" bgcolor="#ffffff">自动变量</td></tr><tr bordercolor="#000000" bgcolor="#e0e0e0"><td class="text" bgcolor="#ffffff">寄存器变量</td></tr><tr class="text"><td class="text" bgcolor="#e0e0e0" rowspan="2">全局变量</td><td class="text" bgcolor="#ffffff">静态变量（只能在本文件中用）</td></tr><tr bordercolor="#000000" bgcolor="#e0e0e0"><td class="text" bgcolor="#ffffff">非静态变量（允许其他文件使用）</td></tr></tbody></table><p>换一个角度</p><table cellspacing="1" width="100%" bgcolor="#000000" border="0"><tbody><tr class="text"><td class="text" bgcolor="#cccccc" height="19">变量类别</td><td class="text" bgcolor="#cccccc">子类别</td></tr><tr class="text"><td class="text" bgcolor="#e0e0e0" rowspan="3"><font color="#000000">静态存储变量</font></td><td class="text" bgcolor="#ffffff"><font color="#000000">静态局部变量（函数）</font></td></tr><tr><td class="text" bgcolor="#ffffff"><font color="#000000">静态全局变量（本文件）</font></td></tr><tr><td class="text" bgcolor="#ffffff"><font color="#000000">非静态全局/外部变量（其他文件引用）</font></td></tr><tr class="text"><td class="text" bgcolor="#e0e0e0" rowspan="3">动态存储变量</td><td class="text" bgcolor="#ffffff" height="19">自动变量</td></tr><tr><td class="text" bgcolor="#ffffff" height="19">寄存器变量</td></tr><tr><td class="text" bgcolor="#ffffff" height="20">形式参数</td></tr></tbody></table><p>extern型的存储变量在处理多文件问题时常能用到，在一个文件中定义extern型的变量即说明这个变量用的是其他文件的。顺便说一下，笔者在做课设时遇到out of memory的错误，于是改成做多文件，再把它include进来（注意自己写的*.h要用“”不用&lt;&gt;），能起到一定的效用。static型的在读程序写结果的试题中是个考点。多数时候整个程序会出现多个定义的变量在不同的函数中，考查在不同位置同一变量的值是多少。主要是遵循一个原则，只要本函数内没有定义的变量就用全局变量（而不是main里的），全局变量和局部变量重名时局部变量起作用，当然还要注意静态与自动变量的区别。 </p><p><font size="+1"><strong>函数：</strong></font></p><p>对于函数最基本的理解是从那个叫main的单词开始的，一开始总会觉得把语句一并写在main里不是挺好的么，为什么偏择出去。其实这是因为对函数还不够熟练，否则函数的运用会给我们编程带来极大的便利。我们要知道函数的返回值类型，参数的类型，以及调用函数时的形式。事先的函数说明也能起到一个提醒的好作用。所谓形参和实参，即在调用函数时写在括号里的就是实参，函数本身用的就是形参，在画流程图时用平行四边形表示传参。</p><p>函数的另一个应用例子就是递归了，笔者开始比较头疼的问题，反应总是比较迟钝，按照老师的方法，把递归的过程耐心准确的逐级画出来，学习的效果还是比较好的，会觉得这种递归的运用是挺巧的，事实上，著名的八皇后、汉诺塔等问题都用到了递归。</p><table class="code" height="214" width="102%" bgcolor="#e0e0e0" border="0"><tbody><tr><td height="210">例子：<br /><font color="#3333ff">long</font> fun(<font color="#3333ff">int</font> n)<br />{<br /><font color="#3333ff">long</font> s;<br /><font color="#3333ff">if</font>(n==1||n==2) s=2;<br />   <font color="#3333ff">else</font> s=n<font color="#000000">-fun(n-1)</font>;<br /><font color="#3333ff">return</font><font color="#000000"> s</font>;<br />}<br />main()<br />{<br />printf("<font color="#ff33cc">%ld</font>",fun(4));<br />}</td></tr></tbody></table><p><img height="169" src="http://beastie.frontfree.net/articles/pages/0000000779/pic.gif" width="193" /></p><p><font size="+1"><strong>数组：</strong></font></p><p>分为一维数组和多维数组，其存储方式画为表格的话就会一目了然，其实就是把相同类型的变量有序的放在一起。因此，在处理比较多的数据时（这也是大多数的情况）数组的应用范围是非常广的。</p><p>具体的实际应用不便举例，而且绝大多数是与指针相结合的，笔者个人认为学习数组在更大程度上是为学习指针做一个铺垫。作为基础的基础要明白几种基本操作：即数组赋值、打印、排序（冒泡排序法和选择排序法）、查找。这些都不可避免的用到循环，如果觉得反应不过来，可以先一点点的把循环展开，就会越来越熟悉，以后自己编写一个功能的时候就会先找出内在规律，较好的运用了。另外数组做参数时，一维的[]里可以是空的，二维的第一个[]里可以是空的但是第二个[]中必须规定大小。</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0">冒泡法排序函数：<br /><font color="#3333ff">void</font> bubble(<font color="#3333ff">int</font><font color="#000000">a[]</font>,<font color="#3333ff">int</font> n)<br />{<br /><font color="#3333ff">int</font> i,j,k;<br /><font color="#3333ff">for</font>(i=1,i&lt;n;i++)<br />   <font color="#3333ff">for</font>(j=0;j&lt;<font color="#000000">n-i-1;</font>j++)<br />   <font color="#3333ff">if</font>(a[j]&gt;a[j+1])<br />    {<font color="#000000"><br />    k=a[j];<br />       a[j]=a[j+1];<br />       a[j+1]=k;<br />       }</font><br />}<br /><br />选择法排序函数：<br /><font color="#3333ff">void</font> sort(<font color="#3333ff">int</font><font color="#000000">a[]</font>,<font color="#3333ff">int</font> n)<br />{<br /><font color="#3333ff">int</font> i,j,k,t;<br /><font color="#3333ff">for</font>(i=0,i&lt;<font color="#000000">n-1</font>;i++)<br />   {<br /><font color="#ff0000">  <font color="#000000"> k=i</font></font><font color="#000000">;</font><br />   <font color="#3333ff">for</font>(<font color="#000000">j=i+1</font>;j&lt;n;j++)<br />      <font color="#3333ff">if</font>(a[k]&lt;a[j]) <font color="#000000">k=j</font>;<br />      <font color="#3333ff">if</font>(<font color="#000000">k!=i</font>)<br />         {<br />         t=a[i];<br />         a[i]=a[k];<br />         a[k]=t;<br />         }<br />   } <br />}<br /><br />折半查找函数（原数组有序）：<br /><font color="#3333ff">void</font> search(<font color="#3333ff">int</font><font color="#000000">a[]</font>,<font color="#3333ff">int</font> n,<font color="#3333ff">int</font> x)<br />{<br /><font color="#3333ff">int</font> left=0,right=n-1,mid,flag=0;<br /><font color="#3333ff">while</font>((flag==0)&amp;&amp;(left&lt;=right))<br />   {<br /><font color="#ff0000">   <font color="#000000">mid=(left+right)/2</font></font><font color="#000000">;</font><br />   <font color="#3333ff">if</font>(x==a[mid])<br />      {<br />      printf("<font color="#ff33cc">%d%d</font>",x,mid);<br />      flag =1;<br />      }<br />      <font color="#3333ff">else</font><font color="#3333ff">if</font>(x&lt;a[mid]) <font color="#000000">right=mid-1;</font><br />                   <font color="#3333ff">else</font><font color="#000000">left=mid+1</font>;<br />   }<br />}</td></tr></tbody></table><p>相关常用的算法还有<strong>判断回文，求阶乘，Fibanacci数列，任意进制转换，杨辉三角形计算</strong>等等<strong>。</strong></p><p><font size="+1"><strong>字符串：</strong></font></p><p>字符串其实就是一个数组（指针），在scanf的输入列中是不需要在前面加“&amp;”符号的，因为字符数组名本身即代表地址。值得注意的是字符串末尾的‘\0’，如果没有的话，字符串很有可能会不正常的打印。另外就是字符串的定义和赋值问题了，笔者有一次的比较综合的上机作业就是字符串打印老是乱码，上上下下找了一圈问题，最后发现是因为</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#3333ff">char</font> *name;</td></tr></tbody></table><p>而不是</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#3333ff">char</font> name[10];</td></tr></tbody></table><p>前者没有说明指向哪儿，更没有确定大小，导致了乱码的错误，印象挺深刻的。 </p><p>另外，字符串的赋值也是需要注意的，如果是用字符指针的话，既可以定义的时候赋初值，即</p><table class="code" width="100%"><tbody><tr><td class="text" bgcolor="#e0e0e0"><font color="#3333ff">char</font> *a="Abcdefg";</td></tr></tbody></table><p>也可以在赋值语句中赋值，即</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#3333ff">char</font> *a;<br />a="<font color="#ff33cc">Abcdefg</font>"; </td></tr></tbody></table><p>但如果是用字符数组的话，就只能在定义时整体赋初值，即char a[5]={"abcd"};而不能在赋值语句中整体赋值。 </p><p>常用字符串函数列表如下，要会自己实现：</p><table cellspacing="1" width="100%" bgcolor="#000000" border="0"><tbody><tr><td class="text" width="21%" bgcolor="#cccccc"><strong>函数作用</strong></td><td class="text" width="30%" bgcolor="#cccccc"><strong>函数调用形式</strong></td><td class="text" width="49%" bgcolor="#cccccc"><strong>备注</strong></td></tr><tr><td class="text" bgcolor="#ffffff">字符串拷贝函数</td><td class="text" bgcolor="#ffffff">strcpy(char*,char *)</td><td class="text" bgcolor="#ffffff">后者拷贝到前者</td></tr><tr><td class="text" bgcolor="#e0e0e0">字符串追加函数</td><td class="text" bgcolor="#e0e0e0">strcat(char*,char *)</td><td class="text" bgcolor="#e0e0e0">后者追加到前者后，返回前者，因此前者空间要足够大</td></tr><tr><td class="text" bgcolor="#ffffff">字符串比较函数</td><td class="text" bgcolor="#ffffff">strcmp(char*,char *)</td><td class="text" bgcolor="#ffffff">前者等于、小于、大于后者时，返回0、正值、负值。注意，不是比较长度，是比较字符ASCII码的大小，可用于按姓名字母排序等。</td></tr><tr><td class="text" bgcolor="#e0e0e0">字符串长度</td><td class="text" bgcolor="#e0e0e0">strlen(char *)</td><td class="text" bgcolor="#e0e0e0">返回字符串的长度，不包括'\0'.转义字符算一个字符。</td></tr><tr><td class="text" bgcolor="#ffffff">字符串型-&gt;整型</td><td class="text" bgcolor="#ffffff" colspan="2">atoi(char *)</td></tr><tr><td class="text" bgcolor="#e0e0e0" rowspan="2">整型-&gt;字符串型</td><td class="text" bgcolor="#e0e0e0">itoa(int,char *,int)</td><td class="text" bgcolor="#e0e0e0">做课设时挺有用的</td></tr><tr><td class="text" bgcolor="#e0e0e0">sprintf(char *,格式化输入）</td><td class="text" bgcolor="#e0e0e0">赋给字符串，而不打印出来。课设时用也比较方便</td></tr></tbody></table><p><strong>注：</strong>对字符串是不允许做==或！=的运算的，只能用字符串比较函数</p><p><font size="+1"><strong>指针：</strong></font></p><p>指针可以说是C语言中最关键的地方了，其实这个“指针”的名字对于这个概念的理解是十分形象的。首先要知道，指针变量的值（即指针变量中存放的值）是指针（即地址）。指针变量定义形式中：基本类型 *指针变量名 中的“*”代表的是这是一个指向该基本类型的指针变量，而不是内容的意思。在以后使用的时候，如*ptr=a时，“*”才表示ptr所指向的地址里放的内容是a。</p><p>指针比较典型又简单的一应用例子是两数互换，看下面的程序，</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td>swap(<font color="#3333ff">int</font><font color="#ff0000"><font color="#000000">c,</font><font color="#3333ff">int</font><font color="#000000">d</font></font>)<br />{<br /><font color="#3333ff">int</font> t;<br />t=c;<br />c=d;<br />d=t;<br />}<br />main()<br />{<br /><font color="#3333ff">int</font> a=2,b=3;<br />swap(<font color="#000000">a,b</font>);<br />printf(“%d,%d”,a,b);<br />}</td></tr></tbody></table><p>这是不能实现a和b的数值互换的，实际上只是形参在这个函数中换来换去，对实参没什么影响。现在，用指针类型的数据做为参数的话，更改如下：</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#000000">swap(#3333FF *p1,<font color="#3333ff">int</font> *p2)<br />{<br /><font color="#3333ff">int</font> t;<br />t=*p1;<br />*p1=*p2;<br />*p2=t;<br />}<br />main()<br />{<br /><font color="#3333ff">int</font> a=2,b=3;<br /><font color="#3333ff">int</font> *ptr1,*ptr2;<br />ptr1=&amp;a;<br />ptr2=&amp;b;<br />swap(prt1,ptr2);<br />printf(“%d,%d”,a,b);<br />}</font></td></tr></tbody></table><p>这样在swap中就把p1,p2 的内容给换了，即把a，b的值互换了。</p><p>指针可以执行<strong>增、减运算</strong>，结合++运算符的法则，我们可以看到:</p><table cellspacing="1" cellpadding="0" width="100%" bgcolor="#000000" border="0"><tbody><tr class="text" bgcolor="#cccccc"><td width="6%" bgcolor="#e0e0e0"><strong>*++s</strong></td><td width="94%" bgcolor="#ffffff"><p>取指针变量加1以后的内容</p></td></tr><tr class="text" bgcolor="#cccccc"><td bgcolor="#e0e0e0"><strong>*s++</strong></td><td bgcolor="#ffffff">取指针变量所指内容后s再加1</td></tr><tr class="text" bgcolor="#cccccc"><td bgcolor="#e0e0e0"><strong>(*s)++</strong></td><td bgcolor="#ffffff">指针变量指的内容加1</td></tr></tbody></table><p><strong>指针和数组</strong>实际上几乎是一样的，数组名可以看成是一个常量指针，一维数组中ptr=&amp;b[0]则下面的表示法是等价的：</p><p><strong>a[3]等价于*(a+3)<br />ptr[3]等价于*(ptr+3) </strong></p><p>下面看一个用指针来自己实现atoi（字符串型-&gt;整型）函数：</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#3333ff">int</font> atoi(<font color="#3333ff">char</font> *s)<br />{<br /><font color="#3333ff">int</font> sign=1,m=0;<br /><font color="#3333ff">if</font>(*s=='+'||*s=='-') <font color="#006600">/*判断是否有符号*/</font><br />sign=(*s++=='+'<font color="#000000">)?1:-1;</font><font color="#006600">/*用到三目运算符*/</font><br /><font color="#3333ff">while</font>(<font color="#000000">*s!='\0'</font>) <font color="#006600">/*对每一个字符进行操作*/</font><br />   {<br />   <font color="#000000">m=m*10+(*s-'0');<br />   s++;</font><font color="#006600">/*指向下一个字符*/</font><br />   }<br /><font color="#3333ff">return</font> m*sign;<br />}</td></tr></tbody></table><p>指向多维数组的指针变量也是一个比较广泛的运用。例如数组a[3][4]，a代表的实际是整个二维数组的首地址，即第0行的首地址，也就是一个指针变量。而a+1就不是简单的在数值上加上1了，它代表的不是a[0][1]，而是第1行的首地址，&amp;a[1][0]。</p><p>指针变量常用的用途还有把指针作为参数传递给其他函数，即<strong>指向函数的指针</strong>。<br />看下面的几行代码：</p><table class="code" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td><font color="#3333ff">void</font> Input(ST *);<br /><font color="#3333ff">void</font> Output(ST *);<br /><font color="#3333ff">void</font> Bubble(ST *);<br /><font color="#3333ff">void</font> Find(ST *);<br /><font color="#3333ff">void</font> Failure(ST *);<br /><font color="#006600">/*函数声明：这五个函数都是以一个指向ST型（事先定义过）结构的指针变量作为参数，无返回值。*/</font><font color="#009900"><br /></font><br /><font color="#3333ff">void</font><font color="#000000">(*process[5])(ST *)</font>={Input,Output,Bubble,Find,Failure};<br /><font color="#006600">/*process被调用时提供5种功能不同的函数共选择(指向函数的指针数组）*/</font><br /><br />printf(<font color="#ff33cc">"\nChoose:\n?"</font>);<br />scanf(<font color="#ff33cc">"%d"</font>,&amp;choice);<br /><font color="#3333ff">if</font>(choice&gt;=0&amp;&amp;choice&lt;=4)<br />(*process[<font color="#000000">choice</font>])(a); <font color="#006600">/*调用相应的函数实现不同功能*;/ </font></td></tr></tbody></table><p>总之，指针的应用是非常灵活和广泛的，不是三言两语能说完的，上面几个小例子只是个引子，实际编程中，会逐渐发现运用指针所能带来的便利和高效率。 </p><p><font size="+1"><strong>文件：</strong></font></p><table class="unnamed1" cellspacing="1" cellpadding="0" width="100%" bgcolor="#000000" border="0"><!--DWLayoutTable--><tbody><tr bgcolor="#cccccc"><td class="text" width="40%"><strong>函数调用形式</strong></td><td class="text"><strong>说明</strong></td></tr><tr bgcolor="#ffffff"><td class="text">fopen("路径","打开方式")</td><td class="text">打开文件</td></tr><tr bgcolor="#e0e0e0"><td class="text">fclose(FILE *)</td><td class="text">防止之后被误用</td></tr><tr bgcolor="#ffffff"><td class="text">fgetc(FILE *)</td><td class="text">从文件中读取一个字符</td></tr><tr bgcolor="#e0e0e0"><td class="text">fputc(ch,FILE *)</td><td class="text">把ch代表的字符写入这个文件里</td></tr><tr bgcolor="#ffffff"><td class="text">fgets(FILE *)</td><td class="text">从文件中读取一行</td></tr><tr bgcolor="#e0e0e0"><td class="text">fputs(FILE *)</td><td class="text">把一行写入文件中</td></tr><tr bgcolor="#ffffff"><td class="text">fprintf(FILE *,"格式字符串",输出表列）</td><td class="text">把数据写入文件</td></tr><tr bgcolor="#e0e0e0"><td class="text">fscanf(FILE *,"格式字符串",输入表列）</td><td class="text">从文件中读取</td></tr><tr bgcolor="#ffffff"><td class="text">fwrite（地址，sizeof（），n，FILE *）</td><td class="text">把地址中n个sizeof大的数据写入文件里</td></tr><tr bgcolor="#e0e0e0"><td class="text">fread（地址，sizeof（），n，FILE *）</td><td class="text">把文件中n个sizeof大的数据读到地址里</td></tr><tr bgcolor="#ffffff"><td class="text">rewind（FILE *）</td><td class="text">把文件指针拨回到文件头</td></tr><tr bgcolor="#e0e0e0"><td class="text">fseek（FILE *，x，0/1/2）</td><td class="text">移动文件指针。第二个参数是位移量，0代表从头移，1代表从当前位置移，2代表从文件尾移。</td></tr><tr bgcolor="#ffffff"><td class="text">feof(FILE *)</td><td class="text">判断是否到了文件末尾</td></tr></tbody></table><br /><table cellspacing="1" cellpadding="0" width="100%" bgcolor="#000000" border="0"><!--DWLayoutTable--><tbody><tr bgcolor="#e0e0e0"><td class="text" width="22%"><strong>文件打开方式</strong></td><td class="text" width="78%"><strong>说明</strong></td></tr><tr bgcolor="#ffffff"><td class="text"><strong>r</strong></td><td class="text">打开只能读的文件</td></tr><tr bgcolor="#ffffff"><td class="text"><strong>w</strong></td><td class="text">建立供写入的文件，如果已存在就抹去原有数据</td></tr><tr bgcolor="#ffffff"><td class="text"><strong>a</strong></td><td class="text">打开或建立一个把数据追加到文件尾的文件</td></tr><tr bgcolor="#ffffff"><td class="text"><strong>r+</strong></td><td class="text">打开用于更新数据的文件</td></tr><tr bgcolor="#ffffff"><td class="text"><strong>w+</strong></td><td class="text">建立用于更新数据的文件，如果已存在就抹去原有数据</td></tr><tr bgcolor="#ffffff"><td class="text"><strong>a+</strong></td><td class="text">打开或建立用于更新数据的文件，数据追加到文件尾</td></tr></tbody></table><p><strong>注：</strong>以上用于文本文件的操作，如果是二进制文件就在上述字母后加“b”。</p><p>我们用文件最大的目的就是能让数据保存下来。因此在要用文件中数据的时候，就是要把数据读到一个结构（一般保存数据多用结构，便于管理）中去，再对结构进行操作即可。例如，文件aa.data中存储的是30个学生的成绩等信息，要遍历这些信息，对其进行成绩输出、排序、查找等工作时，我们就把这些信息先读入到一个结构数组中，再对这个数组进行操作。如下例：</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0"><font color="#ff0000">#include</font>&lt;stdio.h&gt;<br /><font color="#ff0000">#include</font>&lt;stdlib.h&gt;<br /><font color="#ff0000">#define</font> N 30 <p><font color="#3333ff">typedef struct</font> student <font color="#006600">/*定义储存学生成绩信息的数组*/</font><br />{<br /><font color="#3333ff">char</font> *name;<br /><font color="#3333ff">int</font> chinese;<br /><font color="#3333ff">int</font> maths;<br /><font color="#3333ff">int</font> phy;<br /><font color="#3333ff">int</font> total;<br />}<font color="#ff00ff">ST</font>;</p><p>main()<br />{<br /><font color="#ff00ff">ST a[N]</font>; <font color="#006600">/*存储N个学生信息的数组*/</font><br /><font color="#ff00ff">FILE *fp</font>;<br /><font color="#3333ff">void</font><font color="#000000">(*process[3])(ST *)</font>={Output,Bubble,Find}; <font color="#006600">/*实现相关功能的三个函数*/</font><br /><font color="#3333ff">int</font> choice,i=0;<br />Show();<br />printf(<font color="#ff00ff">"\nChoose:\n?"</font>);<br />scanf(<font color="#ff00ff">"%d"</font>,&amp;choice);<br /><font color="#3333ff">while</font>(choice&gt;=0&amp;&amp;choice&lt;=2)<br />   {<br />   <font color="#000000">fp=fopen(</font><font color="#ff0000"><font color="#ff00ff">"aa.dat"</font><font color="#000000">,</font><font color="#ff00ff">"rb"</font><font color="#000000">)</font></font>; <br />   <font color="#3333ff">for</font>(i=0;<font color="#000000">i&lt;N</font>;i++)<br />  <font color="#ff0000">    <font color="#000000">fread(&amp;a[i],</font><font color="#3333ff">sizeof</font><font color="#000000">(ST),1,fp);</font></font><font color="#006600">/*把文件中储存的信息逐个读到数组中去*/</font><br />   fclose(fp);<br />   (*process[choice])<font color="#000000">(a</font>); <font color="#006600">/*前面提到的指向函数的指针，选择操作*/</font><br />   printf(<font color="#ff00ff">"\n"</font>);<br />   Show();<br />   printf(<font color="#ff00ff">"\n?"</font>);<br />   scanf(<font color="#ff00ff">"%d"</font>,&amp;choice);<br />   }<br />}</p><p><font color="#3333ff">void</font> Show()<br />{<br />printf(<font color="#ff00ff">"\n****Choices:****\n0.Display the data form\n1.Bubble it according to the total score\n2.Search\n3.Quit!\n"</font>);<br />}</p><p><font color="#3333ff">void</font> Output(<font color="#000000">ST *a</font>) <font color="#006600">/*将文件中存储的信息逐个输出*/</font><br />{<br /><font color="#3333ff">int</font> i,t=0;<br />printf(<font color="#ff00ff">"Name Chinese Maths Physics Total\n"</font>);<br /><font color="#3333ff">for</font>(i=0;i&lt;N;i++)<br />   {<br />   t=a[i].chinese+a[i].maths+a[i].phy;<br />   a[i].total=t;<br />   printf(<font color="#ff00ff">"%4s%8d%8d%8d%8d\n"</font>,a[i].name,a[i].chinese,a[i].maths,a[i].phy,a[i].total);<br />   }<br />}</p><p><font color="#3333ff">void</font> Bubble(<font color="#000000">ST *a)</font><font color="#006600">/*对数组进行排序，并输出结果*/</font><br />{<br /><font color="#3333ff">int</font> i,pass;<br /><font color="#000000">ST m;</font><br /><font color="#3333ff">for</font>(pass=0;pass<font color="#000000">&lt;N-1</font>;pass++)<br />   <font color="#3333ff">for</font>(i=0;i<font color="#000000">&lt;N-1</font>;i++) <br />      <font color="#3333ff">if</font>(a[i].total&lt;a[i+1].total)<br />         {<br />         m=a[i]; <font color="#006600">/*结构互换*/</font><br />         a[i]=a[i+1];<br />         a[i+1]=m; <br />         }<br />Output(<font color="#000000">a</font>);<br />}</p><p><font color="#3333ff">void</font> Find<font color="#000000">(ST *a)</font><br />{<br /><font color="#3333ff">int</font> i,t=1;<br /><font color="#3333ff">char</font> m[20];<br />printf(<font color="#ff00ff">"\nEnter the name you want:"</font>);<br />scanf(<font color="#ff00ff">"%s"</font>,m);<br /><font color="#3333ff">for</font>(i=0;i&lt;N;i++)<br />   <font color="#3333ff">if</font>(<font color="#000000">!strcmp(m,a[i].name))</font><font color="#006600">/*根据姓名匹配情况输出查找结果*/</font><br />   {<br />   printf(<font color="#ff00ff">"\nThe result is:\n%s, Chinese:%d, Maths:%d,     Physics:%d,Total:%d\n"</font>,m,a[i].chinese,a[i].maths,a[i].phy,a[i].total);<br />  <font color="#ff0000"> <font color="#000000">t=0</font></font><font color="#000000">;</font><br />   }<br /><font color="#3333ff">if</font>(<font color="#000000">t</font>)<br />   printf(<font color="#ff00ff">"\nThe name is not in the list!\n"</font>);<br />}</p></td></tr></tbody></table><p><font size="+1"><strong>链表：</strong></font><br />链表是C语言中另外一个难点。牵扯到结点、动态分配空间等等。用结构作为链表的结点是非常适合的，例如：</p><table class="code" cellspacing="4" cellpadding="0" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0"><font color="#0000ff">struct</font> node <br />{<br /><font color="#0000ff">int</font> data;<br /><font color="#0000ff">struct</font> node *next;<br />};</td></tr></tbody></table><p>其中next是指向自身所在结构类型的指针，这样就可以把一个个结点相连，构成链表。</p><p>链表结构的一大优势就是动态分配存储，不会像数组一样必须在定义时确定大小，造成不必要的浪费。用malloc和free函数即可实现开辟和释放存储单元。其中，malloc的参数多用sizeof运算符计算得到。</p><p>链表的基本操作有：<strong>正、反向建立链表；输出链表；删除链表中结点；在链表中插入结点</strong>等等，都是要熟练掌握的，初学者通过<strong>画图</strong>的方式能比较形象地理解建立、插入等实现的过程。</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0"><font color="#3333ff">typedef struct</font> node<br />{<br /><font color="#3333ff">char</font> data;<br /><font color="#3333ff">struct</font> node *next;<br />}<font color="#000000">NODE</font>; <font color="#006600">/*结点*/</font><br /><br />正向建立链表：<br />NODE *create()<br />{<br /><font color="#3333ff">char</font> ch=<font color="#ff00ff">'a'</font>;<br />NODE *p,*h=NULL,*q=NULL;<br /><font color="#3333ff">while</font>(ch&lt;<font color="#ff00ff">'z'</font>)<br />   {<br />   p=<font color="#000000">(NODE *)malloc(</font><font color="#ff0000"><font color="#3333ff">sizeof</font><font color="#000000">(NODE))</font></font>; <font color="#006600">/*强制类型转换为指针*/</font><br />   p-&gt;data=ch;<br />   <font color="#3333ff">if</font>(h==NULL) h=p;<br />      <font color="#3333ff">else</font><font color="#000000">q-&gt;next=p</font>;<br />   ch++;<br /><font color="#000000">   q=p;</font><br />   }<br />q-&gt;next=NULL; <font color="#006600">/*链表结束*/</font><br /><font color="#3333ff">return</font> h;<br />}</td></tr></tbody></table><p><img height="201" src="http://beastie.frontfree.net/articles/pages/0000000779/pic2.gif" width="287" /></p><p>逆向建立：</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0">NODE *create()<br />{<br /><font color="#3333ff">char</font> ch=<font color="#ff00ff">'a'</font>;<br />NODE *p,*h=NULL;<br /><font color="#3333ff">while</font>(ch&lt;=<font color="#ff00ff">'z'</font>)<br />   {<br />   p=<font color="#000000">(NODE *)malloc(</font><font color="#ff0000"><font color="#3333ff">sizeof</font><font color="#000000">(NODE))</font></font>;<br />   p-&gt;data=ch;<br />   p-&gt;next=h; <font color="#006600">/*不断地把head往前挪*/</font><br />   h=p;<br />   ch++;<br />   }<br /><font color="#3333ff">return</font> h;<br />}</td></tr></tbody></table><p><img height="166" src="http://beastie.frontfree.net/articles/pages/0000000779/pic3.gif" width="146" /></p><p>用递归实现链表逆序输出：</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0" c=""><font color="#3333ff">void</font> output(NODE *h)<br />{<br /><font color="#3333ff">if</font>(h!=NULL)<br />   {<br /><font color="#ff0000">   <font color="#000000">output(h-&gt;next)</font></font><font color="#000000">;</font><br />   printf(<font color="#ff00ff">"%c"</font>,h-&gt;data);<br />   }<br />}</td></tr></tbody></table><p>插入结点（已有升序的链表）：</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0">NODE *insert(NODE *h,<font color="#3333ff">int</font> x)<br />{<br />NODE *<font color="#3333ff">new</font>,*front,*current=h;<br /><font color="#3333ff">while</font>(current!=NULL&amp;&amp;(current-&gt;data&lt;x)) <font color="#006600">/*查找插入的位置*/</font><br />   {<br />   front=current;<br />   current=current-&gt;next;<br />   }<br /><font color="#3333ff">new</font>=<font color="#000000">(NODE *)malloc(</font><font color="#ff0000"><font color="#3333ff">sizeof</font><font color="#000000">(NODE))</font></font>;<br /><font color="#3333ff">new</font>-&gt;data=x;<br /><font color="#3333ff">new</font>-&gt;next=current;<br /><font color="#3333ff">if</font>(current==h) <font color="#006600">/*判断是否是要插在表头*/</font><br />   h=<font color="#3333ff">new</font>;<br /><font color="#3333ff">else</font> front-&gt;next=<font color="#3333ff">new</font>;<br /><font color="#3333ff">return</font> h;<br />}</td></tr></tbody></table><p><img height="131" src="http://beastie.frontfree.net/articles/pages/0000000779/pic4.gif" width="196" /></p><p>删除结点：</p><table class="code" width="100%" border="0"><tbody><tr><td bgcolor="#e0e0e0">NODE *<font color="#3333ff">delete</font>(NODE *h,<font color="#3333ff">int</font> x)<br />{<br />NODE *q,*p=h;<br /><font color="#3333ff">while</font>(p!=NULL&amp;&amp;(p-&gt;data!=x))<br />   {<br />   q=p;<br />   p=p-&gt;next;<br />   }<br /><font color="#3333ff">if</font>(p-&gt;data==x) <font color="#006600">/*找到了要删的结点*/</font><br />   {<br />   <font color="#3333ff">if</font>(p==h) <font color="#006600">/*判断是否要删表头*/</font><br />   h=h-&gt;next;<br />      <font color="#3333ff">else</font><font color="#000000">q-&gt;next=p-&gt;next;<br />   free(p); </font><font color="#006600">/*释放掉已删掉的结点*/</font><br />   }<br /><font color="#3333ff">return</font> h;<br />}</td></tr></tbody></table><p><img height="100" src="http://beastie.frontfree.net/articles/pages/0000000779/pic5.gif" width="227" /></p><p>经常有链表相关的程序填空题，做这样的题要注意看下面提到的变量是否定义了，用到的变量是否赋初值了，是否有给分配空间的没有分配空间，最后看看返回值是否正确。</p><p>笔者水平有限，难免有疏漏、错误的地方，浅显之处，还望指正见谅。上述内容仅是个提示作用，并不包括C语言的全部内容。 </p></table>
<img src ="http://www.blogjava.net/faintbear/aggbug/58871.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2006-07-18 23:29 <a href="http://www.blogjava.net/faintbear/archive/2006/07/18/58871.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>链表3</title><link>http://www.blogjava.net/faintbear/archive/2006/07/17/58657.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Mon, 17 Jul 2006 14:56:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2006/07/17/58657.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/58657.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2006/07/17/58657.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/58657.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/58657.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
				<tbody>
						<tr>
								<td style="FONT-SIZE: 18px" valign="bottom" align="middle" width="85%" bgcolor="#dddddd" height="20">
										<strong>
												<font color="#003399" size="4">
														<b>链表的运算(02) </b>
												</font>
										</strong>
								</td>
								<br />
						</tr>
						<tr>
								<td align="middle" width="100%">
										<br />
								</td>
						</tr>
						<tr>
								<td style="FONT-SIZE: 9pt" align="middle" width="100%">发表日期：2003年4月8日    作者：C语言之家搜集整理  已经有2462位读者读过此文</td>
						</tr>
						<tr>
								<td align="middle" width="100%">
										<!--下面的这一句是设置阅读文本区的宽度-->
										<table style="TABLE-LAYOUT: fixed" cellspacing="0" cellpadding="0" width="90%" align="center" border="0">
												<tbody>
														<tr>
																<td align="middle" width="100%">
																</td>
														</tr>
														<tr>
																<td style="WORD-WRAP: break-word">
																		<font class="news">
																				<br />
																				<font color="#0000ff">3.链表节点的插入<br />4.链表节点的删除</font>
																				<br />
																				<br />
																				<br />
																				<br />
																				<font color="#0000ff">3.链表节点的插入</font>
																				<br />解：<br />    1) 首先声明一个新节点供输入要插入节点的内容<br />    2) 由用户输入一个节点内容(Key)，表示欲插入在哪一个节点之后<br />    3) 持续往下一个节点，直到节点内容Key或节点指针为NULL为止(即找不到该节点)<br />    4) 如果该节点不存在，则插入在节点前<br />        New-&gt;Next=Head<br />        Head=New<br />    5) 如果找到该节点，则<br />        New-&gt;Next=Pointer-&gt;Next<br />        Pointer-&gt;Next=New<br />*程序代码如下：<br />#include&lt;stdlib.h&gt;<br />#include&lt;stdio.h&gt;<br />#define Max 10<br />struct List            /*节点结构声明*/<br />{<br />    int Number;<br />    int Total;<br />    struct List *Next;<br />};<br />typedef struct List Node;<br />typedef Node *Link;<br />int Data[2][Max]={1,3,5,7,2,4,6,8,9,0,15,35,10,67,25,65,38,70,30,20};<br />/*插入节点至链表内*/<br />Link Insert_List(Link Head,Link New,int Key)<br />{<br />    Link Pointer;        /*声明节点*/<br />    Pointer=Head;        /*Pointer指针设为首节点*/<br />    while(1)<br />    {<br />        if(Pointer==NULL)    /*插入在首节点前*/<br />        {<br />            New-&gt;Next=Head;<br />            Head=New;<br />            break;<br />        }<br />        if(Pointer-&gt;Number==Key)    /*插入在链表中间或尾端*/<br />        {<br />            New-&gt;Next=Pointer-&gt;Next;<br />            Pointer-&gt;Next=New;<br />            break;<br />        }<br />        Pointer=Pointer-&gt;Next;    /*指向下一个节点*/<br />    }<br />    return Head;<br />}<br />/*输出链表数据*/<br />void Print_List(Link Head)<br />{<br />    Link Pointer;        /*节点声明*/<br />    Pointer=Head;        /*Pointer指针设为首节点*/<br />    while(Pointer!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        printf("[%d,%d]",Pointer-&gt;Number,Pointer-&gt;Total);<br />        Pointer=Pointer-&gt;Next;    /*指向下一个节点*/<br />    }<br />    printf("\n");<br />}<br />/*释放链表*/<br />void Free_List(Link Head)<br />{<br />    Link Pointer;        /*节点声明*/<br />    while(Head!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        Pointer=Head;<br />        Head=Head-&gt;Next;<br />        free(Pointer);<br />    }<br />}<br />/*建立链表*/<br />Link Create_List(Link Head)<br />{<br />    Link New;        /*节点声明*/<br />    Link Pointer;    /*节点声明*/<br />    int i;<br />    Head=(Link)malloc(sizeof(Node));    /*分配内存*/<br />    if(Head==NULL)<br />        printf("Memory allocate Failure!\n");    /*内存分配失败*/<br />    else<br />    {<br />        Head-&gt;Number=Data[0][0];        /*定义首节点数据编号*/<br />        Head-&gt;Total=Data[1][0];<br />        Head-&gt;Next=NULL;<br />        Pointer=Head;        /*Pointer指针设为首节点*/<br />        for(i=1;i&lt;Max;i++)<br />        {<br />            New=(Link)malloc(sizeof(Node));    /*分配内存*/<br />            New-&gt;Number=Data[0][i];<br />            New-&gt;Total=Data[1][i];<br />            New-&gt;Next=NULL;<br />            Pointer-&gt;Next=New;        /*将新节点串连在原列表尾端*/<br />            Pointer=New;            /*列表尾端节点为新节点*/<br />        }<br />    }<br />    return Head;<br />}<br />/*主程序*/<br />void main()<br />{<br />    Link Head;        /*节点声明*/<br />    Link New;<br />    int Key;<br />    Head=Create_List(Head);    /*建立链表*/<br />    if(Head!=NULL)<br />    {<br />        Print_List(Head);    <br />        while(1)<br />        {<br />            printf("Input 0 to Exit\n");    /*数据输入提示*/<br />            New=(Link)malloc(sizeof(Node));    /*分配内存*/<br />            printf("Please input Data number:");<br />            scanf("%d",&amp;New-&gt;Number);<br />            if(New-&gt;Number==0)        /*输入0时结束循环*/<br />                break;<br />            printf("Please input the data total:");<br />            scanf("%d",&amp;New-&gt;Total);<br />            printf("Please input the data number for Insert:");<br />            scanf("%d",&amp;Key);<br />            Head=Insert_List(Head,New,Key);    /*插入节点*/<br />            Print_List(Head);                /*输出链表数据*/<br />        }<br />        Free_List(Head);        /*释放链表*/<br />    }<br />}<br />*程序运行结果如下：<br /><p align="left"></p><p align="center"><img src="http://www.cstudyhome.com/wenzhang06/uploadpic/20034814224081056.gif" onload="javascript:if(this.width&gt;screen.width-333)this.width=screen.width-333" /></p>------------------------------------------------------------------<br /><br /><font color="#0000ff">4.链表节点的删除</font><br />解：<br />    持续往下一个节点查找要删除的节点，直到节点内容找到或节点指针为NULL(即找不到该节点)。<br />    在删除时，必须记录前一个节点的位置(Back)<br />    如果该节点不存在，输出一个节点不存在的提示<br />    如果该节点存在，且是首节点：<br />        Head=Pointer-&gt;Next<br />        free(Pointer)<br />    如果该节点存在，但不是首节点(即链表内节点或尾端节点)，则：<br />        Back-&gt;Next=Pointer-&gt;Next<br />        free(Pointer)<br />*程序代码如下：<br />#include&lt;stdio.h&gt;<br />#include&lt;stdlib.h&gt;<br />#define Max 10<br />struct List            /*节点结构声明 */<br />{<br />    int Number;<br />    int Total;<br />    struct List *Next;<br />};<br />typedef struct List Node;<br />typedef Node *Link;<br />int Data[2][Max]={1,3,5,7,2,4,6,8,9,10,15,35,10,67,25,65,38,70,30,20};<br />/*删除链表内节点*/<br />Link Delete_List(Link Head,int Key)<br />{<br />    Link Pointer;        /*节点声明*/<br />    Link Back;<br />    Pointer=Head;        /*Pointer 指针设为首节点*/<br />    while(1)<br />    {<br />        if(Pointer-&gt;Next==NULL)<br />        {<br />            printf("Not Found!\n");<br />            break;<br />        }<br />        if(Head-&gt;Number==Key)        /*删除首节点*/<br />        {<br />            Head=Pointer-&gt;Next;<br />            free(Pointer);<br />            break;<br />        }<br />        Back=Pointer;<br />        Pointer=Pointer-&gt;Next;    /*指向下一个节点*/<br />        if(Pointer-&gt;Number==Key)    /*插入在链表中间或尾端*/<br />        {<br />            Back-&gt;Next=Pointer-&gt;Next;<br />            free(Pointer);<br />            break;<br />        }<br />    }<br />    return Head;<br />}<br />/*输出链表数据*/<br />void Print_List(Link Head)<br />{<br />    Link Pointer;        /*节点声明*/<br />    Pointer=Head;        /*Pointer指针设为首节点*/<br />    while(Pointer!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        printf("[%d,%d]",Pointer-&gt;Number,Pointer-&gt;Total);<br />        Pointer=Pointer-&gt;Next;    /*指向下一个节点*/<br />    }<br />    printf("\n");<br />}<br />/*释放链表*/<br />void Free_List(Link Head)<br />{<br />    Link Pointer;        /*节点声明*/<br />    while(Head!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        Pointer=Head;<br />        Head=Head-&gt;Next;        /*指向下一个节点*/<br />        free(Pointer);<br />    }<br />}<br />/*建立链表*/<br />Link Create_List(Link Head)<br />{<br />    Link New;        /*节点声明*/<br />    Link Pointer;<br />    int i;<br />    Head=(Link)malloc(sizeof(Node));        /*分本内存*/<br />    if(Head==NULL)<br />        printf("Memory allocate Failure!\n");    /*内存分配失败*/<br />    else<br />    {<br />        Head-&gt;Number=Data[0][0];        /*定义首节点数据编号*/<br />        Head-&gt;Total=Data[1][0];<br />        Head-&gt;Next=NULL;<br />        Pointer=Head;        /*Pointer指针设为首节点*/<br />        for(i=1;i&lt;Max;i++)<br />        {<br />            New=(Link)malloc(sizeof(Node));        /*分配内存*/<br />            New-&gt;Number=Data[0][i];<br />            New-&gt;Total=Data[1][i];<br />            New-&gt;Next=NULL;<br />            Pointer-&gt;Next=New;        /*将新节点串连在原列表尾端*/<br />            Pointer=New;            /*列表尾端节点为新节点*/<br />        }<br />    }<br />    return Head;<br />}<br />/*主程序*/<br />void main()<br />{<br />    Link Head=NULL;        /*节点声明*/<br />    int Key;<br />    Head=Create_List(Head);        /*建立链表*/<br />    if(Head!=NULL)<br />    {<br />        Print_List(Head);<br />        while(1)<br />        {<br />            printf("Input 0 to exit\n");    /*数据输入提示*/<br />            printf("Please input the data number for Delete:");<br />            scanf("%d",&amp;Key);<br />            if(Key==0)        /*时结束循环*/<br />                break;<br />            Head=Delete_List(Head,Key);    /*删除节点*/<br />            Print_List(Head);              /*输出链表*/<br />        }<br />        Free_List(Head);        /*释放链表*/<br />    }<br />}<font color="#0000ff"><br /><br /><font color="#000000">*程序运行结果如下：</font><br /><p align="left"></p><p align="center"><img src="http://www.cstudyhome.com/wenzhang06/uploadpic/2003481518569133.gif" onload="javascript:if(this.width&gt;screen.width-333)this.width=screen.width-333" /></p>   </font></font>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/faintbear/aggbug/58657.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2006-07-17 22:56 <a href="http://www.blogjava.net/faintbear/archive/2006/07/17/58657.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>链表2</title><link>http://www.blogjava.net/faintbear/archive/2006/07/17/58656.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Mon, 17 Jul 2006 14:54:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2006/07/17/58656.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/58656.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2006/07/17/58656.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/58656.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/58656.html</trackback:ping><description><![CDATA[
		<font color="#0000ff">2.设计一个查找链表中的数据的程序<br /></font>解：<br />    单链表中的数据查找，只能采用线性查找法往下一个节点查找。采用线性查找法查找链表中的数据时与数组不同的是，原来数组是用递增数组索引来查找数据，在链表中是往下一个节点查找。<br />程序代码如下：<br />#include&lt;stdio.h&gt;<br />#include&lt;stdlib.h&gt;<br />#define Max 10<br />struct List        /*结节点结构声明*/    <br />{<br />    int Number;<br />    int Total;<br />    struct List *Next;<br />};<br />typedef struct List Node;<br />typedef Node *Link;<br />int Data[2][Max]=              /*初始化数据*/<br />        {3,9,25,5,7,26,65,80,2,6,1050,3850,1000,5670,2250,9650,2380,<br />            1700,3000,2000};<br />int SearchTime=0;        /*查找次数*/<br />/*链表查找*/<br />int List_Search(int Key,Link Head)<br />{<br />    Link Pointer;<br />    Pointer=Head;        /*Pointer指针设为首节点*/<br />    while(Pointer!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        SearchTime++;<br />        if(Pointer-&gt;Number==Key)<br />        {<br />            printf("Data Number: %d\n",Pointer-&gt;Number);<br />            printf("Data Total: %d\n",Pointer-&gt;Total);<br />            return 1;<br />        }<br />        Pointer=Pointer-&gt;Next;      /*指向下一个节点*/<br />    }<br />    return 0;<br />}<br />/*释放链表*/<br />void Free_List(Link Head)<br />{<br />    Link Pointer;        /*节点声明*/<br />    while(Head!=NULL)    /*当节点为NULL结束循环*/<br />    {<br />        Pointer=Head;<br />        Head=Head-&gt;Next;    /*指向下一个节点*/<br />        free(Pointer);<br />    }<br />}<br />/*建立链表*/<br />Link Create_List(Link Head)<br />{<br />    Link New;        /*节点声明*/<br />    Link Pointer;    /*节点声明*/<br />    int i;<br />    Head=(Link)malloc(sizeof(Node));    /*分配内存*/<br />    if(Head==NULL)<br />        printf("Memory allocate Failure!\n");    /*内存分配失败*/<br />    else<br />    {<br />        Head-&gt;Number=Data[0][0];        /*定义首节点数据编号*/<br />        Head-&gt;Total=Data[1][0];<br />        Head-&gt;Next=NULL;<br />        Pointer=Head;            /*Pointer指针设为首节点*/<br />        for(i=1;i&lt;Max;i++)<br />        {<br />            New=(Link)malloc(sizeof(Node));    /*分配内存*/<br />            New-&gt;Number=Data[0][i];<br />            New-&gt;Total=Data[1][i];<br />            New-&gt;Next=NULL;<br />            Pointer-&gt;Next=New;        /*将新节点串连在原列表尾端*/<br />            Pointer=New;              /*列表尾端节点为新节点*/<br />        }<br />    }<br />    return Head;<br />}<br />/*主程序*/<br />void main()<br />{<br />    Link Head=NULL;        /*节点声明*/<br />    int Num;          /*欲查找数据编号*/<br />    Head=Create_List(Head);    /*建立链表*/<br />    if(Head!=NULL)<br />    {<br />        printf("Please input the data number:");<br />        scanf("%d",&amp;Num);<br />        if(List_Search(Num,Head))<br />            printf("Search Time=%d\n",SearchTime);<br />        else<br />            printf("Not Found!\n");<br />        Free_List(Head);        /*释放链表*/<br />    }<br />}<br />*运行结果如下：<br /><p align="left"></p><p align="center"><img src="http://www.cstudyhome.com/wenzhang06/uploadpic/20034813101264722.gif" onload="javascript:if(this.width&gt;screen.width-333)this.width=screen.width-333" /></p><img src ="http://www.blogjava.net/faintbear/aggbug/58656.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2006-07-17 22:54 <a href="http://www.blogjava.net/faintbear/archive/2006/07/17/58656.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>链表</title><link>http://www.blogjava.net/faintbear/archive/2006/07/17/58655.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Mon, 17 Jul 2006 14:53:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2006/07/17/58655.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/58655.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2006/07/17/58655.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/58655.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/58655.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0">
				<tbody>
						<tr>
								<td style="FONT-SIZE: 18px" valign="bottom" align="middle" width="85%" bgcolor="#dddddd" height="20">
										<strong>
												<font color="#003399" size="4">
														<b>链表的运算(01) </b>
												</font>
										</strong>
								</td>
								<br />
						</tr>
						<tr>
								<td align="middle" width="100%">
										<br />
								</td>
						</tr>
						<tr>
								<td style="FONT-SIZE: 9pt" align="middle" width="100%">发表日期：2003年4月8日    作者：C语言之家搜集整理  已经有3901位读者读过此文</td>
						</tr>
						<tr>
								<td align="middle" width="100%">
										<!--下面的这一句是设置阅读文本区的宽度-->
										<table style="TABLE-LAYOUT: fixed" cellspacing="0" cellpadding="0" width="90%" align="center" border="0">
												<tbody>
														<tr>
																<td align="middle" width="100%">
																</td>
														</tr>
														<tr>
																<td style="WORD-WRAP: break-word">
																		<font class="news">
																				<br />
																				<font color="#0000ff">1.设计一个程序将输入的数据建立成链表、输出链表数据并在程序结束后释放。<br /><font color="#0000ff">2.设计一个查找链表中的数据的程序</font><br /></font>
																				<br />
																				<br />
																				<br />
																				<font color="#0000ff">1.设计一个程序将输入的数据建立成链表、输出链表数据并在程序结束后释放。<br /></font>解：<br />    1)链表的建立：先声明一个首节点Head，并将Head-&gt;Next设为NULL。每输入一个数据就声明一个新节点New，把New-&gt;Next设为NULL，并且链接到之前列表的尾端。<br />    2)链表数据的输出：先将Pointer节点的指针指向第一个节点，将Pointer节点(即第一个节点)的数据输出。然后再将Pointer节点的指针指向Pointer指针的的指针(即下一节点)，将pointer节点(即第一节点)的数据输出。重复执行此步聚直到Pointer指针指向NULL为止。<br />    3)链表的释放：先将Pointer节点的指针指向第一个节点，然后再将首节点设为首节点的指针(即下一节点)。将Pointer节点(即第一节点)释放。重复执行此步聚直到首节点的指针指向NULL为止。<br />程序代码如下：<br />#include&lt;stdlib.h&gt;<br />#include&lt;stdio.h&gt;<br />#define Max 10<br />struct List             /*节点结构声明*/<br />{<br />    int Number;<br />    char Name[Max];<br />    struct List *Next;<br />};<br />typedef struct List Node;<br />typedef Node *Link;<br />/*释放链表*/<br />void Free_List(Link Head)<br />{<br />    Link Pointer;      /*节点声明*/<br />    while(Head!=NULL)      /*当节点为NULL，结束循环*/<br />    {<br />        Pointer=Head;<br />        Head=Head-&gt;Next;   /*指向下一个节点*/<br />        free(Pointer);<br />    }<br />}<br />/*输出链表*/<br />void Print_List(Link Head)<br />{<br />    Link Pointer;          /*节点声明*/<br />    Pointer=Head;          /*Pointer指针设为首节点*/<br />    while(Pointer!=NULL)   /*当节点为NULL结束循环*/<br />    {<br />        printf("##Input Data##\n");<br />        printf("Data Number: %d\n",Pointer-&gt;Number);<br />        printf("Data Name: %s\n",Pointer-&gt;Name);<br />        Pointer=Pointer-&gt;Next;     /*指向下一个节点*/<br />    }<br />}<br />/*建立链表*/<br />Link Create_List(Link Head)<br />{<br />    int DataNum;         /*数据编号*/<br />    char DataName[Max];        /*数据名称*/<br />    Link New;            /*节点声明*/<br />    Link Pointer;        /*节点声明*/<br />    int i;<br />    Head=(Link)malloc(sizeof(Node));     /*分配内存*/<br />    if(Head==NULL)<br />        printf("Memory allocate Failure!\n");    /*内存分配夫败*/<br />    else<br />    {<br />        DataNum=1;      /*初始数据编号*/<br />        printf("Please input the data name:");<br />        scanf("%s",DataName);<br />        Head-&gt;Number=DataNum;     /*定义首节点数据编号*/<br />        for(i=0;i&lt;=Max;i++)<br />            Head-&gt;Name[i]=DataName[i];<br />        Head-&gt;Next=NULL;<br />        Pointer=Head;          /*Pointer指针设为首节点*/<br />        while(1)<br />        {<br />            DataNum++;         /*数据编号递增*/<br />            New=(Link)malloc(sizeof(Node));     /*分配内存*/<br />            printf("Please input the data Name:");<br />            scanf("%s",DataName);<br />            if(DataName[0]=='0')    /*输入0则结束*/<br />                break;<br />            New-&gt;Number=DataNum;<br />            for(i=0;i&lt;Max;i++)<br />            {<br />                New-&gt;Name[i]=DataName[i];<br />            }<br />            New-&gt;Next=NULL;<br />            Pointer-&gt;Next=New;     /*将新节点串连在原列表尾端*/<br />            Pointer=New;         /*列表尾端节点为新节点*/<br />        }<br />    }<br />    return Head;<br />}<br />/*主程序*/<br />void main()<br />{<br />    Link Head;       /*节点声明*/<br />    Head=Create_List(Head);     /*调用建立链表函数*/<br />    if(Head!=NULL)<br />    {<br />        Print_List(Head);      /*调用输出链表数据函数*/<br />        Free_List(Head);       /*调用释放链表函数*/<br />    }<br />}<br /><br />运行结果如下：<br /><p align="left"></p><p align="center"><img src="http://www.cstudyhome.com/wenzhang06/uploadpic/2003481243969783.gif" onload="javascript:if(this.width&gt;screen.width-333)this.width=screen.width-333" /></p></font>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/faintbear/aggbug/58655.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2006-07-17 22:53 <a href="http://www.blogjava.net/faintbear/archive/2006/07/17/58655.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>为什么 char** 不能自动转化为 const char** (转)</title><link>http://www.blogjava.net/faintbear/archive/2005/12/29/25963.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Thu, 29 Dec 2005 15:00:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/12/29/25963.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/25963.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/12/29/25963.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/25963.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/25963.html</trackback:ping><description><![CDATA[<DIV class=postTitle><A id=viewpost1_TitleUrl href="http://blog.vckbase.com/bruceteen/archive/2005/12/07/15691.html">为什么 char** 不能自动转化为 const char**</A> </DIV>
<DIV class=postText>
<P>一次偶然的情况下我发现以下代码竟然无法被编译通过（如果你的编译器，比如VC6或VC2003，允许它编译通过，我想你首先应该换个编译器，比如GCC或VC2005）：<BR><FONT color=#0000ff>void foo( const char* [] ) { }<BR>int main( void )<BR>{<BR>&nbsp;&nbsp;&nbsp; char* s[2];<BR>&nbsp;&nbsp;&nbsp; foo( s );<BR>}</FONT><BR>简化成更一般的形式是：<BR><FONT color=#0000ff>char** p1 = 0;<BR>const char** p2 = p1;</FONT><BR>错误是：<FONT color=#a52a2a>invalid conversion from `char**' to `const char**'</FONT>.</P>
<P><A href="http://blog.vckbase.com/lostpencil" target=_blank>lostpencil</A>更加仔细，使用C编译器给出的是一个警告：<BR><FONT color=#a52a2a>initialization from incompatible pointer type</FONT>.</P>
<P>随后<A href="http://www.vckbase.com/bbs/userinfo.asp?id=hpho" target=_blank>hpho</A>给出了合理的解释，同时<A href="http://groups.google.com/group/comp.lang.c++.moderated" target=_blank>comp.lang.c++.moderated</A>上的<A href="mailto:eckhardt@satorlaser.com">Ulrich Eckhardt</A>也用代码进行了说明。</P>
<P>用代码来说明最直观了：<BR><FONT color=#0000ff>const char* s = "abc";<BR>int main( void )<BR>{<BR>&nbsp;&nbsp;&nbsp; char* p0 = 0;<BR>&nbsp;&nbsp;&nbsp; char** p1 = &amp;p0;<BR>&nbsp;&nbsp;&nbsp; const char** p2 = p1;</FONT> // 先假设这一句是合法的 ( 测试时，可以先强制类型转化一下 )<BR><FONT color=#0000ff>&nbsp;&nbsp;&nbsp; *p2 = s;<BR>&nbsp;&nbsp;&nbsp; *p0 = 'A';</FONT> // 通过p0在修改不应该被修改的s，这显然和const相违背，其运行结果不可知。<BR><FONT color=#0000ff>}<BR><BR><BR><BR><BR></FONT></P></DIV>
<P><FONT color=#0000ff>看了 **的 想到的<BR>tekyDec 29, 2005 -&nbsp; Show original item </FONT></P>
<P><FONT color=#0000ff>看完后.明白**讲的为什么char** 不能自动转化为 const char**,(原文)但对我影响最深的是下面的话:</FONT></P>
<P><FONT color=#0000ff>==================================================================<BR>char *p="abc" 能不能编译通过要看你使用的编译器。鉴于大量遗留代码的存在，大部分编译器允许其通过，或者给个警告。当然，程序员自己必须保证绝不去修改其值。 </FONT></P>
<P><FONT color=#0000ff>程序员不应该在代码中出现*p='A'这样的语句。这是当初约定好了的：编译器允许char *p="abc"通过，而程序员保证不去修改它。 <BR>b. *p='A'编译时应该允许通过，因为单就这条语句而言，它完全合法。 <BR>c. 运行时*p='A'能不能通过要看实际的运行环境，包括你使用的操作系统、编译器、编译器选项 等等，一句话，其运行结果由不得你，且不应该由你去关心，因为这种行为本身已经违反约定了。 <BR>==================================================================</FONT></P>
<P><FONT color=#0000ff>工作关系吧,用CString 和string用的太多了,很少这样定义字符串 char *p=“abcde“了<BR>匝一看,还不适应,:(,渐渐的回想才想起一些来(哎,还是太生疏,赶快写下来,以后别忘了)</FONT></P>
<P><FONT color=#0000ff>这样定义的字符串char *p=“abcde“ ; char *p1=“123445667“;</FONT></P>
<P><FONT color=#0000ff>正如上面提到的是不能再 *p='A',运行的时候会出错,同样,strcpy(p,p1)也会出错哟,</FONT></P>
<P><FONT color=#0000ff>"abcde"字符串可以看做是个常量字符串了,是不能被修改的,</FONT></P>
<P><FONT color=#0000ff>但如果 char p[]=“abcde“ 这样定义,就没有问题,你可以修改*p='A',只要不越界就ok.</FONT></P>
<P><FONT color=#0000ff>并且发现这样两种定义<BR>char *p=“abcde“</FONT></P>
<P><FONT color=#0000ff>char p[]=“abcde“ </FONT></P>
<P><FONT color=#0000ff>在运行的时候,p指向的地址也不是一样的,可见char *p=“abcde“还是有特殊的处理 :),具体怎么处理就不知道了,高手请指教:)</FONT></P><FONT color=#0000ff>
<P><BR>随着测试,又发现个问题,可能是个老问题了吧:</P>
<P><BR>int main(int argc, char* argv[])<BR>{ <BR>&nbsp;int t[10];<BR>&nbsp;char p1[7]="123456";<BR>&nbsp;const char *p2="1234567890123213123";<BR>&nbsp;<BR>&nbsp;int len(0);<BR>&nbsp;<BR>&nbsp; //*p1='C';&nbsp; err</P>
<P>&nbsp;len=strlen(p1);<BR>&nbsp;printf("%d\n",len);<BR>&nbsp;<BR>&nbsp;strcpy(p1,p2);&nbsp;&nbsp; ///??????????<BR>&nbsp;<BR>&nbsp;printf("%s\n",p1);<BR>&nbsp;<BR>&nbsp;len=strlen(p1);<BR>&nbsp;<BR>&nbsp;printf("%d\n",len);<BR>&nbsp;return 0;<BR>}</P>
<P>我定义的是7个字符数组, 但用strcpy把p2拷到p1中,p1是放不下的,但程序却正常执行,warning ,err都没有,运行也正常?</P>
<P><BR>输出 </P>
<P>6<BR>1234567890123213123<BR>19</P>
<P>应该是使用内存越界了阿??怎么会正常运行呢?</P>
<P>难道对于内存越界的使用,运气好才崩溃表现出来,运气不好就正常运行??</P>
<P><BR></FONT>&nbsp;</P><img src ="http://www.blogjava.net/faintbear/aggbug/25963.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-12-29 23:00 <a href="http://www.blogjava.net/faintbear/archive/2005/12/29/25963.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>学习~strtok</title><link>http://www.blogjava.net/faintbear/archive/2005/11/05/18323.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Sat, 05 Nov 2005 12:39:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/11/05/18323.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/18323.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/11/05/18323.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/18323.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/18323.html</trackback:ping><description><![CDATA[<P>extern char *GetField(char *ibuf,char *ChrSet,int num)<BR>{<BR>int&nbsp; i=0;<BR>static char tmp[256];<BR>char *pt,*pstr;</P>
<P>pstr=(char *)malloc(strlen(ibuf)+1);<BR>strcpy(pstr,ibuf);<BR>for(i=0,pt=strtok(pstr,ChrSet);pt!=NULL;i++,pt=strtok(NULL,ChrSet))<BR>{<BR>&nbsp;if(i&gt;=num||pt==NULL) break; <BR>}<BR>strcpy(tmp,pt);<BR>free(pstr);<BR>return(tmp);<BR>}</P>
<P>例：<BR>char *pt;<BR>char buf[]="324324324|234324324|23432432432|23432432432|33333";<BR>pt=GetField(buf,"|",3);</P>
<P><BR><BR><BR><BR>static void addArray(char *buff, char array[][MQ_ARRAY_LEN])<BR>{<BR>&nbsp;&nbsp; </P>
<P>&nbsp;&nbsp;&nbsp; int index = 0;<BR>&nbsp;&nbsp;&nbsp; int i=0;</P>
<P>&nbsp;&nbsp;&nbsp; char *pt,*pstr;<BR>&nbsp;&nbsp;&nbsp; char * ChrSet= ",";</P>
<P>&nbsp;&nbsp;&nbsp; pstr=(char *)malloc(strlen(buff)+1);<BR>&nbsp;&nbsp;&nbsp; pstr = strcpy( pstr, buff);<BR>&nbsp;&nbsp;&nbsp; for(i=0,pt=strtok(pstr,ChrSet); pt!=NULL; i++, pt=strtok(NULL,ChrSet))<BR>&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(pt==NULL) break;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; printf("dealing ... %s\n", pt);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(array[index],pt);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; index++;<BR>&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp; free(pstr);</P>
<P>}<BR></P><img src ="http://www.blogjava.net/faintbear/aggbug/18323.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-11-05 20:39 <a href="http://www.blogjava.net/faintbear/archive/2005/11/05/18323.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之六:性能优化</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13658.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:35:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13658.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13658.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13658.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13658.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13658.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系统编程修炼之六:性能优化</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-07-22<br><br>
	 </p>



<b>使用宏定义</b><br><br>　　在C语言中，宏是产生内嵌代码的唯一方法。对于嵌入式系统而言，为了能达到性能要求，宏是一种很好的代替函数的方法。<br><br>　　写一个"标准"宏MIN ，这个宏输入两个参数并返回较小的一个：<br><br>　　错误做法：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#define MIN(A,B) 　( A &lt;= B ? A : B )</td></tr></tbody></table><br>　　正确做法：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#define MIN(A,B) （（A）&lt;= (B) ? (A) : (B) )</td></tr></tbody></table><br>　　对于宏，我们需要知道三点：<br><br>　　(1)宏定义"像"函数；<br><br>　　(2)宏定义不是函数，因而需要括上所有"参数"；<br><br>　　(3)宏定义可能产生副作用。<br><br>　　下面的代码：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>least = MIN(*p++, b);</td></tr></tbody></table><br>　　将被替换为：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>( (*p++) &lt;= (b) ?(*p++):(b) )</td></tr></tbody></table><br>　　发生的事情无法预料。 <br><br>　　因而不要给宏定义传入有副作用的"参数"。<br><br>　　<b>使用寄存器变量</b><br><br>　
　当对一个变量频繁被读写时，需要反复访问内存，从而花费大量的存取时间。为此，C语言提供了一种变量，即寄存器变量。这种变量存放在CPU的寄存器中，
使用时，不需要访问内存，而直接从寄存器中读写，从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使
用的变量均可定义为寄存器变量，而循环计数是应用寄存器变量的最好候选者。<br><br>　　(1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式，凡需要采用静态存储方式的量都不能定义为寄存器变量，包括：模块间全局变量、模块内全局变量、局部static变量；<br><br>　　(2) register是一个"建议"型关键字，意指程序建议该变量放在寄存器中，但最终该变量可能因为条件不满足并未成为寄存器变量，而是被放在了存储器中，但编译器中并不报错（在C++语言中有另一个"建议"型关键字：inline）。<br><br>　　下面是一个采用寄存器变量的例子：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 求1+2+3+….+n的值 */<br>WORD Addition(BYTE n)<br>{<br>　register i,s=0;<br>　for(i=1;i&lt;=n;i++)<br>　{<br>　　s=s+i;<br>　}<br>　return s;<br>}</td></tr></tbody></table><br>　　本程序循环n次，i和s都被频繁使用，因此可定义为寄存器变量。<br><br>　　<b>内嵌汇编</b><br><br>　　程序中对时间要求苛刻的部分可以用内嵌汇编来重写，以带来速度上的显著提高。但是，开发和测试汇编代码是一件辛苦的工作，它将花费更长的时间，因而要慎重选择要用汇编的部分。<br><br>　　在程序中，存在一个80-20原则，即20%的程序消耗了80%的运行时间，因而我们要改进效率，最主要是考虑改进那20%的代码。<br><br>　　嵌入式C程序中主要使用在线汇编，即在C程序中直接插入_asm{ }内嵌汇编语句：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 把两个输入参数的值相加，结果存放到另外一个全局变量中 */<br>int result; <br>void Add(long a, long *b) <br>{ <br>　_asm <br>　{ <br>　　MOV AX, a <br>　　MOV BX, b <br>　　ADD AX, [BX]<br>　　MOV result, AX<br>　} <br>} </td></tr></tbody></table><br>　　<b>利用硬件特性</b><br><br>　　首先要明白CPU对各种存储器的访问速度，基本上是：<br><br>CPU内部RAM　&gt;　外部同步RAM　&gt;　外部异步RAM　&gt;　FLASH/ROM<br><br>　　对于程序代码，已经被烧录在FLASH或ROM中，我们可以让CPU直接从其中读取代码执行，但通常这不是一个好办法，我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度；<br><br>　　对于UART等设备，其内部有一定容量的接收BUFFER，我们应尽量在BUFFER被占满后再向CPU提出中断。例如计算机终端在向目标机通过RS-232传递数据时，不宜设置UART只接收到一个BYTE就向CPU提中断，从而无谓浪费中断处理时间；<br><br>　
　如果对某设备能采取DMA方式读取，就采用DMA读取，DMA读取方式在读取目标中包含的存储信息较大时效率较高，其数据传输的基本单位是块，而所传输
的数据是从设备直接送入内存的（或者相反）。DMA方式较之中断驱动方式，减少了CPU 对外设的干预，进一步提高了CPU与外设的并行操作程度。<br><br>　　<b>活用位操作</b><br><br>　　使用C语言的位操作可以减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位，理论上可以用"位运算"来完成所有的运算和操作，因而，灵活的位操作可以有效地提高程序运行的效率。举例如下： <br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 方法1 */<br>int i,j;<br>i = 879 / 16;<br>j = 562 % 32; <br>/* 方法2 */<br>int i,j;<br>i = 879 &gt;&gt; 4;<br>j = 562 - (562 &gt;&gt; 5 &lt;&lt; 5);</td></tr></tbody></table><br>　　对于以2的指数次方为"*"、"/"或"%"因子的数学运算，转化为移位运算"&lt;&lt; &gt;&gt;"通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。<br><br>　
　C语言位运算除了可以提高运算效率外，在嵌入式系统的编程中，它的另一个最典型的应用，而且十分广泛地正在被使用着的是位间的与（&amp;）、或
（|）、非（~）操作，这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置，譬如，我们通过将AM186ER型80186处理器的中
断屏蔽控制寄存器的第低6位设置为0（开中断2），最通用的做法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#define INT_I2_MASK 0x0040 <br>wTemp = inword(INT_MASK);<br>outword(INT_MASK, wTemp &amp;~INT_I2_MASK);</td></tr></tbody></table><br>　　而将该位设置为1的做法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#define INT_I2_MASK 0x0040 <br>wTemp = inword(INT_MASK);<br>outword(INT_MASK, wTemp | INT_I2_MASK);</td></tr></tbody></table><br>　　判断该位是否为1的做法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#define INT_I2_MASK 0x0040 <br>wTemp = inword(INT_MASK);<br>if(wTemp &amp; INT_I2_MASK)<br>{<br>… /* 该位为1 */<br>}</td></tr></tbody></table><br>　　上述方法在嵌入式系统的编程中是非常常见的，我们需要牢固掌握。 <br><br>　　<b>总结</b><br><br>　　在性能优化方面永远注意80-20准备，不要优化程序中开销不大的那80%，这是劳而无功的。<br><br>　
　宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法，但宏在本质上不是函数，因而要防止宏展开后出现不可预料的结果，对宏的定义和
使用要慎而处之。很遗憾，标准C至今没有包括C++中inline函数的功能，inline函数兼具无调用开销和安全的优点。<br><br>　　使用寄存器变量、内嵌汇编和活用位操作也是提高程序效率的有效方法。<br><br>　　除了编程上的技巧外，为提高系统的运行效率，我们通常也需要最大可能地利用各种硬件设备自身的特点来减小其运转开销，例如减小中断次数、利用DMA传输方式等。<img src ="http://www.blogjava.net/faintbear/aggbug/13658.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:35 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13658.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之五:键盘操作</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13657.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:34:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13657.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13657.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13657.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13657.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13657.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系统编程修炼之五:键盘操作</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-07-22<br><br>
	 </p>



<b>处理功能键</b><br><br>　　功能键的问题在于，用户界面并非固定的，用户功能键的选择将使屏幕画面处于不同的显示状态下。例如，主画面如图1：<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimise_517_1.jpg" alt="" border="0"><br>图1 主画面</div></td></tr></tbody></table><br>　　当用户在设置XX上按下Enter键之后，画面就切换到了设置XX的界面，如图2：<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimise_517_2.jpg" alt="" border="0"><br>图2 切换到设置XX画面</div></td></tr></tbody></table><br>　　程序如何判断用户处于哪一画面，并在该画面的程序状态下调用对应的功能键处理函数，而且保证良好的结构，是一个值得思考的问题。<br><br>　
　让我们来看看WIN32编程中用到的"窗口"概念，当消息（message）被发送给不同窗口的时候，该窗口的消息处理函数（是一个callback函
数）最终被调用，而在该窗口的消息处理函数中，又根据消息的类型调用了该窗口中的对应处理函数。通过这种方式，WIN32有效的组织了不同的窗口，并处理
不同窗口情况下的消息。<br><br>　　我们从中学习到的就是：<br><br>　　（1）将不同的画面类比为WIN32中不同的窗口，将窗口中的各种元素（菜单、按钮等）包含在窗口之中；<br><br>　　（2）给各个画面提供一个功能键"消息"处理函数，该函数接收按键信息为参数；<br><br>　　（3）在各画面的功能键"消息"处理函数中，判断按键类型和当前焦点元素，并调用对应元素的按键处理函数。<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 将窗口元素、消息处理函数封装在窗口中 */<br>struct windows<br>{<br>　BYTE currentFocus;<br>　ELEMENT element[ELEMENT_NUM];<br>　void (*messageFun) (BYTE keyValue);<br>　…<br>};<br>/* 消息处理函数 */<br>void messageFunction(BYTE keyValue)<br>{<br>　BYTE i = 0;<br>　/* 获得焦点元素 */<br>　while ( (element [i].ID!= currentFocus)&amp;&amp; (i &lt; ELEMENT_NUM) )<br>　{<br>　　i++;<br>　}<br>　/* "消息映射" */<br>　if(i &lt; ELEMENT_NUM)<br>　{<br>　　switch(keyValue)<br>　　{<br>　　　case OK:<br>　　　　element[i].OnOk();<br>　　　　break;<br>　　　…<br>　　}<br>　}<br>}</td></tr></tbody></table><br>　　在窗口的消息处理函数中调用相应元素按键函数的过程类似于"消息映射"，这是我们从WIN32编程中学习到的。编程到了一个境界，很多东西都是相通的了。其它地方的思想可以拿过来为我所用，是为编程中的"拿来主义"。<br><br>　　在这个例子中，如果我们还想玩得更大一点，我们可以借鉴MFC中处理MESSAGE_MAP的方法，我们也可以学习MFC定义几个精妙的宏来实现"消息映射"。<br><b>处理数字键</b><br><br>　　用户输入数字时是一位一位输入的，每一位的输入都对应着屏幕上的一个显示位置（x坐标，y坐标）。此外，程序还需要记录该位置输入的值，所以有效组织用户数字输入的最佳方式是定义一个结构体，将坐标和数值捆绑在一起：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 用户数字输入结构体 */<br>typedef struct tagInputNum<br>{<br>　BYTE byNum; /* 接收用户输入赋值 */<br>　BYTE xPos; /* 数字输入在屏幕上的显示位置x坐标 */<br>　BYTE yPos; /* 数字输入在屏幕上的显示位置y坐标 */<br>}InputNum, *LPInputNum;<br></td></tr></tbody></table><br>　　那么接收用户输入就可以定义一个结构体数组，用数组中的各位组成一个完整的数字：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>InputNum inputElement[NUM_LENGTH]; /* 接收用户数字输入的数组 */<br>/* 数字按键处理函数 */<br>extern void onNumKey(BYTE num)<br>{<br>if(num==0|| num==1) /* 只接收二进制输入 */<br>{<br>　/* 在屏幕上显示用户输入 */<br>　DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, "%1d", num);<br>　/* 将输入赋值给数组元素 */<br>　inputElement[currentElementInputPlace].byNum = num; <br>　/* 焦点及光标右移 */<br>　moveToRight();<br>}<br>} </td></tr></tbody></table><br>　　将数字每一位输入的坐标和输入值捆绑后，在数字键处理函数中就可以较有结构的组织程序，使程序显得很紧凑。<br><br>　　<b>整理用户输入</b><br><br>　　继续第2节的例子，在第2节的onNumKey函数中，只是获取了数字的每一位，因而我们需要将其转化为有效数据，譬如要转化为有效的XXX数据，其方法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 从2进制数据位转化为有效数据：XXX */<br>void convertToXXX()<br>{<br>　BYTE i;<br>　XXX = 0; <br>　for (i = 0; i &lt; NUM_LENGTH; i++)<br>　{ <br>　　XXX += inputElement[i].byNum*power(2, NUM_LENGTH - i - 1);<br>　} <br>}</td></tr></tbody></table><br>　　反之，我们也可能需要在屏幕上显示那些有效的数据位，因为我们也需要能够反向转化：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 从有效数据转化为2进制数据位：XXX */<br>void convertFromXXX()<br>{<br>　BYTE i;<br>　XXX = 0; <br>　for (i = 0; i &lt; NUM_LENGTH; i++)<br>　{ <br>　　inputElement[i].byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2;<br>　} <br>}</td></tr></tbody></table><br>　　当然在上面的例子中，因为数据是2进制的，用power函数不是很好的选择，直接用"&lt;&lt; &gt;&gt;"移位操作效率更高，我们仅是为了说明问题的方便。试想，如果用户输入是十进制的，power函数或许是唯一的选择了。<br><br>　　<b>总结</b><br><br>　　本篇给出了键盘操作所涉及的各个方面：功能键处理、数字键处理及用户输入整理，基本上提供了一个全套的按键处理方案。对于功能键处理方法，将LCD屏幕与Windows窗口进行类比，提出了较新颖地解决屏幕、键盘繁杂交互问题的方案。<br><br>　
　计算机学的许多知识都具有相通性，因而，不断追赶时髦技术而忽略基本功的做法是徒劳无意的。我们最多需要"精通"三种语言（精通，一个在如今的求职简历
里泛滥成灾的词语），最佳拍档是汇编、C、C++（或JAVA），很显然，如果你"精通"了这三种语言，其它语言你应该是可以很快"熟悉"的，否则你就没
有"精通"它们.<img src ="http://www.blogjava.net/faintbear/aggbug/13657.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:34 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13657.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之四:屏幕操作</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13656.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:32:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13656.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13656.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13656.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13656.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13656.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系统编程修炼之四:屏幕操作</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-07-22<br><br>
	 </p>



<b>汉字处理</b><br><br>　　现在要解决的问题是，嵌入式系统中经常要使用的并非是完整的汉字库，往往只是需要提供数量有限的汉字供必要的
显示功能。例如，一个微波炉的LCD上没有必要提供显示"电子邮件"的功能；一个提供汉字显示功能的空调的LCD上不需要显示一条"短消息"，诸如此类。
但是一部手机、小灵通则通常需要包括较完整的汉字库。<br><br>　　如果包括的汉字库较完整，那么，由内码计
算出汉字字模在库中的偏移是十分简单的：汉字库是按照区位的顺序排列的，前一个字节为该汉字的区号，后一个字节为该字的位号。每一个区记录94个汉字，位
号则为该字在该区中的位置。因此，汉字在汉字库中的具体位置计算公式为：94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开
始的。只需乘上一个汉字字模占用的字节数即可，即：(94*(区号-1)+位号-1)*一个汉字字模占用字节数，以16*16点阵字库为例，计算公式则为：(94*(区号-1)+(位号-1))*32。汉字库中从该位置起的32字节信息记录了该字的字模信息。<br><br>　　对于包含较完整汉字库的系统而言，我们可以以上述规则计算字模的位置。但是如果仅仅是提供少量汉字呢？譬如几十至几百个？最好的做法是：<br><br>　　定义宏：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td># define EX_FONT_CHAR(value) <br># define EX_FONT_UNICODE_VAL(value) (value),<br># define EX_FONT_ANSI_VAL(value) (value),</td></tr></tbody></table><br>　　定义结构体：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>typedef struct _wide_unicode_font16x16 <br>{<br>　WORD value; /* 内码 */<br>　BYTE data[32]; /* 字模点阵 */<br>}Unicode;<br>#define CHINESE_CHAR_NUM … /* 汉字数量 */</td></tr></tbody></table><br>　　字模的存储用数组：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>Unicode chinese[CHINESE_CHAR_NUM] =<br>{<br>{<br>EX_FONT_CHAR("业")<br>EX_FONT_UNICODE_VAL(0x4e1a)<br>{0x04,
0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24,
0x48, 0x14, 0x50, 0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04,
0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}<br>},<br>{<br>EX_FONT_CHAR("中")<br>EX_FONT_UNICODE_VAL(0x4e2d)<br>{0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, <br>0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}<br>},<br>{<br>EX_FONT_CHAR("云")<br>EX_FONT_UNICODE_VAL(0x4e91)<br>{0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00, <br><br>0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}<br>},<br>{<br>EX_FONT_CHAR("件")<br>EX_FONT_UNICODE_VAL(0x4ef6)<br>{0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe, <br><br>0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}<br>}<br>}</td></tr></tbody></table><br>　　要显示特定汉字的时候，只需要从数组中查找内码与要求汉字内码相同的即可获得字模。如果前面的汉字在数组中以内码大小顺序排列，那么可以以二分查找法更高效的查找到汉字的字模。<br><br>　　这是一种很有效的组织小汉字库的方法，它可以保证程序有很好的结构。<br><br>　　<b>系统时间显示</b><br><br>　
　从NVRAM中可以读取系统的时间，系统一般借助NVRAM产生的秒中断每秒读取一次当前时间并在LCD上显示。关于时间的显示，有一个效率问题。因为
时间有其特殊性，那就是60秒才有一次分钟的变化，60分钟才有一次小时变化，如果我们每次都将读取的时间在屏幕上完全重新刷新一次，则浪费了大量的系统
时间。<br><br>　　一个较好的办法是我们在时间显示函数中以静态变量分别存储小时、分钟、秒，只有在其内容发生变化的时候才更新其显示。<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>extern void DisplayTime(…)<br>{<br>　static BYTE byHour,byMinute,bySecond;<br>　BYTE byNewHour, byNewMinute, byNewSecond;<br>　byNewHour = GetSysHour();<br>　byNewMinute = GetSysMinute();<br>　byNewSecond = GetSysSecond();<br>　<br>　if(byNewHour!= byHour)<br>　{<br>　　… /* 显示小时 */<br>　　byHour = byNewHour;<br>　}<br>　if(byNewMinute!= byMinute)<br>　{<br>　　… /* 显示分钟 */<br>　　byMinute = byNewMinute;<br>　}<br>　if(byNewSecond!= bySecond)<br>　{<br>　　… /* 显示秒钟 */<br>　　bySecond = byNewSecond;<br>　}<br>}</td></tr></tbody></table><br>　　这个例子也可以顺便作为C语言中static关键字强大威力的证明。当然，在C++语言里，static具有了更加强大的威力，它使得某些数据和函数脱离"对象"而成为"类"的一部分，正是它的这一特点，成就了软件的无数优秀设计。<br><b>动画显示</b><br><br>　　动画是无所谓有，无所谓无的，静止的画面走的路多了，也就成了动画。随着时间的变更，在屏幕上显示不同的静止画面，即是动画之本质。所以，在一个嵌入式系统的LCD上欲显示动画，必须借助定时器。没有硬件或软件定时器的世界是无法想像的：<br><br>　　（1） 没有定时器，一个操作系统将无法进行时间片的轮转，于是无法进行多任务的调度，于是便不再成其为一个多任务操作系统；<br><br>　　（2） 没有定时器，一个多媒体播放软件将无法运作，因为它不知道何时应该切换到下一帧画面； <br><br>　　（3） 没有定时器，一个网络协议将无法运转，因为其无法获知何时包传输超时并重传之，无法在特定的时间完成特定的任务。<br><br>　　因此，没有定时器将意味着没有操作系统、没有网络、没有多媒体，这将是怎样的黑暗？所以，合理并灵活地使用各种定时器，是对一个软件人的最基本需求！<br><br>　　在80186为主芯片的嵌入式系统中，我们需要借助硬件定时器的中断来作为软件定时器，在中断发生后变更画面的显示内容。在时间显示"xx:xx"中让冒号交替有无，每次秒中断发生后，需调用ShowDot：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>void ShowDot()<br>{<br>　static BOOL bShowDot = TRUE; /* 再一次领略static关键字的威力 */<br>　if(bShowDot)<br>　{<br>　　showChar(’:’,xPos,yPos);<br>　}<br>　else<br>　{<br>　　showChar(’ ’,xPos,yPos);<br>　}<br>　bShowDot = ! bShowDot;<br>}</td></tr></tbody></table><br>　　<b>菜单操作</b><br><br>　　无数人为之绞尽脑汁的问题终于出现了，在这一节里，我们将看到，在C语言中哪怕用到一丁点的面向对象思想，软件结构将会有何等的改观！<br><br>　　笔者曾经是个笨蛋，被菜单搞晕了，给出这样的一个系统：<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimise_516_1.jpg" alt="" border="0"><br>图1 菜单范例</div></td></tr></tbody></table><br>　　要求以键盘上的"← →"键切换菜单焦点，当用户在焦点处于某菜单时，若敲击键盘上的OK、CANCEL键则调用该焦点菜单对应之处理函数。我曾经傻傻地这样做着：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 按下OK键 */<br>void onOkKey()<br>{<br>　/* 判断在什么焦点菜单上按下Ok键，调用相应处理函数 */<br>　Switch(currentFocus)<br>　{<br>　　case MENU1:<br>　　　menu1OnOk();<br>　　　break;<br>　　case MENU2:<br>　　　menu2OnOk();<br>　　　break;<br>　　…<br>　}<br>}<br>/* 按下Cancel键 */<br>void onCancelKey()<br>{<br>　/* 判断在什么焦点菜单上按下Cancel键，调用相应处理函数 */<br>　Switch(currentFocus)<br>　{<br>　　case MENU1:<br>　　　menu1OnCancel();<br>　　　break;<br>　　case MENU2:<br>　　　menu2OnCancel();<br>　　　break;<br>　　…<br>　}<br>}</td></tr></tbody></table><br>　　终于有一天，我这样做了：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 将菜单的属性和操作"封装"在一起 */<br>typedef struct tagSysMenu<br>{<br>　char *text; /* 菜单的文本 */<br>　BYTE xPos; /* 菜单在LCD上的x坐标 */<br>　BYTE yPos; /* 菜单在LCD上的y坐标 */<br>　void (*onOkFun)(); /* 在该菜单上按下ok键的处理函数指针 */<br>　void (*onCancelFun)(); /* 在该菜单上按下cancel键的处理函数指针 */<br>}SysMenu, *LPSysMenu;</td></tr></tbody></table><br>　　当我定义菜单时，只需要这样：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>static SysMenu menu[MENU_NUM] =<br>{<br>　{<br>　　"menu1", 0, 48, menu1OnOk, menu1OnCancel<br>　}<br>　,<br>　{<br>　　" menu2", 7, 48, menu2OnOk, menu2OnCancel<br>　}<br>　,<br>　{<br>　　" menu3", 7, 48, menu3OnOk, menu3OnCancel<br>　}<br>　,<br>　{<br>　　" menu4", 7, 48, menu4OnOk, menu4OnCancel<br>　}<br>　…<br>};</td></tr></tbody></table><br>　　OK键和CANCEL键的处理变成：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 按下OK键 */<br>void onOkKey()<br>{ <br>　menu[currentFocusMenu].onOkFun(); <br>}<br>/* 按下Cancel键 */<br>void onCancelKey()<br>{<br>　menu[currentFocusMenu].onCancelFun(); <br>}</td></tr></tbody></table><br>　　程序被大大简化了，也开始具有很好的可扩展性！我们仅仅利用了面向对象中的封装思想，就让程序结构清晰，其结果是几乎可以在无需修改程序的情况下在系统中添加更多的菜单，而系统的按键处理函数保持不变。<br><br>　　面向对象，真神了！<br><b>模拟MessageBox函数</b><br><br>　　MessageBox函数，这个Windows编程中的超级猛料，不知道是多少入门者第
一次用到的函数。还记得我们第一次在Windows中利用MessageBox输出
"Hello,World!"对话框时新奇的感觉吗？无法统计，这个世界上究竟有多少程序员学习Windows编程是从MessageBox
("Hello,World!",…)开始的。在我本科的学校，广泛流传着一个词汇，叫做"’Hello,World’级程序员"，意指入门级程序员，但
似乎"’Hello,World’级"这个说法更搞笑而形象。<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimise_516_2.jpg" alt="" border="0">&nbsp;　<img src="http://www.upsdn.net/images/2005-07/c_optimise_516_3.jpg" alt="" border="0"><br>图2 经典的Hello,World!</div></td></tr></tbody></table><br>　　图2给出了两种永恒经典的Hello,World对话框，一种只具有"确定"，一种则包含"确定"、"取消"。是的，MessageBox的确有，而且也应该有两类！这完全是由特定的应用需求决定的。<br><br>　　嵌入式系统中没有给我们提供MessageBox，但是鉴于其功能强大，我们需要模拟之，一个模拟的MessageBox函数为：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/******************************************<br>/* 函数名称: MessageBox<br>/* 功能说明: 弹出式对话框,显示提醒用户的信息<br>/* 参数说明: lpStr --- 提醒用户的字符串输出信息<br>/* TYPE --- 输出格式(ID_OK = 0, ID_OKCANCEL = 1)<br>/* 返回值: 返回对话框接收的键值,只有两种 KEY_OK, KEY_CANCEL<br>/******************************************<br>typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE;<br>extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE)<br>{<br>　BYTE keyValue = -1;<br><br>　ClearScreen(); /* 清除屏幕 */<br>　DisplayString(xPos,yPos,lpStr,TRUE); /* 显示字符串 */<br>　/* 根据对话框类型决定是否显示确定、取消 */<br>　switch (TYPE)<br>　{<br>　　case ID_OK:<br>　　　DisplayString(13,yPos+High+1, " 确定 ", 0);<br>　　　break;<br>　　case ID_OKCANCEL:<br>　　　DisplayString(8, yPos+High+1, " 确定 ", 0);<br>　　　DisplayString(17,yPos+High+1, " 取消 ", 0);<br>　　　break;<br>　　default:<br>　　　break;<br>　}<br>　DrawRect(0, 0, 239, yPos+High+16+4); /* 绘制外框 */<br>　/* MessageBox是模式对话框，阻塞运行，等待按键 */<br>　while( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) )<br>　{<br>　　keyValue = getSysKey();<br>　}<br>　/* 返回按键类型 */<br>　if(keyValue== KEY_OK)<br>　{<br>　　return ID_OK;<br>　}<br>　else<br>　{<br>　　return ID_CANCEL;<br>　}<br>}</td></tr></tbody></table><br>　　上述函数与我们平素在VC++等中使用的MessageBox是何等的神似啊？实现这个函数，你会看到它在嵌入式系统中的妙用是无穷的。<br><br>　　<b>总结</b><br><br>　　本篇是本系列文章中技巧性最深的一篇，它提供了嵌入式系统屏幕显示方面一些很巧妙的处理方法，灵活使用它们，我们将不再被LCD上凌乱不堪的显示内容所困扰。<br><br>　　屏幕乃嵌入式系统生存之重要辅助，面目可憎之显示将另用户逃之夭夭。屏幕编程若处理不好，将是软件中最不系统、最混乱的部分，笔者曾深受其害。<img src ="http://www.blogjava.net/faintbear/aggbug/13656.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:32 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13656.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之三:内存操作</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13655.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:30:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13655.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13655.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13655.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13655.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13655.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系统编程修炼之三:内存操作</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-07-22<br><script type="text/javascript" src="http://www.upsdn.net/view.php?id=515"></script><br>
	 </p>



<b>数据指针</b><br><br>　　在嵌入式系统的编程中，常常要求在特定的内存单元读写内容，汇编有对应的MOV指令，而除C/C++以外的其
它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中，多借助C语言指针所具有的对绝对地址单元内容的读写能力。以指针直接操作内存多发
生在如下几种情况：<br><br>　　(1) 某I/O芯片被定位在CPU的存储空间而非I/O空间，而且寄存器对应于某特定地址；<br><br>　　(2) 两个CPU之间以双端口RAM通信，CPU需要在双端口RAM的特定单元（称为mail box）书写内容以在对方CPU产生中断；<br><br>　　(3) 读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。<br><br>　　譬如： <br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>unsigned char *p = (unsigned char *)0xF000FF00;<br>*p=11;</td></tr></tbody></table><br>　　以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写入11。<br><br>　　在使用绝对地址指针时，要注意指针自增自减操作的结果取决于指针指向的数据类别。上例中p++后的结果是p= 0xF000FF01，若p指向int，即：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>int *p = (int *)0xF000FF00;</td></tr></tbody></table><br>　　p++(或++p)的结果等同于：p = p+sizeof(int)，而p-(或-p)的结果是p = p-sizeof(int)。<br><br>　　同理，若执行：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>long int *p = (long int *)0xF000FF00;</td></tr></tbody></table><br>　　则p++(或++p)的结果等同于：p = p+sizeof(long int) ，而p-(或-p)的结果是p = p-sizeof(long int)。<br><br>　　记住：CPU以字节为单位编址，而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。<br><br>　　<b>函数指针</b><br><br>　　首先要理解以下三个问题：<br><br>　　（1）C语言中函数名直接对应于函数生成的指令代码在内存中的地址，因此函数名可以直接赋给指向函数的指针；<br><br>　　（2）调用函数实际上等同于"调转指令＋参数传递处理＋回归位置入栈"，本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器；<br><br>　　（3）因为函数调用的本质是跳转到某一个地址单元的code去执行，所以可以"调用"一个根本就不存在的函数实体，晕？请往下看： <br><br>　　请拿出你可以获得的任何一本大学《微型计算机原理》教材，书中讲到，186 CPU启动后跳转至绝对地址0xFFFF0（对应C语言指针是0xF000FFF0，0xF000为段地址，0xFFF0为段内偏移）执行，请看下面的代码：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 */<br>/* 函数指针类型 */<br>lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针，指向*/<br>/* CPU启动后所执行第一条指令的位置 */<br>lpReset(); /* 调用函数 */</td></tr></tbody></table><br>　　在以上的程序中，我们根本没有看到任何一个函数实体，但是我们却执行了这样的函数调用：lpReset()，它实际上起到了"软重启"的作用，跳转到CPU启动后第一条要执行的指令的位置。<br><br>　　记住：函数无它，唯指令集合耳；你可以调用一个没有函数体的函数，本质上只是换一个地址开始执行指令！<br><br>　　<b>数组vs.动态申请</b><br><br>　　在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求，这是因为嵌入式系统的内存空间往往是十分有限的，不经意的内存泄露会很快导致系统的崩溃。<br><br>　　所以一定要保证你的malloc和free成对出现，如果你写出这样的一段程序：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>char * function(void)<br>{<br>　char *p;<br>　p = (char *)malloc(…);<br>　if(p==NULL)<br>　　…;<br>　　… /* 一系列针对p的操作 */<br>　return p; <br>}</td></tr></tbody></table><br>　　在某处调用function()，用完function中动态申请的内存后将其free，如下：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>char *q = function();<br>…<br>free(q);</td></tr></tbody></table><br>　　上述代码明显是不合理的，因为违反了malloc和free成对出现的原则，即"谁申请，就由谁释放"原则。不满足这个原则，会导致代码的耦合度增大，因为用户在调用function函数时需要知道其内部细节！<br><br>　　正确的做法是在调用处申请内存，并传入function函数，如下：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>char *p=malloc(…);<br>if(p==NULL)<br>…;<br>function(p);<br>…<br>free(p);<br>p=NULL;</td></tr></tbody></table><br>　　而函数function则接收参数p，如下：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>void function(char *p)<br>{<br>　… /* 一系列针对p的操作 */<br>}</td></tr></tbody></table><br>　　基本上，动态申请内存方式可以用较大的数组替换。对于编程新手，笔者推荐你尽量采用数组！嵌入式系统可以以博大的胸襟接收瑕疵，而无法"海纳"错误。毕竟，以最笨的方式苦练神功的郭靖胜过机智聪明却范政治错误走反革命道路的杨康。<br><br>　　给出原则：<br><br>　　（1）尽可能的选用数组，数组不能越界访问（真理越过一步就是谬误，数组越过界限就光荣地成全了一个混乱的嵌入式系统）；<br><br>　　（2）如果使用动态申请，则申请后一定要判断是否申请成功了，并且malloc和free应成对出现！<br><b>关键字const</b><br><br>　　const意味着"只读"。区别如下代码的功能非常重要，也是老生长叹，如果你还不知道它们的区别，而且已经在程序界摸爬滚打多年，那只能说这是一个悲哀：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>const int a;<br>int const a;<br>const int *a;<br>int * const a;<br>int const * a const;</td></tr></tbody></table><br>　
　（1）
关键字const的作用是为给读你代码的人传达非常有用的信息。例如，在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改，属于"输
入参数"。在有多个形参的时候，函数的调用者可以凭借参数前是否有const关键字，清晰的辨别哪些是输入参数，哪些是可能的输出参数。<br><br>　　（2）合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数，防止其被无意的代码修改，这样可以减少bug的出现。<br><br>　
　const在C++语言中则包含了更丰富的含义，而在C语言中仅意味着："只能读的普通变量"，可以称其为"不能改变的变量"（这个说法似乎很拗口，但
却最准确的表达了C语言中const的本质），在编译阶段需要的常数仍然只能以#define宏定义！故在C语言中如下程序是非法的：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>const int SIZE = 10;<br>char a[SIZE]; /* 非法：编译阶段不能用到变量 */</td></tr></tbody></table><br>　　<b>关键字volatile</b><br><br>　　C语言编译器会对用户书写的代码进行优化，譬如如下代码：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>int a,b,c;<br>a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/<br>b = a;<br>a = inWord (0x100); /*再次读取I/O空间0x100端口的内容存入a变量*/<br>c = a;</td></tr></tbody></table><br>　　很可能被编译器优化为：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>int a,b,c;<br>a = inWord(0x100); /*读取I/O空间0x100端口的内容存入a变量*/<br>b = a;<br>c = a;</td></tr></tbody></table><br>　　但是这样的优化结果可能导致错误，如果I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值，则其实第2次读操作读出的内容与第一次不同，b和c的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类似优化，正确的做法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>volatile int a；</td></tr></tbody></table><br>　　volatile变量可能用于如下几种情况：<br><br>　　(1) 并行设备的硬件寄存器（如：状态寄存器，例中的代码属于此类）；<br><br>　　(2) 一个中断服务子程序中会访问到的非自动变量(也就是全局变量)；<br><br>　　(3) 多线程应用中被几个任务共享的变量。<br><br>　　<b>CPU字长与存储器位宽不一致处理</b><br><br>　　在背景篇中提到，本文特意选择了一个与CPU字长不一致的存储芯片，就是为了进行本节的讨论，解决CPU字长与存储器位宽不一致的情况。80186的字长为16，而NVRAM的位宽为8，在这种情况下，我们需要为NVRAM提供读写字节、字的接口，如下： <br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>typedef unsigned char BYTE;<br>typedef unsigned int WORD; <br>/* 函数功能：读NVRAM中字节 <br>* 参数：wOffset，读取位置相对NVRAM基地址的偏移<br>* 返回：读取到的字节值<br>*/<br>extern BYTE ReadByteNVRAM(WORD wOffset)<br>{<br>　LPBYTE lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* 为什么偏移要×2? */<br><br>　return *lpAddr;<br>}<br><br>/* 函数功能：读NVRAM中字<br>* 参数：wOffset，读取位置相对NVRAM基地址的偏移<br>* 返回：读取到的字<br>*/<br>extern WORD ReadWordNVRAM(WORD wOffset)<br>{<br>　WORD wTmp = 0;<br>　LPBYTE lpAddr;<br>　/* 读取高位字节 */<br>　lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* 为什么偏移要×2? */ <br>　wTmp += (*lpAddr)*256;<br>　/* 读取低位字节 */<br>　lpAddr = (BYTE*)(NVRAM + (wOffset +1) * 2); /* 为什么偏移要×2? */<br>　wTmp += *lpAddr;<br>　return wTmp;<br>}<br><br>/* 函数功能：向NVRAM中写一个字节 <br>*参数：wOffset，写入位置相对NVRAM基地址的偏移<br>* byData，欲写入的字节<br>*/<br>extern void WriteByteNVRAM(WORD wOffset, BYTE byData)<br>{<br>　…<br>}<br><br>/* 函数功能：向NVRAM中写一个字 */<br>*参数：wOffset，写入位置相对NVRAM基地址的偏移<br>* wData，欲写入的字<br>*/<br>extern void WriteWordNVRAM(WORD wOffset, WORD wData)<br>{<br>　…<br>}</td></tr></tbody></table><br>　　子贡问曰：Why偏移要乘以2?<br><br>　　子曰：请看图1，16位80186与8位NVRAM之间互连只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接。因此，NVRAM的地址只能是偶数地址，故每次以0x10为单位前进！<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimise_515_1.jpg" alt="" border="0"><br>图1 CPU与NVRAM地址线连接</div></td></tr></tbody></table><br>　　子贡再问：So why 80186的地址线A0不与NVRAM的A0连接？<br><br>　　子曰：请看《IT论语》之《微机原理篇》，那里面讲述了关于计算机组成的圣人之道。<br><br>　　<b>总结</b><br><br>　
　本篇主要讲述了嵌入式系统C编程中内存操作的相关技巧。掌握并深入理解关于数据指针、函数指针、动态申请内存、const及volatile关键字等的
相关知识，是一个优秀的C语言程序设计师的基本要求。当我们已经牢固掌握了上述技巧后，我们就已经学会了C语言的99%，因为C语言最精华的内涵皆在内存
操作中体现。<br><br>　　我们之所以在嵌入式系统中使用C语言进行程序设计，99%是因为其强大的内存操作能力！<br><br>　　如果你爱编程，请你爱C语言；<br><br>　　如果你爱C语言，请你爱指针；<br><br>　　如果你爱指针，请你爱指针的指针！<img src ="http://www.blogjava.net/faintbear/aggbug/13655.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:30 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13655.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之二:软件架构篇</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13654.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:29:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13654.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13654.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13654.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13654.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13654.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系统编程修炼之二:软件架构篇</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-07-22<br><br>
	 </p>





<div class="guanggao">
<span id="ad4"></span>
</div>
   　　<b>模块划分</b><br><br>　　模块划分的"划"是规划的意思，意指怎样合理的将一个很大的软件划分为一系列功能
独立的部分合作完成系统的需求。C语言作为一种结构化的程序设计语言，在模块的划分上主要依据功能（依功能进行划分在面向对象设计中成为一个错误，牛顿定
律遇到了&gt;相对论），C语言模块化程序设计需理解如下概念：<br><br>　　（1） 模块即是一个.c文件和一个.h文件的结合，头文件(.h)中是对于该模块接口的声明；<br><br>　　（2） 某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern关键字声明；<br><br>　　（3） 模块内的函数和全局变量需在.c文件开头冠以static关键字声明；<br><br>　　（4） 永远不要在.h文件中定义变量！定义变量和声明变量的区别在于定义会产生内存分配的操作，是汇编阶段的概念；而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。如：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/*module1.h*/<br>int a = 5; /* 在模块1的.h文件中定义int a */<br><br>/*module1 .c*/<br>#include "module1.h" /* 在模块1中包含模块1的.h文件 */<br><br>/*module2 .c*/<br>#include "module1.h" /* 在模块2中包含模块1的.h文件 */<br><br>/*module3 .c*/<br>#include "module1.h" /* 在模块3中包含模块1的.h文件 */</td></tr></tbody></table><br>　　以上程序的结果是在模块1、2、3中都定义了整型变量a，a在不同的模块中对应不同的地址单元，这个世界上从来不需要这样的程序。正确的做法是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/*module1.h*/<br>extern int a; /* 在模块1的.h文件中声明int a */<br><br>/*module1 .c*/<br>#include "module1.h" /* 在模块1中包含模块1的.h文件 */<br>int a = 5; /* 在模块1的.c文件中定义int a */<br><br>/*module2 .c*/<br>#include "module1.h" /* 在模块2中包含模块1的.h文件 */<br><br>/*module3 .c*/<br>#include "module1.h" /* 在模块3中包含模块1的.h文件 */</td></tr></tbody></table><br>　　这样如果模块1、2、3操作a的话，对应的是同一片内存单元。<br><br>　　一个嵌入式系统通常包括两类模块：<br><br>　　（1）硬件驱动模块，一种特定硬件对应一个模块；<br><br>　　（2）软件功能模块，其模块的划分应满足低偶合、高内聚的要求。<br><br>　　<b>多任务还是单任务</b><br><br>　　所谓"单任务系统"是指该系统不能支持多任务并发操作，宏观串行地执行一个任务。而多任务系统则可以宏观并行（微观上可能串行）地"同时"执行多个任务。<br><br>　
　多任务的并发执行通常依赖于一个多任务操作系统（OS），多任务OS的核心是系统调度器，它使用任务控制块（TCB）来管理任务调度功能。TCB包括任
务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时，要用到这些信息。此外，TCB还被用来存
放任务的"上下文"（context)。任务的上下文就是当一个执行中的任务被停止时，所要保存的所有信息。通常，上下文就是计算机当前的状态，也即各个
寄存器的内容。当发生任务切换时，当前运行的任务的上下文被存入TCB，并将要被执行的任务的上下文从它的TCB中取出，放入各个寄存器中。<br><br>　　嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物，我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核，作者正准备进行此项工作，希望能将心得贡献给大家。<br><br>　　究竟选择多任务还是单任务方式，依赖于软件的体系是否庞大。例如，绝大多数手机程序都是多任务的，但也有一些小灵通的协议栈是单任务的，没有操作系统，它们的主程序轮流调用各个软件模块的处理程序，模拟多任务环境。<br><b>单任务程序典型架构</b><br><br>　　（1）从CPU复位时的指定地址开始执行；<br><br>　　（2）跳转至汇编代码startup处执行；<br><br>　　（3）跳转至用户主程序main执行，在main中完成：<br><br>　　a.初试化各硬件设备； <br><br>　　b.初始化各软件模块；<br><br>　　c.进入死循环（无限循环），调用各模块的处理函数<br><br>　　用户主程序和各模块的处理函数都以C语言完成。用户主程序最后都进入了一个死循环，其首选方案是：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>while(1)<br>{<br>}</td></tr></tbody></table><br>　　有的程序员这样写：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>for(;;)<br>{<br>}</td></tr></tbody></table><br>　　这个语法没有确切表达代码的含义，我们从for(;;)看不出什么，只有弄明白for(;;)在C语言中意味着无条件循环才明白其意。<br><br>　　下面是几个"著名"的死循环：<br><br>　　（1）操作系统是死循环；<br><br>　　（2）WIN32程序是死循环；<br><br>　　（3）嵌入式系统软件是死循环；<br><br>　　（4）多线程程序的线程处理函数是死循环。<br><br>　
　你可能会辩驳，大声说："凡事都不是绝对的，2、3、4都可以不是死循环"。Yes，you are
right，但是你得不到鲜花和掌声。实际上，这是一个没有太大意义的牛角尖，因为这个世界从来不需要一个处理完几个消息就喊着要OS杀死它的WIN32
程序，不需要一个刚开始RUN就自行了断的嵌入式系统，不需要莫名其妙启动一个做一点事就干掉自己的线程。有时候，过于严谨制造的不是便利而是麻烦。君不
见，五层的TCP/IP协议栈超越严谨的ISO/OSI七层协议栈大行其道成为事实上的标准？<br><br>　　经常有网友讨论：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>printf("%d,%d",++i,i++); /* 输出是什么？*/<br>c = a+++b; /* c=? */</td></tr></tbody></table><br>　　等类似问题。面对这些问题，我们只能发出由衷的感慨：世界上还有很多有意义的事情等着我们去消化摄入的食物。<br><br>　　实际上，嵌入式系统要运行到世界末日。<br><br>　　<b>中断服务程序</b><br><br>　
　中断是嵌入式系统中重要的组成部分，但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持，提供新的关键字用于标示中断服务程序
(ISR)，类似于__interrupt、#program
interrupt等。当一个函数被定义为ISR的时候，编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。<br><br>　　中断服务程序需要满足如下要求：<br><br>　　(1)不能返回值；<br><br>　　(2)不能向ISR传递参数；<br><br>　　(3) ISR应该尽可能的短小精悍；<br><br>　　(4) printf(char * lpFormatString,…)函数会带来重入和性能问题，不能在ISR中采用。<br><br>　　在某项目的开发中，我们设计了一个队列，在中断服务程序中，只是将中断类型添加入该队列中，在主程序的死循环中不断扫描中断队列是否有中断，有则取出队列中的第一个中断类型，进行相应处理。<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 存放中断的队列 */<br>typedef struct tagIntQueue<br>{<br>　int intType; /* 中断类型 */<br>　struct tagIntQueue *next;<br>}IntQueue;<br><br>IntQueue lpIntQueueHead;<br><br>__interrupt ISRexample () <br>{<br>　int intType;<br>　intType = GetSystemType();<br>　QueueAddTail(lpIntQueueHead, intType)；/* 在队列尾加入新的中断 */<br>}</td></tr></tbody></table><br>　　在主程序循环中判断是否有中断： <br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>While(1)<br>{<br>　If( !IsIntQueueEmpty() )<br>　{<br>　　intType = GetFirstInt();<br>　　switch(intType) /* 是不是很象WIN32程序的消息解析函数? */<br>　　{<br>　　　/* 对，我们的中断类型解析很类似于消息驱动 */<br>　　　case xxx: /* 我们称其为"中断驱动"吧？ */<br>　　　　…<br>　　　　break;<br>　　　case xxx:<br>　　　　…<br>　　　　break;<br>　　　…<br>　　}<br>　}<br>}</td></tr></tbody></table><br>　　按上述方法设计的中断服务程序很小，实际的工作都交由主程序执行了。<br><b>硬件驱动模块</b><br><br>　　一个硬件驱动模块通常应包括如下函数：<br><br>　　（1）中断服务程序ISR<br><br>　　（2）硬件初始化<br><br>　　a.修改寄存器，设置硬件参数（如UART应设置其波特率，AD/DA设备应设置其采样速率等）；<br><br>　　b.将中断服务程序入口地址写入中断向量表：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>/* 设置中断向量表 */<br>m_myPtr = make_far_pointer(0l); /* 返回void far型指针void far * */ <br>m_myPtr += ITYPE_UART; /* ITYPE_UART： uart中断服务程序 */<br>/* 相对于中断向量表首地址的偏移 */<br>*m_myPtr = &amp;UART _Isr; /* UART _Isr：UART的中断服务程序 */</td></tr></tbody></table><br>　　（3）设置CPU针对该硬件的控制线<br><br>　　a.如果控制线可作PIO（可编程I/O）和控制信号用，则设置CPU内部对应寄存器使其作为控制信号；<br><br>　　b.设置CPU内部的针对该设备的中断屏蔽位，设置中断方式（电平触发还是边缘触发）。<br><br>　　（4）提供一系列针对该设备的操作接口函数。例如，对于LCD，其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数；而对于实时钟，其驱动模块则需提供获取时间、设置时间等函数。<br><br>　　<b>C的面向对象化</b><br><br>　　在面向对象的语言里面，出现了类的概念。类是对特定数据的特定操作的集合体。类包含了两个范畴：数据和操作。而C语言中的struct仅仅是数据的集合，我们可以利用函数指针将struct模拟为一个包含数据和操作的"类"。下面的C程序模拟了一个最简单的"类"：<br><br>
<table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%">
<tbody>
<tr>
<td>#ifndef C_Class<br>#define C_Class struct<br>#endif<br>C_Class A <br>{<br>　C_Class A *A_this; /* this指针 */<br>　void (*Foo)(C_Class A *A_this); /* 行为：函数指针 */<br>　int a; /* 数据 */<br>　int b;<br>};</td></tr></tbody></table><br>　
　我们可以利用C语言模拟出面向对象的三个特性：封装、继承和多态，但是更多的时候，我们只是需要将数据与行为封装以解决软件结构混乱的问题。C模拟面向
对象思想的目的不在于模拟行为本身，而在于解决某些情况下使用C语言编程时程序整体框架结构分散、数据和函数脱节的问题。我们在后续章节会看到这样的例
子。 <br><br>　　<b>总结</b><br><br>　　本篇介绍了嵌入式系统编程软件架构方面的知识，主要包括模块划分、多任务还是单任务选取、单任务程序典型架构、中断服务程序、硬件驱动模块设计等，从宏观上给出了一个嵌入式系统软件所包含的主要元素。<br><br>　　请记住：软件结构是软件的灵魂！结构混乱的程序面目可憎，调试、测试、维护、升级都极度困难。<img src ="http://www.blogjava.net/faintbear/aggbug/13654.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:29 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13654.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系统编程修炼之一:背景篇</title><link>http://www.blogjava.net/faintbear/archive/2005/09/21/13653.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 21 Sep 2005 09:28:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/21/13653.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/13653.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/21/13653.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/13653.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/13653.html</trackback:ping><description><![CDATA[<h1><font size="2">http://www.upsdn.net/(推荐下，感兴趣的同志可以看看哈，便携系统开发网)</font><br></h1><br><br><h1>C语言嵌入式系统编程修炼之一:背景篇</h1>





<p>作者:宋宝华 &nbsp; 更新日期:2005-08-30<br>
  来源:yesky.com &nbsp;&nbsp;
	 </p>


不同于一般形式的软件编程，嵌入式系统编程建立在特定的硬件平台上，势必要求其编程语言具备较强的硬件直接操作能力。无疑，汇编语言具备这样的特质。但
是，归因于汇编语言开发过程的复杂性，它并不是嵌入式系统开发的一般选择。而与之相比，C语言--一种"高级的低级"语言，则成为嵌入式系统开发的最佳选
择。笔者在嵌入式系统项目的开发过程中，一次又一次感受到C语言的精妙，沉醉于C语言给嵌入式开发带来的便利。<br><br>　　图1给出了本文的讨论所基于的硬件平台，实际上，这也是大多数嵌入式系统的硬件平台。它包括两部分： <br><br>　　（1） 以通用处理器为中心的协议处理模块，用于网络控制协议的处理；<br><br>　　（2） 以数字信号处理器（DSP）为中心的信号处理模块，用于调制、解调和数/模信号转换。<br><br>　　本文的讨论主要围绕以通用处理器为中心的协议处理模块进行，因为它更多地牵涉到具体的C语言编程技巧。而DSP编程则重点关注具体的数字信号处理算法，主要涉及通信领域的知识，不是本文的讨论重点。<br><br>　
　着眼于讨论普遍的嵌入式系统C编程技巧，系统的协议处理模块没有选择特别的CPU，而是选择了众所周知的CPU芯片--80186，每一位学习过《微机
原理》的读者都应该对此芯片有一个基本的认识，且对其指令集比较熟悉。80186的字长是16位，可以寻址到的内存空间为1MB，只有实地址模式。C语言
编译生成的指针为32位（双字），高16位为段地址，低16位为段内编译，一段最多64KB。<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img src="http://www.upsdn.net/images/2005-07/c_optimsie_513_1.gif" alt="" border="0"><br>图1 系统硬件架构</div></td></tr></tbody></table><br>　　协议处理模块中的FLASH和RAM几乎是每个嵌入式系统的必备设备，前者用于存储程序，后者则是程序运行时指令及数据的存放位置。系统所选择的FLASH和RAM的位宽都为16位，与CPU一致。<br><br>　　实时钟芯片可以为系统定时，给出当前的年、月、日及具体时间（小时、分、秒及毫秒），可以设定其经过一段时间即向CPU提出中断或设定报警时间到来时向CPU提出中断（类似闹钟功能）。<br><br>　
　NVRAM（非易失去性RAM）具有掉电不丢失数据的特性，可以用于保存系统的设置信息，譬如网络协议参数等。在系统掉电或重新启动后，仍然可以读取先
前的设置信息。其位宽为8位，比CPU字长小。文章特意选择一个与CPU字长不一致的存储芯片，为后文中一节的讨论创造条件。<br><br>　　UART则完成CPU并行数据传输与RS-232串行数据传输的转换，它可以在接收到[1~MAX_BUFFER]字节后向CPU提出中断，MAX_BUFFER为UART芯片存储接收到字节的最大缓冲区。<br><br>　　键盘控制器和显示控制器则完成系统人机界面的控制。<br><br>　　以上提供的是一个较完备的嵌入式系统硬件架构，实际的系统可能包含更少的外设。之所以选择一个完备的系统，是为了后文更全面的讨论嵌入式系统C语言编程技巧的方方面面，所有设备都会成为后文的分析目标。<br><br>　
　嵌入式系统需要良好的软件开发环境的支持，由于嵌入式系统的目标机资源受限，不可能在其上建立庞大、复杂的开发环境，因而其开发环境和目标运行环境相互
分离。因此，嵌入式应用软件的开发方式一般是，在宿主机(Host)上建立开发环境，进行应用程序编码和交叉编译，然后宿主机同目标机(Target)建
立连接，将应用程序下载到目标机上进行交叉调试，经过调试和优化，最后将应用程序固化到目标机中实际运行。 <br><br>　　CAD-UL是适用于
x86处理器的嵌入式应用软件开发环境，它运行在Windows操作系统之上，可生成x86处理器的目标代码并通过PC机的COM口（RS-232串口）
或以太网口下载到目标机上运行，如图2。其驻留于目标机FLASH存储器中的monitor程序可以监控宿主机Windows调试平台上的用户调试指令，
获取CPU寄存器的值及目标机存储空间、I/O空间的内容。<br><br>
<table align="center" border="0" width="90%">
<tbody>
<tr>
<td>
<div align="center"><img alt="" src="http://www.upsdn.net/images/2005-07/c_optimsie_513_2.gif" border="0"><br>图2 交叉开发环境</div></td></tr></tbody></table><br>　　后续章节将从软件架构、内存操作、屏幕操作、键盘操作、性能优化等
多方面阐述C语言嵌入式系统的编程技巧。软件架构是一个宏观概念，与具体硬件的联系不大；内存操作主要涉及系统中的FLASH、RAM和NVRAM芯片；
屏幕操作则涉及显示控制器和实时钟；键盘操作主要涉及键盘控制器；性能优化则给出一些具体的减小程序时间、空间消耗的技巧。<br><br>　　在我们的修炼旅途中将经过25个关口，这些关口主分为两类，一类是技巧型，有很强的适用性；一类则是常识型，在理论上有些意义。<img src ="http://www.blogjava.net/faintbear/aggbug/13653.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-21 17:28 <a href="http://www.blogjava.net/faintbear/archive/2005/09/21/13653.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>修练8年C++面向对象程序设计之体会(林锐博士)</title><link>http://www.blogjava.net/faintbear/archive/2005/09/09/12514.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Fri, 09 Sep 2005 02:59:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/09/09/12514.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/12514.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/09/09/12514.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/12514.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/12514.html</trackback:ping><description><![CDATA[修练8年C++面向对象程序设计之体会<BR><BR>
<P style="TEXT-INDENT: 2em">六年前，我刚热恋“面向对象”（Object-Oriented）时，一口气记住了近十个定义。六年后，我从几十万行程序中滚爬出来准备写点心得体会时，却无法解释什么是“面向对象”，就象说不清楚什么是数学那样。软件工程中的时髦术语“面向对象分析”和“面向对象设计”，通常是针对“需求分析”和“系统设计”环节的。“面向对象”有几大学派，就象如来佛、上帝和真主用各自的方式定义了这个世界，并留下一堆经书来解释这个世界。</P>
<P style="TEXT-INDENT: 2em">有些学者建议这样找“对象”：分析一个句子的语法，找出名词和动词，名词就是对象，动词则是对象的方法（即函数）。</P>
<P style="TEXT-INDENT: 2em">当年国民党的文人为了对抗毛泽东的《沁园春·雪》，特意请清朝遗老们写了一些对仗工整的诗，请蒋介石过目。老蒋看了气得大骂：“娘希匹，全都有一股棺材里腐尸的气味。”我看了几千页的软件工程资料，终于发现自己有些“弱智”，无法理解“面向对象”的理论，同时醒悟到“编程是硬道理。”</P>
<P style="TEXT-INDENT: 2em">面向对象程序设计语言很多，如Smalltalk、Ada、Eiffel、Object Pascal、Visual Basic、C++等等。C++语言最讨人喜欢，因为它兼容C 语言，并且具备C 语言的性能。近几年，一种叫Java 的纯面向对象语言红极一时，不少人叫喊着要用Java 革C++的命。我认为Java 好比是C++的外甥，虽然不是直接遗传的，但也几分象样。外甥在舅舅身上玩耍时洒了一泡尿，俩人不该为此而争吵。</P>
<P style="TEXT-INDENT: 2em">关于C++程序设计的书藉非常多，本章不讲C++的语法，只讲一些小小的编程道理。如果我能早几年明白这些小道理，就可以大大改善数十万行程序的质量了。</P>
<P style="TEXT-INDENT: 2em"><STRONG>1. C++面向对象程序设计的重要概念</STRONG></P>
<P style="TEXT-INDENT: 2em">早期革命影片里有这样一个角色，他说：“我是党代表，我代表党，我就是党。”后来他给同志们带来了灾难。</P>
<P style="TEXT-INDENT: 2em">会用C++的程序员一定懂得面向对象程序设计吗？</P>
<P style="TEXT-INDENT: 2em">不会用C++的程序员一定不懂得面向对象程序设计吗？</P>
<P style="TEXT-INDENT: 2em">两者都未必。就象坏蛋入党后未必能成为好人，好人不入党未必变成坏蛋那样。</P>
<P style="TEXT-INDENT: 2em">我不怕触犯众怒地说句大话：“C++没有高手，C 语言才有高手。”在用C 和C++编程8年之后，我深深地遗憾自己不是C 语言的高手，更遗憾没有人点拨我如何进行面向对象程序设计。我和很多C++程序员一样，在享用到C++语法的好处时便以为自己已经明白了面向对象程序设计。就象挤掉牙膏卖牙膏皮那样，真是暴殄天物呀。</P>
<P style="TEXT-INDENT: 2em">人们不懂拼音也会讲普通话，如果懂得拼音则会把普通话讲得更好。不懂面向对象程序设计也可以用C++编程，如果懂得面向对象程序设计则会把C++程序编得更好。本节讲述三个非常基础的概念：“类与对象”、“继承与组合”、“虚函数与多态”。理解这些概念，有助于提高程序的质量，特别是提高“可复用性”与“可扩充性”。</P>
<P style="TEXT-INDENT: 2em">1.1 类与对象</P>
<P style="TEXT-INDENT: 2em">对象（Object）是类（Class）的一个实例（Instance）。如果将对象比作房子，那么类就是房子的设计图纸。所以面向对象程序设计的重点是类的设计，而不是对象的设计。类可以将数据和函数封装在一起，其中函数表示了类的行为（或称服务）。类提供关键字public、protected 和private 用于声明哪些数据和函数是公有的、受保护的或者是私有的。</P>
<P style="TEXT-INDENT: 2em">这样可以达到信息隐藏的目的，即让类仅仅公开必须要让外界知道的内容，而隐藏其它一切内容。我们不可以滥用类的封装功能，不要把它当成火锅，什么东西都往里扔。</P>
<P style="TEXT-INDENT: 2em">类的设计是以数据为中心，还是以行为为中心？</P>
<P style="TEXT-INDENT: 2em">主张“以数据为中心”的那一派人关注类的内部数据结构，他们习惯上将private 类型的数据写在前面，而将public 类型的函数写在后面，如表8.1(a)所示。</P>
<P style="TEXT-INDENT: 2em">主张“以行为为中心”的那一派人关注类应该提供什么样的服务和接口，他们习惯上将public 类型的函数写在前面，而将private 类型的数据写在后面，如表8.1(b)所示。</P>
<P style="TEXT-INDENT: 2em">
<CENTER><IMG src="http://cimg.163.com/catchpic/1/15/15085E8BC7E018B2BDAB3DFEED127750.gif" border=0></CENTER>
<P></P>
<P style="TEXT-INDENT: 2em"></P>
<P style="TEXT-INDENT: 2em">很多C++教课书主张在设计类时“以数据为中心”。我坚持并且建议读者在设计类时“以行为为中心”，即首先考虑类应该提供什么样的函数。Microsoft 公司的COM 规范的核心是接口设计，COM 的接口就相当于类的公有函数[Rogerson 1999]。在程序设计方面，咱们不要怀疑Microsoft 公司的风格。</P>
<P style="TEXT-INDENT: 2em">设计孤立的类是比较容易的，难的是正确设计基类及其派生类。因为有些程序员搞不清楚“继承”（Inheritance）、“组合”（Composition）、“多态”（ Polymorphism）这些概念。 <BR><BR><BR></P>
<P style="TEXT-INDENT: 2em">1.2 继承与组合</P>
<P style="TEXT-INDENT: 2em">如果A 是基类，B 是A 的派生类，那么B 将继承A 的数据和函数。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Func1(void);</P>
<P style="TEXT-INDENT: 2em">void Func2(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B : public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Func3(void);</P>
<P style="TEXT-INDENT: 2em">void Func4(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// Example</P>
<P style="TEXT-INDENT: 2em">main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">B b; // B的一个对象</P>
<P style="TEXT-INDENT: 2em">b.Func1(); // B 从A 继承了函数Func1</P>
<P style="TEXT-INDENT: 2em">b.Func2(); // B 从A 继承了函数Func2</P>
<P style="TEXT-INDENT: 2em">b.Func3();</P>
<P style="TEXT-INDENT: 2em">b.Func4();</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">这个简单的示例程序说明了一个事实：C++的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用，才要防止乱用“继承”。我们要给“继承”立一些使用规则：</P>
<P style="TEXT-INDENT: 2em">一、如果类A 和类B 毫不相关，不可以为了使B 的功能更多些而让B 继承A 的功能。</P>
<P style="TEXT-INDENT: 2em">不要觉得“不吃白不吃”，让一个好端端的健壮青年无缘无故地吃人参补身体。</P>
<P style="TEXT-INDENT: 2em">二、如果类B 有必要使用A 的功能，则要分两种情况考虑：</P>
<P style="TEXT-INDENT: 2em">（1）若在逻辑上B 是A 的“一种”（a kind of ），则允许B 继承A 的功能。如男人（Man）是人（Human）的一种，男孩（Boy）是男人的一种。那么类Man 可以从类Human 派生，类Boy 可以从类Man 派生。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class Human</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class Man : public Human</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class Boy : public Man</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">（2）若在逻辑上A 是B 的“一部分”（a part of），则不允许B 继承A 的功能，而是要用A和其它东西组合出B。例如眼（Eye）、鼻（Nose）、口（Mouth）、耳（Ear）是头（Head）的一部分，所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成，不是派生而成。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class Eye</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Look(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class Nose</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Smell(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class Mouth</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Eat(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class Ear</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Listen(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 正确的设计，冗长的程序</P>
<P style="TEXT-INDENT: 2em">class Head</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Look(void) { m_eye.Look(); }</P>
<P style="TEXT-INDENT: 2em">void Smell(void) { m_nose.Smell(); }</P>
<P style="TEXT-INDENT: 2em">void Eat(void) { m_mouth.Eat(); }</P>
<P style="TEXT-INDENT: 2em">void Listen(void) { m_ear.Listen(); }</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">Eye m_eye;</P>
<P style="TEXT-INDENT: 2em">Nose m_nose;</P>
<P style="TEXT-INDENT: 2em">Mouth m_mouth;</P>
<P style="TEXT-INDENT: 2em">Ear m_ear;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">如果允许Head 从Eye、Nose、Mouth、Ear 派生而成，那么Head 将自动具有Look、Smell、Eat、Listen 这些功能：</P>
<P style="TEXT-INDENT: 2em">// 错误的设计</P>
<P style="TEXT-INDENT: 2em">class Head : public Eye, public Nose, public Mouth, public Ear</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">上述程序十分简短并且运行正确，但是这种设计却是错误的。很多程序员经不起“继承”的诱惑而犯下设计错误。</P>
<P style="TEXT-INDENT: 2em">一只公鸡使劲地追打一只刚下了蛋的母鸡，你知道为什么吗？</P>
<P style="TEXT-INDENT: 2em">因为母鸡下了鸭蛋。</P>
<P style="TEXT-INDENT: 2em">本书3.3 节讲过“运行正确”的程序不见得就是高质量的程序，此处就是一个例证。 <BR><BR><BR><BR></P>
<P style="TEXT-INDENT: 2em">1.3 虚函数与多态</P>
<P style="TEXT-INDENT: 2em">除了继承外，C++的另一个优良特性是支持多态，即允许将派生类的对象当作基类的对象使用。如果A 是基类，B 和C 是A 的派生类，多态函数Test 的参数是A 的 指针。那么Test 函数可以引用A、B、C 的对象。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Func1(void);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void Test(A *a)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">a->Func1();</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">class B : public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class C : public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// Example</P>
<P style="TEXT-INDENT: 2em">main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">A a;</P>
<P style="TEXT-INDENT: 2em">B b;</P>
<P style="TEXT-INDENT: 2em">C c;</P>
<P style="TEXT-INDENT: 2em">Test(&a);</P>
<P style="TEXT-INDENT: 2em">Test(&b);</P>
<P style="TEXT-INDENT: 2em">Test(&c);</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">以上程序看不出“多态”有什么价值，加上虚函数和抽象基类后，“多态”的威力就显示出来了。</P>
<P style="TEXT-INDENT: 2em">C++用关键字virtual 来声明一个函数为虚函数，派生类的虚函数将（override）基类对应的虚函数的功能。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">virtual void Func1(void){ cout<< “This is A::Func1 \n”}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void Test(A *a)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">a->Func1();</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">class B : public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">virtual void Func1(void){ cout<< “This is B::Func1 \n”}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class C : public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">virtual void Func1(void){ cout<< “This is C::Func1 \n”}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// Example</P>
<P style="TEXT-INDENT: 2em">main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">A a;</P>
<P style="TEXT-INDENT: 2em">B b;</P>
<P style="TEXT-INDENT: 2em">C c;</P>
<P style="TEXT-INDENT: 2em">Test(&a); // 输出This is A::Func1</P>
<P style="TEXT-INDENT: 2em">Test(&b); // 输出This is B::Func1</P>
<P style="TEXT-INDENT: 2em">Test(&c); // 输出This is C::Func1</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">如果基类A 定义如下：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">virtual void Func1(void)=0;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">那么函数Func1 叫作纯虚函数，含有纯虚函数的类叫作抽象基类。抽象基类只管定义纯虚函数的形式，具体的功能由派生类实现。</P>
<P style="TEXT-INDENT: 2em">结合“抽象基类”和“多态”有如下突出优点：</P>
<P style="TEXT-INDENT: 2em">（1）应用程序不必为每一个派生类编写功能调用，只需要对抽象基类进行处理即可。这一</P>
<P style="TEXT-INDENT: 2em">招叫“以不变应万变”，可以大大提高程序的可复用性（这是接口设计的复用，而不是代码实现的复用）。</P>
<P style="TEXT-INDENT: 2em">（2）派生类的功能可以被基类指针引用，这叫向后兼容，可以提高程序的可扩充性和可维护性。以前写的程序可以被将来写的程序调用不足为奇，但是将来写的程序可以被以前写的程序调用那可了不起。 <BR><BR><BR></P>
<P style="TEXT-INDENT: 2em"><STRONG>2 良好的编程风格</STRONG></P>
<P style="TEXT-INDENT: 2em">内功深厚的武林高手出招往往平淡无奇。同理，编程高手也不会用奇门怪招写程序。良好的编程风格是产生高质量程序的前提。</P>
<P style="TEXT-INDENT: 2em">2.1 命名约定</P>
<P style="TEXT-INDENT: 2em">有不少人编程时用拼音给函数或变量命名，这样做并不能说明你很爱国，却会让用此程序的人迷糊（很多南方人不懂拼音，我就不懂）。程序中的英文一般不会太复杂，用词要力求准确。匈牙利命名法是Microsoft 公司倡导的[Maguire 1993]，虽然很烦琐，但用习惯了也就成了自然。没有人强迫你采用何种命名法，但有一点应该做到：自己的程序命名必须一致。</P>
<P style="TEXT-INDENT: 2em">以下是我编程时采用的命名约定：</P>
<P style="TEXT-INDENT: 2em">（1）宏定义用大写字母加下划线表示，如MAX_LENGTH；</P>
<P style="TEXT-INDENT: 2em">（2）函数用大写字母开头的单词组合而成，如SetName, GetName ；</P>
<P style="TEXT-INDENT: 2em">（3）指针变量加前缀p，如*pNode ；</P>
<P style="TEXT-INDENT: 2em">（4）BOOL 变量加前缀b，如bFlag ；</P>
<P style="TEXT-INDENT: 2em">（5）int 变量加前缀i，如iWidth ；</P>
<P style="TEXT-INDENT: 2em">（6）float 变量加前缀f，如fWidth ；</P>
<P style="TEXT-INDENT: 2em">（7）double 变量加前缀d，如dWidth ；</P>
<P style="TEXT-INDENT: 2em">（8）字符串变量加前缀str，如strName ；</P>
<P style="TEXT-INDENT: 2em">（9）枚举变量加前缀e，如eDrawMode ；</P>
<P style="TEXT-INDENT: 2em">（10）类的成员变量加前缀m_，如m_strName, m_iWidth ；</P>
<P style="TEXT-INDENT: 2em">对于int, float, double 型的变量，如果变量名的含义十分明显，则不加前缀，避免烦琐。如用于循环的int 型变量i,j,k ；float 型的三维坐标（x,y,z）等。</P>
<P style="TEXT-INDENT: 2em">2.2 使用断言</P>
<P style="TEXT-INDENT: 2em">程序一般分为Debug 版本和Release 版本，Debug 版本用于内部调试，Release 版本发行给用户使用。断言assert 是仅在Debug 版本起作用的宏，它用于检查“不应该”发生的情况。以下是一个内存复制程序，在运行过程中，如果assert 的参数为假，那么程序就会中止（一般地还会出现提示对话，说明在什么地方引发了assert）。</P>
<P style="TEXT-INDENT: 2em">//复制不重叠的内存块</P>
<P style="TEXT-INDENT: 2em">void memcpy(void *pvTo, void *pvFrom, size_t size)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">void *pbTo = (byte *) pvTo;</P>
<P style="TEXT-INDENT: 2em">void *pbFrom = (byte *) pvFrom;</P>
<P style="TEXT-INDENT: 2em">assert( pvTo != NULL && pvFrom != NULL );</P>
<P style="TEXT-INDENT: 2em">while(size - - > 0 )</P>
<P style="TEXT-INDENT: 2em">*pbTo + + = *pbFrom + + ;</P>
<P style="TEXT-INDENT: 2em">return (pvTo);</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">assert 不是一个仓促拼凑起来的宏，为了不在程序的Debug 版本和Release 版本引起差别，assert 不应该产生任何副作用。所以assert 不是函数，而是宏。程序员可以把assert 看成一个在任何系统状态下都可以安全使用的无害测试手段。</P>
<P style="TEXT-INDENT: 2em">很少有比跟踪到程序的断言，却不知道该断言的作用更让人沮丧的事了。你化了很多时间，不是为了排除错误，而只是为了弄清楚这个错误到底是什么。有的时候，程序员偶尔还会设计出有错误的断言。所以如果搞不清楚断言检查的是什么，就很难判断错误是出现在程序中，还是出现在断言中。幸运的是这个问题很好解决，只要加上清晰的注释即可。这本是显而易见的事情，可是很少有程序员这样做。这好比一个人在森林里，看到树上钉着一块“危险”的大牌子。但危险到底是什么？树要倒？有废井？有野兽？除非告诉人们“危险”是什么，否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略，甚至被删除。[Maguire 1993]</P>
<P style="TEXT-INDENT: 2em">以下是使用断言的几个原则：</P>
<P style="TEXT-INDENT: 2em">（1）使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别，后者是必然存在的并且是一定要作出处理的。</P>
<P style="TEXT-INDENT: 2em">（2）使用断言对函数的参数进行确认。</P>
<P style="TEXT-INDENT: 2em">（3）在编写函数时，要进行反复的考查，并且自问：“我打算做哪些假定？”一旦确定了的</P>
<P style="TEXT-INDENT: 2em">假定，就要使用断言对假定进行检查。</P>
<P style="TEXT-INDENT: 2em">（4）一般教科书都鼓励程序员们进行防错性的程序设计，但要记住这种编程风格会隐瞒错误。当进行防错性编程时，如果“不可能发生”的事情的确发生了，则要使用断言进行报警。 <BR><BR><BR></P>
<P style="TEXT-INDENT: 2em">2.3 new、delete 与指针</P>
<P style="TEXT-INDENT: 2em">在C++中，操作符new 用于申请内存，操作符delete 用于释放内存。在C 语言中，函数malloc 用于申请内存，函数free 用于释放内 存。由于C++兼容C 语言，所以new、delete、malloc、free 都有可能一起使用。new 能比malloc 干更多的事，它可以申请对象的内存，而malloc 不能。C++和C 语言中的指针威猛无比，用错了会带来灾难。对于一个指针p，如果是用new申请的内存，则必须用delete 而不能用free 来释放。如果是用malloc 申请的内存，则必须用free 而不能用delete 来释放。在用delete 或用free 释放p 所指的内存后，应该马上显式地将p 置为NULL，以防下次使用p 时发生错误。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">void Test(void)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">float *p;</P>
<P style="TEXT-INDENT: 2em">p = new float[100];</P>
<P style="TEXT-INDENT: 2em">if(p==NULL) return;</P>
<P style="TEXT-INDENT: 2em">…// do something</P>
<P style="TEXT-INDENT: 2em">delete p;</P>
<P style="TEXT-INDENT: 2em">p=NULL; // 良好的编程风格</P>
<P style="TEXT-INDENT: 2em">// 可以继续使用p</P>
<P style="TEXT-INDENT: 2em">p = new float[500];</P>
<P style="TEXT-INDENT: 2em">if(p==NULL) return;</P>
<P style="TEXT-INDENT: 2em">…// do something else</P>
<P style="TEXT-INDENT: 2em">delete p;</P>
<P style="TEXT-INDENT: 2em">p=NULL;</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">我们还要预防“野指针”，“野指针”是指向“垃圾”内存的指针，主要成因有两种：</P>
<P style="TEXT-INDENT: 2em">（1）指针没有初始化。</P>
<P style="TEXT-INDENT: 2em">（2）指针指向已经释放的内存，这种情况最让人防不胜防，示例程序如下：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void Func(void){…}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void Test(void)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">A *p;</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">A a;</P>
<P style="TEXT-INDENT: 2em">p = &a; // 注意a 的生命期</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">p->Func(); // p 是“野指针”，程序出错</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">2.4 使用const</P>
<P style="TEXT-INDENT: 2em">在定义一个常量时，const 比#define 更加灵活。用const 定义的常量含有数据类型，该常量可以参与逻辑运算。例如：</P>
<P style="TEXT-INDENT: 2em">const int LENGTH = 100; // LENGTH 是int 类型</P>
<P style="TEXT-INDENT: 2em">const float MAX=100; // MAX 是float 类型</P>
<P style="TEXT-INDENT: 2em">#define LENGTH 100 // LENGTH 无类型</P>
<P style="TEXT-INDENT: 2em">#define MAX 100 // MAX 无类型</P>
<P style="TEXT-INDENT: 2em">除了能定义常量外，const 还有两个“保护”功能：</P>
<P style="TEXT-INDENT: 2em">一、强制保护函数的参数值不发生变化</P>
<P style="TEXT-INDENT: 2em">以下程序中，函数f 不会改变输入参数name 的值，但是函数g 和h 都有可能改变name的值。</P>
<P style="TEXT-INDENT: 2em">void f(String s); // pass by value</P>
<P style="TEXT-INDENT: 2em">void g(String &s); // pass by referance</P>
<P style="TEXT-INDENT: 2em">void h(String *s); // pass by pointer</P>
<P style="TEXT-INDENT: 2em">main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">String name=“Dog”;</P>
<P style="TEXT-INDENT: 2em">f(name); // name 的值不会改变</P>
<P style="TEXT-INDENT: 2em">g(name); // name 的值可能改变</P>
<P style="TEXT-INDENT: 2em">h(name); // name 的值可能改变</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">对于一个函数而言，如果其‘&’或‘*’类型的参数只作输入用，不作输出用，那么应当在该参数前加上const，以确保函数的代码不会改变该参数的值（如果改变了该参数的值，编译器会出现错误警告）。因此上述程序中的函数g 和h 应该定义成：</P>
<P style="TEXT-INDENT: 2em">void g(const String &s);</P>
<P style="TEXT-INDENT: 2em">void h(const String *s);</P>
<P style="TEXT-INDENT: 2em">二、强制保护类的成员函数不改变任何数据成员的值</P>
<P style="TEXT-INDENT: 2em">以下程序中，类stack 的成员函数Count 仅用于计数，为了确保Count 不改变类中的任何数据成员的值，应将函数Count 定义成const 类型。</P>
<P style="TEXT-INDENT: 2em">class Stack</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">void push(int elem);</P>
<P style="TEXT-INDENT: 2em">void pop(void);</P>
<P style="TEXT-INDENT: 2em">int Count(void) const; // const 类型的函数</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">int num;</P>
<P style="TEXT-INDENT: 2em">int data[100];</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">int Stack::Count(void) const</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">++ num; // 编译错误，num 值发生变化</P>
<P style="TEXT-INDENT: 2em">pop(); // 编译错误，pop 将改变成员变量的值</P>
<P style="TEXT-INDENT: 2em">return num;</P>
<P style="TEXT-INDENT: 2em">} <BR><BR><BR></P>
<P style="TEXT-INDENT: 2em">2.5 其它建议</P>
<P style="TEXT-INDENT: 2em">（1）不要编写一条过分复杂的语句，紧凑的C++/C 代码并不见到能得到高效率的机器代码，却会降低程序的可理解性，程序出错误的几率也会提高。</P>
<P style="TEXT-INDENT: 2em">（2）不要编写集多种功能于一身的函数，在函数的返回值中，不要将正常值和错误标志混在一起。</P>
<P style="TEXT-INDENT: 2em">（3）不要将BOOL 值TRUE 和FALSE 对应于1 和0 进行编程。大多数编程语言将FALSE定义为0，任何非0 值都是TRUE。Visual C++将TRUE 定义为1，而Visual Basic 则将TRUE定义为-1。示例程序如下：</P>
<P style="TEXT-INDENT: 2em">BOOL flag;</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">if(flag) { // do something } // 正确的用法</P>
<P style="TEXT-INDENT: 2em">if(flag==TRUE) { // do something } // 危险的用法</P>
<P style="TEXT-INDENT: 2em">if(flag==1) { // do something } // 危险的用法</P>
<P style="TEXT-INDENT: 2em">if(!flag) { // do something } // 正确的用法</P>
<P style="TEXT-INDENT: 2em">if(flag==FALSE) { // do something } // 不合理的用法</P>
<P style="TEXT-INDENT: 2em">if(flag==0) { // do something } // 不合理的用法</P>
<P style="TEXT-INDENT: 2em">（4）小心不要将“= =”写成“=”，编译器不会自动发现这种错误。</P>
<P style="TEXT-INDENT: 2em">（5）不要将123 写成0123，后者是八进制的数值。</P>
<P style="TEXT-INDENT: 2em">（6）将自己经常犯的编程错误记录下来，制成表格贴在计算机旁边。</P>
<P style="TEXT-INDENT: 2em"><STRONG>3 小结</STRONG></P>
<P style="TEXT-INDENT: 2em">C++/C 程序设计如同少林寺的武功一样博大精深，我练了8 年，大概只学到二三成。所以无论什么时候，都不要觉得自己的编程水平天下第一，看到别人好的技术和风格，要虚心学习。本章的内容少得可怜，就象口渴时只给你一颗杨梅吃，你一定不过瘾。我借花献佛，推荐一本好书：Marshall P. Cline 著的《C++ FAQs》[Cline 1995]。你看了后一定会赞不绝口。会编写C++/C 程序，不要因此得意洋洋，这只是程序员基本的技能要求而已。如果把系统分析和系统设计比作“战略决策”，那么编程充其量只是“战术”。如果指挥官是个大笨蛋，士兵再勇敢也会吃败仗。所以我们程序员不要只把眼光盯在程序上，要让自己博学多才。我们应该向北京胡同里的小孩们学习，他们小小年纪就能指点江山，评论世界大事。</P><img src ="http://www.blogjava.net/faintbear/aggbug/12514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-09-09 10:59 <a href="http://www.blogjava.net/faintbear/archive/2005/09/09/12514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++ primer 笔记（名字空间）</title><link>http://www.blogjava.net/faintbear/archive/2005/07/11/7496.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Mon, 11 Jul 2005 05:24:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/07/11/7496.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/7496.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/07/11/7496.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/7496.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/7496.html</trackback:ping><description><![CDATA[<P><FONT size=2>8.5关于名字空间定义<BR>&nbsp;&nbsp;&nbsp; 全局实体(global entity)<BR>&nbsp;&nbsp;&nbsp; 全局名字空间污染(global namespace pollution)</FONT></P>
<P><FONT size=2>8.5.1名字空间定义<BR>&nbsp;&nbsp;&nbsp; namespace 开头，后面是名字空间的名字。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp; namespace cplusplus_primer {<BR>&nbsp;&nbsp;&nbsp;&nbsp; class matrix {/*****/};<BR>&nbsp;&nbsp;&nbsp;&nbsp; void inverse(matrix &amp;);<BR>&nbsp;&nbsp;&nbsp;&nbsp; matrix operator+(const matrix &amp;m1,matrix &amp;m2)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /******/<BR>&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; const double pi = 3.1416;<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><BR><FONT size=2>&nbsp;&nbsp;&nbsp; 在名字空间cplusplus_primer中声明的类的名字是<BR>&nbsp;&nbsp;&nbsp; cplusplus_primer::matrix<BR>&nbsp;&nbsp;&nbsp; 函数的名字是<BR>&nbsp;&nbsp;&nbsp; cplusplus_primer::inverse()<BR>&nbsp;&nbsp;&nbsp; 常量的名字是<BR>&nbsp;&nbsp;&nbsp; cplusplus_primer::pi</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp; 类，函数，常量被声明它的名字空间的名字限定修饰：<BR>&nbsp;&nbsp;&nbsp; 这些名字被成为限定修饰符(qualified name)</FONT></P>
<P><BR><FONT size=2>&nbsp;&nbsp;&nbsp; 名字空间的定义不一定是连续的.例如<BR>&nbsp;&nbsp;&nbsp; namespace cplusplus_primer{<BR>&nbsp;&nbsp;&nbsp; class&nbsp; matrix {/*****/}<BR>&nbsp;&nbsp;&nbsp; const double pi = 3.1416;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp; namespace cplusplus_primer{<BR>&nbsp;&nbsp;&nbsp; void inverse(matrix &amp;);<BR>&nbsp;&nbsp;&nbsp; matrix operator+ (const matrix &amp;m1,const matrix &amp;m2)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {/********/}<BR>&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp; 名字空间的定义可是非连续的，这对生成一个库很有帮助,它使我们更容易将库的源代码组织成<BR>&nbsp;&nbsp;&nbsp; 接口和实现部分。</FONT></P>
<P><FONT size=2>&nbsp;8.5.2域操作符<BR>&nbsp;&nbsp;&nbsp;&nbsp; ::<BR>&nbsp;&nbsp;&nbsp;&nbsp; 用户声明的名字空间成员名自动被加上前缀，名字空间名后面加上域操作符(::),名字空间成员名<BR>&nbsp;&nbsp;&nbsp;&nbsp; 由该名字空间名进行限定修饰。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; 名字空间成员的声明被隐藏在其名字空间中，除非我们为编译器指定查找的声明的名字空间,否则<BR>&nbsp;&nbsp;&nbsp;&nbsp; 编译器将在当前域及嵌套包含当前域的域中查找该名字的声明。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; 注意！！！<BR>&nbsp;&nbsp;&nbsp;&nbsp; 域操作符也可以被用来引用全局名字空间的成员。因为全局名字空间没有名字。<BR>&nbsp;&nbsp;&nbsp;&nbsp; ::member_name<BR>&nbsp;&nbsp;&nbsp;&nbsp; 指的是全局名字空间的成员。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; #include &lt;iostream&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp; const int max = 65000;<BR>&nbsp;&nbsp;&nbsp;&nbsp; const int lineLength = 12;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; void fibonacci(int max)<BR>&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (max &lt;2) return;<BR>&nbsp;cout &lt;&lt; "0 1";<BR>&nbsp;int v1 = 0,v2=1,cur;<BR>&nbsp;for (int ix=3;ix &lt;= max;++ix)<BR>&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp; cur = v1+v2;<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(cur&gt;::max) break;&nbsp;&nbsp; //引用全局名字空间的变量；<BR>&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; cur &lt;&lt;"";<BR>&nbsp;&nbsp;&nbsp;&nbsp; v1=v2;<BR>&nbsp;&nbsp;&nbsp;&nbsp; v2=cur;<BR>&nbsp;&nbsp;&nbsp;&nbsp; if(ix % lineLength ==0) cout &lt;&lt; endl;<BR>&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>8.5.3 嵌套名字空间<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 。。。。。</FONT></P>
<P><BR><FONT size=2>8.6 使用名字空间成员</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; 使用限定修饰的名字形式namespace_name::member_name来引用名字空间，毫无疑问是非常麻烦的。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; using 声明，using指示符</FONT></P>
<P><FONT size=2>8.6.1 名字空间别名<BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp; namespace International_Business_Machines<BR>&nbsp;&nbsp;&nbsp;&nbsp; {/*********/}</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp; namespace IBM = International_Business_Machines;</FONT></P>
<P><FONT size=2>8.6.2 using声明<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; namespace cplusplus_primer <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; namespace MatrixLib <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; class matrix {/******/};<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using cplusplus::MatrixLib::matrix;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </FONT></P>
<P><FONT size=2></FONT>&nbsp;</P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using 声明引入的名字有以下特性：<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&gt; 它在该域中必须唯一。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&gt; 由外围域中声明引入的相同名字被其隐藏。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3&gt; 它被嵌套域中的相同名字的声明隐藏。</FONT></P>
<P><BR><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; namespace blip {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int bi = 16,bj = 15, bk = 23;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int bj = 0;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void mainip()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using blip::bi;&nbsp;&nbsp;&nbsp; //函数mainip()中的bi指向blip::bi<BR>&nbsp; ++bi;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //设置blip::bi为17<BR>&nbsp; using blip::bj&nbsp;&nbsp;&nbsp;&nbsp; //隐藏全局域中的bj<BR>&nbsp; ++bj;<BR>&nbsp; int bk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //bk在局部域中声明<BR>&nbsp; using blip:bk;&nbsp;&nbsp;&nbsp;&nbsp; //错误:在mainip()中重复定义bk</FONT></P>
<P><BR><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int wrongInit = bk; //错误：bk在这里不可见</FONT></P>
<P><BR><FONT size=2>8.6.3 using 指示符</FONT></P>
<P><BR><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; namespace blip{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int bi = 16,bj = 15, bk = 23;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int bj = 0;</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void mainip()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; using namespace blip;</FONT></P>
<P><FONT size=2>&nbsp; ++bi;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //设置blip::bi为17;<BR>&nbsp; ++bj;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //错误：二义性<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 全局bj还是blip:bj?</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ++::bj;&nbsp;&nbsp;&nbsp;&nbsp; // 设置全局bj为1&nbsp;&nbsp;<BR>&nbsp; ++blip::bj; // 设置blip::bj为16<BR>&nbsp; int bk = 97; //局部bk隐藏blip:bk<BR>&nbsp; ++bk;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //设置局部bk为98</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2></FONT>&nbsp;</P>
<P><FONT size=2>当我们把一个应用程序移植到一个包装在名字空间中的新版本时，using指示符非常<BR>有用，但是使用多个using指示符会引起全局名字空间污染问题。<BR>用多个选择性的using声明来代替using指示符会使这个问题最小化，由多个using<BR>声明引起的二义性的错误在声明点就能被检测到，因此建议使用using声明而不是<BR>using指示符,以便更好地控制程序中地全局名字空间污染问题。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </FONT></P>
<P><FONT size=2></FONT>&nbsp;</P>
<P><FONT size=2><BR></FONT>&nbsp;</P><img src ="http://www.blogjava.net/faintbear/aggbug/7496.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-07-11 13:24 <a href="http://www.blogjava.net/faintbear/archive/2005/07/11/7496.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内存管理&lt;转&gt;</title><link>http://www.blogjava.net/faintbear/archive/2005/07/11/7495.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Mon, 11 Jul 2005 05:20:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/07/11/7495.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/7495.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/07/11/7495.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/7495.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/7495.html</trackback:ping><description><![CDATA[<P><FONT size=2>2004年 11月23日 <BR>17 : 07 内存管理 </FONT></P>
<P><FONT size=2>节选自《高质量C++编程指南》中的《内存管理》，并对其进行简单整理......</FONT></P>
<P><BR><FONT size=2>内存分配方式<BR>---------------------------------------------------------------------------------------------</FONT></P>
<P><FONT size=2>内存分配方式有三种：<BR>---------------------------<BR>（1） 从静态存储区域分配。内存在程序编译的时候就已经分配好，这块内存在程序的整个运行期间都存在。 例如全局变量static变量。<BR>（2） 在栈上创建。在执行函数时，函数内局部变量的存储单元都可以在栈上创建，函数执行结束时这些存储 单元自动被释放。栈内存 分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。<BR>（3） 从堆上分配，亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存，程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定，使用非常灵活，但问题也最多。</FONT></P>
<P><FONT size=2>常见的内存错误及其对策<BR>------------------------------------------------------------------------------------------ <BR>&nbsp;发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误，通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状，时隐时现，增加了改错的难度。有时用户怒气冲冲地把你找来，程序却没有发生任何问题，你一走，错误又发作了。</FONT></P>
<P><FONT size=2>常见的内存错误及其对策如下：</FONT></P>
<P><FONT size=2>1&gt;内存分配未成功，却使用了它。<BR>------------------------------------------<BR>&nbsp;编程新手常犯这种错误，因为他们没有意识到内存分配会不成功。常用解决办法是，在使用内存之前检 指针是否为NULL。如果指针p是函数的参数，那么在函数的入口处用assert(p!=NULL)进行检查。如果&nbsp; 是用malloc或new来申请内存，应该用if(p==NULL) 或if(p=NULL)进行防错处理。</FONT></P>
<P><FONT size=2>2&gt;内存分配虽然成功，但是尚未初始化就引用它<BR>------------------------------------------<BR>&nbsp;犯这种错误主要有两个起因：一是没有初始化的观念；二是误以为内存的缺省初值全为零，导致引用初 错误（例如数组）。<BR>内存的缺省初值究竟是什么并没有统一的标准，尽管有些时候为零值，我们宁可信其无不可信其有。所 以无论用何种方式创建数组，都别忘了赋初值，即便是赋零值也不可省略，不要嫌麻烦。</FONT></P>
<P><FONT size=2>3&gt;内存分配成功并且已经初始化，但操作越过了内存的边界。<BR>------------------------------------------<BR>&nbsp;例如在使用数组时经常发生下标"多1"或者"少1"的操作。特别是在for循环语句中，循环次数很容易搞错，导致数组操作越界。</FONT></P>
<P><FONT size=2>4&gt;忘记了释放内存，造成内存泄露。<BR>------------------------------------------<BR>&nbsp;含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足，你看不到错误。终有一次程序突然死掉，系统出现提示：内存耗尽。动态内存的申请与释放必须配对，程序中malloc与free的使用次数一定要相同，否则肯定有错误（new/delete同理）。</FONT></P>
<P><FONT size=2>5&gt;释放了内存却继续使用它。<BR>------------------------------------------<BR>有三种情况：<BR>（1）程序中的对象调用关系过于复杂，实在难以搞清楚某个对象究竟是否已经释放了内存，此时应该重新设计数据结构，从根本上解决对象管理的混乱局面。<BR>（2）函数的return语句写错了，注意不要返回指向"栈内存"的"指针"或者"引用"，因为该内存在函数体结束时被自动销毁。<BR>（3）使用free或delete释放了内存后，没有将指针设置为NULL。导致产生"野指针"。</FONT></P>
<P><FONT size=2>【规则1】用malloc或new申请内存之后，应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。<BR>【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。<BR>【规则3】避免数组或指针的下标越界，特别要当心发生"多1"或者"少1"操作。<BR>【规则4】动态内存的申请与释放必须配对，防止内存泄漏。<BR>【规则5】用free或delete释放了内存之后，立即将指针设置为NULL，防止产生"野指针"。</FONT></P>
<P><FONT size=2>------------------------------------------------------------------------------------<BR>指针与数组的对比<BR>-----------------<BR>&nbsp;C++/C程序中，指针和数组在不少地方可以相互替换着用，让人产生一种错觉，以为两者是等价的。<BR>&nbsp;&nbsp;&nbsp;&nbsp; <BR>数组<BR>-------------<BR>数组要么在静态存储区被创建（如全局数组），要么在栈上被创建。数组名对应着（而不是指向）一块内存，其地址与容量在生命期内保持不变，只有数组的内容可以改变。</FONT></P>
<P><FONT size=2>指针<BR>--------------<BR>指针可以随时指向任意类型的内存块，它的特征是"可变"，所以我们常用指针来操作动态内存。指针远比数组灵活，但也更危险。</FONT></P>
<P><FONT size=2>下面以字符串为例比较指针与数组的特性。</FONT></P>
<P><FONT size=2>1.修改内容<BR>------------------------------------------<BR>&nbsp;示例1中，字符数组a的容量是6个字符，其内容为hello\0。a的内容可以改变，如a[0]= 'X'。<BR>指针p指向常量字符串"world"（位于静态存储区，内容为world\0），常量字符串的内容是不可以被修改的。<BR>从语法上看，编译器并不觉得语句p[0]= ‘X'有什么不妥，但是该语句企图修改常量字符串的内容而导致运行错误。<BR>&nbsp; </FONT></P>
<P><FONT size=2>&nbsp;示例7-3-1 修改数组和指针的内容<BR>&nbsp;&nbsp; ------------------------------<BR>&nbsp;&nbsp; char a[] = "hello";<BR>&nbsp;&nbsp; a[0] = 'X';<BR>&nbsp;&nbsp; cout &lt;&lt; a &lt;&lt; endl;<BR>&nbsp;&nbsp; char *p = "world"; // 注意p指向常量字符串&nbsp;&nbsp; <BR>&nbsp;&nbsp; p[0] = 'X';&nbsp; // 编译器不能发现该错误cout &lt;&lt; p &lt;&lt; endl;<BR>&nbsp;&nbsp; </FONT></P>
<P><FONT size=2>2.内容复制与比较<BR>------------------------------------------<BR>数组复制<BR>--------<BR>不能对数组名进行直接复制与比较。示例2中，若想把数组a的内容复制给数组b，不能用语句 b = a ，否则将产生编译错误。应该用标准库函数strcpy进行复制。同理，比较b和a的内容是否相同，不能用if(b==a) 来判断，应该用标准库函数strcmp进行比较。</FONT></P>
<P><FONT size=2>指针复制<BR>-------- <BR>语句p = a 并不能把a的内容复制指针p，而是把a的地址赋给了p。要想复制a的内容，可以先用库函数malloc为p申请一块容量为strlen(a)+1个字符的内存，再用strcpy进行字符串复制。同理，语句if(p==a) 比较的不是内容而是地址，应该用库函数strcmp来比较。</FONT></P>
<P><FONT size=2>&nbsp; 示例2 数组和指针的内容复制与比较<BR>&nbsp; -----------------------------------<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 数组... <BR>&nbsp; char a[] = "hello";<BR>&nbsp; char b[10];<BR>&nbsp; strcpy(b, a); //不能用b = a;<BR>&nbsp; if(strcmp(b, a) == 0) // 不能用&nbsp; if (b == a)...<BR>&nbsp; // 指针...<BR>&nbsp; int len = strlen(a);<BR>&nbsp; char *p = (char *)malloc(sizeof(char)*(len+1));&nbsp;&nbsp; <BR>&nbsp; strcpy(p,a); // 不要用 p = a;<BR>&nbsp; if(strcmp(p, a) == 0) // 不要用 if (p == a)...</FONT></P>
<P><FONT size=2>&nbsp;</FONT></P>
<P><FONT size=2>3 计算内存容量<BR>------------------------------------------<BR>数组<BR>----<BR>用运算符sizeof可以计算出数组的容量（字节数）。<BR>示例3中，sizeof(a)的值是12（注意别忘了'\0'）。</FONT></P>
<P><FONT size=2>指针<BR>----<BR>指针p指向a，但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数，<BR>相当于sizeof(char*)，而不是p所指的内存容量。</FONT></P>
<P><FONT size=2>C++/C语言没有办法知道指针所指的内存容量，除非在申请内存时记住它。</FONT></P>
<P><FONT size=2>注意<BR>----<BR>当数组作为函数的参数进行传递时，该数组自动退化为同类型的指针。<BR>示例3中，不论数组a的容量是多少，sizeof(a)始终等于sizeof(char *)。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例3 计算数组和指针的内存容量<BR>&nbsp;-----------------------------<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char a[] = "hello world";<BR>&nbsp;char *p&nbsp; = a; cout&lt;&lt; sizeof(a) &lt;&lt; endl;// 12字节 <BR>&nbsp;cout&lt;&lt; sizeof(p) &lt;&lt; endl;// 4字节</FONT></P>
<P><FONT size=2>&nbsp;示例3（b） 数组退化为指针<BR>&nbsp;-----------------------------<BR>&nbsp;void Func(char a[100])<BR>&nbsp;{<BR>&nbsp; cout&lt;&lt; sizeof(a) &lt;&lt; endl;// 4字节而不是100字节<BR>&nbsp;&nbsp; }</FONT></P>
<P><FONT size=2>4指针参数是如何传递内存的？<BR>------------------------------------------<BR>如果函数的参数是一个指针，不要指望用该指针去申请动态内存。<BR>示例4-1中，Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存，str依旧是NULL，为什么？</FONT></P>
<P><FONT size=2>&nbsp;示例4-1 试图用指针参数申请动态内存<BR>&nbsp;----------------------------------<BR>&nbsp;void GetMemory(char *p, int num)<BR>&nbsp;{<BR>&nbsp; p = (char *)malloc(sizeof(char) * num);<BR>&nbsp;}<BR>&nbsp;void Test(void)<BR>&nbsp;{<BR>&nbsp; char *str = NULL;<BR>&nbsp; GetMemory(str, 100);// str 仍然为 NULL <BR>&nbsp; strcpy(str, "hello"); // 运行错误<BR>&nbsp;}</FONT></P>
<P><FONT size=2>毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本，<BR>指针参数p的副本是 _p，编译器使 _p = p。如果函数体内的程序修改了_p的内容，<BR>就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。<BR>在本例中，_p申请了新的内存，只是把_p所指的内存地址改变了，但是p丝毫未变。<BR>所以函数GetMemory并不能输出任何东西。<BR>事实上，每执行一次GetMemory就会泄露一块内存，因为没有用free释放内存。</FONT></P>
<P><FONT size=2>如果非得要用指针参数去申请内存，那么应该改用"指向指针的指针"，见示例4-2。</FONT></P>
<P><FONT size=2>&nbsp;示例4-2用指向指针的指针申请动态内存<BR>&nbsp;------------------------------------<BR>&nbsp;void GetMemory2(char **p, int num)<BR>&nbsp;{<BR>&nbsp; *p = (char *)malloc(sizeof(char) * num);<BR>&nbsp;}<BR>&nbsp;void Test2(void)<BR>&nbsp;{<BR>&nbsp; char *str = NULL;<BR>&nbsp; GetMemory2(&amp;str, 100); // 注意参数是 &amp;str，而不是str<BR>&nbsp; strcpy(str, "hello");<BR>&nbsp; cout&lt;&lt; str &lt;&lt; endl; <BR>&nbsp; free(str);<BR>&nbsp;}</FONT></P>
<P><BR><FONT size=2>由于"指向指针的指针"这个概念不容易理解，我们可以用函数返回值来传递动态内存。<BR>这种方法更加简单，见示例4-3。</FONT></P>
<P><FONT size=2>&nbsp;示例4-3 用函数返回值来传递动态内存<BR>&nbsp;-----------------------------------<BR>&nbsp;char *GetMemory3(int num)<BR>&nbsp;{<BR>&nbsp; char *p = (char *)malloc(sizeof(char) * num);<BR>&nbsp; return p;<BR>&nbsp;}<BR>&nbsp;void Test3(void)<BR>&nbsp;{<BR>&nbsp; char *str = NULL;<BR>&nbsp; str = GetMemory3(100);<BR>&nbsp; strcpy(str,"hello");<BR>&nbsp; cout&lt;&lt; str &lt;&lt; endl;<BR>&nbsp; free(str);<BR>&nbsp;}</FONT></P>
<P><BR><FONT size=2>用函数返回值来传递动态内存这种方法虽然好用，但是常常有人把return语句用错了。这里强调不要用return语句返回指向"栈内存"的指针，因为该内存在函数结束时自动消亡，见示例4-4。</FONT></P>
<P><FONT size=2>&nbsp;示例4-4 return语句返回指向"栈内存"的指针<BR>&nbsp;--------------------------------------------<BR>&nbsp;char *GetString(void)<BR>&nbsp;{<BR>&nbsp; char p[] = "hello world";<BR>&nbsp; return p;// 编译器将提出警告<BR>&nbsp;}<BR>&nbsp;void Test4(void)<BR>&nbsp;{<BR>&nbsp; char *str = NULL;str = GetString();// str 的内容是垃圾<BR>&nbsp; cout&lt;&lt; str &lt;&lt; endl;<BR>&nbsp;}</FONT></P>
<P><BR><FONT size=2>用调试器逐步跟踪Test4，发现执行str = GetString语句后str不再是NULL指针，<BR>但是str的内容不是"hello world"而是垃圾。</FONT></P>
<P><FONT size=2>如果把示例4-4改写成示例4-5，会怎么样？</FONT></P>
<P><FONT size=2>&nbsp;示例4-5 return语句返回常量字符串<BR>&nbsp;-----------------------------------<BR>&nbsp;char *GetString2(void)<BR>&nbsp;{ <BR>&nbsp; char *p = "hello world";<BR>&nbsp; return p;<BR>&nbsp;}<BR>&nbsp;void Test5(void)<BR>&nbsp;{<BR>&nbsp; char *str = NULL;<BR>&nbsp; str = GetString2();<BR>&nbsp; cout&lt;&lt; str &lt;&lt; endl;<BR>&nbsp;} </FONT></P>
<P><BR><FONT size=2>函数Test5运行虽然不会出错，但是函数GetString2的设计概念却是错误的。<BR>因为GetString2内的"hello world"是常量字符串，位于静态存储区，它在程序生命期内恒定不变。<BR>无论什么时候调用GetString2，它返回的始终是同一个"只读"的内存块。</FONT></P>
<P><FONT size=2>5 free和delete把指针怎么啦？<BR>------------------------------------------<BR>别看free和delete的名字恶狠狠的（尤其是delete），<BR>它们只是把指针所指的内存给释放掉，但并没有把指针本身干掉。<BR>用调试器跟踪示例5，发现指针p被free以后其地址仍然不变（非NULL），<BR>只是该地址对应的内存是垃圾，p成了"野指针"。<BR>如果此时不把p设置为NULL，会让人误以为p是个合法的指针。<BR>如果程序比较长，我们有时记不住p所指的内存是否已经被释放，<BR>在继续使用p之前，通常会用语句if (p !=NULL)进行防错处理。<BR>很遗憾，此时if语句起不到防错作用，因为即便p不是NULL指针，<BR>它也不指向合法的内存块。</FONT></P>
<P><FONT size=2>&nbsp;示例5&nbsp; p成为野指针<BR>&nbsp;--------------------<BR>&nbsp;char *p = (char *) malloc(100);<BR>&nbsp;strcpy(p, "hello"); <BR>&nbsp;free(p);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // p 所指的内存被释放，但是p所指的地址仍然不变 ... <BR>&nbsp;if(p != NULL) // 没有起到防错作用 <BR>&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(p, "world"); // 出错<BR>&nbsp;}</FONT></P>
<P><FONT size=2>6 动态内存会被自动释放吗？<BR>------------------------------------------<BR>函数体内的局部变量在函数结束时自动消亡。很多人误以为示例6是正确的。<BR>理由是p是局部的指针变量，它消亡的时候会让它所指的动态内存一起完蛋。这是错觉！</FONT></P>
<P><FONT size=2>&nbsp;示例7-6 试图让动态内存自动释放<BR>&nbsp;------------------------------<BR>&nbsp;void Func(void)<BR>&nbsp;{ <BR>&nbsp; char *p = (char *) malloc(100); // 动态内存会自动释放吗？<BR>&nbsp;}</FONT></P>
<P><BR><FONT size=2>&nbsp;我们发现指针有一些"似是而非"的特征：<BR>&nbsp;------------------------------------<BR>（1）指针消亡了，并不表示它所指的内存会被自动释放。<BR>（2）内存被释放了，并不表示指针会消亡或者成了NULL指针。<BR>&nbsp;&nbsp;&nbsp;&nbsp; 这表明释放内存并不是一件可以草率对待的事。也许有人不服气，<BR>&nbsp;&nbsp;&nbsp;&nbsp; 一定要找出可以草率行事的理由：<BR>&nbsp;&nbsp;&nbsp;&nbsp; 如果程序终止了运行，一切指针都会消亡，动态内存会被操作系统回收。<BR>&nbsp;&nbsp;&nbsp;&nbsp; 既然如此，在程序临终前，就可以不必释放内存、不必将指针设置为NULL了。<BR>&nbsp;&nbsp;&nbsp;&nbsp; 终于可以偷懒而不会发生错误了吧？<BR>&nbsp;想得美。如果别人把那段程序取出来用到其它地方怎么办？</FONT></P>
<P><FONT size=2>7 杜绝"野指针"<BR>------------------------------------------<BR>"野指针"不是NULL指针，是指向"垃圾"内存的指针。<BR>人们一般不会错用NULL指针，因为用if语句很容易判断。<BR>但是"野指针"是很危险的，if语句对它不起作用。</FONT></P>
<P><FONT size=2>"野指针"的成因主要有两种：</FONT></P>
<P><FONT size=2>（1）指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针，<BR>它的缺省值是随机的，它会乱指一气。所以，指针变量在创建的同时应当被初始化，<BR>要么将指针设置为NULL，要么让它指向合法的内存。例如<BR>&nbsp;char *p = NULL;<BR>&nbsp;char *str = (char *) malloc(100);</FONT></P>
<P><FONT size=2>（2）指针p被free或者delete之后，没有置为NULL，让人误以为p是个合法的指针。参见7.5节。</FONT></P>
<P><FONT size=2>（3）指针操作超越了变量的作用范围。这种情况让人防不胜防，示例程序如下：<BR>&nbsp;class A <BR>&nbsp;{ <BR>&nbsp;public:<BR>&nbsp; void Func(void){ cout &lt;&lt; "Func of class A" &lt;&lt; endl;<BR>&nbsp; }</FONT></P>
<P><FONT size=2>&nbsp;void Test(void)<BR>&nbsp;{<BR>&nbsp;A&nbsp; *p;<BR>&nbsp;{<BR>&nbsp; A&nbsp; a;<BR>&nbsp; p = &amp;a; // 注意 a 的生命期<BR>&nbsp;}<BR>&nbsp; p-&gt;Func();// p是"野指针"<BR>&nbsp;}</FONT></P>
<P><FONT size=2>函数Test在执行语句p-&gt;Func()时，对象a已经消失，而p是指向a的，所以p就成了"野指针"。<BR>但奇怪的是我运行这个程序时居然没有出错，这可能与编译器有关。</FONT></P>
<P><FONT size=2>8 有了malloc/free为什么还要new/delete ？<BR>------------------------------------------<BR>malloc与free是C++/C语言的标准库函数，new/delete是C++的运算符。<BR>它们都可用于申请动态内存和释放内存。</FONT></P>
<P><FONT size=2>对于非内部数据类型的对象而言，光用maloc/free无法满足动态对象的要求。<BR>对象在创建的同时要自动执行构造函数，对象在消亡之前要自动执行析构函数。<BR>由于malloc/free是库函数而不是运算符，不在编译器控制权限之内，<BR>不能够把执行构造函数和析构函数的任务强加于malloc/free。<BR>&nbsp;<BR>因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new，以及一个能完成清理与释放内存工作的运</FONT></P>
<P><FONT size=2>算符delete。注意new/delete不是库函数。<BR>我们先看一看malloc/free和new/delete如何实现对象的动态内存管理，见示例8。</FONT></P>
<P><FONT size=2>&nbsp;示例8 用malloc/free和new/delete如何实现对象的动态内存管理<BR>&nbsp;-------------------------------------------------------------<BR>&nbsp;class Obj<BR>&nbsp;{<BR>&nbsp;public :<BR>&nbsp; Obj(void){ cout &lt;&lt; "Initialization" &lt;&lt; endl; }<BR>&nbsp; ~Obj(void){cout &lt;&lt; "Destroy" &lt;&lt; endl; }<BR>&nbsp; void Initialize(void){ cout &lt;&lt; "Initialization" &lt;&lt; endl; }<BR>&nbsp; void&nbsp;&nbsp;&nbsp; Destroy(void){ cout &lt;&lt; "Destroy" &lt;&lt; endl; }<BR>&nbsp;};<BR>&nbsp;void UseMallocFree(void)<BR>&nbsp;{ <BR>&nbsp; Obj&nbsp; *a = (obj *)malloc(sizeof(obj)); // 申请动态内存 <BR>&nbsp; a-&gt;Initialize(); // 初始化<BR>&nbsp; a-&gt;Destroy(); // 清除工作 <BR>&nbsp; free(a);// 释放内存<BR>&nbsp;}<BR>&nbsp;void UseNewDelete(void)<BR>&nbsp;{<BR>&nbsp; Obj&nbsp; *a = new Obj;// 申请动态内存并且初始化<BR>&nbsp; delete a;// 清除并且释放内存<BR>&nbsp;}</FONT></P>
<P><FONT size=2>类Obj的函数Initialize模拟了构造函数的功能，函数Destroy模拟了析构函数的功能。<BR>函数UseMallocFree中，由于malloc/free不能执行构造函数与析构函数，<BR>必须调用成员函数Initialize和Destroy来完成初始化与清除工作。<BR>函数UseNewDelete则简单得多。</FONT></P>
<P><FONT size=2>所以我们不要企图用malloc/free来完成动态对象的内存管理，应该用new/delete。<BR>由于内部数据类型的"对象"没有构造与析构的过程，对它们而言malloc/free和new/delete是等价的。<BR>&nbsp;<BR>既然new/delete的功能完全覆盖了malloc/free，为什么C++不把malloc/free淘汰出局呢？<BR>这是因为C++程序经常要调用C函数，而C程序只能用malloc/free管理动态内存。</FONT></P>
<P><FONT size=2>如果用free释放"new创建的动态对象"，那么该对象因无法执行析构函数而可能导致程序出错。<BR>如果用delete释放"malloc申请的动态内存"，理论上讲程序不会出错，但是该程序的可读性很差。<BR>所以new/delete必须配对使用，malloc/free也一样。</FONT></P>
<P><FONT size=2>9 内存耗尽怎么办？<BR>------------------------------------------<BR>如果在申请动态内存时找不到足够大的内存块malloc和new将返回NULL指针，宣告内存申请失败。<BR>通常有三种方式处理"内存耗尽"问题。</FONT></P>
<P><FONT size=2>（1）判断指针是否为NULL，如果是则马上用return语句终止本函数。例如：<BR>&nbsp;void Func(void)<BR>&nbsp;{<BR>&nbsp; A&nbsp; *a = new A;<BR>&nbsp; if(a == NULL)<BR>&nbsp; {<BR>&nbsp;&nbsp; return;<BR>&nbsp; }<BR>&nbsp;...<BR>&nbsp;}</FONT></P>
<P><FONT size=2>（2）判断指针是否为NULL，如果是则马上用exit(1)终止整个程序的运行。例如：<BR>&nbsp;void Func(void)<BR>&nbsp;{<BR>&nbsp; A&nbsp; *a = new A;<BR>&nbsp; if(a == NULL)<BR>&nbsp; {<BR>&nbsp;&nbsp; cout &lt;&lt; "Memory Exhausted" &lt;&lt; endl;<BR>&nbsp;&nbsp; exit(1);<BR>&nbsp; }<BR>&nbsp; ...<BR>&nbsp;} </FONT></P>
<P><FONT size=2>（3）为new和malloc设置异常处理函数。</FONT></P>
<P><FONT size=2>例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数，<BR>也可以让malloc享用与new相同的异常处理函数。详细内容请参考C++使用手册。</FONT></P>
<P><FONT size=2>上述（1）（2）方式使用最普遍。如果一个函数内有多处需要申请动态内存，<BR>那么方式（1）就显得力不从心（释放内存很麻烦），应该用方式（2）来处理。</FONT></P>
<P><FONT size=2>很多人不忍心用exit(1)，问："不编写出错处理程序，让操作系统自己解决行不行？"<BR>不行。如果发生"内存耗尽"这样的事情，一般说来应用程序已经无药可救。<BR>如果不用exit(1) 把坏程序杀死，它可能会害死操作系统。<BR>道理如同：如果不把歹徒击毙，歹徒在老死之前会犯下更多的罪。</FONT></P>
<P><FONT size=2>有一个很重要的现象要告诉大家。对于32位以上的应用程序而言<BR>，无论怎样使用malloc与new，几乎不可能导致"内存耗尽"。<BR>我在Windows 98下用Visual C++编写了测试程序，见示例9。<BR>这个程序会无休止地运行下去，根本不会终止。<BR>因为32位操作系统支持"虚存"，内存用完了，自动用硬盘空间顶替。<BR>我只听到硬盘嘎吱嘎吱地响，Window 98已经累得对键盘、鼠标毫无反应。</FONT></P>
<P><FONT size=2>我可以得出这么一个结论：对于32位以上的应用程序，"内存耗尽"错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了：反正错误处理程序不起作用，我就不写了，省了很多麻烦。</FONT></P>
<P><FONT size=2>我不想误导读者，必须强调：不加错误处理将导致程序的质量很差，千万不可因小失大。</FONT></P>
<P><FONT size=2>&nbsp; 示例9试图耗尽操作系统的内存<BR>&nbsp; void main(void)<BR>&nbsp; {<BR>&nbsp;&nbsp; float *p = NULL; <BR>&nbsp;&nbsp; while(TRUE) <BR>&nbsp;&nbsp; {&nbsp; <BR>&nbsp;&nbsp;&nbsp; p = new float[1000000];&nbsp; <BR>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "eat memory" &lt;&lt; endl;&nbsp; <BR>&nbsp;&nbsp;&nbsp; if(p==NULL) exit(1); <BR>&nbsp;&nbsp; }<BR>&nbsp; }</FONT></P>
<P><FONT size=2>10 malloc/free 的使用要点<BR>------------------------------------------<BR>&nbsp;函数malloc的原型如下：<BR>&nbsp; void * malloc(size_t size);<BR>&nbsp;用malloc申请一块长度为length的整数类型的内存，程序如下：<BR>&nbsp; int&nbsp; *p = (int *) malloc(sizeof(int) * length);<BR>我们应当把注意力集中在两个要素上："类型转换"和"sizeof"。<BR>&nbsp;malloc返回值的类型是void *，所以在调用malloc时要显式地进行类型转换，<BR>将void * 转换成所需要的指针类型。<BR>&nbsp;malloc函数本身并不识别要申请的内存是什么类型，它只关心内存的总字节数。<BR>我们通常记不住int,float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节<BR>，在32位下是4个字节；而float变量在16位系统下是4个字节，在32位下也是4个字节。</FONT></P>
<P><FONT size=2>最好用以下程序作一次测试：<BR>cout &lt;&lt; sizeof(char) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(int) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(unsigned int) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(long) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(unsigned long) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(float) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(double) &lt;&lt; endl;<BR>cout &lt;&lt; sizeof(void *) &lt;&lt; endl;<BR>&nbsp;<BR>在malloc的"()"中使用sizeof运算符是良好的风格，但要当心有时我们会昏了头，写出 p = malloc(sizeof(p))这样的程序来。</FONT></P>
<P><FONT size=2>&nbsp;函数free的原型如下：</FONT></P>
<P><FONT size=2>void free( void * memblock );<BR>&nbsp;<BR>为什么free函数不象malloc函数那样复杂呢？这是因为指针p的类型以及它所指的内存的容量事先都是知道的，语句free(p)能正确地释放内存。如果p是NULL指针，那么free对p无论操作多少次都不会出问题。<BR>如果p不是NULL指针，那么free对p连续操作两次就会导致程序运行错误。</FONT></P>
<P><FONT size=2>11 new/delete 的使用要点<BR>------------------------------------------<BR>&nbsp;<BR>运算符new使用起来要比函数malloc简单得多，例如：<BR>int&nbsp; *p1 = (int *)malloc(sizeof(int) * length);<BR>int&nbsp; *p2 = new int[length];<BR>这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言，<BR>new在创建动态对象的同时完成了初始化工作。<BR>如果对象有多个构造函数，那么new的语句也可以有多种形式。<BR>例如<BR>class Obj<BR>{<BR>public :<BR>&nbsp;Obj(void);&nbsp; // 无参数的构造函数<BR>&nbsp;Obj(int x);&nbsp; // 带一个参数的构造函数<BR>...<BR>}<BR>void Test(void)<BR>{<BR>&nbsp;Obj&nbsp; *a = new Obj;<BR>&nbsp;Obj&nbsp; *b = new Obj(1); // 初值为1<BR>&nbsp;...<BR>&nbsp;delete a;<BR>&nbsp;delete b;<BR>}<BR>如果用new创建对象数组，那么只能使用对象的无参数构造函数。例如<BR>&nbsp;Obj&nbsp; *objects = new Obj[100]; // 创建100个动态对象<BR>不能写成<BR>&nbsp;Obj&nbsp; *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1<BR>在用delete释放对象数组时，留意不要丢了符号‘[]'。例如<BR>&nbsp;delete []objects; // 正确的用法<BR>delete objects; // 错误的用法<BR>后者相当于delete objects[0]，漏掉了另外99个对象。</FONT></P>
<P><FONT size=2>12 一些心得体会<BR>------------------------------------------<BR>（1）越是怕指针，就越要使用指针。不会正确使用指针，肯定算不上是合格的程序员。<BR>（2）必须养成"使用调试器逐步跟踪程序"的习惯，只有这样才能发现问题的本质。</FONT></P>
<P><FONT size=2>最后，说一句："呵呵，有点长，不知道大家有没有时间把它看完？"</FONT></P>
<P><FONT size=2><BR></FONT>&nbsp;</P><img src ="http://www.blogjava.net/faintbear/aggbug/7495.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-07-11 13:20 <a href="http://www.blogjava.net/faintbear/archive/2005/07/11/7495.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>说明以下关键字的作用 auto static register const volatile extern</title><link>http://www.blogjava.net/faintbear/archive/2005/06/22/6521.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Wed, 22 Jun 2005 02:39:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/06/22/6521.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/6521.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/06/22/6521.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/6521.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/6521.html</trackback:ping><description><![CDATA[<H1 align=center><FONT size=2>说明以下关键字的作用 (转)&nbsp;</FONT></H1>
<H2 align=center><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#auto"><FONT size=2>auto</FONT></A><FONT size=2>　　</FONT><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#static"><FONT size=2>stati<SPAN class=hilite1>c</SPAN>、</FONT></A><FONT size=2>　　</FONT><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#register"><FONT size=2>register</FONT></A><FONT size=2>　　</FONT><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#const"><FONT size=2><SPAN class=hilite1>c</SPAN>onst </FONT></A><FONT size=2>　　</FONT><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#volatile"><FONT size=2>volatile </FONT></A><FONT size=2>　　</FONT><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html#extern"><FONT size=2><SPAN class=hilite2>extern</SPAN> </FONT></A></H2><FONT size=2><A href="http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html">http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html</A>
<HR color=olive noShade>
</FONT><A name=auto></A>
<H3><FONT size=2>(1)auto </FONT></H3>
<P><FONT size=2>　　这个这个关键字用于声明<SPAN class=hilite3>变量</SPAN>的生存期为自动，即将不在任何类、结构、枚举、联合和函数中定义的<SPAN class=hilite3>变量</SPAN>视为全局<SPAN class=hilite3>变量</SPAN>，而在函数中定义的<SPAN class=hilite3>变量</SPAN>视为局部<SPAN class=hilite3>变量</SPAN>。这个关键字不怎么多写，因为所有的<SPAN class=hilite3>变量</SPAN>默认就是auto的。 </FONT></P>
<P><A name=register><FONT size=2></FONT></A>
<H3><FONT size=2>(2)register </FONT></H3>
<P><FONT size=2>　　这个关键字命令编译器<FONT color=red>尽可能</FONT>的将<SPAN class=hilite3>变量</SPAN>存在<SPAN class=hilite1>C</SPAN>PU内部寄存器中而不是通过内存寻址访问以提高效率。 </FONT><A name=static></A></P>
<H3><FONT size=2>(3)stati<SPAN class=hilite1>c</SPAN> </FONT></H3>
<P><FONT size=2>　　常见的两种用途: <BR>　　　　1&gt;统计函数被调用的次数; <BR>　　　　2&gt;减少局部数组建立和赋值的开销.<SPAN class=hilite3>变量</SPAN>的建立和赋值是需要一定的处理器开销的，特别是数组等含有较多元素的存储类型。在一些含有较多的<SPAN class=hilite3>变量</SPAN>并且被经常调用的函数中，可以将一些数组声明为stati<SPAN class=hilite1>c</SPAN>类型，以减少建立或者初始化这些<SPAN class=hilite3>变量</SPAN>的开销. </FONT></P>
<P><FONT size=2>　　详细说明: <BR>　　　　1&gt;、<SPAN class=hilite3>变量</SPAN>会被放在程序的全局存储区中，这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈<SPAN class=hilite3>变量</SPAN>和堆<SPAN class=hilite3>变量</SPAN>的区别。 <BR>　　　　2&gt;、<SPAN class=hilite3>变量</SPAN>用stati<SPAN class=hilite1>c</SPAN>告知编译器，自己仅仅在<SPAN class=hilite3>变量</SPAN>的作用范围内可见。这一点是它与全局<SPAN class=hilite3>变量</SPAN>的区别。 <BR>　　　　<FONT color=red>3&gt;当stati<SPAN class=hilite1>c</SPAN>用来修饰全局<SPAN class=hilite3>变量</SPAN>时，它就改变了全局<SPAN class=hilite3>变量</SPAN>的作用域，使其不能被别的程序<SPAN class=hilite2>extern</SPAN>，限制在了当前文件里，但是没有改变其存放位置，还是在全局静态储存区。</FONT> </FONT>
<P><FONT size=2>　　使用注意: <BR>　　　　1&gt;若全局<SPAN class=hilite3>变量</SPAN>仅在单个<SPAN class=hilite1>C</SPAN>文件中访问，则可以将这个<SPAN class=hilite3>变量</SPAN>修改为静态全局<SPAN class=hilite3>变量</SPAN>，以降低模块间的耦合度； <BR>　　　　2&gt;若全局<SPAN class=hilite3>变量</SPAN>仅由单个函数访问，则可以将这个<SPAN class=hilite3>变量</SPAN>改为该函数的静态局部<SPAN class=hilite3>变量</SPAN>，以降低模块间的耦合度； <BR>　　　　3&gt;设计和使用访问动态全局<SPAN class=hilite3>变量</SPAN>、静态全局<SPAN class=hilite3>变量</SPAN>、静态局部<SPAN class=hilite3>变量</SPAN>的函数时，需要考虑重入问题(只要输入数据相同就应产生相同的输出)。 </FONT><A name=const></A>
<H3><FONT size=2>(4)<SPAN class=hilite1>c</SPAN>onst </FONT></H3>
<P><FONT size=2>　　被<SPAN class=hilite1>c</SPAN>onst修饰的东西都受到强制保护，可以预防意外的变动，能提高程序的健壮性。它可以修饰函数的参数、返回值，甚至函数的定义体。 </FONT></P>
<P><FONT size=2>　　作用: <BR>　　　　1&gt;修饰输入参数 <BR>　　　　　　a.对于非内部数据类型的输入参数，应该将“值传递”的方式改为“<SPAN class=hilite1>c</SPAN>onst引用传递”，目的是提高效率。例如将void Fun<SPAN class=hilite1>c</SPAN>(A a) 改为void Fun<SPAN class=hilite1>c</SPAN>(<SPAN class=hilite1>c</SPAN>onst A &amp;a)。 <BR>　　　　　　b.对于内部数据类型的输入参数，不要将“值传递”的方式改为“<SPAN class=hilite1>c</SPAN>onst引用传递”。否则既达不到提高效率的目的，又降低了函数的可理解性。例如void Fun<SPAN class=hilite1>c</SPAN>(int x) 不应该改为void Fun<SPAN class=hilite1>c</SPAN>(<SPAN class=hilite1>c</SPAN>onst int &amp;x)。 <BR>　　　　2&gt;用<SPAN class=hilite1>c</SPAN>onst修饰函数的返回值 <BR>　　　　　　a.如果给以“指针传递”方式的函数返回值加<SPAN class=hilite1>c</SPAN>onst修饰，那么函数返回值（即指针）的内容不能被修改，该返回值只能被赋给加<SPAN class=hilite1>c</SPAN>onst修饰的同类型指针。 <BR>　　　　　　　如对于： <SPAN class=hilite1>c</SPAN>onst <SPAN class=hilite1>c</SPAN>har * GetString(void); <BR>　　　　　　　如下语句将出现编译错误： <BR>　　　　　　 　<SPAN class=hilite1>c</SPAN>har *str = GetString();//<SPAN class=hilite1>c</SPAN>annot <SPAN class=hilite1>c</SPAN>onvert from '<SPAN class=hilite1>c</SPAN>onst <SPAN class=hilite1>c</SPAN>har *' to '<SPAN class=hilite1>c</SPAN>har *'； <BR>　　　　　　　正确的用法是： <BR>　　　　　　　<SPAN class=hilite1>c</SPAN>onst <SPAN class=hilite1>c</SPAN>har *str = GetString(); <BR>　　　　　　b.如果函数返回值采用“值传递方式”，由于函数会把返回值复制到外部临时的存储单元中，加<SPAN class=hilite1>c</SPAN>onst修饰没有任何价值。 如不要把函数int GetInt(void) 写成<SPAN class=hilite1>c</SPAN>onst int GetInt(void)。 <BR>　　　　3&gt;<SPAN class=hilite1>c</SPAN>onst成员函数的声明中，<SPAN class=hilite1>c</SPAN>onst关键字只能放在函数声明的尾部,表示该类成员不修改对象. </FONT>
<P><FONT size=2>　　　说明： <BR>　　　　<SPAN class=hilite1>c</SPAN>onst type m; //修饰m为不可改变 <BR>　　　示例： <BR>　　　　typedef <SPAN class=hilite1>c</SPAN>har * pStr; //新的类型pStr; <BR>　　　　<SPAN class=hilite1>c</SPAN>har string[4] = "ab<SPAN class=hilite1>c</SPAN>"; <BR>　　　　<SPAN class=hilite1>c</SPAN>onst <SPAN class=hilite1>c</SPAN>har *p1 = string； <BR>　　　　p1++; //正确，上边修饰的是*p1,p1可变 <BR>　　　　<SPAN class=hilite1>c</SPAN>onst pStr p2 = string; <BR>　　　　p2++; //错误，上边修饰的是p2，p2不可变,*p2可变 <BR>　　　同理，<SPAN class=hilite1>c</SPAN>onst修饰指针时用此原则判断就不会混淆了。 <BR>　　　　<SPAN class=hilite1>c</SPAN>onst int *value; //*value不可变，value可变 <BR>　　　　int* <SPAN class=hilite1>c</SPAN>onst value; //value不可变，*value可变 <BR>　　　　<SPAN class=hilite1>c</SPAN>onst (int *) value; //(int *)是一种type,value不可变,*value可变 <BR>　　　　　　　　　　　　　　//逻辑上这样理解，编译不能通过，需要tydef int* NewType; <BR>　　　　<SPAN class=hilite1>c</SPAN>onst int* <SPAN class=hilite1>c</SPAN>onst value;//*value,value都不可变 </FONT><A name=volatile></A>
<H3><FONT size=2>(5)volatile </FONT></H3>
<P><FONT size=2>　　表明某个<SPAN class=hilite3>变量</SPAN>的值可能在外部被改变，优化器在用到这个<SPAN class=hilite3>变量</SPAN>时必须每次都小心地重新读取这个<SPAN class=hilite3>变量</SPAN>的值，而不是使用保存在寄存器里的备份。它可以适用于基础类型如：int,<SPAN class=hilite1>c</SPAN>har,long......也适用于<SPAN class=hilite1>C</SPAN>的结构和<SPAN class=hilite1>C</SPAN>++的类。当对结构或者类对象使用volatile修饰的时候，结构或者类的所有成员都会被视为volatile. <BR>　　该关键字在多线程环境下经常使用，因为在编写多线程的程序时，同一个<SPAN class=hilite3>变量</SPAN>可能被多个线程修改，而程序通过该<SPAN class=hilite3>变量</SPAN>同步各个线程。 <BR>　　简单示例： <BR>　　　DWORD __std<SPAN class=hilite1>c</SPAN>all threadFun<SPAN class=hilite1>c</SPAN>(LPVOID signal) <BR>　　　{ <BR>　　　　　int* intSignal=reinterpret_<SPAN class=hilite1>c</SPAN>ast(signal); <BR>　　　　　*intSignal=2; <BR>　　　　　while(*intSignal!=1) <BR>　　　　　sleep(1000); <BR>　　　　　return 0; <BR>　　　} <BR>　　该线程启动时将intSignal 置为2，然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变，否则该线程不会退出。但是实际运行的时候该线程却不会退出，即使在外部将它的值改为1，看一下对应的伪汇编代码就明白了： <BR>　　　　　mov ax,signal <BR>　　　　　label: <BR>　　　　　if(ax!=1) <BR>　　　　　goto label <BR>　　对于<SPAN class=hilite1>C</SPAN>编译器来说，它并不知道这个值会被其他线程修改。自然就把它<SPAN class=hilite1>c</SPAN>a<SPAN class=hilite1>c</SPAN>he在寄存器里面。<SPAN class=hilite1>C</SPAN> 编译器是没有线程概念的,这时候就需要用到volatile。volatile 的本意是指：这个值可能会在当前线程外部被改变。也就是说，我们要在threadFun<SPAN class=hilite1>c</SPAN>中的intSignal前面加上volatile关键字，这时候，编译器知道该<SPAN class=hilite3>变量</SPAN>的值会在外部改变，因此每次访问该<SPAN class=hilite3>变量</SPAN>时会重新读取，所作的循环变为如下面伪码所示： <BR>　　　　　label: <BR>　　　　　mov ax,signal <BR>　　　　　if(ax!=1) <BR>　　　　　goto label </FONT></P>
<P><FONT size=2>　　<FONT color=red>注意：</FONT>一个参数既可以是<SPAN class=hilite1>c</SPAN>onst同时是volatile，是volatile因为它可能被意想不到地改变。它是<SPAN class=hilite1>c</SPAN>onst因为程序不应该试图去修改它。 </FONT><A name=extern></A>
<H3><FONT size=2>(6)<SPAN class=hilite2>extern</SPAN> </FONT></H3>
<P><FONT size=2>　　<SPAN class=hilite2>extern</SPAN> 意为“外来的”···它的作用在于告诉编译器：有这个<SPAN class=hilite3>变量</SPAN>，它可能不存在当前的文件中，但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。</FONT></P><img src ="http://www.blogjava.net/faintbear/aggbug/6521.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-06-22 10:39 <a href="http://www.blogjava.net/faintbear/archive/2005/06/22/6521.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>memset请教 （转）</title><link>http://www.blogjava.net/faintbear/archive/2005/06/21/6485.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Tue, 21 Jun 2005 05:59:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/06/21/6485.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/6485.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/06/21/6485.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/6485.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/6485.html</trackback:ping><description><![CDATA[<P>&nbsp;</P>
<P>memset请教 </P>
<P>&nbsp;<BR>作者：tianzhushan&nbsp;&nbsp;&nbsp;&nbsp; 发表时间：2002/09/03 09:08am<BR>&nbsp;<BR>版主：<BR>&nbsp;&nbsp; 请问：<BR>&nbsp;&nbsp; memset ,memcpy 和strcpy 的根本区别？<BR>&nbsp;&nbsp; 我总觉得memset,memcpy 可以用strcpy等代替&nbsp; <BR>&nbsp;</P>
<P>--------------------------------------------------------------------------------<BR>此文章相关评论: <BR>&nbsp;<BR>该文章有15个相关评论如下：(点这儿可以发表评论)<BR>&nbsp;<BR>zhuhuix 发表于： 2002/09/03 09:14am<BR>&nbsp;<BR>char str[100];<BR>memset(str,0,100);<BR>memcpy(str,"This is a",9);<BR>strcpy(&amp;str[9],"example!"); <BR>&nbsp; <BR>yuejw 发表于： 2002/09/03 09:14am<BR>&nbsp;<BR>它们用处不同，但大部分情况下可以完成相同的要求。<BR>memset用来对一段内存空间全部设置为某个字符。</P>
<P>memcpy用来做内存拷贝，你可以拿它拷贝任何数据类型的对象。</P>
<P>strcpy就只能拷贝字符串了，它遇到'\0'就结束拷贝。<BR>&nbsp;<BR>&nbsp; <BR>HopeCao 发表于： 2002/09/03 10:02am<BR>&nbsp;<BR>很详细！ <BR>&nbsp; <BR>tianzhushan 发表于： 2002/09/03 05:37pm<BR>&nbsp;<BR>各位：&nbsp; <BR>&nbsp;&nbsp; 能否就这三者的最典型的应用各举一个例子。<BR>&nbsp;&nbsp; 结合例子，说说他们的区别和联系，以及为什么用这个而不用另外一个。<BR>&nbsp;&nbsp; 越详细越好。<BR>&nbsp;&nbsp; 谢谢！ <BR>&nbsp; <BR>betydu 发表于： 2002/09/04 09:21am<BR>&nbsp;<BR>对版主yuejw的补充<BR>memset用来对一段内存空间全部设置为某个字符，一般用在对定义的字符串进行初始化为‘ ’或‘\0’；例:char a[100];memset(a, '\0', sizeof(a));<BR>memcpy用来做内存拷贝，你可以拿它拷贝任何数据类型的对象，可以指定拷贝的数据长度；例：char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a)，会造成b的内存地址溢出。</P>
<P>strcpy就只能拷贝字符串了，它遇到'\0'就结束拷贝；例：char a[100],b[50];strcpy(a,b);如用strcpy(b,a)，要注意a中的字符串长度（第一个‘\0’之前）是否超过50位，如超过，则会造成b的内存地址溢出。<BR>&nbsp;<BR>&nbsp; <BR>iloveunix 发表于： 2002/09/04 04:56pm<BR>&nbsp;<BR>补充：我的一点心得<BR>memset可以方便的清空一个结构类型的变量或数组。<BR>如：<BR>struct sample_struct<BR>{<BR>&nbsp;char&nbsp;&nbsp; csName[16];<BR>&nbsp;int&nbsp;&nbsp;&nbsp; iSeq;<BR>&nbsp;int&nbsp;&nbsp;&nbsp; iType;<BR>};</P>
<P>对于变量<BR>struct sample_strcut&nbsp; stTest;</P>
<P>一般情况下，清空stTest的方法：<BR>stTest.csName[0]='\0';<BR>stTest.iSeq=0;<BR>stTest.iType=0;</P>
<P>用memset就非常方便：<BR>memset(&amp;stTest,0,sizeof(struct sample_struct));</P>
<P>如果是数组：<BR>struct sample_struct&nbsp;&nbsp; TEST[10];<BR>则<BR>memset(TEST,0,sizeof(struct sample_struct)*10);</P>
<P>&nbsp;<BR>&nbsp; <BR>zhtrue 发表于： 2002/09/04 05:06pm<BR>&nbsp;<BR>对这个问题有疑问，不是对函数的疑问，而是因为没有弄懂mem和str的区别。<BR>mem是一段内存，他的长度，必须你自己记住<BR>str也是一段内存，不过它的长度，你不用记，随时都可以计算出来<BR>所以memcpy需要第三个参数，而strcpy不需要<BR>&nbsp;<BR>&nbsp; <BR>tianzhushan 发表于： 2002/09/05 06:19pm<BR>&nbsp;<BR>谢谢各位的高见！小弟受益匪浅 <BR>&nbsp; <BR>凌曦 发表于： 2002/09/05 09:47pm<BR>&nbsp;<BR>str也可以用用个参数的strncpy(a,b,n) <BR>&nbsp; <BR>arbol 发表于： 2002/09/06 03:02pm<BR>&nbsp;<BR>还有，记住memcpy是不拷贝最后的终止符的 <BR>&nbsp; <BR>编程浪子 发表于： 2002/09/12 00:58am<BR>&nbsp;<BR>楼上的邮政老兄好像说错了吧？应该是strcpy不拷贝'\0'的，而memcpy应该是通吃，见着什么拷贝什么。 <BR>&nbsp; <BR>chrmhf 发表于： 2002/09/13 08:29am<BR>&nbsp;<BR>memset主要应用是初始化某个内存空间。<BR>memcpy是用于COPY源空间的数据到目的空间中。<BR>strcpy用于字符串COPY,遇到‘\0’，将结束。<BR>－－－－－－－－－－－－－－－－－－－－－－<BR>如果你理解了这些，你应该知道他们的区别：例如你初始化某块空间的时候，用到memcpy<BR>那么应该怎么写，是不是显得很笨。<BR>int&nbsp; m[100]<BR>-&gt;memset((void*)m,0x00,sizeof(int)*100);//GOOD<BR>..memcpy((void*)m,"\0\0\0\0....",sizeof(int)*100);//foolish<BR>&nbsp;<BR>&nbsp; <BR>HopeCao 发表于： 2002/09/13 01:58pm<BR>&nbsp;<BR>strcpy <BR>&nbsp;原型：extern char *strcpy(char *dest,char *src); <BR>&nbsp;用法：#include &lt;string.h&gt;<BR>&nbsp;功能：把src所指由NULL结束的字符串复制到dest所指的数组中。<BR>&nbsp;说明：src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回指向dest的指针。<BR>memcpy <BR>&nbsp;原型：extern void *memcpy(void *dest, void *src, unsigned int count);<BR>&nbsp;用法：#include &lt;string.h&gt;<BR>&nbsp;功能：由src所指内存区域复制count个字节到dest所指内存区域。<BR>&nbsp;说明：src和dest所指内存区域不能重叠，函数返回指向dest的指针。<BR>memset<BR>&nbsp;原型：extern void *memset(void *buffer, int c, int count);<BR>&nbsp;用法：#include &lt;string.h&gt;<BR>&nbsp;功能：把buffer所指内存区域的前count个字节设置成字符c。<BR>&nbsp;说明：返回指向buffer的指针。<BR>&nbsp;<BR>&nbsp; <BR>编程浪子 发表于： 2002/09/15 09:38pm<BR>&nbsp;<BR>呵呵，各位都是高手，真是不厌其烦的谆谆教导啊！<BR>&nbsp;<BR>&nbsp; <BR>obss 发表于： 2002/09/17 06:25pm<BR>&nbsp;<BR>谢谢，有些收获 <BR>&nbsp; <BR>&nbsp;<BR>&nbsp;<BR></P><img src ="http://www.blogjava.net/faintbear/aggbug/6485.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-06-21 13:59 <a href="http://www.blogjava.net/faintbear/archive/2005/06/21/6485.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++常见问题</title><link>http://www.blogjava.net/faintbear/archive/2005/06/21/6484.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Tue, 21 Jun 2005 05:56:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/06/21/6484.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/6484.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/06/21/6484.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/6484.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/6484.html</trackback:ping><description><![CDATA[<P><BR><FONT size=2>一、#include “filename.h”和#include filename.h&gt;的区别</FONT></P>
<P><FONT size=2>#include “filename.h”是指编译器将从当前工作目录上开始查找此文件</FONT></P>
<P><FONT size=2>#include filename.h&gt;是指编译器将从标准库目录中开始查找此文件</FONT></P>
<P><BR><FONT size=2>二、头文件的作用</FONT></P>
<P><FONT size=2>加强安全检测</FONT></P>
<P><FONT size=2>通过头文件可能方便地调用库功能，而不必关心其实现方式</FONT></P>
<P><BR><FONT size=2>三、* , &amp;修饰符的位置</FONT></P>
<P><FONT size=2>对于*和&amp;修饰符，为了避免误解，最好将修饰符紧靠变量名</FONT></P>
<P><BR><FONT size=2>四、if语句</FONT></P>
<P><FONT size=2>不要将布尔变量与任何值进行比较，那会很容易出错的。</FONT></P>
<P><FONT size=2>整形变量必须要有类型相同的值进行比较</FONT></P>
<P><FONT size=2>浮点变量最好少比点，就算要比也要有值进行限制</FONT></P>
<P><FONT size=2>指针变量要和NULL进行比较，不要和布尔型和整形比较</FONT></P>
<P><BR><FONT size=2>五、const和#define的比较</FONT></P>
<P><FONT size=2>const有数据类型，#define没有数据类型</FONT></P>
<P><FONT size=2>个别编译器中const可以进行调试，#define不可以进行调试</FONT></P>
<P><FONT size=2>在类中定义常量有两种方式</FONT></P>
<P><FONT size=2>1、 在类在声明常量，但不赋值，在构造函数初始化表中进行赋值；</FONT></P>
<P><FONT size=2>2、 用枚举代替const常量。</FONT></P>
<P><BR><FONT size=2>六、C++函数中值的传递方式</FONT></P>
<P><FONT size=2>有三种方式：值传递(Pass by value)、指针传递(Pass by pointer)、引用传递(Pass by reference)</FONT></P>
<P><FONT size=2>void fun(char c) //pass by value</FONT></P>
<P><FONT size=2>void fun(char *str) //pass by pointer</FONT></P>
<P><FONT size=2>void fun(char &amp;str) //pass by reference</FONT></P>
<P><FONT size=2>如果输入参数是以值传递的话，最好使用引用传递代替，因为引用传递省去了临时对象的构造和析构</FONT></P>
<P><FONT size=2>函数的类型不能省略，就算没有也要加个void</FONT></P>
<P><BR><FONT size=2>七、函数体中的指针或引用常量不能被返回</FONT></P>
<P><FONT size=2>Char *func(void)</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>char str[]=”Hello Word”;</FONT></P>
<P><FONT size=2>//这个是不能被返回的，因为str是个指定变量，不是一般的值，函数结束后会被注销掉</FONT></P>
<P><FONT size=2>return str; </FONT></P>
<P><FONT size=2>}</FONT></P>
<P><FONT size=2>函数体内的指针变量并不会随着函数的消亡而自动释放</FONT></P>
<P><BR><FONT size=2>八、一个内存拷贝函数的实现体</FONT></P>
<P><FONT size=2>void *memcpy(void *pvTo,const void *pvFrom,size_t size)</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>assert((pvTo!=NULL)&amp;&amp;(pvFrom!=NULL));</FONT></P>
<P><FONT size=2>byte *pbTo=(byte*)pvTo; //防止地址被改变</FONT></P>
<P><FONT size=2>byte *pbFrom=(byte*)pvFrom;</FONT></P>
<P><FONT size=2>while (size-- &gt;0)</FONT></P>
<P><FONT size=2>pbTo++ = pbForm++;</FONT></P>
<P><FONT size=2>return pvTo;</FONT></P>
<P><FONT size=2>} </FONT></P>
<P><BR><FONT size=2>九、内存的分配方式</FONT></P>
<P><FONT size=2>分配方式有三种，请记住，说不定那天去面试的时候就会有人问你这问题</FONT></P>
<P><FONT size=2>1、 静态存储区，是在程序编译时就已经分配好的，在整个运行期间都存在，如全局变量、常量。</FONT></P>
<P><FONT size=2>2、 栈上分配，函数内的局部变量就是从这分配的，但分配的内存容易有限。</FONT></P>
<P><FONT size=2>3、 堆上分配，也称动态分配，如我们用new,malloc分配内存，用delete,free来释放的内存。</FONT></P>
<P><BR><FONT size=2>十、内存分配的注意事项</FONT></P>
<P><FONT size=2>用new或malloc分配内存时，必须要对此指针赋初值。</FONT></P>
<P><FONT size=2>用delete 或free释放内存后，必须要将指针指向NULL</FONT></P>
<P><FONT size=2>不能修改指向常量的指针数据</FONT></P>
<P><BR><FONT size=2>十一、内容复制与比较</FONT></P>
<P><FONT size=2>//数组……</FONT></P>
<P><FONT size=2>char a[]=”Hello Word!”;</FONT></P>
<P><FONT size=2>char b[10];</FONT></P>
<P><FONT size=2>strcpy(b,a);</FONT></P>
<P><FONT size=2>if (strcmp(a,b)==0)</FONT></P>
<P><FONT size=2>{}</FONT></P>
<P><FONT size=2>//指针……</FONT></P>
<P><FONT size=2>char a[]=”Hello Word!”;</FONT></P>
<P><FONT size=2>char *p;</FONT></P>
<P><FONT size=2>p=new char[strlen(a)+1];</FONT></P>
<P><FONT size=2>strcpy(p,a);</FONT></P>
<P><FONT size=2>if (strcmp(p,a)==0)</FONT></P>
<P><FONT size=2>{}</FONT></P>
<P><BR><FONT size=2>十二、sizeof的问题</FONT></P>
<P><FONT size=2>记住一点，C++无法知道指针所指对象的大小，指针的大小永远为4字节</FONT></P>
<P><FONT size=2>char a[]=”Hello World!”</FONT></P>
<P><FONT size=2>char *p=a;</FONT></P>
<P><FONT size=2>count sizeof(a) end; //12字节</FONT></P>
<P><FONT size=2>count sizeof(p) endl; //4字节</FONT></P>
<P><FONT size=2>而且，在函数中，数组参数退化为指针，所以下面的内容永远输出为4</FONT></P>
<P><FONT size=2>void fun(char a[1000])</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>count sizeof(a) endl; //输出4而不是1000</FONT></P>
<P><FONT size=2>}</FONT></P>
<P><BR><FONT size=2>十三、关于指针</FONT></P>
<P><FONT size=2>1、 指针创建时必须被初始化</FONT></P>
<P><FONT size=2>2、 指针在free 或delete后必须置为NULL</FONT></P>
<P><FONT size=2>3、 指针的长度都为4字节</FONT></P>
<P><FONT size=2>４、释放内存时，如果是数组指针，必须要释放掉所有的内存，如</FONT></P>
<P><FONT size=2>char *p=new char[100];</FONT></P>
<P><FONT size=2>strcpy(p,”Hello World”);</FONT></P>
<P><FONT size=2>delete []p; //注意前面的［］号</FONT></P>
<P><FONT size=2>p=NULL;</FONT></P>
<P><FONT size=2>５、数组指针的内容不能超过数组指针的最大容易。</FONT></P>
<P><FONT size=2>如:</FONT></P>
<P><FONT size=2>char *p=new char[5];</FONT></P>
<P><FONT size=2>strcpy(p,”Hello World”); //报错 目标容易不够大</FONT></P>
<P><FONT size=2>delete []p; //注意前面的［］号</FONT></P>
<P><FONT size=2>p=NULL;</FONT></P>
<P><BR><FONT size=2>十四、关于malloc/free 和new /delete</FONT></P>
<P><FONT size=2>l malloc/free 是C/C+的内存分配符，new /delete是C++的内存分配符。</FONT></P>
<P><FONT size=2>l 注意：malloc/free是库函数，new/delete是运算符</FONT></P>
<P><FONT size=2>l malloc/free不能执行构造函数与析构函数，而new/delete可以</FONT></P>
<P><FONT size=2>l new/delete不能在C上运行，所以malloc/free不能被淘汰</FONT></P>
<P><FONT size=2>l 两者都必须要成对使用</FONT></P>
<P><FONT size=2>l C++中可以使用_set_new_hander函数来定义内存分配异常的处理</FONT></P>
<P><BR><FONT size=2>十五、Ｃ++的特性</FONT></P>
<P><FONT size=2>Ｃ++新增加有重载(overload)，内联（inline），Const，Virtual四种机制</FONT></P>
<P><FONT size=2>重载和内联：即可用于全局函数，也可用于类的成员函数；</FONT></P>
<P><FONT size=2>Const和Virtual：只可用于类的成员函数；</FONT></P>
<P><FONT size=2>重载：在同一类中，函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数，必须用全局引用符号::引用。</FONT></P>
<P><FONT size=2>覆盖是指派生类函数覆盖基类函数</FONT></P>
<P><FONT size=2>函数名相同；</FONT></P>
<P><FONT size=2>参数相同；</FONT></P>
<P><FONT size=2>基类函数必须有Virtual关键字；</FONT></P>
<P><FONT size=2>不同的范围(派生类和基类)。</FONT></P>
<P><FONT size=2>隐藏是指派生类屏蔽了基类的同名函数相同</FONT></P>
<P><FONT size=2>1、 函数名相同，但参数不同，此时不论基类有无Virtual关键字，基类函数将被隐藏。</FONT></P>
<P><FONT size=2>2、 函数名相同，参数也相同，但基类无Virtual关键字(有就是覆盖)，基类函数将被隐藏。</FONT></P>
<P><FONT size=2>内联：inline关键字必须与定义体放在一起，而不是单单放在声明中。</FONT></P>
<P><FONT size=2>Const：const是constant的缩写，“恒定不变”的意思。被const修饰的东西都受到强制保护，可以预防意外的变动，能提高程序的健壮性。</FONT></P>
<P><FONT size=2>1、 参数做输入用的指针型参数，加上const可防止被意外改动。</FONT></P>
<P><FONT size=2>2、 按值引用的用户类型做输入参数时，最好将按值传递的改为引用传递，并加上const关键字，目的是为了提高效率。数据类型为内部类型的就没必要做这件事情；如：</FONT></P>
<P><FONT size=2>将void Func(A a) 改为void Func(const A &amp;a)。</FONT></P>
<P><FONT size=2>而void func(int a)就没必要改成void func(const int &amp;a);</FONT></P>
<P><FONT size=2>3、 给返回值为指针类型的函数加上const，会使函数返回值不能被修改，赋给的变量也只能是const型变量。如：函数const char*GetString(void); char *str=GetString()将会出错。而const char *str=GetString()将是正确的。</FONT></P>
<P><FONT size=2>4、 Const成员函数是指此函数体内只能调用Const成员变量，提高程序的键壮性。如声明函数 int GetCount(void) const;此函数体内就只能调用Const成员变量。</FONT></P>
<P><FONT size=2>Virtual：虚函数：派生类可以覆盖掉的函数，纯虚函数：只是个空函数，没有函数实现体；</FONT></P>
<P><BR><FONT size=2>十六、extern“C”有什么作用？</FONT></P>
<P><FONT size=2>Extern “C”是由Ｃ＋＋提供的一个连接交换指定符号，用于告诉Ｃ＋＋这段代码是Ｃ函数。这是因为C++编译后库中函数名会变得很长，与C生成的不一致，造成Ｃ＋＋不能直接调用C函数，加上extren “c”后，C++就能直接调用C函数了。</FONT></P>
<P><FONT size=2>Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。</FONT></P>
<P><BR><FONT size=2>十七、构造函数与析构函数</FONT></P>
<P><FONT size=2>派生类的构造函数应在初始化表里调用基类的构造函数；</FONT></P>
<P><FONT size=2>派生类和基类的析构函数应加Virtual关键字。</FONT></P>
<P><FONT size=2>不要小看构造函数和析构函数，其实编起来还是不容易。</FONT></P>
<P><FONT size=2>#include iostream.h&gt;</FONT></P>
<P><FONT size=2>class Base</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>public: </FONT></P>
<P><FONT size=2>virtual ~Base() { cout "~Base" endl ; }</FONT></P>
<P><FONT size=2>};</FONT></P>
<P><FONT size=2>class Derived : public Base</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>public: </FONT></P>
<P><FONT size=2>virtual ~Derived() { cout "~Derived" endl ; }</FONT></P>
<P><FONT size=2>};</FONT></P>
<P><FONT size=2>void main(void)</FONT></P>
<P><FONT size=2>{</FONT></P>
<P><FONT size=2>Base * pB = new Derived; // upcast</FONT></P>
<P><FONT size=2>delete pB;</FONT></P>
<P><FONT size=2>}</FONT></P>
<P><FONT size=2>输出结果为：</FONT></P>
<P><FONT size=2>~Derived</FONT></P>
<P><FONT size=2>~Base</FONT></P>
<P><FONT size=2>如果析构函数不为虚，那么输出结果为</FONT></P>
<P><FONT size=2>~Base</FONT></P>
<P><BR><FONT size=2>十八、#IFNDEF/#DEFINE/#ENDIF有什么作用</FONT></P>
<P><FONT size=2>仿止该头文件被重复引用</FONT></P>
<P><FONT size=2>&nbsp;<BR>&nbsp;<BR></FONT></P><img src ="http://www.blogjava.net/faintbear/aggbug/6484.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-06-21 13:56 <a href="http://www.blogjava.net/faintbear/archive/2005/06/21/6484.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>教你理解复杂的C/C++声明</title><link>http://www.blogjava.net/faintbear/archive/2005/05/14/4285.html</link><dc:creator>小力力力</dc:creator><author>小力力力</author><pubDate>Sat, 14 May 2005 02:13:00 GMT</pubDate><guid>http://www.blogjava.net/faintbear/archive/2005/05/14/4285.html</guid><wfw:comment>http://www.blogjava.net/faintbear/comments/4285.html</wfw:comment><comments>http://www.blogjava.net/faintbear/archive/2005/05/14/4285.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/faintbear/comments/commentRss/4285.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/faintbear/services/trackbacks/4285.html</trackback:ping><description><![CDATA[曾经碰到过让你迷惑不解、类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗？本文将由易到难，一步一步教会你如何理解这种复杂的C/C++声明。 <BR><BR>　　我们将从每天都能碰到的较简单的声明入手，然后逐步加入const修饰符和typedef，还有函数指针，最后介绍一个能够让你准确地理解任何C/C++声明的“右左法则”。 <BR><BR>　　需要强调一下的是，复杂的C/C++声明并不是好的编程风格；我这里仅仅是教你如何去理解这些声明。注意：为了保证能够在同一行上显示代码和相关注释，本文最好在至少1024x768分辨率的显示器上阅读。 <BR>　　基础 <BR>　　让我们从一个非常简单的例子开始，如下： <BR><BR>　　 int n; <BR><BR>　　这个应该被理解为“declare n as an int”（n是一个int型的变量）。 <BR>　　接下去来看一下指针变量，如下： <BR><BR>　　int *p; <BR><BR>　　这个应该被理解为“declare p as an int *”（p是一个int *型的变量），或者说p是一个指向一个int型变量的指针。我想在这里展开讨论一下：我觉得在声明一个指针（或引用）类型的变量时，最好将*（或&amp;）写在紧靠变量之前，而不是紧跟基本类型之后。这样可以避免一些理解上的误区，比如： <BR><BR>　　int* p,q; <BR><BR>　　第一眼看去，好像是p和q都是int*类型的，但事实上，只有p是一个指针，而q是一个最简单的int型变量。 <BR>　　我们还是继续我们前面的话题，再来看一个指针的指针的例子： <BR><BR>　　char **argv; <BR><BR>　　理论上，对于指针的级数没有限制，你可以定义一个浮点类型变量的指针的指针的指针的指针... <BR>　　再来看如下的声明： <BR><BR>　　int RollNum[30][4]; <BR>　　int (*p)[4]=RollNum; <BR>　　int *q[5]; <BR><BR>　　这里，p被声明为一个指向一个4元素（int类型）数组的指针，而q被声明为一个包含5个元素（int类型的指针）的数组。<BR>　　另外，我们还可以在同一个声明中混合实用*和&amp;，如下：<BR><BR>　　int **p1; // p1 is a pointer to a pointer to an int.<BR>　　int *&amp;p2; // p2 is a reference to a pointer to an int.<BR>　　int &amp;*p3; // ERROR: Pointer to a reference is illegal.<BR>　　int &amp;&amp;p4; // ERROR: Reference to a reference is illegal.<BR><BR>　　注：p1是一个int类型的指针的指针；p2是一个int类型的指针的引用；p3是一个int类型引用的指针（不合法！）；p4是一个int类型引用的引用（不合法！）。<BR>　　const修饰符<BR>　　 当你想阻止一个变量被改变，可能会用到const关键字。在你给一个变量加上const修饰符的同时，通常需要对它进行初始化，因为以后的任何时候你将没有机会再去改变它。例如：<BR><BR>　　const int n=5;<BR>　　int const m=10;<BR><BR>　　 上述两个变量n和m其实是同一种类型的——都是const int（整形恒量）。因为C++标准规定，const关键字放在类型或变量名之前等价的。我个人更喜欢第一种声明方式，因为它更突出了const修饰符的作用。<BR>　　 当const与指针一起使用时，容易让人感到迷惑。例如，我们来看一下下面的p和q的声明：<BR><BR>　　const int *p;<BR>　　int const *q;<BR><BR>　　 他们当中哪一个代表const int类型的指针（const直接修饰int），哪一个代表int类型的const指针（const直接修饰指针）？实际上，p和q都被声明为const int类型的指针。而int类型的const指针应该这样声明：<BR><BR>　　int * const r= &amp;n; // n has been declared as an int<BR><BR>　　 这里，p和q都是指向const int类型的指针，也就是说，你在以后的程序里不能改变*p的值。而r是一个const指针，它在声明的时候被初始化指向变量n（即r=&amp;n;）之后，r的值将不再允许被改变（但*r的值可以改变）。<BR>　　 组合上述两种const修饰的情况，我们来声明一个指向const int类型的const指针，如下：<BR><BR>　　const int * const p=&amp;n // n has been declared as const int<BR><BR>　　 下面给出的一些关于const的声明，将帮助你彻底理清const的用法。不过请注意，下面的一些声明是不能被编译通过的，因为他们需要在声明的同时进行初始化。为了简洁起见，我忽略了初始化部分；因为加入初始化代码的话，下面每个声明都将增加两行代码。<BR><BR>　　char ** p1; // pointer to pointer to char<BR>　　const char **p2; // pointer to pointer to const char<BR>　　char * const * p3; // pointer to const pointer to char<BR>　　const char * const * p4; // pointer to const pointer to const char<BR>　　char ** const p5; // const pointer to pointer to char<BR>　　const char ** const p6; // const pointer to pointer to const char<BR>　　char * const * const p7; // const pointer to const pointer to char<BR>　　const char * const * const p8; // const pointer to const pointer to const char<BR><BR>　　 注：p1是指向char类型的指针的指针；p2是指向const char类型的指针的指针；p3是指向char类型的const指针；p4是指向const char类型的const指针；p5是指向char类型的指针的const指针；p6是指向const char类型的指针的const指针；p7是指向char类型const指针的const指针；p8是指向const char类型的const指针的const指针。<BR>　　typedef的妙用<BR>　　 typedef给你一种方式来克服“*只适合于变量而不适合于类型”的弊端。你可以如下使用typedef：<BR><BR>　　typedef char * PCHAR;<BR>　　PCHAR p,q;<BR><BR>　　 这里的p和q都被声明为指针。（如果不使用typedef，q将被声明为一个char变量，这跟我们的第一眼感觉不太一致！）下面有一些使用typedef的声明，并且给出了解释：<BR><BR>　　typedef char * a; // a is a pointer to a char<BR>　　typedef a b(); // b is a function that returns<BR>　　 // a pointer to a char<BR>　　typedef b *c; // c is a pointer to a function<BR>　　 // that returns a pointer to a char<BR>　　typedef c d(); // d is a function returning<BR>　　 // a pointer to a function<BR>　　 // that returns a pointer to a char<BR>　　typedef d *e; // e is a pointer to a function <BR>　　 // returning a pointer to a <BR>　　 // function that returns a <BR>　　 // pointer to a char<BR>　　e var[10]; // var is an array of 10 pointers to <BR>　　 // functions returning pointers to <BR>　　 // functions returning pointers to chars.<BR><BR>　　 typedef经常用在一个结构声明之前，如下。这样，当创建结构变量的时候，允许你不使用关键字struct（在C中，创建结构变量时要求使用struct关键字，如struct tagPOINT a；而在C++中，struct可以忽略，如tagPOINT b）。<BR><BR>　　typedef struct tagPOINT<BR>　　{<BR>　　 int x;<BR>　　 int y;<BR>　　}POINT;<BR>　　POINT p; /* Valid C code */<BR><BR>　　函数指针<BR>　　 函数指针可能是最容易引起理解上的困惑的声明。函数指针在DOS时代写TSR程序时用得最多；在Win32和X-Windows时代，他们被用在需要回调函数的场合。当然，还有其它很多地方需要用到函数指针：虚函数表，STL中的一些模板，Win NT/2K/XP系统服务等。让我们来看一个函数指针的简单例子：<BR><BR>　　int (*p)(char);<BR><BR>　　 这里p被声明为一个函数指针，这个函数带一个char类型的参数，并且有一个int类型的返回值。另外，带有两个float类型参数、返回值是char类型的指针的指针的函数指针可以声明如下：<BR><BR>　　char ** (*p)(float, float);<BR><BR>　　 那么，带两个char类型的const指针参数、无返回值的函数指针又该如何声明呢？参考如下：<BR><BR>　　void * (*a[5])(char * const, char * const);<BR><BR>　　 “右左法则”[重要！！！]<BR>　　The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.<BR>　　 这是一个简单的法则，但能让你准确理解所有的声明。这个法则运用如下：从最内部的括号开始阅读声明，向右看，然后向左看。当你碰到一个括号时就调转阅读的方向。括号内的所有内容都分析完毕就跳出括号的范围。这样继续，直到整个声明都被分析完毕。<BR>　　 对上述“右左法则”做一个小小的修正：当你第一次开始阅读声明的时候，你必须从变量名开始，而不是从最内部的括号。<BR>　　 下面结合例子来演示一下“右左法则”的使用。<BR><BR>　　int * (* (*fp1) (int) ) [10];<BR><BR>　　 阅读步骤：<BR>　　 1. 从变量名开始 -------------------------------------------- fp1<BR>　　 2. 往右看，什么也没有，碰到了)，因此往左看，碰到一个* ------ 一个指针<BR>　　 3. 跳出括号，碰到了(int) ----------------------------------- 一个带一个int参数的函数<BR>　　 4. 向左看，发现一个* --------------------------------------- （函数）返回一个指针<BR>　　 5. 跳出括号，向右看，碰到[10] ------------------------------ 一个10元素的数组<BR>　　 6. 向左看，发现一个* --------------------------------------- 指针<BR>　　 7. 向左看，发现int ----------------------------------------- int类型<BR>　　 总结：fp1被声明成为一个函数的指针的指针的数组，这个数组有10个元素，函数的原型为带一个int类型的参数，返回值为一个指针？<BR>　　再来看一个例子： <BR><BR>　　int *( *( *arr[5])())();<BR><BR>　　 阅读步骤：<BR>　　 1. 从变量名开始 -------------------------------------------- arr<BR>　　 2. 往右看，发现是一个数组 ---------------------------------- 一个5元素的数组<BR>　　 3. 向左看，发现一个* --------------------------------------- 指针<BR>　　 4. 跳出括号，向右看，发现() -------------------------------- 不带参数的函数<BR>　　 5. 向左看，碰到* ------------------------------------------- （函数）返回一个指针<BR>　　 6. 跳出括号，向右发现() ------------------------------------ 不带参数的函数<BR>　　 7. 向左，发现* --------------------------------------------- （函数）返回一个指针<BR>　　 8. 继续向左，发现int --------------------------------------- int类型<BR>　　 总结：<BR>　　 还有更多的例子：<BR><BR>　　float ( * ( *b()) [] )(); // b is a function that returns a <BR>　　 // pointer to an array of pointers<BR>　　 // to functions returning floats.<BR>　　void * ( *c) ( char, int (*)()); // c is a pointer to a function that takes<BR>　　 // two parameters:<BR>　　 // a char and a pointer to a<BR>　　 // function that takes no<BR>　　 // parameters and returns<BR>　　 // an int<BR>　　 // and returns a pointer to void.<BR>　　void ** (*d) (int &amp;, <BR>　　 char **(*)(char *, char **)); // d is a pointer to a function that takes<BR>　　 // two parameters:<BR>　　 // a reference to an int and a pointer<BR>　　 // to a function that takes two parameters:<BR>　　 // a pointer to a char and a pointer<BR>　　 // to a pointer to a char<BR>　　 // and returns a pointer to a pointer <BR>　　 // to a char<BR>　　 // and returns a pointer to a pointer to void<BR>　　float ( * ( * e[10]) <BR>　　 (int &amp;) ) [5]; // e is an array of 10 pointers to <BR>　　 // functions that take a single<BR>　　 // reference to an int as an argument <BR>　　 // and return pointers to<BR>　　 // an array of 5 floats.<BR><img src ="http://www.blogjava.net/faintbear/aggbug/4285.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/faintbear/" target="_blank">小力力力</a> 2005-05-14 10:13 <a href="http://www.blogjava.net/faintbear/archive/2005/05/14/4285.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>