一、关键字
1、register关键字
(1)register作用的实现原理?
计算机的三大组成部分:
CPU的三大组成部分:运算器、控制器、存储器
cache高速缓存器
存储器
寄存器
(2)为什么要把变量变为寄存器变量?(register关键字作用?)
经常被访问的变量我们就可以用register修饰为寄存器变量,请求编译器尽可能的将变量存在CPU的内部寄存器中,节省了CPU从内存中抓取数据的时间,从而提高了运行效率。
(3)使用register关键字应注意哪些?
register只能修饰局部变量,不能修饰全局变量和函数;
register修饰的变量不能通过取地址来获取寄存器变量;
register修饰的变量一定是CPU能接受的数据类型。
2、static关键字
(1)static关键字作用:
static修饰的变量全部保存在数据段的静态存储区,没有初始化的系统默认初始化为0;
static修饰静态局部变量,延长局部变量的生命周期,直至整个程序结束后释放。
static修饰全局变量,使得此全局变量只在本文件可见,不能在其他文件调用。
static修饰函数,使得此函数只在本文件调用,不能在其他文件调用。
(2)什么时候用static关键字?
可以重命名变量 、函数;
想让局部变量在程序结束后释放用static,我们要尽可能的少的使用全局变量,全局 变量比较危险,对所有文件可见,可能在别的文件中发生更改。
想让全局变量、函数只在本文件中被调用,对函数、全局变量起保护作用。
3、extern关键字
extern关键字:(全局变量)外部声明,声明这个变量是在其他文件中定义的,声明时一定要标注变量的数据类型。
4、const关键字
const修饰变量,使变量成为只读变量。
比如:const int num = 5;
const 修饰num,num为只读变量,空间里的值可变,但是不能通过num这个变量名来修改这个空间的对应值。
举例:
(1)const int a = 10;
(2)int const a = 10;
(3)const int a[10] = {1,2,3,4,5,6,7,8,9,10};
(4)const int *p;
(5)int * const p;
(6)const struct devices dev[5];
(7)struct devices const * dev[5];
技巧:
将类型去掉;
看const修饰谁,谁的值就是不能更改的,是readonly的.
(1)去掉类型int变成了”const a = 10”,a的值就不能变了;
(2)去掉类型int变成了”const a = 10”,a的值就不能变了;
(3)去掉类型int变成了”const a[10]”,a数组里的值就不变;
(4)const修饰*p,去掉类型int变成了”const *P”,p所指向的空间的值就不变;
(5)const 修饰p,去掉类型int *变成”const p”,指针p里的值就不能改变,也就是说p不能再指向其他地址,但是p所指向的空间里的值是可变的;
(6)去掉类型struct devices变成”const dev[5]”,dev[5]数组里的值就不改变;
(7)这是一个devices结构体类型的指针数组,它就拥有5个devices结构体类型指针,每个指针指向一个devices结构体,const修饰*dev[5],去掉类型struct devices变成”const *dev[5]”,指针数组dev中每个元素指向的空间里的值不变。
const 使用注意事项:
(1)const修饰变量,一定要对变量做初始化;
(2)一般用const修饰函数形参,防止函数实现过程中修改形参的值。
5、typedef关键字
给数据类型重起名字;
作用:
(1)提高代码的移植性;
(2)方便在编程定义变量、方便编写程序;
(3)解释某些变量的作用,起到注释的作用。
拓展:
typedef与define区别:
1、执行时间:typedef在编译阶段有类型检查的功能;define是宏定义,在预处理阶段,只进行简单机械的字符串替换,不进行任何检查;
2、功能:typedef定义类型的别名,定义与平台无关的数据类型,与struct结合使用等等;define不止可以为类型起别名,还可以定义常量、变量、编译开关等;
3、作用域:typedef有自己的作用域;define没有作用域的限制,只要是之前定义过的宏,在以后的程序中都可以使用;
6、volatile关键字
(1)不会在两个操作之间把volatile变量缓存在寄存器当中,在多任务中,甚至stejmp环境下变量可能被其他程序改变,编译器无法知道,volatile就是告诉编译器这种情况的;
(2)不做常量合并,常量传播等优化;
(3)对volatile变量的读写不会被优化掉,如果你对一个变量赋值,但后面没用到,编译器常常可以忽略掉那个赋值操作,然而对Memory Mapped IO处理是不能这样优化的。
二、复合数据类型
1、struct结构体
作用:封装数据
#include<stdio.h>
struct student
{
int id;
char name[20];
int age;
};
typedef struct student Stu;
int main()
{
struct student stu;
struct student *p_stu = &stu;
// p_stu->id = 1;
scanf("%d",&(p_stu->id));
printf("id = %d\n",p_stu->id);
strcpy(p_stu->name,"zhangsan");
printf("name = %s\n",p_stu->name);
return 0;
}
结构体数组:
#include<stdio.h>
struct student
{
int id;
char name[20];
int age;
};
int main()
{
struct student stu_array[3];
int i;
for(i = 0; i < 3; i++)
{
scanf("%d",&(stu_array[i].id));
scanf("%s",stu_array[i].name);
scanf("%d",&(stu_array[i].age));
}
for(i = 0; i < 3; i++)
{
printf("%d\t",stu_array[i].id);
printf("%s\t",stu_array[i].name);
printf("%d\t",stu_array[i].age);
printf("\n");
}
return 0;
}
结构体注意事项:
结构体有字对齐、半字对齐
内存空间:相同类型放一起,减少内存空间
函数返回多个值:结构体封装or传出参数
2、union共用体
(1)与结构体区别:
共用体共用同一个空间,由最大的决定
共用一个空间会产生值得覆盖;
应用:利用共用体测试操作系统是大端字节序还是小端字节序
#include<stdio.h>
union judge_cpu
{
int num;
char ch;
};
int main()
{
union judge_cpu jcp;
jcp.num = 0x12345678;
int num = 0x12345678;
char *p = #
#if 0
if(jcp.ch == 0x78)
{
printf("small!\n");
}
else
{
printf("big!\n");
}
#endif
if(*p == 0x78)
{
printf("small\n");
}
else
{
printf("big\n");
}
return 0;
}
3、enum枚举
枚举的作用:
1、杜绝幻数,提高可读性;
2、需要大量的整数宏时,用枚举。
三、编译预处理
1、宏定义指令
宏定义主要避免幻数,因为数字本身没有意义,宏定义命名时要清晰的表明其用途
拓展:
#define宏定义与枚举的区别:
(1)#define宏常量是在预编译阶段进行简单的替换,枚举常量则是在编译的时候确定其值;
(2)一般在编译器里,可以调试枚举常量,但是不能调试宏常量;
(3)枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。
定义宏函数:
#include<stdio.h>
#define MAX(a,b) a > b ? a : b
int main()
{
int num = MAX(5,6);
return 0;
}
自定义函数与宏函数有什么区别:
(1)函数式宏定义省去了函数的调用、函数的返回、释放、传参等,提高运行效率。
(2)函数式宏定义不做语法检查不安全;
(3)函数安全,会做语法检查,但运行效率会低;
用编译时间换内存空间:宏函数
用内存空间换执行/运行时间:inline(修饰函数、内敛函数)
内置宏定义:
__LINE__打印所在行
__func__显示函数名
__TIME__显示时间
__DATA__显示日期
2、条件编译
#if 0 #endif
#if 1 #endif
#ifdef和 #ifndef:防止头文件包含带来的重复定义
比如:a.h
#ifndef A_H
#define A_H
...
#endif
3、嵌入式中的死循环
(1)while(1)
{
}
(2)for(;;)
{
}
break :结束所有循环
continue:结束当次循环