表达式中的数组名可以看作是指针 把数组当作指针,简化了很多东西。我们不再需要一种复杂的机制区分它们,把它们传递到一个函数时不必忍受必须复制所有数组内容的低效率。不过,数组和指针并不是在任何情况下都是等效的,更详细的讨论参见第4章。
register 关键字 这个关键字能给编译器设计者提供线索,就是程序中的哪些变量属于热门(经常被使用),这样就可以把它们存放到寄存器中。这个设计可以说是一一个失误,如果让编译器在使用各个变量时自动处理寄存器的分配工作,显然比一 -经声明就把这类变量在生命期内始终保留在寄存器里要好。使用register关键字,简化了编译器,却把包袱丢给了程序员。
C预处理器实现的3个主要功能:
字符串替换 形式类似“把所有的foo替换为baz",通常用于为常量提供一个符号名。头文件包含 (这是在BCPL中首创的)一般性的声明可以被分离到头文件中,并且可以被许多源文件使用。虽然约定采用“.h”作为头文件的扩展名,但在头文件和包含实现代码的对象库之间在命名上却没有相应的约定,这多少令人不快。通用代码模板的扩展 与函数不同,宏(marco)在连续几个调用中所接收的参数的类型可以不同(宏的实际参数只是按照原样输出)。这个特性的加入比前两个稍晚,而且多少显得有些笨拙。在宏的扩展中,空格会对扩展的结果造成很大的影响。容易混淆的const
关键字const并不能把变量变成常量!在一个符号前加上const 限定符只是表示这个符号不能被赋值。也就是它的值对于这个符号来说是只读的,但它并不能防止通过程序的内部(甚至是外部)的方法来修改这个值。const 最有用之处就是用它来限定函数的形参,这样该函数将不会修改实参指针所指的数据,但其他的函教却可能会修改它。这也许就是C和C++中const最一般的用法。const和*的组合通常只用于在数组形式的参数中模拟传值调用。它声称“我给你一个指向它的指针,但你不能修改它。.这个约定类似于极为常见的void *的用法,尽管在理论上它可以用于任何情形,但通常被限制于把指针从一种类型转换为另一种类型。类似地,还可以取一个const变量的地址对无符号类型的建议
尽量不要在你的代码中使用无符号类型,以免增加不必要的复杂性。尤其是,不要仅仅因为无符号数不存在负值(如年龄、国债)而用它来表示数量。尽量使用像int 那样的有符号类型,这样在涉及升级混合类型的复杂细节时,不必担心边界情况(如一1被翻译为非常大的正数)。只有在使用位段和二进制掩码时,才可以用无符号数。应该在表达式中使用强制类型转 换,使操作数均为有符号数或者无符号数,这样就不必由编译器来选择结果的类型。一个‘L’ 的NUL和两个L’的NULL 牢记下面的话,它有助于回忆指针和ASCII码零的正确术语:
一个‘L的NUL用于结束一个ACSII字符串,两个‘L’的NULL用于表示什么也不指向( 空指针)。当然,如果出现了三个’L’ 的NULL,那就要检查一下有没有拼写错误了。ACSII 字符中零的位模式被称为‘NUL’。 表示哪里也不指向的特殊的指针值则是NULL’ 。 这两个术语不可互换。switch语句
也许switch语句最大的缺点是它不会在每个case标签后面的语句执行完毕后自动中止。一旦执行某个case语句,程序将会依次执行后面所有的case,除非遇到break语句。这称之为 “ fall through ” ,它的意思是:如果case语句后面不加beak, 就依次执行下去,以满足某些特殊情况的要求。但实际上.这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。大部分lint程序在发现 “ fall through " 情况时甚至会发出警告信息。break语句事实上跳出的是最近的那层循环语句或switch语句,sizeof语句 当sizeof 的操作数是个类型名时,两边必须加上括号(这常常使人误以为它是个函数),但操作数如果是变量则不必加括号。
空格- -最后的领域
许多人会告诉你空格在C语言中没有什么意义,只要你喜欢,随便多输入几个或者少输入几个都没有关系。但事实并非如此!这里有几个例子,空格从根本上改变了程序的意思或程序的有效性。“\”字符可用于对一些字符进行“转义”,包括newline (这里指回车键)。被转义的newline在逻辑上把下一行当作当前行的延续,它可用于连接长字符串。如果在“\”和回车键之间不小心留上一两个空格就会出现问题,“\ newline ”和“\newline”就不一样。这个错误很难被发现,因为你是在寻找某种无形的东西(在应该是newline 的地方出现了一个空格,注意newline并不是一个有形的字符,所以“\”后面有没有空格在实际代码中根本看不出来)。newline在典型情况下用于转义连续多行的宏定义。现在,你可以把数组当作第一等级的类型,用赋值语句拷贝整个数组,以传值调用的方式把它传递到函数,或者把它作为函数的返回类型。
关于联合 联合一般被用来节省空间,因为有些数据项是不可能同时出现的,如果同时存储它们,显然颇为浪费。
关于枚举 缺省情况下,整型值从零开始。如果对列表中的某个标识符进行了赋值,那么紧接其后的那个标识符的值就比所赋的值大1,然后类推。枚举具有一个优点: #define 定义的名字一般在编译时被丢弃,而枚举名字则通常一直在调试器中可见,可以在调试代码时使用它们。
typedef int x[10]和#define x int[10]的区别
在typedef和宏文本替换之间存在一个关键性的区别。正确思考这个问题的方法就是把typedef看成是一种彻底的“封装”类型——在声明它之后不能再往里面增加别的东西。它和宏的区别体现在两个方面。首先,可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。 #define peach int unsigned peach i;/*没问题*/ typedef int banana; unsigned banana i;/* 错误,非法 */ 其次,在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示: #define int_ptr int * int_ptr chalk, cheese;经过宏扩展,第二行变为:
int * chalk, cheese;这使得chalk和cheese成为不同的类型,就好象是辣椒酱与细香葱的区别: chalk 是一个指向int的指针,而cheese则是一个int。 相反,下面的代码中:
typedef char * char_ptr; char_ptr Bentley, Rolls_Royce;Bentley和Rolls_Royce 的类型依然相同。虽然前面的类型名变了,但它们的类型相同,都是指向char的指针。
参考https://blog.csdn.net/ywcpig/article/details/52303745
参考https://blog.csdn.net/ywcpig/article/details/52303745
setjmp和longjmp goto语句不能跳出C语言当前的函数(这也是“longjmp"取名的由来,它可以跳得很远,甚至可以跳到其他文件的函数中)。用longjmp只能跳回到曾经到过的地方。在执行setjmp的地方仍留有一个过程活动记录。从这个角度讲,longjmp更像是“从何处来(come from)”而不是“往哪里去(go to)”。longjmp接受一个额外的整型参数并返回它的值,这可以知道是由longjmp转移到这里的还是从上一条语句执行后自然而然来到这里的。需要注意的地方是:保证局部变量在longjmp过程中一直保持它的值的惟一可靠方法是把它声明为volatile (这适用于那些值在setjmp执行和longjmp返回之间会改变的变量)。setjmp/longjmp最大的用途是错误恢复。只要还没有从函数中返回,一旦发现一个不可恢复的错误,可以把控制转移到主输入循环,并从那里重新开始。有些人使用setjmp/ongimp从一串无数的函数调用中立即返回。还有一些人用它们防范潜在的危险代码。在使用setjmp和longjmp的任何源文件中,必须包含头文件<setjmp.h>.这行代码打印出存储一个字符字面值类型的长度。你敢确定它的结果就是字符的长度,也就是1吗?那就运行一下代码试试。你会发现事实上的结果是4(或者是你机器上int 的长度)。字符常量的类型是int,根据提升规则,它由char转换为int。 整型提升就是char、short int和位段类型(无论signed或unsigned)以及枚举类型将被提升为int,前提是 int能够完整地容纳原先的数据,否则将被转换为unsigned int。ANSI C表示如果编译器能够保证运算结果一致,也可以省略类型提升——这通常出现在表达式中存在常量操作数的时候。 表8-1提供了一个常见类型提升的列表。它们可以出现在任何表达式中,并不局限于涉及操作符和混合类型操作数的表达式。 警惕!真正值得注意之处——参数也会被提升! 另一个会发生隐式类型转换的地方就是参数传递。在K&RC中,由于函数的参数也是表达式,所以也会发生类型提升。在ANSIC 中,如果使用了适当的函数原型,类型提升便不会发生,否则也会发生。在被调用函数的内部,提升后的参数被裁减为原先声明的大小。