深入理解C语言关键字用法


本篇文章总结了各个关键字的用法、特殊用法。对C语言的关键字进行深入的理解。


一、C语言标准定义的关键字(共32个)及其意义

关键字 意义
auto 声明自动变量,缺省时编译器一般默认为auto
int 声明整型变量
long 声明长整型变量
double 声明双精度变量
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 条件语句的否定分支
for 一种循环语句‘
goto 无条件跳转
sizeof 计算对象所占内存空间大小’

二、用法

下面就一一讲解这些关键字,但讲解之前要先明确两个概念:
什么是定义?什么是声明?
举个例子:

a)int i;
b) extern int i;

哪个是定义,哪个是声明?或者都是定义还是都是声明?这个概念十分重要。

      什么是定义:定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它去一个名字,这个人名字就是变量名。但注意,这个名字一旦和这块内存匹配起来,他们就共同生死,终生不离不弃。并且这块内存的位置也不能被改变。一个变量或对象在一定的区域内只能被定义一次,如果多次定义,编译器会提示你重复定义。
      什么是声明:有两重含义,如下:
      第一重含义:告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到变量或对象是在别的地方定义的。声明可以出现多次。
      第二重含义:告诉编译器,我这个名字我先预定了,别的地方再也不能用它来作为变量名或对象名。比如你在图书馆自习室的某个座位上放了一本书,表明这个座位已经有人预订,别人再也不允许使用这个座位。其实这个时候你本人并没有坐在这个座位上。这种声明最典型的例子就是函数参数的声明,例如:
void fun(int i, char c);
      好,这样一解释,我们可以很清楚的判断:A)是定义;B)是声明。
那他们的区别也很清晰了。记住,定义声明最重要的区别:定义创建了对象并为这个
      对象分配了内存,声明没有分配内存。

2.1auto

auto:编译器在默认的缺省情况下,所有变量都是 auto 的。

2.2 register

      register:这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对。你想想,一个 CPU 的寄存器也就那么几个或几十个,你要是定义了很多很多 register 变量,它累死也可能不能全部把这些变量放入寄存器吧,轮也可能轮不到你。
      不知道什么是寄存器?那见过太监没有?没有?其实我也没有。没见过不要紧,见过就麻烦大了。_,大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。这个小太监只是个中转站,并无别的功能。
      好,那我们再联想到我们的 CPU。CPU 不就是我们的皇帝同志么?大臣就相当于我们的内存,数据从他这拿出来。那小太监就是我们的寄存器了(这里先不考虑 CPU 的高速缓存区)。数据从内存里拿出来先放到寄存器,然后 CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。这里要说明的一点是:小太监是主动的从大臣手里接过奏章,然后主动的交给皇帝同志,但寄存器没这么自觉, 它从不主动干什么事。一个皇帝可能有好些小太监,那么一个 CPU 也可以有很多寄存器, 不同型号的 CPU 拥有寄存器的数量不一样。
      为啥要这么麻烦啊?速度!就是因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。进水楼台先得月嘛,它离 CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据是不是快多了?那有人问既然它速度那么快,那我们的内存硬盘都改成寄存器得了呗。我要说的是:你真有钱!
使用register 修饰符的注意点
虽然寄存器的速度非常快,但是使用 register 修饰符也有些限制的:register 变量必须是能被 CPU 寄存器所接受的类型。意味着 register 变量必须是一个单个的值,并且其长度应小于或等于整型的长度。 而且 register 变量可能不存放在内存中,所以不能用取址运算符“&” 来获取 register 变量的地址。

2.3static

static 在C语言里主要有两个作用,C++对他进行了扩展
      第一个作用:修饰变量。变量又分为局部和全局变量,但它们都存在内存的静态区。静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用 extern 声明也没法
      使用他。准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它。想要使用就得在前面再加 extern ***。恶心吧?要想不恶心,很简单,直接在文件顶端定义不就得了。
      静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。

第二个作用:修饰函数
      函数前加 static 使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
      关键字 static 有着不寻常的历史。起初,在 C 中引入关键字 static 是为了表示退出一个块后仍然存在的局部变量。随后,static 在 C 中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用 static 关键字来表示这第二种含义。

2.4基本数据类型

深入理解C语言关键字用法_第1张图片short、int、long、char、float、double 这六个关键字代表 C 语言里的六种基本数据类型。
怎么去理解它们呢? 举个例子:见过藕煤球的那个东西吧?(没见过?煤球总见过吧)。那个东西叫藕煤器,拿着它在和好的煤堆里这么一咔,一个煤球出来了。半径 12cm,12 个孔。不同型号的藕煤器咔出来的煤球大小不一样,孔数也不一样。这个藕煤器其实就是个模子。
现在我们联想一下,short、int、long、char、float、double 这六个东东是不是很像不同类型的藕煤器啊?拿着它们在内存上咔咔咔,不同大小的内存就分配好了,当然别忘了给 它们取个好听的名字。在 32 位的系统上 short 咔出来的内存大小是 2 个byte;int 咔出来的内存大小是4 个byte;long 咔出来的内存大小是4 个byte;float 咔出来的内存大小是4 个byte;
double 咔出来的内存大小是 8 个byte;char 咔出来的内存大小是 1 个 byte。(注意这里指一般情况,可能不同的平台还会有所不同,具体平台可以用 sizeof 关键字测试一下)
很简单吧?咔咔咔很爽吧?是很简单,也确实很爽,但问题就是你咔出来这么多内存块, 你总不能给他取名字叫做x1,x2,x3,x4,x5…或者长江 1 号,长江 2 号…吧。它们长得这么像(不是你家的老大,老二,老三…),过一阵子你就会忘了到底哪个名字和哪个内存块匹配了(到底谁嫁给谁了啊?_)。所以呢,给他们取一个好的名字绝对重要。下面我们就来研究研究取什么样的名字好。

2.5sizeof

sizeof 是关键字不是函数,其实就算不知道它是否为 32 个关键字之一时,我们也可以借助编译器确定它的身份。看下面的例子:
int i=0;
A),sizeof(int); B),sizeof(i); C),sizeof int; D),sizeof i; 毫无疑问,32 位系统下 A),B)的值为 4。那C)的呢?D)的呢?
在 32 位系统下,通过 Visual C++6.0 或任意一编译器调试,我们发现 D)的结果也为 4。咦?sizeof 后面的括号呢?没有括号居然也行,那想想,函数名后面没有括号行吗?由此轻易得出 sizeof 绝非函数。
好,再看 C)。编译器怎么怎么提示出错呢?不是说 sizeof 是个关键字,其后面的括号可以没有么?那你想想 sizeof int 表示什么啊?int 前面加一个关键字?类型扩展?明显不正确,我们可以在 int 前加 unsigned,const 等关键字但不能加 sizeof。好,记住:sizeof 在计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略。一般情况下, 咱也别偷这个懒,乖乖的写上括号,继续装作一个“函数”,做一个“披着函数皮的关键字”。做我的关键字,让人家认为是函数去吧。
sizeof(int)*p 表示什么意思?

sizeof(int)*p 表示什么意思?
留几个问题(讲解指针与数组时会详细讲解),32 位系统下:
int *p = NULL;
sizeof§的值是多少?
sizeof(*p)呢?

int a[100];
sizeof (a) 的值是多少?
sizeof(a[100])呢?//请尤其注意本例。
sizeof(&a)呢?
sizeof(&a[0])呢?

int b[100];
void fun(int b[100])
{

sizeof(b);// sizeof (b) 的值是多少?

}

2.6 singed 、unsigned 关键字

我们知道计算机底层只认识 0、1.任何数据到了底层都会变计算转换成 0、1.那负数怎么存储呢?肯定这个“-”号是无法存入内存的,怎么办?很好办,做个标记。把基本数据类型的最高位腾出来,用来存符号,同时约定如下:最高位如果是 1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是 0,表明这个数是正数, 其值为除最高位以外的剩余位的值。

这样的话,一个 32 位的 signed int 类型整数其值表示法范围为:- 231 ~ 231 -1;8 位的

char 类型数其值表示的范围为- 27 ~ 27 -1。一个 32 位的 unsigned int 类型整数其值表示法

范围为:0~

232 -1;8 位的 char 类型数其值表示的范围为 0~ 28 -1。同样我们的 signed 关

键字也很宽恒大量,你也可以完全当它不存在,编译器缺省默认情况下数据为 signed 类型的。
上面的解释很容易理解,下面就考虑一下这个问题:
int main()
{
char a[1000]; int i;
for(i=0; i<1000; i++)
{
a[i] = -1-i;
}
printf("%d",strlen(a)); return 0;

}
此题看上去真的很简单,但是却鲜有人答对。答案是 255。别惊讶,我们先分析分析。
for 循环内,当 i 的值为 0 时,a[0]的值为-1。关键就是-1 在内存里面如何存储。
我们知道在计算机系统中,数值一律用补码来表示(存储)。主要原因是使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反,然后整个数加 1。
按照负数补码的规则,可以知道-1 的补码为 0xff,-2 的补码为 0xfe……当 i 的值为 127 时,a[127]的值为-128,而-128 是char 类型数据能表示的最小的负数。当 i 继续增加,a[128] 的值肯定不能是-129。因为这时候发生了溢出,-129 需要 9 位才能存储下来,而 char 类型数据只有 8 位,所以最高位被丢弃。剩下的 8 位是原来 9 位补码的低 8 位的值,即 0x7f。当 i 继续增加到 255 的时候,-256 的补码的低 8 位为 0。然后当i 增加到 256 时,-257 的补码的低 8 位全为 1,即低八位的补码为 0xff,如此又开始一轮新的循环……
按照上面的分析,a[0]到 a[254]里面的值都不为 0,而 a[255]的值为 0。strlen 函数是计算字符串长度的,并不包含字符串最后的‘\0’。而判断一个字符串是否结束的标志就是看是否遇到‘\0’。如果遇到‘\0’,则认为本字符串结束。
分析到这里,strlen(a)的值为 255 应该完全能理解了。这个问题的关键就是要明白 char 类型默认情况下是有符号的,其表示的值的范围为[-128,127],超出这个范围的值会产生溢出。另外还要清楚的就是负数的补码怎么表示。弄明白了这两点,这个问题其实就很简单了。

留三个问题:

1),按照我们上面的解释,那-0 和+0 在内存里面分别怎么存储?
2),int i = -20;
unsigned j = 10;
i+j 的值为多少?为什么?
3), 下面的代码有什么问题?
unsigned i ;
for (i=9;i>=0;i–)
{
printf("%u\n",i);
}

2.7 if 、else

•float 变量与“零值”进行比较

float 变量与“零值”进行比较的 if 语句怎么写?
float fTestVal = 0.0;
A), if(fTestVal == 0.0); if(fTestVal != 0.0);
B), if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON 为定义好的精度。
哪一组或是那些组正确呢?我们来分析分析:
float 和 double 类型的数据都是有精度限制的,这样直接拿来与 0.0 比,能正确吗?明显

不能,看例子:

 的值四舍五入精确到小数点后 10 位为:3.1415926536,你拿它减去

0.00000000001 然后再四舍五入得到的结果是多少?你能说前后两个值一样吗?
EPSINON 为定义好的精度,如果一个数落在[0.0-EPSINON,0.0+EPSINON] 这个闭区间内,我们认为在某个精度内它的值与零值相等;否则不相等。扩展一下,把 0.0 替换为你想比较的任何一个浮点数,那我们就可以比较任意两个浮点数的大小了,当然是在某个精度内。
同样的也不要在很大的浮点数和很小的浮点数之间进行运算,比如:
10000000000.00 + 0.00000000001
这样计算后的结果可能会让你大吃一惊。

2.8 switch case

case 关键字后面的值有什么要求吗?
case 后面只能是整型或字符型的常量或常量表达式

你可能感兴趣的:(C语言知识与常见问题,c语言,开发语言,后端)