资源简介 英雄:德玛西亚 防御:80 攻击:60英雄:盲僧 防御:90 攻击:80英雄:小法 防御:40 攻击:85英雄:小炮 防御:50 攻击:90(共34张PPT)预处理、二进制的概述和操作212345C语言编译过程#include#define条件编译二进制的概述和操作C语言编译过程gcc -E hello.c -o hello.i 1、预处理gcc -S hello.i –o hello.s 2、编译gcc -c hello.s -o hello.o 3、汇编gcc hello.o -o hello_elf 4、链接3C语言编译过程预编译将.c 中的头文件展开、宏展开生成的文件是.i文件编译将预处理之后的.i 文件生成 .s 汇编文件汇编将.s汇编文件生成.o 目标文件链接将.o 文件链接成目标文件4预处理的基本概念C语言对源程序处理的四个步骤预处理、编译、汇编、链接预处理1. 预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。2. 这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作。5预编译命令预处理C编译器提供的预处理功能主要有以下四种:1)文件包含 #include2)宏定义 #define3)条件编译 #if #endif ..4)一些特殊作用的预定义宏6文件包含处理文件包含处理“文件包含处理”是指一个源文件可以将另外一个文件的全部内容包含进来C语言提供了#include命令用来实现“文件包含”的操作7文件包含处理#include < > 与 #include ""1.""表示系统先在file1.c所在的当前目录找file1.h如果找不到,再按系统指定的目录检索.2.< >表示系统直接按系统指定的目录检索.8文件包含处理注意:1.#include <>常用于包含库函数的头文件2.#include ""常用于包含自定义的头文件3.理论上#include可以包含任意格式的文件(.c .h等)但我们一般用于头文件的包含9文件包含处理案例:10main.c:fun.h编译: gcc main.c fun.c –o mainfun.c宏定义宏定义在源程序中,允许一个标识符(宏名)来表示一个语言符号字符串用指定的符号代替指定的信息11宏定义分类:在C语言中,“宏”分为:无参数的宏和有参数的宏12宏定义1.无参数的宏定义#define 宏名 字符串例:#define PI 3.141926在编译预处理时,将程序中在该语句以后出现的所有的PI都用3.1415926代替这种方法使用户能以一个简单的名字代替一个长的字符串在预编译时将宏名替换成字符串的过程称为“宏展开”宏定义,只在宏定义的文件中起作用13宏定义案例1:14宏定义案例2:15宏定义说明:1)宏名一般用大写,以便于与变量区别.2)字符串可以是常数、表达式等.3)宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错16宏定义说明:4)宏定义不是C语言,不在行末加分号.5)宏名有效范围为从定义到本源文件结束.6)可以用#undef命令终止宏定义的作用域.7)在宏定义中,可以引用已定义的宏名.17宏定义2.带参数的宏定义1)格式:#define 宏名(形参表) 字符串2)调用:宏名(形参表)3)宏展开:进行宏替换18宏定义案例1:19宏定义案例1:20宏定义说明:用3和2分别代替宏定义中的形式参数a和 b,用3*2代替S(3,2).因此赋值语句展开为:Area = 3*2;21宏定义使用带参的宏定义最好加上括号(避免宏的副作用)22案例:条件编译条件编译一般情况下,源程序中所有的行都参加编译.但有时希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件.23条件编译测试存在:24#ifdef 标识符程序段1#else程序段2#endif条件编译25#ifndef 标识符程序段1#else程序段2#endif测试不存在:根据表达式定义:#if 标识符程序段1#else程序段2#endif条件编译条件编译的作用1、防止头文件被重复包含引用#ifndef __LCD_H__#define __LCD_H__需要声明的变量、函数宏定义结构体#endif2、软件裁剪同样的C源代码,条件选项不同可以编译出不同的可执行程序26二进制的概述和操作27二进制(binary)的概念在数学和数字电路中指以2为基数的记数系统,以2为基数代表系统是二进位制的。这一系统中,通常用两个不同的符号0(代表零)和1(代表一)来表示二进制的概述和操作28二进制的原码、反码、补码原码:是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值反码:正数的反码还是等于原码,负数的反码就是他的原码除符号位外,按位取反补码:正数的补码等于他的原码,负数的补码等于反码+1二进制的概述和操作29unsigned char a = 100;存储时:原码:0110 0100反码:0110 0100补码:0110 0100取出时:补码:0110 0100反码:0110 0100原码:0110 0100 = 100unsigned char a = -1;存储时:原码:1000 0001反码:1111 1110补码:1111 1111取出时:补码:1111 1111反码:1111 1111原码:1111 1111 = 255二进制的概述和操作30char a = -5;存储时:原码:1000 0101反码:1111 1010补码:1111 1011取出时:补码:1111 1011反码:1111 1010原码:1000 0101 = -5char a = 129;存储时:原码:1000 0001反码:1000 0001补码:1000 0001取出时:补码:1000 0001反码:1000 0000原码:1111 1111 = -127二进制的概述和操作31二进制的位移左移 <<右移 >>注意右移分:逻辑右移、算数右移二进制的概述和操作32右移逻辑右移 高位补0,低位溢出算数右移 高位补符号位,低位溢出 (有符号数)逻辑右移:低位溢出、高位补00101 1010 >>3 0000 1011算数右移:对有符号数来说低位溢出、高位补符号位1010 1101 >> 3 1111 010 10101 0011 >>3 0000 101 0二进制的概述和操作33左移左移<< 高位溢出,低位补05<<1THANK YOU知识点1【写代码的过程】编辑器:程序员写代码的过程(记事本、vc6.0、vim)(让程序员看懂)编译器:查看代码的语法错误,生成汇编语言。汇编器:将生成好汇编语言 生成 二进制语言(目标文件)连接器:将生成好 二进制语言+用到的库+启动代码 ==>可执行文件知识点2【完整的c代码分析】1、案例1:hello iot1 //行注释:2 #include//std 标准 i输入 o输出(标准的输入输出头文件)34 /* 块注释 不能嵌套5 main 是程序的入口 有且仅有一个6 main左边的int 代表的是函数的返回值类型7 ()里面函数的形参(函数外部将数据传递到函数内部的桥梁)8 */9 int main(int argc,char *argv[])10 {//函数的功能都在{}里面实现11 //使用任何东西 必先存在12 //printf:将""中的字符串 输出到 终端上13 printf("hello iot\n");//来至系统库文件14 //;c语言的语句结束标记1516 //如果你的代码 一闪而过 可以用带阻塞的代码17 getchar();18 return 0;19 }20 //int char return 都是后面要讲的关键字总结:1、main有且只有一个2、printf的头文件 是stdio.h3、注释 行注释 块注释(不嵌套)案例2:求圆的面积知道的条件:半径r算法:面积= π*r*r解析步骤:1、r通过 键盘输入 或者 特定的值2、定义一个面积变量 area = π*r*r3、将圆的面积输出到终端上1 #include2 //定义一个宏 建议大写 和普通变量区分开3 #define PI 3.1445 int main(int argc,char *argv[])6 {7 float r=0.0f;//定义一个r变量 系统给r开辟4字节空间8 float area = 0.0f;//定义一个面积变量910 //获得半径 从键盘获得scanf11 printf("请输入圆的半径r:");12 scanf("%f", &r);//带阻塞1314 //算法:计算面积area = 3.14 * r * r15 area = PI*r*r;1617 //将面积输出%.2f中的.2表示小数部分保留两位18 printf("area = %.2f\n",area);1920 return 0;21 }运行结果:案例:用分函数的方式 求两个数的和步骤分析:1、定义两个变量data1 data2 获取键盘输入2、定义一个函数 去 计算上面两个变量data1 data2的和在 函数的内部 计算(怎么将数据 传递 到函数内部呢?)需要用形参 将data1 data2传递到 函数内部3、得到函数内部的计算结果(怎么得到呢?)通过函数的返回值 得到函数的计算结果1 #include23 int add_fun(int a,int b)4 {5 return a+b;6 }78 int main(int argc,char *argv[])9 {10 //定义两个变量 获取键盘输入11 //int data1 = 0;12 //int data2 = 0;13 int data1 = 0, data2 = 0;14 int ret = 0;//存放函数的结果1516 printf("请输入两个int变量:");17 scanf("%d %d",&data1,&data2);1819 //函数的调用20 ret = add_fun(data1,data2);//a = data1 b = data22122 //输出ret的结果23 printf("ret = %d\n",ret);2425 return 0;26 }结果:知识点1【内存的分区】(了解)知识点2【普通局部变量、普通全局变量、静态局部变量、静态全局变量】1、普通局部变量2、普通全局变量3、静态局部变量4、静态全局变量知识点3【全局函数(普通函数) 和 静态函数(局部函数)】1、全局函数:普通函数2、静态函数(局部函数)知识点4【gcc编译过程】(了解)知识点5【头文件包含<>,""】(了解)知识点6【define 宏】、1、不带参数的宏2、带参数的宏 (宏 函数)3、带参数的宏(宏函数) 和 普通函数 有啥区别知识点7【条件编译】案例1:测试不存在案例2:测试存在案例3:判断表达式综合案例:通过条件编译 控制大小写的转换知识点1【内存的分区】(了解)知识点2【普通局部变量、普通全局变量、静态局部变量、静态全局变量】1、普通局部变量定义形式:在{}里面定义的普通变量 就是普通局部变量。作用范围:离它最近的{}之间有效生命周期:离它最近的{}之间有效,离开{}的局部变量 系统自动回收存储区域:栈区注意事项:a、普通局部变量不初始化 内容不确定b、普通局部变量 同名 就近原则1 void test01()2 {3 //局部变量 同名 就近原则4 int data = 100;5 {6 int data = 200;7 printf("A:data = %d\n",data);//2008 }910 printf("B:data = %d\n",data);//10011 }2、普通全局变量定义形式:定义在函数外边的变量 就是普通全局变量1 int data;//普通全局变量 在函数外边定义2 void test02()3 {45 }作用范围:a、当前源文件 都有效1 #include2 extern int data;//声明一下 不要赋值3 void test01()4 {5 printf("test01 中 data = %d\n",data);//1006 }78 int data=100;//普通全局变量 在函数外边定义910 void test02()11 {12 printf("test02 中 data = %d\n",data);//10013 }14 int main(int argc,char *argv[])15 {16 printf("main 中 data = %d\n",data);//10017 test01();18 test02();1920 }b、其他源文件使用全局变量时 必须加extern声明。//extern 本质:告诉编译器 变量/函数 来至其他源文件,请通过编译代码:main.c1 #include2 extern void my_printf(void);3 extern int data;//声明4 int main(int argc,char *argv[])5 {6 printf("main 中 data = %d\n",data);//10078 my_printf();910 }11 int data = 100;fun.c1 #include2 extern int data;//声明data来至其他源文件3 void my_printf(void)4 {5 printf("在fun.c中 data = %d\n",data);6 }运行结果:生命周期:整个进程 都有效(程序结束的时候 全局变量 才被释放)存储区域:全局区注意事项a、全局变量 不初始化 内容为0。b、如果全局变量 要在其他源文件中使用 必须在所使用的源文件中加extern声明。c、如果全局变量 和 局部变量 同名 在{}中优先使用局部变量3、静态局部变量定义形式:在{}中定义 前面 必须加static 修饰 这样的变量 叫 静态局部变量。1 void test01()2 {3 static int num;//静态局部变量4 return;5 }作用范围:离它最近的{}之间有效。1 void test01()2 {3 {4 static int num;//静态局部变量5 }6 //说明 静态局部变量 只在离它最近的{}有效7 printf("num = %d\n",num);//err 不识别num8 return;9 }生命周期:整个进程 ,(程序结束的时候 静态局部变量 才被释放)-----------------案例:普通局部变量----------------------1 #include2 void fun1(void)3 {4 int num = 10;//普通的局部变量5 num++;67 printf("num = %d\n",num);8 return;9 }10 int main(int argc,char *argv[])11 {12 fun1();//1113 fun1();//1114 fun1();//1115 fun1();//1116 return 0;17 }-----------案例:静态局部变量--------------------------1 #include2 void fun1(void)3 {4 //静态局部变量 只能被初始化一次5 //静态局部变量 生命周期 是整个进程6 static int num = 10;//静态局部变量7 num++;8 printf("num = %d\n",num);9 return;10 }11 int main(int argc,char *argv[])12 {13 fun1();//1114 fun1();//1215 fun1();//1316 fun1();//1417 return 0;18 }存储区域:全局区注意事项:a、静态局部变量 不初始化 内容为0.(全局区)b、只能被定义一次(重要)4、静态全局变量定义形式:在函数外边定义 同时加static 这样的变量就是 静态全局变量1 #include2 static int data = 10;//静态全局变量3 int main(int argc,char *argv[])4 {56 return 0;7 }作用范围:当前源文件 有效 不能在其他源文件中使用。生命周期:整个进程,(程序结束 静态全局变量才被释放)存储区域:全局区注意事项:1、静态全局变量 不初始化 内容为02、静态全局变量 只在当前源文件 有效知识点3【全局函数(普通函数) 和 静态函数(局部函数)】1、全局函数:普通函数 1 void my_fun(void)2 {3 printf("(全局函数)普通函数\n");4 return;5 }特点:其他源文件 可以使用 全局函数,必须加extern 声明2、静态函数(局部函数)1 static void my_static_fun(void)2 {3 printf("(静态函数)局部函数\n");4 return;5 }特点:只能在当前源文件使用 不能在其他源文件使用。注意:如果想在其他源文件 调用 静态函数 需要将静态函数 封装在 全局函数中。同时全局函数 和静态函数 必须是同一个源文件。fun.c12 static void my_static_fun(void)3 {4 printf("(静态函数)局部函数\n");5 return;6 }78 void my_fun(void)9 {10 printf("(全局函数)普通函数\n");11 my_static_fun();12 return;13 }main.c1 #include2 extern void my_fun(void);3 //static void my_static_fun(void);4 int main(int argc,char *argv[])5 {6 my_fun();7 //my_static_fun();8 return 0;9 }案例:知识点4【gcc编译过程】(了解)1 #include预处理:头文件包含、宏替换、条件编译、删除注释 不做语法检查编译:将预处理后的文件 生成 汇编文件 语法检查汇编:将汇编文件 编译 二进制文件链接:将众多的二进制文件+库+启动代码 生成 可执行文件总结:一步到位 编译: gcc 源文件 -o 可执行文件知识点5【头文件包含<>,""】(了解)#include 表示从系统的指定目录下寻找hehe.h#include "hehe.h" 表示 先从源文件 所在的目录寻找 如果找不到 再到系统指定的目录下找。my_fun.h1 #define PI 3.14fmain.c1 #include//用于包含系统的头文件2 #include"my_fun.h"//用户包含 用户自定义的 头文件34 int main(int argc,char *argv[])5 {6 printf("PI = %f\n",PI);7 return 0;8 }知识点6【define 宏】、宏只在当前源文件有效宏 一般为 大写。(将宏 和 普通变量 区分开)1、不带参数的宏1 #include23 //宏 后面不要加;4 #define N "hehe"5 void test01()6 {7 //在预处理阶段 "hehe" 替换 代码中所有出现的N (宏展开)8 printf("%s\n",N);9 return;10 }11 int main(int argc,char *argv[])12 {13 test01();14 return 0;15 }终止宏 的作用范围: #undef 宏名1 #include23 //宏 后面不要加;4 #define N "hehe"5 void test01()6 {7 printf("%s\n",N);//OK 识别的89 //使用#undef N终止 N的作用10 #undef N1112 //printf("%s\n",N);//err 不识别N1314 return;15 }16 int main(int argc,char *argv[])17 {18 test01();19 return 0;20 }2、带参数的宏 (宏 函数)#define 宏名(参数1,参数2,...) 字符串1 //宏的参数 a b 不能写类型2 //#define MY_ADD(int a, int b) a+b //错误3 #define MY_ADD(a,b) a+b45 //调用 宏名(参数)6 MY_ADD(10,20);// 10+20案例1:1 //宏展开 本质 就是替换2 #define MY_MUL1(a,b) a*b3 #define MY_MUL2(a,b) ((a)*(b))4 void test01()5 {6 printf("%d\n", MY_MUL1(10,20));//MY_MUL1(10,20) == 10*2078 //MY_MUL1( 10+10, 20+20 )==10+10*20+209 printf("%d\n", MY_MUL1(10+10,20+20));//230 不能保证完整性1011 //MY_MUL2(10+10,20+20) == ((10+10)*(20+20))12 printf("%d\n", MY_MUL2(10+10,20+20));//800 保证完整性1314 return;15 }16 int main(int argc,char *argv[])17 {18 test01();19 return 0;20 }3、带参数的宏(宏函数) 和 普通函数 有啥区别带参数的宏(宏函数) 调用多少次 就会展开多少次,执行代码的时候 没有函数调用的过程,也不需要函数的出入栈,所以带参数的宏 浪费空间 节省了时间。代参的函数:代码只有一份,存在代码段, 调用的时候去代码段读取函数指令,调用的时候 要压栈(保存调用函数前的相关信息),调用完出栈(恢复调用函数前的相关信息),所以函数浪费了时间 节省空间。1 #include23 //宏展开 本质 就是替换4 //宏函数5 #define MY_MUL1(a,b) a*b6 #define MY_MUL2(a,b) ((a)*(b))78 //普通函数9 int my_mul(int a,int b)//a=10+10=20 b=20+20=4010 {11 printf("a = %d\n",a);//2012 printf("b = %d\n",b);//401314 return a*b;15 }16 void test01()17 {18 //预处理阶段完成 宏的替换19 printf("%d\n", MY_MUL1(10,20));//MY_MUL1(10,20) == 10*2020 printf("%d\n", MY_MUL1(10+10,20+20));//10+10*20+20=230 不能保证完整性21 printf("%d\n", MY_MUL2(10+10,20+20));//((10+10)*(20+20))=800 保证完整性2223 //执行24 printf("%d\n", my_mul(10+10, 20+20));//2526 return;27 }28 int main(int argc,char *argv[])29 {30 test01();31 return 0;32 }案例:1 #define MY_ADD(a,b) a+b2 #define MY_MUL1(a,b) a*b34 printf("%d\n",MY_MUL( MY_ADD(10+10, 20+20), MY_MUL(10+10,20+20) ) );知识点7【条件编译】案例1:测试不存在运行结果:案例2:测试存在运行结果:案例3:判断表达式综合案例:通过条件编译 控制大小写的转换1 #include23 int main()4 {5 char buf[128]="";6 int i=0;7 printf("请输入字符串:");8 //fgets 会获取 换行符 '\n'9 fgets(buf,sizeof(buf), stdin);10 //去掉换行符 strlen返回的是字符串是长度 不包含'\0'11 //strlen(buf)‐1 这是 换行符 的下标位置12 buf[strlen(buf)‐1] = 0;1314 //while(buf[i] != '\0')1516 //char buf[128];17 //buf[i]是取数组中的第i个元素的值。1819 while(buf[i])//最后一个元素是'\0' == 0==假 循环进不去20 {21 #if 122 if(buf[i]>='A' && buf[i]<='Z')23 buf[i] = buf[i]+32;24 #else25 if(buf[i]>='a' && buf[i]<='z')26 buf[i] = buf[i]‐32;27 #endif28 i++;29 }303132 printf("buf = %s\n",buf);33 return 0;34 }德玛西亚 P <