C专家编程整理

       

       符号就是程序中的一个基本信息单元。而组成符号的字符序列就不同,同一组字符序列在某个上下文环境中属于一个符号,而在另一个上下文中可能完全属于另一个符号。

        编译器中负责将程序分解为一个一个符号的部分,一般称为“词法分析器”。

        =不同于==,可能会因为漏打多打而出现错误。在写==习惯性写成如:if (3 == a),会减少出错。

       词法分析中的“贪心法”,如果(编译器的)输入流截止至某个字符之前都己经被分解为一个个符号,那么下一个符号将包括从该字符之后可能组成一个符号的最长字符串。

       我们一旦知道如何声明一个变量,也就自然知道如何对一个常数进行类型转换,将其转型为该变量的类型。对于函数指针时,一般使用typedef较好。如typedef void(*pFun)(int,int);   pFun pfun;

       我们在函数声明时忽略了函数的返回值类型,但是此时对编译器而言会隐含地将函数返回类型视作int类型。对于main函数,默认情况下是int型,当然也可以声明其他类型,只是一般情况下程序不能够正常退出。

       switch中的case后面必须是常量,而且是编译时期就可以计算出的常量,不能就会编译错误。

       悬挂的else所引发的问题,else总是与同一对括号内最后的未匹配的if相结合。如 if () if () ; else ;  与       if() {if ();} else;   是不同的。

       数组初始列表中,出现一个多余的逗号,也是正确的,int dyas[] = {12,312,2,};说是为了方便自动化生成。

       C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。对于一个数组,我们只能够做做两件事:确定该数组的大小,以及获得指向该数组下标为0元素的指针。

       两个指针间的操作,相同类型的指针可以进行相减大小比较操作,其他操作都是非法的,即使是两个相同类型的操作。

       如果我们在应该出现指针的,去采用了数组名来替换,那么数组名就被当作指向该数组下标为0的元素的指针。

       数组名除了被用作运算符sizeof的参数这一情形,没有改变。其他所有情况中数组名代表指向数组中下标为0元素的指针。

       对于一个数组a[N],a + i,i + a是相同的所以a[i]i[a]是相同的,不会编译错误,也可以运行起来。这种写法也是正确的"0123456789"[5]

       我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。

       编译器除了一个重要的例外情况,在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。


       C标准明确允许这种用法:数组中实际不存在的“溢界”元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较。当然如果要引用该元素,那就是非法的了。

       C语言中只有四个运算符(&&  || ?:  ,)存在规定的求值顺序。运算符&&和运算符||首先对其左边的操作数求值,只在需要时才对右侧操作数求值。运算符?:有三个操作数:在a?b:c中,操作数a首先被求值,根据a的值再求操作数b或c的值。而逗号操作符,首先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值。分隔函数参数的逗号并非逗号运算符。如f(x,y)中的求值顺序是未定义的,而在函数g((x,y))中去是确定的先x后y的顺序。

 

       C语言中的一个重要思想就是分别编译(Separate Compilation),即若干个源程序可以在不同的时候单独进行编译,然后恰当的时候整合到一起。连接器一般是与C编译器分离的,它不可能了解C语言的诸多细节。

       典型的连接器把由编译器或汇编器生成的若干个目标模块,整合成一个被称为载入模块或可执行文件的实体,该实体能够被操作系统直接执行。连接器通常把目标模块看成是由一组外部对象(external object)组成的。每个外部对象代表着机器内在中的某个部分,并通过一个外部名称来识别。困此,程序中的每个函数和每个外部变量,如果没有被声明为static,就都是一个外部对象。某些C编译器会对静态函数和静态变量的名称做一定改变,将它们也作为外部对象。使用static的确可以解决问题。

       连接器的一个重要工作就是解决命名冲突。处理命名冲突的最好办法就是干脆完全禁止。

       连接器的输入是一组目标模块和库文件。连接器的输出是一个载入模块。连接器的读入目标模块和库文件,同时生成载入模块。对每个目标模块中的每个外部对象,连接器都要检查载入模块,看是否己有同名的外部对象。如果没有,连接器就将该外部对象添加到载入模块中;如果有,连接器就要开始解决命名冲突。

       除了外部对象之外,目标模块中还可能包括了对其他模块中的外部对象的引用。


       extern int a;包括了extern关键字,这就显式地说明了a的存储空间是在程序的其他地方分配的,从连接器的角度来看,上述声明是一个对外部变量a的引用。

       严格的规则是,每个外部变量只能够定义一次。

       两个具有相同名称的外部对象实际上是同一个对象。头文件中的变量,应该尽量声明成static,否则,可能会因为多个源文件包含,而引起重复定义出错,作用域限制在一个源文件内,对于其他源文件,是不可见的。static修饰符是一个能够减少此类命名冲突的有用工具。

       static 修饰符不仅适用于变量,也适用于函数。

       如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整数。如果一个函数没有float、short、或者char类型的参数,在函数声明中可以以省略参数类型的说明。这样做依赖于调用能够正确提供数目正确且类型恰当的实参。这里,“恰当”并不意味着“等同”:float类型的参数会自动转换为double类型,short或char类型的参数会自动转换为int型,否则也会出现编译错误。

       外部变量需要注意的是,不仅需要extern声明,还要注意类型一致,否则将针带来不必要的隐患,一般情况是,会出现编译错误。他们会共同使用同一个存储空间。如一个文件中是char filename[] = “hello the world”,另一个文件中应该是extern char filename[],这个filenameg不可用sizeof来求,因为sizeof是在编译时期就必须确定下来,写成extern char *filename,是存在问题的。

       C语言的规则是,如果未声明的标识符后跟一个开括号,那么它将被视为一个返回整形的函数。


       在严格意义上的编译过程开始之前,C语言预处理器首先对程序代码作了必要的转换处理。使用预处理器的作用在于,我们可以方便的将在程序中出现的所在实例统统加以修改,而且C语言实现在函数调用时都会带来重大的系统开销

       宏提供了一种对组成C程序的字符进行变换的方式,而并不作用程序中的对象。#define f  (x) ((x) – 1) 意思是f代表(x)  ((x)-1),这一规则只对宏定义有效,而对宏调用无效。

       #define fun(a) #a

       #ifndef fun               //这里的fun实际上是指fun(a)这个宏

       #define fun(a) #a#a

       #endif /*!fun*/

       双##号在宏定义的作用是,将两个子串(token)联接起来,从而形成一个新的子串。

       #define paster(n) printf(“token”#n”=%d”,token##n)

       int toaken9 = 9;

       paster(9)

       最终扩展为 printf(“token””9””= %d”,token9);

 

       在定义宏的过程中,非常容易出错。我们最好在宏定义中把每个参数都用括号括起来,同样,整个表达式也括起来。但是这样仍不可避免引起多处求值。如:#define max((a)>(b)?(a)*(b)),调用max(2,i++),这里会引起i自增两次,可能这不是调用者所想要的。宏定义中,也尽量不要出现if或者{},出现if,可能会引起else的配对问题;出现{},可能会引起多余的分号,else无法配对问题。使用宏的另一个危险是,宏能产生非常庞大的表达式,占用的空间远远超过了编程者所期望的空间。

       宏定义可以带来的一个好处是,移值性。typedef 命一个别名,可能是更好的选择。#define T1 char *            typedefchar * T2;             定义T1 p1,p2;  与 T2 p1,p2。是不一样的,在这个情况下,typedef会更加方便一些。

       __FILE__和__LINE__是内建于C语言预处理器中的宏,它们会被扩展为所在文件的文件名和所外代码行的行号。

 

       函数声明中略去参数类型的说明,这在ANSI C标准中也是合法的。因为这样的声明并没有对参数类型做出任何说明,就意味着如果在函数调用时传入了错误类型的参数,函数调用就会不声不响的失败。

       ANSC C标准所能保证的只是,C实现必须能够区别出前6个字符不同的外部名称。而且,这个定义中并没有区分大写字母与其对应的小写字母。

       short型、int型和long型,C语言中的字符行为方式与小整数相似。C语言的定义中对各种不同类型整数的相同长度作了一些规定。

       1)3种类型的整数其长度是非递减的。

       2)一个普通(int类型)整数足够大以容纳任何数组下标。

       3)字符长度是由硬件决定。


       将字符声明为无符号类型(unsigned char)。这样,无论是什么编译器,在将该字符转换为整数时都只需将多余的位填充为0即可。

       如果被移位的对象是无符号数,那么突出的位将被0填充。如果被移位的对象是有符号数,那么C语言实现既可以用0填充突空出的位,也可以用符号位的副本填充空出的位。

       移位的速度虽然相比操作2的速度快很多,但是这种情况仍然是不可用除于2来代替的,有些负数的移位得到的就可能不是你想要的结果。如-9>>1和-9/2的结果是不同的。

       在所有的C程序中,误用null指针的效果都是未定义的,如果是用于写的操作,必定会出错。读的操作因机器而定。


       getchar返回的是int型,putchar的参数是int型,一般情况下,我都会用char型传参,或者接受返回值,可能会引起平台间的差异,而出现错误。

       fp =fopen(file,”r+”);一个输入操作不能随后直接紧跟一个输出操作,反之亦然。如果要同时进行输入和输出操作,必须在其中插入fseek函数的调用。fseek可以更改文件的状态。

       实际上所有的C语言实现中都包括有signal库函数,作为捕获异步事件的一种方式。signal(signaltype,handlerfunction)。需要注意,信号甚至可能出现在某些复杂库函数(如malloc)的执行过程中。因此,从安全的角度,信号的处理函数不应该调用上述类型的库函数。

       假设malloc函数的执行过程被一个信号中断,malloc函数用来跟踪内存的数据结构很可能只有部分更新。如果signal函数再调用malloc函数,可能会造成灾难性的后果。较好的做法是,让signal处理函数尽可能的简单。


       写程序过程中的几个建议

       不要凭自己的意识去判定一个表达式,严格的依据运算符的优先级进行判定。

       直截了当地表明自己的意图。进行比较可以把常量放在表达式的左侧,避免一些误操作。

       考查最简单的特例。如空值。

       使用不对称边界。便于计算。

       注意潜伏在暗处的bug。一些由机器提供的特定函数,引起跨平台问题。程序的生命期往往要长于它运行其上的机器的生命期。

       防制性编程。


       格式字符串中的每个格式项都由一个%符号打头,后面接一个称为格式码的字符,格式码指明了格式转换的类型。格式码不一定要紧跟在%符号之后,它们中间可能夹一些可选的字符,这些可选字符以各种方式修改转换。

       %o格式项请求输出8进制整数,而x%和%X则请求输出16进制整数,都会当作无符号型整数进行打印。%x与%X的区别在于%x格式项中用小写字母a,b,c,d,e,f来表示,而%X格式项中用大写字母A,B,C,D,E,F。

       %s格式项用于打印字符串,对应的参数是一个字符指针,地址中的内容直到出现一个空字符(‘\0’)才终止。printf(s)与printf(“%s”,s)这两个间是存在区别的,第1个将字符串s中的任何%字符视为一个格式串的标志,因而其后的字符会视为格式码。

       %g格式项用于打印那些不需要按列对齐的浮点数特别有用。它在打印出对应的数值(必须为浮点型或双精度类型)时,会去掉数值尾缀的零,保留六位有效数字。

       %e打印浮点数时,要求一律显式地使用指数形式,%e格式打印出小数点后6位有效数字,而并非%g格式项打出的数是总是共6位有效数字。

       %f格式项则恰好相反,强制禁止使用指数形式来表示浮点数,%f格式项的要求与%e的格式项相同,即小数点后6位有效数字。

       %E和%G格式项与它们对应的%e和%g格式项在行为方式上基本相同,队了用大写的E代替小写e来表示指数形式。

       %%格式项用于打印出一个%字符。

       如果一个short整数作为任何一个整数(也包括printf整数)的参数出现,它会自动扩展为一个正常长度的整数。如果某个参数是long型整数。我们可以在格式码之前紧挨着插入一个长度修饰符l,创造出%ld,%lo,%lx,%lu作为新的格式码。

       宽度修饰符出现在%符号和格式码的中间,其作用是指定它所修饰的格式项所应打印的字符数。如果待打印的数值不能填满位置,它的左侧就会被补上空格字符以使这个数值的宽度满足要求。如果等打印字符的数值太大而超过了给定的宽度。输出域就会适当地调整以容纳该数值。宽度修饰符绝对不会截断一个输出域。

       宽度修饰符对所有格式码都有效,甚至%%也不例外。

       printf(“%8%”);将会打印7个空格,再打印一个百分号。

       精度修饰符包括一个小数点,和小数点后面的一串数字。精度修饰符出现在%符号和宽度修饰符之后,格式码与长度修饰码之前。

       对于整数格式项%d、%o、%x和%u,精度修饰符指定了打印数字的最少位数。如果等打印的数值并不需要这么多位数的数字来表示,就会在它的前面补上0。

       对于%e、%E和%f格式项,精度修饰符指定了小数点后应该出现的数字位数。除非标志另有说明。

       对于%g和%G格式项,精度修饰符指定了打印数值中的有效数字位数。

       对于%s格式项,精度修饰符指定了将要从相应的字符串中打印的字符数,如果字符串中的字符数目少于精度修饰符中的数目,输出的字符数,就会少于精度修饰符中的数目。

       对于%c和%%格式项,精度修饰符将被忽略。

       在显示宽度大于被显示位数时,数据尾部都以显示区的右端对齐,左端则被填充空白字符。标志字符 - 的作用是,要求显示方式改为左对齐,在右端填充空白字符。因此,当域域宽修饰符存在时,标志字符 -  才有意义。

       标志字符+的作用是,规定每个待打印的数值在输出时都应该以它的符号(正号或负号)作为第一个字符。

       空白字符作为标志字符时,它的含义是:如果某数是一个非负数,就在它的前面插入一个空白字符。如果我们希望让固定样内的数值向左对齐,而又不想用标志字符+,这一点就特别有用。

       如果我们希望在固定样内按科学计数法打印数值,格式项%e和%+e要比正常的格式有用的多。因为,这时出现的在非负数前面的正号(或者空白)保证了所有输出的小数会对齐。

       给%o格式项加上标志字符#的效果是:当有必要时增加数值输出的精度。这么规定的意义在于,让八进制数值输出的格式与大多数C程序员惯用的方式一至。%#o与0%o的区别在与输出0时,前者是0,后者是00;%#x

       标志字符#对浮点数的格式在与,小数点必须打印出来,即使小数点后面没有数字,也是如此。如果是用于%g或者%G格式项,打印出的数字尾缀0也将不会去掉。

       printf函数允许间接指定域宽和精宽。我们只需要用*替换域宽修饰符或精度修饰符其中之一,或者两者都替换。printf(“%*.*s”,20,3,”nihaotheworld”)。后面的参数列表中将依次出现代表域宽的参数,代表精度的参数以及要打印参数的值。printf(“%*d\n”,n)。在*codeblock中是无效的。

       如果*用于替换宽度修饰符,而与其对应参数的值为负数,那么效果相当于把负号为-号标志字符来处理。

       %p用于以某种形式打印一个指针,具体的实现与特定的C语言有关。可以用%.来替换%0,效果也非常接近。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值