??xml version="1.0" encoding="utf-8" standalone="yes"?>色噜噜的亚洲男人的天堂,久久精品亚洲中文字幕无码网站,ww亚洲ww在线观看国产http://www.tkk7.com/faintbear/category/981.html风嗖嗖的刮着......zh-cnWed, 06 Jan 2010 03:57:20 GMTWed, 06 Jan 2010 03:57:20 GMT60静态局部变?/title><link>http://www.tkk7.com/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.tkk7.com/faintbear/archive/2010/01/06/308408.html</guid><wfw:comment>http://www.tkk7.com/faintbear/comments/308408.html</wfw:comment><comments>http://www.tkk7.com/faintbear/archive/2010/01/06/308408.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/faintbear/comments/commentRss/308408.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/faintbear/services/trackbacks/308408.html</trackback:ping><description><![CDATA[在局部变量前加上“static”关键字,成了静态局部变量。静态局部变量存攑֜内存的全局数据区。函数结束时Q静态局部变量不会消失,每次该函数调? Ӟ也不会ؓ光新分配空间。它始终ȝ在全局数据区,直到E序q行l束。静态局部变量的初始化与全局变量cMQ如果不为其昑ּ初始化,则C++自动为其 初始化ؓ0?br /> 静态局部变量与全局变量׃n全局数据区,但静态局部变量只在定义它的函C可见。静态局部变量与局部变量在存储位置上不同,使得其存在的旉也不同,D对这两者操?的运行结果也不同?br /> 例如Q下面的E序定义了全局变量、静态局部变量和局部变量:<br /> <span id="qmsy6oc" class="ff0000"><font color="#ff0000"> //*********************<br /> //**   ch5_2.cpp  **<br /> //********************* </font></span> <p class="ff0000"><font color="#ff0000">    #include <iostream.h></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 <<"a:" <<a<br /> <<" b:" <<b<br /> <<" n:" <<n <<endl;<br /> b+=4;<br /> func();<br /> cout <<"a:" <<a<br /> <<" b:" <<b<br /> <<" n:" <<n <<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 <<"a:" <<a<br /> <<" b:" <<b<br /> <<" n:" <<n <<endl;<br /> }</font></p> <p class="p14">  q行l果为:<br /> <span id="6wmssks" 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 /> E序中主函数main()两次调用了func()函数Q从q行l果可以看出Q程序控制每ơ进入func()函数Ӟ局部变量b都被初始化。而静态局? 变量a仅在W一ơ调用时被初始化Q第二次q入该函数时Q不再进行初始化Q这时它的值是W一ơ调用后的结果?? main()函数中的变量a和b与func()函数中的变量a和bI间位置是不一LQ所以相应的g不一栗关于变量作用域和可见性的q一步讨W? 章?br /> 静态局部变量的用途有许多Q可以用它定某函数是否被调用q。用它保留多次调用的倹{?/p> <p class="p14"><strong><font size="5">寚w态局部变量的说明Q?/font></strong> <br /> (1) 静态局部变量在静态存储区内分配存储单元。在E序整个q行期间都不释放。而自动变量(卛_态局部变量)属于动态存储类别,存储在动态存储区I间(而不是静态存储区I间)Q函数调用结束后即释放?<br /> (2) 为静态局部变量赋初值是在编译时q行值的Q即只赋初gơ,在程序运行时它已有初倹{以后每ơ调用函数时不再重新赋初D只是保留上ơ函数调用结束时? 倹{而ؓ自动变量赋初|不是在编译时q行的,而是在函数调用时q行Q每调用一ơ函数重新给一ơ初|相当于执行一ơ赋D句?<br /> <br /> (3) 如果在定义局部变量时不赋初值的话,寚w态局部变量来_~译时自动赋初?(Ҏ值型变量)或空字符(对字W型变量)。而对自动变量来说Q如果不赋初 |则它的值是一个不定的倹{这是由于每ơ函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的?<br /> (4) 虽然静态局部变量在函数调用l束后仍然存在,但其他函数是不能引用它的Q也是_在其他函C它是“不可?#8221;的?/p> <img src ="http://www.tkk7.com/faintbear/aggbug/308408.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/faintbear/" target="_blank">力力力</a> 2010-01-06 11:37 <a href="http://www.tkk7.com/faintbear/archive/2010/01/06/308408.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/C++ E序设计员应聘常见面试试题深入剖?http://www.tkk7.com/faintbear/archive/2006/07/27/60321.html力力力力力力Thu, 27 Jul 2006 06:08:00 GMThttp://www.tkk7.com/faintbear/archive/2006/07/27/60321.htmlhttp://www.tkk7.com/faintbear/comments/60321.htmlhttp://www.tkk7.com/faintbear/archive/2006/07/27/60321.html#Feedback2http://www.tkk7.com/faintbear/comments/commentRss/60321.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/60321.html C/C++ E序设计员应聘常见面试试题深入剖?
旉Q?006-5-30 17:06:00  
.引言

  本文的写作目的ƈ不在于提供C/C++E序员求职面试指|而旨在从技术上分析面试题的内涵。文中的大多数面试题来自各大论坛Q部分试题解{也参考了|友的意见?br />
  许多面试题看似简单,却需要深厚的基本功才能给出完的解答。企业要求面试者写一个最单的strcpy函数都可看出面试者在技术上I竟辑ֈ了怎样的程度,我们能真正写好一个strcpy函数吗?我们都觉得自pQ可是我们写出的strcpy很可能只能拿?0分中?分。读者可从本文看到strcpy 函数?分到10分解{的例子Q看看自己属于什么样的层ơ。此外,q有一些面试题考查面试者敏L思维能力?

  分析q些面试题,本n包含很强的趣x;而作Z名研发h员,通过对这些面试题的深入剖析则可进一步增w的内功?br />
  2.N?br />
  试题1Q?br />
void test1()
{
 char string[10];
 char* str1 = "0123456789";
 strcpy( string, str1 );
}

  试题2Q?br />
void test2()
{
 char string[10], str1[10];
 int i;
 for(i=0; i<10; i++)
 {
  str1[i] = 'a';
 }
 strcpy( string, str1 );
}

  试题3Q?br />
void test3(char* str1)
{
 char string[10];
 if( strlen( str1 ) <= 10 )
 {
  strcpy( string, str1 );
 }
}

  解答Q?br />
  试题1字符串str1需?1个字节才能存放下Q包括末’\0’)Q而string只有10个字节的I间Qstrcpy会导致数l越界;

  对试?Q如果面试者指出字W数lstr1不能在数l内l束可以l?分;如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存h复制的字节数h不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10 分;

  对试?Qif(strlen(str1) <= 10)应改为if(strlen(str1) < 10)Q因为strlen的结果未l计’\0’所占用?个字节?br />
  剖析Q?br />
  考查对基本功的掌握:

  (1)字符串以’\0’结;

  (2)Ҏl越界把握的敏感度;

  (3)库函数strcpy的工作方式,如果~写一个标准strcpy函数的dgؓ10Q下面给出几个不同得分的{案Q?br />
  2?br />
void strcpy( char *strDest, char *strSrc )
{
  while( (*strDest++ = * strSrc++) != ‘\0?);
}

  4?br />
void strcpy( char *strDest, const char *strSrc )
//源字符串加constQ表明其入参敎ͼ??br />{
  while( (*strDest++ = * strSrc++) != ‘\0?);
}

  7?br />
void strcpy(char *strDest, const char *strSrc)
{
 //Ҏ地址和目的地址加非0断言Q加3?br /> assert( (strDest != NULL) && (strSrc != NULL) );
 while( (*strDest++ = * strSrc++) != ‘\0?);
}

  10?br />
//Z实现铑ּ操作Q将目的地址q回Q加3分!

char * strcpy( char *strDest, const char *strSrc )
{
 assert( (strDest != NULL) && (strSrc != NULL) );
 char *address = strDest;
 while( (*strDest++ = * strSrc++) != ‘\0?);
  return address;
}

  ?分到10分的几个{案我们可以清楚的看刎ͼ小的strcpy竟然暗藏着q么多玄机,真不是盖的!需要多么扎实的基本功才能写一个完的strcpy啊!

  (4)对strlen的掌握,它没有包括字W串末尾?\0'?br />
  读者看了不同分值的strcpy版本Q应该也可以写出一?0分的strlen函数了,完美的版本ؓQ?int strlen( const char *str ) //输入参数const

{
 assert( strt != NULL ); //断言字符串地址?
 int len;
 while( (*str++) != '\0' )
 {
  len++;
 }
 return len;
}

  试题4Q?br />
void GetMemory( char *p )
{
 p = (char *) malloc( 100 );
}

void Test( void )
{
 char *str = NULL;
 GetMemory( str );
 strcpy( str, "hello world" );
 printf( str );
}

  试题5Q?br />
char *GetMemory( void )
{
 char p[] = "hello world";
 return p;
}

void Test( void )
{
 char *str = NULL;
 str = GetMemory();
 printf( str );
}

  试题6Q?br />
void GetMemory( char **p, int num )
{
 *p = (char *) malloc( num );
}

void Test( void )
{
 char *str = NULL;
 GetMemory( &str, 100 );
 strcpy( str, "hello" );
 printf( str );
}

  试题7Q?br />
void Test( void )
{
 char *str = (char *) malloc( 100 );
 strcpy( str, "hello" );
 free( str );
 ... //省略的其它语?br />}

  解答Q?br />
  试题4传入中GetMemory( char *p )函数的Ş参ؓ字符串指针,在函数内部修改Ş参ƈ不能真正的改变传入Ş参的|执行?br />
char *str = NULL;
GetMemory( str );

  后的str仍然为NULLQ?br />
  试题5?br />
char p[] = "hello world";
return p;

  的p[]数组为函数内的局部自动变量,在函数返回后Q内存已l被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期?br />
  试题6的GetMemory避免了试?的问题,传入GetMemory的参Cؓ字符串指针的指针Q但是在GetMemory中执行申请内存及赋D?br />
*p = (char *) malloc( num );

  后未判断内存是否甌成功Q应加上Q?br />
if ( *p == NULL )
{
 ...//q行甌内存p|处理
}

  试题7存在与试?同样的问题,在执?br />
char *str = (char *) malloc(100);

  后未q行内存是否甌成功的判断;另外Q在free(str)后未|str为空Q导致可能变成一个“野”指针,应加上:

str = NULL;

  试题6的Test函数中也未对malloc的内存进行释放?br />
  剖析Q?br />
  试题4?考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正的回答其中50~60的错误。但是要完全解答正确Q却也绝非易事?br />
  对内存操作的考查主要集中在:

  Q?Q指针的理解Q?br />
  Q?Q变量的生存期及作用范围Q?br />
  Q?Q良好的动态内存申请和释放习惯?br />
  再看看下面的一D늨序有什么错误:

swap( int* p1,int* p2 )
{
 int *p;
 *p = *p1;
 *p1 = *p2;
 *p2 = *p;
}

  在swap函数中,p是一个“野”指针,有可能指向系l区Q导致程序运行的崩溃。在VC++中DEBUGq行时提C错误“Access Violation”。该E序应该改ؓQ?br />
swap( int* p1,int* p2 )
{
 int p;
 p = *p1;
 *p1 = *p2;
 *p2 = p;
}

 3.内功?br />
  试题1Q分别给出BOOLQintQfloatQ指针变?与“零值”比较的 if 语句Q假讑֏量名为varQ?br />
  解答Q?br />
   BOOL型变量:if(!var)

   int型变量: if(var==0)

   float型变量:

   const float EPSINON = 0.00001;

   if ((x >= - EPSINON) && (x <= EPSINON)

   指针变量Q  if(var==NULL)

  剖析Q?br />
  考查?值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0)Q而int型变量也可以写成if(!var)Q指针变量的判断也可以写成if(!var)Q上q写法虽然程序都能正运行,但是未能清晰地表辄序的意思?


  const关键字至有下列n个作用:

  Q?Q欲L一个变量被改变Q可以用const关键字。在定义该const变量Ӟ通常需要对它进行初始化Q因Z后就没有Z再去改变它了Q?br />
  Q?Q对指针来说Q可以指定指针本wؓconstQ也可以指定指针所指的数据为constQ或二者同时指定ؓconstQ?br />
  Q?Q在一个函数声明中Qconst可以修饰形参Q表明它是一个输入参敎ͼ在函数内部不能改变其|

  Q?Q对于类的成员函敎ͼ若指定其为constcdQ则表明其是一个常函数Q不能修改类的成员变量;

  Q?Q对于类的成员函敎ͼ有时候必L定其q回gؓconstcdQ以使得其返回g为“左值”。例如:

const classA operator*(const classA& a1,const classA& a2);

  operator*的返回结果必L一个const对象。如果不是,q样的变态代码也不会~译出错Q?br />
classA a, b, c;
(a * b) = c; // 对a*b的结果赋?/td>

  操作(a * b) = c昄不符合编E者的初衷Q也没有M意义?br />
  剖析Q?br />
  惊讶吗?小的static和const居然有这么多功能Q我们能回答几个Q如果只能回{?~2个,那还真得闭关再好好修g点{?br />
  q个题可以考查面试者对E序设计知识的掌握程度是初、中U还是比较深入,没有一定的知识q度和深度,不可能对q个问题l出全面的解{。大多数人只能回{出static和const关键字的部分功能?br />
  4.技巧题

  试题1Q请写一个C函数Q若处理器是Big_endian的,则返?Q若是Little_endian的,则返?

  解答Q?br />
int checkCPU()
{
 {
  union w
  {
   int a;
   char b;
  } c;
  c.a = 1;
  return (c.b == 1);
 }
}

  剖析Q?br />
  嵌入式系l开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPUҎ作数的存放方式是从低字节到高字节Q而Big-endian模式Ҏ作数的存放方式是从高字节C字节。例如,16bit宽的?x1234在Little- endian模式CPU内存中的存放方式Q假设从地址0x4000开始存放)为:

内存地址存放内容
0x40000x34
0x40010x12

  而在Big-endian模式CPU内存中的存放方式则ؓQ?br />
内存地址存放内容
0x40000x12
0x40010x34

  32bit宽的?x12345678在Little-endian模式CPU内存中的存放方式Q假设从地址0x4000开始存放)为:

内存地址存放内容
0x40000x78
0x40010x56
0x40020x34
0x40030x12

  而在Big-endian模式CPU内存中的存放方式则ؓQ?br />
内存地址存放内容
0x40000x12
0x40010x34
0x40020x56
0x40030x78

  联合体union的存N序是所有成员都从低地址开始存放,面试者的解答利用该特性,L地获得了CPU对内存采用Little-endianq是Big-endian模式d。如果谁能当场给个解{,那简直就是一个天才的E序员?br />
  试题2Q写一个函数返?+2+3+?n的|假定l果不会过长整型变量的范围Q?

  解答Q?br />
int Sum( int n )
{
 return ( (long)1 + n) * n / 2;  //或return (1l + n) * n / 2;
}

  剖析Q?br /> 
  对于q个题,只能_也许最单的{案是最好的{案。下面的解答Q或者基于下面的解答思\M化,不管怎么“折䏀,其效率也不可能与直接return ( 1 l + n ) * n / 2相比Q?

int Sum( int n )
{
 long sum = 0;
 for( int i=1; i<=n; i++ )
 {
  sum += i;
 }
 return sum;
}

  所以程序员们需要敏感地数学等知识用在E序设计中?


力力力 2006-07-27 14:08 发表评论
]]>
c 语言隄分析整理 (?http://www.tkk7.com/faintbear/archive/2006/07/18/58871.html力力力力力力Tue, 18 Jul 2006 15:29:00 GMThttp://www.tkk7.com/faintbear/archive/2006/07/18/58871.htmlhttp://www.tkk7.com/faintbear/comments/58871.htmlhttp://www.tkk7.com/faintbear/archive/2006/07/18/58871.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/58871.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/58871.html
c 语言隄分析整理
原创Qimy 2003q??0?

q篇文章主要是介l一些在复习C语言的过E中W者个为比较重点的地方Q较好的掌握q些重点会对C的运用更加得心应手。此外会包括一些细节、易错的地方。涉及的主要内容包括Q变量的作用域和存储cd、函数、数l、字W串、指针、文件、链表等。一些最基本的概念在此就不多作解释了Q仅希望能有只言片语l同是C语言初学者的学习和上E提供一点点的帮助?br />

变量作用域和存储cdQ?/strong>

了解了基本的变量cd后,我们要进一步了解它的存储类别和变量作用域问题?/p>
变量cd子类?/td>
局部变?/td>静态变量(d函数Q变量g保留Q?/td>
自动变量
寄存器变?/td>
全局变量静态变量(只能在本文g中用Q?/td>
非静态变量(允许其他文g使用Q?/td>

换一个角?/p>
变量cd子类?/td>
静态存储变?/font>静态局部变量(函数Q?/font>
静态全局变量Q本文gQ?/font>
非静态全局/外部变量Q其他文件引用)
动态存储变?/td>自动变量
寄存器变?/td>
形式参数

extern型的存储变量在处理多文g问题时常能用刎ͼ在一个文件中定义extern型的变量卌明这个变量用的是其他文g的。顺便说一下,W者在做课设时遇到out of memory的错误,于是Ҏ做多文gQ再把它includeq来Q注意自己写?.h要用“”不?lt;>Q,能vC定的效用。static型的在读E序写结果的试题中是个考点。多数时候整个程序会出现多个定义的变量在不同的函CQ考查在不同位|同一变量的值是多少。主要是遵@一个原则,只要本函数内没有定义的变量就用全局变量Q而不是main里的Q,全局变量和局部变量重名时局部变量v作用Q当然还要注意静态与自动变量的区别?

函数Q?/strong>

对于函数最基本的理解是从那个叫main的单词开始的Q一开始M觉得把语句一q写在main里不是挺好的么,Z么偏择出厅R其实这是因为对函数q不够熟l,否则函数的运用会l我们编E带来极大的便利。我们要知道函数的返回值类型,参数的类型,以及调用函数时的形式。事先的函数说明也能起到一个提醒的好作用。所谓Ş参和实参Q即在调用函数时写在括号里的是实参Q函数本w用的就是Ş参,在画程图时用^行四边Ş表示传参?/p>

函数的另一个应用例子就是递归了,W者开始比较头疼的问题Q反应L比较q钝Q按照老师的方法,把递归的过E耐心准确的逐d来,学习的效果还是比较好的,会觉得这U递归的运用是挺y的,事实上,著名的八皇后、汉诺塔{问题都用到了递归?/p>
例子Q?br />long fun(int n)
{
long s;
if(n==1||n==2) s=2;
   else s=n-fun(n-1);
return s;
}
main()
{
printf("%ld",fun(4));
}

数组Q?/strong>

分ؓ一l数l和多维数组Q其存储方式Mؓ表格的话׃一目了Ӟ其实是把相同类型的变量有序的放在一赗因此,在处理比较多的数据时Q这也是大多数的情况Q数l的应用范围是非常广的?/p>

具体的实际应用不便D例,而且l大多数是与指针相结合的Q笔者个为学习数l在更大E度上是为学习指针做一个铺垫。作为基的基要明白几U基本操作:xl赋倹{打印、排序(冒排序法和选择排序法)、查找。这些都不可避免的用到@环,如果觉得反应不过来,可以先一点点的把循环展开Q就会越来越熟悉Q以后自q写一个功能的时候就会先扑և内在规律Q较好的q用了。另外数l做参数Ӟ一l的[]里可以是I的Q二l的W一个[]里可以是I的但是W二个[]中必规定大?/p>
冒法排序函敎ͼ
void bubble(inta[],int n)
{
int i,j,k;
for(i=1,i<n;i++)
   for(j=0;j<n-i-1;j++)
   if(a[j]>a[j+1])
    {
    k=a[j];
       a[j]=a[j+1];
       a[j+1]=k;
       }

}

选择法排序函敎ͼ
void sort(inta[],int n)
{
int i,j,k,t;
for(i=0,i<n-1;i++)
   {
   k=i;
   for(j=i+1;j<n;j++)
      if(a[k]<a[j]) k=j;
      if(k!=i)
         {
         t=a[i];
         a[i]=a[k];
         a[k]=t;
         }
   }
}

折半查找函数Q原数组有序Q:
void search(inta[],int n,int x)
{
int left=0,right=n-1,mid,flag=0;
while((flag==0)&&(left<=right))
   {
   mid=(left+right)/2;
   if(x==a[mid])
      {
      printf("%d%d",x,mid);
      flag =1;
      }
      elseif(x<a[mid]) right=mid-1;
                   elseleft=mid+1;
   }
}

相关常用的算法还?strong>判断回文Q求阶乘QFibanacci数列QQ意进制{换,杨辉三角形计?/strong>{等?/strong>

字符Ԍ

字符串其实就是一个数l(指针Q,在scanf的输入列中是不需要在前面加?amp;”符LQ因为字W数l名本n即代表地址。值得注意的是字符串末‘\0’,如果没有的话Q字W串很有可能会不正常的打印。另外就是字W串的定义和赋值问题了Q笔者有一ơ的比较l合的上Z业就是字W串打印老是qQ上上下下找了一圈问题,最后发现是因ؓ

char *name;

而不?/p>
char name[10];

前者没有说明指向哪儿,更没有确定大,D了ؕ码的错误Q印象挺深刻的?

另外Q字W串的赋g是需要注意的Q如果是用字W指针的话,既可以定义的时候赋初|?/p>
char *a="Abcdefg";

也可以在赋D句中赋|?/p>
char *a;
a="Abcdefg";

但如果是用字W数l的话,只能在定义时整体赋初|即char a[5]={"abcd"};而不能在赋D句中整体赋倹{?

常用字符串函数列表如下,要会自己实现Q?/p>
函数作用函数调用形式备注
字符串拷贝函?/td>strcpy(char*,char *)后者拷贝到前?/td>
字符串追加函?/td>strcat(char*,char *)后者追加到前者后Q返回前者,因此前者空间要_?/td>
字符串比较函?/td>strcmp(char*,char *)前者等于、小于、大于后者时Q返?、正倹{负倹{注意,不是比较长度Q是比较字符ASCII码的大小Q可用于按姓名字母排序等?/td>
字符串长?/td>strlen(char *)q回字符串的长度Q不包括'\0'.转义字符一个字W?/td>
字符串型->整型atoi(char *)
整型->字符串型itoa(int,char *,int)做课设时挺有用的
sprintf(char *,格式化输入)赋给字符Ԍ而不打印出来。课设时用也比较方便

注:对字W串是不允许?=或!=的运的Q只能用字符串比较函?/p>

指针Q?/strong>

指针可以说是C语言中最关键的地方了Q其实这个“指针”的名字对于q个概念的理解是十分形象的。首先要知道Q指针变量的|x针变量中存放的|是指针(卛_址Q。指针变量定义Ş式中Q基本类?*指针变量?中的?”代表的是这是一个指向该基本cd的指针变量,而不是内容的意思。在以后使用的时候,?ptr=aӞ?”才表示ptr所指向的地址里放的内Ҏa?/p>

指针比较典型又简单的一应用例子是两C换,看下面的E序Q?/p>
swap(intc,intd)
{
int t;
t=c;
c=d;
d=t;
}
main()
{
int a=2,b=3;
swap(a,b);
printf(?d,%d?a,b);
}

q是不能实现a和b的数g换的Q实际上只是形参在这个函C换来换去Q对实参没什么媄响。现在,用指针类型的数据做ؓ参数的话Q更改如下:

swap(#3333FF *p1,int *p2)
{
int t;
t=*p1;
*p1=*p2;
*p2=t;
}
main()
{
int a=2,b=3;
int *ptr1,*ptr2;
ptr1=&a;
ptr2=&b;
swap(prt1,ptr2);
printf(?d,%d?a,b);
}

q样在swap中就把p1,p2 的内容给换了Q即把aQb的g换了?/p>

指针可以执行增、减q算Q结?+q算W的法则Q我们可以看?

*++s

取指针变量加1以后的内?/p>

*s++取指针变量所指内容后s再加1
(*s)++指针变量指的内容?

指针和数l?/strong>实际上几乎是一LQ数l名可以看成是一个常量指针,一l数l中ptr=&b[0]则下面的表示法是{h的:

a[3]{h?(a+3)
ptr[3]{h?(ptr+3)

下面看一个用指针来自己实现atoiQ字W串?>整型Q函敎ͼ

int atoi(char *s)
{
int sign=1,m=0;
if(*s=='+'||*s=='-') /*判断是否有符?/
sign=(*s++=='+')?1:-1;/*用到三目q算W?/
while(*s!='\0') /*Ҏ一个字W进行操?/
   {
   m=m*10+(*s-'0');
   s++;
/*指向下一个字W?/
   }
return m*sign;
}

指向多维数组的指针变量也是一个比较广泛的q用。例如数la[3][4]Qa代表的实际是整个二维数组的首地址Q即W?行的首地址Q也是一个指针变量。而a+1׃是简单的在数g加上1了,它代表的不是a[0][1]Q而是W?行的首地址Q?amp;a[1][0]?/p>

指针变量常用的用途还有把指针作ؓ参数传递给其他函数Q即指向函数的指?/strong>?br />看下面的几行代码Q?/p>
void Input(ST *);
void Output(ST *);
void Bubble(ST *);
void Find(ST *);
void Failure(ST *);
/*函数声明Q这五个函数都是以一个指向ST型(事先定义q)l构的指针变量作为参敎ͼ无返回倹{?/

void(*process[5])(ST *)={Input,Output,Bubble,Find,Failure};
/*process被调用时提供5U功能不同的函数共选择(指向函数的指针数l)*/

printf("\nChoose:\n?");
scanf("%d",&choice);
if(choice>=0&&choice<=4)
(*process[choice])(a); /*调用相应的函数实C同功?;/

MQ指针的应用是非常灵zdq泛的,不是三言两语能说完的Q上面几个小例子只是个引子,实际~程中,会逐渐发现q用指针所能带来的便利和高效率?

文gQ?/strong>

函数调用形式说明
fopen("路径","打开方式")打开文g
fclose(FILE *)防止之后被误?/td>
fgetc(FILE *)从文件中d一个字W?/td>
fputc(ch,FILE *)把ch代表的字W写入这个文仉
fgets(FILE *)从文件中d一?/td>
fputs(FILE *)把一行写入文件中
fprintf(FILE *,"格式字符?,输出表列Q?/td>把数据写入文?/td>
fscanf(FILE *,"格式字符?,输入表列Q?/td>从文件中d
fwriteQ地址QsizeofQ)QnQFILE *Q?/td>把地址中n个sizeof大的数据写入文g?/td>
freadQ地址QsizeofQ)QnQFILE *Q?/td>把文件中n个sizeof大的数据d地址?/td>
rewindQFILE *Q?/td>把文件指针拨回到文g?/td>
fseekQFILE *QxQ?/1/2Q?/td>Ud文g指针。第二个参数是位U量Q?代表从头U,1代表从当前位|移Q?代表从文件尾UR?/td>
feof(FILE *)判断是否C文g末尾

文g打开方式说明
r打开只能ȝ文g
w建立供写入的文gQ如果已存在抹d有数?/td>
a打开或徏立一个把数据q加到文件尾的文?/td>
r+打开用于更新数据的文?/td>
w+建立用于更新数据的文Ӟ如果已存在就抹去原有数据
a+打开或徏立用于更新数据的文gQ数据追加到文g?/td>

注:以上用于文本文g的操作,如果是二q制文g在上述字母后加“b”?/p>

我们用文件最大的目的是能让数据保存下来。因此在要用文g中数据的时候,是要把数据d一个结构(一般保存数据多用结构,便于理Q中去,再对l构q行操作卛_。例如,文gaa.data中存储的?0个学生的成W{信息,要遍历这些信息,对其q行成W输出、排序、查扄工作Ӟ我们把q些信息先读入到一个结构数l中Q再对这个数l进行操作。如下例Q?/p>
#include<stdio.h>
#include<stdlib.h>
#define N 30

typedef struct student /*定义储存学生成W信息的数l?/
{
char *name;
int chinese;
int maths;
int phy;
int total;
}ST;

main()
{
ST a[N]; /*存储N个学生信息的数组*/
FILE *fp;
void(*process[3])(ST *)={Output,Bubble,Find}; /*实现相关功能的三个函?/
int choice,i=0;
Show();
printf("\nChoose:\n?");
scanf("%d",&choice);
while(choice>=0&&choice<=2)
   {
   fp=fopen("aa.dat","rb");
   for(i=0;i<N;i++)
      fread(&a[i],sizeof(ST),1,fp);/*把文件中储存的信息逐个d数组中去*/
   fclose(fp);
   (*process[choice])(a); /*前面提到的指向函数的指针Q选择操作*/
   printf("\n");
   Show();
   printf("\n?");
   scanf("%d",&choice);
   }
}

void Show()
{
printf("\n****Choices:****\n0.Display the data form\n1.Bubble it according to the total score\n2.Search\n3.Quit!\n");
}

void Output(ST *a) /*文件中存储的信息逐个输出*/
{
int i,t=0;
printf("Name Chinese Maths Physics Total\n");
for(i=0;i<N;i++)
   {
   t=a[i].chinese+a[i].maths+a[i].phy;
   a[i].total=t;
   printf("%4s%8d%8d%8d%8d\n",a[i].name,a[i].chinese,a[i].maths,a[i].phy,a[i].total);
   }
}

void Bubble(ST *a)/*Ҏl进行排序,q输出结?/
{
int i,pass;
ST m;
for(pass=0;pass<N-1;pass++)
   for(i=0;i<N-1;i++)
      if(a[i].total<a[i+1].total)
         {
         m=a[i]; /*l构互换*/
         a[i]=a[i+1];
         a[i+1]=m;
         }
Output(a);
}

void Find(ST *a)
{
int i,t=1;
char m[20];
printf("\nEnter the name you want:");
scanf("%s",m);
for(i=0;i<N;i++)
   if(!strcmp(m,a[i].name))/*Ҏ姓名匚w情况输出查找l果*/
   {
   printf("\nThe result is:\n%s, Chinese:%d, Maths:%d,     Physics:%d,Total:%d\n",m,a[i].chinese,a[i].maths,a[i].phy,a[i].total);
   t=0;
   }
if(t)
   printf("\nThe name is not in the list!\n");
}

链表Q?/strong>
链表是C语言中另外一个难炏V牵扯到l点、动态分配空间等{。用l构作ؓ链表的结Ҏ非常适合的,例如Q?/p>
struct node
{
int data;
struct node *next;
};

其中next是指向自w所在结构类型的指针Q这样就可以把一个个l点相连Q构成链表?/p>

链表l构的一大优势就是动态分配存储,不会像数l一样必d定义时确定大,造成不必要的费。用malloc和free函数卛_实现开辟和释放存储单元。其中,malloc的参数多用sizeofq算W计得到?/p>

链表的基本操作有Q?strong>正、反向徏立链表;输出链表Q删除链表中l点Q在链表中插入结?/strong>{等Q都是要熟练掌握的,初学者通过d的方式能比较形象地理解徏立、插入等实现的过E?/p>
typedef struct node
{
char data;
struct node *next;
}NODE; /*l点*/

正向建立链表Q?br />NODE *create()
{
char ch='a';
NODE *p,*h=NULL,*q=NULL;
while(ch<'z')
   {
   p=(NODE *)malloc(sizeof(NODE)); /*强制cd转换为指?/
   p->data=ch;
   if(h==NULL) h=p;
      elseq->next=p;
   ch++;
   q=p;
   }
q->next=NULL; /*链表l束*/
return h;
}

逆向建立Q?/p>
NODE *create()
{
char ch='a';
NODE *p,*h=NULL;
while(ch<='z')
   {
   p=(NODE *)malloc(sizeof(NODE));
   p->data=ch;
   p->next=h; /*不断地把head往前挪*/
   h=p;
   ch++;
   }
return h;
}

用递归实现链表逆序输出Q?/p>
void output(NODE *h)
{
if(h!=NULL)
   {
   output(h->next);
   printf("%c",h->data);
   }
}

插入l点Q已有升序的链表Q:

NODE *insert(NODE *h,int x)
{
NODE *new,*front,*current=h;
while(current!=NULL&&(current->data<x)) /*查找插入的位|?/
   {
   front=current;
   current=current->next;
   }
new=(NODE *)malloc(sizeof(NODE));
new->data=x;
new->next=current;
if(current==h) /*判断是否是要插在表头*/
   h=new;
else front->next=new;
return h;
}

删除l点Q?/p>
NODE *delete(NODE *h,int x)
{
NODE *q,*p=h;
while(p!=NULL&&(p->data!=x))
   {
   q=p;
   p=p->next;
   }
if(p->data==x) /*扑ֈ了要删的l点*/
   {
   if(p==h) /*判断是否要删表头*/
   h=h->next;
      elseq->next=p->next;
   free(p);
/*释放掉已删掉的结?/
   }
return h;
}

l常有链表相关的E序填空题,做这L题要注意看下面提到的变量是否定义了,用到的变量是否赋初gQ是否有l分配空间的没有分配I间Q最后看看返回值是否正?/p>

W者水qx限,隑օ有疏漏、错误的地方Q浅显之处,q望指正见谅。上q内容仅是个提示作用Qƈ不包括C语言的全部内宏V?



力力力 2006-07-18 23:29 发表评论
]]>
链表3http://www.tkk7.com/faintbear/archive/2006/07/17/58657.html力力力力力力Mon, 17 Jul 2006 14:56:00 GMThttp://www.tkk7.com/faintbear/archive/2006/07/17/58657.htmlhttp://www.tkk7.com/faintbear/comments/58657.htmlhttp://www.tkk7.com/faintbear/archive/2006/07/17/58657.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/58657.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/58657.html 链表的运?02)

发表日期Q?003q??日    作者:C语言之家搜集整理  已经?462位读者读q此?/td>

3.链表节点的插?br />4.链表节点的删?/font>



3.链表节点的插?/font>
解:
    1) 首先声明一个新节点供输入要插入节点的内?br />    2) q戯入一个节点内?Key)Q表C欲插入在哪一个节点之?br />    3) 持箋往下一个节点,直到节点内容Key或节Ҏ针ؓNULL为止(x不到该节?
    4) 如果该节点不存在Q则插入在节点前
        New->Next=Head
        Head=New
    5) 如果扑ֈ该节点,?br />        New->Next=Pointer->Next
        Pointer->Next=New
*E序代码如下Q?br />#include<stdlib.h>
#include<stdio.h>
#define Max 10
struct List            /*节点l构声明*/
{
    int Number;
    int Total;
    struct List *Next;
};
typedef struct List Node;
typedef Node *Link;
int Data[2][Max]={1,3,5,7,2,4,6,8,9,0,15,35,10,67,25,65,38,70,30,20};
/*插入节点至链表内*/
Link Insert_List(Link Head,Link New,int Key)
{
    Link Pointer;        /*声明节点*/
    Pointer=Head;        /*Pointer指针设ؓ首节?/
    while(1)
    {
        if(Pointer==NULL)    /*插入在首节点?/
        {
            New->Next=Head;
            Head=New;
            break;
        }
        if(Pointer->Number==Key)    /*插入在链表中间或*/
        {
            New->Next=Pointer->Next;
            Pointer->Next=New;
            break;
        }
        Pointer=Pointer->Next;    /*指向下一个节?/
    }
    return Head;
}
/*输出链表数据*/
void Print_List(Link Head)
{
    Link Pointer;        /*节点声明*/
    Pointer=Head;        /*Pointer指针设ؓ首节?/
    while(Pointer!=NULL)    /*当节点ؓNULLl束循环*/
    {
        printf("[%d,%d]",Pointer->Number,Pointer->Total);
        Pointer=Pointer->Next;    /*指向下一个节?/
    }
    printf("\n");
}
/*释放链表*/
void Free_List(Link Head)
{
    Link Pointer;        /*节点声明*/
    while(Head!=NULL)    /*当节点ؓNULLl束循环*/
    {
        Pointer=Head;
        Head=Head->Next;
        free(Pointer);
    }
}
/*建立链表*/
Link Create_List(Link Head)
{
    Link New;        /*节点声明*/
    Link Pointer;    /*节点声明*/
    int i;
    Head=(Link)malloc(sizeof(Node));    /*分配内存*/
    if(Head==NULL)
        printf("Memory allocate Failure!\n");    /*内存分配p|*/
    else
    {
        Head->Number=Data[0][0];        /*定义首节Ҏ据编?/
        Head->Total=Data[1][0];
        Head->Next=NULL;
        Pointer=Head;        /*Pointer指针设ؓ首节?/
        for(i=1;i<Max;i++)
        {
            New=(Link)malloc(sizeof(Node));    /*分配内存*/
            New->Number=Data[0][i];
            New->Total=Data[1][i];
            New->Next=NULL;
            Pointer->Next=New;        /*新节点串连在原列表*/
            Pointer=New;            /*列表节点为新节点*/
        }
    }
    return Head;
}
/*ȝ?/
void main()
{
    Link Head;        /*节点声明*/
    Link New;
    int Key;
    Head=Create_List(Head);    /*建立链表*/
    if(Head!=NULL)
    {
        Print_List(Head);    
        while(1)
        {
            printf("Input 0 to Exit\n");    /*数据输入提示*/
            New=(Link)malloc(sizeof(Node));    /*分配内存*/
            printf("Please input Data number:");
            scanf("%d",&New->Number);
            if(New->Number==0)        /*输入0时结束@?/
                break;
            printf("Please input the data total:");
            scanf("%d",&New->Total);
            printf("Please input the data number for Insert:");
            scanf("%d",&Key);
            Head=Insert_List(Head,New,Key);    /*插入节点*/
            Print_List(Head);                /*输出链表数据*/
        }
        Free_List(Head);        /*释放链表*/
    }
}
*E序q行l果如下Q?br />

------------------------------------------------------------------

4.链表节点的删?/font>
解:
    持箋往下一个节Ҏ找要删除的节点,直到节点内容扑ֈ或节Ҏ针ؓNULL(x不到该节??br />    在删除时Q必记录前一个节点的位置(Back)
    如果该节点不存在Q输Z个节点不存在的提C?br />    如果该节点存在,且是首节点:
        Head=Pointer->Next
        free(Pointer)
    如果该节点存在,但不是首节点(即链表内节点或尾端节?Q则Q?br />        Back->Next=Pointer->Next
        free(Pointer)
*E序代码如下Q?br />#include<stdio.h>
#include<stdlib.h>
#define Max 10
struct List            /*节点l构声明 */
{
    int Number;
    int Total;
    struct List *Next;
};
typedef struct List Node;
typedef Node *Link;
int Data[2][Max]={1,3,5,7,2,4,6,8,9,10,15,35,10,67,25,65,38,70,30,20};
/*删除链表内节?/
Link Delete_List(Link Head,int Key)
{
    Link Pointer;        /*节点声明*/
    Link Back;
    Pointer=Head;        /*Pointer 指针设ؓ首节?/
    while(1)
    {
        if(Pointer->Next==NULL)
        {
            printf("Not Found!\n");
            break;
        }
        if(Head->Number==Key)        /*删除首节?/
        {
            Head=Pointer->Next;
            free(Pointer);
            break;
        }
        Back=Pointer;
        Pointer=Pointer->Next;    /*指向下一个节?/
        if(Pointer->Number==Key)    /*插入在链表中间或*/
        {
            Back->Next=Pointer->Next;
            free(Pointer);
            break;
        }
    }
    return Head;
}
/*输出链表数据*/
void Print_List(Link Head)
{
    Link Pointer;        /*节点声明*/
    Pointer=Head;        /*Pointer指针设ؓ首节?/
    while(Pointer!=NULL)    /*当节点ؓNULLl束循环*/
    {
        printf("[%d,%d]",Pointer->Number,Pointer->Total);
        Pointer=Pointer->Next;    /*指向下一个节?/
    }
    printf("\n");
}
/*释放链表*/
void Free_List(Link Head)
{
    Link Pointer;        /*节点声明*/
    while(Head!=NULL)    /*当节点ؓNULLl束循环*/
    {
        Pointer=Head;
        Head=Head->Next;        /*指向下一个节?/
        free(Pointer);
    }
}
/*建立链表*/
Link Create_List(Link Head)
{
    Link New;        /*节点声明*/
    Link Pointer;
    int i;
    Head=(Link)malloc(sizeof(Node));        /*分本内存*/
    if(Head==NULL)
        printf("Memory allocate Failure!\n");    /*内存分配p|*/
    else
    {
        Head->Number=Data[0][0];        /*定义首节Ҏ据编?/
        Head->Total=Data[1][0];
        Head->Next=NULL;
        Pointer=Head;        /*Pointer指针设ؓ首节?/
        for(i=1;i<Max;i++)
        {
            New=(Link)malloc(sizeof(Node));        /*分配内存*/
            New->Number=Data[0][i];
            New->Total=Data[1][i];
            New->Next=NULL;
            Pointer->Next=New;        /*新节点串连在原列表*/
            Pointer=New;            /*列表节点为新节点*/
        }
    }
    return Head;
}
/*ȝ?/
void main()
{
    Link Head=NULL;        /*节点声明*/
    int Key;
    Head=Create_List(Head);        /*建立链表*/
    if(Head!=NULL)
    {
        Print_List(Head);
        while(1)
        {
            printf("Input 0 to exit\n");    /*数据输入提示*/
            printf("Please input the data number for Delete:");
            scanf("%d",&Key);
            if(Key==0)        /*时结束@?/
                break;
            Head=Delete_List(Head,Key);    /*删除节点*/
            Print_List(Head);              /*输出链表*/
        }
        Free_List(Head);        /*释放链表*/
    }
}

*E序q行l果如下Q?/font>

  


力力力 2006-07-17 22:56 发表评论
]]>
链表2http://www.tkk7.com/faintbear/archive/2006/07/17/58656.html力力力力力力Mon, 17 Jul 2006 14:54:00 GMThttp://www.tkk7.com/faintbear/archive/2006/07/17/58656.htmlhttp://www.tkk7.com/faintbear/comments/58656.htmlhttp://www.tkk7.com/faintbear/archive/2006/07/17/58656.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/58656.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/58656.html2.设计一个查N表中的数据的E序
解:
    单链表中的数据查找,只能采用U性查找法往下一个节Ҏ找。采用线性查找法查找链表中的数据时与数组不同的是Q原来数l是用递增数组索引来查找数据,在链表中是往下一个节Ҏ找?br />E序代码如下Q?br />#include<stdio.h>
#include<stdlib.h>
#define Max 10
struct List        /*l节点结构声?/   
{
    int Number;
    int Total;
    struct List *Next;
};
typedef struct List Node;
typedef Node *Link;
int Data[2][Max]=              /*初始化数?/
        {3,9,25,5,7,26,65,80,2,6,1050,3850,1000,5670,2250,9650,2380,
            1700,3000,2000};
int SearchTime=0;        /*查找ơ数*/
/*链表查找*/
int List_Search(int Key,Link Head)
{
    Link Pointer;
    Pointer=Head;        /*Pointer指针设ؓ首节?/
    while(Pointer!=NULL)    /*当节点ؓNULLl束循环*/
    {
        SearchTime++;
        if(Pointer->Number==Key)
        {
            printf("Data Number: %d\n",Pointer->Number);
            printf("Data Total: %d\n",Pointer->Total);
            return 1;
        }
        Pointer=Pointer->Next;      /*指向下一个节?/
    }
    return 0;
}
/*释放链表*/
void Free_List(Link Head)
{
    Link Pointer;        /*节点声明*/
    while(Head!=NULL)    /*当节点ؓNULLl束循环*/
    {
        Pointer=Head;
        Head=Head->Next;    /*指向下一个节?/
        free(Pointer);
    }
}
/*建立链表*/
Link Create_List(Link Head)
{
    Link New;        /*节点声明*/
    Link Pointer;    /*节点声明*/
    int i;
    Head=(Link)malloc(sizeof(Node));    /*分配内存*/
    if(Head==NULL)
        printf("Memory allocate Failure!\n");    /*内存分配p|*/
    else
    {
        Head->Number=Data[0][0];        /*定义首节Ҏ据编?/
        Head->Total=Data[1][0];
        Head->Next=NULL;
        Pointer=Head;            /*Pointer指针设ؓ首节?/
        for(i=1;i<Max;i++)
        {
            New=(Link)malloc(sizeof(Node));    /*分配内存*/
            New->Number=Data[0][i];
            New->Total=Data[1][i];
            New->Next=NULL;
            Pointer->Next=New;        /*新节点串连在原列表*/
            Pointer=New;              /*列表节点为新节点*/
        }
    }
    return Head;
}
/*ȝ?/
void main()
{
    Link Head=NULL;        /*节点声明*/
    int Num;          /*Ʋ查找数据编?/
    Head=Create_List(Head);    /*建立链表*/
    if(Head!=NULL)
    {
        printf("Please input the data number:");
        scanf("%d",&Num);
        if(List_Search(Num,Head))
            printf("Search Time=%d\n",SearchTime);
        else
            printf("Not Found!\n");
        Free_List(Head);        /*释放链表*/
    }
}
*q行l果如下Q?br />



力力力 2006-07-17 22:54 发表评论
]]>
链表http://www.tkk7.com/faintbear/archive/2006/07/17/58655.html力力力力力力Mon, 17 Jul 2006 14:53:00 GMThttp://www.tkk7.com/faintbear/archive/2006/07/17/58655.htmlhttp://www.tkk7.com/faintbear/comments/58655.htmlhttp://www.tkk7.com/faintbear/archive/2006/07/17/58655.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/58655.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/58655.html 链表的运?01)

发表日期Q?003q??日    作者:C语言之家搜集整理  已经?901位读者读q此?/td>

1.设计一个程序将输入的数据徏立成链表、输出链表数据ƈ在程序结束后释放?br />2.设计一个查N表中的数据的E序



1.设计一个程序将输入的数据徏立成链表、输出链表数据ƈ在程序结束后释放?br />解:
    1)链表的徏立:先声明一个首节点HeadQƈHead->Next设ؓNULL。每输入一个数据就声明一个新节点NewQ把New->Next设ؓNULLQƈ且链接到之前列表的尾端?br />    2)链表数据的输出:先将Pointer节点的指针指向第一个节点,Pointer节点(即第一个节?的数据输出。然后再Pointer节点的指针指向Pointer指针的的指针(即下一节点)Q将pointer节点(即第一节点)的数据输出。重复执行此步聚直到Pointer指针指向NULL为止?br />    3)链表的释放:先将Pointer节点的指针指向第一个节点,然后再将首节点设为首节点的指?即下一节点)。将Pointer节点(即第一节点)释放。重复执行此步聚直到首节点的指针指向NULL为止?br />E序代码如下Q?br />#include<stdlib.h>
#include<stdio.h>
#define Max 10
struct List             /*节点l构声明*/
{
    int Number;
    char Name[Max];
    struct List *Next;
};
typedef struct List Node;
typedef Node *Link;
/*释放链表*/
void Free_List(Link Head)
{
    Link Pointer;      /*节点声明*/
    while(Head!=NULL)      /*当节点ؓNULLQ结束@?/
    {
        Pointer=Head;
        Head=Head->Next;   /*指向下一个节?/
        free(Pointer);
    }
}
/*输出链表*/
void Print_List(Link Head)
{
    Link Pointer;          /*节点声明*/
    Pointer=Head;          /*Pointer指针设ؓ首节?/
    while(Pointer!=NULL)   /*当节点ؓNULLl束循环*/
    {
        printf("##Input Data##\n");
        printf("Data Number: %d\n",Pointer->Number);
        printf("Data Name: %s\n",Pointer->Name);
        Pointer=Pointer->Next;     /*指向下一个节?/
    }
}
/*建立链表*/
Link Create_List(Link Head)
{
    int DataNum;         /*数据~号*/
    char DataName[Max];        /*数据名称*/
    Link New;            /*节点声明*/
    Link Pointer;        /*节点声明*/
    int i;
    Head=(Link)malloc(sizeof(Node));     /*分配内存*/
    if(Head==NULL)
        printf("Memory allocate Failure!\n");    /*内存分配夫|*/
    else
    {
        DataNum=1;      /*初始数据~号*/
        printf("Please input the data name:");
        scanf("%s",DataName);
        Head->Number=DataNum;     /*定义首节Ҏ据编?/
        for(i=0;i<=Max;i++)
            Head->Name[i]=DataName[i];
        Head->Next=NULL;
        Pointer=Head;          /*Pointer指针设ؓ首节?/
        while(1)
        {
            DataNum++;         /*数据~号递增*/
            New=(Link)malloc(sizeof(Node));     /*分配内存*/
            printf("Please input the data Name:");
            scanf("%s",DataName);
            if(DataName[0]=='0')    /*输入0则结?/
                break;
            New->Number=DataNum;
            for(i=0;i<Max;i++)
            {
                New->Name[i]=DataName[i];
            }
            New->Next=NULL;
            Pointer->Next=New;     /*新节点串连在原列表*/
            Pointer=New;         /*列表节点为新节点*/
        }
    }
    return Head;
}
/*ȝ?/
void main()
{
    Link Head;       /*节点声明*/
    Head=Create_List(Head);     /*调用建立链表函数*/
    if(Head!=NULL)
    {
        Print_List(Head);      /*调用输出链表数据函数*/
        Free_List(Head);       /*调用释放链表函数*/
    }
}

q行l果如下Q?br />



力力力 2006-07-17 22:53 发表评论
]]>
Z?char** 不能自动转化?const char** (?http://www.tkk7.com/faintbear/archive/2005/12/29/25963.html力力力力力力Thu, 29 Dec 2005 15:00:00 GMThttp://www.tkk7.com/faintbear/archive/2005/12/29/25963.htmlhttp://www.tkk7.com/faintbear/comments/25963.htmlhttp://www.tkk7.com/faintbear/archive/2005/12/29/25963.html#Feedback1http://www.tkk7.com/faintbear/comments/commentRss/25963.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/25963.htmlZ?char** 不能自动转化?const char**

一ơ偶然的情况下我发现以下代码竟然无法被编译通过Q如果你的编译器Q比如VC6或VC2003Q允许它~译通过Q我想你首先应该换个~译器,比如GCC或VC2005Q:
void foo( const char* [] ) { }
int main( void )
{
    char* s[2];
    foo( s );
}

化成更一般的形式是:
char** p1 = 0;
const char** p2 = p1;

错误是:invalid conversion from `char**' to `const char**'.

lostpencil更加仔细Q用C~译器给出的是一个警告:
initialization from incompatible pointer type.

随后hphol出了合理的解释Q同?A target=_blank>comp.lang.c++.moderated上的Ulrich Eckhardt也用代码q行了说明?/P>

用代码来说明最直观了:
const char* s = "abc";
int main( void )
{
    char* p0 = 0;
    char** p1 = &p0;
    const char** p2 = p1;
// 先假设这一句是合法?( 试Ӟ可以先强制类型{化一?)
    *p2 = s;
    *p0 = 'A';
// 通过p0在修改不应该被修改的sQ这昄和const相违背,其运行结果不可知?BR>}




看了 **?惛_?BR>tekyDec 29, 2005 -  Show original item

看完?明白**讲的Z么char** 不能自动转化?const char**,(原文)但对我媄响最q是下面的?

==================================================================
char *p="abc" 能不能编译通过要看你用的~译器。鉴于大量遗留代码的存在Q大部分~译器允许其通过Q或者给个警告。当ӞE序员自己必M证绝不去修改其倹{?

E序员不应该在代码中出现*p='A'q样的语句。这是当初约定好了的Q编译器允许char *p="abc"通过Q而程序员保证不去修改它?
b. *p='A'~译时应该允讔R过Q因为单p条语句而言Q它完全合法?
c. q行?p='A'能不能通过要看实际的运行环境,包括你用的操作pȝ、编译器、编译器选项 {等Q一句话Q其q行l果׃得你Q且不应该由你去兛_Q因U行为本w已l违反约定了?
==================================================================

工作关系?用CString 和string用的太多?很少q样定义字符?char *p=“abcde“了
匝一?q不适应,:(,渐渐的回x惌v一些来(?q是太生?赶快写下?以后别忘?

q样定义的字W串char *p=“abcde?; char *p1=?23445667?

正如上面提到的是不能?*p='A',q行的时候会出错,同样,strcpy(p,p1)也会出错?

"abcde"字符串可以看做是个常量字W串?是不能被修改?

但如?char p[]=“abcde?q样定义,没有问?你可以修?p='A',只要不越界就ok.

q且发现q样两种定义
char *p=“abcde?/FONT>

char p[]=“abcde?

在运行的时?p指向的地址也不是一L,可见char *p=“abcde“还是有Ҏ的处?:),具体怎么处理׃知道?高手h?)


随着试,又发C问题,可能是个老问题了?


int main(int argc, char* argv[])
{
 int t[10];
 char p1[7]="123456";
 const char *p2="1234567890123213123";
 
 int len(0);
 
  //*p1='C';  err

 len=strlen(p1);
 printf("%d\n",len);
 
 strcpy(p1,p2);   ///??????????
 
 printf("%s\n",p1);
 
 len=strlen(p1);
 
 printf("%d\n",len);
 return 0;
}

我定义的?个字W数l? 但用strcpy把p2拷到p1?p1是放不下?但程序却正常执行,warning ,err都没?q行也正?


输出

6
1234567890123213123
19

应该是用内存越界了??怎么会正常运行呢?

N对于内存界的?q气好才崩溃表现出来,q气不好正常运??


 



力力力 2005-12-29 23:00 发表评论
]]>
学习~strtokhttp://www.tkk7.com/faintbear/archive/2005/11/05/18323.html力力力力力力Sat, 05 Nov 2005 12:39:00 GMThttp://www.tkk7.com/faintbear/archive/2005/11/05/18323.htmlhttp://www.tkk7.com/faintbear/comments/18323.htmlhttp://www.tkk7.com/faintbear/archive/2005/11/05/18323.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/18323.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/18323.htmlextern char *GetField(char *ibuf,char *ChrSet,int num)
{
int  i=0;
static char tmp[256];
char *pt,*pstr;

pstr=(char *)malloc(strlen(ibuf)+1);
strcpy(pstr,ibuf);
for(i=0,pt=strtok(pstr,ChrSet);pt!=NULL;i++,pt=strtok(NULL,ChrSet))
{
 if(i>=num||pt==NULL) break;
}
strcpy(tmp,pt);
free(pstr);
return(tmp);
}

例:
char *pt;
char buf[]="324324324|234324324|23432432432|23432432432|33333";
pt=GetField(buf,"|",3);





static void addArray(char *buff, char array[][MQ_ARRAY_LEN])
{
  

    int index = 0;
    int i=0;

    char *pt,*pstr;
    char * ChrSet= ",";

    pstr=(char *)malloc(strlen(buff)+1);
    pstr = strcpy( pstr, buff);
    for(i=0,pt=strtok(pstr,ChrSet); pt!=NULL; i++, pt=strtok(NULL,ChrSet))
    {
        if(pt==NULL) break;
      //  printf("dealing ... %s\n", pt);
        strcpy(array[index],pt);
        index++;
    }
    free(pstr);

}



力力力 2005-11-05 20:39 发表评论
]]>
C语言嵌入式系l编E修g?性能优化http://www.tkk7.com/faintbear/archive/2005/09/21/13658.html力力力力力力Wed, 21 Sep 2005 09:35:00 GMThttp://www.tkk7.com/faintbear/archive/2005/09/21/13658.htmlhttp://www.tkk7.com/faintbear/comments/13658.htmlhttp://www.tkk7.com/faintbear/archive/2005/09/21/13658.html#Feedback3http://www.tkk7.com/faintbear/comments/commentRss/13658.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/13658.htmlC语言嵌入式系l编E修g?性能优化

作?宋宝?  更新日期:2005-07-22

使用宏定?/b>

  在C语言中,宏是产生内嵌代码的唯一Ҏ。对于嵌入式pȝ而言Qؓ了能辑ֈ性能要求Q宏是一U很好的代替函数的方法?br>
  写一?标准"宏MIN Q这个宏输入两个参数q返回较的一个:

  错误做法Q?br>
#define MIN(A,B)  ( A <= B ? A : B )

  正确做法Q?br>
#define MIN(A,B) Q(AQ?lt;= (B) ? (A) : (B) )

  对于宏,我们需要知道三点:

  (1)宏定??函数Q?br>
  (2)宏定义不是函敎ͼ因而需要括上所?参数"Q?br>
  (3)宏定义可能生副作用?br>
  下面的代码:

least = MIN(*p++, b);

  被替换为:

( (*p++) <= (b) ?(*p++):(b) )

  发生的事情无法预料?

  因而不要给宏定义传入有副作用的"参数"?br>
  使用寄存器变?/b>

   当对一个变量频J被dӞ需要反复访问内存,从而花费大量的存取旉。ؓ此,C语言提供了一U变量,卛_存器变量。这U变量存攑֜CPU的寄存器中, 使用Ӟ不需要访问内存,而直接从寄存器中dQ从而提高效率。寄存器变量的说明符是register。对于@环次数较多的循环控制变量及@环体内反复 用的变量均可定义为寄存器变量Q而@环计数是应用寄存器变量的最好候选者?br>
  (1) 只有局部自动变量和形参才可以定义ؓ寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量Q包括:模块间全局变量、模块内全局变量、局部static变量Q?br>
  (2) register是一?"型关键字Q意指程序徏议该变量攑֜寄存器中Q但最l该变量可能因ؓ条g不满_ƈ未成为寄存器变量Q而是被放在了存储器中Q但~译器中q不报错Q在C++语言中有另一?"型关键字QinlineQ?br>
  下面是一个采用寄存器变量的例子:

/* ?+2+3+?+n的?*/
WORD Addition(BYTE n)
{
 register i,s=0;
 for(i=1;i<=n;i++)
 {
  s=s+i;
 }
 return s;
}

  本程序@环nơ,i和s都被频繁使用Q因此可定义为寄存器变量?br>
  内嵌汇编

  E序中对旉要求苛刻的部分可以用内嵌汇编来重写,以带来速度上的显著提高。但是,开发和试汇编代码是一件辛苦的工作Q它花Ҏ长的旉Q因而要慎重选择要用汇编的部分?br>
  在程序中Q存在一?0-20原则Q即20%的程序消耗了80%的运行时_因而我们要改进效率Q最主要是考虑改进?0%的代码?br>
  嵌入式CE序中主要用在U汇~,卛_CE序中直接插入_asm{ }内嵌汇编语句Q?br>
/* 把两个输入参数的值相加,l果存放到另外一个全局变量?*/
int result;
void Add(long a, long *b)
{
 _asm
 {
  MOV AX, a
  MOV BX, b
  ADD AX, [BX]
  MOV result, AX
 }
}

  利用gҎ?/b>

  首先要明白CPU对各U存储器的访问速度Q基本上是:

CPU内部RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM

  对于E序代码Q已l被烧录在FLASH或ROM中,我们可以让CPU直接从其中读取代码执行,但通常q不是一个好办法Q我们最好在pȝ启动后将FLASH或ROM中的目标代码拯入RAM中后再执行以提高取指令速度Q?br>
  对于UART{设备,其内部有一定容量的接收BUFFERQ我们应量在BUFFER被占满后再向CPU提出中断。例如计机l端在向目标机通过RS-232传递数据时Q不宜设|UART只接收到一个BYTE向CPU提中断,从而无谓浪费中断处理时_

   如果Ҏ讑֤能采取DMA方式dQ就采用DMAdQDMAd方式在读取目标中包含的存储信息较大时效率较高Q其数据传输的基本单位是块,而所传输 的数据是从设备直接送入内存的(或者相反)。DMA方式较之中断驱动方式Q减了CPU 对外讄q预Q进一步提高了CPU与外讄q行操作E度?br>
  zȝ位操?/b>

  使用C语言的位操作可以减少除法和取模的q算。在计算机程序中数据的位是可以操作的最数据单位,理论上可以用"位运?来完成所有的q算和操作,因而,灉|的位操作可以有效地提高程序运行的效率。D例如下:

/* Ҏ1 */
int i,j;
i = 879 / 16;
j = 562 % 32;
/* Ҏ2 */
int i,j;
i = 879 >> 4;
j = 562 - (562 >> 5 << 5);

  对于?的指数次方ؓ"*"?/"?%"因子的数学运,转化为移位运?<< >>"通常可以提高法效率。因Z除运指令周期通常比移位运大?br>
   C语言位运除了可以提高运效率外Q在嵌入式系l的~程中,它的另一个最典型的应用,而且十分q泛地正在被使用着的是位间的与Q?amp;Q、或 Q|Q、非Q~Q操作,q跟嵌入式系l的~程特点有很大关pR我们通常要对g寄存器进行位讄Q譬如,我们通过AM186ER?0186处理器的? 断屏蔽控制寄存器的第?位设|ؓ0Q开中断2Q,最通用的做法是Q?br>
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp &~INT_I2_MASK);

  而将该位讄?的做法是Q?br>
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp | INT_I2_MASK);

  判断该位是否?的做法是Q?br>
#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
if(wTemp & INT_I2_MASK)
{
?/* 该位? */
}

  上述Ҏ在嵌入式pȝ的编E中是非常常见的Q我们需要牢固掌握?

  ȝ

  在性能优化斚w永远注意80-20准备Q不要优化程序中开销不大的那80%Q这是劳而无功的?br>
   宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法,但宏在本质上不是函数Q因而要防止宏展开后出C可预料的l果Q对宏的定义? 使用要慎而处之。很遗憾Q标准C至今没有包括C++中inline函数的功能,inline函数兼具无调用开销和安全的优点?br>
  使用寄存器变量、内嵌汇~和zȝ位操作也是提高程序效率的有效Ҏ?br>
  除了~程上的技巧外Qؓ提高pȝ的运行效率,我们通常也需要最大可能地利用各种g讑֤自n的特Ҏ减小其运转开销Q例如减中断次数、利用DMA传输方式{?img src ="http://www.tkk7.com/faintbear/aggbug/13658.html" width = "1" height = "1" />

力力力 2005-09-21 17:35 发表评论
]]>
C语言嵌入式系l编E修g?键盘操作http://www.tkk7.com/faintbear/archive/2005/09/21/13657.html力力力力力力Wed, 21 Sep 2005 09:34:00 GMThttp://www.tkk7.com/faintbear/archive/2005/09/21/13657.htmlhttp://www.tkk7.com/faintbear/comments/13657.htmlhttp://www.tkk7.com/faintbear/archive/2005/09/21/13657.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/13657.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/13657.htmlC语言嵌入式系l编E修g?键盘操作

作?宋宝?  更新日期:2005-07-22

处理功能?/b>

  功能键的问题在于Q用L面ƈ非固定的Q用户功能键的选择屏幕画面处于不同的显C状态下。例如,ȝ面如?Q?br>

? ȝ?/div>

  当用户在讄XX上按下Enter键之后,画面切换到了设|XX的界面,如图2Q?br>

? 切换到设|XX画面

  E序如何判断用户处于哪一画面Qƈ在该画面的程序状态下调用对应的功能键处理函数Q而且保证良好的结构,是一个值得思考的问题?br>
   让我们来看看WIN32~程中用到的"H口"概念Q当消息QmessageQ被发送给不同H口的时候,该窗口的消息处理函数Q是一个callback? 敎ͼ最l被调用Q而在该窗口的消息处理函数中,又根据消息的cd调用了该H口中的对应处理函数。通过q种方式QWIN32有效的组l了不同的窗口,q处? 不同H口情况下的消息?br>
  我们从中学习到的是Q?br>
  Q?Q将不同的画面类比ؓWIN32中不同的H口Q将H口中的各种元素Q菜单、按钮等Q包含在H口之中Q?br>
  Q?Q给各个画面提供一个功能键"消息"处理函数Q该函数接收按键信息为参敎ͼ

  Q?Q在各画面的功能?消息"处理函数中,判断按键cd和当前焦点元素,q调用对应元素的按键处理函数?br>
/* 窗口元素、消息处理函数封装在H口?*/
struct windows
{
 BYTE currentFocus;
 ELEMENT element[ELEMENT_NUM];
 void (*messageFun) (BYTE keyValue);
 ?br>};
/* 消息处理函数 */
void messageFunction(BYTE keyValue)
{
 BYTE i = 0;
 /* 获得焦点元素 */
 while ( (element [i].ID!= currentFocus)&& (i < ELEMENT_NUM) )
 {
  i++;
 }
 /* "消息映射" */
 if(i < ELEMENT_NUM)
 {
  switch(keyValue)
  {
   case OK:
    element[i].OnOk();
    break;
   ?br>  }
 }
}

  在窗口的消息处理函数中调用相应元素按键函数的q程cM?消息映射"Q这是我们从WIN32~程中学习到的。编E到了一个境界,很多东西都是盔R的了。其它地方的思想可以拿过来ؓ我所用,是ؓ~程中的"拿来M"?br>
  在这个例子中Q如果我们还想玩得更大一点,我们可以借鉴MFC中处理MESSAGE_MAP的方法,我们也可以学习MFC定义几个_֦的宏来实?消息映射"?br>处理数字?/b>

  用户输入数字时是一位一位输入的Q每一位的输入都对应着屏幕上的一个显CZ|(x坐标Qy坐标Q。此外,E序q需要记录该位置输入的|所以有效组l用h字输入的最x式是定义一个结构体Q将坐标和数值捆l在一P

/* 用户数字输入l构?*/
typedef struct tagInputNum
{
 BYTE byNum; /* 接收用户输入赋?*/
 BYTE xPos; /* 数字输入在屏q上的显CZ|x坐标 */
 BYTE yPos; /* 数字输入在屏q上的显CZ|y坐标 */
}InputNum, *LPInputNum;

  那么接收用户输入可以定义一个结构体数组Q用数组中的各位l成一个完整的数字Q?br>
InputNum inputElement[NUM_LENGTH]; /* 接收用户数字输入的数l?*/
/* 数字按键处理函数 */
extern void onNumKey(BYTE num)
{
if(num==0|| num==1) /* 只接收二q制输入 */
{
 /* 在屏q上昄用户输入 */
 DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, "%1d", num);
 /* 输入赋值给数组元素 */
 inputElement[currentElementInputPlace].byNum = num;
 /* 焦点及光标右U?*/
 moveToRight();
}
}

  数字每一位输入的坐标和输入值捆l后Q在数字键处理函C可以较有结构的l织E序QɽE序昑־很紧凑?br>
  整理用户输入

  l箋W?节的例子Q在W?节的onNumKey函数中,只是获取了数字的每一位,因而我们需要将其{化ؓ有效数据Q譬如要转化为有效的XXX数据Q其Ҏ是:

/* ?q制数据位{化ؓ有效数据QXXX */
void convertToXXX()
{
 BYTE i;
 XXX = 0;
 for (i = 0; i < NUM_LENGTH; i++)
 {
  XXX += inputElement[i].byNum*power(2, NUM_LENGTH - i - 1);
 }
}

  反之Q我们也可能需要在屏幕上显C那些有效的数据位,因ؓ我们也需要能够反向{化:

/* 从有效数据{化ؓ2q制数据位:XXX */
void convertFromXXX()
{
 BYTE i;
 XXX = 0;
 for (i = 0; i < NUM_LENGTH; i++)
 {
  inputElement[i].byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2;
 }
}

  当然在上面的例子中,因ؓ数据?q制的,用power函数不是很好的选择Q直接用"<< >>"UM操作效率更高Q我们仅是ؓ了说明问题的方便。试惻I如果用户输入是十q制的,power函数或许是唯一的选择了?br>
  ȝ

  本篇l出了键盘操作所涉及的各个方面:功能键处理、数字键处理及用戯入整理,基本上提供了一个全套的按键处理Ҏ。对于功能键处理ҎQ将LCD屏幕与WindowsH口q行cLQ提Z较新颖地解决屏幕、键盘繁杂交互问题的Ҏ?br>
   计算机学的许多知识都h盔R性,因而,不断q赶旉技术而忽略基本功的做法是徒劳无意的。我们最多需?_N?三种语言Q精通,一个在如今的求职简? 里泛滥成灄词语Q,最x档是汇编、C、C++Q或JAVAQ,很显Ӟ如果?_N?了这三种语言Q其它语a你应该是可以很快"熟悉"的,否则你就? ?_N?它们.

力力力 2005-09-21 17:34 发表评论
]]>
C语言嵌入式系l编E修g?屏幕操作http://www.tkk7.com/faintbear/archive/2005/09/21/13656.html力力力力力力Wed, 21 Sep 2005 09:32:00 GMThttp://www.tkk7.com/faintbear/archive/2005/09/21/13656.htmlhttp://www.tkk7.com/faintbear/comments/13656.htmlhttp://www.tkk7.com/faintbear/archive/2005/09/21/13656.html#Feedback1http://www.tkk7.com/faintbear/comments/commentRss/13656.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/13656.htmlC语言嵌入式系l编E修g?屏幕操作

作?宋宝?  更新日期:2005-07-22

汉字处理

  现在要解决的问题是,嵌入式系l中l常要用的q是完整的汉字库,往往只是需要提供数量有限的汉字供必要的 昄功能。例如,一个微波炉的LCD上没有必要提供显C?电子邮g"的功能;一个提供汉字显C功能的I的LCD上不需要显CZ?短消?Q诸如此cR? 但是一部手机、小灵通则通常需要包括较完整的汉字库?br>
  如果包括的汉字库较完_那么Q由内码? 出汉字字模在库中的偏移是十分简单的Q汉字库是按照区位的序排列的,前一个字节ؓ该汉字的区号Q后一个字节ؓ该字的位受每一个区记录94个汉字,? 号则字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数l是?为开始而区号位h?为开 始的。只需乘上一个汉字字模占用的字节数即可,卻I(94*(区号-1)+位号-1)*一个汉字字模占用字节数Q以16*16炚w字库ZQ计公式则为:(94*(区号-1)+(位号-1))*32。汉字库中从该位|v?2字节信息记录了该字的字模信息?br>
  对于包含较完整汉字库的系l而言Q我们可以以上述规则计算字模的位|。但是如果仅仅是提供量汉字呢?譬如几十臛_百个Q最好的做法是:

  定义宏:

# define EX_FONT_CHAR(value)
# define EX_FONT_UNICODE_VAL(value) (value),
# define EX_FONT_ANSI_VAL(value) (value),

  定义l构体:

typedef struct _wide_unicode_font16x16
{
 WORD value; /* 内码 */
 BYTE data[32]; /* 字模炚w */
}Unicode;
#define CHINESE_CHAR_NUM ?/* 汉字数量 */

  字模的存储用数组Q?br>
Unicode chinese[CHINESE_CHAR_NUM] =
{
{
EX_FONT_CHAR("?)
EX_FONT_UNICODE_VAL(0x4e1a)
{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}
},
{
EX_FONT_CHAR("?)
EX_FONT_UNICODE_VAL(0x4e2d)
{0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,
0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}
},
{
EX_FONT_CHAR("?)
EX_FONT_UNICODE_VAL(0x4e91)
{0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00,

0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}
},
{
EX_FONT_CHAR("?)
EX_FONT_UNICODE_VAL(0x4ef6)
{0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe,

0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}
}
}

  要显C特定汉字的时候,只需要从数组中查扑ֆ码与要求汉字内码相同的即可获得字模。如果前面的汉字在数l中以内码大顺序排列,那么可以以二分查找法更高效的查找到汉字的字模?br>
  q是一U很有效的组l小汉字库的ҎQ它可以保证E序有很好的l构?br>
  pȝ旉昄

   从NVRAM中可以读取系l的旉Q系l一般借助NVRAM产生的秒中断每秒d一ơ当前时间ƈ在LCD上显C。关于时间的昄Q有一个效率问题。因? 旉有其Ҏ性,那就?0U才有一ơ分钟的变化Q?0分钟才有一ơ小时变化,如果我们每次都将d的时间在屏幕上完全重新刷Cơ,则浪费了大量的系l? 旉?br>
  一个较好的办法是我们在旉昄函数中以静态变量分别存储小时、分钟、秒Q只有在其内容发生变化的时候才更新其显C?br>
extern void DisplayTime(?
{
 static BYTE byHour,byMinute,bySecond;
 BYTE byNewHour, byNewMinute, byNewSecond;
 byNewHour = GetSysHour();
 byNewMinute = GetSysMinute();
 byNewSecond = GetSysSecond();
 
 if(byNewHour!= byHour)
 {
  ?/* 昄时 */
  byHour = byNewHour;
 }
 if(byNewMinute!= byMinute)
 {
  ?/* 昄分钟 */
  byMinute = byNewMinute;
 }
 if(byNewSecond!= bySecond)
 {
  ?/* 昄U钟 */
  bySecond = byNewSecond;
 }
}

  q个例子也可以顺便作为C语言中static关键字强大威力的证明。当Ӟ在C++语言里,statich了更加强大的威力Q它使得某些数据和函数脱?对象"而成?c?的一部分Q正是它的这一特点Q成׃软g的无CU设计?br>动画昄

  动画是无所谓有Q无所谓无的,静止的画面走的\多了Q也成了动甅R随着旉的变_在屏q上昄不同的静止画面,x动画之本质。所以,在一个嵌入式pȝ的LCD上欲昄动画Q必d助定时器。没有硬件或软g定时器的世界是无法想像的Q?br>
  Q?Q?没有定时器,一个操作系l将无法q行旉片的轮{Q于是无法进行多d的调度,于是便不再成其ؓ一个多d操作pȝQ?br>
  Q?Q?没有定时器,一个多媒体播放软g无法运作,因ؓ它不知道何时应该切换C一帧画面;

  Q?Q?没有定时器,一个网l协议将无法q{Q因为其无法L何时包传输超时ƈ重传之,无法在特定的旉完成特定的Q务?br>
  因此Q没有定时器意味着没有操作pȝ、没有网l、没有多媒体Q这是怎样的黑暗?所以,合理q灵zd使用各种定时器,是对一个Y件h的最基本需求!

  ?0186Z芯片的嵌入式pȝ中,我们需要借助g定时器的中断来作Y件定时器Q在中断发生后变更画面的昄内容。在旉昄"xx:xx"中让冒号交替有无Q每ơ秒中断发生后,需调用ShowDotQ?br>
void ShowDot()
{
 static BOOL bShowDot = TRUE; /* 再一ơ领略static关键字的威力 */
 if(bShowDot)
 {
  showChar(??xPos,yPos);
 }
 else
 {
  showChar(??xPos,yPos);
 }
 bShowDot = ! bShowDot;
}

  菜单操作

  无数Zؓ之绞脑汁的问题l于出现了,在这一节里Q我们将看到Q在C语言中哪怕用C丁点的面向对象思想QY件结构将会有何等的改观!

  W者曾l是个笨蛋,被菜单搞晕了Q给L一个系l:


? 菜单范例

  要求以键盘上???键切换菜单焦点,当用户在焦点处于某菜单时Q若敲击键盘上的OK、CANCEL键则调用该焦点菜单对应之处理函数。我曄d地这样做着Q?br>
/* 按下OK?*/
void onOkKey()
{
 /* 判断在什么焦点菜单上按下Ok键,调用相应处理函数 */
 Switch(currentFocus)
 {
  case MENU1:
   menu1OnOk();
   break;
  case MENU2:
   menu2OnOk();
   break;
  ?br> }
}
/* 按下Cancel?*/
void onCancelKey()
{
 /* 判断在什么焦点菜单上按下Cancel键,调用相应处理函数 */
 Switch(currentFocus)
 {
  case MENU1:
   menu1OnCancel();
   break;
  case MENU2:
   menu2OnCancel();
   break;
  ?br> }
}

  l于有一天,我这样做了:

/* 菜单的属性和操作"装"在一?*/
typedef struct tagSysMenu
{
 char *text; /* 菜单的文?*/
 BYTE xPos; /* 菜单在LCD上的x坐标 */
 BYTE yPos; /* 菜单在LCD上的y坐标 */
 void (*onOkFun)(); /* 在该菜单上按下ok键的处理函数指针 */
 void (*onCancelFun)(); /* 在该菜单上按下cancel键的处理函数指针 */
}SysMenu, *LPSysMenu;

  当我定义菜单Ӟ只需要这P

static SysMenu menu[MENU_NUM] =
{
 {
  "menu1", 0, 48, menu1OnOk, menu1OnCancel
 }
 ,
 {
  " menu2", 7, 48, menu2OnOk, menu2OnCancel
 }
 ,
 {
  " menu3", 7, 48, menu3OnOk, menu3OnCancel
 }
 ,
 {
  " menu4", 7, 48, menu4OnOk, menu4OnCancel
 }
 ?br>};

  OK键和CANCEL键的处理变成Q?br>
/* 按下OK?*/
void onOkKey()
{
 menu[currentFocusMenu].onOkFun();
}
/* 按下Cancel?*/
void onCancelKey()
{
 menu[currentFocusMenu].onCancelFun();
}

  E序被大大简化了Q也开始具有很好的可扩展性!我们仅仅利用了面向对象中的封装思想Q就让程序结构清晎ͼ其结果是几乎可以在无需修改E序的情况下在系l中d更多的菜单,而系l的按键处理函数保持不变?br>
  面向对象Q真了Q?br>模拟MessageBox函数

  MessageBox函数Q这个Windows~程中的猛料Q不知道是多入门者第 一ơ用到的函数。还记得我们W一ơ在Windows中利用MessageBox输出 "Hello,World!"对话框时新奇的感觉吗Q无法统计,q个世界上究竟有多少E序员学习Windows~程是从MessageBox ("Hello,World!",?开始的。在我本U的学校Q广泛流传着一个词汇,叫做"’Hello,World’E序?Q意指入门E序员,? g"’Hello,World’"q个说法更搞W而Ş象?br>
  
? l典的Hello,World!

  ?l出了两U永恒经典的Hello,World对话框,一U只h"定"Q一U则包含"定"?取消"。是的,MessageBox的确有,而且也应该有两类Q这完全是由特定的应用需求决定的?br>
  嵌入式系l中没有l我们提供MessageBoxQ但是鉴于其功能强大Q我们需要模拟之Q一个模拟的MessageBox函数为:

/******************************************
/* 函数名称: MessageBox
/* 功能说明: 弹出式对话框,昄提醒用户的信?br>/* 参数说明: lpStr --- 提醒用户的字W串输出信息
/* TYPE --- 输出格式(ID_OK = 0, ID_OKCANCEL = 1)
/* q回? q回对话框接收的键?只有两种 KEY_OK, KEY_CANCEL
/******************************************
typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE;
extern BYTE MessageBox(LPBYTE lpStr, BYTE TYPE)
{
 BYTE keyValue = -1;

 ClearScreen(); /* 清除屏幕 */
 DisplayString(xPos,yPos,lpStr,TRUE); /* 昄字符?*/
 /* Ҏ对话框类型决定是否显C确定、取?*/
 switch (TYPE)
 {
  case ID_OK:
   DisplayString(13,yPos+High+1, " 定 ", 0);
   break;
  case ID_OKCANCEL:
   DisplayString(8, yPos+High+1, " 定 ", 0);
   DisplayString(17,yPos+High+1, " 取消 ", 0);
   break;
  default:
   break;
 }
 DrawRect(0, 0, 239, yPos+High+16+4); /* l制外框 */
 /* MessageBox是模式对话框Q阻塞运行,{待按键 */
 while( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) )
 {
  keyValue = getSysKey();
 }
 /* q回按键cd */
 if(keyValue== KEY_OK)
 {
  return ID_OK;
 }
 else
 {
  return ID_CANCEL;
 }
}

  上述函数与我们^素在VC++{中使用的MessageBox是何{的似啊?实现q个函数Q你会看到它在嵌入式pȝ中的妙用是无IL?br>
  ȝ

  本篇是本pd文章中技巧性最q一,它提供了嵌入式系l屏q显C方面一些很巧妙的处理方法,灉|使用它们Q我们将不再被LCD上凌׃堪的昄内容所困扰?br>
  屏幕乃嵌入式pȝ生存之重要辅助,面目可憎之显C将另用户逃之夭夭。屏q编E若处理不好Q将是Y件中最不系l、最混ؕ的部分,W者曾深受其害?img src ="http://www.tkk7.com/faintbear/aggbug/13656.html" width = "1" height = "1" />

力力力 2005-09-21 17:32 发表评论
]]>
C语言嵌入式系l编E修g?内存操作http://www.tkk7.com/faintbear/archive/2005/09/21/13655.html力力力力力力Wed, 21 Sep 2005 09:30:00 GMThttp://www.tkk7.com/faintbear/archive/2005/09/21/13655.htmlhttp://www.tkk7.com/faintbear/comments/13655.htmlhttp://www.tkk7.com/faintbear/archive/2005/09/21/13655.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/13655.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/13655.htmlC语言嵌入式系l编E修g?内存操作

作?宋宝?  更新日期:2005-07-22

数据指针

  在嵌入式pȝ的编E中Q常常要求在特定的内存单元读写内容,汇编有对应的MOV指oQ而除C/C++以外的其 它编E语a基本没有直接讉Kl对地址的能力。在嵌入式系l的实际调试中,多借助C语言指针所h的对l对地址单元内容的读写能力。以指针直接操作内存多发 生在如下几种情况Q?br>
  (1) 某I/O芯片被定位在CPU的存储空间而非I/OI间Q而且寄存器对应于某特定地址Q?br>
  (2) 两个CPU之间以双端口RAM通信QCPU需要在双端口RAM的特定单元(UCؓmail boxQ书写内容以在对方CPU产生中断Q?br>
  (3) d在ROM或FLASH的特定单元所烧录的汉字和英文字模?br>
  譬如Q?

unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;

  以上E序的意义ؓ在绝对地址0xF0000+0xFF00(80186使用16位段地址?6位偏Ud址)写入11?br>
  在用绝对地址指针Ӟ要注意指针自增自减操作的l果取决于指针指向的数据cd。上例中p++后的l果是p= 0xF000FF01Q若p指向intQ即Q?br>
int *p = (int *)0xF000FF00;

  p++(?+p)的结果等同于Qp = p+sizeof(int)Q而p-(?p)的结果是p = p-sizeof(int)?br>
  同理Q若执行Q?br>
long int *p = (long int *)0xF000FF00;

  则p++(?+p)的结果等同于Qp = p+sizeof(long int) Q而p-(?p)的结果是p = p-sizeof(long int)?br>
  CQCPU以字节ؓ单位~址Q而C语言指针以指向的数据cd长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的?br>
  函数指针

  首先要理解以下三个问题:

  Q?QC语言中函数名直接对应于函数生成的指o代码在内存中的地址Q因此函数名可以直接赋给指向函数的指针;

  Q?Q调用函数实际上{同?调{指oQ参C递处理+回归位置入栈"Q本质上最核心的操作是函数生成的目标代码的首地址赋给CPU的PC寄存器;

  Q?Q因为函数调用的本质是蟩转到某一个地址单元的codeL行,所以可?调用"一个根本就不存在的函数实体Q晕Q请往下看Q?

  hZ可以获得的Q何一本大学《微型计机原理》教材,书中讲到Q?86 CPU启动后蟩转至l对地址0xFFFF0Q对应C语言指针?xF000FFF0Q?xF000为段地址Q?xFFF0为段内偏U)执行Q请看下面的代码Q?br>
typedef void (*lpFunction) ( ); /* 定义一个无参数、无q回cd?*/
/* 函数指针cd */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*/
/* CPU启动后所执行W一条指令的位置 */
lpReset(); /* 调用函数 */

  在以上的E序中,我们Ҏ没有看到M一个函数实体,但是我们却执行了q样的函数调用:lpReset()Q它实际上vC"软重?的作用,跌{到CPU启动后第一条要执行的指令的位置?br>
  CQ函数无它,唯指令集合耻I你可以调用一个没有函C的函敎ͼ本质上只是换一个地址开始执行指令!

  数组vs.动态申?/b>

  在嵌入式pȝ中动态内存申请存在比一般系l编E时更严格的要求Q这是因为嵌入式pȝ的内存空间往往是十分有限的Q不l意的内存泄露会很快Dpȝ的崩溃?br>
  所以一定要保证你的malloc和free成对出现Q如果你写出q样的一D늨序:

char * function(void)
{
 char *p;
 p = (char *)malloc(?;
 if(p==NULL)
  ?
  ?/* 一pd针对p的操?*/
 return p;
}

  在某处调用function()Q用完function中动态申L内存后将其freeQ如下:

char *q = function();
?br>free(q);

  上述代码明显是不合理的,因ؓq反了malloc和free成对出现的原则,?谁申Pq谁释?原则。不满q个原则Q会D代码的耦合度增大,因ؓ用户在调用function函数旉要知道其内部l节Q?br>
  正确的做法是在调用处甌内存Qƈ传入function函数Q如下:

char *p=malloc(?;
if(p==NULL)
?
function(p);
?br>free(p);
p=NULL;

  而函数function则接收参数pQ如下:

void function(char *p)
{
 ?/* 一pd针对p的操?*/
}

  基本上,动态申请内存方式可以用较大的数l替换。对于编E新手,W者推荐你量采用数组Q嵌入式pȝ可以以博大的胸襟接收瑕疵Q而无?L"错误。毕竟,以最W的方式苦练功的郭靖胜q机明却范政治错误走反革命道路的杨康?br>
  l出原则Q?br>
  Q?Q尽可能的选用数组Q数l不能越界访问(真理过一步就是谬误,数组过界限光荣地成全了一个؜q嵌入式系l)Q?br>
  Q?Q如果用动态申P则申请后一定要判断是否甌成功了,q且malloc和free应成对出玎ͼ
关键字const

  const意味着"只读"。区别如下代码的功能非常重要Q也是老生长叹Q如果你q不知道它们的区别,而且已经在程序界摸爬滚打多年Q那只能说这是一个悲哀Q?br>
const int a;
int const a;
const int *a;
int * const a;
int const * a const;

   Q?Q? 关键字const的作用是为给M代码的h传达非常有用的信息。例如,在函数的形参前添加const关键字意味着q个参数在函C内不会被修改Q属?? 入参?。在有多个Ş参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的L别哪些是输入参数Q哪些是可能的输出参数?br>
  Q?Q合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参敎ͼ防止其被无意的代码修改,q样可以减少bug的出现?br>
   const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着Q?只能ȝ普通变?Q可以称其ؓ"不能改变的变?Q这个说法似乎很拗口Q但 却最准确的表达了C语言中const的本质)Q在~译阶段需要的常数仍然只能?define宏定义!故在C语言中如下程序是非法的:

const int SIZE = 10;
char a[SIZE]; /* 非法Q编译阶D不能用到变?*/

  关键字volatile

  C语言~译器会对用户书写的代码q行优化Q譬如如下代码:

int a,b,c;
a = inWord(0x100); /*dI/OI间0x100端口的内容存入a变量*/
b = a;
a = inWord (0x100); /*再次dI/OI间0x100端口的内容存入a变量*/
c = a;

  很可能被~译器优化ؓQ?br>
int a,b,c;
a = inWord(0x100); /*dI/OI间0x100端口的内容存入a变量*/
b = a;
c = a;

  但是q样的优化结果可能导致错误,如果I/OI间0x100端口的内容在执行W一ơ读操作后被其它E序写入新|则其实第2ơ读操作d的内容与W一ơ不同,b和c的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类g化,正确的做法是Q?br>
volatile int aQ?/td>

  volatile变量可能用于如下几种情况Q?br>
  (1) q行讑֤的硬件寄存器Q如Q状态寄存器Q例中的代码属于此类Q;

  (2) 一个中断服务子E序中会讉K到的非自动变?也就是全局变量)Q?br>
  (3) 多线E应用中被几个Q务共享的变量?br>
  CPU字长与存储器位宽不一致处?/b>

  在背景篇中提刎ͼ本文Ҏ选择了一个与CPU字长不一致的存储芯片Q就是ؓ了进行本节的讨论Q解决CPU字长与存储器位宽不一致的情况?0186的字长ؓ16Q而NVRAM的位宽ؓ8Q在q种情况下,我们需要ؓNVRAM提供d字节、字的接口,如下Q?

typedef unsigned char BYTE;
typedef unsigned int WORD;
/* 函数功能Q读NVRAM中字?
* 参数QwOffsetQ读取位|相对NVRAM基地址的偏U?br>* q回Q读取到的字节?br>*/
extern BYTE ReadByteNVRAM(WORD wOffset)
{
 LPBYTE lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* Z么偏U要×2? */

 return *lpAddr;
}

/* 函数功能Q读NVRAM中字
* 参数QwOffsetQ读取位|相对NVRAM基地址的偏U?br>* q回Q读取到的字
*/
extern WORD ReadWordNVRAM(WORD wOffset)
{
 WORD wTmp = 0;
 LPBYTE lpAddr;
 /* d高位字节 */
 lpAddr = (BYTE*)(NVRAM + wOffset * 2); /* Z么偏U要×2? */
 wTmp += (*lpAddr)*256;
 /* d低位字节 */
 lpAddr = (BYTE*)(NVRAM + (wOffset +1) * 2); /* Z么偏U要×2? */
 wTmp += *lpAddr;
 return wTmp;
}

/* 函数功能Q向NVRAM中写一个字?
*参数QwOffsetQ写入位|相对NVRAM基地址的偏U?br>* byDataQ欲写入的字?br>*/
extern void WriteByteNVRAM(WORD wOffset, BYTE byData)
{
 ?br>}

/* 函数功能Q向NVRAM中写一个字 */
*参数QwOffsetQ写入位|相对NVRAM基地址的偏U?br>* wDataQ欲写入的字
*/
extern void WriteWordNVRAM(WORD wOffset, WORD wData)
{
 ?br>}

  子A问曰QWhy偏移要乘??

  子曰Q请看图1Q?6?0186?位NVRAM之间互连只能以地址UA1对其A0,CPU本n的A0与NVRAM不连接。因此,NVRAM的地址只能是偶数地址Q故每次?x10为单位前q!


? CPU与NVRAM地址U连?/div>

  子A再问QSo why 80186的地址UA0不与NVRAM的A0q接Q?br>
  子曰Q请看《IT》之《微机原理篇》,那里面讲qC关于计算机组成的圣h之道?br>
  ȝ

   本篇主要讲述了嵌入式pȝC~程中内存操作的相关技巧。掌握ƈ深入理解关于数据指针、函数指针、动态申请内存、const及volatile关键字等? 相关知识Q是一个优U的C语言E序设计师的基本要求。当我们已经牢固掌握了上q技巧后Q我们就已经学会了C语言?9%Q因为C语言最_֍的内늚在内? 操作中体现?br>
  我们之所以在嵌入式系l中使用C语言q行E序设计Q?9%是因为其强大的内存操作能力!

  如果你爱~程Q请你爱C语言Q?br>
  如果你爱C语言Q请你爱指针Q?br>
  如果你爱指针Q请你爱指针的指针!

力力力 2005-09-21 17:30 发表评论
]]>
C语言嵌入式系l编E修g?软g架构?/title><link>http://www.tkk7.com/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.tkk7.com/faintbear/archive/2005/09/21/13654.html</guid><wfw:comment>http://www.tkk7.com/faintbear/comments/13654.html</wfw:comment><comments>http://www.tkk7.com/faintbear/archive/2005/09/21/13654.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/faintbear/comments/commentRss/13654.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/faintbear/services/trackbacks/13654.html</trackback:ping><description><![CDATA[<h1>C语言嵌入式系l编E修g?软g架构?/h1> <p>作?宋宝?  更新日期:2005-07-22<br><br> </p> <div id="oi2ke28" class="guanggao"> <span id="ad4"></span> </div>   <b>模块划分</b><br><br>  模块划分??是规划的意思,意指怎样合理的将一个很大的软g划分Zpd功能 独立的部分合作完成系l的需求。C语言作ؓ一U结构化的程序设计语aQ在模块的划分上主要依据功能Q依功能q行划分在面向对象设计中成ؓ一个错误,牛顿? 律遇C>相对论)QC语言模块化程序设计需理解如下概念Q?br><br>  Q?Q?模块x一?c文g和一?h文g的结合,头文?.h)中是对于该模块接口的声明Q?br><br>  Q?Q?某模块提供给其它模块调用的外部函数及数据需?h中文件中冠以extern关键字声明;<br><br>  Q?Q?模块内的函数和全局变量需?c文g开头冠以static关键字声明;<br><br>  Q?Q?永远不要?h文g中定义变量!定义变量和声明变量的区别在于定义会生内存分配的操作Q是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶D从其它模块L外部函数和变量。如Q?br><br> <table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"> <tbody> <tr> <td>/*module1.h*/<br>int a = 5; /* 在模??h文g中定义int a */<br><br>/*module1 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */<br><br>/*module2 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */<br><br>/*module3 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */</td></tr></tbody></table><br>  以上E序的结果是在模???中都定义了整型变量aQa在不同的模块中对应不同的地址单元Q这个世界上从来不需要这LE序。正的做法是:<br><br> <table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"> <tbody> <tr> <td>/*module1.h*/<br>extern int a; /* 在模??h文g中声明int a */<br><br>/*module1 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */<br>int a = 5; /* 在模??c文g中定义int a */<br><br>/*module2 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */<br><br>/*module3 .c*/<br>#include "module1.h" /* 在模?中包含模??h文g */</td></tr></tbody></table><br>  q样如果模块1??操作a的话Q对应的是同一片内存单元?br><br>  一个嵌入式pȝ通常包括两类模块Q?br><br>  Q?Q硬仉动模块,一U特定硬件对应一个模块;<br><br>  Q?QY件功能模块,其模块的划分应满低偶合、高内聚的要求?br><br>  <b>多Q务还是单d</b><br><br>  所?单Q务系l?是指该系l不能支持多dq发操作Q宏观串行地执行一个Q务。而多dpȝ则可以宏观ƈ行(微观上可能串行)?同时"执行多个d?br><br>   多Q务的q发执行通常依赖于一个多d操作pȝQOSQ,多Q务OS的核心是pȝ调度器,它用Q务控制块QTCBQ来理d调度功能。TCB包括? 务的当前状态、优先、要{待的事件或资源、Q务程序码的v始地址、初始堆栈指针等信息。调度器在Q务被ȀzLQ要用到q些信息。此外,TCBq被用来? 放Q务的"上下?Qcontext)。Q务的上下文就是当一个执行中的Q务被停止Ӟ所要保存的所有信息。通常Q上下文是计算机当前的状态,也即各个 寄存器的内容。当发生d切换Ӟ当前q行的Q务的上下文被存入TCBQƈ要被执行的d的上下文从它的TCB中取出,攑օ各个寄存器中?br><br>  嵌入式多dOS的典型例子有Vxworks、ucLinux{。嵌入式OSq遥不可及的神坛之物,我们可以用不?000行代码实C个针?0186处理器的功能最单的OS内核Q作者正准备q行此项工作Q希望能心得A献给大家?br><br>  I竟选择多Q务还是单d方式Q依赖于软g的体pL否庞大。例如,l大多数手机E序都是多Q务的Q但也有一些小灵通的协议栈是单Q务的Q没有操作系l,它们的主E序轮流调用各个软g模块的处理程序,模拟多Q务环境?br><b>单Q务程序典型架?/b><br><br>  Q?Q从CPU复位时的指定地址开始执行;<br><br>  Q?Q蟩转至汇编代码startup处执行;<br><br>  Q?Q蟩转至用户ȝ序main执行Q在main中完成:<br><br>  a.初试化各g讑֤Q?<br><br>  b.初始化各软g模块Q?br><br>  c.q入d@环(无限循环Q,调用各模块的处理函数<br><br>  用户ȝ序和各模块的处理函数都以C语言完成。用户主E序最后都q入了一个死循环Q其首选方案是Q?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>  有的E序员这样写Q?br><br> <table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"> <tbody> <tr> <td>for(;;)<br>{<br>}</td></tr></tbody></table><br>  q个语法没有切表达代码的含义,我们从for(;;)看不Z么,只有弄明白for(;;)在C语言中意味着无条件@环才明白其意?br><br>  下面是几?著名"的死循环Q?br><br>  Q?Q操作系l是d@环;<br><br>  Q?QWIN32E序是死循环Q?br><br>  Q?Q嵌入式pȝ软g是死循环Q?br><br>  Q?Q多U程E序的线E处理函数是d@环?br><br>   你可能会辩驳Q大声说Q?凡事都不是绝对的Q???都可以不是死循环"。YesQyou are rightQ但是你得不到鲜花和掌声。实际上Q这是一个没有太大意义的牛角,因ؓq个世界从来不需要一个处理完几个消息喊着要OS杀d的WIN32 E序Q不需要一个刚开始RUNp行了断的嵌入式系l,不需要莫名其妙启动一个做一点事干掉自qU程。有时候,q于严}刉的不是便利而是ȝ。君? 见,五层的TCP/IP协议栈超严谨的ISO/OSI七层协议栈大行其道成Z实上的标准?<br><br>  l常有网友讨论:<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>  {类似问题。面对这些问题,我们只能发出p的感慨:世界上还有很多有意义的事情等着我们L化摄入的食物?br><br>  实际上,嵌入式系l要q行C界末日?br><br>  <b>中断服务E序</b><br><br>   中断是嵌入式pȝ中重要的l成部分Q但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持Q提供新的关键字用于标示中断服务E序 (ISR)Q类g__interrupt?program interrupt{。当一个函数被定义为ISR的时候,~译器会自动函数增加中断服务E序所需要的中断现场入栈和出栈代码?br><br>  中断服务E序需要满_下要求:<br><br>  (1)不能q回|<br><br>  (2)不能向ISR传递参敎ͼ<br><br>  (3) ISR应该可能的短小_悍Q?br><br>  (4) printf(char * lpFormatString,?函数会带来重入和性能问题Q不能在ISR中采用?br><br>  在某目的开发中Q我们设计了一个队列,在中断服务程序中Q只是将中断cdd入该队列中,在主E序的死循环中不断扫描中断队列是否有中断Q有则取出队列中的第一个中断类型,q行相应处理?br><br> <table align="center" bgcolor="#dadacf" border="1" bordercolor="#ffcc66" width="90%"> <tbody> <tr> <td>/* 存放中断的队?*/<br>typedef struct tagIntQueue<br>{<br> int intType; /* 中断cd */<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)Q?* 在队列尾加入新的中断 */<br>}</td></tr></tbody></table><br>  在主E序循环中判断是否有中断Q?<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) /* 是不是很象WIN32E序的消息解析函? */<br>  {<br>   /* 对,我们的中断类型解析很cM于消息驱?*/<br>   case xxx: /* 我们U其?中断驱动"吧? */<br>    ?br>    break;<br>   case xxx:<br>    ?br>    break;<br>   ?br>  }<br> }<br>}</td></tr></tbody></table><br>  按上q方法设计的中断服务E序很小Q实际的工作都交׃E序执行了?br><b>g驱动模块</b><br><br>  一个硬仉动模块通常应包括如下函敎ͼ<br><br>  Q?Q中断服务程序ISR<br><br>  Q?Q硬件初始化<br><br>  a.修改寄存器,讄g参数Q如UART应设|其波特率,AD/DA讑֤应设|其采样速率{)Q?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); /* q回void far型指针void far * */ <br>m_myPtr += ITYPE_UART; /* ITYPE_UARTQ?uart中断服务E序 */<br>/* 相对于中断向量表首地址的偏U?*/<br>*m_myPtr = &UART _Isr; /* UART _IsrQUART的中断服务程?*/</td></tr></tbody></table><br>  Q?Q设|CPU针对该硬件的控制U?br><br>  a.如果控制U可作PIOQ可~程I/OQ和控制信号用,则设|CPU内部对应寄存器其作为控制信P<br><br>  b.讄CPU内部的针对该讑֤的中断屏蔽位Q设|中断方式(电^触发q是边缘触发Q?br><br>  Q?Q提供一pd针对该设备的操作接口函数。例如,对于LCDQ其驱动模块应提供绘制像素、画Uѝ绘制矩c显C字W点늭函数Q而对于实旉Q其驱动模块则需提供获取旉、设|时间等函数?br><br>  <b>C的面向对象化</b><br><br>  在面向对象的语言里面Q出Ccȝ概念。类是对特定数据的特定操作的集合体。类包含了两个范_数据和操作。而C语言中的struct仅仅是数据的集合Q我们可以利用函数指针将struct模拟Z个包含数据和操作?c?。下面的CE序模拟了一个最单的"c?Q?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); /* 行ؓQ函数指?*/<br> int a; /* 数据 */<br> int b;<br>};</td></tr></tbody></table><br>   我们可以利用C语言模拟出面向对象的三个Ҏ:装、承和多态,但是更多的时候,我们只是需要将数据与行为封装以解决软gl构混ؕ的问题。C模拟面向 对象思想的目的不在于模拟行ؓ本nQ而在于解x些情况下使用C语言~程时程序整体框架结构分散、数据和函数p的问题。我们在后箋章节会看到这L? 子?<br><br>  <b>ȝ</b><br><br>  本篇介绍了嵌入式pȝ~程软g架构斚w的知识,主要包括模块划分、多dq是单Q务选取、单dE序典型架构、中断服务程序、硬仉动模块设计等Q从宏观上给Z一个嵌入式pȝ软g所包含的主要元素?br><br>  误住:软gl构是Y件的灵魂Q结构؜qE序面目可憎Q调试、测试、维护、升U都极度困难?img src ="http://www.tkk7.com/faintbear/aggbug/13654.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/faintbear/" target="_blank">力力力</a> 2005-09-21 17:29 <a href="http://www.tkk7.com/faintbear/archive/2005/09/21/13654.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言嵌入式系l编E修g一:背景?/title><link>http://www.tkk7.com/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.tkk7.com/faintbear/archive/2005/09/21/13653.html</guid><wfw:comment>http://www.tkk7.com/faintbear/comments/13653.html</wfw:comment><comments>http://www.tkk7.com/faintbear/archive/2005/09/21/13653.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/faintbear/comments/commentRss/13653.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/faintbear/services/trackbacks/13653.html</trackback:ping><description><![CDATA[<h1><font size="2">http://www.upsdn.net/(推荐下,感兴的同志可以看看哈,便携pȝ开发网)</font><br></h1><br><br><h1>C语言嵌入式系l编E修g一:背景?/h1> <p>作?宋宝?  更新日期:2005-08-30<br> 来源:yesky.com    </p> 不同于一般Ş式的软g~程Q嵌入式pȝ~程建立在特定的gq_上,势必要求其编E语a具备较强的硬件直接操作能力。无疑,汇编语言具备q样的特质。但 是,归因于汇~语a开发过E的复杂性,它ƈ不是嵌入式系l开发的一般选择。而与之相比,C语言--一U?高的低U?语言Q则成ؓ嵌入式系l开发的最佳? 择。笔者在嵌入式系l项目的开发过E中Q一ơ又一ơ感受到C语言的精妙,沉醉于C语言l嵌入式开发带来的便利?br><br>  ?l出了本文的讨论所Z的硬件^収ͼ实际上,q也是大多数嵌入式系l的gq_。它包括两部分: <br><br>  Q?Q?以通用处理器ؓ中心的协议处理模块,用于|络控制协议的处理;<br><br>  Q?Q?以数字信号处理器QDSPQؓ中心的信号处理模块,用于调制、解调和?模信可{换?br><br>  本文的讨Z要围l以通用处理器ؓ中心的协议处理模块进行,因ؓ它更多地牉|到具体的C语言~程技巧。而DSP~程则重点关注具体的数字信号处理法Q主要涉及通信领域的知识,不是本文的讨论重炏V?br><br>   着g讨论普遍的嵌入式pȝC~程技巧,pȝ的协议处理模块没有选择特别的CPUQ而是选择了众所周知的CPU芯片--80186Q每一位学习过《微? 原理》的读者都应该Ҏ芯片有一个基本的认识Q且对其指o集比较熟悉?0186的字长是16位,可以d到的内存I间?MBQ只有实地址模式。C语言 ~译生成的指针ؓ32位(双字Q,?6位ؓD地址Q低16位ؓD内~译Q一D|?4KB?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>? pȝg架构</div></td></tr></tbody></table><br>  协议处理模块中的FLASH和RAM几乎是每个嵌入式pȝ的必备设备,前者用于存储程序,后者则是程序运行时指o及数据的存放位置。系l所选择的FLASH和RAM的位宽都?6位,与CPU一致?br><br>  实时钟芯片可以ؓpȝ定时Q给出当前的q、月、日及具体时_时、分、秒及毫U)Q可以设定其l过一D|间即向CPU提出中断或设定报警时间到来时向CPU提出中断Q类似闹钟功能)?br><br>   NVRAMQ非易失LRAMQ具有掉电不丢失数据的特性,可以用于保存pȝ的设|信息,譬如|络协议参数{。在pȝ掉电或重新启动后Q仍然可以读取先 前的讄信息。其位宽?位,比CPU字长。文章特意选择一个与CPU字长不一致的存储芯片Qؓ后文中一节的讨论创造条件?br><br>  UART则完成CPUq行数据传输与RS-232串行数据传输的{换,它可以在接收到[1~MAX_BUFFER]字节后向CPU提出中断QMAX_BUFFER为UART芯片存储接收到字节的最大缓冲区?br><br>  键盘控制器和昄控制器则完成pȝ人机界面的控制?br><br>  以上提供的是一个较完备的嵌入式pȝg架构Q实际的pȝ可能包含更少的外设。之所以选择一个完备的pȝQ是Z后文更全面的讨论嵌入式系lC语言~程技巧的Ҏ面面Q所有设备都会成为后文的分析目标?br><br>   嵌入式系l需要良好的软g开发环境的支持Q由于嵌入式pȝ的目标机资源受限Q不可能在其上徏立庞大、复杂的开发环境,因而其开发环境和目标q行环境怺 分离。因此,嵌入式应用Y件的开发方式一般是Q在宿主?Host)上徏立开发环境,q行应用E序~码和交叉编译,然后宿主机同目标?Target)? 立连接,应用程序下载到目标Zq行交叉调试Q经q调试和优化Q最后将应用E序固化到目标机中实际运行?<br><br>  CAD-UL是适用? x86处理器的嵌入式应用Y件开发环境,它运行在Windows操作pȝ之上Q可生成x86处理器的目标代码q过PC机的COM口(RS-232串口Q? 或以太网口下载到目标Zq行Q如?。其ȝ于目标机FLASH存储器中的monitorE序可以监控宿主机Windows调试q_上的用户调试指oQ? 获取CPU寄存器的值及目标机存储空间、I/OI间的内宏V?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>? 交叉开发环?/div></td></tr></tbody></table><br>  后箋章节从软g架构、内存操作、屏q操作、键盘操作、性能优化{? 多方面阐qC语言嵌入式系l的~程技巧。Y件架构是一个宏观概念,与具体硬件的联系不大Q内存操作主要涉及系l中的FLASH、RAM和NVRAM芯片Q? 屏幕操作则涉及显C控制器和实旉Q键盘操作主要涉及键盘控制器Q性能优化则给Z些具体的减小E序旉、空间消耗的技巧?br><br>  在我们的修炼旅途中经q?5个关口,q些兛_dZc,一cL技巧型Q有很强的适用性;一cd是常识型Q在理论上有些意义?img src ="http://www.tkk7.com/faintbear/aggbug/13653.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/faintbear/" target="_blank">力力力</a> 2005-09-21 17:28 <a href="http://www.tkk7.com/faintbear/archive/2005/09/21/13653.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>修练8qC++面向对象E序设计之体?林锐博士)http://www.tkk7.com/faintbear/archive/2005/09/09/12514.html力力力力力力Fri, 09 Sep 2005 02:59:00 GMThttp://www.tkk7.com/faintbear/archive/2005/09/09/12514.htmlhttp://www.tkk7.com/faintbear/comments/12514.htmlhttp://www.tkk7.com/faintbear/archive/2005/09/09/12514.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/12514.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/12514.html

六年前,我刚热恋“面向对象”(Object-OrientedQ时Q一口气C了近十个定义。六q后Q我从几十万行程序中滚爬出来准备写点心得体会Ӟ却无法解释什么是“面向对象”,p说不清楚什么是数学那样。Y件工E中的时髦术语“面向对象分析”和“面向对象设计”,通常是针对“需求分析”和“系l设计”环节的。“面向对象”有几大学派Q就象如来佛、上帝和真主用各自的方式定义了这个世界,q留下一堆经书来解释q个世界?/P>

有些学者徏议这h“对象”:分析一个句子的语法Q找出名词和动词Q名词就是对象,动词则是对象的方法(卛_敎ͼ?/P>

当年国民党的文hZҎ毛泽东的《沁园春·雪》,Ҏh朝遗老们写了一些对仗工整的诗,误介石q目。老蒋看了气得大骂Q“娘希匹Q全都有一股棺材里腐尸的气呟뀂”我看了几千늚软g工程资料Q终于发现自己有些“弱智”,无法理解“面向对象”的理论Q同旉悟到“编E是道理。?/P>

面向对象E序设计语言很多Q如Smalltalk、Ada、Eiffel、Object Pascal、Visual Basic、C++{等。C++语言最讨h喜欢Q因为它兼容C 语言Qƈ且具备C 语言的性能。近几年Q一U叫Java 的纯面向对象语言U极一Ӟ不少人叫喊着要用Java 革C++的命。我认ؓJava 好比是C++的外甥,虽然不是直接遗传的,但也几分象样。外甥在舅舅w上玩耍时z了一泡尿Q俩Z该ؓ此而争c?/P>

关于C++E序设计的书藉非常多Q本章不讲C++的语法,只讲一些小的~程道理。如果我能早几年明白q些道理,可以大大改善数十万行程序的质量了?/P>

1. C++面向对象E序设计的重要概?/STRONG>

早期革命q里有q样一个角Ԍ他说Q“我是党代表Q我代表党,我就是党。”后来他l同志们带来了灾难?/P>

会用C++的程序员一定懂得面向对象程序设计吗Q?/P>

不会用C++的程序员一定不懂得面向对象E序设计吗?

两者都未必。就象坏蛋入党后未必能成为好人,好h不入党未必变成坏蛋那栗?/P>

我不怕触犯众怒地说句大话Q“C++没有高手QC 语言才有高手。”在用C 和C++~程8q之后,我深深地遗憾自己不是C 语言的高手,更遗憾没有hҎ我如何进行面向对象程序设计。我和很多C++E序员一P在n用到C++语法的好处时便以己已l明白了面向对象E序设计。就象挤掉牙膏卖牙膏皮那P真是暴殄天物呀?/P>

Z不懂拼音也会讲普通话Q如果懂得拼韛_会把普通话讲得更好。不懂面向对象程序设计也可以用C++~程Q如果懂得面向对象程序设计则会把C++E序~得更好。本节讲qC个非常基的概念:“类与对象”、“承与l合”、“虚函数与多态”。理解这些概念,有助于提高程序的质量Q特别是提高“可复用性”与“可扩充性”?/P>

1.1 cM对象

对象QObjectQ是c(ClassQ的一个实例(InstanceQ。如果将对象比作房子Q那么类是房子的设计图U。所以面向对象程序设计的重点是类的设计,而不是对象的设计。类可以数据和函数装在一P其中函数表示了类的行为(或称服务Q。类提供关键字public、protected 和private 用于声明哪些数据和函数是公有的、受保护的或者是U有的?/P>

q样可以辑ֈ信息隐藏的目的,卌cM仅公开必须要让外界知道的内容,而隐藏其它一切内宏V我们不可以滥用cȝ装功能Q不要把它当成火锅,什么东襉K往里扔?/P>

cȝ设计是以数据Z心,q是以行Zؓ中心Q?/P>

d“以数据Z心”的那一zhxcȝ内部数据l构Q他们习惯上private cd的数据写在前面,而将public cd的函数写在后面,如表8.1(a)所C?/P>

d“以行ؓZ心”的那一zhxcd该提供什么样的服务和接口Q他们习惯上public cd的函数写在前面,而将private cd的数据写在后面,如表8.1(b)所C?/P>

很多C++教课书主张在设计cL“以数据Z心”。我坚持q且读者在设计cL“以行ؓZ心”,即首先考虑cd该提供什么样的函数。Microsoft 公司的COM 规范的核心是接口设计QCOM 的接口就相当于类的公有函数[Rogerson 1999]。在E序设计斚wQ咱们不要怀疑Microsoft 公司的风根{?/P>

设计孤立的类是比较容易的Q难的是正确设计基类及其zcR因为有些程序员搞不清楚“扎쀝(InheritanceQ、“组合”(CompositionQ、“多态”( PolymorphismQ这些概c?


1.2 l承与组?/P>

如果A 是基c,B 是A 的派生类Q那么B 承A 的数据和函数。示例程序如下:

class A

{

public:

void Func1(void);

void Func2(void);

};

class B : public A

{

public:

void Func3(void);

void Func4(void);

};

// Example

main()

{

B b; // B的一个对?/P>

b.Func1(); // B 从A l承了函数Func1

b.Func2(); // B 从A l承了函数Func2

b.Func3();

b.Func4();

}

q个单的CZE序说明了一个事实:C++的“扎쀝特性可以提高程序的可复用性。正因ؓ“扎쀝太有用、太Ҏ用,才要防止q“扎쀝。我们要l“扎쀝立一些用规则:

一、如果类A 和类B 毫不相关Q不可以Z使B 的功能更多些而让B l承A 的功能?/P>

不要觉得“不吃白不吃”,让一个好端端的健壮青q无~无故地吃h参补w体?/P>

二、如果类B 有必要用A 的功能,则要分两U情况考虑Q?/P>

Q?Q若在逻辑上B 是A 的“一U”(a kind of Q,则允许B l承A 的功能。如男hQManQ是人(HumanQ的一U,男孩QBoyQ是男h的一U。那么类Man 可以从类Human zQ类Boy 可以从类Man z。示例程序如下:

class Human

{

?/P>

};

class Man : public Human

{

?/P>

};

class Boy : public Man

{

?/P>

};

Q?Q若在逻辑上A 是B 的“一部分”(a part ofQ,则不允许B l承A 的功能,而是要用A和其它东西组合出B。例如眼QEyeQ、EQNoseQ、口QMouthQ、耻IEarQ是_HeadQ的一部分Q所以类Head 应该qEye、Nose、Mouth、Ear l合而成Q不是派生而成。示例程序如下:

class Eye

{

public:

void Look(void);

};

class Nose

{

public:

void Smell(void);

};

class Mouth

{

public:

void Eat(void);

};

class Ear

{

public:

void Listen(void);

};

// 正确的设计,冗长的程?/P>

class Head

{

public:

void Look(void) { m_eye.Look(); }

void Smell(void) { m_nose.Smell(); }

void Eat(void) { m_mouth.Eat(); }

void Listen(void) { m_ear.Listen(); }

private:

Eye m_eye;

Nose m_nose;

Mouth m_mouth;

Ear m_ear;

};

如果允许Head 从Eye、Nose、Mouth、Ear z而成Q那么Head 自动具有Look、Smell、Eat、Listen q些功能Q?/P>

// 错误的设?/P>

class Head : public Eye, public Nose, public Mouth, public Ear

{

};

上述E序十分短ƈ且运行正,但是q种设计却是错误的。很多程序员l不起“扎쀝的诱惑而犯下设计错误?/P>

一只公鸡劲地q打一只刚下了蛋的母鸡Q你知道Z么吗Q?/P>

因ؓ母鸡下了鸭蛋?/P>

本书3.3 节讲q“运行正”的E序不见得就是高质量的程序,此处是一个例证?



1.3 虚函C多?/P>

除了l承外,C++的另一个优良特性是支持多态,卛_许将zcȝ对象当作基类的对象用。如果A 是基c,B 和C 是A 的派生类Q多态函数Test 的参数是A ?指针。那么Test 函数可以引用A、B、C 的对象。示例程序如下:

class A

{

public:

void Func1(void);

};

void Test(A *a)

{

a->Func1();

}

class B : public A

{

?/P>

};

class C : public A

{

?/P>

};

// Example

main()

{

A a;

B b;

C c;

Test(&a);

Test(&b);

Test(&c);

};

以上E序看不出“多态”有什么h|加上虚函数和抽象基类后,“多态”的威力显C出来了?/P>

C++用关键字virtual 来声明一个函Cؓ虚函敎ͼzcȝ虚函数将QoverrideQ基cd应的虚函数的功能。示例程序如下:

class A

{

public:

virtual void Func1(void){ cout<< “This is A::Func1 \n”}

};

void Test(A *a)

{

a->Func1();

}

class B : public A

{

public:

virtual void Func1(void){ cout<< “This is B::Func1 \n”}

};

class C : public A

{

public:

virtual void Func1(void){ cout<< “This is C::Func1 \n”}

};

// Example

main()

{

A a;

B b;

C c;

Test(&a); // 输出This is A::Func1

Test(&b); // 输出This is B::Func1

Test(&c); // 输出This is C::Func1

};

如果基类A 定义如下Q?/P>

class A

{

public:

virtual void Func1(void)=0;

};

那么函数Func1 叫作U虚函数Q含有纯虚函数的cd作抽象基cR抽象基cd定义纯虚函数的形式Q具体的功能由派生类实现?/P>

l合“抽象基cZ和“多态”有如下H出优点Q?/P>

Q?Q应用程序不必ؓ每一个派生类~写功能调用Q只需要对抽象基类q行处理卛_。这一

招叫“以不变应万变”,可以大大提高E序的可复用性(q是接口设计的复用,而不是代码实现的复用Q?/P>

Q?Q派生类的功能可以被基类指针引用Q这叫向后兼容,可以提高E序的可扩充性和可维护性。以前写的程序可以被来写的E序调用不为奇Q但是将来写的程序可以被以前写的E序调用那可了不赗?


2 良好的编E风?/STRONG>

内功深厚的武林高手出招往往qxE无奇。同理,~程高手也不会用奇门怪招写程序。良好的~程风格是生高质量E序的前提?/P>

2.1 命名U定

有不h~程时用拼音l函数或变量命名Q这样做q不能说明你很爱国,却会让用此程序的p(很多南方Z懂拼韻I我就不懂Q。程序中的英文一般不会太复杂Q用词要力求准确。匈牙利命名法是Microsoft 公司倡导的[Maguire 1993]Q虽然很烦琐Q但用习惯了也就成了自然。没有h你采用何U命名法Q但有一点应该做刎ͼ自己的程序命名必M致?/P>

以下是我~程旉用的命名U定Q?/P>

Q?Q宏定义用大写字母加下划U表C,如MAX_LENGTHQ?/P>

Q?Q函数用大写字母开头的单词l合而成Q如SetName, GetName Q?/P>

Q?Q指针变量加前缀pQ如*pNode Q?/P>

Q?QBOOL 变量加前~bQ如bFlag Q?/P>

Q?Qint 变量加前~iQ如iWidth Q?/P>

Q?Qfloat 变量加前~fQ如fWidth Q?/P>

Q?Qdouble 变量加前~dQ如dWidth Q?/P>

Q?Q字W串变量加前~strQ如strName Q?/P>

Q?Q枚丑֏量加前缀eQ如eDrawMode Q?/P>

Q?0Q类的成员变量加前缀m_Q如m_strName, m_iWidth Q?/P>

对于int, float, double 型的变量Q如果变量名的含义十分明显,则不加前~Q避免烦琐。如用于循环的int 型变量i,j,k Qfloat 型的三维坐标Qx,y,zQ等?/P>

2.2 使用断言

E序一般分为Debug 版本和Release 版本QDebug 版本用于内部调试QRelease 版本发行l用户用。断aassert 是仅在Debug 版本起作用的宏,它用于检查“不应该”发生的情况。以下是一个内存复制程序,在运行过E中Q如果assert 的参Cؓ假,那么E序׃中止Q一般地q会出现提示对话Q说明在什么地方引发了assertQ?/P>

//复制不重叠的内存?/P>

void memcpy(void *pvTo, void *pvFrom, size_t size)

{

void *pbTo = (byte *) pvTo;

void *pbFrom = (byte *) pvFrom;

assert( pvTo != NULL && pvFrom != NULL );

while(size - - > 0 )

*pbTo + + = *pbFrom + + ;

return (pvTo);

}

assert 不是一个仓促拼凑v来的宏,Z不在E序的Debug 版本和Release 版本引v差别Qassert 不应该生Q何副作用。所以assert 不是函数Q而是宏。程序员可以把assert 看成一个在Mpȝ状态下都可以安全用的无害试手段?/P>

很少有比跟踪到程序的断言Q却不知道该断言的作用更让h沮的事了。你化了很多旉Q不是ؓ了排除错误,而只是ؓ了弄清楚q个错误到底是什么。有的时候,E序员偶还会设计出有错误的断言。所以如果搞不清楚断a查的是什么,很隑ֈ断错误是出现在程序中Q还是出现在断言中。幸q的是这个问题很好解冻I只要加上清晰的注释即可。这本是显而易见的事情Q可是很有E序员这样做。这好比一个h在森林里Q看到树上钉着一块“危险”的大牌子。但危险到底是什么?树要倒?有废井?有野兽?除非告诉Z“危险”是什么,否则q个警告牌难以v到积极有效的作用。难以理解的断言常常被程序员忽略Q甚臌删除。[Maguire 1993]

以下是用断a的几个原则:

Q?Q用断a捕捉不应该发生的非法情况。不要؜淆非法情况与错误情况之间的区别,后者是必然存在的ƈ且是一定要作出处理的?/P>

Q?Q用断a对函数的参数q行认?/P>

Q?Q在~写函数Ӟ要进行反复的考查Qƈ且自问:“我打算做哪些假定?”一旦确定了?/P>

假定Q就要用断a对假定进行检查?/P>

Q?Q一般教U书都鼓q序员们进行防错性的E序设计Q但要记住这U编E风g隐瞒错误。当q行防错性编E时Q如果“不可能发生”的事情的确发生了,则要使用断言q行报警?


2.3 new、delete 与指?/P>

在C++中,操作Wnew 用于甌内存Q操作符delete 用于释放内存。在C 语言中,函数malloc 用于甌内存Q函数free 用于释放?存。由于C++兼容C 语言Q所以new、delete、malloc、free 都有可能一起用。new 能比malloc q更多的事,它可以申请对象的内存Q而malloc 不能。C++和C 语言中的指针威猛无比Q用错了会带来灾难。对于一个指针pQ如果是用new甌的内存,则必ȝdelete 而不能用free 来释放。如果是用malloc 甌的内存,则必ȝfree 而不能用delete 来释放。在用delete 或用free 释放p 所指的内存后,应该马上昑ּ地将p |ؓNULLQ以防下ơ用p 时发生错误。示例程序如下:

void Test(void)

{

float *p;

p = new float[100];

if(p==NULL) return;

?/ do something

delete p;

p=NULL; // 良好的编E风?/P>

// 可以l箋使用p

p = new float[500];

if(p==NULL) return;

?/ do something else

delete p;

p=NULL;

}

我们q要预防“野指针”,“野指针”是指向“垃䏀内存的指针Q主要成因有两种Q?/P>

Q?Q指针没有初始化?/P>

Q?Q指针指向已l释攄内存Q这U情冉|让h防不胜防Q示例程序如下:

class A

{

public:

void Func(void){…}

};

void Test(void)

{

A *p;

{

A a;

p = &a; // 注意a 的生命期

}

p->Func(); // p 是“野指针”,E序出错

}

2.4 使用const

在定义一个常量时Qconst ?define 更加灉|。用const 定义的常量含有数据类型,该常量可以参与逻辑q算。例如:

const int LENGTH = 100; // LENGTH 是int cd

const float MAX=100; // MAX 是float cd

#define LENGTH 100 // LENGTH 无类?/P>

#define MAX 100 // MAX 无类?/P>

除了能定义常量外Qconst q有两个“保护”功能:

一、强制保护函数的参数g发生变化

以下E序中,函数f 不会改变输入参数name 的|但是函数g 和h 都有可能改变name的倹{?/P>

void f(String s); // pass by value

void g(String &s); // pass by referance

void h(String *s); // pass by pointer

main()

{

String name=“Dog?

f(name); // name 的g会改?/P>

g(name); // name 的值可能改?/P>

h(name); // name 的值可能改?/P>

}

对于一个函数而言Q如果其?’或?’类型的参数只作输入用,不作输出用,那么应当在该参数前加上constQ以保函数的代码不会改变该参数的|如果改变了该参数的|~译器会出现错误警告Q。因此上q程序中的函数g 和h 应该定义成:

void g(const String &s);

void h(const String *s);

二、强制保护类的成员函C改变M数据成员的?/P>

以下E序中,cstack 的成员函数Count 仅用于计敎ͼZ保Count 不改变类中的M数据成员的|应将函数Count 定义成const cd?/P>

class Stack

{

public:

void push(int elem);

void pop(void);

int Count(void) const; // const cd的函?/P>

private:

int num;

int data[100];

};

int Stack::Count(void) const

{

++ num; // ~译错误Qnum 值发生变?/P>

pop(); // ~译错误Qpop 改变成员变量的?/P>

return num;

}


2.5 其它

Q?Q不要编写一条过分复杂的语句Q紧凑的C++/C 代码q不见到能得到高效率的机器代码,却会降低E序的可理解性,E序出错误的几率也会提高?/P>

Q?Q不要编写集多种功能于一w的函数Q在函数的返回gQ不要将正常值和错误标志混在一赗?/P>

Q?Q不要将BOOL 值TRUE 和FALSE 对应? ? q行~程。大多数~程语言FALSE定义?QQ何非0 值都是TRUE。Visual C++TRUE 定义?Q而Visual Basic 则将TRUE定义?1。示例程序如下:

BOOL flag;

?/P>

if(flag) { // do something } // 正确的用?/P>

if(flag==TRUE) { // do something } // 危险的用?/P>

if(flag==1) { // do something } // 危险的用?/P>

if(!flag) { // do something } // 正确的用?/P>

if(flag==FALSE) { // do something } // 不合理的用法

if(flag==0) { // do something } // 不合理的用法

Q?Q小心不要将? =”写成?”,~译器不会自动发现这U错误?/P>

Q?Q不要将123 写成0123Q后者是八进制的数倹{?/P>

Q?Q将自己l常犯的~程错误记录下来Q制成表D在计机旁边?/P>

3 结

C++/C E序设计如同林寺的武功一样博大精深,我练? q_大概只学C三成。所以无Z么时候,都不要觉得自q~程水^天下W一Q看到别人好的技术和风格Q要虚心学习。本章的内容得可怜,p口时只l你一颗杨梅吃Q你一定不q瘾。我借花献佛Q推荐一本好书:Marshall P. Cline 著的《C++ FAQs》[Cline 1995]。你看了后一定会赞不l口。会~写C++/C E序Q不要因此得意洋z,q只是程序员基本的技能要求而已。如果把pȝ分析和系l设计比作“战略决{”,那么~程充其量只是“战术”。如果指挥官是个大笨蛋,士兵再勇敢也会吃败仗。所以我们程序员不要只把眼光盯在E序上,要让自己博学多才。我们应该向北京胡同里的孩们学习,他们小q纪p指点江山Q评Z界大事?/P>

力力力 2005-09-09 10:59 发表评论
]]>
c++ primer W记Q名字空_http://www.tkk7.com/faintbear/archive/2005/07/11/7496.html力力力力力力Mon, 11 Jul 2005 05:24:00 GMThttp://www.tkk7.com/faintbear/archive/2005/07/11/7496.htmlhttp://www.tkk7.com/faintbear/comments/7496.htmlhttp://www.tkk7.com/faintbear/archive/2005/07/11/7496.html#Feedback1http://www.tkk7.com/faintbear/comments/commentRss/7496.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/7496.html8.5关于名字I间定义
    全局实体(global entity)
    全局名字I间污染(global namespace pollution)

8.5.1名字I间定义
    namespace 开_后面是名字空间的名字?/FONT>

    namespace cplusplus_primer {
     class matrix {/*****/};
     void inverse(matrix &);
     matrix operator+(const matrix &m1,matrix &m2)
     {
      /******/
     }
     const double pi = 3.1416;
   
    }


    在名字空间cplusplus_primer中声明的cȝ名字?BR>    cplusplus_primer::matrix
    函数的名字是
    cplusplus_primer::inverse()
    帔R的名字是
    cplusplus_primer::pi

    c,函数Q常量被声明它的名字I间的名字限定修饎ͼ
    q些名字被成为限定修饰符(qualified name)


    名字I间的定义不一定是q箋?例如
    namespace cplusplus_primer{
    class  matrix {/*****/}
    const double pi = 3.1416;

    }

    namespace cplusplus_primer{
    void inverse(matrix &);
    matrix operator+ (const matrix &m1,const matrix &m2)
        {/********/}
    }

    名字I间的定义可是非q箋的,q对生成一个库很有帮助,它我们更容易将库的源代码组l成
    接口和实现部分?/FONT>

 8.5.2域操作符
     ::
     用户声明的名字空间成员名自动被加上前~Q名字空间名后面加上域操作符(::),名字I间成员?BR>     p名字I间名进行限定修饰?/FONT>

     名字I间成员的声明被隐藏在其名字I间中,除非我们为编译器指定查找的声明的名字I间,否则
     ~译器将在当前域及嵌套包含当前域的域中查找该名字的声明?/FONT>

     注意Q!Q?BR>     域操作符也可以被用来引用全局名字I间的成员。因为全局名字I间没有名字?BR>     ::member_name
     指的是全局名字I间的成员?/FONT>

     #include <iostream>
     const int max = 65000;
     const int lineLength = 12;

     void fibonacci(int max)
     {
        if (max <2) return;
 cout << "0 1";
 int v1 = 0,v2=1,cur;
 for (int ix=3;ix <= max;++ix)
 {
     cur = v1+v2;
     if(cur>::max) break;   //引用全局名字I间的变量;
     cout << cur <<"";
     v1=v2;
     v2=cur;
     if(ix % lineLength ==0) cout << endl;
 }
     }

8.5.3 嵌套名字I间
     
      。。。。?/FONT>


8.6 使用名字I间成员

     使用限定修饰的名字Ş式namespace_name::member_name来引用名字空_毫无疑问是非帔R烦的?/FONT>

     using 声明Qusing指示W?/FONT>

8.6.1 名字I间别名
    
     namespace International_Business_Machines
     {/*********/}

     namespace IBM = International_Business_Machines;

8.6.2 using声明
   
    
      namespace cplusplus_primer
      {
        namespace MatrixLib
        {
         class matrix {/******/};
        }
      }

      
      using cplusplus::MatrixLib::matrix;
     

 

      using 声明引入的名字有以下Ҏ:
       1> 它在该域中必d一?BR>       2> 由外围域中声明引入的相同名字被其隐藏?BR>       3> 它被嵌套域中的相同名字的声明隐藏?/FONT>


       namespace blip {
        int bi = 16,bj = 15, bk = 23;

       }

       int bj = 0;

       void mainip()
       {
         using blip::bi;    //函数mainip()中的bi指向blip::bi
  ++bi;              //讄blip::bi?7
  using blip::bj     //隐藏全局域中的bj
  ++bj;
  int bk;            //bk在局部域中声?BR>  using blip:bk;     //错误:在mainip()中重复定义bk


       }

       int wrongInit = bk; //错误Qbk在这里不可见


8.6.3 using 指示W?/FONT>


       namespace blip{
       int bi = 16,bj = 15, bk = 23;
       }

       int bj = 0;

       void mainip()
       {
         using namespace blip;

  ++bi;       //讄blip::bi?7;
  ++bj;        //错误Q二义?BR>                 全局bjq是blip:bj?

         ++::bj;     // 讄全局bj?  
  ++blip::bj; // 讄blip::bj?6
  int bk = 97; //局部bk隐藏blip:bk
  ++bk;        //讄局部bk?8

      
       }

 

当我们把一个应用程序移植到一个包装在名字I间中的新版本时Qusing指示W非?BR>有用Q但是用多个using指示W会引v全局名字I间污染问题?BR>用多个选择性的using声明来代替using指示W会使这个问题最化Q由多个using
声明引v的二义性的错误在声明点p被检到Q因此徏议用using声明而不?BR>using指示W?以便更好地控制程序中地全局名字I间污染问题?BR>      

 


 



力力力 2005-07-11 13:24 发表评论
]]>
内存理<?gt;http://www.tkk7.com/faintbear/archive/2005/07/11/7495.html力力力力力力Mon, 11 Jul 2005 05:20:00 GMThttp://www.tkk7.com/faintbear/archive/2005/07/11/7495.htmlhttp://www.tkk7.com/faintbear/comments/7495.htmlhttp://www.tkk7.com/faintbear/archive/2005/07/11/7495.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/7495.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/7495.html2004q?11?3?
17 : 07 内存理

节选自《高质量C++~程指南》中的《内存管理》,q对其进行简单整?.....


内存分配方式
---------------------------------------------------------------------------------------------

内存分配方式有三U:
---------------------------
Q?Q?从静态存储区域分配。内存在E序~译的时候就已经分配好,q块内存在程序的整个q行期间都存在?例如全局变量static变量?BR>Q?Q?在栈上创建。在执行函数Ӟ函数内局部变量的存储单元都可以在栈上创徏Q函数执行结束时q些存储 单元自动被释放。栈内存 分配q算内置于处理器的指令集中,效率很高Q但是分配的内存定w有限?BR>Q?Q?从堆上分配,亦称动态内存分配。程序在q行的时候用malloc或new甌L多少的内存,E序员自p责在何时用free或delete释放内存。动态内存的生存期由我们军_Q用非常灵z,但问题也最多?/FONT>

常见的内存错误及其对{?BR>------------------------------------------------------------------------------------------
 发生内存错误是g非常ȝ的事情。编译器不能自动发现q些错误Q通常是在E序q行时才能捕捉到。而这些错误大多没有明昄症状Q时隐时玎ͼ增加了改错的隑ֺ。有时用h气冲冲地把你找来,E序却没有发生Q何问题,你一赎ͼ错误又发作了?/FONT>

常见的内存错误及其对{如下:

1>内存分配未成功,却用了它?BR>------------------------------------------
 ~程新手常犯q种错误Q因Z们没有意识到内存分配会不成功。常用解军_法是Q在使用内存之前 指针是否为NULL。如果指针p是函数的参数Q那么在函数的入口处用assert(p!=NULL)q行查。如?nbsp; 是用malloc或new来申请内存,应该用if(p==NULL) 或if(p=NULL)q行防错处理?/FONT>

2>内存分配虽然成功Q但是尚未初始化引用它
------------------------------------------
 犯这U错误主要有两个起因Q一是没有初始化的观念;二是误以为内存的~省初值全为零Q导致引用初 错误Q例如数l)?BR>内存的缺省初值究竟是什么ƈ没有l一的标准,管有些时候ؓ零|我们宁可信其无不可信其有。所 以无论用何种方式创徏数组Q都别忘了赋初|即便是赋零g不可省略Q不要嫌ȝ?/FONT>

3>内存分配成功q且已经初始化,但操作越q了内存的边界?BR>------------------------------------------
 例如在用数l时l常发生下标"?"或??"的操作。特别是在for循环语句中,循环ơ数很容易搞错,D数组操作界?/FONT>

4>忘记了释攑ֆ存,造成内存泄露?BR>------------------------------------------
 含有q种错误的函数每被调用一ơ就丢失一块内存。刚开始时pȝ的内存充I你看不到错误。终有一ơ程序突然死掉,pȝ出现提示Q内存耗尽。动态内存的甌与释攑ֿ配对,E序中malloc与free的用次C定要相同Q否则肯定有错误Qnew/delete同理Q?/FONT>

5>释放了内存却l箋使用它?BR>------------------------------------------
有三U情况:
Q?Q程序中的对象调用关p过于复杂,实在难以搞清楚某个对象究竟是否已l释放了内存Q此时应该重新设计数据结构,从根本上解决对象理的؜乱局面?BR>Q?Q函数的return语句写错了,注意不要q回指向"栈内??指针"或?引用"Q因内存在函Cl束时被自动销毁?BR>Q?Q用free或delete释放了内存后Q没有将指针讄为NULL。导致?野指??/FONT>

【规?】用malloc或new甌内存之后Q应该立x查指针值是否ؓNULL。防止用指针gؓNULL的内存?BR>【规?】不要忘Cؓ数组和动态内存赋初倹{防止将未被初始化的内存作ؓ叛_g用?BR>【规?】避免数l或指针的下标越界,特别要当心发??"或??"操作?BR>【规?】动态内存的甌与释攑ֿ配对,防止内存泄漏?BR>【规?】用free或delete释放了内存之后,立即指针设|ؓNULLQ防止?野指??/FONT>

------------------------------------------------------------------------------------
指针与数l的Ҏ
-----------------
 C++/CE序中,指针和数l在不少地方可以怺替换着用,让h产生一U错觉,以ؓ两者是{h的?BR>    
数组
-------------
数组要么在静态存储区被创建(如全局数组Q,要么在栈上被创徏。数l名对应着Q而不是指向)一块内存,其地址与容量在生命期内保持不变Q只有数l的内容可以改变?/FONT>

指针
--------------
指针可以随时指向Lcd的内存块Q它的特征是"可变"Q所以我们常用指针来操作动态内存。指针远比数l灵z,但也更危险?/FONT>

下面以字W串Z比较指针与数l的Ҏ?/FONT>

1.修改内容
------------------------------------------
 CZ1中,字符数组a的容量是6个字W,其内容ؓhello\0。a的内容可以改变,如a[0]= 'X'?BR>指针p指向帔R字符?world"Q位于静态存储区Q内容ؓworld\0Q,帔R字符串的内容是不可以被修改的?BR>从语法上看,~译器ƈ不觉得语句p[0]= ‘X'有什么不妥,但是该语句企图修改常量字W串的内容而导致运行错误?BR> 

 CZ7-3-1 修改数组和指针的内容
   ------------------------------
   char a[] = "hello";
   a[0] = 'X';
   cout << a << endl;
   char *p = "world"; // 注意p指向帔R字符?nbsp; 
   p[0] = 'X';  // ~译器不能发现该错误cout << p << endl;
  

2.内容复制与比?BR>------------------------------------------
数组复制
--------
不能Ҏl名q行直接复制与比较。示?中,若想把数la的内容复制给数组bQ不能用语句 b = a Q否则将产生~译错误。应该用标准库函数strcpyq行复制。同理,比较b和a的内Ҏ否相同,不能用if(b==a) 来判断,应该用标准库函数strcmpq行比较?/FONT>

指针复制
--------
语句p = a q不能把a的内容复制指针pQ而是把a的地址赋给了p。要惛_制a的内容,可以先用库函数malloc为p甌一块容量ؓstrlen(a)+1个字W的内存Q再用strcpyq行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址Q应该用库函数strcmp来比较?/FONT>

  CZ2 数组和指针的内容复制与比?BR>  -----------------------------------
        // 数组...
  char a[] = "hello";
  char b[10];
  strcpy(b, a); //不能用b = a;
  if(strcmp(b, a) == 0) // 不能?nbsp; if (b == a)...
  // 指针...
  int len = strlen(a);
  char *p = (char *)malloc(sizeof(char)*(len+1));  
  strcpy(p,a); // 不要?p = a;
  if(strcmp(p, a) == 0) // 不要?if (p == a)...

 

3 计算内存定w
------------------------------------------
数组
----
用运符sizeof可以计算出数l的定wQ字节数Q?BR>CZ3中,sizeof(a)的值是12Q注意别忘了'\0'Q?/FONT>

指针
----
指针p指向aQ但是sizeof(p)的值却?。这是因为sizeof(p)得到的是一个指针变量的字节敎ͼ
相当于sizeof(char*)Q而不是p所指的内存定w?/FONT>

C++/C语言没有办法知道指针所指的内存定wQ除非在甌内存时记住它?/FONT>

注意
----
当数l作为函数的参数q行传递时Q该数组自动退化ؓ同类型的指针?BR>CZ3中,不论数组a的容量是多少Qsizeof(a)始终{于sizeof(char *)?BR>      
       CZ3 计算数组和指针的内存定w
 -----------------------------
        char a[] = "hello world";
 char *p  = a; cout<< sizeof(a) << endl;// 12字节
 cout<< sizeof(p) << endl;// 4字节

 CZ3QbQ?数组退化ؓ指针
 -----------------------------
 void Func(char a[100])
 {
  cout<< sizeof(a) << endl;// 4字节而不?00字节
   }

4指针参数是如何传递内存的Q?BR>------------------------------------------
如果函数的参数是一个指针,不要指望用该指针ȝ请动态内存?BR>CZ4-1中,Test函数的语句GetMemory(str, 200)q没有str获得期望的内存,str依旧是NULLQؓ什么?

 CZ4-1 试图用指针参数申请动态内?BR> ----------------------------------
 void GetMemory(char *p, int num)
 {
  p = (char *)malloc(sizeof(char) * num);
 }
 void Test(void)
 {
  char *str = NULL;
  GetMemory(str, 100);// str 仍然?NULL
  strcpy(str, "hello"); // q行错误
 }

毛病出在函数GetMemory中。编译器L要ؓ函数的每个参数制作时副本,
指针参数p的副本是 _pQ编译器?_p = p。如果函C内的E序修改了_p的内容,
导致参数p的内容作相应的修攏V这是指针可以用作输出参数的原因?BR>在本例中Q_p甌了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变?BR>所以函数GetMemoryq不能输ZQ何东ѝ?BR>事实上,每执行一ơGetMemory׃泄露一块内存,因ؓ没有用free释放内存?/FONT>

如果非得要用指针参数ȝ请内存,那么应该改用"指向指针的指?Q见CZ4-2?/FONT>

 CZ4-2用指向指针的指针甌动态内?BR> ------------------------------------
 void GetMemory2(char **p, int num)
 {
  *p = (char *)malloc(sizeof(char) * num);
 }
 void Test2(void)
 {
  char *str = NULL;
  GetMemory2(&str, 100); // 注意参数?&strQ而不是str
  strcpy(str, "hello");
  cout<< str << endl;
  free(str);
 }


׃"指向指针的指?q个概念不容易理解,我们可以用函数返回值来传递动态内存?BR>q种Ҏ更加单,见示?-3?/FONT>

 CZ4-3 用函数返回值来传递动态内?BR> -----------------------------------
 char *GetMemory3(int num)
 {
  char *p = (char *)malloc(sizeof(char) * num);
  return p;
 }
 void Test3(void)
 {
  char *str = NULL;
  str = GetMemory3(100);
  strcpy(str,"hello");
  cout<< str << endl;
  free(str);
 }


用函数返回值来传递动态内存这U方法虽然好用,但是常常有h把return语句用错了。这里强调不要用return语句q回指向"栈内?的指针,因ؓ该内存在函数l束时自动消亡,见示?-4?/FONT>

 CZ4-4 return语句q回指向"栈内?的指?BR> --------------------------------------------
 char *GetString(void)
 {
  char p[] = "hello world";
  return p;// ~译器将提出警告
 }
 void Test4(void)
 {
  char *str = NULL;str = GetString();// str 的内Ҏ垃圾
  cout<< str << endl;
 }


用调试器逐步跟踪Test4Q发现执行str = GetString语句后str不再是NULL指针Q?BR>但是str的内容不?hello world"而是垃圾?/FONT>

如果把示?-4改写成示?-5Q会怎么P

 CZ4-5 return语句q回帔R字符?BR> -----------------------------------
 char *GetString2(void)
 {
  char *p = "hello world";
  return p;
 }
 void Test5(void)
 {
  char *str = NULL;
  str = GetString2();
  cout<< str << endl;
 }


函数Test5q行虽然不会出错Q但是函数GetString2的设计概念却是错误的?BR>因ؓGetString2内的"hello world"是常量字W串Q位于静态存储区Q它在程序生命期内恒定不变?BR>无论什么时候调用GetString2Q它q回的始l是同一?只读"的内存块?/FONT>

5 free和delete把指针怎么啦?
------------------------------------------
别看free和delete的名字恶狠狠的(其是deleteQ,
它们只是把指针所指的内存l释放掉Q但q没有把指针本nq掉?BR>用调试器跟踪CZ5Q发现指针p被free以后其地址仍然不变Q非NULLQ,
只是该地址对应的内存是垃圾Qp成了"野指??BR>如果此时不把p讄为NULLQ会让h误以为p是个合法的指针?BR>如果E序比较长,我们有时C住p所指的内存是否已经被释放,
在l用p之前Q通常会用语句if (p !=NULL)q行防错处理?BR>很遗憾,此时if语句起不到防错作用,因ؓ即便p不是NULL指针Q?BR>它也不指向合法的内存块?/FONT>

 CZ5  p成ؓ野指?BR> --------------------
 char *p = (char *) malloc(100);
 strcpy(p, "hello");
 free(p);        // p 所指的内存被释放,但是p所指的地址仍然不变 ...
 if(p != NULL) // 没有起到防错作用
 {      strcpy(p, "world"); // 出错
 }

6 动态内存会被自动释攑֐Q?BR>------------------------------------------
函数体内的局部变量在函数l束时自动消亡。很多h误以为示?是正的?BR>理由是p是局部的指针变量Q它消亡的时候会让它所指的动态内存一起完蛋。这是错觉!

 CZ7-6 试图让动态内存自动释?BR> ------------------------------
 void Func(void)
 {
  char *p = (char *) malloc(100); // 动态内存会自动释放吗?
 }


 我们发现指针有一?似是而非"的特征:
 ------------------------------------
Q?Q指针消亡了Qƈ不表C它所指的内存会被自动释放?BR>Q?Q内存被释放了,q不表示指针会消亡或者成了NULL指针?BR>     q表明释攑ֆ存ƈ不是一件可以草率对待的事。也许有Z服气Q?BR>     一定要扑և可以草率行事的理由:
     如果E序l止了运行,一切指针都会消亡,动态内存会被操作系l回收?BR>     既然如此Q在E序临终前,可以不必释攑ֆ存、不必将指针讄为NULL了?BR>     l于可以h而不会发生错误了吧?
 惛_。如果别人把那段E序取出来用到其它地Ҏ么办?

7 杜绝"野指?
------------------------------------------
"野指?不是NULL指针Q是指向"垃圾"内存的指针?BR>Z一般不会错用NULL指针Q因为用if语句很容易判断?BR>但是"野指?是很危险的,if语句对它不v作用?/FONT>

"野指?的成因主要有两种Q?/FONT>

Q?Q指针变量没有被初始化。Q何指针变量刚被创建时不会自动成ؓNULL指针Q?BR>它的~省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化Q?BR>要么指针设|ؓNULLQ要么让它指向合法的内存。例?BR> char *p = NULL;
 char *str = (char *) malloc(100);

Q?Q指针p被free或者delete之后Q没有置为NULLQ让以ؓp是个合法的指针。参?.5节?/FONT>

Q?Q指针操作超了变量的作用范围。这U情况让人防不胜ԌCZE序如下Q?BR> class A
 {
 public:
  void Func(void){ cout << "Func of class A" << endl;
  }

 void Test(void)
 {
 A  *p;
 {
  A  a;
  p = &a; // 注意 a 的生命期
 }
  p->Func();// p?野指?
 }

函数Test在执行语句p->Func()Ӟ对象a已经消失Q而p是指向a的,所以p成?野指??BR>但奇怪的是我q行q个E序时居然没有出错,q可能与~译器有兟?/FONT>

8 有了malloc/freeZ么还要new/delete Q?BR>------------------------------------------
malloc与free是C++/C语言的标准库函数Qnew/delete是C++的运符?BR>它们都可用于甌动态内存和释放内存?/FONT>

对于非内部数据类型的对象而言Q光用maloc/free无法满动态对象的要求?BR>对象在创建的同时要自动执行构造函敎ͼ对象在消亡之前要自动执行析构函数?BR>׃malloc/free是库函数而不是运符Q不在编译器控制权限之内Q?BR>不能够把执行构造函数和析构函数的Q务强加于malloc/free?BR> 
因此C++语言需要一个能完成动态内存分配和初始化工作的q算WnewQ以及一个能完成清理与释攑ֆ存工作的q?/FONT>

符delete。注意new/delete不是库函数?BR>我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示??/FONT>

 CZ8 用malloc/free和new/delete如何实现对象的动态内存管?BR> -------------------------------------------------------------
 class Obj
 {
 public :
  Obj(void){ cout << "Initialization" << endl; }
  ~Obj(void){cout << "Destroy" << endl; }
  void Initialize(void){ cout << "Initialization" << endl; }
  void    Destroy(void){ cout << "Destroy" << endl; }
 };
 void UseMallocFree(void)
 {
  Obj  *a = (obj *)malloc(sizeof(obj)); // 甌动态内?
  a->Initialize(); // 初始?BR>  a->Destroy(); // 清除工作
  free(a);// 释放内存
 }
 void UseNewDelete(void)
 {
  Obj  *a = new Obj;// 甌动态内存ƈ且初始化
  delete a;// 清除q且释放内存
 }

cObj的函数Initialize模拟了构造函数的功能Q函数Destroy模拟了析构函数的功能?BR>函数UseMallocFree中,׃malloc/free不能执行构造函C析构函数Q?BR>必须调用成员函数Initialize和Destroy来完成初始化与清除工作?BR>函数UseNewDelete则简单得多?/FONT>

所以我们不要企囄malloc/free来完成动态对象的内存理Q应该用new/delete?BR>׃内部数据cd?对象"没有构造与析构的过E,对它们而言malloc/free和new/delete是等L?BR> 
既然new/delete的功能完全覆盖了malloc/freeQؓ什么C++不把malloc/free淘汰出局呢?
q是因ؓC++E序l常要调用C函数Q而CE序只能用malloc/free理动态内存?/FONT>

如果用free释放"new创徏的动态对?Q那么该对象因无法执行析构函数而可能导致程序出错?BR>如果用delete释放"malloc甌的动态内?Q理Z讲程序不会出错,但是该程序的可读性很差?BR>所以new/delete必须配对使用Qmalloc/free也一栗?/FONT>

9 内存耗尽怎么办?
------------------------------------------
如果在申请动态内存时找不到够大的内存块malloc和new返回NULL指针Q宣告内存申请失败?BR>通常有三U方式处?内存耗尽"问题?/FONT>

Q?Q判断指针是否ؓNULLQ如果是则马上用return语句l止本函数。例如:
 void Func(void)
 {
  A  *a = new A;
  if(a == NULL)
  {
   return;
  }
 ...
 }

Q?Q判断指针是否ؓNULLQ如果是则马上用exit(1)l止整个E序的运行。例如:
 void Func(void)
 {
  A  *a = new A;
  if(a == NULL)
  {
   cout << "Memory Exhausted" << endl;
   exit(1);
  }
  ...
 }

Q?Qؓnew和malloc讄异常处理函数?/FONT>

例如Visual C++可以用_set_new_hander函数为new讄用户自己定义的异常处理函敎ͼ
也可以让malloc享用与new相同的异常处理函数。详l内容请参考C++使用手册?/FONT>

上述Q?Q(2Q方式用最普遍。如果一个函数内有多处需要申请动态内存,
那么方式Q?Q就昑־力不从心Q释攑ֆ存很ȝQ,应该用方式(2Q来处理?/FONT>

很多Z忍心用exit(1)Q问Q?不编写出错处理程序,让操作系l自p册不行Q?
不行。如果发?内存耗尽"q样的事情,一般说来应用程序已l无药可救?BR>如果不用exit(1) 把坏E序杀死,它可能会x操作pȝ?BR>道理如同Q如果不把歹徒击毙,歹徒在老死之前会犯下更多的|?/FONT>

有一个很重要的现象要告诉大家。对?2位以上的应用E序而言
Q无论怎样使用malloc与newQ几乎不可能D"内存耗尽"?BR>我在Windows 98下用Visual C++~写了测试程序,见示??BR>q个E序会无休止地运行下去,Ҏ不会l止?BR>因ؓ32位操作系l支?虚存"Q内存用完了Q自动用盘I间替?BR>我只听到盘嘎吱嘎吱地响QWindow 98已经累得寚w盘、鼠标毫无反应?/FONT>

我可以得么一个结论:对于32位以上的应用E序Q?内存耗尽"错误处理E序毫无用处。这下可把Unix和WindowsE序员们乐坏了:反正错误处理E序不v作用Q我׃写了Q省了很多麻烦?/FONT>

我不惌D者,必须Q不加错误处理将DE序的质量很差,千万不可因小失大?/FONT>

  CZ9试图耗尽操作pȝ的内?BR>  void main(void)
  {
   float *p = NULL;
   while(TRUE)
   { 
    p = new float[1000000]; 
    cout << "eat memory" << endl; 
    if(p==NULL) exit(1);
   }
  }

10 malloc/free 的用要?BR>------------------------------------------
 函数malloc的原型如下:
  void * malloc(size_t size);
 用malloc甌一块长度ؓlength的整数类型的内存Q程序如下:
  int  *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上Q?cd转换"?sizeof"?BR> mallocq回值的cd是void *Q所以在调用malloc时要昑ּ地进行类型{换,
void * 转换成所需要的指针cd?BR> malloc函数本nq不识别要申L内存是什么类型,它只兛_内存的d节数?BR>我们通常C住int,float{数据类型的变量的确切字节数。例如int变量?6位系l下?个字?BR>Q在32位下?个字节;而float变量?6位系l下?个字节,?2位下也是4个字节?/FONT>

最好用以下E序作一ơ测试:
cout << sizeof(char) << endl;
cout << sizeof(int) << endl;
cout << sizeof(unsigned int) << endl;
cout << sizeof(long) << endl;
cout << sizeof(unsigned long) << endl;
cout << sizeof(float) << endl;
cout << sizeof(double) << endl;
cout << sizeof(void *) << endl;
 
在malloc?()"中用sizeofq算W是良好的风|但要当心有时我们会昏了头Q写?p = malloc(sizeof(p))q样的程序来?/FONT>

 函数free的原型如下:

void free( void * memblock );
 
Z么free函数不象malloc函数那样复杂呢?q是因ؓ指针p的类型以及它所指的内存的容量事先都是知道的Q语句free(p)能正地释放内存。如果p是NULL指针Q那么free对p无论操作多少ơ都不会出问题?BR>如果p不是NULL指针Q那么free对pq箋操作两次׃DE序q行错误?/FONT>

11 new/delete 的用要?BR>------------------------------------------
 
q算Wnew使用h要比函数malloc单得多,例如Q?BR>int  *p1 = (int *)malloc(sizeof(int) * length);
int  *p2 = new int[length];
q是因ؓnew内置了sizeof、类型{换和cd安全查功能。对于非内部数据cd的对象而言Q?BR>new在创建动态对象的同时完成了初始化工作?BR>如果对象有多个构造函敎ͼ那么new的语句也可以有多UŞ式?BR>例如
class Obj
{
public :
 Obj(void);  // 无参数的构造函?BR> Obj(int x);  // 带一个参数的构造函?BR>...
}
void Test(void)
{
 Obj  *a = new Obj;
 Obj  *b = new Obj(1); // 初gؓ1
 ...
 delete a;
 delete b;
}
如果用new创徏对象数组Q那么只能用对象的无参数构造函数。例?BR> Obj  *objects = new Obj[100]; // 创徏100个动态对?BR>不能写成
 Obj  *objects = new Obj[100](1);// 创徏100个动态对象的同时赋初?
在用delete释放对象数组Ӟ留意不要丢了W号‘[]'。例?BR> delete []objects; // 正确的用?BR>delete objects; // 错误的用?BR>后者相当于delete objects[0]Q漏掉了另外99个对象?/FONT>

12 一些心得体?BR>------------------------------------------
Q?Q越是怕指针,p要用指针。不会正用指针,肯定不上是合格的程序员?BR>Q?Q必d?使用调试器逐步跟踪E序"的习惯,只有q样才能发现问题的本质?/FONT>

最后,说一句:"呵呵Q有炚wQ不知道大家有没有时间把它看完?"


 



力力力 2005-07-11 13:20 发表评论
]]>
说明以下关键字的作用 auto static register const volatile externhttp://www.tkk7.com/faintbear/archive/2005/06/22/6521.html力力力力力力Wed, 22 Jun 2005 02:39:00 GMThttp://www.tkk7.com/faintbear/archive/2005/06/22/6521.htmlhttp://www.tkk7.com/faintbear/comments/6521.htmlhttp://www.tkk7.com/faintbear/archive/2005/06/22/6521.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/6521.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/6521.html说明以下关键字的作用 (? 

auto  static?/FONT>  register  const   volatile   extern

http://blog.yesky.com/Blog/xioxu/archive/2005/06/03/138294.html

(1)auto

  q个q个关键字用于声?SPAN class=hilite3>变量的生存期动,卛_不在McR结构、枚举、联合和函数中定义的变量视ؓ全局变量Q而在函数中定义的变量视ؓ局?SPAN class=hilite3>变量。这个关键字不怎么多写Q因为所有的变量默认是auto的?

(2)register

  q个关键字命令编译器可?/FONT>的将变量存在CPU内部寄存器中而不是通过内存d讉K以提高效率?

(3)static

  常见的两U用?
    1>l计函数被调用的ơ数;
    2>减少局部数l徏立和赋值的开销.变量的徏立和赋值是需要一定的处理器开销的,特别是数l等含有较多元素的存储类型。在一些含有较多的变量q且被经常调用的函数中,可以一些数l声明ؓstaticcdQ以减少建立或者初始化q些变量的开销.

  详细说明:
    1>?SPAN class=hilite3>变量会被攑֜E序的全局存储ZQ这样可以在下一ơ调用的时候还可以保持原来的赋倹{这一Ҏ它与堆栈变量和堆变量的区别?
    2>?SPAN class=hilite3>变量用static告知~译器,自己仅仅?SPAN class=hilite3>变量的作用范围内可见。这一Ҏ它与全局变量的区别?
    3>当static用来修饰全局变量Ӟ它就改变了全局变量的作用域Q其不能被别的E序externQ限制在了当前文仉Q但是没有改变其存放位置Q还是在全局静态储存区?/FONT>

  使用注意:
    1>若全局变量仅在单个C文g中访问,则可以将q个变量修改为静态全局变量Q以降低模块间的耦合度;
    2>若全局变量仅由单个函数讉KQ则可以这?SPAN class=hilite3>变量改ؓ该函数的静态局?SPAN class=hilite3>变量Q以降低模块间的耦合度;
    3>设计和用访问动态全局变量、静态全局变量、静态局?SPAN class=hilite3>变量的函数时Q需要考虑重入问题(只要输入数据相同应产生相同的输??

(4)const

  ?SPAN class=hilite1>const修饰的东襉K受到强制保护Q可以预防意外的变动Q能提高E序的健壮性。它可以修饰函数的参数、返回|甚至函数的定义体?

  作用:
    1>修饰输入参数
      a.对于非内部数据类型的输入参数Q应该将“g递”的方式改ؓ?SPAN class=hilite1>const引用传递”,目的是提高效率。例如将void Func(A a) 改ؓvoid Func(const A &a)?
      b.对于内部数据cd的输入参敎ͼ不要“g递”的方式改ؓ?SPAN class=hilite1>const引用传递”。否则既达不到提高效率的目的Q又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)?
    2>?SPAN class=hilite1>const修饰函数的返回?
      a.如果l以“指针传递”方式的函数q回值加const修饰Q那么函数返回|x针)的内容不能被修改Q该q回值只能被赋给?SPAN class=hilite1>const修饰的同cd指针?
       如对于: const char * GetString(void);
       如下语句出现编译错误:
        char *str = GetString();//cannot convert from 'const char *' to 'char *'Q?
       正确的用法是Q?
       const char *str = GetString();
      b.如果函数q回值采用“g递方式”,׃函数会把q回值复制到外部临时的存储单元中Q加const修饰没有M价倹{?如不要把函数int GetInt(void) 写成const int GetInt(void)?
    3>const成员函数的声明中Q?SPAN class=hilite1>const关键字只能放在函数声明的N,表示该类成员不修改对?

   说明Q?
    const type m; //修饰mZ可改?
   CZQ?
    typedef char * pStr; //新的cdpStr;
    char string[4] = "abc";
    const char *p1 = stringQ?
    p1++; //正确Q上边修饰的?p1,p1可变
    const pStr p2 = string;
    p2++; //错误Q上边修饰的是p2Qp2不可?*p2可变
   同理Q?SPAN class=hilite1>const修饰指针时用此原则判断就不会h了?
    const int *value; //*value不可变,value可变
    int* const value; //value不可变,*value可变
    const (int *) value; //(int *)是一Utype,value不可?*value可变
              //逻辑上这L解,~译不能通过Q需要tydef int* NewType;
    const int* const value;//*value,value都不可变

(5)volatile

  表明某个变量的值可能在外部被改变,优化器在用到q个变量时必Lơ都心地重新读取这?SPAN class=hilite3>变量的|而不是用保存在寄存器里的备份。它可以适用于基cd如:int,char,long......也适用?SPAN class=hilite1>C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,l构或者类的所有成员都会被视ؓvolatile.
  该关键字在多U程环境下经怋用,因ؓ在编写多U程的程序时Q同一?SPAN class=hilite3>变量可能被多个线E修改,而程序通过?SPAN class=hilite3>变量同步各个U程?
  单示例:
   DWORD __stdcall threadFunc(LPVOID signal)
   {
     int* intSignal=reinterpret_cast(signal);
     *intSignal=2;
     while(*intSignal!=1)
     sleep(1000);
     return 0;
   }
  该线E启动时intSignal |ؓ2Q然后@环等待直到intSignal ? 旉出。显然intSignal的值必d外部被改变,否则该线E不会退出。但是实际运行的时候该U程却不会退出,即在外部将它的值改?Q看一下对应的伪汇~代码就明白了:
     mov ax,signal
     label:
     if(ax!=1)
     goto label
  对于C~译器来_它ƈ不知道这个g被其他线E修攏V自然就把它cache在寄存器里面?SPAN class=hilite1>C ~译器是没有U程概念?q时候就需要用到volatile。volatile 的本意是指:q个值可能会在当前线E外部被改变。也是_我们要在threadFunc中的intSignal前面加上volatile关键字,q时候,~译器知道该变量的g在外部改变,因此每次讉K?SPAN class=hilite3>变量时会重新dQ所作的循环变ؓ如下面伪码所C:
     label:
     mov ax,signal
     if(ax!=1)
     goto label

  注意Q?/FONT>一个参数既可以?SPAN class=hilite1>const同时是volatileQ是volatile因ؓ它可能被意想不到地改变。它?SPAN class=hilite1>const因ؓE序不应该试囑֎修改它?

(6)extern

  extern 意ؓ“外来的”···它的作用在于告诉编译器Q有q个变量Q它可能不存在当前的文g中,但它肯定要存在于工程中的某一个源文g中或者一个Dll的输Z?/FONT>



力力力 2005-06-22 10:39 发表评论
]]>
memseth Q{Q?/title><link>http://www.tkk7.com/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.tkk7.com/faintbear/archive/2005/06/21/6485.html</guid><wfw:comment>http://www.tkk7.com/faintbear/comments/6485.html</wfw:comment><comments>http://www.tkk7.com/faintbear/archive/2005/06/21/6485.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/faintbear/comments/commentRss/6485.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/faintbear/services/trackbacks/6485.html</trackback:ping><description><![CDATA[<P> </P> <P>memseth </P> <P> <BR>作者:tianzhushan     发表旉Q?002/09/03 09:08am<BR> <BR>版主Q?BR>   请问Q?BR>   memset ,memcpy 和strcpy 的根本区别?<BR>   我总觉得memset,memcpy 可以用strcpy{代?nbsp; <BR> </P> <P>--------------------------------------------------------------------------------<BR>此文章相兌? <BR> <BR>该文章有15个相兌论如下:(点这儿可以发表评?<BR> <BR>zhuhuix 发表于: 2002/09/03 09:14am<BR> <BR>char str[100];<BR>memset(str,0,100);<BR>memcpy(str,"This is a",9);<BR>strcpy(&str[9],"example!"); <BR>  <BR>yuejw 发表于: 2002/09/03 09:14am<BR> <BR>它们用处不同Q但大部分情况下可以完成相同的要求?BR>memset用来对一D内存空间全部设|ؓ某个字符?/P> <P>memcpy用来做内存拷贝,你可以拿它拷贝Q何数据类型的对象?/P> <P>strcpy只能拷贝字W串了,它遇?\0'q束拷贝?BR> <BR>  <BR>HopeCao 发表于: 2002/09/03 10:02am<BR> <BR>很详l! <BR>  <BR>tianzhushan 发表于: 2002/09/03 05:37pm<BR> <BR>各位Q?nbsp; <BR>   能否p三者的最典型的应用各举一个例子?BR>   l合例子Q说说他们的区别和联p,以及Z么用q个而不用另外一个?BR>   详l越好?BR>   谢谢Q?<BR>  <BR>betydu 发表于: 2002/09/04 09:21am<BR> <BR>对版主yuejw的补?BR>memset用来对一D内存空间全部设|ؓ某个字符Q一般用在对定义的字W串q行初始化ؓ?’或‘\0’;?char a[100];memset(a, '\0', sizeof(a));<BR>memcpy用来做内存拷贝,你可以拿它拷贝Q何数据类型的对象Q可以指定拷贝的数据长度Q例Qchar a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a)Q会造成b的内存地址溢出?/P> <P>strcpy只能拷贝字W串了,它遇?\0'q束拷贝;例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a)Q要注意a中的字符串长度(W一个‘\0’之前)是否过50位,如超q,则会造成b的内存地址溢出?BR> <BR>  <BR>iloveunix 发表于: 2002/09/04 04:56pm<BR> <BR>补充Q我的一点心?BR>memset可以方便的清IZ个结构类型的变量或数l?BR>如:<BR>struct sample_struct<BR>{<BR> char   csName[16];<BR> int    iSeq;<BR> int    iType;<BR>};</P> <P>对于变量<BR>struct sample_strcut  stTest;</P> <P>一般情况下Q清IstTest的方法:<BR>stTest.csName[0]='\0';<BR>stTest.iSeq=0;<BR>stTest.iType=0;</P> <P>用memset非常方便:<BR>memset(&stTest,0,sizeof(struct sample_struct));</P> <P>如果是数l:<BR>struct sample_struct   TEST[10];<BR>?BR>memset(TEST,0,sizeof(struct sample_struct)*10);</P> <P> <BR>  <BR>zhtrue 发表于: 2002/09/04 05:06pm<BR> <BR>对这个问题有疑问Q不是对函数的疑问,而是因ؓ没有弄懂mem和str的区别?BR>mem是一D内存,他的长度Q必M自己C<BR>str也是一D内存,不过它的长度Q你不用讎ͼ随时都可以计出?BR>所以memcpy需要第三个参数Q而strcpy不需?BR> <BR>  <BR>tianzhushan 发表于: 2002/09/05 06:19pm<BR> <BR>谢谢各位的高见!弟受益匪浅 <BR>  <BR>凌曦 发表于: 2002/09/05 09:47pm<BR> <BR>str也可以用用个参数的strncpy(a,b,n) <BR>  <BR>arbol 发表于: 2002/09/06 03:02pm<BR> <BR>q有Q记住memcpy是不拯最后的l止W的 <BR>  <BR>~程子 发表于: 2002/09/12 00:58am<BR> <BR>g的邮政老兄好像说错了吧Q应该是strcpy不拷?\0'的,而memcpy应该是通吃Q见着什么拷贝什么?<BR>  <BR>chrmhf 发表于: 2002/09/13 08:29am<BR> <BR>memset主要应用是初始化某个内存I间?BR>memcpy是用于COPY源空间的数据到目的空间中?BR>strcpy用于字符串COPY,遇到‘\0’,结束?BR>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-<BR>如果你理解了q些Q你应该知道他们的区别:例如你初始化某块I间的时候,用到memcpy<BR>那么应该怎么写,是不是显得很W?BR>int  m[100]<BR>->memset((void*)m,0x00,sizeof(int)*100);//GOOD<BR>..memcpy((void*)m,"\0\0\0\0....",sizeof(int)*100);//foolish<BR> <BR>  <BR>HopeCao 发表于: 2002/09/13 01:58pm<BR> <BR>strcpy <BR> 原型Qextern char *strcpy(char *dest,char *src); <BR> 用法Q?include <string.h><BR> 功能Q把src所指由NULLl束的字W串复制到dest所指的数组中?BR> 说明Qsrc和dest所指内存区域不可以重叠且dest必须有够的I间来容Usrc的字W串?BR>       q回指向dest的指针?BR>memcpy <BR> 原型Qextern void *memcpy(void *dest, void *src, unsigned int count);<BR> 用法Q?include <string.h><BR> 功能Q由src所指内存区域复制count个字节到dest所指内存区域?BR> 说明Qsrc和dest所指内存区域不能重叠,函数q回指向dest的指针?BR>memset<BR> 原型Qextern void *memset(void *buffer, int c, int count);<BR> 用法Q?include <string.h><BR> 功能Q把buffer所指内存区域的前count个字节设|成字符c?BR> 说明Q返回指向buffer的指针?BR> <BR>  <BR>~程子 发表于: 2002/09/15 09:38pm<BR> <BR>呵呵Q各位都是高手,真是不厌其烦的谆谆教导啊Q?BR> <BR>  <BR>obss 发表于: 2002/09/17 06:25pm<BR> <BR>谢谢Q有些收?<BR>  <BR> <BR> <BR></P><img src ="http://www.tkk7.com/faintbear/aggbug/6485.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/faintbear/" target="_blank">力力力</a> 2005-06-21 13:59 <a href="http://www.tkk7.com/faintbear/archive/2005/06/21/6485.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>c++常见问题http://www.tkk7.com/faintbear/archive/2005/06/21/6484.html力力力力力力Tue, 21 Jun 2005 05:56:00 GMThttp://www.tkk7.com/faintbear/archive/2005/06/21/6484.htmlhttp://www.tkk7.com/faintbear/comments/6484.htmlhttp://www.tkk7.com/faintbear/archive/2005/06/21/6484.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/6484.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/6484.html
一?include “filename.h”和#include filename.h>的区?/FONT>

#include “filename.h”是指编译器从当前工作目录上开始查找此文g

#include filename.h>是指~译器将从标准库目录中开始查找此文g


二、头文g的作?/FONT>

加强安全?/FONT>

通过头文件可能方便地调用库功能,而不必关心其实现方式


三? , &修饰W的位置

对于*?amp;修饰W,Z避免误解Q最好将修饰W紧靠变量名


四、if语句

不要布变量与MD行比较,那会很容易出错的?/FONT>

整Ş变量必须要有cd相同的D行比?/FONT>

点变量最好少比点Q就要比也要有D行限?/FONT>

指针变量要和NULLq行比较Q不要和布尔型和整Ş比较


五、const?define的比?/FONT>

const有数据类型,#define没有数据cd

个别~译器中const可以q行调试Q?define不可以进行调?/FONT>

在类中定义常量有两种方式

1?在类在声明常量,但不赋|在构造函数初始化表中q行赋|

2?用枚举代替const帔R?/FONT>


六、C++函数中值的传递方?/FONT>

有三U方式:g?Pass by value)、指针传?Pass by pointer)、引用传?Pass by reference)

void fun(char c) //pass by value

void fun(char *str) //pass by pointer

void fun(char &str) //pass by reference

如果输入参数是以g递的话,最好用引用传递代替,因ؓ引用传递省M临时对象的构造和析构

函数的类型不能省略,q没有也要加个void


七、函C中的指针或引用常量不能被q回

Char *func(void)

{

char str[]=”Hello Word?

//q个是不能被q回的,因ؓstr是个指定变量Q不是一般的|函数l束后会被注销?/FONT>

return str;

}

函数体内的指针变量ƈ不会随着函数的消亡而自动释?/FONT>


八、一个内存拷贝函数的实现?/FONT>

void *memcpy(void *pvTo,const void *pvFrom,size_t size)

{

assert((pvTo!=NULL)&&(pvFrom!=NULL));

byte *pbTo=(byte*)pvTo; //防止地址被改?/FONT>

byte *pbFrom=(byte*)pvFrom;

while (size-- >0)

pbTo++ = pbForm++;

return pvTo;

}


九、内存的分配方式

分配方式有三U,误住,说不定那天去面试的时候就会有人问你这问题

1?静态存储区Q是在程序编译时已l分配好的,在整个运行期间都存在Q如全局变量、常量?/FONT>

2?栈上分配Q函数内的局部变量就是从q分配的Q但分配的内存容易有限?/FONT>

3?堆上分配Q也U动态分配,如我们用new,malloc分配内存Q用delete,free来释攄内存?/FONT>


十、内存分配的注意事项

用new或malloc分配内存Ӟ必须要对此指针赋初倹{?/FONT>

用delete 或free释放内存后,必须要将指针指向NULL

不能修改指向帔R的指针数?/FONT>


十一、内容复制与比较

//数组…?/FONT>

char a[]=”Hello Word!?

char b[10];

strcpy(b,a);

if (strcmp(a,b)==0)

{}

//指针…?/FONT>

char a[]=”Hello Word!?

char *p;

p=new char[strlen(a)+1];

strcpy(p,a);

if (strcmp(p,a)==0)

{}


十二、sizeof的问?/FONT>

C一点,C++无法知道指针所指对象的大小Q指针的大小永远?字节

char a[]=”Hello World!?/FONT>

char *p=a;

count sizeof(a) end; //12字节

count sizeof(p) endl; //4字节

而且Q在函数中,数组参数退化ؓ指针Q所以下面的内容永远输出?

void fun(char a[1000])

{

count sizeof(a) endl; //输出4而不?000

}


十三、关于指?/FONT>

1?指针创徏时必被初始?/FONT>

2?指针在free 或delete后必ȝ为NULL

3?指针的长度都?字节

Q、释攑ֆ存时Q如果是数组指针Q必要释放掉所有的内存Q如

char *p=new char[100];

strcpy(p,”Hello World?;

delete []p; //注意前面的EQ号

p=NULL;

Q、数l指针的内容不能过数组指针的最大容易?/FONT>

?

char *p=new char[5];

strcpy(p,”Hello World?; //报错 目标Ҏ不够?/FONT>

delete []p; //注意前面的EQ号

p=NULL;


十四、关于malloc/free 和new /delete

l malloc/free 是C/C+的内存分配符Qnew /delete是C++的内存分配符?/FONT>

l 注意Qmalloc/free是库函数Qnew/delete是运符

l malloc/free不能执行构造函C析构函数Q而new/delete可以

l new/delete不能在C上运行,所以malloc/free不能被淘?/FONT>

l 两者都必须要成对?/FONT>

l C++中可以用_set_new_hander函数来定义内存分配异常的处理


十五、E++的特?/FONT>

Q?+新增加有重蝲(overload)Q内联(inlineQ,ConstQVirtual四种机制

重蝲和内联:卛_用于全局函数Q也可用于类的成员函敎ͼ

Const和VirtualQ只可用于类的成员函敎ͼ

重蝲Q在同一cMQ函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函C叫重载。如果在cM调用同名的全局函数Q必ȝ全局引用W号::引用?/FONT>

覆盖是指zcd数覆盖基cd?/FONT>

函数名相同;

参数相同Q?/FONT>

基类函数必须有Virtual关键字;

不同的范?zcd基类)?/FONT>

隐藏是指zcd蔽了基类的同名函数相?/FONT>

1?函数名相同,但参C同,此时不论基类有无Virtual关键字,基类函数被隐藏?/FONT>

2?函数名相同,参数也相同,但基cLVirtual关键?有就是覆?Q基cd数将被隐藏?/FONT>

内联Qinline关键字必M定义体放在一P而不是单单放在声明中?/FONT>

ConstQconst是constant的羃写,“恒定不变”的意思。被const修饰的东襉K受到强制保护Q可以预防意外的变动Q能提高E序的健壮性?/FONT>

1?参数做输入用的指针型参数Q加上const可防止被意外改动?/FONT>

2?按值引用的用户cd做输入参数时Q最好将按g递的改ؓ引用传递,q加上const关键字,目的是ؓ了提高效率。数据类型ؓ内部cd的就没必要做qg事情Q如Q?/FONT>

void Func(A a) 改ؓvoid Func(const A &a)?/FONT>

而void func(int a)没必要Ҏvoid func(const int &a);

3?l返回gؓ指针cd的函数加上constQ会使函数返回g能被修改Q赋l的变量也只能是const型变量。如Q函数const char*GetString(void); char *str=GetString()会出错。而const char *str=GetString()是正确的?/FONT>

4?Const成员函数是指此函C内只能调用Const成员变量Q提高程序的键壮性。如声明函数 int GetCount(void) const;此函C内就只能调用Const成员变量?/FONT>

VirtualQ虚函数Q派生类可以覆盖掉的函数Q纯虚函敎ͼ只是个空函数Q没有函数实CQ?/FONT>


十六、extern“C”有什么作用?

Extern “C”是由EQ+提供的一个连接交换指定符P用于告诉Q+Q这D代码是Q函数。这是因为C++~译后库中函数名会变得很长,与C生成的不一_造成Q+Q不能直接调用C函数Q加上extren “c”后QC++p直接调用C函数了?/FONT>

Extern “C”主要用正规DLL函数的引用和导出 ?在C++包含C函数或C头文件时使用。用时在前面加上extern “c?关键字即可?/FONT>


十七、构造函C析构函数

zcȝ构造函数应在初始化表里调用基类的构造函敎ͼ

zcd基类的析构函数应加Virtual关键字?/FONT>

不要看构造函数和析构函数Q其实编hq是不容易?/FONT>

#include iostream.h>

class Base

{

public:

virtual ~Base() { cout "~Base" endl ; }

};

class Derived : public Base

{

public:

virtual ~Derived() { cout "~Derived" endl ; }

};

void main(void)

{

Base * pB = new Derived; // upcast

delete pB;

}

输出l果为:

~Derived

~Base

如果析构函数不ؓ虚,那么输出l果?/FONT>

~Base


十八?IFNDEF/#DEFINE/#ENDIF有什么作?/FONT>

仿止该头文g被重复引?/FONT>

 
 



力力力 2005-06-21 13:56 发表评论
]]>
教你理解复杂的C/C++声明http://www.tkk7.com/faintbear/archive/2005/05/14/4285.html力力力力力力Sat, 14 May 2005 02:13:00 GMThttp://www.tkk7.com/faintbear/archive/2005/05/14/4285.htmlhttp://www.tkk7.com/faintbear/comments/4285.htmlhttp://www.tkk7.com/faintbear/archive/2005/05/14/4285.html#Feedback0http://www.tkk7.com/faintbear/comments/commentRss/4285.htmlhttp://www.tkk7.com/faintbear/services/trackbacks/4285.html
  我们从每天都能到的较单的声明入手Q然后逐步加入const修饰W和typedefQ还有函数指针,最后介l一个能够让你准地理解MC/C++声明的“右左法则”?

  需要强调一下的是,复杂的C/C++声明q不是好的编E风|我这里仅仅是教你如何ȝ解这些声明。注意:Z保证能够在同一行上昄代码和相x释,本文最好在臛_1024x768分L率的昄器上阅读?
  基础
  让我们从一个非常简单的例子开始,如下Q?

   int n;

  q个应该被理解ؓ“declare n as an int”(n是一个int型的变量Q?
  接下L看一下指针变量,如下Q?

  int *p;

  q个应该被理解ؓ“declare p as an int *”(p是一个int *型的变量Q,或者说p是一个指向一个int型变量的指针。我惛_q里展开讨论一下:我觉得在声明一个指针(或引用)cd的变量时Q最好将*Q或&Q写在紧靠变量之前,而不是紧跟基本类型之后。这样可以避免一些理解上的误区,比如Q?

  int* p,q;

  W一眼看去,好像是p和q都是int*cd的,但事实上Q只有p是一个指针,而q是一个最单的int型变量?
  我们q是l箋我们前面的话题,再来看一个指针的指针的例子:

  char **argv;

  理论上,对于指针的数没有限Ӟ你可以定义一个Q点类型变量的指针的指针的指针的指?..
  再来看如下的声明Q?

  int RollNum[30][4];
  int (*p)[4]=RollNum;
  int *q[5];

  q里Qp被声明ؓ一个指向一?元素QintcdQ数l的指针Q而q被声明ؓ一个包?个元素(intcd的指针)的数l?BR>  另外Q我们还可以在同一个声明中混合实用*?amp;Q如下:

  int **p1; // p1 is a pointer to a pointer to an int.
  int *&p2; // p2 is a reference to a pointer to an int.
  int &*p3; // ERROR: Pointer to a reference is illegal.
  int &&p4; // ERROR: Reference to a reference is illegal.

  注:p1是一个intcd的指针的指针Qp2是一个intcd的指针的引用Qp3是一个intcd引用的指针(不合法!Q;p4是一个intcd引用的引用(不合法!Q?BR>  const修饰W?BR>   当你想阻止一个变量被改变Q可能会用到const关键字。在你给一个变量加上const修饰W的同时Q通常需要对它进行初始化Q因Z后的M时候你没有机会再L变它。例如:

  const int n=5;
  int const m=10;

   上述两个变量n和m其实是同一U类型的——都是const intQ整形恒量)。因为C++标准规定Qconst关键字放在类型或变量名之前等L。我个h更喜Ƣ第一U声明方式,因ؓ它更H出了const修饰W的作用?BR>   当const与指针一起用时Q容易让人感到迷惑。例如,我们来看一下下面的p和q的声明:

  const int *p;
  int const *q;

   他们当中哪一个代表const intcd的指针(const直接修饰intQ,哪一个代表intcd的const指针Qconst直接修饰指针Q?实际上,p和q都被声明为const intcd的指针。而intcd的const指针应该q样声明Q?BR>
  int * const r= &n; // n has been declared as an int

   q里Qp和q都是指向const intcd的指针,也就是说Q你在以后的E序里不能改?p的倹{而r是一个const指针Q它在声明的时候被初始化指向变量nQ即r=&n;Q之后,r的值将不再允许被改变(?r的值可以改变)?BR>   l合上述两种const修饰的情况,我们来声明一个指向const intcd的const指针Q如下:

  const int * const p=&n // n has been declared as const int

   下面l出的一些关于const的声明,帮助你d理清const的用法。不q请注意Q下面的一些声明是不能被编译通过的,因ؓ他们需要在声明的同时进行初始化。ؓ了简zv见,我忽略了初始化部分;因ؓ加入初始化代码的话,下面每个声明都将增加两行代码?BR>
  char ** p1; // pointer to pointer to char
  const char **p2; // pointer to pointer to const char
  char * const * p3; // pointer to const pointer to char
  const char * const * p4; // pointer to const pointer to const char
  char ** const p5; // const pointer to pointer to char
  const char ** const p6; // const pointer to pointer to const char
  char * const * const p7; // const pointer to const pointer to char
  const char * const * const p8; // const pointer to const pointer to const char

   注:p1是指向charcd的指针的指针Qp2是指向const charcd的指针的指针Qp3是指向charcd的const指针Qp4是指向const charcd的const指针Qp5是指向charcd的指针的const指针Qp6是指向const charcd的指针的const指针Qp7是指向charcdconst指针的const指针Qp8是指向const charcd的const指针的const指针?BR>  typedef的妙?BR>   typedefl你一U方式来克服?只适合于变量而不适合于类型”的弊端。你可以如下使用typedefQ?BR>
  typedef char * PCHAR;
  PCHAR p,q;

   q里的p和q都被声明为指针。(如果不用typedefQq被声明Z个char变量Q这跟我们的W一眼感觉不太一_Q下面有一些用typedef的声明,q且l出了解释:

  typedef char * a; // a is a pointer to a char
  typedef a b(); // b is a function that returns
   // a pointer to a char
  typedef b *c; // c is a pointer to a function
   // that returns a pointer to a char
  typedef c d(); // d is a function returning
   // a pointer to a function
   // that returns a pointer to a char
  typedef d *e; // e is a pointer to a function
   // returning a pointer to a
   // function that returns a
   // pointer to a char
  e var[10]; // var is an array of 10 pointers to
   // functions returning pointers to
   // functions returning pointers to chars.

   typedefl常用在一个结构声明之前,如下。这P当创建结构变量的时候,允许你不使用关键字structQ在C中,创徏l构变量时要求用struct关键字,如struct tagPOINT aQ而在C++中,struct可以忽略Q如tagPOINT bQ?BR>
  typedef struct tagPOINT
  {
   int x;
   int y;
  }POINT;
  POINT p; /* Valid C code */

  函数指针
   函数指针可能是最Ҏ引v理解上的困惑的声明。函数指针在DOS时代写TSRE序时用得最多;在Win32和X-Windows时代Q他们被用在需要回调函数的场合。当Ӟq有其它很多地方需要用到函数指针:虚函数表QSTL中的一些模板,Win NT/2K/XPpȝ服务{。让我们来看一个函数指针的单例子:

  int (*p)(char);

   q里p被声明ؓ一个函数指针,q个函数带一个charcd的参敎ͼq且有一个intcd的返回倹{另外,带有两个floatcd参数、返回值是charcd的指针的指针的函数指针可以声明如下:

  char ** (*p)(float, float);

   那么Q带两个charcd的const指针参数、无q回值的函数指针又该如何声明呢?参考如下:

  void * (*a[5])(char * const, char * const);

   “右左法则”[重要Q!Q]
  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.
   q是一个简单的法则Q但能让你准理解所有的声明。这个法则运用如下:从最内部的括号开始阅d明,向右看,然后向左看。当你碰C个括hp转阅ȝ方向。括号内的所有内定w分析完毕p出括L范围。这Ll,直到整个声明都被分析完毕?BR>   对上q“右左法则”做一个小的修正Q当你第一ơ开始阅d明的时候,你必M变量名开始,而不是从最内部的括受?BR>   下面l合例子来演CZ下“右左法则”的使用?BR>
  int * (* (*fp1) (int) ) [10];

   阅读步骤Q?BR>   1. 从变量名开?-------------------------------------------- fp1
   2. 往右看Q什么也没有Q碰C)Q因此往左看Q碰C? ------ 一个指?BR>   3. 跛_括号Q碰C(int) ----------------------------------- 一个带一个int参数的函?BR>   4. 向左看,发现一? --------------------------------------- Q函敎ͼq回一个指?BR>   5. 跛_括号Q向右看Q碰到[10] ------------------------------ 一?0元素的数l?BR>   6. 向左看,发现一? --------------------------------------- 指针
   7. 向左看,发现int ----------------------------------------- intcd
   ȝQfp1被声明成Z个函数的指针的指针的数组Q这个数l有10个元素,函数的原型ؓ带一个intcd的参敎ͼq回gؓ一个指针?
  再来看一个例子:

  int *( *( *arr[5])())();

   阅读步骤Q?BR>   1. 从变量名开?-------------------------------------------- arr
   2. 往右看Q发现是一个数l?---------------------------------- 一?元素的数l?BR>   3. 向左看,发现一? --------------------------------------- 指针
   4. 跛_括号Q向右看Q发?) -------------------------------- 不带参数的函?BR>   5. 向左看,到* ------------------------------------------- Q函敎ͼq回一个指?BR>   6. 跛_括号Q向叛_?) ------------------------------------ 不带参数的函?BR>   7. 向左Q发? --------------------------------------------- Q函敎ͼq回一个指?BR>   8. l箋向左Q发现int --------------------------------------- intcd
   ȝQ?BR>   q有更多的例子:

  float ( * ( *b()) [] )(); // b is a function that returns a
   // pointer to an array of pointers
   // to functions returning floats.
  void * ( *c) ( char, int (*)()); // c is a pointer to a function that takes
   // two parameters:
   // a char and a pointer to a
   // function that takes no
   // parameters and returns
   // an int
   // and returns a pointer to void.
  void ** (*d) (int &,
   char **(*)(char *, char **)); // d is a pointer to a function that takes
   // two parameters:
   // a reference to an int and a pointer
   // to a function that takes two parameters:
   // a pointer to a char and a pointer
   // to a pointer to a char
   // and returns a pointer to a pointer
   // to a char
   // and returns a pointer to a pointer to void
  float ( * ( * e[10])
   (int &) ) [5]; // e is an array of 10 pointers to
   // functions that take a single
   // reference to an int as an argument
   // and return pointers to
   // an array of 5 floats.


力力力 2005-05-14 10:13 发表评论
]]>
վ֩ģ壺 պVAĻ| ޾Ʒ߹ۿ| һɫƬ| 2019Ļֱ| VA߹ۿ| 99|| þ޾Ʒa| þAV뾫Ʒ| պƷһ| վɫƵѹۿ| ɫվapp߹ۿ | av˾Ʒһ | ޾Ʒŷۺ| ޾Ʒֻ| ŷ޹Ʒ㶮| ˳77777վ| ëƬav߲һ| ŮƷƵѹۿ| 18ڵվ | ޾ƷƵ| Ƶ| һaɫëƬ| ߲˳Ƶվ| ޾Ʒ2021| ɫ޵һ| ձϵ1ҳϵ| þ޾Ʒav| ŷպɫ| avרҳ| ƷþþþþӰ| AVһ| ޹Ʒۺɫ| ޹һ߹ۿ| պһһ| ޾Ʒavɫ| ޾Ʒ| һ˿wwwƵ | ƷþƵ| aëƬ߹ۿ| ޻Ƭֻѹۿ| ޾ƷAAƬѽ |