回顾:
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语言分配的内存的方法:
-
定义变量:做不到大量分配内存
-
定义数组:可以做到大量分配内存,但是数组类型一致
-
结构体(联合体):可以利用数组分配大量内存,也可以让其他成员数据类型不一致
还有优点小缺陷:总之还是利用数组
总结:以上三种方法,如果采用数组实现大量分配内存,是可以的,但是致命缺陷在于
必须提前一次性分配好,这样有可能造成内存浪费
例如:
int a[1000000]; //一次性分配4*1000000字节内存,因为数组定义时需要指定下标
程序实际运行时可能就用了100个字节,其他内存无形白白浪费了
关键数组,变量,结构体,都涉及局部性和全局性,局部非静态变量内存生命周期又很短
-
问:如何做到大量动态分配内存(想用多少内存就分配多少内存),并且分配的内存跟数据类型无关,并且不用考虑到什么局部,什么全局问题
答:利用大名鼎鼎的malloc和free函数
2. malloc和free函数的特点
- 随时随地动态分配想用的内存,并且内存你大小随时指定
- 分配的内存彻底跟数据类型无关
- 不用考虑局部非静态变量(出了花括号内存消亡问题)
- 缺点:分配内存的效率低,只能在堆区分配内存(堆区概念)
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;
}
关注公众号,获取更多精彩内容