C基本知识点拾遗

最近需要学习C语言,找了些C容易犯错的知识点和重点,以提示自己。

总体上必须清楚的:

 1)程序结构是三种:  顺序结构选择结构(分支结构)、循环结构

 2)读程序都要从main()入口, 然后从最上面顺序往下读(碰到循环做循环,碰到选择做选择),有且只有一个main函数。

 3)计算机的数据在电脑中保存是以二进制的形式. 数据存放的位置就是 他的地址.

 4)bit 是指为0 或者1 byte 是指字节, 一个字节 = 八个位.

概念常考到的:

1、编译预处理不是C语言的一部分,不占运行时间,不要加分号。C语言编译的程序称为源程序,它以ASCII数值存放在文本文件中。

2、#define PI 3.1415926; 这个写法是错误的,一定不能出现分号。 

3、每个C语言程序中main函数有且只有一个

4、在函数中不可以再定义函数。

5、算法:可以没有输入,但是一定要有输出

6、break可用于循环结构和switch语句。

7、逗号运算符的级别最低赋值的级别倒数第二

第一章C语言的基础知识

第一节、对C语言的基础认识

1、C语言编写的程序称为源程序又称为编译单位

2、C语言书写格式是自由的,每行可以写多个语句,可以写多行。

3、一个C语言程序有且只有一个main函数,是程序运行的起点

第二节、熟悉vc++

1、VC是软件,用来运行写的C语言程序。

2、每个C语言程序写完后,都是先编译,后链接,最后运行。(.c---à.obj---à.exe)这个过程中注意.c和.obj文件时无法运行的,只有.exe文件才可以运行。(常考!)

第三节、标识符

1、标识符(必考内容):

合法的要求是由字母数字下划线组成。有其它元素就错了。

并且第一个必须为字母或则是下划线。第一个为数字就错了

2、标识符分为关键字、预定义标识符、用户标识符。

关键字:不可以作为用户标识符号main define  scanf  printf 都不是关键字。迷惑你的地方If是可以做为用户标识符。因为If中的第一个字母大写了,所以不是关键字。

预定义标识符:背诵define scanf  printf  include。记住预定义标识符可以做为用户标识符。

用户标识符:基本上每年都考,详细请见书上习题。

第四节:进制的转换

十进制转换成二进制、八进制、十六进制。

    二进制、八进制、十六进制转换成十进制。

第五节:整数与实数

1)C语言只有八、十、十六进制,没有二进制。但是运行时候,所有的进制都要转换成二进制来进行处理。(考过两次)

    a、C语言中的八进制规定要以0开头。018的数值是非法的,八进制是没有8的,逢8进1。 

    b、C语言中的十六进制规定要以0x开头。

2)小数的合法写法C语言小数点两边有一个是零的话可以不用写

1.0在C语言中可写成1.

0.1在C语言中可以写成.1。

3)实型数据的合法形式:

a、2.333e-1 就是合法的,且数据是2.333×10-1

b、考试口诀:ee后必有数,e必为整数请结合书上的例子。

4) 整型一般是4个字节, 字符型1个字节,双精度一般是8个字节:

  long int x; 表示x是长整型。

    unsigned int x; 表示x是无符号整型。

第六、七节:算术表达式和赋值表达式

核心:表达式一定有数值

1、算术表达式:+,-,*,/,%

   考试一定要注意:“/” 两边都是整型的话,结果就是一个整型。 3/2的结果就是1.

                  “/” 如果有一边是小数,那么结果就是小数。 3/2.0的结果就是0.5

                  “%”符号请一定要注意是余数,考试最容易算成了除号。)%符号两边要求是整数。不是整数就错了。[注意!!!]

2、赋值表达式:表达式数值是最左边的数值,a=b=5;该表达式为5,常量不可以赋值

1、int x=y=10: 错啦,定义时不可以连续赋值。

2、int x,y;

x=y=10;   对滴,定义完成后可以连续赋值。

3、赋值的左边只能是一个变量

4、int x=7.7;对滴,x就是7

5、float y=7;对滴,x就是7.0

3、复合的赋值表达式:

   int a=2;

   a*=2+3;运行完成后,a的值是12。

一定要注意,首先要在2+3的上面打上括号。变成(2+3)再运算。

4、自加表达式:

自加、自减表达式:假设a=5,++a(是为6), a++(为5);

运行的机理:++a 是先把变量的数值加上1,然后把得到的数值放到变量a中,然后再用这个++a表达式的数值为6,而a++是先用该表达式的数值为5,然后再把a的数值加上1为6,

再放到变量a中。 进行了++a和a++后在下面的程序中再用到a的话都是变量a中的6了。

  考试口诀:++在前先加后用++在后先用后加

5、逗号表达式:

优先级别最低。表达式的数值逗号最右边的那个表达式的数值。

(2,3,4)的表达式的数值就是4。

 z=(2,3,4)(整个是赋值表达式) 这个时候z的值为4。(有点难度哦!)

  z=  234 (整个是逗号表达式)这个时候z的值为2。

补充:

1、空语句不可以随意执行,会导致逻辑错误

2、注释是最近几年考试的重点,注释不是C语言,不占运行时间,没有分号。不可以嵌套!

3、强制类型转换

  一定是 (int)a 不是  int(a),注意类型上一定有括号的。

   注意(int)(a+b) 和(int)a+b 的区别。前是把a+b转型,后是把a转型再加b。

4、三种取整丢小数的情况

    1、int a =1.6;

              2、(int)a;  

              3、1/2; 3/2;

第八节、字符

1)字符数据的合法形式::

   '1'字符个字节,"1"是字符串占个字节(含有一个结束符号)。

  '0' 的ASCII数值表示为48,'a' 的ASCII数值是97,'A'的ASCII数值是65。

一般考试表示单个字符错误的形式:'65'    "1"  

字符是可以进行算术运算的,记住: '0'-0=48

大写字母和小写字母转换的方法: 'A'+32='a'  相互之间一般是相差32。

2)转义字符:

转义字符分为一般转义字符、八进制转义字符、十六进制转义字符

一般转义字符:背诵\0、 \n、 \’、 \”、 \\。

八进制转义字符:  ‘\141’ 是合法的, 前导的0是不能写的。

十六进制转义字符:’\x6d’ 才是合法的,前导的0不能写,并且x是小写

3、字符型和整数是近亲:两个具有很大的相似之处

          char a = 65 ; 

            printf(“%c”,a);  得到的输出结果:a

printf(“%d”, a); 得到的输出结果:65                

第九章、位运算 

1)位运算的考查:会有一到二题考试题目。

总的处理方法:几乎所有的位运算的题目都要按这个流程来处理(先把十进制变成二进制再变成十进制)。

例1: char a = 6,b;

     b = a<<2;  这种题目的计算是先要把a的十进制6化成二进制,再做位运算。

例2: 一定要记住,异或的位运算符号” ^ ”。0 异或 1得到1。

                                         0 异或 0得到0。两个女的生不出来

考试记忆方法:一男(1)一女(0)才可以生个小孩(1)。

例3: 在没有舍去数据的时候,<<左移一位表示乘以2;>>右移一位表示除以2。   

第二章

第一节:数据输出(一)(二)

1、使用printf和scanf函数时,要在最前面加上#include“stdio.h”

2、printf可以只有一个参数,也可以有两个参数。(选择题考过一次)

3、printf(“ 第一部分 ”,第二部分  );把第二部分的变量、表达式、常量以第一部分的形式展现出来!

4、printf(“a=%d,b=%d”,12, 34) 考试重点!

一定要记住是将12和34以第一部分的形式现在在终端也就是黑色的屏幕上。考试核心为一模一样在黑色屏幕上面显示为  a=12,b=34          

  printf(“a=%d,\n b=%d”,12, 34)那么输出的结果就是:a=12,

b=34

   5、int x=017;   一定要弄清楚为什么是这个结果!过程很重要

      printf(“%d”, x); 15

printf(“%o”, x); 17

printf(“%#o”,x); 017

printf(“%x”, x); 11 

printf(“%#x”,x); 0x11

   6、int x=12,y=34; 注意这种题型

      char z=‘a’;    

      printf(“%d ”,x,y); 一个格式说明,两个输出变量,后面的y不输出

      printf(“%c”,z);      结果为:12a

   7、一定要背诵的

格式说明

表示内容

格式说明

表示内容

%d

整型   int

%c

字符  char

%ld

长整型 long int

%s

字符串

%f

浮点型 float

%o

八进制

%lf

double

%#o

带前导的八进制

%%

输出一个百分号

%x

十六进制

%5d

 

%#x

带前导的十六进制

举例说明:

printf(“%2d”,123 );  第二部分有三位,大于指定的两位,原样输出123

printf(“%5d”,123 );  第二部分有三位,小于指定的五位,左边补两个空格  123

printf(“%10f”,1.25 ); 小数要求补足6位的,没有六位的补0,。结果为 1.250000

printf(“%5.3f”,125 ); 小数三位,整个五位,结果为1.250(小数点算一位)

printf(“%3.1f”,1.25 );小数一位,整个三位,结果为1.3(要进行四舍五入)

第三节 数据输入

1、scanf(“a=%d,b=%d”,&a,&b) 考试超级重点!

一定要记住是以第一部分的格式在终端输入数据。考试核心为:一模一样

在黑色屏幕上面输入的为  a=12,b=34才可以把12和34正确给a和b 。有一点不同也不行。        

2、scanf(“%d,%d”,x,y);这种写法绝对错误,scanf的第二个部分一定要是地址!

scanf(“%d,%d”,&x,&y);注意写成这样才可以!

3、特别注意指针在scanf的考察

例如: int x=2;int *p=&x;

scanf(“%d”,x);   错误          scanf(“%d”,p);正确

scanf(“%d”,&p);  错误         scanf(“%d”,*p)错误

4、指定输入的长度 (考试重点)

终端输入:1234567

scanf(“%2d%4d%d”,&x,&y,&z);x为12,y为3456,z为7

终端输入:1 234567     由于1和2中间有空格,所以只有1位给x

scanf(“%2d%4d%d”,&x,&y,&z);x为1,y为2345,z为67

5、字符和整型是近亲:

intx=97;

printf(“%d”,x);   结果为97

printf(“%c”,x);   结果为 a

6、输入时候字符和整数的区别(考试超级重点

scanf(“%d”,&x);这个时候输入1,特别注意表示的是整数1

scanf(“%c”,&x);这个时候输入1,特别注意表示的是字符‘1’ASCII为整数48。

补充说明:

1)scanf函数的格式考察:

   注意该函数的第二个部分是&a 这样的地址,不是a; 

   scanf(“%d%d%*d%d”,&a,&b,&c); 跳过输入的第三个数据。

2)putchar ,getchar 函数的考查:

   char a = getchar() 是没有参数的,从键盘得到你输入的一个字符给变量a。

   putchar(‘y’)把字符y输出到屏幕中。

3)如何实现两个变量x ,y中数值的互换(要求背下来)

   不可以把 x=y ,y=x; 要用中间变量 t=x;x=y;y=t

4)如何实现保留三位小数,第四位四舍五入的程序,(要求背下来)

       y=(int)(x*100+0.5)/100.0   这个保留两位,对第三位四舍五入

       y=(int)(x*1000+0.5)/1000.0 这个保留三位,对第四位四舍五入

y=(int)(x*10000+0.5)/10000.0 这个保留四位,对第五位四舍五入

   这个有推广的意义,注意 x = (int)x 这样是把小数部分去掉。

                                 第三章

特别要注意:C语言中是用非0表示逻辑真的,用0表示逻辑假的。

            C语言构造类型没有逻辑类型

            关系运算符号:注意<=的写法,==和=的区别!(考试重点)

            if只管后面一个语句要管多个,请用大括号

1)关系表达式:

   a、表达式的数值只能为1(表示为真),或0(表示假)。

如 9>8这个关系表达式是真的,所以9>8这个表达式的数值就是1。

如 7<6这个关系表达式是假的,所以7<6这个表达式的数值就是0

   b、考试最容易错的:就是int x=1,y=0,z=2;

                          x

错的,但是如果是C语言那么就是正确的!因为要1<0为假得到0,表达式就变成

了0<2那么运算结果就是1,称为了真的了!

   c、等号和赋值的区别!一定记住“=”就是赋值,“= =”才是等号。虽然很多人可以背

诵,但我依然要大家一定好好记住,否则,做错了,我一定会强烈的鄙视你!

2)逻辑表达式:

   核心:表达式的数值只能为1(表示为真),或0(表示假)。

a)  共有&&   ||   ! 三种逻辑运算符号。

b) !>&&>||  优先的级别。

c)  注意短路现象。考试比较喜欢考到。详细请见书上例子,一定要会做例1和例2

d)  表示 x 小于0大于10的方法。

0一定记住)。是先计算0(0表示比0大比10

3)if 语句

  a、else 是与最接近的if且没有else的语句匹配。

  b、交换的程序写法:t=x;x=y;y=t;

  c、if(a

     if(a

  d、单独的if语句:if(a

     标准的if语句:if(a

else  min=b;

     嵌套的if语句:if(a

if(b>c)printf(“ok!”);

     多选一的if语句if(a= =t)printf(“a”);

                   else if(b= =t)printf(“b”);

                   else if(c= =t)printf(“c”);

                   else pritnf(“d”);

     通过习题,要熟悉以上几种if语句!

经典考题:结合上面四种if语句题型做题,答错了,请自行了断!预备,开始!

int  a=1,b=0;

if(!a)b++;

else if(a= =0)

if(a)b+=2;

else b+=3;请问b的值是多少?

如果没有看懂题目,你千万不要自行了断,这样看得懂不会做的人才会有理由的活着。

 正确的是b为3。

int  a=1,b=0;

if(!a)b++;    是假的不执行

elseif(a= =0)    是假的执行

if(a)b+=2; 属于else if的嵌套if语句,不执行。

elseb+=3;        if-else-if语句没有一个正确的,就执行else的语句!

4)条件表达式:

     表达式1 ?表达式2 :表达式3

a、考试口诀:真前假后

   b、注意是当表达式1的数值是0时,才采用表达式2的数值做为整个运算结果,当表达式1的数值0时,就用表达式3的数值做为整个的结果。

   c、int a=1,b=2,c=3,d=4,e=5;

k=a>b?c:d>e?d:e;求k的数值时多少?  答案为san

5)switch语句:

a) 执行的流程一定要弄懂!上课时候详细的过程讲了,请自己一定弄懂!

b)注意有break 和没有break的差别,书上的两个例子,没有break时候,只要有一个case匹配了,剩下的都要执行,有break则是直接跳出了swiche语句。break在C语言中就是分手,一刀两断的意思。

c) switch只可以和break一起用,不可以和continue用。

d) switch(x)   x:是整型常量,字符型常量,枚举型数据。

{case 1: ….     不可以是变量。

case 2: ….

}

    e)switch是必考题型,请大家一定要完成书上的课后的switch的习题。

                                       第四章

1)三种循环结构:

   a)for() ; while();  do- while()三种。

   b)for循环当中必须两个分号,千万不要忘记。

   c)写程序的时候一定要注意,循环一定要有结束的条件,否则成了死循环。

   d) do-while()循环的最后一个while();的分号一定不能够丢。(当心上机改错),do-while循环是至少执行一次循环。

2) break 和  continue的差别

   记忆方法:

break:是打破的意思,(破了整个循环)所以看见break就退出整个一层循环。

continue: 是继续的意思,(继续循环运算),但是结束本次循环,就是循环体内剩下的语句不再执行,跳到循环开始,然后判断循环条件,进行新一轮的循环

3)嵌套循环

   就是有循环里面还有循环,这种比较复杂,要一层一层一步一步耐心的计算,一般记住两层是处理二维数组的。

4)while((c=getchar())!=’\n’) 和

while(c=getchar() !=’\n’)的差别

先看a = 3 != 2  和 (a=3)!=2 的区别:

(!=号的级别高于=号 所以第一个先计算 3!=2) 第一个a的数值是得到的1;第二个a的数值是3。

考试注意点: 括号在这里的重要性。

5)每行输出五个的写法:

for(i=0;i<=100;i++)

{printf(“%d”,i);

 if((i+1)%5==0)printf(“\n”); 如果i是从1开始的话,就是if(i%5==0)printf(“\n”);

}

6)如何整除一个数:i%5==0表示整除5

                   I%2==0表示整除2,同时表示是偶数!

7)输入123,输出321逆序输出数据

int i=123;       

while(i!=0)

{

  printf(“%d”,i%10

i=i/10;}

8)for只管后面一个语句

inti=3;

for(i=3;i<6;i++):

printf(“#”):                请问最终打印几个#号?答案为一个!

9)不停的输入,直到输入# 停止输入!      不停的输入,直到输入$停止输入!

    while( (x=getchar())!=’ # ’ )                 while( (x=getchar())!=’$ ’ )

不停的输入,直到遇到?停止输入!

while((x=getchar())!=’ ? ’)   解说:一定要注意这种给出了条件,然后如何去写的方法!  

10)for循环和switch语句的和在一起考题!   

11)多次出现的考题

intk=1                       int k=1;

while(- -k);                while(k--);

printf(“%d”,k);           printf(“%d”,k);    

结果为0                     结果为-1

第五章

1、函数:是具有一定功能的一个程序块,是C语言的基本组成单位。

2、函数不可以嵌套定义。但是可以嵌套调用。

3、函数名缺省返回值类型,默认为 int。

4、C语言由函数组成,但有且仅有一个main函数!是程序运行的开始!

5、如何判断a是否为质数:背诵这个程序!

void  iszhishu( int a )

{  for(i=2;i

    if(a%i==0) printf(“不是质数”);

   printf(“是质数!”);

}

6、如何求阶层n! 背诵这个程序!

   int fun(int n)

{  int p=1;

for(i=1;i<=n;i++) p=p*i;

return p;

}

7、函数的参数可以是常量,变量,表达式,甚至是函数调用

  add(int x,int y){returnx+y;}

  main()

{ int sum;

 sum=add(add(7,8),9);请问sum的结果是多少? 结果为24

}

8、 函数的参数,返回数值(示意图):

9、一定要注意参数之间的传递

   实参和形参之间 传数值,和传地址的差别。(考试的重点)

      传数值的话,形参的变化不会改变实参的变化。

      传地址的话,形参的变化就会有可能改变实参的变化。

10、函数声明的考查:

一定要有:函数名,函数的返回类型,函数的参数类型。不一定要有:形参的名称

填空题也可能会考到!以下是终极难度的考题。打横线是函数声明怎么写!

int*fun(int a[] , int b[])             

{

…………..

}已经知道函数是这样。这个函数的正确的函数声明怎么写?

int *fun(int *a , int *b)               这里是函数声明的写法,注意数组就是指针

int *fun(int a[] , int b[])              这种写法也是正确的                      

int *fun(int b[] , int c[])              这种写法也是正确的,参数的名称可以随便写

int *fun(int * , int *)                这种写法也是正确的,参数的名称可以不写 

11、要求掌握的库函数:

    a、库函数是已经写好了函数,放在仓库中,我们只需要如何去使用就可以了!   

b、以下这些库函数经常考到,所以要背诵下来。

abs()、 sqrt()、fabs()、pow()、sin()  其中pow(a,b)是重点。23是由pow(2,3)表示的。

 

第六章

指针变量的本质是用来放地址,而一般的变量是放数值的。

1、int *p 中   *p和p的差别:简单说*p是数值,p是地址!

*p可以当做变量来用;*的作用是取后面地址p里面的数值

 p是当作地址来使用。可以用在scanf函数中:scanf(“%d”,p);

 

2、*p++ 和 (*p)++的之间的差别:改错题目中很重要!考试超级重点

         *p++是 地址会变化。      口诀:取当前值,然后再移动地址!

         (*p)++ 是数值会要变化。 口诀:取当前值,然后再使数值增加1。   

例题:int *p,a[]={1,3,5,7,9};

      p=a;

      请问*p++和(*p)++的数值分别为多少?

      *p++:  这个本身的数值为1。由于是地址会增加一,所以指针指向数值3了。 

(*p)++ 这个本身的数值为1。由于有个++表示数值会增加,指针不移动,但数值1由于自加了一次变成了2。      

3、二级指针:

  *p:一级指针:存放变量的地址。

  **q:二级指针:存放一级指针的地址。

  常考题目:   int x=7;

               int*p=&x,**q=p;

               问你:*p为多少?*q为多少?**q为多少?

                       7          p         7

               再问你:**q=&x的写法可以吗?  

                      不可以,因为二级指针只能存放一级指针的地址。

4、三名主义:(考试的重点)

   数组名:表示第一个元素的地址。数组名不可以自加,他是地址常量名。(考了很多次)

   函数名:表示该函数的入口地址。

   字符串常量名:表示第一个字符的地址。

5、移动指针(经常加入到考试中其他题目综合考试)

  char *s=“meikanshu”  

while(*s){printf(“%c”,*s);s++;}

这个s首先会指向第一个字母m然后通过循环会一次打印出一个字符,s++是地址移动,打印了一个字母后,就会移动到下一个字母!

6指针变量两种初始化(一定要看懂)

方法一:int a=2,*p=&a;(定义的同时初始化)

方法二:int a=2,*p;  (定义之后初始化)

    p=&a;

7传数值和传地址(每年必考好多题目)

void fun(int a,intb)                    void fun(int *a,int *b)            

{ int t ;                                       { int t ;

  t=a;a=b;b=t;                                  t=*a;*a=*b;*b=t;

}                                               }

main()                                     main()

{ int x=1,y=3,                             { int x=1,y=3,

  fun(x,y);                                 fun(&x,&y)

  printf(“%d,%d”,x,y);                    printf(“%d,%d”,x,y);

}                                             }

这个题目答案是1和3。                        这个题目的答案就是3和1。

传数值,fun是用变量接受,所以fun中     传地址,fun用指针接受!这个时候fun

的交换不会影响到main中的x和y 。        中的交换,就会影响到main中的x和y。

传数值,形参的变化不会影响实参。          传地址形参的变化绝大多数会影响到实参!

 

8、函数返回值是地址,一定注意这个*号(上机考试重点)  

int *fun(int*a,int *b)   可以发现函数前面有个*,这个就说明函数运算结果是地址                            

{ if(*a>*b)returna;     return a 可以知道返回的是a地址。

  else return b;                                                                        

}

main()

{ int x=7,y=8,*max;

  max = fun(&x,&y);      由于fun(&x,&y)的运算结果是地址,所以用max来接收。

  printf(“%d,%d”,)   

}                                             

9、考试重要的话语:

指针变量是存放地址的。并且指向哪个就等价哪个,所有出现*p的地方都可以用它等价的代替。例如:int a=2,*p=&a;

    *p=*p+2;

(由于*p指向变量a,所以指向哪个就等价哪个,这里*p等价于a,可以相当于是a=a+2) 

 

第七章

数组: 存放的类型是一致的。多个数组元素的地址是连续的。

1、一维数组的初始化:

inta[5]={1,2,3,4,5};  合法

int a[5]={1,2,3,};    合法

inta[]={1,2,3,4,5};   合法,常考,后面决定前面的大小!

inta[5]={1,2,3,4,5,6};不合法,赋值的个数多余数组的个数了

2、一维数组的定义;

int a[5];注意这个地方有一个重要考点,定义时数组的个数不是变量一定是常量。

int a[5]                 合法,最正常的数组

int a[1+1]               合法,个数是常量2,是个算术表达式

int a[1/2+4]             合法,同样是算术表达式

int x=5,int a[x];           不合法,因为个数是x,是个变量,非法的,

define P 5  int a[P]       合法,define 后的的P是符号常量,只是长得像变量

3、二维数组的初始化

inta[2][3]={1,2,3,4,5,6};               合法,很标准的二维的赋值。

inta[2][3]={1,2,3,4,5, };                合法,后面一个默认为0

inta[2][3]={{1,2,3,} {4,5,6}};           合法,每行三个。

inta[2][3]={{1,2,}{3,4,5}};             合法,第一行最后一个默认为0

inta[2][3]={1,2,3,4,5,6,7};              不合法,赋值的个数多余数组的个数了。

inta[][3]={1,2,3,4,5,6};                不合法,不可以缺省行的个数。

inta[2][]={1,2,3,4,5,6};                合法,可以缺省列的个数

补充:

1)一维数组的重要概念:

对a[10]这个数组的讨论。

1、a表示数组名,是第一个元素的地址,也就是元素a[0]的地址。(等价于&a

2、a是地址常量,所以只要出现a++,或者是a=a+2赋值的都是错误的。

3、a是一维数组名,所以它是列指针,也就是说a+1跳一列。 

对a[3][3]的讨论。

1、a表示数组名,是第一个元素的地址,也就是元素a[0][0]的地址。

2、a是地址常量,所以只要出现a++,或者是a=a+2赋值的都是错误的。

3、a是二维数组名,所以它是行指针,也就是说a+1跳一行

4、a[0]、a[1]、a[2]也都是地址常量,不可以对它进行赋值操作,同时它们都是列指针,a[0]+1,a[1]+1,a[2]+1都是跳一列。

5、注意a和a[0] 、a[1]、a[2]是不同的,它们的基类型是不同的。前者是一行元素,后三者是一列元素。

2) 二维数组做题目的技巧:

如果有a[3][3]={1,2,3,4,5,6,7,8,9}这样的题目。

步骤一:把他们写成:      第一列 第二列 第三列  

a[0]à  1    2    3   ->第一行

a[1]à   4     5    6  —>第二行

a[2]à   7     8    9  ->第三行

步骤二:这样作题目间很简单:    

*(a[0]+1)我们就知道是第一行的第一个元素往后面跳一列,那么这里就是a[0][1]元素,所以是1。

*(a[1]+2)我们就知道是第二行的第一个元素往后面跳二列。那么这里就是a[1][2]元素,所以是6。

一定记住:只要是二维数组的题目,一定是写成如上的格式,再去做题目,这样会比较简单。

3) 数组的初始化,一维和二维的,一维可以不写,二维第二个一定要写

     int a[]={1,2} 合法。  int a[][4]={2,3,4}合法。   但inta[4][]={2,3,4}非法。

4) 二维数组中的行指针

 int a[1][2]; 

其中a现在就是一个行指针,a+1跳一行数组元素。  搭配(*)p[2]指针

     a[0],a[1]现在就是一个列指针。a[0]+1 跳一个数组元素。搭配*p[2]指针数组使用

5) 还有记住脱衣服法则:超级无敌重要

   a[2]  变成   *(a+2)  a[2][3]变成 *(a+2)[3]再可以变成  *(*(a+2)+3)

这个思想很重要!

 

 

其它重点

文件的复习方法

把上课时候讲的文件这一章的题目要做一遍,一定要做,基本上考试的都会在练习当中。

1)字符串的 strlen() 和 strcat() 和strcmp() 和strcpy()的使用方法一定要记住。他们的参数都是地址。其中strcat()和strcmp()有两个参数。

 

2)strlen 和 sizeof的区别也是考试的重点;

 

3)define  f(x)(x*x)  和  define   f(x) x*x 之间的差别。一定要好好的注意这写容易错的地方,替换的时候有括号和没有括号是很大的区别。

 

4)int  *p

p= (int *)malloc(4);

p= (int *)malloc(sizeof(int));以上两个等价

当心填空题目,malloc的返回类型是 void *

 

6)函数的递归调用一定要记得有结束的条件,并且要会算简单的递归题目。要会作递归的题目

 

7)结构体和共用体以及链表要掌握最简单的。typedef考的很多,而且一定要知道如何引用结构体中的各个变量,链表中如何填加和删除节点,以及何如构成一个简单的链表,一定记住链表中的节点是有两个域,一个放数值,一个放指针。

 

8)函数指针的用法(*f)()记住一个例子:

      int add(int x, int y)

{....}

 main()

{ int  (*f)();

  f=add;

 }

赋值之后:合法的调用形式为1、add(2,3);

2、f(2,3);

3、(*f)(2,3)

9)两种重要的数组长度:

char a[]={‘a’,’b’,’c’};  数组长度为3,字符串长度不定。sizeof(a)为3。

char a[5]={ ‘a’,’b’,’c’}  数组长度为5,字符串长度3。sizeof(a)为5。

10)scanf 和 gets的数据:

如果输入的是 good  good study!

那么scanf(“%s”,a); 只会接收 good. 考点:不可以接收空格。

     gets(a); 会接收 good good study! 考点:可以接收空格。

11)共用体的考查:

union TT

{ int a;

charch[2];}

考点一: sizeof (struct TT) = 4;

12)“文件包含”的考查点:

       no1.c                  no2.c

#include”no2.c”

main()

{ add(29 , 33);

 …….

}

 

int add(int a,int b)

{

return a+b;

}

 

 

 

 

 

 


这里一个C语言程序是有两个文件组成,分别是no1.c, no2.c。那么no1.c中最开始有个#include”no2.c”他表示把第二个文件的内容给包含过来,那么no1.c中调用add()函数的时候就可以了把数值传到no2.c中的被调用函数add()了。

一个文件必须要有main函数。 这句话错了。 例如:no2.c就没有。

头文件一定是以.h结束的。 这句话错了。例如:no1.c中就是#include”no2.c”以.c结尾的。

13)指针迷惑的考点:

char ch[]=”iamhandsome”;

char *p=ch;

问你 *(p+2) 和 *p+2的结果是多少?

      ‘m’       ‘k’  结果是这两个,想不通的同学请作死的想!想通为止!

14)数组中放数组一定要看懂:

   int a[8]={1,2,3,4,4,3,2,2};

   int b[5]={0};

   b[a[3]]++   这个写法要看懂,结果要知道是什么?b[4]++,本身是0,运行完后,b[4]为1了。

15)字符串的赋值

  C语言中没有字符串变量,所以用数组和指针存放字符串:

1、char  ch[10]={“abcdefgh”};                       对

2、char  ch[10]=“abcdefgh”;                         对

3、char  ch[10]={‘a’,’b’,’c’,’d’,’e’,’f’,’g’,’h’};           对

4、char  *p=“abcdefgh”;                           对

5、char  *p;                                     对

p=“abcdefgh”;

6、char  ch[10];                    错了!数组名不可以赋值!

          ch=“abcdefgh”;

7、char  *p={“abcdefgh”};           错了!不能够出现大括号!

16)字符串赋值的函数背诵:一定要背诵,当心笔试填空题目。

  把s指针中的字符串复制到t指针中的方法

  1、while( (*t=*s)!=null){s++;t++;}  完整版本

  2、while( *t=*s ){s++;t++;}           简单版本

  3、while( *t++=*s++);                   高级版本

17)typedef 是取别名,不会产生新的类型,他同时也是关键字

考点一:typedef int qq  那么 int x 就可以写成 qq x

考点二:typedef int *qq  那么 int *x就可以写成 qq x

18)static 考点是一定会考的!复习相关的习题。

static int x;默认值为0。

int x:默认值为不定值。

19)函数的递归调用


 C精华总结

C++的前世是C,而且C所留下的神秘以及精简在C++中是青出于蓝而胜于蓝!C所带给人的困惑以及灵活太多,即使一个有几年经验的高段C程序员仍然有可能在C语言的小水沟里翻船。不过其实C语言真的不难,下面我想指出C语言中最神秘而又诡谲多变的四个地方,它们也继续在C++语言中变幻莫测。 
指针,数组,类型的识别,参数可变的函数。

一.指针。


它的本质是地址的类型。在许多语言中根本就没有这个概念。但是它却正是C灵活,高效,在面向过程的时代所向披靡的原因所在。因为C的内存模型基本上对应了现在von Neumann(冯·诺伊曼)计算机的机器模型,很好的达到了对机器的映射。不过有些人似乎永远也不能理解指针【注1】。 
注1:Joel Spolsky就是这样认为的,他认为对指针的理解是一种aptitude,不是通过训练就可以达到的 
指针可以指向值、数组、函数,当然它也可以作为值使用。 
看下面的几个例子: 
int* p;//p是一个指针,指向一个整数 
int** p;//p是一个指针,它指向第二个指针,然后指向一个整数 
int (*pa)[3];//pa是一个指针,指向一个拥有3个整数的数组 
int (*pf)();//pf是一个指向函数的指针,这个函数返回一个整数 
后面第四节我会详细讲解标识符(identifier)类型的识别。 
1.指针本身的类型是什么? 
先看下面的例子:int a;//a的类型是什么? 
对,把a去掉就可以了。因此上面的4个声明语句中的指针本身的类型为: 
int* 
int** 
int (*)[3] 
int (*)() 
它们都是复合类型,也就是类型与类型结合而成的类型。意义分别如下: 
point to int(指向一个整数的指针) 
pointer to pointer to int(指向一个指向整数的指针的指针) 
pointer to array of 3 ints(指向一个拥有三个整数的数组的指针) 
pointer to function of parameter is void and return value is int (指向一个函数的指针,这个函数参数为空,返回值为整数) 
2.指针所指物的类型是什么? 
很简单,指针本身的类型去掉 “*”号就可以了,分别如下: 
int 
int* 
int ()[3] 
int ()() 
3和4有点怪,不是吗?请擦亮你的眼睛,在那个用来把“*”号包住的“()”是多余的,所以: 
int ()[3]就是int [3](一个拥有三个整数的数组) 
int ()()就是int ()(一个函数,参数为空,返回值为整数)【注2】 
注2:一个小小的提醒,第二个“()”是一个运算符,名字叫函数调用运算符(function call operator)。 
3.指针的算术运算。 
请再次记住:指针不是一个简单的类型,它是一个和指针所指物的类型复合的类型。因此,它的算术运算与之(指针所指物的类型)密切相关。 
int a[8]; 
int* p = a; 
int* q = p + 3; 
p++; 
指针的加减并不是指针本身的表示加减,要记住,指针是一个元素的地址,它每加一次,就指向下一个元素。所以: 
int* q = p + 3;//q指向从p开始的第三个整数。 
p++;//p指向下一个整数。 
double* pd; 
……//某些计算之后 
double* pother = pd – 2;//pother指向从pd倒数第二个double数。 
4.指针本身的大小。 
在一个现代典型的32位机器上【注3】,机器的内存模型大概是这样的,想象一下,内存空间就像一个连续的房间群。每一个房间的大小是一个字节(一般是黑客动画吧8位)。有些东西大小是一个字节(比如char),一个房间就把它给安置了;但有些东西大小是几个字节(比如double就是8个字节,int就是4 个字节,我说的是典型的32位),所以它就需要几个房间才能安置。 
注3:什么叫32位?就是机器CPU一次处理的数据宽度是32位,机器的寄存器容量是32位,机器的数据,内存地址总线是32位。当然还有一些细节,但大致就是这样。16位,64位,128位可以以此类推。 
这些房间都应该有编号(也就是地址),32位的机器内存地址空间当然也是32位,所以房间的每一个编号都用32位的数来编码【注4】。请记住指针也可以作为值使用,作为值的时候,它也必须被安置在房间中(存储在内存中),那么指向一个值的指针需要一个地址大小来存储,即32位,4个字节,4个房间来存储。 
注4:在我们平常用到的32位机器上,绝少有将32位真实内存地址空间全用完的(232 = 4G),即使是服务器也不例外。现代的操作系统一般会实现32位的虚拟地址空间,这样可以方便运用程序的编制。关于虚拟地址(线性地址)和真实地址的区别以及实现,可以参考《Linux源代码情景分析》的第二章存储管理,在互联网上关于这个主题的文章汗牛充栋,你也可以google一下。 
但请注意,在C++中指向对象成员的指针(pointer to member data or member function)的大小不一定是4个字节。为此我专门编制了一些程序,发现在我的两个编译器(VC7.1.3088和Dev-C++4.9.7.0)上,指向对象成员的指针的大小没有定值,但都是4的倍数。不同的编译器还有不同的值。对于一般的普通类(class),指向对象成员的指针大小一般为4,但在引入多重虚拟继承以及虚拟函数的时候,指向对象成员的指针会增大,不论是指向成员数据,还是成员函数。【注5】。 
注5:在Andrei Alexandrescu的《Modern C++ Design》的5.13节Page124中提到,成员函数指针实际上是带标记的(tagged)unions,它们可以对付多重虚拟继承以及虚拟函数,书上说成员函数指针大小是16,但我的实践告诉我这个结果不对,而且具体编译器实现也不同。一直很想看看GCC的源代码,但由于旁骛太多,而且心不静,本身难度也比较高(这个倒是不害怕^_^),只有留待以后了。 
还有一点,对一个类的static member来说,指向它的指针只是普通的函数指针,不是pointer to class member,所以它的大小是4。 
5.指针运算符&和* 
它们是一对相反的操作,&取得一个东西的地址(也就是指针),*得到一个地址里放的东西。这个东西可以是值(对象)、函数、数组、类成员(class member)。 
其实很简单,房间里面居住着一个人,&操作只能针对人,取得房间号码; 
*操作只能针对房间,取得房间里的人。 
参照指针本身的类型以及指针所指物的类型很好理解。 
小结:其实你只要真正理解了1,2,就相当于掌握了指针的牛鼻子。后面的就不难了,指针的各种变化和C语言中其它普通类型的变化都差不多(比如各种转型)。

二.数组。

在C语言中,对于数组你只需要理解三件事。 
1.C语言中有且只有一维数组。 
所谓的n维数组只是一个称呼,一种方便的记法,都是使用一维数组来仿真的。 
C语言中数组的元素可以是任何类型的东西,特别的是数组作为元素也可以。所以int a[3][4][5]就应该这样理解:a是一个拥有3个元素的数组,其中每个元素是一个拥有4个元素的数组,进一步其中每个元素是拥有5个整数元素的数组。 
是不是很简单!数组a的内存模型你应该很容易就想出来了,不是吗?:) 
2.数组的元素个数,必须作为整数常量在编译阶段就求出来。 
int i; 
int a[];//不合法,编译不会通过。 
也许有人会奇怪char str[] = “test”;没有指定元素个数为什么也能通过,因为编译器可以根据后面的初始化字符串在编译阶段求出来, 
不信你试试这个:int a[]; 
编译器无法推断,所以会判错说“array size missing in a”之类的信息。不过在最新的C99标准中实现了变长数组【注6】 
注6:如果你是一个好奇心很强烈的人,就像我一样,那么可以查看C99标准6.7.5.2。 
3.对于数组,可以获得数组第一个(即下标为0)元素的地址(也就是指针),从数组名获得。 
比如int a[5]; int* p = a;这里p就得到了数组元素a[0]的地址。 
其余对于数组的各种操作,其实都是对于指针的相应操作。比如a[3]其实就是*(a+3)的简单写法,由于*(a+3)==*(3+a),所以在某些程序的代码中你会看到类似3[a]的这种奇怪表达式,现在你知道了,它就是a[3]的别名。还有一种奇怪的表达式类似a[-1],现在你也明白了,它就是* (a-1)【注7】。 
注7:你肯定是一个很负责任的人,而且也知道自己到底在干什么。你难道不是吗?:)所以你一定也知道,做一件事是要付出成本的,当然也应该获得多于成本的回报。 
我很喜欢经济学,经济学的一个基础就是做什么事情都是要花成本的,即使你什么事情也不做。时间成本,金钱成本,机会成本,健康成本……可以这样说,经济学的根本目的就是用最小的成本获得最大的回报。 
所以我们在自己的程序中最好避免这种邪恶的写法,不要让自己一时的智力过剩带来以后自己和他人长时间的痛苦。用韦小宝的一句话来说:“赔本的生意老子是不干的!” 
但是对邪恶的了解是非常必要的,这样当我们真正遇到邪恶的时候,可以免受它对心灵的困扰! 
对于指向同一个数组不同元素的指针,它们可以做减法,比如int* p = q+i;p-q的结果就是这两个指针之间的元素个数。i可以是负数。但是请记住:对指向不同的数组元素的指针,这样的做法是无用而且邪恶的! 
对于所谓的n维数组,比如int a[2][3];你可以得到数组第一个元素的地址a和它的大小。*(a+0)(也即a[0]或者*a)就是第一个元素,它又是一个数组int[3],继续取得它的第一个元素,*(*(a+0)+0)(也即a[0][0]或者*(*a)),也即第一个整数(第一行第一列的第一个整数)。如果采用这种表达式,就非常的笨拙,所以a[0][0]记法上的简便就非常的有用了!简单明了! 
对于数组,你只能取用在数组有效范围内的元素和元素地址,不过最后一个元素的下一个元素的地址是个例外。它可以被用来方便数组的各种计算,特别是比较运算。但显然,它所指向的内容是不能拿来使用和改变的! 
关于数组本身大概就这么多,下面简要说一下数组和指针的关系。它们的关系非常暧昧,有时候可以交替使用。 
比如 int main(int args, char* argv[])中,其实参数列表中的char* argv[]就是char** argv的另一种写法。因为在C语言中,一个数组是不能作为函数引数(argument)【注8】直接传递的。因为那样非常的损失效率,而这点违背了C语言设计时的基本理念——作为一门高效的系统设计语言。 
注8:这里我没有使用函数实参这个大陆术语,而是运用了台湾术语,它们都是argument这个英文术语的翻译,但在很多地方中文的实参用的并不恰当,非常的勉强,而引数表示被引用的数,很形象,也很好理解。很快你就可以像我一样适应引数而不是实参。 
dereferance,也就是*运算符操作。我也用的是提领,而不是解引用。 
我认为你一定智勇双全:既有宽容的智慧,也有面对新事物的勇气!你不愿意承认吗?:) 
所以在函数参数列表(parameter list)中的数组形式的参数声明,只是为了方便程序员的阅读!比如上面的char* argv[]就可以很容易的想到是对一个char*字符串数组进行操作,其实质是传递的char*字符串数组的首元素的地址(指针)。其它的元素当然可以由这个指针的加法间接提领(dereferance)【参考注8】得到!从而也就间接得到了整个数组。 
但是数组和指针还是有区别的,比如在一个文件中有下面的定义: 
char myname[] = “wuaihua”; 
而在另一个文件中有下列声明: 
extern char* myname; 
它们互相是并不认识的,尽管你的本义是这样希望的。 
它们对内存空间的使用方式不同【注9】。 
对于char myname[] = “wuaihua”如下 
myname 







\0 
对于char* myname;如下表 
myname 
\|/ 







\0 
注9:可以参考Andrew Konig的《C陷阱与缺陷》4.5节。 
改变的方法就是使它们一致就可以了。 
char myname[] = “wuaihua”; 
extern char myname[]; 
或者 
char* myname = “wuaihua”;//C++中最好换成const char* myname = “wuaihua”。 
extern char* myname; 
C之诡谲(下)

三.类型的识别。

基本类型的识别非常简单: 
int a;//a的类型是a 
char* p;//p的类型是char* 
…… 
那么请你看看下面几个: 
int* (*a[5])(int, char*); //#1 
void (*b[10]) (void (*)()); //#2 
doube(*)() (*pa)[9]; //#3 
如果你是第一次看到这种类型声明的时候,我想肯定跟我的感觉一样,就如晴天霹雳,五雷轰顶,头昏目眩,一头张牙舞爪的狰狞怪兽扑面而来。 
不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎的纸老虎! 
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 (*)()”【注10】,返回值是“void”。完毕! 
注10:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。 
#3. doube(*)() (*pa)[9]; 
pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”【也即一个指针,指向一个函数,函数参数为空,返回值是“double”】。 
现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式熟悉之后,就可以用“typedef”来简化这种类型声明。 
#1:int* (*a[5])(int, char*); 
typedef int* (*PF)(int, char*);//PF是一个类型别名【注11】。 
PF a[5];//跟int* (*a[5])(int, char*);的效果一样! 
注 11:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字 typedef加在该语句的开头”。可以参看《程序员》杂志2001.3期《C++高手技巧20招》。 
#2:void (*b[10]) (void (*)()); 
typedef void (*pfv)(); 
typedef void (*pf_taking_pfv)(pfv); 
pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一样! 
#3. doube(*)() (*pa)[9]; 
typedef double(*PF)(); 
typedef PF (*PA)[9]; 
PA pa; //跟doube(*)() (*pa)[9];的效果一样! 
3.const和volatile在类型声明中的位置 
在这里我只说const,volatile是一样的【注12】! 
注12:顾名思义,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在后面的习惯用法。

四.参数可变的函数

C语言中有一种很奇怪的参数“…”,它主要用在引数(argument)个数不定的函数中,最常见的就是printf函数。 
printf(“Enjoy yourself everyday!\n”); 
printf(“The value is %d!\n”, value); 
…… 
你想过它是怎么实现的吗? 
1. printf为什么叫printf? 
不管是看什么,我总是一个喜欢刨根问底的人,对事物的源有一种特殊的癖好,一段典故,一个成语,一句行话,我最喜欢的就是找到它的来历,和当时的意境,一个外文翻译过来的术语,最低要求我会尽力去找到它原本的外文术语。特别是一个字的命名来历,我一向是非常在意的,中国有句古话:“名不正,则言不顺。 ”printf中的f就是format的意思,即按格式打印【注13】。 
注13:其实还有很多函数,很多变量,很多命名在各种语言中都是非常讲究的,你如果细心观察追溯,一定有很多乐趣和满足,比如哈希表为什么叫hashtable而不叫hashlist?在C++的SGI STL实现中有一个专门用于递增的函数iota(不是itoa),为什么叫这个奇怪的名字,你想过吗? 
看文章我不喜欢意犹未尽,己所不欲,勿施于人,所以我把这两个答案告诉你: 
(1)table与list做为表讲的区别: 
table: 
-------|--------------------|------- 
item1 | kadkglasgaldfgl | jkdsfh 
-------|--------------------|------- 
item2 | kjdszhahlka | xcvz 
-------|--------------------|------- 
list: 
**** 
*** 
******* 
***** 
That's the difference! 
如果你还是不明白,可以去看一下hash是如何实现的! 
(2)The name iota is taken from the programming language APL. 
而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母, 
希腊字母表中有这样一个字母,大写为Ι,小写为ι, 
它的英文拼写正好是iota,这个字母在θ(theta)和κ(kappa)之间! 
下面有一段是这样的: 
APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-set, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using only ASCII characters. 
在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。 
在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。一个典型实现如下【注14】: 
typedef char* va_list; 
#define va_start(list) list = (char*)&va_alist 
#define va_end(list) 
#define va_arg(list, mode) 
((mode*) (list += sizeof(mode)))[-1] 
注14:你可以查看C99标准7.15节获得详细而权威的说明。也可以参考Andrew Konig的《C陷阱与缺陷》的附录A。 
ANSI C还提供了vprintf函数,它和对应的printf函数行为方式上完全相同,只不过用va_list替换了格式字符串后的参数序列。至于它是如何实现的,你在认真读完《The C Programming Language》后,我相信你一定可以do it yourself! 
使用这些工具,我们就可以实现自己的可变参数函数,比如实现一个系统化的错误处理函数error。它和printf函数的使用差不多。只不过将stream重新定向到stderr。在这里我借鉴了《C陷阱与缺陷》的附录A的例子。 
实现如下: 
#include 
#include 
void error(char* format, …) 

va_list ap; 
va_start(ap, format); 
fprintf(stderr, “error: “); 
vfprintf(stderr, format, ap); 
va_end(ap); 
fprintf(stderr, “\n”); 
exit(1); 

你还可以自己实现printf: 
#include 
int printf(char* format, …) 

va_list ap; 
va_start(ap, format); 
int n = vprintf(format, ap); 
va_end(ap); 
return n; 

我还专门找到了VC7.1的头文件看了一下,发现各个宏的具体实现还是有区别的,跟很多预处理(preprocessor)相关。其中va_list就不一定是char*的别名。 
typedef struct { 
char *a0; /* pointer to first homed integer argument */ 
int offset; /* byte offset of next parameter */ 
} va_list; 
其它的定义类似。 
经常在Windows进行系统编程的人一定知道函数调用有好几种不同的形式,比如__stdcall,__pascal,__cdecl。在Windows下_stdcall,__pascal是一样的,所以我只说一下__stdcall和__cdecl的区别。 
(1)__stdcall表示被调用端自身负责函数引数的压栈和出栈。函数参数个数一定的函数都是这种调用形式。 
例如:int fun(char c, double d),我们在main函数中使用它,这个函数就只管本身函数体的运行,参数怎么来的,怎么去的,它一概不管。自然有main负责。不过,不同的编译器的实现可能将参数从右向左压栈,也可能从左向右压栈,这个顺序我们是不能加于利用的【注15】。 
注15:你可以在Herb Sutter的《More Exceptional C++》中的条款20:An Unmanaged Pointer Problem, Part 1:Parameter Evaluation找到相关的细节论述。 
(2)__cdecl表示调用端负责被调用端引数的压栈和出栈。参数可变的函数采用的是这种调用形式。 
为什么这种函数要采用不同于前面的调用形式呢?那是因为__stdcall调用形式对它没有作用,被调用端根本就无法知道调用端的引数个数,它怎么可能正确工作?所以这种调用方式是必须的,不过由于参数参数可变的函数本身不多,所以用的地方比较少。 
对于这两种方式,你可以编制一些简单的程序,然后反汇编,在汇编代码下面你就可以看到实际的区别,很好理解的! 
重载函数有很多匹配(match)规则调用。参数为“…”的函数是匹配最低的,这一点在Andrei Alexandrescu的惊才绝艳之作《Modern C++ Design》中就有用到,参看Page34-35,2.7“编译期间侦测可转换性和继承性”。 
后记: 
C语言的细节肯定不会只有这么多,但是这几个出现的比较频繁,而且在C语言中也是很重要的几个语言特征。如果把这几个细节彻底弄清楚了,C语言本身的神秘就不会太多了。 
C 语言本身就像一把异常锋利的剪刀,你可以用它做出非常精致优雅的艺术品,也可以剪出一些乱七八糟的废纸片。能够将一件武器用到出神入化那是需要时间的,需要多长时间?不多,请你拿出一万个小时来,英国Exter大学心理学教授麦克.侯威专门研究神童和天才,他的结论很有意思:“一般人以为天才是自然而生、流畅而不受阻的闪亮才华,其实,天才也必须耗费至少十年光阴来学习他们的特殊技能,绝无例外。要成为专家,需要拥有顽固的个性和坚持的能力……每一行的专业人士,都投注大量心血,培养自己的专业才能。”【注16】 
注16:台湾女作家、电视节目主持人吴淡如《拿出一万个小时来》。《读者》2003.1期。“不用太努力,只要持续下去。想拥有一辈子的专长或兴趣,就像一个人跑马拉松赛一样,最重要的是跑完,而不是前头跑得有多快。” 
推荐两本书: 
K&R的《The C Programming language》,Second Edition。 
Andrew Konig的《C陷阱与缺陷》。本文从中引用了好几个例子,一本高段程序员的经验之谈。 
但是对纯粹的初学者不太合适,如果你有一点程序设计的基础知识,花一个月的时间好好看看这两本书,C语言本身就不用再花更多的精力了

转自:http://hi.baidu.com/qq421681423/blog/item/0c7292ec533bd0d62e2e2105.html







你可能感兴趣的:(【C&C++】,c语言)