宏定义
宏有两种定义方法:一般定义在代码开头,有无参数宏定义和有参数宏定义。接下来我们依次讲解
无参数宏定义
定义形式:#define 标识符 字符序列
举例:#define MAX_VALUE 256
当我们在写代码时使用标识符,当程序开始编译前,程序中出现的MAX_VALUE,会替换成256
注:
1.无参数宏定义只能作为字符序列的替换工作,不作任何语法的检查
2.如果宏定义不当,定义错误要到预处理(替换)之后的编译阶段才能被发现
带参数宏定义
定义形式:#define 标识符(参数表) 字符序列
举例:
#define ADD(a) a + 5
程序中出现ADD()时,空号中的参数代入后面的表达式中计算所得值返回
#define MAX(a,b) a > b ? a : b
程序中出现MAX(a,b)时如果a>b的结果为真,那么表达式的结果会是a,表达式的结果为假,那么
表达式的结果就会是b。
#define MAX(a,b) a > b ? a : b; printf("hello")
同上个宏定义一样,但是在执行完毕后,会额外打印hello
优点:宏定义相比使用函数来完成MAX的功能时,不用额外给MAX开辟一块内存,而是在编译前直接用字符序列代替MAX部分
注意:
1.定义宏的标识符与左圆括号之间不允许有空白符,应紧接在一起
2.宏与函数的区别:函数分配额外的堆栈空间,而宏只是替换
3.为了避免出错,宏定义中给形参加上括号
4.末尾不需要分号
5.define可以替代多行的代码,需要换行时在换行前的语句后面加\
举例:
#define MALLOC(n,type)\
((type*)malloc((n)*sizeof(type)))
条件编译
#if _WIN32 操作系统
int g_OS = 0
#elif __linux__
int g_OS = 1
#endif
#define FALG 1
#if FALG == 1
int nFalg = 1;
#else
int nFalg = 1561
常见预定义符号
__ FILE __ 进行编译的文件路径
__ LINE __ 此文件内当前行号
__ DATE __ 编译时日期
__ TIME __ 编译时时间
__ STDC __ 如果编译器遵循ANSI C ,值为1,否则未定义
这些预定义符号可以直接进行使用
如以下代码
#include <stdio.h>
int main()
{
printf("当前文件路径:%s\n",__FILE__);
printf("当前行号:%d\n",__LINE__);
}
结构体
结构体基本概念
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型
结构体定义和使用
定义方式: struct 结构体名 { 结构体成员列表 };
通过结构体创建变量的方式有三种:
1.struct 结构体名 变量名
举例如下:struct _PlayerInfo Playobj;
2.struct 结构体名 变量名 = { 成员1值 , 成员2值...}
struct _PlayerInfo
{
char szName[50];
int nHP;
float PosX;
float PosY;
};
3.定义结构体时顺便创建变量
struct _PlayerInfo
{
char szName[50];
int nHP;
float PosX;
float PosY;
}Playobj;
注意:
1:定义结构体时的关键字是struct,不可省略
2:创建结构体变量时,关键字struct可以省略
3:结构体变量利用操作符 ''.'' 访问成员
结构体起别名
typedef struct _PlayerInfo
{
char szName[50];
int nHP;
float PosX;
float PosY;
}PlayerInfo;
也可*PPlayerInfo用作后续程序用指针时的别名
此时PlayerInfo等于struct _PlayerInfo
其内存为其包含的所有成员所占内存总和决定
结构体数组
结构体数组就是将自定义的结构体放入到数组中
定义形式为:struct 结构体名 数组名[元素个数] = { {} , {} , ... {} }
如下一个程序进行讲解
此处先定义一个结构体:
struct student
{
string name;
int age;
int score;
}
此时在主程序中定义结构体数组
int main()
{
struct student arr[3]={{"张三",18,80 },{"李四",19,60 },{"王五",20,70 }
};
结构体指针
结构体指针就是通过指针访问结构体中的成员,利用操作符 -> 通过结构体指针访问结构体属性
如下一个例子进行讲解:
此时我们定义一个结构体:
struct student
{
string name;
int age;
int score;
};
此时我们开始在主程序中应用:
int main()
{
struct student stu = { "张三",18,100, };
struct student * p = &stu;
p->score = 80; 此时指针通过 -> 操作符可以访问成员
return 0;
}
结构体做函数参数
结构体做函数参数就是将结构体作为参数向函数中传递,传递方式有两种:
1.值传递
2.地址传递
下面举例讲解:
此时有一个已经定义好的结构体
struct student
{
int age;
int score;
};
此时有一个要用到结构体值传递的函数:
void printStudent1(student stu )
{
stu.age = 28; 此处如此调用结构体成员
printf("%d,%d",age,score);
}
此时有一个要用到结构体地址传递的函数:
void printStudent2(student *stu)
{
stu->age = 28; 此处如此调用结构体成员
printf("%d,%d",age,score);
}
此时我们要做主函数调用这两个函数了
int main()
{
student stu = {18,100};
printStudent1(stu);
printStudent2(&stu);
return 0;
};
注意:如果不想修改主函数中的数据,用值传递,反之用地址传递(详解可看函数章节)
结构体在程序中的使用
typedef struct _PlayerInfo
{
char szName[50];
int nHP;
float PosX;
float PosY;
}PlayerInfo;
int main()
{
struct _PlayerInfo Playobj = { "rkvir",100,50.32f,100.09f };定义一个结构体
printf("%s\r\n",Playobj.szName) 打印结构体的属性
Playobj.nHP = 500; 在结构体外可修改属性
struct _PlayerInfo* pPlayobj; 定义一个结构体指针
pPlayobj = &Playobj; 此时该指针指向结构体
pPlayobj->PosX = 200.05f; 此时利用指针访问属性不再用.而是-> 这叫间接访问
printf("%f\r\n",pPlayobj->Posx); 以下两个利用指针访问结构体属性的方式
printf("%f\r\n",(&Playobj)->Posx);
struct _PlayerInfo* pPlayobj2 = malloc(sizeof( struct _PlayerInfo));
利用动态申请内存malloc函数申请一个同原结构体大小的内存空间并创建一个结构体,malloc应
用时需要包含头文件malloc.h
sizeof( struct _PlayerInfo)申请内存的长度
struct _PlayerInfo的长度就是他所包含的属性长度综合
malloc应用时需要包含头文件malloc.h
pPlayobj2->PosY = 100.5f;
printf("%f\r\n", pPlayobj2->PosY);
}
联合体
联合体的优点是在同一份的内存空间中定义多个不同变量。比如我们设置两个局部变量,int类型
0x12345678和char类型0x78,当我们单独定义时,他们都需要8字节的空间。但当我们使用联合
体时,就只需要分配int类型的0x12345678的数据内存大小,但该联合体却可以存储了int和char两
个数据类型变量
联合体的有两种定义方式:
方式一:
union MyUnion
{
char szName[50];
int nHP;
float PosX;
float PosY;
};
方式二:
union
{
char szName[50];
int nHP;
float PosX;
float PosY;
}MyUnion;
方式一中的MyUnion表示一种联合体的数据类型
方式二中的MyUnion表示此联合体类型的变量
联合体的内存长度由最长成员所占内存决定,每当使用下一个成员时,上一个所使用的成员所占内
存会被覆盖,这是因为联合体的成员是共享内存空间的
枚举类型
enum MyEnum
{
zero, 打印0
one = 10 打印10
two, 打印11
three = 12138 打印12138
};
struct
{
int a : 8;
int b : 8;
int c : 8;
int d : 8;
}rk; 命名结构体rk
rk.a = 0x78;
rk.b = 0x56;
rk.c = 0x34;
rk.d = 0x12;
printf("0x%X\r\n",rk)
打印结果0x12345678
作用:按照八位来拆分int
a b c d 作为索引
每八位都可以触发每个索引对应的一个功能
此时int不再代表一个数,而是作为功能开关
注:每个int都有32位,在极限情况下,可以有32位分别对应32个功能
例如
if (rk.a == 1)
{
开始影子列表
}
else if(rk.b == 1)
{
开启拓展页表
}
文件
文件分为二进制文件和文本文件,其具体结构如下:
typedef struct
{
short level; 缓冲区“满”或“空”的程度
unsigned flags 文件状态标志
char fd; 文件描述符
unsigned char hold; 如缓冲区无内容不读取字符
short bsize; 缓冲区的大小
unsigned char*buffer; 数据缓冲区的位置
unsigned char*curp; 文件位置标记指针当前的指向
unsigned istemp; 临时文件指示器
short token; 用于有效性检查
}FILE,*FILE;
文件操作流程
C语言来操作文件流程:
1.将文件以数据流的形式打开,这是因为一个程序与数据的交互是以流的形式进行的
2.将文件数据读入内存,可以通指向文件数据的文件指针对文件中的数据进行操作
3.关闭数据流
文件操作的函数
fopen()
fopen():该函数用于打开函数:如果正常打开,则返回被打开文件的文件指针;如果打开异常,
则返回NULL
格式:FILE* fp = fopen(char *filename, *type);
char *filename:文件名
文件名有两种填写方式:
1.文件的绝对地址,即文件的具体路径,格式为:D:\\father\\son.exe,或者D:/father/son.exe;
2.文件的相对地址,即相对于当前文件的位置去查找我们要应用的文件地址
*type:文件打开模式
打开模式有以下几种:
r:只能从文件中读数据,该文件必须先存在,否则打开失败
w:只能向文件写数据,若指定的文件不存在则创建它,如果存在则先删除它再重建一个新文件
a:向文件增加新数据(不删除原数据),若文件不存在则打开失败,打开时位置指针移到文件末尾
rb、wb、ab:表示以二进制形式打开的文件
举例:
FILE* fp = fopen("e:\zjj\a.txt","r"); 此处是以文件的绝对地址作为文件名,读的方式打开文件
fclose()
fclose():该函数用于关闭文件:如果正常关闭则返回0;如果异常则返回EOF
格式:int n = fclose(fp);
fp表示一个已经被打开文件的文件指针
举例:
FILE* fp = fopen("d:\a.txt","w");
int n = fclose(fp);
fread()
fread():二进制文件读函数,但它也可以操作文本文件。用于将文件中数据读取到缓冲区
格式:size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
void *buffer :用于接收读取数据的缓冲区的指针
size_t size :要读取的数据的单位大小
size_t count:要读取的数据的单元个数
FILE *stream:要读取的文件的指针
使用该函数后,可以在缓冲区中观察到读取的文件的数据
fwrite()
fwrite():二进制文件写函数,但它也可以操作文本文件。用于将缓冲区数据写入文件中
格式:fwrite(void *buffer, size_t size, size_t count , FILE *stream)
void *buffer :用于存储要写入文件数据的缓冲区的指针
size_t size :要写入的数据的单位大小
size_t count:要写入的数据的单元个数
FILE *stream:被写入数据的文件的指针
返回值:实际写入数据的基本单元个数
使用该函数后,打开被写入文件,可以发现写入文件的数据
fseek()
fseek():定位指针在文件中位置函数,定位成功,返回0,否则返回其他值。
格式:int i = fseek(FILE *stream, long offset, int origin);
stream:要操作的文件指针
offset:指针的偏移量,整数表示正向偏移,负数表示负向偏移
origin:表示指针从文件的哪里开始偏移,可能取值为:SEEK_CUR,SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头,值为0
SEEK_CUR: 当前位置,值为1
SEEK_END: 文件结尾,值为2
举例:int i = fseek(fp,sizeof(char)*2,0); 指针指向从文件开头向后偏移2字节
ftell()
ftell():获取当前指针位置相对于文件首地址的偏移字节数 ,当指针在起始位置时值为1
该函数一般用在fseek函数后面,用于确定指针移动后的位置
格式:long或int lenth = ftell(FILE *stream);
FILE *stream:操作的文件的指针
举例:
FILE* fp = fopen("d:\a.txt","rb"); 打开a.txt文件,假设文件大小3字节
int start = ftell(fp); 确定指针最开始指向的位置,即文件初始位置,此处为1
fseek(fp,0,2); 将指针移到文件尾
int size = ftell(fp); 获取此时指针的位置为3,而不是2,因而可以用此方法算文件大小