2007年4月25日星期三

一道微软亚洲工程院C语言笔试题的解答

题目:
struct S
{
int i;
int * p;
};

void main()
{
struct S s;
int * p = &s.i;

p[0] = 4;
p[1] = 3;

s.p = p;

s.p[1] = 1;
s.p[0] = 2;
}


问程序会在哪一行死掉。

分析:这道题有点难度。如果你对指针掌握的不错的话,仔细分析,相信最终还是可以迎刃而解的。下面就来逐条分析

struct S s;
int * p = &s.i;/*取成员s.i的地址*/

p[0] = 4;;/*设置成员s.i为4。因为指针p指向i地址,p[0]指向i*/
p[1] = 3;/*设置成员s.p为3。因为p[0]指向s.i,p[1]指向指针s.p*/

s.p = p;/*重新设置指针s.p为s.i的地址*/

s.p[1] = 1;/*置s.p指针为1。因前面s.p指向s.i的地址,固s.p[1]指向s.p*/
s.p[0] = 2;/*因为s.p指针已经通过前面被设置为1,即非法地址,所以s.p[0]想通过s.p去访问s.i显然是非法的。*/

在内存中的对应关系:
 | s.i | s.p |
| p[0] | p[1] |
| s.p[0] | s.p[1] |
后话:这里出题者故意将s.p[0]和s.p[1]的访问次序对调,就是想通过设置s.p[1]搞死s.p[0]的相关操作。如果先执行s.p[0] = 2,然后执行s.p[1] = 1,那么所有代码都正常通过。
答案:程序执行最后一句s.p[0] = 2死掉。

高手详解:sscanf函数的高级用法

sscanf是一个很好用的函数,利用它可以从字符串中取出整数、浮点数和字符串等等。它的使用方法简单,特别对于整数和浮点数来说。但新手可能并不知道处理字符串时的一些高级用法,这里做个简要说明吧。

  1. 常见用法。

以下是引用片段:
  char str[512] = {0};
  sscanf("123456 ", "%s", str);
  printf("str=%s\n", str);


  2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。

以下是引用片段:
  sscanf("123456 ", "%4s", str);
  printf("str=%s\n", str);


  3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

以下是引用片段:
  sscanf("123456 abcdedf", "%[^ ]", str);
  printf("str=%s\n", str);


  4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。

以下是引用片段:
  sscanf("123456abcdedfBCDEF", "%[1-9a-z]", str);
  printf("str=%s\n", str);


  5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

以下是引用片段:
  sscanf("123456abcdedfBCDEF", "%[^A-Z]", str);
  printf("str=%s\n", str);

c语言的试题和技巧

1引言
 本文的写作目的并不在于提供C/C++程序员求职面试指导,而旨在从技术上分析面试题的
内涵。文中的大多数面试题来自各大论坛,部分试题解答也参考了网友的意见。
  许多面试题看似简单,却需要深厚的基本功才能给出完美的解答。企业要求面试者写
一个最简单的strcpy函数都可看出面试者在技术上究竟达到了怎样的程度,我们能真正写
好一个strcpy函数吗?我们都觉得自己能,可是我们写出的strcpy很可能只能拿到10分中
的2分。读者可从本文看到strcpy函数从2分到10分解答的例子,看看自己属于什么样的层
次。此外,还有一些面试题考查面试者敏捷的思维能力。
分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题
的深入剖析则可进一步增强自身的内功。
  2.找错题
  试题1:
void test1()
{
 char string[10];
 char* str1 = "0123456789";
 strcpy( string, str1 );
}
  试题2:
void test2()
{
 char string[10], str1[10];
 int i;
 for(i=0; i<10; address =" strDest;" p =" (char" str =" NULL;" str =" NULL;" str =" GetMemory();" p =" (char" str =" NULL;" str =" (char">

char *str = NULL;
GetMemory( str );
  后的str仍然为NULL;
  试题5中
char p[] = "hello world";
return p;
  的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序
员常犯的错误,其根源在于不理解变量的生存期。

  试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,
但是在GetMemory中执行申请内存及赋值语句

*p = (char *) malloc( num );
  后未判断内存是否申请成功,应加上:
if ( *p == NULL )
{
 ...//进行申请内存失败处理
}
  试题7存在与试题6同样的问题,在执行
char *str = (char *) malloc(100);
  后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变
成一个“野”指针,应加上:
str = NULL;
  试题6的Test函数中也未对malloc的内存进行释放。
  剖析:
  试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回
答其中50~60的错误。但是要完全解答正确,却也绝非易事。
 对内存操作的考查主要集中在:
  (1)指针的理解;
  (2)变量的生存期及作用范围;
  (3)良好的动态内存申请和释放习惯。
  再看看下面的一段程序有什么错误:
swap( int* p1,int* p2 )
{
 int *p;
 *p = *p1;
 *p1 = *p2;
 *p2 = *p;
}
  在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在V
C++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:
swap( int* p1,int* p2 )
{
 int p;
 p = *p1;
 *p1 = *p2;
 *p2 = p;
}
  3.内功题
  试题1:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变
量名为var)
  解答:
  BOOL型变量:if(!var)
   int型变量: if(var==0)
   float型变量:   const float EPSINON = 0.00001;
  if ((x >= - EPSINON) && (x <= EPSINON)   指针变量:  if(var==NULL)   剖析:   考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变 量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正 确运行,但是未能清晰地表达程序的意思。   一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var ),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用 if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这 是一种很好的编程习惯。   浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设 法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。   试题2:以下为Windows NT下的32位C++程序,请计算sizeof的值 void Func ( char str[100] ) {  sizeof( str ) = ? } void *p = malloc( 100 ); sizeof ( p ) = ?   解答: sizeof( str ) = 4 sizeof ( p ) = 4   剖析:   Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了 本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作 自增、自减等操作,可以被修改。   数组名的本质如下:  (1)数组名指代一种数据结构,这种数据结构就是数组;  例如: char str[10]; cout << least =" MIN(*p++," n="2,移位后应该是“hiabcdefgh”" n =" strlen(" n =" strlen(" str =" NULL);" operate ="(const" str="="NULL)" m_data =" new" m_data =" '\0';" length =" strlen(str);" m_data =" new" length =" strlen(other.m_data);" m_data =" new" operate ="(const" this ="="" length =" strlen(" m_data =" new" a =" 1;" b ="="" sum =" 0;" i="1;">

2007年4月24日星期二

why we need to know the details of a technology (zz)

作为一名好的程序员,重视细节是一个必须要具备的优点。粗枝大叶的人很难成为一名好的程序员,至于好的架构师就更不要指望了。好的架构师来自于好的程序员,认为自己可以不经过多年程序员的严格考验就成为一名合格的架构师,那是癞蛤蟆想吃天鹅肉。

但是在国内,很多人满足于仅仅知道一些buzzword。他本人在做数据库开发,却不肯去深入了解不同SQL语句的性能差异;本人在做业务层开发,却不肯 去深入了解重构为何物;本人在做Web表现层开发,却不肯去深入了解XHTML/CSS(我不提JavaScript,我认为即使在一个传统的Web开发 团队中,开发人员也应该真正精通XHTML/CSS)。

我碰到过一些开发者,他们知道很多的buzzword,然而对于任何一个具体的领域的掌握却非常稀松平常。他们的简历上常常写着这个也会那个也会,但是一 问具体的问题就露馅了。他们对于任何一个技术领域都仅仅只知道一点点皮毛,无法满足一般软件项目的开发需要,更无法满足对于质量要求更高的软件产品的开发 需要。我带开发团队,其实更喜欢那些真正肯深入钻研,对工作质量精益求精的人,而不大喜欢什么都知道一些,但是遇到真正的困难就畏首畏尾的人。

国外为何出了这么多大牛,因为他们有着非常丰富的实践经验,他们对于自己所从事的技术领域的理解是非常深刻的。这些经验大部分都是来自于实践,来自于书本 的经验只占一小部分。国内的一些开发者希望通过半年内读完10本架构师著作速成为一名优秀的架构师,那是不大可能的。通过这种方式,最多也只能拾人牙慧, 是不可能成为Erich Gamma和Rod Johnson那样的人的。还有一些人总是喜欢摆出似乎已经无所不知的嘴脸,其实他们并不了解某种技术的细节,所以他们的判断往往失之毫厘,差之千里。

由此我发现西方人和中国人的一个很大的差别,就是西方人非常注重细节,而中国人则更喜欢观其大略。观其大略在某些领域也许是足够了,但是如果一个程序员也 满足于对任何事情都观其大略,那肯定是要误大事的。我只说中国人,不说东方人,因为我发现日本人在这方面与西方人是相同的。

我有一些朋友,对于某一个技术领域,一旦扎下去之后就研究的非常深。我很佩服这样的朋友,也很喜欢与他们交流一些真正深层次的技术问题。一个最近的例子就陈黎夫,他的新著《ASP.NET Ajax程序设计—第I卷》刚刚出版。黎 夫是一个非常勤奋的人,一个80后的年轻人。在工作之余,他写了很多技术blog,在不到1年时间里翻译了Foundations of Atlas和The Zen of CSS。他还计划撰写三卷的大部头ASP.NET Ajax的专著,现在这个计划正在有条不紊地进行着,第一卷已经顺利出版。我对他的高产感到很佩服,我唯一担心的就是他的身体,希望他也能把身体锻炼好。

memory allocation

很多人都觉得学习C++是特别困难的事情。C++学习是比较复杂的:它的内存分配、指针、以及面向对象思想的实现等等,确实需要一定的技术积累。我们将以专题的形式,为大家逐一剖析c++的技术重点和难点。

本专题讨论的就是内存分配。学习c++如果不了解内存分配是一件非常可悲的事情。而且,可以这样讲,一个C++程序员无法掌握内存、无法了解内存,是不能够成为一个合格的C++程序员的。

一、内存基本构成
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动 态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

二、三者之间的区别
我们通过代码段来看看对这样的三部分内存需要怎样的操作和不同,以及应该注意怎样的地方。
例一:静态存储区与栈区

char* p = “Hello World1”;

char a[] = “Hello World2”;

p[2] = ‘A’;

a[2] = ‘A’;

char* p1 = “Hello World1;”

screen.width-333)this.width=screen.width-333" border="0">

这个程序是有错误的,错误发生在p[2] = ‘A’这行代码处,为什么呢,是变量p和变量数组a都存在于栈区的(任何临时变量都是处于栈区的,包括在main()函数中定义的变量)。但是,数据 “Hello World1”和数据“Hello World2”是存储于不同的区域的。

因为数据“Hello World2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任何问题的。因为指针变量p仅仅能够存储某个存储空间的地址,数据“Hello World1”为字符串常量,所以存储在静态存储区。虽然通过p[2]可以访问到静态存储区中的第三个数据单元,即字符‘l’所在的存储的单元。但是因为 数据“Hello World1”为字符串常量,不可以改变,所以在程序运行时,会报告内存错误。并且,如果此时对p和p1输出的时候会发现p和p1里面保存的地址是完全相 同的。换句话说,在数据区只保留一份相同的数据(见图1-1)。

例二:栈区与堆区

char* f1()

{

char* p = NULL;

char a;

p = &a;

return p;

}

char* f2()

{

char* p = NULL:

p =(char*) new char[4];

return p;

}


这两个函数都是将某个存储空间的地址返回,二者有何区别呢?f1()函数虽然返回的是一个存储空间,但是此空间为临时空间。也就是说,此空间只有短暂的生 命周期,它的生命周期在函数f1()调用结束时,也就失去了它的生命价值,即:此空间被释放掉。所以,当调用f1()函数时,如果程序中有下面的语句:

char* p ;

p = f1();

*p = ‘a’;


此时,编译并不会报告错误,但是在程序运行时,会发生异常错误。因为,你对不应该操作的内存(即,已经释放掉的存储空间)进行了操作。但是,相比之下, f2()函数不会有任何问题。因为,new这个命令是在堆中申请存储空间,一旦申请成功,除非你将其delete或者程序终结,这块内存将一直存在。也可 以这样理解,堆内存是共享单元,能够被多个函数共同访问。如果你需要有多个数据返回却苦无办法,堆内存将是一个很好的选择。但是一定要避免下面的事情发 生:

void f()

{

char * p;

p = (char*)new char[100];

}


这个程序做了一件很无意义并且会带来很大危害的事情。因为,虽然申请了堆内存,p保存了堆内存的首地址。但是,此变量是临时变量,当函数调用结束时p变量 消失。也就是说,再也没有变量存储这块堆内存的首地址,我们将永远无法再使用那块堆内存了。但是,这块堆内存却一直标识被你所使用(因为没有到程序结束, 你也没有将其delete,所以这块堆内存一直被标识拥有者是当前您的程序),进而其他进程或程序无法使用。我们将这种不道德的“流氓行为”(我们不用, 却也不让别人使用)称为内存泄漏。这是我们C++程序员的大忌!!请大家一定要避免这件事情的发生。

总之,对于堆区、栈区和静态存储区它们之间最大的不同在于,栈的生命周期很短暂。但是堆区和静态存储区的生命周期相当于与程序的生命同时存在(如果您不在 程序运行中间将堆内存delete的话),我们将这种变量或数据成为全局变量或数据。但是,对于堆区的内存空间使用更加灵活,因为它允许你在不需要它的时 候,随时将它释放掉,而静态存储区将一直存在于程序的整个生命周期中。
我们此专题仅仅是简要的分析了内存基本构成以及使用它们时需要注意的问题。对内存的分析和讨论将一直贯穿于我们以后所有的专题,这也就是为什么把它作为第一讲的原因。

上班人员必读:“五险一金”详解!

问:什么是五险一金?

答:“五险一金”讲的是五种保险,包括养老保险、医疗保险、失业保险、工伤保险和生育保险;“一金”指的是住房公积金。

其中养老保险、医疗保险和失业保险,这三种险是由企业和个人共同缴纳的保费,工伤保险和生育保险完全是由企业承担的。个人不需要缴纳。这里要注意的是“五险”是法定的,而“一金”不是法定的。

问:“五险一金”的缴费比例是什么?

答:目前北京养老保险缴费比例:单位20%(其中17%划入统筹基金,3%划入个人帐户),个人8%(全部划入个人帐户);医疗保险缴费比例:单位10%,个人2%+3元。

失业保险缴费比例:单位1.5%,个人0.5%;工伤保险根据单位被划分的行业范围来确定它的工伤费率;生育保险缴费比例:单位0.8%,个人不交钱。

公积金缴费比例: 根据企业的实际情况,选择住房公积金缴费比例。但原则上最高缴费额不得超过北京市职工平均工资300%的10%。

统筹基金即:在养老保险制度从国家—单位制逐渐向国家—社会制转变的过程中需要国家统筹,以解决经济发展不平衡及人口老龄化等问题。

(1)以企业缴费为主建立社会统筹基金;(2)由职工和企业缴费为主建立个人帐户;(3)政府负担养老保险基金的管理费用。这种社会统筹和个人帐户相结合 的半基金制有利于应付中国人口老龄化危机,逐渐分散旧制度到新制度的转轨成本,逐步实现由企业养老保险制度到个人养老保险制度的转变。

四险一金的缴纳额度每个地区的规定都不同,基数是以工资总额为基数。有的企业在发放时有基本工资,有相关一些补贴,但有的企业在缴纳时,只是基本工资,这是违反法律规定的。具体比例要向当地的劳动部门去咨询。

关于养老保险、失业保险和医疗保险的支取,是在法定允许的情况下才可以领取,是由设保登记部门来发放,比如“养老保险,要达到法定的年龄才可以,失业保险 金的领取也是要具备条件,比如你到户口所在地的街道办事处办理失业证明,同时又办了求职证,就是指你失业以后还必须有求职的意愿,这样的条件才可以领取。

如果失业之后你不想工作,那么就不能给你发保险金。另外,养老金和失业金是不能同时享受的。

问:试用期内是否享有保险?

答:在试用期内也应该有享受保险,因为试用期是合同期的一个组成部分,它不是隔离在合同期之外的。所以在试用期内也应该上保险。另外,企业给员工上保险是 一个法定的义务,不取决于当事人的意思或自愿与否,即使员工表示不需要交保险也不行,而且商业保险不能替代社会保险。养老保险的享受待遇。

累计缴纳养老保险15年以上,并达到法定退休年龄,可以享受养老保险待遇:

1、按月领取按规定计发的基本养老金,直至死亡。

基本养老金的计算公式如下:

基本养老金=基础养老金+个人账户养老金+过渡性养老金=退休前一年全市职工月平均工资×20%(缴费年限不满15年的按15%)+个人账户本息和÷120+指数化月平均缴费工资×1997年底前缴费年限×1.4%。

2、死亡待遇。(1)丧葬费(2)一次性抚恤费(3)符合供养条件的直系亲属生活困难补助费,按月发放,直至供养直系亲属死亡。

注意:养老保险应尽量连续缴纳,根据有关文件规定,凡企业或被保险人间断缴纳基本养老保险费的(失业人员领取失业保险金期间或按有关规定不缴费的人员除 外),被保险人符合国家规定的养老条件,计算基本养老金时,其基础性养老金的计算基数,按累计间断的缴费时间逐年前推至相应年度上一年的本市职工平均工资 计算(累计间断的缴费时间,按每满12个月为一个间断缴费年度计算,不满12个月不计算)

举例来说吧:

如果你2020年退休,正常你的基础养老金是2019年的社会平均工资×20%,但是如果你在退休之前养老保险中断了30个月,就是中断了2.5年,按2年算,你的基础养老金就是2017年社会平均工资×20%

问:参加医疗保险享受哪些待遇?

答:1、门、急诊医疗费用。在职职工年度内(1月1日-12月31日)符合基本医疗保险规定范围的医疗费累计超过2000元以上部分;

2、结算比例。合同期内派遣人员2000元以上部分报销50%,个人自付50%;在一个年度内累计支付派遣人员门、急诊报销最高数额为2万元。

3、参保人员要妥善保管好在定点医院就诊的门诊医疗单据(含大额以下部分的收据、处方底方等),作为医疗费用报销凭证; 

4、三种特殊病的门诊就医:参保人员患恶性肿瘤放射治疗和化学治疗、肾透析、肾移植后服抗排异药需在门诊就医时,由参保人就医的二、三级定点医院开据”疾 病诊断证明”,并填写《北京市医疗保险特殊病种申报审批表》,报区医保中心审批备案。这三种特殊病的门诊就医及取药仅限在批准就诊的定点医院,不能到定点 零售药店购买。发生的医疗费符合门诊特殊病规定范围的,参照住院进行结算。

5、住院医疗

●住院押金:符合住院条件的参保人员,在收入住院时,医院收取参保人员部分押金,押金数额由医院根据病情按比例确定。如被派遣人员单位和参保人员未能按时足额缴纳医疗保险费的,住院押金由派遣人员个人全额垫付。

●结算周期:参保人员住院治疗每90天为一个结算周期:不超过90天的,每次住院为一个结算周期。

●恶性肿瘤患者门诊放射治疗和化学治疗、肾透析、肾移植后服抗排异药、患有精神病需常年住院的患者其发生的医疗费用每360天为一个结算周期。

●参保人员在定点的社区卫生服务中心(站)的家庭病床治疗发生的医疗费用,每90天为一个结算周期。

●参保人员出院或阶段治疗结束时,需由派遣人员个人先与医院结清应由派遣人员个人自费和自付的费用,应由基本医疗保险统筹基金和大额医疗互助资金支付的医疗费用,由医院向医保中心申报审核、结算。

●参保人员住院治疗,符合基本医疗保险规定范围的医疗费的结算,设定基本医疗统筹基金支付起付线和最高支付额。

●起付线第一次住院为1300元,以后住院为650元,最高支付限额为5万元;超过最高支付上限的(不含起付标准以下以及派遣人员个人负担部分)大额医疗费用互助。

资金支付70%,派遣人员个人负担30%。在一个年度内最高支付10万元。住院费用的结算标准,在一个结算周期内按医院等级和费用数额采取分段计算、累加支付的办法。(各项比例有调整时,按新的标准执行)

注意:非因公交通事故,医保是免责的!

问:参加失业保险将能享受待遇?

答:失业保险连续缴纳一年以上,档案退回街道后。可以在街道享受失业保险待遇。

1.失业保险金:是指失业保险经办机构按规定支付给符合条件的失业人员的基本生活费用,它是最主要的失业保险待遇。失业保险待遇根据北京市相关文件执行。

2.领取失业保险金期间的医疗补助金:是指支付给失业人员领取失业保险金期间发生的医疗费用的补助。根据北京市有关政策法规执行。

3.领取失业保险金期间死亡的失业人员的丧葬补助金和其供养的配偶、直系亲属的抚恤金按有关规定执行。

问:参加工伤保险享受哪些待遇?

答:在合同期内不幸发生意外,需向企业索取情况说明,并加盖企业公章,尽快(最好在三个工作日内)申请工伤认定并需提供下列材料。

1、初次治疗诊断书或住院病历。

2、职业病诊断证明(原件、复印件各一份)。

3、交通事故需提供交通大队的事故裁决书或交通部门的交通事故证明。

4、身份证复印件。

5、有效期内的劳动合同原件。

问:参加生育保险享受哪些待遇?

答:可以报销与生育有关费用。报销范围包括,生育津贴、生育医疗费用、计划生育手术医疗费用、国家和本市规定的其他与生育有关的费用。 

生育津贴按照女职工本人生育当月的缴费基数除以30再乘以产假天数计算。生育津贴为女职工产假期间的工资,生育津贴低于本人工资标准的,差额部分由企业补足。 

生育医疗费用包括女职工因怀孕、生育发生的医疗检查费、接生费、手术费、住院费和药品费。计划生育手术医疗费用包括职工因计划生育发生的医疗费用。
现在要求,医保缴够20年,养老交够15年才有资格领养老金和享受退休后的医保报销。(完)

The usage of Typedef

一.基本概念剖析

int* (*a[5])(int, char*); //#1
void (*b[10]) (void (*)()); //#2
double(*)() (*pa)[9]; //#3


1.C语言中函数声明和数组声明。函数声明一般是这样:
int fun(int, double);
对应函数指针(pointer to function)的声明是这样:
int (*pf)(int, double);
可以这样使用:
pf = &fun; //赋值(assignment)操作
(*pf)(5, 8.9);//函数调用操作
也请注意,C语言本身提供了一种简写方式如下:
pf = fun; // 赋值(assignment)操作
pf(5, 8.9); // 函数调用操作
不过我本人不是很喜欢这种简写,它对初学者带来了比较多的迷惑。
数组声明一般是这样:
int a[5];
对于数组指针(pointer to array)的声明是这样:
int (*pa)[5];
可以这样使用:
pa = &a; // 赋值(assignment)操作
int i = (*pa)[2]; // 将a[2]赋值给i;

2.有了上面的基础,我们就可以对付开头的三只纸老虎了!:) 这个时候你需要复习一下各种运算符的优先顺序和结合顺序了,顺便找本书看看就够了。
#1:int* (*a[5])(int, char*);
首先看到标识符名a,“[]”优先级大于“*”,a与“[5]”先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,
指针指向“(int, char*)”,对,指向一个函数,函数参数是“int, char*”,返回值是“int*”。完毕,我们干掉了第一个纸老虎。:)
#2:void (*b[10]) (void (*)());
b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void (*)()”【注1】,返回值是“void”。完毕!
注1:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。
#3:double(*)()(*pa)[9];
pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“double(*)()”【也即一个指针,指向一个函数,函数参数为空,返回值是“double
”】。(注意typedef int* p[9]与typedef int(*p)[9]的区别,前者定义一个数组,此数组包含9个int*类型成员,而后者定义一个指向数组的指针,被指向的数组包含9个int类型成员)。
现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式熟悉之后,就可以用“typedef”来简化这种类型声明。
#1:int* (*a[5])(int, char*);
typedef int* (*PF)(int, char*);//PF是一个类型别名【注2】。
PF a[5];//跟int* (*a[5])(int, char*);的效果一样!
注2:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法
很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头”。
#2:void (*b[10])(void (*)());
typedef void (*pfv)();
typedef void (*pf_taking_pfv)(pfv);
pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一样!
#3. double(*)()(*pa)[9];
typedef double(*PF)();
typedef PF (*PA)[9];
PA pa; //跟doube(*)()(*pa)[9];的效果一样!

3.const和volatile在类型声明中的位置。
在这里我只说const,volatile是一样的!【注3】
注3:顾名思义,volatile修饰的量就是很容易变化,不稳定的量,它可能被其它线程,操作系统,硬件等等在未知的时间改变,
所以它被存储在内存中,每次取用它的时候都只能在内存中去读取,它不能被编译器优化放在内部寄存器中。
类型声明中const用来修饰一个常量,我们一般这样使用:const在前面:
const int; //int是const
const char*;//char是const
char* const;//*(指针)是const
const char* const;//char和*都是const
对初学者,const char*和 char* const是容易混淆的。这需要时间的历练让你习惯它。 上面的声明有一个对等的写法:const在后面:
int const; //int是const
char const*;//char是const
char* const;//*(指针)是const
char const* const;//char和*都是const
第一次你可能不会习惯,但新事物如果是好的,我们为什么要拒绝它呢?:)const在后面有两个好处:
A.const所修饰的类型正好是在它前面的那一个。如果这个好处还不能让你动心的话,那请看下一个!
B.我们很多时候会用到typedef的类型别名定义。比如typedef char* pchar,如果用const来修饰的话,
当const在前面的时候,就是const pchar,你会以为它就是const char* ,但是你错了,它的真实含义是char* const。
是不是让你大吃一惊!但如果你采用const在后面的写法,意义就怎么也不会变,不信你试试!
不过,在真实项目中的命名一致性更重要。你应该在两种情况下都能适应,并能自如的转换,公司习惯,
商业利润不论在什么时候都应该优先考虑!不过在开始一个新项目的时候,你可以考虑优先使用const在后面的习惯用法。


二.

Typedef声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。
不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用typedef避免缺欠,从而使代码更健壮。
typedef声明,简称typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。
所谓美观,意指typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。
本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱,如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法.
typedef使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于typedef关键字右边。
例如:typedef int size;
此声明定义了一个 int 的同义字,名字为 size。注意typedef并不创建新的类型。它仅仅为现有类型添加一个同义字。
你可以在任何需要 int 的上下文中使用 size:
void measure(size * psz);
size array[4];
size len = file.getlength();
typedef 还可以掩饰复合类型,如指针和数组。例如,你不用象下面这样重复定义有81个字符元素的数组:
char line[81]; char text[81];
定义一个typedef,每当要用到相同类型和大小的数组时,可以这样:
typedef char Line[81];
Line text, secondline;
getline(text);
同样,可以象下面这样隐藏指针语法:
typedef char * pstr;
int mystrcmp(pstr, pstr);   
这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个const char *类型的参数。因此,它可能会误导人们象下面这样声明:
int mystrcmp(const pstr, const pstr);
这是错误的,事实上,const pstr被编译器解释为char * const(一个指向 char 的常量指针),而不是const char *(指向常量 char 的指针)。
这个问题很容易解决:
typedef const char * cpstr;
int mystrcmp(cpstr, cpstr);
上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是typedef在编译时被解释
,因此让编译器来应付超越预处理器能力的文本替换。例如:
typedef int (*PF) (const char *, const char *);
这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么
上述这个 typedef 是不可或缺的:
PF Register(PF pf);
Register()的参数是一个PF类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *);
很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,
而是一种必需。typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。
这并不是说typedef会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。
下面将带到第二个陷阱:
typedef register int FAST_COUNTER; // 错误编译通不过
问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,
在 typedef 声明中不能用 register(或任何其它存储类关键字)。typedef 有另外一个重要的用途,那就是定义机器无关的类型,
例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以获得最高的精度:

typedef long double REAL;
在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
typedef double REAL;
并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:
typedef float REAL;
你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。
在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗?
标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。
此外,象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string,allocator> 和 basic_ofstream>。