- 相关推荐
C语言关键字
C语言关键字1
一、皇帝身边的小太监----寄存器
不知道什么是寄存器?那见过太监没有?没有?其实我也没有。没见过不要紧,见过就麻烦大了。^_^,大家都看过古装戏,那些皇帝们要阅读奏章的时候,大臣总是先将奏章交给皇帝旁边的小太监,小太监呢再交给皇帝同志处理。这个小太监只是个中转站,并无别的功能。
好,那我们再联想到我们的CPU。CPU 不就是我们的皇帝同志么?大臣就相当于我们的`内存,数据从他这拿出来。那小太监就是我们的寄存器了(这里先不考虑CPU 的高速缓存区)。数据从内存里拿出来先放到寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。这里要说明的一点是:小太监是主动的从大臣手里接过奏章,然后主动的交给皇帝同志,但寄存器没这么自觉,它从不主动干什么事。一个皇帝可能有好些小太监,那么一个CPU 也可以有很多寄存器,不同型号的CPU 拥有寄存器的数量不一样。
为啥要这么麻烦啊?速度!就是因为速度。寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。进水楼台先得月嘛,它离CPU 很近,CPU 一伸手就拿到数据了,比在那么大的一块内存里去寻找某个地址上的数据是不是快多了?那有人问既然它速度那么快,那我们的内存硬盘都改成寄存器得了呗。我要说的是:你真有钱!
二、举例
register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的'话,应将其保存在CPU的寄存器中,以加快其存储速度。例如下面的内存块拷贝代码,
#ifdef NOSTRUCTASSIGN
memcpy (d, s, l)
{
register char *d;
register char *s;
register int i;
while (i--)
*d++ = *s++;
}
#endif
三、使用register 修饰符的注意点
但是使用register修饰符有几点限制。
首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的.次数不够多,不足以装入和存储变量所带来的额外开销。
早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
C语言关键字2
为什么引入const?
const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
const关键字使用非常的灵活,这一点和php差别很大,php中const用来在类中定义一个常量,而在c中,const因位置不同有不同的作用,因情景不同有不同的角色,使用起来也是非常的灵活。
(1):const用来修饰普通的.变量(指针变量除外)的时候,const type name 和 type const name 这两种形式是完全等价的,都表示其是常量,不能进行修改。
#include
int main(){
const int num =23;
printf("result=%dn",num);
num =31;
printf("result=%dn",num); //报错,num是常量,不能修改
}
(2):const用来修饰指针变量的时候,分为以下四种情况
1、const type *name :这种情况下,const修饰的指针变量name所指向的type类型对象,也就是说指向的这个对象是不能进行修改的,因为其是常量,而指针变量确实可以进行修改的
#include
int main(){
int tmp = 23;
const int *num = &tmp;
printf("result=%dn",*num);
(*num) = 24; //报错,因为指针num指向的int类型的对象是不能进行修改的
printf("result=%dn",*num);
}
2、type const *name :这种情况下,const修饰的指针变量name所指向的type类型对象,意思完全同上,只是颠倒了以下顺序。
#include
int main(){
int tmp = 23;
int const* num = &tmp;
printf("result=%dn",*num);
(*num) = 24; //报错,因为指针num指向的int类型的对象是不能进行修改的
printf("result=%dn",*num);
}
3、type * const name :这种情况下,const修饰的指针变量name,也就是说这个指针变量的值是不能进行修改的,但是指针变量所指向的对象确实可以修改的
#include
int main(){
int tmp = 100;
int *const num = &tmp;
printf("result=%dn",*num);
int change = 23;
num = &change; //报错,因为指针num是不能进行修改的
printf("result=%dn",*num);
}
4、const type * const name :这种情况下,const修饰的指针变量name以及指针变量name所指向的对象,也就是说这个指针变量以及这个指针变量所指向的对象都是不能进行修改的
(3):const在函数中的参数的作用:
void get_value( const int num ){
num=23; //报错
}
调用get_value()函数的时候,传递num参数到函数,因为定义了const,所以在函数中num是不能进行修改的
C语言关键字3
数据类型修饰相关
auto按照自动的方式进行变量的存储
const定义常量或常参数
extern声明外部变量或函数
register指定变量的存储类型是寄存器变量
static指定变量的存储类型是静态变量,或指定函数是静态函数
volatile变量的`值可能在程序的外部被改变
数据类型相关
char字符型数据
short定义短整型变量或指针
int整型数据
long长整型数据
signed有符号的整型数据
unsigned定义无符号的整型变量或数据
float单精度浮点型数据
double双精度浮点型数据
struct结构体型数据
enum枚举型类型
union联合型数据
void空型数据
typedef为数据类型定义别名
流程控制相关
continue结束本次循环进入下一次循环
break跳出循环或switch语句
switch定义switch语句
case定义switch中的case子句
default定义switch中的default子句
do定义do-while语句
while定义while或do-while语句
if定义if语句或if-else语句
else定义if-else语句
for定义for循环语句
goto定义goto语句
预处理相关
#include包含头文件
#define定义宏
#undef取消已经定义的宏
#if定义条件编译的条件
#ifdef定义条件编译的条件
ifndef定义条件编译的条件
#elif定义条件编译的条件
#endif结束条件编译
C语言关键字4
通俗一点说: 枚举就是你的那个变量就那么几种状态,不会出现别的,而且你也不想被五一修改,那么就定义为枚举好了。状态机的状态,用这个表示最合适不过了。
下面说一说枚举和define的应用场景
两enum是一个类型和变量了。
define 只是一个宏定义
define特点:
优点:宏定义可为多种类型的值,如字符串、整型、浮点型等。
补充:其实他还可以用于各种函数替换,变量组合拼凑......这些是枚举所无法达到的.。
缺点:没有范围限制(全局范围有效),容易发生冲突,产生不确定的结果;
多个相关的值一起定义时比较散乱。
enum特点:
缺点:只能为整型值
优点:遵循范围规则,不易和其它定义发生冲突。
多个相关值一组,比较清晰。
一般情况下二者可选时尽量用enum。
再说一遍一般情况下二者可选时尽量用enum。
另外debug的时候,枚举变量显示的数据,是你的枚举,调试很方便的。
C语言关键字5
auto :声明自动变量
short :声明短整型变量或函数
int: 声明整型变量或函数
long :声明长整型变量或函数
float:声明浮点型变量或函数
double :声明双精度变量或函数
char :声明字符型变量或函数
struct:声明结构体变量或函数
union:声明共用数据类型
enum :声明枚举类型
typedef:用以给数据类型取别名
const :声明只读变量
unsigned:声明无符号类型变量或函数
signed:声明有符号类型变量或函数
extern:声明变量是在其他文件正声明
register:声明寄存器变量
static :声明静态变量
volatile:说明变量在程序执行中可被隐含地改变
void :声明函数无返回值或无参数,声明无类型指针
if:条件语句
else :条件语句否定分支(与 if 连用)
switch :用于开关语句 case:开关语句分支
for:一种循环语句
do :循环语句的循环体
while :循环语句的`循环条件
goto:无条件跳转语句
continue:结束当前循环,开始下一轮循环
break:跳出当前循环
default:开关语句中的“其他”分支
sizeof:计算数据类型长度
return :子程序返回语句(可以带参数,也可不带参数)循环条件
C语言关键字6
要理解 restrict,先要知道什么是 Pointer aliasing。
Pointer aliasing 是指两个或以上的指针指向同一数据,例如
int i = 0;
int *a = &i;
int *b = &i;
这样会有什么问题呢?
如果编译器采用最安全的假设,即不理会两个指针会否指向同一数据,那么通过指针读写数据是很直观的。
然而,这种假设会令编译器无法优化,例如:
int foo(int *a, int *b)
{
*a = 5;
*b = 6;
return *a + *b; // 不一定是 11!
}
如果 a 和 b 都指向同一数据,*b = 6 会导致 *a = 6,返回12。所以编译器在做 *a + *b 的时候,需要重新读取 *a 指向的数据:
foo:
movl $5, (%rdi) # 存储 5 至 *a
movl $6, (%rsi) # 存储 6 至 *b
movl (%rdi), %eax # 重新读取 *a (因为有可能被上一行指令造成改变)
addl $6, %eax # 加上 6
ret
如果我们确保两个指针不指向同一数据,就可以用 restrict 修饰指针类型:
int rfoo(int *restrict a, int *restrict b)
{
*a = 5;
*b = 6;
return *a + *b;
}
编译器就可以根据这个信息,做出优化:
rfoo:
movl $11, %eax # 在编译期已计算出 11
movl $5, (%rdi) # 存储 5 至 *a
movl $6, (%rsi) # 存储 6 至 *b
ret
但如果用了 restrict 去修饰两个指针,而它们在作用域内又指向同一地址,那么是未定义行为。
总括而言,restrict 是为了告诉编译器额外信息(两个指针不指向同一数据),从而生成更优化的'机器码。注意,编译器是无法自行在编译期检测两个指针是否 alias。如使用 restrict,程序员也要遵守契约才能得出正确的代码(指针不能指向相同数据)。
以个人经验而言,编写代码时通常会忽略 pointer aliasing 的问题。更常见是在性能剖测时,通过反编译看到很多冗余的读取指令,才会想到加入 restrict 关键字来提升性能。
C语言关键字7
A循环语句:
for:一种循环语句
do :循环语句的循环体
while :循环语句的.循环条件
break:跳出当前循环
continue:结束当前循环,开始下一轮循环 B条件语句:
if: 条件语句
else:条件语句否定分支(与 if 连用)
goto:无条件跳转语句 C开关语句:
switch :用于开关语句
case:开关语句分支
default:开关语句中的“其他”分支 D返回语句:
return :子程序返回语句(可以带参数,也看不带参数)
C语言关键字8
(1) char :声明字符型变量或函数
(2) double :声明双精度变量或函数
(3) enum :声明枚举类型
(4) float:声明浮点型变量或函数
(5) int: 声明整型变量或函数
(6) long :声明长整型变量或函数
(7) short :声明短整型变量或函数
(8) signed:声明有符号类型变量或函数
(9) struct:声明结构体变量或函数
(10) union:声明共用体(联合)数据类型
(11) unsigned:声明无符号类型变量或函数
(12) void :声明函数无返回值或无参数,声明无类型指针
C语言关键字9
因为在app开发中,定义网络链接或者设定常用变量时经常会用到一些C中的`关键字,但很多同学只见其名而不知其意,今天就介绍2个开发中常用的C语言关键字,分别是C语言中的static和extern关键字。
1.static
static的.作用
修饰局部变量:
1.延长局部变量的生命周期,程序结束才会销毁
2.局部变量只会生成一份内存,只会初始化一次
3.改变局部变量的作用域
写法:
//方法内部static修饰的变量
//不管调用与否,只会调用一次
- (void)staticTest
{
// static修饰局部变量
static int age = 0;
age++;
NSLog(@"%d",age);
}
//即调用两次该方法
//打印结果
//1
//2
修饰全局变量
1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改
2.避免重复定义全局变量
写法:
// 全局变量:只有一份内存,
//所有文件共享,与extern联合使用。
// 写在方法外部
// static修饰全局变量
static int age = 20;
//调用
extern int age;
NSLog(@"%d",age);
//打印结果
//20
2.extern
extern作用:
只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量
extern工作原理:
先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
C语言关键字10
标识符
定义变量时,我们使用了诸如“a”“abc”“mn123”这样的名字,它们都是程序员自己起的,一般能够表达出变量的作用,这叫做标识符(Identifier)。
标识符就是程序员自己起的名字,除了变量名,后面还会讲到函数名、宏名、结构体名等。不过,名字也不能随便起,C语言规定,标识符只能由字母(A~Z, a~z)、数字(0~9)和下划线(_)组成,并且第一个字符必须是字母或下划线。
以下标识符是合法的:
a, x, x3, BOOK_1, sum5
以下标识符是非法的:
3s 不能以数字开头
s*T 出现非法字符*
-3x 不能以减号(-)开头
bowy-1 出现非法字符减号(-)
在使用标识符时还必须注意以下几点:
C语言虽然不限制标识符的长度,但是它受到不同编译器的限制,同时也受到具体机器的限制。例如在某个编译器中规定标识符前128位有效,当两个标识符前128位相同时,则被认为是同一个标识符。
在标识符中,大小写是有区别的,例如BOOK和book 是两个不同的标识符。
标识符虽然可由程序员随意定义,但标识符是用于标识某个量的符号,因此,命名应尽量有相应的意义,以便于阅读理解,作到“顾名思义”。
关键字
关键字(Keywords)是由C语言规定的具有特定意义的字符串,通常也称为保留字,例如 int、char、long、float、unsigned 等。我们定义的标识符不能与关键字相同,否则会出现错误。
你也可以将关键字理解为具有特殊含义的标识符,它们已经被系统使用,我们不能再使用了。
标准C语言中一共规定了32个关键字,大家可以参考C语言关键字及其解释[共32个],后续我们会一一讲解。
注释
注释(Comments)可以出现在代码中的.任何位置,用来向用户提示或解释程度的意义。程序编译时,会忽略注释,不做任何处理,就好像它不存在一样。
C语言支持单行注释和多行注释:
单行注释以//开头,直到本行末尾(不能换行);
多行注释以/*开头,以*/结尾,注释内容可以有一行或多行。
一个使用注释的例子:
/* Powered by: c.biancheng.net Author: xiao p Date: 20xx-6-26*/#includeint main(){ /* puts 会在末尾自动添加换行符 */ puts("http://c.biancheng.net"); printf("C语言中文网"); //printf要手动添加换行符 return 0;}
运行结果:
http://c.biancheng.net
C语言中文网
在调试程序的过程中可以将暂时不使用的语句注释掉,使编译器跳过不作处理,待调试结束后再去掉注释。
需要注意的是,多行注释不能嵌套使用。例如下面的注释是错误的:
/*C语言/*中文*/网*/
而下面的注释是正确的:
/*C语言中文网*/ /*c.biancheng.net*/
C语言关键字11
const 我们称之为常量修饰符,意即其所修饰的对象为常量(immutable)。
我们来分情况看语法上它该如何被使用。
1、函数体内修饰局部变量。
例:
void func(){
const int a=0;
}
首先,我们先把const这个单词忽略不看,那么a是一个int类型的局部自动变量,
我们给它赋予初始值0。
然后再看const.
const作为一个类型限定词,和int有相同的地位。
const int a;
int const a;
是等价的。于是此处我们一定要清晰的明白,const修饰的对象是谁,是a,和int没
有关系。const 要求他所修饰的对象为常量,不可被改变,不可被赋值,不可作为左值(l-value)。
这样的写法也是错误的。
const int a;
a=0;
这是一个很常见的使用方式:
const double pi=3.14;
在程序的后面如果企图对pi再次赋值或者修改就会出错。
然后看一个稍微复杂的例子。
const int* p;
还是先去掉const 修饰符号。
注意,下面两个是等价的。
int* p;
int *p;
其实我们想要说的是,*p是int类型。那么显然,p就是指向int的指针。
同理
const int* p;
其实等价于
const int (*p);
int const (*p);
即,*p是常量。也就是说,p指向的数据是常量。
于是
p+=8; //合法
*p=3; //非法,p指向的数据是常量。
那么如何声明一个自身是常量指针呢?方法是让const尽可能的`靠近p;
int* const p;
const右面只有p,显然,它修饰的是p,说明p不可被更改。然后把const去掉,可以
看出p是一个指向 int形式变量的指针。
于是
p+=8; //非法
*p=3; //合法
再看一个更复杂的例子,它是上面二者的综合
const int* const p;
说明p自己是常量,且p指向的变量也是常量。
于是
p+=8; //非法
*p=3; //非法
const 还有一个作用就是用于修饰常量静态字符串。
例如:
const char* name=David;
如果没有const,我们可能会在后面有意无意的写name[4]='x'这样的语句,这样会
导致对只读内存区域的赋值,然后程序会立刻异常终止。有了 const,这个错误就
能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译
期被发现。
const 还可以用来修饰数组
const char s[]=David;
与上面有类似的作用。
2、在函数声明时修饰参数
来看实际中的一个例子。
void * memmove(void *dst, const void *src, size_t len);
这是标准库中的一个函数,用于按字节方式复制字符串(内存)。
它的第一个参数,是将字符串复制到哪里去(dest),是目的地,这段内存区域必须
是可写。
它的第二个参数,是要将什么样的字符串复制出去,我们对这段内存区域只做读
取,不写。
于是,我们站在这个函数自己的角度来看,src 这个指针,它所指向的内存内所存
储的数据在整个函数执行的过程中是不变。于是src所指向的内容是常量。于是就
需要用const修饰。
例如,我们这里这样使用它。
const char* s=hello;
char buf[100];
memmove(buf,s,6); //这里其实应该用strcpy或memcpy更好
如果我们反过来写,
memmove(s,buf,6);
那么编译器一定会报错。事实是我们经常会把各种函数的参数顺序写反。事实是编
译器在此时帮了我们大忙。如果编译器静悄悄的不报错,(在函数声明处去掉
const即可),那么这个程序在运行的时候一定会崩溃。
这里还要说明的一点是在函数参数声明中const一般用来声明指针而不是变量本身。
例如,上面的size_t len,在函数实现的时候可以完全不用更改len的值,那么是否
应该把len也声明为常量呢?可以,可以这么做。我们来分析这么做有什么优劣。
如果加了const,那么对于这个函数的实现者,可以防止他在实现这个函数的时候修
改不需要修改的值(len),这样很好。
但是对于这个函数的使用者,
1。这个修饰符号毫无意义,我们可以传递一个常量整数或者一个非常量整数过
去,反正对方获得的只是我们传递的一个copy。
2。暴露了实现。我不需要知道你在实现这个函数的时候是否修改过len的值。
所以,const一般只用来修饰指针。
再看一个复杂的例子
int execv(const char *path, char *const argv[]);
着重看后面这个,argv.它代表什么。
如果去掉const,我们可以看出
char * argv[];
argv是一个数组,它的每个元素都是char *类型的指针。
如果加上const.那么const修饰的是谁呢?他修饰的是一个数组,argv[],意思就是
说这个数组的元素是只读的。那么数组的元素的是什么类型呢?是char *类型的指
针.也就是说指针是常量,而它指向的数据不是。
于是
argv[1]=NULL; //非法
argv[0][0]='a'; //合法
3、全局变量。
我们的原则依然是,尽可能少的使用全局变量。
我们的第二条规则 则是,尽可能多的使用const。
如果一个全局变量只在本文件中使用,那么用法和前面所说的函数局部变量没有什
么区别。
如果它要在多个文件间共享,那么就牵扯到一个存储类型的问题。
有两种方式。
1.使用extern
例如
/* file1.h */
extern const double pi;
/* file1.c */
const double pi=3.14;
然后其他需要使用pi这个变量的,包含file1.h
#include file1.h
或者,自己把那句声明复制一遍就好。
这样做的结果是,整个程序链接完后,所有需要使用pi这个变量的共享一个存储区域。
最后,说说const的作用。
const 的好处,是引入了常量的概念,让我们不要去修改不该修改的内存。直接的
作用就是让更多的逻辑错误在编译期被发现。所以我们要尽可能的多使用const。
但是很多人并不习惯使用它,更有甚者,是在整个程序 编写/调试 完后才补
const。如果是给函数的声明补const,尚好。如果是给 全局/局部变量补const,那
么……那么,为时已晚,无非是让代码看起来更漂亮了。
C语言关键字12
其实在写代码的时候,关键字还是用的比较多的,这里主要就平常中用到的常用关键字进行总结,便于更全面的理解其在代码中的意图。下文主要介绍C语言的关键字define的相关内容。
1 、防止一个头文件被重复包含
当我们的程序很大很大时,比如超过20000行时,我们很可能没有办法对所有的文件都有清楚的了解,特别是.h
文件,那么我们怎么做才能避免include时重复包含呢 答案就是#define
如下:
a.h b.h c.h 三个文件
a.h 内容
int variable_a;
b.h 内容
#include "a.h"
int variable_b;
c.h内容
#include "a.h"
#include "b.h"
int c;
那么c.h就包含了两次a.h,那如何避免呢
#ifndef A_H
#define A_H
//头文件A
#endif
这样就可以保证a.h只会被包含一次
2、简单替换
#define MACRO_A 15
后面出现的MACRO_A 都会被替换为15这个数
3、带参数的替换
#define ABS( a ) ( ((a)>0) ? (a) : (-(a)) )
可以使用宏替换来实现简单的`函数。
以下是#define的高级用法
4、高级用法
define中的三个特殊符号:#,##,#@
#defineConn(x,y) x##y
#defineToChar(x) #@x
#defineToString(x) #x
(1)x##y表示什么?表示x连接y,举例说:
intn=Conn(123,456);/* 结果就是n=123456;*/
char*str=Conn("asdf","adf");/*结果就是 str = "asdfadf";*/
(2)再来看#@x,其实就是给x加上单引号,结果返回是一个const char。举例说:
char a = ToChar(1);结果就是a='1';
做个越界试验char a = ToChar(123);结果就错了;
但是如果你的参数超过四个字符,编译器就给给你报错了!
error C20xx: too many characters in constant :P
(3)最后看看#x,估计你也明白了,他是给x加双引号
char* str = ToString(123132);就成了str="123132";
C语言关键字13
1.作用于变量:
用static声明局部变量-------局部变量指在代码块{}内部定义的变量,只在代码块内部有效(作用域),其缺省的存储方式是自动变量或说是动态 存储的,即指令执行到变量定义处时才给变量分配存储单元,跳出代码块时释放内存单元(生命期)。用static声明局部变量时,则改变变量的存储方式(生 命期),使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过 由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。
用static声明外部变量-------外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内 存单元。同时其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用 static关键字对其作出声明。
总结:用static声明局部变量,使其变为静态存储方式(静态数据区),作用域不变;用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
2.作用于函数:
使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
const作用: “只读(readonly)”
1.定义常量
(1)const
修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的,readonly。
TYPE const ValueName = value;
const TYPE ValueName = value;
(2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义.
extend const int ValueName = value;
2.指针使用CONST
(1)指针本身是常量不可变
char * const pContent;
const (char*) pContent;
(2)指针所指向的内容是常量不可变
const char *pContent;
char const *pContent;
(3)两者都不可变
const char* const pContent;
(4)还有其中区别方法,沿着*号划一条线:如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。
3.函数中使用CONST
(1)const修饰函数参数
a.传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)
void function(const int Var);
b.参数指针所指内容为常量不可变
void function(const char* Var);
c.参数指针本身为常量不可变(也无意义,因为char* Var也是形参)
void function(char* const Var);
d.参数为引用,为了增加效率同时防止修改。修饰引用参数时:
void function(const Class& Var); //引用参数在函数内不可以改变
void function(const TYPE& Var); //引用参数在函数内为常量不可变
这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的.,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本, 然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效.另外只有引 用的const传递可以传递一个临时对象,因为临时对象都是const属性, 且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的 const传递能够捕捉到这个家伙.
(2)const 修饰函数返回值
const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。
a.
const int fun1() //这个其实无意义,因为参数返回本身就是赋值。
b.
const int * fun2() //调用时
const int *pValue = fun2(); //我们可以把fun2()看作成一个变量,即指针内容不可变。
c.
int* const fun3() //调用时
int * const pValue = fun2(); //我们可以把fun2()看作成一个变量,即指针本身不可变。
C语言关键字14
1、static 修饰函数
函数的访问范围就被限定在本文件以内,任何本文件以外内容的访问此函数,都是非法和无效的,编译不会通过,提示找不到该符号。 所以,我们在进行一个函数设计的时候,首先需要考虑的就是,这个函数的作用是什么,作用范围是什么,我们应该怎么去保证模块化。如果没有被外部文件访问,那么就把它限定为static。这就是这个static 修饰函数的用处。
2、static 修饰变量
无论在文件的哪个位置,用static修饰 变量,这个变量运行时都是占用的'RAM里的静态全局数据区,再说一遍,静态全局。在程序运行的整个生命周期内是不会释放的,这个我们也在前面单片机相关的文章里讲过了。
既然static 修饰的变量和全局变量占用一样的空间,为何还有static修饰,直接全局变量也就OK了啊? 对于硬件来说,有没有static自然是一样的,但是对于软件却不一样。
函数外static修饰的变量,仅仅限定与本文件使用,所以你可以不关注本文件以外的信息,就知道这个变量是如何变化和使用的,这就减小了你阅读程序的难度。
函数内static修饰的变量,仅仅限定于本函数使用,所以你可以不关注本函数以外的信息,就知道这个变量是如何变化的,进一步减小了你阅读程序的难度,也方便查询问题。
所以能定义为static的就尽量定义为static(后面会再更新如何去除static,书写可重入函数)。
C语言关键字15
1.static关键字
这个关键字的作用是饿很难强大的。
要对static关键字深入了解,首先需要掌握标准C程序的组成。
标准C程序一直由下列部分组成:
1)正文段——CPU执行的机器指令部分,也就是你的程序。一个程序只有一个副本;只读,这是为了防止程序由于意外事故而修改自身指令;
2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。
注意:只有全局变量被分配到数据段中。
4)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。这句很关键,常常有笔试题会问到什么东西放到栈里面就足以说明。
5)堆——动态存储分配。
在嵌入式C语言当中,它有三个作用:
作用一:在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
这样定义的变量称为局部静态变量:在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。也就是上面的作用一中提到的在函数体内定义的变量。除了类型符外,若不加其它关键字修饰,默认都是局部变量。比如以下代码:
void test1(void)
{
unsigned char a;
static unsigned char b;
…
a++;
b++;
}
在这个例子中,变量a是局部变量,变量b为局部静态变量。作用一说明了局部静态变量b的特性:在函数体,一个被声明为静态的变量(也就是局部静态变量)在这一函数被调用过程中维持其值不变。这句话什么意思呢?若是连续两次调用上面的函数test1:
void main(void)
{
…
test1();
test1();
…
}
然后使程序暂停下来,读取a和b的值,你会发现,a=1,b=2。怎么回事呢,每次调用test1函数,局部变量a都会重新初始化为0x00;然后执行a++;而局部静态变量在调用过程中却能维持其值不变。
通常利用这个特性可以统计一个函数被调用的次数。
声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的.,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。代码如下:
void count();
int main()
{
int i;
for (i = 1; i <= 3; i++)
{
count();
{
return 0;
}
void count()
{
static num = 0;
num++;
printf(" I have been called %d",num,"times/n");
}
输出结果为:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.
看一下局部静态变量的详细特性,注意它的作用域。
1)内存中的位置:静态存储区
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。
作用二:在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
这样定义的变量也称为全局静态变量:在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。也就是上述作用二中提到的在模块内(但在函数体外)声明的静态变量。
定义全局静态变量的好处:
<1>不会被其他文件所访问,修改,是一个本地的局部变量。
全局变量的详细特性,注意作用域,可以和局部静态变量相比较:
1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)
3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。
当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。
作用三:在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
这样定义的函数也成为静态函数:在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
定义静态函数的好处:
<1>其他文件中可以定义相同名字的函数,不会发生冲突
这里我一直强调数据和函数的本地化,这对于程序的结构甚至优化都有巨大的好处,更大的作用是,本地化的数据和函数能给人传递很多有用的信息,能约束数据和函数的作用范围。在C++的对象和类中非常注重的私有和公共数据/函数其实就是本地和全局数据/函数的扩展,这也从侧面反应了本地化数据/函数的优势。
最后说一下存储说明符,在标准C语言中,存储说明符有以下几类:
auto、register、extern和static
对应两种存储期:自动存储期和静态存储期。
auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。
2. const 关键字
const关键字也是一个优秀程序中经常用到的关键字。关键字const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。通过给优化器一些附加的信息,使用关键字const 也许能产生更紧凑的代码。合理地使用关键字const 可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
深入理解const关键字,你必须知道:
a. const关键字修饰的变量可以认为有只读属性,但它绝不与常量划等号。
如下代码:
const int i=5;
int j=0;
...
i=j; //非法,导致编译错误,因为只能被读
j=i; //合法
b. const关键字修饰的变量在声明时必须进行初始化。如下代码:
const int i=5; //合法
const int j; //非法,导致编译错误
c. 用const声明的变量虽然增加了分配空间,但是可以保证类型安全。const最初是从C++变化得来的,它可以替代define来定义常量。在旧版本(标准前)的c中,如果想建立一个常量,必须使用预处理器:
#define PI 3.14159
此后无论在何处使用PI,都会被预处理器以3.14159替代。编译器不对PI进行类型检查,也就是说可以不受限制的建立宏并用它来替代值,如果使用不慎,很可能由预处理引入错误,这些错误往往很难发现。而且,我们也不能得到PI的地址(即不能向PI传递指针和引用)。const的出现,比较好的.解决了上述问题。
d. C标准中,const定义的常量是全局的。
e. 必须明白下面语句的含义,我自己是反复记忆了许久才记住,方法是:若是想定义一个只读属性的指针,那么关键字const要放到‘* ’后面。
char *const cp; //指针不可改变,但指向的内容可以改变
char const *pc1; //指针可以改变,但指向的内容不能改变
const char *pc2; //同上(后两个声明是等同的)
f. 将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。
参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。例子:
void fun0(const int * a );
void fun1(const int & a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const int * a,则不能对传递进来的指针所指向的内容进行改变,保护了原指针所指向的内容;如形参为const int & a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
g. 修饰函数返回值,可以阻止用户修改返回值。(在嵌入式C中一般不用,主要用于C++)
h. const消除了预处理器的值替代的不良影响,并且提供了良好的类型检查形式和安全性,在可能的地方尽可能的使用const对我们的编程有很大的帮助,前提是:你对const有了足够的理解。
最后,举两个常用的标准C库函数声明,它们都是使用const的典范。
1.字符串拷贝函数:char *strcpy(char *strDest,const char *strSrc);
2.返回字符串长度函数:int strlen(const char *str);
3. volatile关键字
一个定义为volatile 的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i)
dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。
如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
不懂得volatile 的'内容将会带来灾难,这也是区分C语言和嵌入式C语言程序员的一个关键因素。为强调volatile的重要性,再次举例分析:
代码一:
int a,b,c;
//读取I/O空间0x100端口的内容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
代码二:
volatile int a;
int a,b,c;
//读取I/O空间0x100端口的内容
a= inword(0x100);
b=a;
a=inword(0x100)
c=a;
在上述例子中,代码一会被绝大多数编译器优化为如下代码:
a=inword(0x100)
b=a;
c=a;
这显然与编写者的目的不相符,会出现I/O空间0x100端口漏读现象,若是增加volatile,像代码二所示的那样,优化器将不会优化掉任何代码.
从上面来看,volatile关键字是会降低编译器优化力度的,但它保证了程序的正确性,所以在适合的地方使用关键字volatile是件考验编程功底的事情.
4.struct与typedef关键字
面对一个人的大型C/C++程序时,只看其对struct的使用情况我们就可以对其编写者的编程经验进行评估。因为一个大型的C/C++程序,势必要涉及一些(甚至大量)进行数据组合的结构体,这些结构体可以将原本意义属于一个整体的数据组合在一起。从某种程度上来说,会不会用struct,怎样用struct是区别一个开发人员是否具备丰富开发经历的标志。
在网络协议、通信控制、嵌入式系统的`C/C++编程中,我们经常要传送的不是简单的字节流(char型数组),而是多种数据组合起来的一个整体,其表现形式是一个结构体。
经验不足的开发人员往往将所有需要传送的内容依顺序保存在char型数组中,通过指针偏移的方法传送网络报文等信息。这样做编程复杂,易出错,而且一旦控制方式及通信协议有所变化,程序就要进行非常细致的修改。
用法:
在C中定义一个结构体类型要用typedef:
typedef struct Student
{
int a;
}Stu;
于是在声明变量的时候就可:Stu stu1;
如果没有typedef就必须用struct Student stu1;来声明
这里的Stu实际上就是struct Student的别名。
另外这里也可以不写Student(于是也不能struct Student stu1;了)
typedef struct
{
int a;
}Stu;
struct关键字的一个总要作用是它可以实现对数据的封装,有一点点类似与C++的对象,可以将一些分散的特性对象化,这在编写某些复杂程序时提供很大的方便性.
比如编写一个菜单程序,你要知道本级菜单的菜单索引号、焦点在屏上是第几项、显示第一项对应的菜单条目索引、菜单文本内容、子菜单索引、当前菜单执行的功能操作。若是对上述条目单独操作,那么程序的复杂程度将会大到不可想象,若是菜单层数少些还容易实现,一旦菜单层数超出四层,呃~我就没法形容了。若是有编写过菜单程序的朋友或许理解很深。这时候结构体struct就开始显现它的威力了:
//结构体定义
typedef struct
{
unsigned char CurrentPanel;//本级菜单的菜单索引号
unsigned char ItemStartDisplay; //显示第一项对应的菜单条目索引
unsigned char FocusLine; //焦点在屏上是第几项
}Menu_Statestruct;
typedef struct
{
unsigned char *MenuTxt; //菜单文本内容
unsigned char MenuChildID;//子菜单索引
void (*CurrentOperate)();//当前菜单执行的功能操作
}MenuItemStruct;
typedef struct
{
MenuItemStruct *MenuPanelItem;
unsigned char MenuItemCount;
}MenuPanelStruct;
菜单程序中的结构体定义,这个菜单程序最大可以到256级菜单。当初要写一个菜单程序之前,并没有对结构体了解多少,也没有想到使用结构体。只是一层层菜单的单独处理:如果按键按下,判断是哪个按键,调用对应按键的子程序,刷屏显示。这样处理起来每一层都要判断当前的光标所在行,计算是不是在显示屏的顶层,是不是在显示层的底层,是不是需要翻页等等,非常的繁琐。
【C语言关键字】相关文章:
C语言学习经验07-01
C语言学习经验(精品)07-01
c语言中什么是静态变量05-26
C语言学习经验[精华6篇]07-01
计算机二级C语言难不难 考什么03-05
C位的作文11-21
C罗将缺席季前赛,曼晚称C罗获得额外假期03-11
维生素c的故事反思07-15
为什么不看好C2C二手车电商08-14