C语言关键字总结.docx
- 文档编号:19344080
- 上传时间:2023-04-24
- 格式:DOCX
- 页数:15
- 大小:23.10KB
C语言关键字总结.docx
《C语言关键字总结.docx》由会员分享,可在线阅读,更多相关《C语言关键字总结.docx(15页珍藏版)》请在冰豆网上搜索。
C语言关键字总结
变量可以分为:
全局变量、静态全局变量、局部变量和静态局部变量
按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。
按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。
这两者在存储方式上并无不同。
这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。
而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。
把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static全局变量与普通的全局变量有什么区别:
static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:
static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。
局部变量的值不可知。
提示:
A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量
E.函数中必须要使用static变量情况:
比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
一、static关键字
1.static变量
静态变量的类型说明符是static。
静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量。
例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能成为静态外部变量,或称静态全局变量。
2.静态局部变量
静态局部变量属于静态存储方式,它具有以下特点:
(1)静态局部变量在函数内定义它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。
退出该函数后,尽管该变量还继续存在,但不能使用它。
(2)允许对构造类静态局部量赋初值例如数组,若未赋以初值,则由系统自动赋以0值。
(3)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。
而对自动变量不赋初值,则其值是不定的。
根据静态局部变量的特点,可以看出它是一种生存期为整个源程序的量。
虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。
因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。
虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。
3.静态全局变量
全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。
这两者在存储方式上并无不同。
这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。
而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。
由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。
把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
因此static这个说明符在不同的地方所起的作用是不同的。
4.static函数
内部函数和外部函数
当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
1内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static函数类型函数名(函数参数表)
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。
但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
使用内部函数的好处是:
不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
2外部函数
外部函数的定义:
在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern]函数类型函数名(函数参数表)
调用外部函数时,需要对其进行说明:
[extern]函数类型函数名(参数类型表)[,函数名2(参数类型表2)……];
static函数与普通函数作用域不同,仅在本文件。
只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。
对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
static的作用
在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)static的第二个作用是:
隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
为理解这句话,我举例来说明。
我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
chara='A';//globalvariable
voidmsg()
{
printf("Hello\n");
}
下面是main.c的内容
intmain(void)
{
externchara;//externvariablemustbedeclaredbeforeuse
printf("%c",a);
(void)msg();
return0;
}
程序的运行结果是:
AHello
前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。
此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。
例如在a和msg的定义前加上static,main.c就看不到它们了。
利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
(2)static的第二个作用是:
保持变量内容的持久。
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
共有两种变量存储在静态存储区:
全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
#include
intfun(void){
staticintcount=3;//事实上此赋值语句从来没有执行过
returncount--;
}
intcount=1;
intmain(void)
{
printf("global\t\tlocalstatic\n");
for(;count<=3;++count)
printf("%d\t\t%d\n",count,fun());
return0;
}
程序的运行结果是:
globallocalstatic
13
22
31
(3)static的第三个作用是默认初始化为0。
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。
在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。
如果定义成静态的,就省去了一开始置0的操作。
再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。
如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。
#include
inta;
intmain(void)
{
inti;
staticcharstr[10];
printf("integer:
%d;string:
(begin)%s(end)",a,str);
return0;
}
程序的运行结果如下
integer:
0;string:
(begin)(end)
最后对static的三条作用做一句话总结。
首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
二、volatile关键字
一个定义为volatile的变量是说这变量可能会被意想不到地改变,优化器在用到这个变量时都重新读取这个变量的值,而不是使用保存在寄存器里的备份。
简言之,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多线程应用中被几个任务共享的变量应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
1).一个参数既可以是const还可以是volatile吗?
解释为什么。
2).一个指针可以是volatile吗?
解释为什么。
3).下面的函数有什么错误:
intsquare(volatileint*ptr)
{return*ptr**ptr;}
下面是答案:
1).是的。
一个例子是只读的状态寄存器。
它是volatile因为它可能被意想不到地改变。
它是const因为程序不应该试图去修改它。
2).是的。
一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3).这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
intsquare(volatileint*ptr)
{
inta,b;
a=*ptr;
b=*ptr;
returna*b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。
结果,这段代码可能返不是你所期望的平方值!
正确的代码如下:
longsquare(volatileint*ptr)
{
inta;
a=*ptr;
returna*a;
}
关键在于两个地方:
1.编译器的优化
在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
典型的例子
for(inti=0;i<100000;i++);
这个语句用来测试空循环的速度的,但是编译器肯定要把它优化掉,根本就不执行。
如果你写成
for(volatileinti=0;i<100000;i++);
它就会执行了
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。
比如:
staticinti=0;
intmain(void)
{
...
while
(1)
{
if(i)dosomething();
}
}
voidISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。
如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。
此例中i也应该如此说明。
三、const关键字
主要作用
(1)可以定义const常量,具有不可变性。
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患
(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
(5)为函数重载提供了一个参考
classA{
voidf(inti){......}//一个函数
voidf(inti)const{......}//上一个函数的重载
};
(6)可以节省空间,避免不必要的内存分配
#definePI3.14159//常量宏
constdoulbePi=3.14159;//此时并未将Pi放入内存中
doublei=Pi;//此时为Pi分配内存,以后不再分配!
doubleI=PI;//编译期间进行宏替换,分配内存
doublej="Pi";//没有内存分配
doubleJ="PI";//再进行宏替换,又一次分配内存!
宏常量可以用来指定标准数组的大小和作为const值的初始化值
#defineLEN10
constintlength=10;//没有给常量length分配内存
intarray[LEN];//正确
intarray1[length];//错误!
!
!
!
!
constintnum=LEN;//正确
constintnum1=length;//错误!
!
!
!
!
(7)提高了效率
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
使用方法
1.定义常量
(1)const修饰变量。
它的含义是:
const修饰的类型为TYPE的变量value是不可变的。
TYPEconstValueName=value;
constTYPEValueName=value;
(2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义。
extendconstintValueName=value;
2.指针使用const
(1)指针本身是常量不可变
(char*)constpContent;
const(char*)pContent;
(2)指针所指向的内容是常量不可变
const(char)*pContent;
(char)const*pContent;
(3)两者都不可变
constchar*constpContent;
3.函数中使用
(1)参数指针所指内容为常量不可变
voidfunction(constchar*Var);
(2)参数为引用,为了增加效率同时防止修改。
修饰引用参数时:
voidfunction(constClass&Var);//引用参数在函数内不可以改变
voidfunction(constTYPE&Var);//引用参数在函数内为常量不可变
4.const修饰函数返回值,含义和const修饰普通变量以及指针的含义基本相同。
constint*fun2()//调用时constint*pValue=fun2();
//我们可以把fun2()看作成一个变量,即指针内容不可变。
int*constfun3()//调用时int*constpValue=fun2();
//我们可以把fun2()看作成一个变量,即指针本身不可变。
5.const修饰成员变量
表示成员常量,不能被修改,同时它只能在初始化列表中赋值。
classA
{
constintnValue;//成员常量不能被修改
A(intx):
nValue(x){};//只能在初始化列表中赋值
}
6.const修饰成员函数
const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。
一般写在函数的最后来修饰。
classA
{
voidfunction()const;//常成员函数,它不改变对象的成员变量.
//也不能调用类中任何非const成员函数。
}
const成员函数不被允许修改它所在对象的任何一个数据成员。
const成员函数能够访问对象的const成员,而其他成员函数不可以。
四、sizeof和strlen区别
1.sizeof是操作符,strlen是库函数。
2.sizeof可以用类型和变量做参数,strlen只能用char*做参数,且必须是以'\0'结尾的。
3.数组做sizeof的参数不退化,传递给strlen就退化为指针了。
4.strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而编译器在编译时就计算sizeof的结果,并且sizeof计算的是数据类型占内存的大小。
五、C中的malloc和C++中的new有什么区别
(1)new、delete是操作符,可以重载,只能在C++中使用。
(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。
(3)new可以调用对象的构造函数,对应的delete调用相应的析构函数。
(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数
(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。
六、简述strcpy、sprintf与memcpy的区别
(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。
(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。
七、链表和数组有什么区别
(1)存储形式:
数组是一块连续的空间,声明时就要确定长度。
链表是一块可不连续的动态空间,
长度可变,每个结点要保存相邻结点指针。
(2)数据查找:
数组的线性查找速度快,查找操作直接使用偏移地址。
链表需要按顺序检索结点,
效率低。
(3)数据插入或删除:
链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
(4)越界问题:
链表不存在越界问题,数组有越界问题。
八、栈与队列的特点
(1)栈是先进后出,只能操作尾节点,队列是先进先出。
(2)对插入和删除操作的"限定"。
栈是限定只能在表的一端进行插入和删除操作的线性表,而队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
(3)遍历数据速度不同。
栈只能从头部取数据也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性队列怎不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多
九、C++的引用和C语言的指针有什么区别
(1)引用必须被初始化,但是不分配存储空间。
指针不声明时初始化,在初始化的时候需要分配存储空间。
(2)引用初始化以后不能被改变,指针可以改变所指的对象。
(3)不存在指向空值的引用,但是存在指向空值的指针。
十、typedef和define有什么区别
(1)用法不同:
typedef用来定义一种数据类型的别名,增强程序的可读性。
define主要用来定义常量,以及书写复杂使用频繁的宏。
(2)执行时间不同:
typedef是编译过程的一部分,有类型检查的功能。
define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
(3)作用域不同:
typedef有作用域限定。
define不受作用域约束,只要是在define声明后的引用都是正确的。
(4)对指针的操作不同:
typedef和define定义的指针时有很大的区别。
注意:
typedef定义是语句,因为句尾要加上分号。
而define不是语句,千万不能在句尾加分号。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 关键字 总结