最近自己一直在忙于找工作,在面试的时候发现专业基础知识不是很牢固,很多细节问题只是知道但说不出个所以然,因此下定决心“重学”一些基础课程,以温故而知新!偶然发现一本觉得很不错的书《Dissection C》(不适合初学者),深入浅出地分析和讲解了C语言中一些需要注意的知识点,书很薄,经典的东西通常都是简练的,不说废话。很多计算机科班出身的人都会认为自己的C语言学的不错,但实际上那只是自己的感觉,要真正精通一门技术,非十年修炼难以。通过对本书的阅读和总结,希望自己可以从这本书中得到提升。(成功者总是站在巨人的肩膀上 :),感谢本书的作者! )
PS: 为了便于总结,相关的知识点放在一起进行归纳。
[1] C语言有多少个关键字?sizeof怎么用?它是函数吗?
[2] 什么是定义?什么是声明?它们有何区别?
[3] 关键字角色的逐个分析
(1) auto
(2) register
(3) static
(4) 基本数据类型
(5) sizeof
[1] C语言有多少个关键字?sizeof怎么用?它是函数吗?
很多人不知道C语言有多少个关键字,大多数人会认为sizeof是函数,因为它后面跟着一对括号,当然在某种情况下也可以不跟。
基础不牢的人会惊奇地发现C语言标准定义的32个关键字,sizeof不是函数竟然是关键字。
C 语言标准定义的 32 个 关键字
|
|
关键字 |
意义 |
auto |
声明自动变量,缺省时编译器一般默认为auto |
int |
整形变量 |
double |
双精度变量 |
long |
长整型变量 |
char |
字符型变量 |
float |
浮点型变量 |
short |
短整型变量 |
signed |
有符号类型变量 |
unsigned |
无符号类型变量 |
struct |
结构体变量 |
union |
联合数据类型 |
enum |
枚举类型 |
static |
静态变量 |
switch |
用于开关语句 |
case |
开关语句分支 |
default |
开关语句中的“其他”分支 |
break |
跳出当前循环 |
register |
寄存器变量 |
const |
只读变量 |
volatile |
说明变量在程序执行中可被隐含地改变 |
typedef |
用以给数据类型取别名(当然还有其他作用) |
extern |
变量是在其他文件中声明(也可以看做是引用变量) |
return |
子程序返回语句(可以带参数,也可不带参数) |
void |
声明函数无返回值或无参数,声明空类型指针 |
continue |
结束当前循环,开始下一轮循环 |
do |
循环语句的循环体 |
while |
循环语句的循环条件 |
if |
条件语句 |
else |
条件语句否定分支(与if 连用) |
for |
一种循环语句(可意会不可言传) |
goto |
无条件跳转语句 |
sizeof |
计算对象所占内存空间大小 |
[2] 什么是定义?什么是声明?它们有何区别?
int i;// 定义
extern int i;// 声明
定义:就是(编译器)创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。
注意:
(1) 这个名字一旦和这块内存匹配起来,它们就同生共死,终生不离不弃。
(2) 这块内存的位置也不能被改变。
(3) 一个变量或对象在一定的区域内(比如函数内,全局等)只能被定义一次,如果定义多次,编译器会提示你重复定义同一个变量或对象。
声明,有两重含义如下:
第一重含义:告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到的变量或对象是在别的地方定义的。声明可以出现多次。
第二重含义:告诉编译器,这个名字我先预定了,别的地方再也不能用它来作为变量名或对象名。这种声明最典型的例子就是函数参数的声明。
注意:
定义和声明最主要的区别是:定义创建了对象并为这个对象分配了内存,声明没有分配内存。
[3] 关键字“角色”的逐个分析
(1) auto
你就当它不存在吧,编译器在默认的缺省情况下,所有变量都是auto的。
(2) register
这个关键字请求编译器尽可能地将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。注意是:尽可能,不是绝对。因为,一个CPU的寄存器也就那么几个或几十个,你要是定义了很多register变量,编译器不可能全部把这些变量放入寄存器中。
皇帝身边的小太监——寄存器
小太监是皇帝的中转站。CPU看做皇帝,内存看做大臣,寄存器看做小太监。(这里不考虑CPU的高速缓存区)。
数据从内存里拿出来放到寄存器,然后CPU再从寄存器里读取数据来处理,处理后同样把数据通过寄存器存放到内存里,CPU不直接和内存打交道。
为啥要这么麻烦呢?速度!就是因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。近水楼台先得月嘛,它离CPU很近,CPU一伸手就拿到数据了,比往那么大的一块内存里去寻找某个地址上的数据是不是快多了?那有人问既然它速度那么快,那我们的内存硬盘都改成寄存器得了呗,问题是,你真有钱!
使用register修饰符的注意点:
虽然寄存器的速度非常快,但是使用register修饰符也有些限制的:
(1) register变量必须是能被CPU寄存器所接受的类型。意味着register变量必须是一个单个的值,并且其长度应<=整形类型的长度。
(2) register变量可能不存放在内存中,所以不能用取地址运算符“&”来获取register变量的地址。
(3) static
这个关键字在C语言里主要有两个作用,C++对它进行了扩展。
第一个作用:修饰变量。
变量可以分为局部和全局变量,但它们都存在内存的静态区。
静态全局变量:
作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法使用它。准确地说,作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它,要想使用就需要在前面再加extern语句。如果想方便些,可以直接在文件顶端定义,就不在需要使用extern语句了。
静态局部变量:
在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍能用到这个值。
例子:
#include <cstdio> static int j;// 静态全局变量 void fun1() { static int i=0;// 静态局部变量 ++i; } void fun2() { j=0; ++j; } int main() { for (int k=0; k<10; ++k) { fun1(); fun2(); } return 0; } /* output: i=10 j=1 */
第二个作用:修饰函数。
函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称为内部函数)。
使用内部函数的好处是:不同人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
关键字static有着不寻常的历史:
起初,在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后,static在C中有了第二种含义,用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用static关键字来表示这第二种含义。
PS: C++里对static赋予了第三种含义。
(4) 基本数据类型
short, int, long, char, float, double
在32位的系统上,char是1B,short是2B,int是4B=long是4B=float是4B,double是8B。
注意:不同的平台会有所不同,具体值可以通过使用sizeof关键字测试一下。
变量的命名规则
【规则1-1】 命名应当直观且可以拼读,可望文知意,便于记忆和阅读。
标识符最好采用英文单词或其组合,不允许使用拼音。程序中的英文单词一般不要太复杂,用词应当准确。
【规则1-2】命名的长度应当符合“min-length && max-information”的原则。
C是一种简洁的语言,命名也应该是简洁的。
【规则1-3】当标识由多个词组成时,每个词的第一个字母大写,其余全部小写。
比如:int CurrentVal;
【规则1-4】尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。比如驱动开发时为管道命名,非编号名字反而不好。
初学者总是喜欢用带编号的变量名或函数名,这样子看上去很简单方便,但其实是一颗颗定时炸弹。
【规则1-5】在对多个文件之间共同使用的全局变量或函数要加范围限定符(建议使用模块名(缩写)作为范围限定符)。
比如:GUI_,等。
【规则1-6】标识符名分为两部分:规范标识符前缀(后缀)+含义标识。非全局变量可以不使用范围限定符前缀。
【规则1-7】作用域前缀命名规则。
略
【规则1-8】数据类型前缀命名规则。
比如:int iVariable; void * vpVariable;
【规则1-9】含义标识命名规则,变量命名使用名词性词组,函数命名使用动词性词组。
比如:
变量名 DataGotFromSD// 从SD中取得的数据
函数名 GetDataFromSD// 取数据
【规则1-10】程序中不得出现仅靠大小写区分的相似的标识符。
比如:int x, X;// 变量x与X容易混淆
【规则1-11】一个函数名禁止被用于其它之处。
比如:函数名和变量名相同。
【规则1-12】所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。
比如:
const int MAX_LENGTH=100;// 这不是常量,而是一个只读变量
#define FILE_PATH "/usr/tmp"
【规则1-13】考虑到习惯性问题,局部变量中可采用通用的命名方式,仅限于n、i、j等作为循环变量使用。
一般来说,习惯上用n, m, i, j, k等表示int类型的变量;c, ch等表示字符类型变量;a等表示数组;p等表示指针。当然这仅仅是一般习惯。
【规则1-14】定义变量的同时,千万别忘了初始化。定义变量时,编译器并不一定清空了这块内存,它的值可能是无效的数据。
【规则1-15】不同类型数据之间的运算要注意精度扩展问题,一般低精度数据将向高精度数据扩展。
(5) sizeof
最冤枉的关键字,常年被人误认为函数。
sizeof是关键字不是函数,其实就算不知道它是否为32个关键字之一时,我们也可以借助编译器确定它的身份。
int i=0;
A) sizeof(int); B) sizeof(i); C) sizeof int; D) sizeof i;
在32位系统下,毫无疑问,A和B都正确。通过VC6、VS2008或任意一编译器调试,我们发现D也正确。sizeof后面没有括号也可以,说明sizeof不是函数。
再想想C为什么编译器提示出错?我们可以在int前加unsigned、const等关键字但不能加sizeof关键字。
记住:sizeof在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略。为了便于记忆,我们在使用sizeof关键字的时候,还是乖乖地写上括号,继续让sizeof装作一个“函数”吧。
#include <cstdio> void fun(int b[100]) { printf("sizeof(b)=%d/n",sizeof(b)); // 4 } int main() { int a[100]={0}; int *p=NULL; printf("sizeof(p)=%d/n",sizeof(p)); // 4 printf("sizeof(*p)=%d/n",sizeof(*p)); // 4 printf("sizeof(a)=%d/n",sizeof(a)); // 400 printf("sizeof(a[99])=%d/n",sizeof(a[99])); // 4 printf("sizeof(a[100])=%d/n",sizeof(a[100])); // 4 printf("sizeof(&a)=%d/n",sizeof(&a)); // 4 printf("sizeof(&a[0])=%d/n",sizeof(&a[0])); // 4 fun(a); return 0; }
2010-10-6 wcdj