C语言基础(六)复杂数据类型与文件操作

宏定义

宏有两种定义方法:一般定义在代码开头,有无参数宏定义和有参数宏定义。接下来我们依次讲解

无参数宏定义

定义形式:#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.将内存中的数据写入到此文件中即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值