宏定义
宏有两种定义方法:一般定义在代码开头,有无参数宏定义和有参数宏定义。接下来我们依次讲解
无参数宏定义
定义形式:#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,因而可以用此方法算文件大小
作业
01.构造一个书籍信息结构体,并且使用结构体数组声明100个元素,编写增删改查4个函数
一个建议的图书信息管理系统
#include<stdio.h>
#include<stdbool.h>
#include<string.h>
struct BookInof
{
int nFalg;
char szName[100];
int nNumber;
int nPrice;
};
struct BookInof Book[100];
bool add(int nFalg, char* szName,int nNumber,int nPrice)
{
for (int i = 0; i <= 100; i++)
{
if (Book[i].nFalg == 0)
{
strcpy(Book[i].szName, szName);
Book[i].nNumber = nNumber;
Book[i].nPrice = nPrice;
Book[i].nFalg = 1;
printf("添加的书本的名字是%s\r\n", Book[i].szName);
printf("添加的书本的书号时%d\r\n", Book[i].nNumber);
printf("书本的价格是%d\r\n", Book[i].nPrice);
return true;
}
return false;
}
}
bool del(int nNumber)
{
char szstr[50] = { 0 };
for (int i = 0; i <= 100; i++)
{
if (Book[i].nNumber == nNumber)
{
if (Book[i].nFalg == 0)
{
printf("没有此书号的信息请重新输入\r\n");
return false;
}
else
{
Book[i].nFalg = 0;
Book[i].nNumber = 0;
for (int x = 0; x <= strlen(Book[i].szName); x++)
{
Book[i].szName[x] = szstr[x];
}
printf("删除成功\r\n");
return true;
}
}
}
return false;
}
bool mod(int nNumber)
{
int nSwitch = 1;
for (int i = 0; i <= 100; i++)
{
if (Book[i].nNumber == nNumber)
{
if (Book[i].nFalg == 0)
{
printf("没有此书号的信息请重新输入\r\n");
return false;
}
else
{
rk:
printf("请输入你要修改的信息:一书名,二书号,三价格,四退出\r\n");
int function = 0;
scanf("%d", &function);
switch (function)
{
case 1:
{
int x = 0;
printf("请输入你要修改的书名\r\n");
char szName[30] = { 0 };
scanf("%s", szName);
for (x = 0; x <= 30; x++)
{
Book[i].szName[x] = szName[x];
}
Book[i].szName[x + 1] = '\0';
printf("修改成功\r\n");
goto rk;
}
case 2:
{
printf("请输入你要修改的书号\r\n");
int number = 0;
scanf("%d", &number);
Book[i].nNumber = number;
printf("修改成功\r\n");
goto rk;
}
case 3:
{
printf("请输入你要修改的价格\r\n");
int nprice = 0;
scanf("%d", &nprice);
Book[i].nPrice = nprice;
printf("修改成功\r\n");
goto rk;0
}
case 4:
{
printf("退出功能\r\n");
nSwitch = 0;
return true;
}
default:
{
printf("功能输入错误,请重新输入");
goto rk;
}
}
}
}
}
return false;
}
bool query(int Number)
{
for (int i = 0; i <= 100; i++)
{
if (Book[i].nNumber == Number)
{
if (Book[i].nFalg == 0)
{
printf("没有此书号的信息\r\n");
return false;
}
else
{
printf("查询的书本信息如下:\r\n");
printf("查询的书本书名为:%s\r\n", Book[i].szName);
printf("查询的书本的书号是%d\r\n", Book[i].nNumber);
printf("查询的书本价格:%d\r\n", Book[i].nPrice);
return true;
}
}
}
return false;
}
int main()
{
struct BookInof Book[100];
int nFunction = 0;
int nFalg = 0;
char szName[50] = {0};
int nNumber = 0;
int nPrice = 0;
int nSwitch = 1;
while (nSwitch)
{
printf("请输入你要执行的功能\r\n");
printf("一,增加图书信息\r\n");
printf("二,删除图书信息\r\n");
printf("三,修改图书信息\r\n");
printf("四,查询图书信息\r\n");
printf("五,退出功能\r\n");
scanf("%d", &nFunction);
switch (nFunction)
{
case 1:
{
printf("欢迎进入增加图书信息功能\r\n");
printf("请输入你要添加的图书的书名,书号,价格\r\n");
scanf("%s %d %d", szName, &nNumber, &nPrice);
add(nFalg, szName, nNumber, nPrice);
break;
}
case 2:
{
printf("欢迎进入删除图书信息功能");
printf("请输入你要删除的图书的书号\r\n");
scanf("%d", &nNumber);
del(nNumber);
break;
}
case 3:
{
printf("欢迎进入修改图书信息功能");
printf("请输入你要修改的图书的书号\r\n");
scanf("%d", &nNumber);
mod(nNumber);
break;
}
case 4:
{
printf("欢迎进入查询图书信息功能");
printf("请输入你要查询的图书的书号\r\n");
scanf("%d", &nNumber);
query(nNumber);
break;
}
case 5 :
{
nSwitch = 0;
printf("退出功能");
}
}
}
return 0;
}
2.将记事本的.exe文件读取到内存,并返回读取后在内存中的地址
思路:
1.打开要读取的文件,二进制读的形式
2.计算文件内容的长度(字节为单位)
3.动态申请内存,大小要够存放此文件内容
4.将文件内容写入申请的内存
5.返回申请内存的起始地址,即文件写入内存的地址
将内存中的数据存储到一个文件中,(.exe格式),然后双击打开,看是否能够使用
思路:
1.在第一题的基础上,只需要再打开一个文件,fopen如果发现没有此文件,则会新建
2.将内存中的数据写入到此文件中即可