原创 C语言关键字

2013-8-29 07:21 1441 15 15 分类: MCU/ 嵌入式
1.c语言中一共有32个关键字,分别是:auto、int、double、long、char、short、float、unsigned、signed、sizeof、extern、static、goto、if、else、struct、typedef、union、enum、switch、case、break、default、do、while、const、register、volatile、return、void、for、continue。注意:define、include这些带#号的都不是关键字,是预处理指令。
2.定义与声明
定义 是创建一个对象并为止分配内存。 如:int a;
声明 是告诉编译器在程序中有这么一个对象,并没有分配内存。 如: extern int a;
3.对于register这个关键字定义的变量,不能进行取地址运算(&),因为对于x86架构来说,地址都是在内存中的,不是在寄存器中的,所以对寄存器进行取地址是没有意义的。并且应该注意的是给register定义的变量,应该赋一个比寄存器大小 要小的值。注意:register只是请求寄存器变量,但是不一定申请成功。
4.关键字static:=
对于static有两种用法:
a.修饰变量:对于静态全局变量和静态局部变量,都有一个特点就是不能被作用域外面,或外文件调用(即使是使用了extern也没用)。原因就是它是存储在静态存储区中的。对于函数中的静态局部变量还有一个问题,就是它是存在静态存储区的,即使函数结束栈区收回,这个变量的值也不改变。static int i=0; 这是一条初始化语句 而不是一条赋值语句 所以跟i=0不一样的。
b.修饰函数 :是定义为静态函数,使函数只能在文件内部使用,这样不同文件中的函数名就不怕重名了。原因也是相同的,就是static修饰的一切都是在静态存储区中的。
-
static代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(void)
  4. {
  5. static int j=0;
  6. int k;
  7. void fun1()
  8. {
  9. j=0;
  10. j++;
  11. printf("fun1 %d\n",j);
  12. }
  13. void fun2()
  14. {
  15. static int i=0;
  16. //i=0;
  17. printf("fun2 %d\n",i);
  18. i++;
  19. }
  20. for(k=0;k<10;k++)
  21. {
  22. fun1();
  23. fun2();
  24. }
  25. return 1;
  26. }
#include <stdio.h>
#include <stdlib.h>

int main(void) 
{
	static int j=0;
	int k;
	void fun1()
	{
		j=0;
		j++;
		printf("fun1 %d\n",j);
	}
	void fun2()
	{

		static int i=0;
		//i=0;
		printf("fun2 %d\n",i);
		i++;
	}
	for(k=0;k<10;k++)
	{
			fun1();
			fun2();
	} 
	return 1;  
}


5.关键字sizeof:
怎么说明sizeof是关键字不是函数,这里有两个例子:
a. int i; printf("%d\n",sizeof i); 可见 sizeof是关键字
b. sizeof(fun()); 不调用fun函数 因为sizeof是在预编译期间完成的 说明是关键字
sizeof的代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. void fun(int b[100])
  4. {
  5. printf("sizeof(b) is %d\n",sizeof(b));
  6. }
  7. int main(void)
  8. {
  9. int *p=NULL;
  10. int a[100];
  11. int b[100];
  12. printf("sizeof(p) is %d\n",sizeof(p));
  13. printf("sizeof(*p) is %d\n",sizeof(*p));
  14. printf("sizeof(a[100]) is %d\n",sizeof(a[100]));
  15. printf("sizeof(a) is %d\n",sizeof(a));
  16. printf("sizeof(&a) is %d\n",sizeof(&a));
  17. printf("sizeof(&a[0] is %d\n",sizeof(&a[0]));
  18. fun(b);
  19. return 1;
  20. }
#include <stdio.h>
#include <stdlib.h>

void fun(int b[100])
{
	printf("sizeof(b) is %d\n",sizeof(b));
}

int main(void) 
{
	int *p=NULL;
	int a[100];
	int b[100];
	printf("sizeof(p) is %d\n",sizeof(p));
	printf("sizeof(*p) is %d\n",sizeof(*p));
	printf("sizeof(a[100]) is %d\n",sizeof(a[100]));
	printf("sizeof(a) is %d\n",sizeof(a));
	printf("sizeof(&a) is %d\n",sizeof(&a));
	printf("sizeof(&a[0] is %d\n",sizeof(&a[0]));
	
	fun(b);
	return 1;
}
6.关键字if:
a.对于bool类型的比较:FLASE都是0 TRUE不一定是1 所以应该用if(bool_num); if(!bool_num);
对于浮点型与0比较要是否注意:不能直接比较,要定义精度,其实浮点型与浮点型比较也要注意这个问题,就是不能直接比较,要设定精度,如图:
原因跟浮点型的存储格式有关,因为float的有效位是6位,超出6位就未知了,所以不能直接进行比较。同样的原因,也不能用一个很大的浮点数去加一个很小的浮点数。这个加法可能体现不出来。
b.对于if后面的分号问题 ,一定要注意, 会被解析成if后面有一个空语句, 所以使用空语句的时候最好使用NULL;
c.在使用if else的时候,应该if里面先处理正常情况(出现概率大的情况),else里面处理异常情况,这是一个好习惯看着代码舒服。
7.关键字switch、case:
注意case后面应该是整型或者字符型的常量及常量表达式,case后面最好是应该安装字母或数字顺序排列,先处理正常情况,后处理异常情况。
8.关键字void:
void *的一般用途是, 接收任何类型的指针 ,如当传入函数的指针类型不确定的时候,一般用 void*接收任何类型的指针。
void* 指针作为右值赋值给其他指针的时候一定要强制类型转换,因为void* 指针类型不定。
GNU中void *p p++跟char *p p++是一样的 。
注意:strcpy跟memcpy的区别 就是 strcpy是char * memcpy是void *所以说strcpy是给字符串赋值,memset是给整块内存赋值。
9.关键字extern:
extern就有两种用法:一种是声明外部定义的变量或函数、另一种是extern c告诉编译器以标准c语言方式编译
10.关键字return:
使用return的时候,要注意不能返回栈内指针,因为在函数体结束后,栈是会被收回的,其实是不能期望返回一个指针,来返回一块内存。因为返回一个指针或者地址没有问题,因为return是copy然后返回的,但是那个指针指向的内存如果是在函数栈中的话,就很有可能在函数结束后被收回了!!!
return ; 一般返回的值是1,根据编译器而定。
11.关键字const:
a.const是用来定义只读变量的,切忌它定义的是变量,不是常量,真的常量是#define的和enum。
b.在陈正冲老师的这本书中的第35页,有说编译器不为普通const只读变量分配内存空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高,节省空间。具体的没怎么看懂,本次学习也不打算看懂了(因为它说const修饰的全局只读变量是在静态区的,我太认同)~~~嘿嘿
c.其实const就是修饰变量,然后这个变量就不能当作左值了,当作左值,编译器就报错!!!
d. 其实const中最不好区分的知识点是,如图:
其实对于这四个情况的记忆很简单,就是看const跟谁近,是const *p ,还是 * const p,还是const * const p,这样就很容易看出来const是修饰谁的了吧。
e.但是const修饰的变量可以通过,指针将其改变。
f.const修饰函数参数表示在函数体内不希望改变参数的值,比如说在strcmp等函数中,用的都是const char*
g.const修饰函数返回值表示返回值不可以改变,多用于返回指针的情况:
  1. cosnt int* func()
  2. {
  3. static int count = 0;
  4. count++;
  5. return &count;
  6. }
cosnt int* func()
{
      static int  count  =  0;
      count++;
      return &count;
}

h.在看const修饰谁,谁不变的问题上,可以把类型去掉再看,代码如下:

  1. struct student
  2. {
  3. }*str;
  4. const str stu3;
  5. str const stu4;
struct student
{
		
}*str;
const str stu3;
str const stu4;

str是一个类型 ,所以在去掉类型的时候,应该都变成const stu3和const stu4了,所以说应该是stu4和stu3这个指针不能被赋值。
12.关键字volatile:
volatile搞嵌入式的,一定都特别属性这个关键字,记得第一使用这个关键字的时候是在韦东山老师的,Arm裸机视频的时候。volatile是告诉编译不要对这个变量进行任何优化,直接在内存中进行取值。一般用在对寄存器进行赋值的时候,或修饰可能被多个线程访问的变量。

注意:const volatile int i; 应该是定义了一个只读寄存器。
13.关键字struct:
a.对于空结构体的大小问题 ,vc和gcc的输出是不一样的,vc是1 、gcc是0 ,而且vc对于结构体的定义也和gcc不一样 ,vc中有c++的标准扩展了struct的作用,而gcc中是纯c的标准,就是按照标准c语言来的。
b.struct这里还有一个很有用的东西,就是柔性数组,这个东西很有意思,我已经在数据结构的静态链表中进行了阐述,这里就仅仅记录一下,不详细说明了。
14.关键字union:
union有一个作用就是判断,pc是大端存储还是小端存储的,x86是小端存储的,这个东西是有cpu决定的。arm(由存储器控制器决定)和x86一样都是小端的。
下面的是一个大端小端的一个例子,代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(void)
  4. {
  5. int a[5]={1,2,3,4,5};
  6. int *p=(int *)(&a+1); //数组指针 加一 进行正常的指针运算 走到数
  7. 组尾
  8. int *d=(int *)((int)a+1);//地址加一 不是指针运算
  9. //printf("%x\n",*((char *)((int)a+1)-1));
  10. /*因为是小端存储 高地址 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 低地址*/
  11. /*变成了 0x02 0x00 0x00 0x00 */
  12. printf("%x,%x",p[-1],*d); /* 第二个值就是这么存储的0x02 0x00 0x00 0x00 低地址处 所以就是2000000*/
  13. int a=0x11223344;
  14. char *p=(char *)((int)&a);
  15. printf("%x\n%x\n",*(p+0),p+0);
  16. printf("%x\n%x\n",*(p+1),p+1);
  17. return 0;
  18. }
#include <stdio.h>
#include <stdlib.h>
int main(void) 
{
	int a[5]={1,2,3,4,5};
	int *p=(int *)(&a+1);  //数组指针 加一  进行正常的指针运算 走到数

组尾 
	int *d=(int *)((int)a+1);//地址加一  不是指针运算
	//printf("%x\n",*((char *)((int)a+1)-1));
	 
	/*因为是小端存储  高地址  0x00  0x00  0x00  0x02  0x00  0x00  0x00  0x01 低地址*/
	/*变成了 0x02  0x00  0x00  0x00 */ 
	printf("%x,%x",p[-1],*d);  /*  第二个值就是这么存储的0x02  0x00  0x00  0x00  低地址处  所以就是2000000*/
	int a=0x11223344;
	char *p=(char *)((int)&a);
	printf("%x\n%x\n",*(p+0),p+0); 
	printf("%x\n%x\n",*(p+1),p+1);
	return 0;
}
下面是一个利用union判断PC是大端小端的例子,代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. union
  4. {
  5. int i;
  6. char a[2];
  7. }*p,u;
  8. int main(void)
  9. {
  10. p=&u;
  11. p->i=0x3839;
  12. printf("%x\n",p->i);
  13. printf("a0p=%x,a1p=%x\n",&(p->a[0]),&(p->a[1]));
  14. printf("a0=%x,a1=%x\n",p->a[0],p->a[1]);
  15. return 0;
  16. }
#include <stdio.h>
#include <stdlib.h>
union
{
	int i;
	char a[2];
}*p,u;

int main(void) 
{
	p=&u;
	p->i=0x3839;
	printf("%x\n",p->i);
	printf("a0p=%x,a1p=%x\n",&(p->a[0]),&(p->a[1]));
	printf("a0=%x,a1=%x\n",p->a[0],p->a[1]);	
	return 0;
}
15.enum关键字:
枚举enum其实就是int类型,用来保存枚举常量的。enum枚举类型,这个才是真正的常量,定义常量一般用enum 。#define是宏定义是在预编译期间单纯的替换。#define宏定义无法调试,枚举常量是可以调试的。#define宏定义是无类型信息的,枚举类型是有类型信息的常量,是int型的。
16.typedef关键字:
a.typedef用于给一个已经存在的数据类型重新命名。
b.typedef并没有产生新的数据类型
c.typedef重定义的类型不能进行unsigned和signed进行扩展
原因在于typedef 定义新类型的时候 应该定义全了,unsigned int是一个类型 不能拆开的。
  1. typedef unsigned int int32;
typedef  unsigned  int   int32;
d.typedef 和 #define的区别:typedef是给已有的类型取别名,而#define只是简单的字符替换。区别如下图:
#define PCHAR char* PCHAR p3,p4; //p3是char*型 p4是char型
typedef char* PCHAR; PCHAR p1,p2; //p1和p2都是 char*型
e.有一个知识点忘记了,嘿嘿,程序如下:
  1. typedef struct student
  2. {
  3. }str,*str1;
typedef struct student
{
}str,*str1;

str1 abc; 就是定义一个struct student *类型
str abc; 就是定义一个struct student 类型
17.关键字for
a.长循环应该在最内层,这样可以减少各个层直接的切换
b.看看如下两段代码有什么区别:
  1. 程序一:
  2. for(i=0; i<m; i++)
  3. {
  4. for(j=0; j<n; j++)
  5. {
  6. for(k=0; k<p; k++)
  7. {
  8. c[j] = a[k] * b[k][j];
  9. }
  10. }
  11. }
  12. 程序二:
  13. for(i=0; i<m; i++)
  14. {
  15. for(k=0; k<p; k++)
  16. {
  17. for(j=0; j<n; j++)
  18. {
  19. c[j] = a[k] * b[k][j];
  20. }
  21. }
  22. }
程序一:
for(i=0; i<m; i++)
{
    for(j=0; j<n; j++)
    {
        for(k=0; k<p; k++)
        {
            c[j] = a[k] * b[k][j];
        }
    }
}

程序二:
for(i=0; i<m; i++)
{
    for(k=0; k<p; k++)
    {
        for(j=0; j<n; j++)
        {
            c[j] = a[k] * b[k][j];
        }
    }
}
从程序来看,两者实现了同样的功能,区别只是第二层和第三层循环交换了位置。但是他们的差距却是巨大的 ,这个需要从CPU的cache来说了, cpu每次访问内存的时候都会先从内存将数据读入cache ,然后以后都从cache取数据。但是cache的大小是有限的 ,因此只会有部分进入cache。我们来看这个程序 c[j] = a[k] * b[k][j]; 我们都知道C中二维数组是在内存中一维排列的,如果我们把k循环放在第三层 ,那么cache基本没有用了, 每次都需要重新到内存取数据,交换后每次取到cache的数据都可以复用多次 。所以说第二种写法效率高。
18.关键字char(本节最重要的知识点char越界的问题):
对于char有两种类型,分别是:unsigned char(范围是0~255)和 signed char(范围是-128~127) 一个是有符号的,一个是没有符号的。
在计算机中数据都是以数据的补码形式进行存储的,所以如图:
对于无符号类型(unsigned int):就是不考虑最高位的问题,都是原码与补码相等的情况。
然后我们说说越界的问题,对于一个unsigned char i; 我们给 i = 256;这很明显越界了,i是0到255的,那256的补码是什么再在它补码中取低八位就是i的值了。256的补码是1 0000 0000,所以printf ("%d\n",i);的值会是0。如果i = -1;-1的补码是1111 1111 所以会打印出255。
对于一个char类型的越界又是什么样的呢?
char i; 我们给 i = 129; 129是一个正数,它的补码就是原码:是1000 0001,但是它是char型,在char型中1000 0001是什么,如图是-127。所以printf("%d\n",i); 得到的是-127。如果i = -129,它的补码是0111 1111,所以它打印出来的是127。如果是i = 259,我们就把它的补码取低八位来看。259的补码是1 0000 0011 所以说打印出来的是3。最后一个例子,如果i = 385,它的补码是1 1000 0001 ,取低八位是1000 0001,所以打印的应该是-127。
其实不管是有符号的还是没符号的,原则就一个,把数据转换成为补码,取低八位,然后在上面的图中去比较,就ok了。
给一个练习,代码如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int main()
  5. {
  6. char a[1000];
  7. int i;
  8. for(i=0; i<1000; i++)
  9. {
  10. a = (-1-i);
  11. }
  12. while(a)
  13. {
  14. printf("%d\n",a);
  15. i++;
  16. }
  17. printf("%d\n",strlen(a));
  18. return 0;
  19. }
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
	char a[1000];
	int i;
	for(i=0; i<1000; i++)
	{
		a = (-1-i);
	}	
         while(a)
	{
	 	printf("%d\n",a);
		i++;
	}
	printf("%d\n",strlen(a));
	return 0;
}


打印结构是什么:答案是255 分析步骤跟上面是一样的,自己算算吧!!!
其实int的越界原理跟char是一样的。
19.一个关于tab键的问题:
不同编辑器的tab键的字符数是不一样的,一般是4个字符,也有两个字节的,要注意一下,为了代码格式的整齐,建议设置一下tab或者使用空格。

本节遗留问题:

1.printf的实现问题,其实就是可变参数的问题,看linux源码,还有一个问题就是转移字符的问题,char p = '\'' 这样一个问题。
2.浮点型的存储格式,为什么有效位是6位,小数是怎么保存的。

文章评论0条评论)

登录后参与讨论
我要评论
0
15
关闭 站长推荐上一条 /2 下一条