目录
一、预处理
预处理:
编程流程:
1.编辑
2.编译 gcc main.c 生成可执行文件a.out
3.运行
4.调试
编译过程:
1.预处理
//预编译 -- 将 代码中相关 预处理命令执行 最终生产一个 只包含c语言代码的文件
指令:gcc -E file.c hello.i
2.编译
//编译 -- 对语法进行检查,将这个c的源代码 生产 汇编代码
指令:gcc -S file.c -o hello.s
3.汇编
//汇编 -- 表示将 汇编源代码 最终生成 机器代码 //object
指令:gcc -c file.s -o hello.o
4.链接
//链接 -- 将使用到的其它代码了链接到一起 生成 最终可执行文件
1.宏定义
语法:
#define 宏名 宏值
注意:
1. 预处理命令 都是以 # 开头的
2. 宏名 命名规则 和 之前标识符命名规则一致
注:
宏名一般都写大写 ,以区别与普通变量名
3. 预处理 实际上 是将 宏名 用 宏值(预处理阶段的 字符串) 原样替换 //文本替换
注意:
c语言字符串中出现的 "宏名" 不会被替换,如下列代码中的printf引号中的N就不会被替换成10
如:
#include <stdio.h>
#define N 10
int main(void)
{
printf("N=&d\n",N);
return 0;
}
4. 应用
a. 提高代码可读性
b. 一改全改,方便代码修改
5. 宏名的 作用域
#undef 宏名
表示 取消 宏名的 定义
注意:
只是在预处理阶段发挥作用。
作用范围:
从定义处开始,到 #undef 结束
编写代码时,可以使用宏定义,给一个宏名表示数字
#include <stdio.h>
#define ARRAY_SIZE 10
int main(void)
{
int a[ARRAY_SIZE]={1,2,3,4,5,6,7,8,9,10};
int i=0;
for(i=0;i<ARRAY_SIZE;++i)
{
printf("%d ",a[i]);
}
putchar('\n');
return 0;
}
注意宏定义的时候,后面不要加分号,否则会报错
我们可以从.i文件里看出它为什么会出错
可以看出,如果宏定义时带分号,文本原样替换时就会把分号带上
带参宏定义:
语法:
#define 宏名(参数) 宏值
eg:
#define ADD(a,b) a+b
#include <stdio.h>
#define ADD(a,b) a+b
#define MUT(a,b) a*b
#define JIAN(a,b) a-b
#define CHU(a,b) a/b
int main(void)
{
int x=8;
int y=2;
printf("+ %d\n",ADD(x,y));
printf("* %d\n",MUT(x,y));
printf("- %d\n",JIAN(x,y));
printf("/ %d\n",CHU(x,y));
return 0;
}
注意:
1.带参宏 和 函数有本质的区别
a.处理阶段不一样
宏定义 --预处理阶段
函数 --编译阶段
b.使用不一样
宏 -- 预处理阶段 就使用结束了
宏的使用,本质上,是文本的原样替换
参数
宏的参数,只是进行 文本替换用,不进行语法检查
函数 -- 调用时,进行使用
函数的使用,本质上是函数代码的调用
参数
函数的参数,是有类型的,编译阶段是要进行类型检查的
c.应用
一般对于一些短小代码 ,考虑写成带参宏
不超过5行的代码
d.宏的副作用
如:
#include <stdio.h>
#define MUT(a,b) a*b
int main(void)
{
printf("* %d\n",MUT(1+2,3+4));
return 0;
}
我们的本意原来是计算表达式之后,两个数相乘,应该为3*7=21,但是宏定义实际上只是文本原样替换,所以会变成1+2*3+4=11,这就是宏的副作用。
处理:
会变的都加括号,整体也加括号。
如下列处理
#include <stdio.h>
#define MUT(a,b) ((a)*(b))
int main(void)
{
printf("* %d\n",MUT(1+2,3+4));
return 0;
}
这样计算就是:((1+2)*(3+4))=21
e.注意
宏定义 必须 写在同一行
如果要换行,需要每行最后加上续行符\,并且续行符后面不能有空格
注意:在用宏定义时后面不能加;但是如果在宏定义出写do while(0),就可以加分号
如18行位置处,就可以加分号
2.文件包含
#include <文件名>
#include "文件名"
<> 与 ""
区别:
在于,查找头文件的方式不一样
<> //到系统默认的路径寻找对应的头文件
"" //表示先到当下目录下寻找头文件,如没有,再到系统默认路径下寻找
3.条件编译
(1).
#ifdef 标识符
程序段 1
#else
程序段 2
#endif
含义:
如果 定义了 标识符
则 预处理程序段1 //就是将程序段1的代码 保留
否则
保留程序段2
用途:
//1.调试代码
//2.设计头文件
(2).
#ifndef 标识符
程序段1
#else
程序段2
#endif
含义:
如果 没有定义了 标识符
则 预处理 程序段1 //就是将程序段1的代码 保留
否则
处理 程序段2
(3).
#if 表达式
程序段1
#else
程序段2
#endif
含义:
表达式为真 处理程序段1 表达式为假 处理程度段2
eg:
#if 0
...
#endif
#ifndef __DAYS_H
#define __DAYS_H
int days(int year,int mouth);
#endif
#ifndef防止多次包含,带来重复定义的问题
二、指针
指针概念:
地址 内存单元的编号
指针 就是 地址
指针 也是一种数据类型
指针类型
这种数据类型 是专门用来处理 地址 这种数据
1.指针变量定义
基类型 * 指针变量名
(1)基类型
//数据类型
整型
浮点型
字符型
数组类型
指针类型
....
//结构体类型 ,函数类型
作用:
表示该指针类型 所指向的内存空间 存放什么类型的数据
(2).*
//定义时,表示此时定义的是一个 指针类型 的变量
(3).指针变量名
//符合标识符命名规则
int * p; //pointer
int a = 10; //a所在的空间是用来存放 int(整型)类型的数据的
&a //表示获得a所在空间的首地址
//表示 获得了一块 可以存在int型数据的内存空间的地址
2 指针类型
int *p; //int * ---整体叫指针类型
数据类型 变量名;
int* p;
//int* 含义 首先表示是一个 指针类型
//表示指向int型数据的指针类型
3. 指针变量的引用
int a = 10;
int *p = &a; //p指向a ---因为p中保存了a的地址
* //指针运算符
//单目运算
//运算对象 --- 只能指针(地址)
//通过a可以修改这块地址内的值,也就是通过a访问的 -- 直接访问
*p //表示访问 p所指向的 基类型的 内存空间,通过*p是间接访问
step1: 首先拿出p中地址,到内存中定位
step2: 偏移出sizeof(基类型)大小的一块空间
step3: 将偏移出的这块空间,当做一个基类型变量来看 //*p运算完的效果
*p //运算效果 相当于就是一个基类型的变量
可以认为*p与a等价
int a; //
a 的数据类型 int
&a 的数据类型 int* //地址这种数据---对应到一种数据类型 --指针类型
//
float b;
&b //float*
#include <stdio.h>
int main(void)
{
int a=0x12345678;
short *p=(short*)&a;
printf("a=%#x\n",a);
printf("*p=%#x\n",*p);
return 0;
}
注意类型匹配,或者进行强制转换,如第6行,因为a是个int型,4个字节,而short是2个字节,所以需要强转,电脑是小端存储的,所以*p=0x5678