达内C语言基础DAY13

这篇博客详细介绍了C语言中的内存管理,包括结构体、联合体和枚举的使用,以及函数指针和二级指针的概念。重点讲解了malloc和free函数动态分配和释放内存,强调了它们在内存分配上的灵活性。此外,还涵盖了文件操作的基础,如fopen、fclose、fread、fwrite等函数的使用,以及文件指针的定位操作。内容深入浅出,适合C语言初学者巩固基础知识。
摘要由CSDN通过智能技术生成

回顾:

1. C语言的三大复合类型(程序员自定义的数据类型)

1.1 结构体

可以包含不同数据类型的变量

typedef struct A {
	int a;
	int b;
	char c;
	short d;
} A_t;
typedef struct A A_t;
struct A aa;
A_t aa;
A_t *p = &aa;
初始化两种方式
通过变量访问成员:aa.a,aa.b
通过指针访问成员:p->a, p->b
结构体和数组
结构体和函数
结构体和函数指针

1.2 联合体

union

跟结构体一样

共有一块内存

1.3 枚举

enum

给一个数字取别名,提高代码的可读性

掌握经典代码

利用#define替换

2. 函数指针

函数名是函数的地址

本质就是一种数据类型,当成int类型

将来利用函数指针定义函数指针变量保存一个函数地址,也就是让这个函数指针变量指向函数

声明语法:

typedef 返回值数据类型 (*别名)(形参表);//把别名当int

回调函数:

把函数当成参数,传给别的函数调用

3. 二级指针

数据类型 **二级指针变量名 = 一级指针地址
int a = 250;
int *p = &a;
int **p = &p;
脑子立马浮现内存图
&pp:二级指针地址
pp:一级指针地址
&p:一级指针地址
p:a的首地址

二级指针和字符串的复杂关系!

结论:

char *A[元素个数] 等价于 char **A;   玩法和数组一模一样
int main(int avgc, char *argv[]) 或者 int main(int argc, char **argv[])

int a = 0;
char *p = "100";
a = strtoul(p, NULL, 0/10/8/16);

第十三课:malloc和free标准库函数

1. 回顾:目前C语言分配的内存的方法:

  1. 定义变量:做不到大量分配内存

  2. 定义数组:可以做到大量分配内存,但是数组类型一致

  3. 结构体(联合体):可以利用数组分配大量内存,也可以让其他成员数据类型不一致

    ​ 还有优点小缺陷:总之还是利用数组

    总结:以上三种方法,如果采用数组实现大量分配内存,是可以的,但是致命缺陷在于

    ​ 必须提前一次性分配好,这样有可能造成内存浪费

    例如:

    int a[1000000];   //一次性分配4*1000000字节内存,因为数组定义时需要指定下标
    

    程序实际运行时可能就用了100个字节,其他内存无形白白浪费了

    关键数组,变量,结构体,都涉及局部性和全局性,局部非静态变量内存生命周期又很短

  4. 问:如何做到大量动态分配内存(想用多少内存就分配多少内存),并且分配的内存跟数据类型无关,并且不用考虑到什么局部,什么全局问题

    答:利用大名鼎鼎的malloc和free函数

2. malloc和free函数的特点

  1. 随时随地动态分配想用的内存,并且内存你大小随时指定
  2. 分配的内存彻底跟数据类型无关
  3. 不用考虑局部非静态变量(出了花括号内存消亡问题)
  4. 缺点:分配内存的效率低,只能在堆区分配内存(堆区概念)

3. 详解malloc和free函数

malloc函数原型:

void *malloc(unsigned long size);   // size:大小

功能:从堆区动态分配内存

参数:

size:指定要分配的内存大小,单位是字节

返回值:返回分配的内存的首地址

​ 如果分配内存失败,操作系统会返回一个NULL

注意:如果用此函数需要添加#include <stdlib.h>声明

例如:

int *p = (int)malloc(8);   // 连续分配8字节内存,并且将内存的首地址,返回保存给指针变量p,p指向分配的8字节内
// 安全判断
if(NULL == p){
	printf("分配内存失败\n");
} else {
	printf("分配内存成功\n");
	*(p+0) = 2;  // 向8字节的前4字节写入数据2
	*(p+1) = 3;  // 向8字节的后字节写入数据3
	printf("%d %d\n", *(p+0), *(p+1));
}

或者
    
void *p = malloc(8);
// 安全判断
if(NULL == p){
	printf("分配内存失败\n");
} else {
	printf("分配内存成功\n");
	*(int *)(p+0) = 2;  // 向8字节的前4字节写入数据2, (int *)强转为int型指针
	*(int *)(p+4) = 3;  // 向8字节的后字节写入数据3  p1+4:地址加4,指向后4直接内存,(int *)将来要操作4直接内存
	printf("%d %d\n", *(int *)(p+0), *(int *)(p+4));
}

在这里插入图片描述

free函数原型:

void free(void *p)

函数功能:释放用malloc分配的内存,只要不调用free,malloc函数分配的内存就不会释放

​ 所以无需考虑malloc分配的内存的局部性和全局性

参数:

p:传递分配的内存的首地址

例如:

free(p);
// 好习惯:释放完毕,将指针完毕赋值为NULL,否则p是一个野指针,因为它原来指向的内存已经释放
p = NULL;

参考代码:malloc.c 和 malloc1.c

/*mallochefree函数演示*/

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int *p = NULL;
    p = (int *)malloc(8); // 分配8字节内存首地址给p,p指向分配的8字节内存
    // 安全判断
    if (NULL == p)
    {
        printf("分配内存失败\n");
        return -1;
    }
    printf("分配内存成功\n");
    // 向8字节内写入数据
    *(p + 0) = 100;  // 前4字节
    *(p + 1) = 200;  // 后4字节
    printf("%d %d\n", *(p + 0), *(p + 1));
    
    free(p); // 释放内存 
    p = NULL;   // 再将p指向空  防止p1变成野指针

    // 采用无类型指针接收
    void *p1 = NULL;
    p1 = (short *)malloc(8);
    if (NULL == p1)
    {
        printf("分配内存失败\n");
        return -1;
    }
    printf("分配内存成功\n");
    *(short *)(p1 + 0) = 10;
    *(short *)(p1 + 2) = 20;
    *(short *)(p1 + 4) = 30;
    *(short *)(p1 + 6) = 40;
    printf("%d %d %d %d\n", *(short *)(p1 + 0), *(short *)(p1 + 2),
            *(short *)(p1 + 4), *(short *)(p1 + 6));

    free(p1); // 释放内存   
    p1 = NULL;   //再将p指向空 防止p1变成野指针
    return 0;
}

结果:
分配内存成功                                                                           100 200                                                                             分配内存成功                                                                           10 20 30 40                                                                        
malloc1.c
/*malloc和结构体*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*声明描述学生信息的结构体数据类型*/
typedef struct student
{
    char name[30];
    int age;
} stu_t;
/*分配学生信息的内存并且初始化学生信息,并且返回学生信息对应的内存地址*/
stu_t *get_student_info(void)
{
    // 调用malloc函数分配一块内存来描述存储学生信息,只要不调用free,这块内存就永远存在,不考虑全局和变量
    stu_t *pst = malloc(sizeof(stu_t)); // 分配内存大小就是一个结构体占用的内存大小
    if (NULL == pst)
    {
        printf("分配内存失败\n");
    }
    else
    {
        // 初始化学生信息
        strcpy(pst->name, "小明");
        pst->age = 18;
        return pst; //返回malloc分配的内存首地址,这块内存现在有了关羽学生的信息
    }
}
int main(void)
{
    // 调用此函数获取学生信息,p指向malloc分配的存储学生信息的内存首地址
    stu_t *p = get_student_info();
    if (NULL == p)
    {
        return -1;
    }
    // 打印学生信息
    printf("%s %d\n", p->name, p->age);
    free(p);
    p = NULL;
    return 0;
}
malloc2.c
/*获取不同学生的信息*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*定义学生信息的结构体*/
typedef struct  student {
    char name[32];
    int age;
} stu_t;
/*定义分配获取学生信息的函数*/
stu_t *get_student_info(char *name, int age) {
    stu_t *pst = (stu_t *)malloc(sizeof(stu_t));   // 分配一个学生信息内存
    //初始化学生信息,初始化内存
    strcpy(pst->name, name);
    pst->age = age;
    return pst;   // 返回学生信息内存的首地址
}

#define LEN     (4)   // 定义学生常量宏
int main(void)
{
    stu_t *array[LEN] = {NULL};
    char name[32] = {0};   // 保存输入的学生姓名
    for(int i = 0; i < LEN; i++) {
        printf("请输入学生姓名:");
        scanf("%s", name);   // 把输入的字符串保存到数组name中
        stu_t *p = get_student_info(name, i+18);  // 调用此函数给一个学生信息分配内存并且初始化学生信息
        array[i] = p;    // 把每个学生的首地址保存到array数组中
    }
    // 打印学生信息
    for(int i = 0; i < LEN; i++) {
        printf("%s %d\n", array[i]->name, array[i]->age);
    }
    // 释放内训
    for(int i = 0; i < LEN; i++) {
        free(array[i]);
        printf("arrray[%d]=%d\n", i , array[i]);
    }
    // 初始化为NULL
    for(int i = 0; i < LEN; i++) {
        array[i] = NULL;
    }
    return 0;
}
malloc3.c
/*获取不同学生的信息*/
#include <stdio.h>
#include <stdlib.h>
#define LEN (4) // 定义学生个数常量宏
/*定义学生信息的结构体*/
typedef struct student
{
    char name[32];
    int age;
} stu_t;
/*定义分配获取学生信息的函数*/
stu_t *get_student_info(void)
{
    stu_t *p = (stu_t *)malloc(sizeof(stu_t) * LEN); // 分配4个学生信息的内存
    if (NULL == p)
    {
        return NULL;
    }
    /*临时保存4个学生信息内存首地址的指针,不能返回p,因为p后面做++运算*/
    stu_t *ptmp = p;
    // 初始化每个学生信息,初始化内存
    for (int i = 0; i < LEN; i++)
    {
        printf("请输入学生的姓名:");
        scanf("%s", p->name);
        // scanf("%d", &p->age);   // 年龄
        p->age = i + 18;
        p++; // 指向下一个学生的内存地址
    }
    return ptmp;
}

int main(void)
{
    stu_t *p = get_student_info(); // 调用此函数给4个学生信息分配内存并且初始化学生信息
    for (int i = 0; i < LEN; i++)
    {
        printf("%s %d\n", p[i].name, p[i].age); // 等价于(p+i)->name, (p+i)->age
    }
    free(p);
    return 0;
}

在这里插入图片描述

在这里插入图片描述

第十四课:文件操作相关库函数

1. 文件操作的标准C库函数:fopen/fclose/fread/fwrite/fseek/rewind

2. fopen函数原型

FILE *fopen(const char *filename, const char *mode);

(f=file:文件,open:打开,close:关闭,read:读,write:写,seek:定位,a:append:追加)

功能:打开文件

参数:

filename:指定要打开的文件名,建议用绝对路径,例如:"/home/tarena/a.txt"

mode:指定打开文件时的方式

​ “r”:以只读(只能打开不能修改)方式打开,该文件必须存在

​ “r+”:以读或者写的方式打开,前提是必须存在

​ “w”:以只写(只能修改不能查看)的方式打开,若文件存在,会将文件清空,如果文件 不存在,创建新文件

​ “w+”:以读/写的方式打开,如果文件存在会将文件清空,如果文件不存在,创建新文件

​ “a”:以追加附加的方式打开只写文件,如果文件不存在,创建文件,如果文件存在,把 心内容添加到文件的尾部

​ “a+”:以追加附加的方式打开读/写文件,如果文件不存在,创建文件,如果文件存在, 把心内容添加到文件的尾部

​ 返回值:返回一个描述文件信息的结构体指针:FILE *指针,当做int *

​ 打开失败,返回NULL

3. fclose函数原型

void fclose(File, *fp)

功能:关闭文件,fp:传递fopen的返回值就是文件指针

4. fwrite函数原型

unsign long write fwrite(void *ptr, unsigned long size, unsigned long nmemb, FILE *fp)

功能:想文件写入数据就是将内存中的数据写入到文件所在的硬盘上

参数:

ptr:传递保存数据的内存首地址

size:指定要写入的单个数据块大小(类似文章中每个段落的大小),例如:一个int类型数据块 等于4字节

nmemb:指定要写入的单个数据的个数(类似文章中段落的个数),例如:共有10个数据块

​ 所以总共写入的大小=10*4字节

fp:指定要写入的文件指针(就是文件),也就是硬盘

返回值:写入失败返回-1,写入成功返回实际写入的数据块个数(类似文章段落的个数)

每次写完,fp文件指针就会跑到文件的尾部!

5. rewind函数原型

void rewind(FILE *fp)

功能:将文件指针重新指定到文件的开头

6. fread函数原型

unsigned long fread(void *buffer, unsigned long size, unsigned long count, FILE *fp)

功能:从文件所在的硬盘上读取数据保存在内存中

buffer:保存内存首地址

size:指定要读取的单个数据块的代销

count:指定要读取的数据块个数

fp:文件指针,就是文件

返回值:读取失败返回-1,读取成功返回实际读取的数据块个数

每次写完,fp文件指针就会跑到文件的尾部!

7.fseek函数原型

int fseek(FILE *fp, long offset, int fromwhere)

功能:定位文件指针,从哪里开始访问文件

fp:文件指针

offset:偏移量

fromwhere:用一下三个宏

​ SEEK_SET:从文件开头算偏移量

​ SEEK_CUR:从当前位置开始算偏移量

​ SEEK_END:从哪个文件尾部开始算偏移量

参考代码:file.c
/*文件操作库函数*/
#include <stdio.h>
int main(void)
{
    FILE *fp = NULL;   // 创建初始化描述文件信息的结构体指针
    fp = fopen("E:/C/c-study-notes/tarena//stdc/13/day13/a.bin", "w+"); //可读可写方式打开
    if (NULL == fp)
    {
        printf("打开文件失败\n");
        return -1;
    }
    // 向数组中的内存数据向文件写入
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8};
    int len = sizeof(a) / sizeof(a[0]);
    int size = 0;
    size = fwrite(a, sizeof(int), len, fp);
    printf("实际写入的数据块个数是%d\n", size);
    // 将fp文件指针重新指定到文件的开头
    rewind(fp);
    // 从文件所在的硬盘上读取数据到内存中
    int b[8] = {0}; // 保存读取的数据
    size = fread(b, sizeof(int), 10, fp);
    printf("实际读取了%d个数据块\n", size);
    for (int i = 0; i < size; i++)
    {
        printf("b[%d] = %d\n", i, b[i]);
    }
    // 将文件指针定位到从文件开头开始往后8个字节的位置
    // 1, 2, 3, 4, 5, 6, 7, 8
    int c[2] = {0}; //保存读取的数据
    fseek(fp, 8, SEEK_SET);
    fread(c, sizeof(int), 2, fp); // 此时fp跑到5这个数的位置
    printf("%d %d\n", c[0], c[1]);
    // 将文件指针从当前位置往后移动8个字节
    fseek(fp, 8, SEEK_CUR);
    fread(c, sizeof(int), 2, fp); // 此时fp跑到文件尾部
    printf("%d %d\n", c[0], c[1]);
    // 从文件尾部往前移动12字节
    fseek(fp, -12, SEEK_END);
    fread(c, sizeof(int), 2, fp); // 此时fp跑到8这个数的位置
    printf("%d %d\n", c[0], c[1]);
    // 关闭文件
    fclose(fp);
    return 0;
}

C语言综合演练

实现自己的命令行终端,实现算数运算功能

cmd.h

/*cmd.h头文件卫士*/
#ifndef __CMD_H
#define __CMD_H
// 声明函数指针,将来指向对应的处理函数
typedef int(*cb_t)(int, int);
// 声明描述学生信息的结构体数据类型
typedef struct cmd
{
    const char *name;  // 描述命令的名称:"add","sub"等等,  加const不允许修改
    cb_t callback;    // 描述命令对应的处理函数:cmd_add,cmd_sub等等
} cmd_t;

/*声明查找命令函数*/
extern const cmd_t *find_cmd(const char *);
#endif

cmd.c

/*cmd.c:各种定义*/
#include "cmd.h"
#include <string.h>

// 定义加减函数
int cmd_add(int a, int b) {
    return a + b;
}
int cmd_sub(int a, int b) {
    return a - b;
}

/*定义加减命令的结构体变量:采用结构体数组*/
const cmd_t cmd_tbl[] = {  // cmd_t数据类型,cmd_tbl数组名
    {"add", cmd_add},  // 加法命令结构体信息
    {"sub", cmd_sub}  //  减法命令结构体信息
};

/*定义求数组元素个数的宏*/
#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))

/*定义根据命令名称在数组中查找命令函数*/
const cmd_t *find_cmd(const char *name) {
    for(int i = 0; i < ARRAY_SIZE(cmd_tbl); i++) {
        if(!strcmp(name, cmd_tbl[i].name)) { // 根据传递的名称与数组的每个元素中的名称进行比较,
                                        //加!是因为strcmp的返回值是0,要取反
                return &cmd_tbl[i];  //返回匹配的命令行结构体的首地址
        }
    }
    return NULL;  // 没有找到
}

main.c

/*main.c:各种调用*/

#include <stdio.h>
#include <string.h>
#include "cmd.h"

#define LEN     (32)
static char buf[LEN];

int main(void)
{
    while (1)  // 用户可以不断输入命令
    {
        int a = 0, b = 0 , ret = 0;
        printf("请输入命令名:");
        scanf("%s", buf);  // 获取命令
        // 到cmd.c的数组中根据命令的名称找命令的结构体,一旦找到就可以调用命令的处理函数,
        // 找到之后返回命令对应结构体的首地址保存到p
        const cmd_t *p;
        p = find_cmd(buf);
        if(p != NULL) { // 找到了
        	printf("请输入两个数字:");
        	scanf("%d %d", &a, &b);
        	// 调用处理函数
        	ret = p->callback(a, b);
        	printf("%d\n", ret);
        } else { // 没找到
        	if(!strcmp(buf, "quit"))
        		return 0;   // 结束程序
        	printf("命令名错误\n");
        }
    }
    

    return 0;
}

在这里插入图片描述关注公众号,获取更多精彩内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值