C语言提高

目录

1 内存分区

1.1 typedef的使用

1.1.1 简化结构体创建变量

1.1.2 区分数据类型 

1.1.3 提高移植性

1.2 void使用

1.2.1 void万能指针

1.3 sizeof的使用

1.3.1 本质

1.3.2 返回值类型

1.3.3 用途

1.4 变量的修改方式

1.4.1 直接修改

1.4.2 间接修改

1.4.2.1 自定义数据类型

1.5内存分区模型

1.5.1 内存分区

1.5.1.1 内存四区 程序运行前

对程序进行编译

代码区

数据区

已初始化

未初始化

1.5.2 内存四区 程序运行后

1.5.2.1 栈区

1.5.2.2 堆区

1.5.3 栈区注意事项

1.5.4 堆区

1.5.4.1  堆区的注意事项

1.5.4 静态变量和全局变量

1.5.4.1 静态变量

static

1.5.4.2 全局变量

extern

1.5.5 常量

1.5.5.1 const修饰全局常量

1.5.5.2 const修饰局部常量

1.5.5.1 字符串常量

1.5.6 宏函数

1.5.7 调用惯例

调用惯例惯例内容

1.5.8 栈的生长方向

1.5.9 内存存储方式

1.6 calloc和realloc

1.6.1 calloc函数

1.6.2 realloc

4 指针强化

4.1.1 指针变量

4.1.2 野指针和空指针

4.1.2.1 空指针

4.1.2.2 野指针

4.1.3 指针的步长

4.1.3.1 指针步长练习

4.2 指针的意义_间接赋值

4.2.1 间接赋值的三大条件

4.2.2 建立一个合适的指针地址

4.2.3 间接赋值

4.3 指针做函数参数

4.4 字符串指针强化

4.4.1 字符串指针做函数参数

4.4.1.1 字符串基本操作

4.4.1.1.1 字符串练习---字符串拷贝

4.4.1.1.2 字符串反转

4.4.2 字符串的格式化

4.4.2.1 sprintf

4.4.2.2 sscanf

4.4.2.2.1 sscanf练习

4.4.2.3 字符串练习---查找字符串

4.5 一级指针易错点

4.5.1 越界

4.5.2 指针叠加会不断改变指针指向

4.5.3 返回局部变量地址

4.5.4 同一块内存释放多次(不可以释放野指针)

5 二级指针

5.1 二级指针基本概念

5.2 输入特性

5.3 输出特性

5.4 二级指针练习---文件的读写

5.5 位运算

5.5.1 位逻辑运算符

5.5.1.1 按位取反 ~

5.5.1.2 位与(AND) : &

5.5.1.3 或:|

5.5.1.4 位异或: ^

5.6 移位运算符

5.6.1 左移<<

5.6.2 右移>>

6 多维数组

6.1 一维数组

6.1.1 数组名

6.1.2 数组和指针

6.2 多维数组

6.2.1 二维数组数组名

6.3 指针数组

 7 结构体

7.1 结构体的使用

7.2 结构体嵌套指针

7.2.1 结构体嵌套一级指针

7.2.1.1 结构体嵌套一级指针练习

7.2.2 结构体嵌套二级指针

7.2.2.1 结构体二级指针嵌套练习

7.2.2.1.1 自

7.2.2.1.2 视频

7.3 结构体偏移

7.4 结构体字节对齐

7.4.1 内存对齐

7.4.2 内存对齐案例

8 文件操作

8.1 文件相关概念

8.1.1 文件的概念

8.1.2 流的概念

8.1.2.1 二进制流

8.2 文件的操作

8.2.1 文件流总览

8.2.2 文件指针

8.2.3 文件缓存区

8.2.4 文件打开关闭

8.2.4.1 文件打开(fopen)

参数:

返回值:

8.2.4.2 文件关闭(fclose)

8.2.5 文件读写函数

8.2.5.1 文件读写-字符方式读写

8.2.5.2 文件读写-按行的方式读写

8.2.5.3 文件读写-按照块读写文件

8.2.5.4 文件读写-按格式化读写文件

8.2.5.5 文件读写-随机位置

8.2.5.6 文件读写注意事项

8.2.5.7 文件练习-配置文件

9 链表

9.1 链表概念

9.2 链表的分类

9.3 静态链表 动态链表

9.  清空链表 销毁链表

清空链表

销毁链表

函数指针

函数指针的定义方式

回调函数

打印任意类型数据

打印任意类型数组,在数组中找元素

11 预处理

11.1 文件包含指令(#include)

11.1.1 文件包含处理

11.2 宏定义

11.2.1 无参数的宏定义(宏常量)

11.2.2 带参数的宏定义(宏函数)

11.3 条件编译

11.3.1 基本概念

11.3.2 条件编译

11.4 一些特殊的预定宏

12 动态库的封装和使用

12.1 库的基本概念

12.2.1 静态库的创建

12.2.3 静态库的优缺点

13 递归函数

13.1 递归函数的基本概念

13.2 普通函数调用

13.3 递归函数调用

13.4 斐波那契

面相接口


1 内存分区

1.1 typedef的使用

1.1.1 简化结构体创建变量

1.1.2 区分数据类型 

1.1.3 提高移植性

        可以在typedef后修改数据类型,所有mytype类型都被修改

int main()
{
	
	typedef int mytype;

	mytype i = 10;
	mytype j = 10;

}

1.2 void使用

void无类型,不可以通过void创建变量

void用途:限定函数的返回值,函数参数

1.2.1 void万能指针

可以直接转换为任意类型的指针,不用强转

1.3 sizeof的使用

1.3.1 本质

不是函数,是运算符

1.3.2 返回值类型

无符号整型 unsigned int

1.3.3 用途

统计数组占用内存空间大小

当数组名传入到函数中,数组名退化为一个指针,指针执行数组中的第一个元素的地址

1.4 变量的修改方式

1.4.1 直接修改

1.4.2 间接修改

1.4.2.1 自定义数据类型

        指针代表的是某个类型的地址的起始位置,不同类型的指针,每次所取的内存空间大小不同。可以通过各种方法取找到内存中的数据。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

typedef struct person_st
{
	char c;		//0~3
	int i;		//4~7
	char c2;	//8~11
	int i2;		//11~15
}person;


int main()
{
	person p = { 'a',1 ,'b',2};

	//直接修改i2
	p.i2 = 100;
	printf("直接修改:%d\n",p.i2);

	//间接修改i2
	person* p2 = &p;
	p2->i2 = 1000;
	printf("间接修改:%d\n",p.i2);

	/* 
	p2 + 1 ----->跳转16个字节
	char* 类型每次跳转一个字节,可以用char*去跳转

	取值时需要取4个字节,char*每次取1个字节
	故把char*转换为int*,再解引用
	*/

	
	char* p3 = (char*) & p;
	int* p4 = (int*) & p;
	printf("地址跳转取值char:%d\n", *(int*)(p3 + 12));
	printf("地址跳转取值int:%d\n", *(int*)(p4+3));

}

输出结果为

1.5内存分区模型

1.5.1 内存分区

1.5.1.1 内存四区 程序运行前
对程序进行编译

1) 预处理 :宏定义展开,头文件展开,条件编译,这里并不会检查语法

2)编译:检查语法,将预处理后的文件编译生成汇编文件

3)汇编:将汇编文件生成目标问价(二进制文件)

4)链接:将目标文件链接为可执行文件

代码区

代码共享

只读

数据区

静态变量,全局变量,常量

已初始化

data

未初始化

bss

1.5.2 内存四区 程序运行后

1.5.2.1 栈区

先进后出

编译器管理数据开辟,释放

容量有限,不要将大量数据开辟到栈区

1.5.2.2 堆区

容量远远大于栈区

手动申请和释放

malloc---申请  free---释放

程序结束后会统一收回

1.5.3 栈区注意事项

不要返回局部变量的地址,局部变量在函数体执行完毕后被释放,再次操作是非法操作,结果未知。

1.5.4 堆区

利用malloc将数据创建到堆区

利用free将堆区释放掉

1.5.4.1  堆区的注意事项

如果主调函数中 一个空指针分配内存,利用同级的指针是分配失败的

解决方式:利用高级指针修饰低级指针(二级指针修饰一级指针 )

1.5.4 静态变量和全局变量

1.5.4.1 静态变量
static

运行前分配内存,生命周期在整个程序运行期间存活,放在数据区

内部链接属性(只能在本文件中使用)

自动初始化为0值或空值

static变量的值有继承性

另外常用来修饰一个变量或者函数(防止当前函数对外扩展) ,修饰的变量或函数只能在当前范围内使用,其他文件引用不到,可以间接使用

static 修饰的变量只有一个空间,变量具有继承性

1.5.4.2 全局变量
extern

c语言下全局变量默认加了关键字extern

外部链接属性(在其他文件中使用需要加 extern 让程序去其他文件中找)

不能改变被说明的变量的值或类型

1.5.5 常量

1.5.5.1 const修饰全局常量

直接修改,失败

间接修改,语法通过,运行失败,受到常量区保护

1.5.5.2 const修饰局部常量

直接修改,失败

间接修改,成功,放在栈上

1.5.5.1 字符串常量

VS下将相同的字符串常量看成一个,对于字符串常量的修改vs是不可以改的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

int main()
{

	const char* p1 = "hello world";
	const char* p2 = "hello world";
	const char* p3 = "hello world";

	printf("%d\n", p1);
	printf("%d\n", p2);
	printf("%d\n", p3);
	printf("%d\n", &"hello world");

}

结果

都指向了同一个地址

字符串常量可以共享,只读不可以写

1.5.6 宏函数

在预编译阶段做了宏替换

优点:以空间换时间(普通函数,入栈出栈时间开销)

使用场景:将频繁使用,短小的函数,封装宏函数

注意事项:运算保证完整性 加括号

1.5.7 调用惯例

主调函数和被调函数要有一致约定,才能正确调用函数,这个约定称为调用惯例

c/c++默认调用惯例   cdcl

调用惯例惯例内容

出栈方   主调函数

参数传递顺序    从右往左

名称修饰     _+函数名

1.5.8 栈的生长方向

栈底 --- 高地址

栈顶 --- 低地址

1.5.9 内存存储方式

高位字节数据 --- 高地址

低位字节数据 --- 低地址

小端对齐

比如:

0x11223344 (16进制,占4字节)

低位为44 高位为11

在内存中存放:

44 -> 33 -> 22 -> 11

低地址 --------> 高地址

1.6 calloc和realloc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


int main()
{
	//malloc不会做清零的操作
	int* p1 = (int*)malloc(sizeof(int) * 10);

	//参数1:开辟的个数   参数2:每个占多少字节
	//calloc会对开辟的空间值零
	int* p2 = (int*)calloc(10, sizeof(int));

	int* p3 = (int*)malloc(sizeof(int) * 10);
	for (int i = 0; i < 10; i++)
	{
		p3[i] = i;
	}
	//参数1:原空间的地址   参数2:重新分配内存大小
	//如果开辟的空间比原来的大,后续空间不会清零
	int* p4 = (int*)realloc(p3, sizeof(int) *20);
	for (int i = 0; i < 20; i++)
	{
		printf("%d\n", p4[i]);
	}

	free(p1);
	free(p2);
	free(p4);
	return 0;
}

1.6.1 calloc函数

#include <stdlib.h>

void* calloc(size_t nmemb,size_t size);

功能:
    在内存动态存储区分配nmemb块长度为size字节的连续区域。calloc自动将分配的内存置0

参数:
    nmemb:所需内存单元数量
    size:每个内存单元的大小(单位:字节)

返回值
    成功:分配空间的起始地址
    失败:NULL

1.6.2 realloc

#include <stdlib.h>

void *realloc(void *ptr,size_t size);

功能:
    重新分配用malloc或者calloc函数在堆中分配内存空间的大小。
    realloc不会自动清理增加的内存,需要手动清理,如果指定的地址后面有连续空间,那么就会在已有地址的基础上增加内存,如果指定的地址后面没有空间,那么realloc会重新分配新的连续内存,把旧的内存的值拷贝到新内存,同时释放旧内存。

参数:
    ptr:为之前用malloc或者calloc分配的内存地址(分配内存的首地址),如果此参数等于NULL,那么和calloc与malloc功能一致
    size:为重新分配的内存大小,单位:字节

返回值:
    成功:新分配的堆内存地址
    失败:NULL

realloc机制:
    首先在原有空间后查看空闲空间,如果空闲空间足够大,直接在原空间后续开辟新空间大小并使用。
    如果空闲空间不够用,在内存中直接找一块足够大的空间,将原来空间的数据拷贝到新空间,并且释放原有的内存空间,将新空间的首地址返回

如果size比原空间小,则截取size大小空间,后面的空间释放掉

4 指针强化

4.1.1 指针变量

4.1.2 野指针和空指针

4.1.2.1 空指针

指向NULL的指针

不能向空指针和野指针指向的内存进行操作,如果操作,程序中断

不允许向NULL 和 非法 地址拷贝内存

4.1.2.2 野指针

 指针变量未初始化

malloc后free未置空

指针变量超出了作用域

空指针 可以重复释放

野指针 不可以重复释放

4.1.3 指针的步长

1、指针变量+1后跳跃的字节数

2、在解引用时,取出的字节数

char *p = buff;
printf("%d\n", *(int*)(p+1));

用p获得首地址

在printf中

(p+1)用于定位,往后一个字节

(int *)用于强转,往后取4个字节

*用于解引用,取值

4.1.3.1 指针步长练习

获取结构体中属性的偏移量

offsetoff(结构体类型,属性)

头文件 <stddef.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

#include <stddef.h>	//offsetoff的头文件

struct test_st
{
	char a;			//0~3
	int b;			//4~7
	char c[64];		//8~71
	int d;			//72~75
};


int main()
{
	struct test_st p = { 'a',10,"hello world",1000 };

	//p中的d属性偏移量
	printf("d的偏移量为:%d\n", offsetof(struct test_st, d));

	//取值
	printf("d的值为:%d\n", *(int*)((char*)&p + offsetof(struct test_st, d)));

}

结果为:

4.2 指针的意义_间接赋值

4.2.1 间接赋值的三大条件

1)2个变量(一个普通变量 一个指针变量,或者一个实参一个形参)

2)建立关系

3)通过 * 操作指针指向的内存

利用Qt找到内存,操作内存

4.2.2 建立一个合适的指针地址

指针类型

4.2.3 间接赋值

调用函数修改 x 需要传入 x 的地址。

4.3 指针做函数参数

输入:主调函数分配内存

输出:主调函数分配内存

4.4 字符串指针强化

4.4.1 字符串指针做函数参数

4.4.1.1 字符串基本操作
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


int main()
{
	//字符串的结尾要加一个'\0',否则可能会出现乱码
	char str1[] = { 'h','e','l','l','o','\0'};
	printf("str1 : %s\n", str1);


	//输出初始化的部分,没有初始化的部分为 0 ,不会输出
	char str2[100] = { 'h','e','l','l','o' };
	printf("str2 : %s\n", str1);


	//双引号的字符串会在最后自动添加'/0'
	char str3[] = "hello";
	printf("str3 : %s\n", str3);
	//sizeof统计内存中数组大小,包含'\0'
	printf("sizeof str3:%d\n", sizeof(str3));			//6
	//strlen计算字符串长度,到'\0'结束,不会统计'\0'
	printf("strlen str3:%d\n", strlen(str3));			//5


	char str4[100] = "hello";
	printf("str4 : %s\n", str4);
	printf("sizeof str4:%d\n", sizeof(str4));			//100
	printf("strlen str4:%d\n", strlen(str4));			//5


	char str5[100] = "hello\0world";
	printf("str5 : %s\n", str5);
	printf("sizeof str5:%d\n", sizeof(str5));			//100  末尾还有一个'\0'
	printf("strlen str5:%d\n", strlen(str5));			//5

	// 0--八进制   012 = 10 \012为换行符
	char str6[] = "hello\012world";
	printf("str6 : %s\n", str6);						//hello \n world
	printf("sizeof str6:%d\n", sizeof(str6));			//12
	printf("strlen str6:%d\n", strlen(str6));			//11
}

结果为

A---65,a---97

字符串结束标志 \0

4.4.1.1.1 字符串练习---字符串拷贝
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

//第一种实现方式,通过数组遍历
void copyString01(char* buf, const char* str)
{
	int i;
	int len = strlen(str);
	for (i  = 0;i<len; i++)
	{
		buf[i] = str[i];
	}
	buf[len] = '\0';
	return;
}

//第二种实现方式,利用字符指针
void copyString02(char* buf, const char* str)
{
	while (*str != '\0')
	{
		*buf = *str;
		buf++;
		str++;
	}
	*buf = '\0';
}

//第三种实现方式,利用while条件计算
void copyString03(char* buf, const char* str)
{
	while (*buf++=*str++)
	{

	}
	
}


int main()
{
	const char* str = "hello world!";

	char buf[1024];

	copyString03(buf, str);

	printf("buf = %s\n", buf);
}
4.4.1.1.2 字符串反转
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

void reverse1(char* str)
{
	int len = strlen(str);
	int start = 0;
	int end = len - 1;
	char temp;
	while (start < end)
	{
		temp = str[start];
		str[start] = str[end];
		str[end] = temp;
		start++;
		end--;
	}
}

void reverse2(char* str)
{
	int len = strlen(str);
	char* start = str;
	char* end = str + len - 1;
	char temp;
	while (start < end)
	{
		temp = *start;
		*start = *end;
		*end = temp;
		start++;
		end--;
	}
}

int main()
{
	char str[] = "abcdefg";

	reverse2(str);
	printf("str反转 %s", str);

	return 0;
}

4.4.2 字符串的格式化

4.4.2.1 sprintf
#include <stdio>

int sprintf(char *str,const char *format,...);

功能:
    根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符'\0' 为止。

参数:
    str:字符串首地址
    format:字符串格式,用法和printf()一样

返回值:
    成功:实际格式化的字符个数
    失败:-1

实现:字符串拼接,数字转为字符串

设置宽度 ,左对齐 -   ,  右对齐

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


int main()
{
	int num = 100;
	char buf[1024];
	memset(buf, 0, 1024);

	//参数1:格式化的目标字符串
	//参数2:格式化形式
	//参数3:格式化中的参数...
	sprintf(buf, "今天是%d年 %d月 %d日", 2023, 9, 6);
	printf("%s\n", buf);

	//拼接字符串
	memset(buf, 0, 1024);
	char str1[] = "hello";
	char str2[] = "world";
	int len = sprintf(buf, "%s %s", str1, str2);
	printf("%s\n", buf);
	printf("len = %d\n", len);
	

	//数字转字符串
	memset(buf, 0, 1024);
	sprintf(buf, "%d", num);
	printf("num=%s\n", buf);


	//设置宽度   默认右对齐 用法和printf一样
	memset(buf, 0, 1024);
	sprintf(buf, "%8d", num);
	printf("num=%s\n", buf);

	//左对齐
	memset(buf, 0, 1024);
	sprintf(buf, "%-8d", num);
	printf("num=%s\n", buf);

	//转换成16进制字符串 --- %x,小写
	memset(buf, 0, 1024);
	sprintf(buf, "0x%x", num);
	printf("num=%s\n", buf);
	return 0;
}
4.4.2.2 sscanf

将一个字符串格式化后筛选有效数据

#include <stdio.h>

int sscanf(consr char *str,const char *format, ...);

sscanf(原字符串,"格式化",目标字符串);

功能:
    从str指定的字符串读取数据,并根据参数format字符串来转换并格式化字符串数据。

参数:
    str:指定的字符串首地址
    format:字符串格式,用法和scanf()一样

返回值
    成功:成功则返回参数数目,失败则返回-1
    失败:-1
   格式                                                   作用
%*s 或 %*d跳过数据
%[width]读指定宽度数据
%[a-z]匹配a到z中任意字符(尽可能多的字符)
%[aBc]匹配a、B、c中一员,贪婪性
%[^a]匹配非a的任意字符,贪婪性
%[^a-z]表示读取除a-z外的所有字符

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>



//%*s 或 %*d 跳过数据
//%s  或 %d 保留
void test01()
{
	const char* str = "132456770abcdefg";

	char buf[1024];

	sscanf(str, "%*d%s", buf);
	printf("buf = %s\n", buf);
}


//忽略字母不能用%*s%s   数字也是字符串
/*
解决方法1:在原字符串str中 字母与数字之间加 空格或\t
遇到空格会结束

解决方法2:%*[a-z]

*/
void test02()
{
	const char* str = "abcdefg 132456770";

	char buf[1024];

	sscanf(str, "%*[a-z]%s", buf);
	printf("buf = %s\n", buf);
}

//宽度 %[width]s 读取指定宽度的数据
void test03()
{
	const char* str = "abcdefg 132456770";

	char buf[1024];

	sscanf(str, "%5s", buf);
	printf("buf = %s\n", buf);
}

//%[a-z]   读取a-z的内容,需要先将前面的内容忽略掉,
//只会取最先遇到的一串,中断后,后面出现的不会再取
void test04()
{
	const char* str = "12312abcdddabccccc 132456770";

	char buf[1024];
	
	sscanf(str, "%*d%[a-c]", buf);
	printf("buf = %s\n", buf);
}

//%[aBc] 匹配a、B、c中的一员,贪婪性
//如果匹配失败,后续不会在匹配
void test05()
{
	const char* str = "12312aBcddaBccccc 132456770";

	char buf[1024];

	sscanf(str, "%*d%[aBc]", buf);
	printf("buf = %s\n", buf);
}

//%[^a-z] 匹配非a-z的内容
//%[^a] 匹配非a的内容
void test06()
{
	const char* str = "12312aBcddaBccccc 132456770";

	char buf[1024];

	sscanf(str, "%[^a]", buf);
	printf("buf = %s\n", buf);
}

int main()
{
	test06();

	return 0;
}
4.4.2.2.1 sscanf练习

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>




void test01()
{
	const char* ip = "127.0.0.1";

	int num1=0, num2=0, num3=0, num4=0;

	//num前需要有&,取地址   与scanf类似
	sscanf(ip, "%d %d %d %d", &num1, &num2, &num3, &num4);

	printf("num1 = %d\n", num1);
	printf("num2 = %d\n", num2);
	printf("num3 = %d\n", num3);
	printf("num4 = %d\n", num4);
}


//取#与@之间的abc
void test02()
{
	const char* str = "sadfsdfqwe#abc@324234sdf";
	char buf[1024];

	//忽略非# 忽略# 取非@   到@停止
	sscanf(str, "%*[^#] # %[^@]",buf);
	printf("%s\n", buf);


}

//输出helloworld 和 it.cn
void test03()
{
	const char* str = "helloworld@it.cn";
	char buf1[1024];
	char buf2[1024];

	//取非@ 忽略@  取剩下的字符串
	sscanf(str, "%[^@] %*[@] %s", buf1,buf2);
	printf("%s\n", buf1);
	printf("%s\n", buf2);

}


int main()
{
	test03();

	return 0;
}
4.4.2.3 字符串练习---查找字符串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


int myString(const char *str,const char *des)
{
	int num = 0;
	while (*str != '\0')
	{
		if (*str != *des)
		{
			str++;
			num++;
			continue;
		}
		const char* tempStr = str;
		const char* tempDes = des;

		while (*tempDes != '\0')
		{
			if (*tempStr != *tempDes)
			{
				str++;
				num++;
				break;
			}
				tempStr++;
				tempDes++;
		}
		if(*tempDes == '\0')
			return num;
	}
	return -1;
}

int main()
{
	const char* str = "abcdefghijklmn";
	const char* des = "ijk";

	int ret = myString(str,des);
	if (ret == -1)
		printf("没有该字符串\n");
	printf("ret = %d\n", ret);

	return 0;
} 

4.5 一级指针易错点

4.5.1 越界

 此时会发生越界,数组空间位3,但是保存了四个元素,最后一个位'\0'

char arr[3] = "abc";

4.5.2 指针叠加会不断改变指针指向

指针叠加会不断改变指针指向,释放指针出错,解决方式,利用临时指针

错误:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


void test01()
{
	char* p = (char*)malloc(sizeof(char) * 64);
	
	for (int i = 0; i < 10; i++)
	{
		*p = i + 97;
		printf("%c\n", *p);
		p++;
	}

	if (p != NULL)
	{
		free(p);
	}

}

int main()
{
	test01();
	return 0;
} 

运行代码会报错

这是因为此时p已经不是这块内存的首地址,此时free会报错

解决方案,创建一个临时指针变量来进行遍历

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


void test01()
{
	char* p = (char*)malloc(sizeof(char) * 64);
	char* pp = p;

	for (int i = 0; i < 10; i++)
	{
		*pp = i + 97;
		printf("%c\n", *pp);
		pp++;
	}

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}

}

int main()
{
	test01();
	return 0;
} 

4.5.3 返回局部变量地址

创建在栈区的内存会随着函数的结束释放掉

如果需要返回地址需要创建堆区的内存

4.5.4 同一块内存释放多次(不可以释放野指针)

5 二级指针

5.1 二级指针基本概念

5.2 输入特性

主调函数分配内存,被调函数使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

//二级指针的输入特性 ,主调函数分配内存(test01),被调函数使用(printArray)
void printArray(int** p,int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("%d   ", *(p[i]));
	}
}

//在堆区开辟内存
void test01()
{
	int** pArray = (int**)malloc(sizeof(int*) * 5);

	int a1 = 100;
	int a2 = 200;
	int a3 = 300;
	int a4 = 400;
	int a5 = 500;

	//堆区数组维护 栈上的数据地址,建立关系
	/*
	*	*p,**pp
	*	pp = &p
	*	*pp = p = &a
	* 
	*/
	pArray[0] = &a1;
	pArray[1] = &a2;
	pArray[2] = &a3;
	pArray[3] = &a4;
	pArray[4] = &a5;

	//打印数组
	printArray(pArray, 5);

	if (pArray != NULL)
	{
		free(pArray);
		pArray = NULL;
	}
}


//在堆区开辟内存
void test02()
{
	int* pArray[5];
	
	for (int i = 0; i < 5; i++)
	{
		int* p = (int *)malloc(4);
		*(pArray[i]) = i + 100;
	}
	//数组可以通过计算获取长度,指针不行,sizeof里是指针类型的话 会返回4或者8
	int len = sizeof(pArray) / sizeof(*pArray);
	printArray(pArray, 5);

	//堆区内存释放
	for (int i = 0; i < len; i++)
	{
		if (p[i] != NULL) {
			free(p[i]);
			p[i] = NULL;
		}
	}

}






int main()
{
	test02();
	return 0;
} 

5.3 输出特性

被调函数分配内存,主调函数使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


void printArray02(int* p,int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("%d   ", p[i]);
	}
}

//二级指针做函数参数的输出特性
void allocateSpace(int** pp)
{
	int* pArray = (int *)malloc(sizeof(int*) * 10);
	for (int i = 0; i < 10; i++)
	{
		pArray[i] = 100 + i;
	}
	*pp = pArray;
}

void freeSpace(int* p)
{
	if (p != NULL)
	{
		free(p);
		
	}
	
}


void test01()
{
	int* p = NULL;
	allocateSpace(&p);

	printArray02(p, 10);
	//释放
	freeSpace(p);
	p = NULL;
}






int main()
{
	test01();
	return 0;
} 

5.4 二级指针练习---文件的读写

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

//获取行数
int getFileLIne(FILE* file)
{
	//判断参数合法性
	if (file == NULL)
	{
		return 0;
	}
	char buf[1024];
	int line = 0;
	while (fgets(buf, 1024, file) != NULL)
	{
		line++;
	}
	//重置光标
	fseek(file, 0, SEEK_SET);
	return line;
}

void printfArry(char** pArray, int line)
{
	if (pArray == NULL)
	{
		return;
	}
	if (line <= 0)
	{
		return;
	}

	for (int i = 0; i < line; i++)
	{
		printf("第%d行为:%s\n", (i + 1), pArray[i]);
	}
}

void readFileData(FILE* file, char** pArray, int line)
{
	if (file == NULL)
	{
		return;
	}
	if (pArray == NULL)
	{
		return;
	}
	if (line <= 0)
	{
		return;
	}

	char buf[1024] = { 0 };
	int index = 0;
	
	while (fgets(buf, 1024, file) != NULL)
	{
		
		//计算开辟每个字符串的大小    尾部'\0'
		int size = strlen(buf) + 1;
		//将buf中的\n改为\0
		buf[strlen(buf) - 1] = '\0';
		 
		
		//开辟堆区空间
		char* p = (char*)malloc(sizeof(char) * size);
		
		//将文件中读取的内容,放到创建的空间中
		memcpy(p, buf, size - 1);
		//将开辟空间的数据,放入我们维护的数组中
		pArray[index++] = p;
		//清空buf
		memset(buf, 0, 1024);
		
	}
	

}


void freeSpace(char** pArray,int line)
{
	if (pArray == NULL)
	{
		return;
	}

	for (int i = 0; i < line; i++)
	{
		free(pArray[i]);
		pArray[i] = NULL;
	}
	free(pArray);
	pArray = NULL;
}

void test01()
{
	int line = 0;
	FILE* file = fopen("./test.txt", "r");
	if (file == NULL)
	{
		printf("文件打开失败\n");
		return;
	}

	//获取文件行数
	line = getFileLIne(file);
	printf("文件有效行数为%d行\n", line);
	char** pArray = (char**)malloc(sizeof(char*) * line);

	//将文件中的数据放入pArray的数组中
	readFileData(file, pArray, line);
	printfArry(pArray, line);

	//释放空间
	freeSpace(pArray,line);

	//关闭文件
	fclose(file);
}






int main()
{
	test01();
	return 0;
}

5.5 位运算

5.5.1 位逻辑运算符

5.5.1.1 按位取反 ~

0 .0..10 补码

取反

1 .1..01 补码  =  -3

转换为原码

1 .0..11 原码 =-3

5.5.1.2 位与(AND) : &

同真为真,有假为假

判断奇偶

与0001 与 判断最后一位


int main()
{
	int num = 123;
	if ((num & 1) == 0)
	{
		printf("偶数\n");
	}
	else {
		printf("奇数\n");
	}
	return 0;
}
5.5.1.3 或:|

让某个标志位为1 , 可以与1或


/*
5: 0101
3: 0011
5|3
0111 = 7
*/

int main()
{
	int num1 = 5;
	int num2 = 3;
	printf("%d", num1 | num2);
	return 0;
}
5.5.1.4 位异或: ^

相同为假,不同为真

int main()
{
	int num1 = 5;
	int num2 = 7;

	//交换两个数 方法一
	num1 = num1 ^ num2;
	num2 = num1 ^ num2;
	num1 = num1 ^ num2;

	//交换两个数 方法二
	num1 = num1 + num2;
	num2 = num1 - num2;
	num1 = num1 - num2;

	printf("num1 = %d,num2 = %d", num1, num2);  //num1=7 ,num2=5
}

5.6 移位运算符

5.6.1 左移<<

<<n == *2^n  (乘以2^n)

5.6.2 右移>>

>>n == /2*n  (除以2^n)

int main()
{
	int num1 = 20;

	printf("num1 <<= 3 = %d\n", num1 <<= 3);   // 左移三位=,类似+=   结果为160

	printf("num1 >>= 1 = %d\n", num1 >>= 1);   // 右移一位=,类似+=   结果为80
}

如果是整数,以0填充高位

如果为负数,结果要根据机器,

6 多维数组

6.1 一维数组

6.1.1 数组名

  • 除了两种情况,一维数组名指向数组的第一个元素的指针
  1. sizeof
  2. 取地址

  • 数组名 指针常量 ,指针的指向不可以修该,指针指向的值可以修改
  • 数组访问下标可以为负数(步长)
int main()
{
	int arr[5] = { 1,2,3,4,5 };

	//大小为20
	printf("sizeof(arr) = %d\n", sizeof(arr));


	//对数组名 取地址
	//取地址后步长为20(sizeof(arr))
    //arr+1 只向后偏移1个元素
	printf("%d\n", &arr);
	printf("%d\n", &arr+1);

	//数组名 指针常量 int * const p
	//arr = NULL 指针的指向不能改变
	//arr[0] = 100 指针指向的值可以修改

	//数组访问的时候 下标可以为负数、
	//访问时并没有修改p的值,结果都为3
	int* p = arr;
	p = p + 3;
	printf("p=%d\n", p[-1]);   //给人看
	
	printf("p=%d\n", *(p - 1));   //给机器看

	return 0;
}

6.1.2 数组和指针

1、先定义出数组的类型,再通过类型定义出数组指针

2、先定义出数组指针的类型,通过类型创建数组指针变量

3、直接定义数组指针变量

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


//1、先定义出数组的类型,再通过类型定义出数组指针
void test01()
{
	int arr[5] = { 1,2,3,4,5 };

	//ARRAY_TYPE 是一个有5个int元素的数组的 类型
	typedef int(ARRAY_TYPE)[5];

	ARRAY_TYPE* arrP = &arr;
	//*arrP == arr
	for (int i = 0; i < 5; i++)
	{
		printf("%d\n", (*arrP)[i]);
	}

}


//2、先定义出数组指针的类型,通过类型创建数组指针变量
void test02()
{
	int arr[5] = { 1,2,3,4,5 };
	//*ARRAY_TYPE 是一个有5个int元素的数组的 类型 的指针类型
	typedef int(*ARRAY_TYPE)[5];

	ARRAY_TYPE arrP = &arr;

	for (int i = 0; i < 5; i++)
	{
		printf("%d\n", (*arrP)[i]);
	}

}

//3、直接定义数组指针变量
void test03()
{
	int arr[5] = { 1,2,3,4,5 };

	int(*p)[5] = &arr;
	for (int i = 0; i < 5; i++)
	{
		printf("%d\n", (*p)[i]);
	}
}




int main()
{
	test03();

	return 0;
}

6.2 多维数组

6.2.1 二维数组数组名

sizeof获取整个二维数组的大小

取地址的步长为整个二维数组长度

除了上面两种情况外,指向的是第一行数组的 数组指针

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

void printArray(int(*pArr)[3], int len1, int len2)
{
	for (int i = 0; i < len1; i++)
	{
		for (int j = 0; j < len2; j++)
		{
			printf("%d  ", pArr[i][j]);
			//printf("%d  ", *(*(pArr+i)+j));

		}
		printf("%d");
	}
}

//二维数组做函数参数
void test01()
{
	int arr[3][3] =
	{
		{1,2,3},
		{4,5,6},
		{7,8,9},
	};

	printArray(arr, 3, 3);
}




int main()
{
	//第一种
	int arr[3][3] =
	{
		{1,2,3},
		{4,5,6},
		{7,8,9},
	};

	//第二种
	int arr2[3][3] = { 1,2,3,4,5,6,7,8,9 };

	//第三种
	int arr3[][3] = { 1,2,3,4,5,6,7,8,9 };

	//一般情况下 指向第一行数组指针 p为行指针
	int(*p)[3] = arr;

	//通过p指针访问 6 元素
	printf("%d\n", *(*p + 5)); 
	printf("%d\n", *(*(p + 1) + 2));
	printf("%d\n", p[1][2]);


	return 0;
}

6.3 指针数组

指针数组排序,选择排序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


void showArr(char* arr[], int len)
{
	for (int k = 0; k < len; k++)
	{
		printf("%s\n", arr[k]);
	}
}

//选择排序
void selectSort(char* arr[], int size)
{
	int i, j, max;
	char* p;
	for (i = 0; i < size ; i++)
	{
		max = i;
		for (j = i + 1; j < size; j++)
		{
			if (strcmp(arr[max], arr[j]) <  0)
			{
				max = j;
			}
		}
		if (max != i)
		{
			p = arr[i];
			arr[i] = arr[max];
			arr[max] = p;
		}
	}
	showArr(arr, size);
}




int main()
{
	char* pArr[] = { "aaa","ccc","ddd","eee","bbb","fff" };
	
	int len = sizeof(pArr) / sizeof(*pArr);
	selectSort(pArr, len);
	

	return 0;
}

 7 结构体

7.1 结构体的使用

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


//typedef 给结构体取别名  person02 == struct Person01_st
typedef struct Person01_st
{
	char name[64];
	int age;
}person01;

//不用typedef, person是struct Person02_st类型的变量
struct Person02_st
{
	char name[64];
	int age;
}person02;

//只有person03一个变量
struct
{
	char name[64];
	int age;
}person03;





int main() 
{
	//在栈中创建结构体
	person01 p01;		
	person01 p02[3];	//结构体类型的指针

	//在堆区创建结构体
	person01 *p03 = (person01*) malloc(sizeof(person01) * 4);

	//赋值
	for (int i = 0; i < 4; i++)
	{
		sprintf(p03[i].name, "name%d", i);
		p03[i].age = i;
	}



	return 0;
}

7.2 结构体嵌套指针

7.2.1 结构体嵌套一级指针

struct person_st
{
    char name[64];
    int age;
};

p1 = p2;

将p2的name覆盖p1的name
  p2的age 覆盖p1的age

2. 当结构体中有属性创建在堆区,赋值之后,释放会导致堆区属性重复释放,并且有内存泄漏

解决:

需要手动复制

struct person——st
{
    char *name;
    int age;
};

浅拷贝会导致p2 name的内存重复释放,p1 name 的内存泄漏

解决方法:

需要手动复制

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>



struct Person01_st
{
	char *name;
	int age;
};

void test01()
{

	struct Person01_st p1;
	p1.name =(char *) malloc(sizeof(char) * 64);
	strcpy(p1.name, "aaa");
	p1.age = 1;

	struct Person01_st p2;
	p2.name = (char*)malloc(sizeof(char) * 128);
	strcpy(p2.name, "bbb");
	p2.age = 2;

	//手动复制

	//释放掉原来堆区内存
	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	//分配内存,+1是给'\0'  途中的红线
	p1.name = (char*)malloc(sizeof(strlen(p2.name)) + 1);
	strcpy(p1.name, p2.name);
	p1.age = p2.age;


	printf("p1=%s,%d\n", p1.name, p1.age);
	printf("p2=%s,%d\n", p2.name, p2.age);

	//释放内存
	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		free(p2.name);
		p2.name = NULL;
	}
}



int main() 
{
	test01();
	return 0;
}
7.2.1.1 结构体嵌套一级指针练习

要求

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>


struct Person01_st
{
	char *name;
	int age;
};



int main() 
{
	//分配结构体数组内存
	struct Person01_st** p = (struct Person01_st**)malloc(sizeof(struct Person01_st*) * 3);
	if (p == NULL)
	{
		return -1;
	}

	for (int i = 0; i < 3; i++)
	{
		//分配结构体内存
		struct Person01_st* p2 = (struct Person01_st*)malloc(sizeof(struct Person01_st));
		if (p2 == NULL)
		{
			return -1;
		}
		//分配name内存
		p2->name = (char*)malloc(sizeof(char) * 64);
		sprintf(p2->name, "name%d", i);
		p2->age = i;
		p[i] = p2;
	}

	//输出
	for (int i = 0; i < 3; i++)
	{
		printf("p%d,name:%s,age=%d\n", i, p[i]->name, p[i]->age);
	}

	//释放
	for (int i = 0; i < 3; i++)
	{
		free(p[i]->name);
		p[i]->name = NULL;

		free(p[i]);
		p[i] = NULL;
	}
	free (p);
	p = NULL;



	return 0;
}

7.2.2 结构体嵌套二级指针

7.2.2.1 结构体二级指针嵌套练习
7.2.2.1.1 自
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

typedef struct Teacher_st
{
	char* name;
	char** student;
}Teacher;



int main() 
{
	//创建老师二级结构体指针,用来存放结构体指针
	Teacher** tArr = NULL;
	tArr = (Teacher**)malloc(sizeof(Teacher*) * 3);


	for (int i = 0; i < 3; i++)
	{
		//创建结构体指针,用来指向结构体
		Teacher* teacher = (Teacher*)malloc(sizeof(Teacher));
		tArr[i] = teacher;

		//创建学生二级字符指针,用于存放字符一级指针(指向学生的信息)
		char** sArr = NULL;
		sArr = (char**)malloc(sizeof(char*) * 4);
		tArr[i]->student = sArr;

		//为tname分配空间
		char* tname = (char*)malloc(sizeof(char) * 64);
		if (tname == NULL)
		{
			return -1;
		}
		
		sprintf(tname, "teacher_%d", i);
		//第i个老师的name   tArr[i] 取第i个结构体指针
		//结构体指针使用 -> 指向成员
		tArr[i]->name= tname;
		

		for (int j = 0; j < 4; j++)
		{
			//为学生信息分配空间
			char* sname = (char*)malloc(sizeof(char) * 64);
			if (sname == NULL)
			{
				return -1;
			}
			sprintf(sname, "%s,student_%d",tname,j);
			//第i个老师的第j个学生
			//tArr[i] 选择老师,在老师中的第j个学生
			tArr[i]->student[j] = sname;
		}
	}

	//输出
	for (int i = 0 ; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("t = %s\n", tArr[i]->name);
			printf("s = %s\n", tArr[i]->student[j]);
		}
		printf("---------------------\n");
	}

	//释放
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			free(tArr[i]->student[j]);
		}
		free(tArr[i]->name);
		free(tArr[i]->student);
		free(tArr[i]);
	}
	free(tArr);


	return 0;
}
7.2.2.1.2 视频
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <typeinfo>

typedef struct Teacher_st
{
	char* name;
	char** student;
}Teacher;

void allocateSpace(Teacher*** tArray)
{
	if (tArray == NULL)
	{
		return;
	}

	//堆区分配内存
	Teacher **p = (Teacher**)malloc(sizeof(Teacher*) * 3);

	for (int i = 0; i < 3; i++)
	{
		//为老师结构体分配内存
		Teacher* teacher = (Teacher*)malloc(sizeof(Teacher));
		p[i] = teacher;
		//为老师姓名分配内存
		char* name = (char*)malloc(sizeof(char) * 64);
		//给姓名赋值
		sprintf(name, "teacher_%d", i);
		p[i]->name = name;

		//给老师带领学生数组分配内存
		p[i]->student = (char**)malloc(sizeof(char*) * 4);

		//给学生姓名分配内存 并且赋值
		for (int j = 0; j < 4; j++)
		{
			p[i]->student[j] = (char*)malloc(sizeof(char) * 64);
			sprintf(p[i]->student[j], "%s,student_%d",p[i]->name, j);
		}

	}
	//建立关系
	*tArray = p;

}

void printTeacher(Teacher **tArray)
{
	for (int i = 0; i < 3; i++)
	{
		printf("%s\n", tArray[i]->name);

		for (int j = 0; j < 4; j++)
		{
			printf("\t%s\n", tArray[i]->student[j]);
		}
	}
}

void freeTeacher(Teacher** tArray)
{
	for (int i = 0; i < 3; i++)
	{
		//释放老师姓名
		free(tArray[i]->name);
		for (int j = 0; j < 4; j++)
		{
			//释放学生姓名
			free(tArray[i]->student[j]);
		}
		//释放学生结构体
		free(tArray[i]->student);

		//释放老师
		free(tArray[i]);
	}
	//释放老师数组
	free(tArray);
}

void test01()
{
	//老师数组创建
	Teacher** tArray = NULL;

	//分配内存
	allocateSpace(&tArray);

	//打印所有老师和学生的信息
	printTeacher(tArray);

	//释放
	freeTeacher(tArray);
}

int main() 
{
	test01();
	return 0;
}

7.3 结构体偏移

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>


struct Teacher01_st
{
	char a;	//0 ~3
	int b;	//4~ 7
};

struct Teacher02_st
{
	char c;
	int d;
	//等价于Teacher01_st展开
	struct Teacher01_st e;
};

void test01()
{
	struct Teacher01_st t = { 'a',10 };
	struct Teacher01_st* p = &t;

	printf("b的属性偏移量为%d\n", (int)offsetof(struct Teacher01_st, b));

	printf("b的属性偏移量为%d\n", (int)&(p->b) - (int)p);			//将地址强转为int类型做减法
}

//通过偏移量 操作内存
void test02()
{
	struct Teacher01_st t = { 'a',10 };
	//偏移时步长为(char*),取时步长为(int*),最后解引用
	printf("t.b = %d\n", *(int*)((char*)&t + offsetof(struct Teacher01_st, b)));
	printf("t.b = %d\n", *(int*)((int*)&t + 1));
}

void test03()
{
	struct Teacher02_st k = { 'a',10,'b',20 };
	//二次偏移
	int offset1 = offsetof(struct Teacher02_st, e);
	int offset2 = offsetof(struct Teacher01_st, b);

	printf("%d\n", *(int*)((char*)&k + offset1 + offset2));

	printf("%d\n", ((struct Teacher01_st*)((char*)&k + offset1))->b);
}

int main()
{
	test01();
	test02();
	return 0;
}

7.4 结构体字节对齐

7.4.1 内存对齐

内存对齐的好处:以空间换时间

结构体计算内存对齐原则

第一个属性开始 从0开始计算偏移量
第二个属性 要放在该属性的大小 ‘与 对齐模数比 取小的值的 整数倍上 
当所有属性都计算完毕后 , 整体做二次偏移,将上面计算的结果 扩充到 这个结构体最大的数据类型 的整数倍 与 对齐模数比取小的值

默认的对齐模数为8

//查看对齐模数,默认对齐模数为8
//可以修改为2的n次方
#pragma pack (show)		
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>

//#pragma pack (show)		//查看对齐模数,默认对齐模数为8

#pragma pack (1)		//对齐模数可以改为2的n次方


//对齐原则

//第一个属性开始 从0开始计算偏移量
//第二个属性 要放在该属性的大小 ‘与 对齐模数比 取小的值的 整数倍上 
//当所有属性都计算完毕后 , 整体做二次偏移,将上面计算的结果 扩充到 
//这个结构体最大的数据类型 的整数倍 与 对齐模数比取小的值


typedef struct Teacher_st
{
	int a;		//0~3
	char b;		//4~7
	double c;	//8~15
	float d;	//17~19
}Teacher;

void test01()
{
	//大小为24  , 所有属性相加大小为20,二次扩充要为最大属性(double 8)的整数倍 扩充到24
	//当对齐模数为1时,大小为17,没有内存对齐
	printf("teacher sizeof = %d \n", sizeof(Teacher));


}


int main()
{
	test01();
	return 0;
}

7.4.2 内存对齐案例

当结构体中嵌套结构体,子结构体只要放在子结构体中最大数据类型的整数倍

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>




//对齐原则

//第一个属性开始 从0开始计算偏移量
//第二个属性 要放在该属性的大小 与 对齐模数比 取小的值的 整数倍上 
//当所有属性都计算完毕后 , 整体做二次偏移,将上面计算的结果 扩充到 
//这个结构体最大的数据类型 的整数倍 与 对齐模数比取小的值


typedef struct Teacher_st
{
	int a;		//0~3
	char b;		//4~7
	double c;	//8~15
	float d;	//17~19
}Teacher;


//最大类型为double 8,二次偏移结果不变 为40
typedef struct Teacher_st02
{
	char a;		//0~7
	//第二个属性 要放在该属性的大小 与 对齐模数比 取小的值的 整数倍上 
	//不是取整个结构体的大小,而是结构体中最大的属性的大小
	//在Teacher中最大为double 8,所以需要是8的整数倍
	Teacher b;	//8~31 等价于Teacher结构体 可以拆开来看
	double c;	//32~39
}Teacher02;


void test01()
{
	//大小为40 ,所有属性相加大小为40,二次扩充要为最大属性(double 8)的整数倍 
	// 40为8的整数倍,所以不变
	printf("teacher sizeof = %d \n", sizeof(Teacher02));
}


int main()
{
	test01();
	return 0;
}

8 文件操作

8.1 文件相关概念

8.1.1 文件的概念

8.1.2 流的概念

input --- 写文件 输入

output --- 读文件 输出(显示器,文件)

8.1.2.1 二进制流

8.2 文件的操作

8.2.1 文件流总览

8.2.2 文件指针

8.2.3 文件缓存区

        

8.2.4 文件打开关闭

8.2.4.1 文件打开(fopen)

头文件:stdio.h

功能是打开一个文件,其声明格式是:

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

参数:

filename : 字符串,表示要打开的文件名称。 mode : 字符串,表示文件的访问模式,可以是以下表格中的值。

返回值:

成功:该函数返回一个 FILE 指针(文件指针)。

失败:返回 NULL。

文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件(从文件获取)出错
“w”(只写)为了输出数据,打开一个文本文件(向文件输出)建立一个新的文件
“a”(追加)向文本文件尾添加数据出错
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出文件,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)允许读和写,如果文件不存在则创建,如果文件存在则把文件长度截断为0再重新写建立一个新的文件
“a+”(读写)打开一个文件,在文件末尾读进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个二进制文本文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件末尾进行读和写建立一个新的文件
8.2.4.2 文件关闭(fclose)

头文件:#include <stdio.h>

fclose()函数用来关闭当前文件流,其原型为: int fclose(FILE * stream);

【参数】stream为文件流指针。

【返回值】若关文件动作成功则返回0,有错误发生时则返回EOF,并把错误代码存到errno。

fclose()用来关闭fopen()打开的文件,此动作会让缓冲区内的数据写入文件中,并释放系统所提供的文件资源。

注意:使用fopen()打开的文件,一定要记得使用fclose()关闭,否则会出现很多意想不到的情况,例如对文件的更改没有被记录到磁盘上,其他进程无法存取该文件等。

8.2.5 文件读写函数

按照字符读写文件:

fgetc() - 用于读取文件中的一个字符。

返回值:成功返回字符ASCLL码,失败返回EOF(end of file)

fputc() - 用于将一个字符写入文件。

按照行读写文件:

fgets() - 用于从文件中读取一行。

fputs()-用于将一行字符写入文件

按照块读写文件

fread()

fwrite()

按照格式化读写文件:

fprintf() - 用于格式化和写入字符串到文件。

fscanf() - 用于读取并格式化输入自文件。

按照随机位置读写文件:

fseek()

ftell()

ewind()

8.2.5.1 文件读写-字符方式读写

fgetc() - 用于读取文件中的一个字符。

返回值:成功返回字符ASCLL码,失败返回EOF(end of file)

fputc() - 用于将一个字符写入文件。

判断是否读到文件尾

    while ((c = fgetc(f_read)) != EOF)

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>


void test01()
{
	FILE* f_write = fopen("./test1.txt", "w+");
	if (f_write == NULL)
	{
		return;
	}

	char buf[] = "hello world";

	//写文件,fputc
	for (int i = 0; i < strlen(buf); i++)
	{
		fputc(buf[i], f_write);
	}
	//关闭文件
	fclose(f_write);

	//读文件
	FILE* f_read = fopen("./test1.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	char c;
	while ((c = fgetc(f_read)) != EOF)
	{
		printf("%c",c);
	}
	printf("\n");

	fclose(f_read);
}


int main()
{
	test01();
	return 0;
}
8.2.5.2 文件读写-按行的方式读写

按照行读写文件:

fgets() - 用于从文件中读取一行。

fputs()-用于将一行字符写入文件

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>


void test02()
{
	FILE* f_write = fopen("./test1.txt", "w+");
	if (f_write == NULL)
	{
		return;
	}

	//按行写入,手动输入\n
	char* buf[] =
	{
		"锄禾日当午\n",
		"汗滴禾下土\n",
		"谁之盘中餐\n",
		"粒粒皆辛苦\n",
	};

	//写文件,fputs
	for (int i = 0; i < 4; i++)
	{
		fputs(buf[i], f_write);
	}
	//关闭文件
	fclose(f_write);

	//读文件
	FILE* f_read = fopen("./test1.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	while (!feof(f_read))
	{
		//当读到最后一行时,还会进行一次循环,所以需要初始化,不然最后一行会输出两次
		char temp[1024] = {0};
		fgets(temp, 1024,f_read);
		printf("%s", temp);
	}
	
	fclose(f_read);
}


int main()
{
	test02();
	return 0;
}
8.2.5.3 文件读写-按照块读写文件

自定义类型的读写

按照块读写文件

参数:数据地址 块大小 块个数 文件指针

fread()

fwrite()

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

typedef struct Student_st
{
	char* name;
	int age;
}st;

//按照块读写文件,fread(),fwrite()
void test03()
{
	//写文件 rb以二进制 读
	FILE* f_write = fopen("./test1", "wb");
	if (f_write == NULL)
	{
		return;
	}

	st student[] =
	{
		{"name1",10},
		{"name2",20},
		{"name3",30},
		{"name4",40},
	};

	//每次写一块
	for (int i = 0; i < 4; i++)
	{
		//参数:数据地址 块大小 块个数 文件指针
		fwrite(&student[i], sizeof(st), 1, f_write);
	}

	fclose(f_write);

	//读文件
	FILE* f_read = fopen("./test1", "rb");
	if (f_read == NULL)
	{
		return;
	}

	st temp[4];
	//一次读4块
	fread(&temp, sizeof(st), 4, f_read);


	//输出
	for (int i = 0; i < 4; i++)
	{
		printf("%s,%d\n", temp[i].name, temp[i].age);
	}


	fclose(f_read);


}


int main()
{
	test03();
	return 0;
}
8.2.5.4 文件读写-按格式化读写文件

按照格式化读写文件:

fprintf() - 用于格式化和写入字符串到文件。

fscanf() - 用于读取并格式化输入自文件。

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>



void test04()
{
	//写文件
	FILE* f_write = fopen("./test1.txt", "w");

	if (f_write == NULL)
	{
		return;
	}

	fprintf(f_write, "hello world %d", 12345);
	fclose(f_write);

	//读文件
	FILE* f_read = fopen("./test1.txt", "r");
	if (f_read == NULL)
	{
		return;
	}

	char buf[1024];
	while (!feof(f_read))
	{
		//格式化读
		fscanf(f_read, "%s", buf);
		printf("%s", buf);
	}
	fclose(f_read);
}


int main()
{
	test04();
	return 0;
}
8.2.5.5 文件读写-随机位置

按照随机位置读写文件:

fseek()

perror("提示错误信息") 

ewind() //将光标置首

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

typedef struct Student_st
{
	char* name;
	int age;
}st;

//随机读 fseek
void test05()
{
	//写文件 rb以二进制 读
	FILE* f_write = fopen("./test1", "wb");
	if (f_write == NULL)
	{
		perror("文件打开失败");	//errno 宏,全局变量
		return;
	}

	st student[] =
	{
		{"name1",10},
		{"name2",20},
		{"name3",30},
		{"name4",40},
	};

	//每次写一块
	for (int i = 0; i < 4; i++)
	{
		//参数:数据地址 块大小 块个数 文件指针
		fwrite(&student[i], sizeof(st), 1, f_write);
	}

	fclose(f_write);

	//读文件
	FILE* f_read = fopen("./test1", "rb");
	if (f_read == NULL)
	{
		return;
	}

	st temp;
	//移动文件光标
	//从文件起始位置开始
	//fseek(f_read, sizeof(st) * 2, SEEK_SET);

	//从文件尾往回,如果是-1 则读取的是第四个元素
	fseek(f_read, - (long)sizeof(st) * 2, SEEK_END);

	//将文件光标置首
	rewind(f_read);

	//读取第三个数据
	fread(&temp, sizeof(st), 1, f_read);


	//输出
	printf("%s,%d\n", temp.name, temp.age);
	fclose(f_read);

}


int main()
{
	test05();
	return 0;
}
8.2.5.6 文件读写注意事项
  1. 字符方式读文件 如果用feof会有滞后性,解决方案:直接和EOF对比( (c = fgetc(...,...) )!= EOF )

  2. 如果结构体中属性创建在堆区(指针),保存数据到文件的时候,不要将指针放入文件中,需要保存具体的数据

8.2.5.7 文件练习-配置文件

需求

fgets返回值

成功返回字符串,错误或文件结束条件返回NULL。 

使用 feof 或 ferror 来确定是否发生错误。 

strchr

  • strchr函数功能为在一个串中查找给定字符的第一个匹配之处。函数原型为:char *strchr(const char *str, int c),即在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。strchr函数包含在C 标准库 <string.h>中。
  • str-- 要被检索的 C 字符串。
  • c-- 在 str 中要搜索的字符。
  • 返回一个指向该字符串中第一次出现的字符的指针,如果字符串中不包含该字符则返回NULL空指针。
  • 注意,该函数式大小写区分的函数,大小字母和小写字母被看成是不一样的

memset

void *memset(void *s, int c, size_t n);

s指向要填充的内存块。

c是要被设置的值。

n是要被设置该值的字符数。

返回类型是一个指向存储区s的指针。

清空操作:

char buf[1024];

memset(buf,0,1024);

config.h

#pragma once
#ifndef _config
#define _config

//配置信息结构体
struct ConfigInfo_st
{
	char key[64];
	char value[64];
};

//获取文件有效行数
int getFileLine(const char* filePath);

#endif // !_config


//判断传入字符串是否有效,是返回1,否返回0
int isinvalidLine(char* str);

//文件分析
void parseFile(const char* filePath, int line, struct ConfigInfo_st** configInfo);

//根据key获取对应value
char* getValueByKey(char* key, struct ConfigInfo_st* configInfo, int line);

//释放内存
void free(struct ConfigInfo_st* configInfo);

config.c

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//获取文件有效行数
int getFileLine(const char* filePath)
{
	FILE* file = fopen(filePath, "r");
	if (file == NULL)
	{
		perror("文件打开失败!");
		return -1;
	}
	char buf[1024] = { 0 };
	int line = 0;
	while (fgets(buf, 1024, file) != NULL)
	{
		if (isinvalidLine(buf))
		{
			line++;
		}
		
	}
	return line;
}

//有效行数
int isinvalidLine(char* str)
{
	if (str[0] == '\n' || (strchr(str, ':') == NULL))
	{
		return 0;
	}
	return 1;
}


//文本解析
void parseFile(const char* filePath, int line, struct ConfigInfo_st** configInfo)
{
	struct ConfigInfo_st* config = (struct ConfigInfo_st*)malloc(sizeof(struct ConfigInfo_st) * line);
	if (config == NULL)
	{
		return;
	}
	FILE* file = fopen(filePath, "r");
	if (file == NULL)
	{
		return;
	}

	char buf[1024];
	int index = 0;
	while (fgets(buf, 1024, file) != NULL)
	{
		if (isinvalidLine(buf))
		{
			//key:value
			//清空key和value
			memset(config[index].key, 0, 64);
			memset(config[index].value, 0, 64);

			char* pos = strchr(buf, ':');
			//截取key的数据
			strncpy(config[index].key, buf, pos - buf);
			
			//截取value的数据
			//从‘:’后开始   pos+1
			//最后-1为减去换行符
			strncpy(config[index].value, pos+1,strlen(pos+1)-1);
			index++;
		}
		memset(buf, 0,1024);
	}



	*configInfo = config;


}


//根据key获取对应value
char* getValueByKey(char* key, struct ConfigInfo_st* configInfo, int line)
{
	for (int i = 0; i < line; i++)
	{
		if( strcmp(configInfo[i].key,key)==0)
		{
			return configInfo[i].value;
		}
	}
	return NULL;
}


//释放内存
void free(struct ConfigInfo_st* configInfo)
{
	if(configInfo == NULL)
	{
		return;
	}

	free(configInfo);
	configInfo = NULL;
}

main.c

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"



int main()
{

	int line = getFileLine("./test1.txt");

	printf("%d", line);

	//将文件中的数据解析 并放到数组中
	
	struct ConfigInfo_st* configInfo = NULL;
	parseFile("test1.txt", line, &configInfo);

	//根据key获取
	printf("value = %s", getValueByKey("heroName", configInfo, line));
	return 0;

	//释放内存
	free(configInfo);
	configInfo = NULL;
	return 0;
}

9 链表

9.1 链表概念

9.2 链表的分类

链表的分类1:

静态链表 链表分配在栈上

动态链表 链表分配在堆上

链表的分类2:

单向链表 , 双向链表

单项循环链表

双向循环链表

9.3 静态链表 动态链表

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

typedef struct LinkNode_st
{
	//数据域
	int data;
	//指针域
	struct LinkNode_st* next;
}Node;

//静态链表
void test1()
{
	//节点创建
	Node node1 = { 1,NULL };
	Node node2 = { 2,NULL };
	Node node3 = { 3,NULL };
	Node node4 = { 4,NULL };
	Node node5 = { 5,NULL };

	//建立关系
	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
	node4.next = &node5;

	//遍历链表
	Node* p = &node1;
	while (p != NULL)
	{
		printf("%d\n", p->data);
		p = p->next;
	}


}


//动态链表
void test2()
{
	//创建节点
	Node* node1= (Node*)malloc(sizeof(Node));
	Node* node2= (Node*)malloc(sizeof(Node));
	Node* node3= (Node*)malloc(sizeof(Node));
	Node* node4= (Node*)malloc(sizeof(Node));
	Node* node5= (Node*)malloc(sizeof(Node));

	//赋值 并建立关系
	node1->data = 1;
	node1->next = node2;

	node2->data = 2;
	node2->next = node3;

	node3->data = 3;
	node3->next = node4;

	node4->data = 4;
	node4->next = node5;

	node5->data = 5;
	node5->next = NULL;

	//遍历链表
	Node* p = node1;
	while (p != NULL)
	{
		printf("%d\n", p->data);
		p = p->next;
	}

	//释放
	free(node1);
	free(node2);
	free(node3);
	free(node4);
	free(node5);

}



int main()
{
	test2();
}

9.  清空链表 销毁链表

清空链表

struct LinkNode* p =Head->next;

while(p != NULL)
{
    //记录下一个节点位置
    struct Node* nextNode = p -> next;

    //释放当前节点
    free(p);

    //节点向后移动
    p = nextNode;

}

Head -> next = NULL;

销毁链表

//清空链表
...

//free头结点
free(Head);

函数指针

指向函数入口地址的指针

函数名 函数的入口地址

函数没有参数

将p强制类型转换 (void (*)())p

函数有参数

返回值类型 (指针变量名)(参数列表)
void (*p)(int)

函数指针的定义方式

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

void func(int a,char b)
{
	printf("hello world\n");
}

void test1()
{
	//先定义出函数的类型,再通过类型定义函数指针变量
	typedef void(FUNC_TYPE)(int, char); //定义出一个函数类型,返回值是void,形参列表(int,char)
	FUNC_TYPE* pFunc = func;
	pFunc(1, 'a');
}

void test2()
{
	//先定义出函数指针的类型,再通过类型定义函数指针变量
	typedef void(*FUNC_TYPE)(int, char);
	FUNC_TYPE pFunc = func;
	func(1, 'a');
}

void test3()
{
	//直接定义函数指针变量
	void (*pFunc)(int, char) = func;
	pFunc(1, 'a');
}

//函数指针数组
void func1()
{
	printf("func1\n");
}
void func2()
{
	printf("func2\n");
}
void func3()
{
	printf("func3\n");
}
void func4()
{
	printf("func4\n");
}

void test4()
{
	//函数指针数组的定义方式
	void(*func_arr[4])();
	func_arr[0] = func1;
	func_arr[1] = func2;
	func_arr[2] = func3;
	func_arr[3] = func4;
	for (int i = 0; i < 4; i++)
	{
		//没有参数 函数指针也需要加括号
		func_arr[i]();
	}
}

int main()
{
	test1();
	test2();
	test3();
	test4();

}

回调函数

函数指针做函数的参数---回调函数

打印任意类型数据

提供函数 可以打印任意类型数据

参数一 数据地址 参数二 打印函数地址

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

typedef struct student_st
{
	int age;
	char* name;
}st;

//提供一个函数,可以打印任意类型数据
void func(void* data,void(*p)(void*))
{
	p(data);
}

//int
void pi(void* data)
{
	int* num = (int*)data;
	printf("%d\n", *num);
}

//double
void pd(void* data)
{
	double* num = (double*)data;
	printf("%f\n", *num);
}

//自定义类型
void ps(void* data)
{
	st* num = (st*)data;
	printf("%d,%s\n", num->age, num->name);
}

//由用户自己提供打印的函数
void test1()
{
	int a = 10;
	func(&a, pi);

	double b = 1.1;
	func(&b, pd);

	st s = { 10,"aaa" };
	func(&s, ps);
}



int main()
{
	test1();
}

打印任意类型数组,在数组中找元素

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

typedef struct student_st
{
	int age;
	char name[64];
}st;

//参数一 传入数组的首地址 参数二 数组中每个元素占用的内存空间 参数三 数组长度 参数四 回调函数
void printArray(void* data,int size,int len,void(*myPrint)(void*))
{
	char* p = (char*)data;//利用p接受数组首地址,步长设置为1
	for (int i = 0; i < len; i++)
	{
		char* pAddr = p + i * size;
		//因为不知道类型   打印操作交给用户printf
		myPrint(pAddr);
	}
}

//int
void pi(void* data)
{
	printf("%d\n", *(int*)data);
}

//char
void pc(void* data)
{
	printf("%c\n", *(char*)data);
}


//自定义类型
void ps(void* data)
{
	st* s = (st*)data;
	printf("%d,%s\n", s->age, s->name);
}


//由用户自己提供打印的函数
void test1()
{
	int a[] = {1,2,3,4,5};
	int len1 = sizeof(a) / sizeof(*a);
	printArray(a,sizeof(int), len1, pi);

	char c[] = { 'a','b','c','d','e','\0' };
	int len2 = sizeof(c) / sizeof(*c);
	printArray(c, sizeof(char), len2, pc);

	st s1 = { 1,"a" };
	st s2 = { 2,"b" };
	st s3 = { 3,"c" };
	st s[3];
	s[0] = s1;
	s[1] = s2;
	s[2] = s3;
	printArray(s, sizeof(st), 3, ps);
}



int main()
{
	test1();
}
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

typedef struct student_st
{
	int age;
	char name[64];
}st;

//参数一 传入数组的首地址 参数二 数组中每个元素占用的内存空间 参数三 数组长度 参数四 回调函数
void printArray(void* data, int size, int len, void(*myPrint)(void*))
{
	char* p = (char*)data;//利用p接受数组首地址,步长设置为1
	for (int i = 0; i < len; i++)
	{
		char* pAddr = p + i * size;
		//因为不知道类型   打印操作交给用户printf
		myPrint(pAddr);
	}
}

//int
void pi(void* data)
{
	printf("%d\n", *(int*)data);
}

//char
void pc(void* data)
{
	printf("%c\n", *(char*)data);
}


//自定义类型
void ps(void* data)
{
	st* s = (st*)data;
	printf("%d,%s\n", s->age, s->name);
}

//查找数组元素
int findArr(void* data, int size, int len,void* tag,int (*myCompare)(void*,void*))
{
	char* p = (char*)data;
	for (int i = 0; i < len; i++)
	{
		//获取到每个元素的首地址
		char* pAddr = p + i * size;
		if (myCompare(pAddr, tag))
		{
			return 1;
		}
	}
	return 0;
}

int myCompare(void* data, void* tag)
{
	st* s1 = (st*)data;
	st* s2 = (st*)tag;
	if (s1->age == s2->age && !strcmp(s1->name,s2->name))
	{
		return 1;
	}
	return 0;
}

//由用户自己提供打印的函数
void test1()
{
	int a[] = { 1,2,3,4,5 };
	int len1 = sizeof(a) / sizeof(*a);
	printArray(a, sizeof(int), len1, pi);

	char c[] = { 'a','b','c','d','e','\0' };
	int len2 = sizeof(c) / sizeof(*c);
	printArray(c, sizeof(char), len2, pc);

	st s1 = { 1,"a" };
	st s2 = { 2,"b" };
	st s3 = { 3,"c" };
	
	st s[3];
	s[0] = s1;
	s[1] = s2;
	s[2] = s3;
	int len3 = sizeof(s) / sizeof(*s);
	printArray(s, sizeof(st), len3, ps);

	st tag = { 3,"c" };
	//有返回1,无返回0
	int k = findArr(s,sizeof(st),len3,&tag,myCompare);
	if (k == 1)
	{
		printf("找到\n");
	}
	else {
		printf("未找到\n");
	}
}



int main()
{
	test1();
}

11 预处理

11.1 文件包含指令(#include)

11.1.1 文件包含处理

11.2 宏定义

11.2.1 无参数的宏定义(宏常量)

11.2.2 带参数的宏定义(宏函数)

11.3 条件编译

11.3.1 基本概念

第二种常用于检测头文件是否重复

11.3.2 条件编译

11.4 一些特殊的预定宏

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "config.h"

//预处理指令
//1.头文件包含
// ""自定义头文件  <>系统头文件


//2.宏定义
//宏常量 不重视作用域,定义了就可以在后续中使用
//没有数据类型
//可以利用undef卸载宏

//宏函数 用于频繁短小函数 封装为宏函数
//声明函数时 要保证运算的完整性 加()
void test01()
{
#define MAX 1999
//#undef MAX

}


//3 条件编译
#define DEBUG

#ifdef DEBUG
void func()
{
	printf("DEBUG版本调用\n");
}
#else
void func()
{
	printf("DEBUG版本没有调用\n");
}
#endif // DEBUG


//特殊的宏

void func2(char* p)
{
	if (p == NULL)
	{
		printf("文件:%s的%d行出错\n", __FILE__, __LINE__);
	}
	printf("日期:%s\n", __DATE__);
	printf("时间:%s\n", __TIME__);
}

void test02()
{
	func2(NULL);
}


int main()
{
	//需要调用,函数名相同
	test02();
}

12 动态库的封装和使用

12.1 库的基本概念

12.2.1 静态库的创建

12.2.3 静态库的优缺点

静态库 浪费资源,更新发布麻烦

动态库 节省资源,更新发布简单

13 递归函数

13.1 递归函数的基本概念

13.2 普通函数调用

13.3 递归函数调用

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

/*
递归函数
函数调用自己
递归必须有结束条件
*/

//递归 逆序输出 a b c d e
void test01(char c)
{

	if (c < 101)
	{
		test01(c + 1);
	}
	printf("%c", c);
}

int main()
{
	test01('a');
}

注意输出的位置

13.4 斐波那契

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>


int fibonacci(int pos)
{
	if (pos == 1 || pos == 2)
	{
		return 1;
	}

	return fibonacci(pos - 1) + fibonacci(pos - 2);

}

int main()
{
	//第  位的斐波那契数为
	int ret = fibonacci(10);
	printf("%d\n", ret);

}

面相接口

complany1.h

#pragma once

#ifndef __MY1__
#define __MY1__

typedef struct Player_st
{
	char name[64];
	int level;
	int exp;		//经验
}pl;


//初始化游戏 参数1 人物指针 参数2 人物姓名
void INIT_GAME1(void** player, char* name);

//核心战斗
int FIGHT_GAME1 (void* player, int gameDiff);

//查看玩家信息
void PRINT_GAME1(void* palyer);

//离开游戏
void EXIT_GAME1(void* player);

//判断玩家是否胜利
int isWin(int winRate, int diff);


#endif // !__MY1__

complany1.cpp

#include "complany1.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


//初始化游戏 参数1 人物指针 参数2 人物姓名
void INIT_GAME1(void** player, char* name)
{
	printf("333\n");
	struct Player_st* py = NULL;
	py = (struct Player_st*)malloc(sizeof(struct Player_st));
	if (py == NULL)
	{
		return;
	}
	//创建成功 赋值
	//sprintf(py->name, "%s", name);
	strcpy(py->name, name);
	py->exp = 0;
	py->level = 0;
	*player = py;
}


//核心战斗
int FIGHT_GAME1(void* player, int gameDiff)
{
	pl* py = (pl*)player;


	//给玩家增加的经验
	int addExp = 0;

	switch (gameDiff)
	{
	case 1:
		addExp = isWin(90, 1);
		break;
	case 2:
		addExp = isWin(70, 2);
		break;
	case 3:
		addExp = isWin(50, 3);
		break;
	default:
		break;
	}
	//将经验累加到玩家
	py->exp += addExp;
	//升级
	py->level = py->exp / 10;

	if (addExp == 0)
	{
		//战斗失败
		return 0;
	}
	else
	{
		//战斗胜利
		return 1;
	}

}

//判断玩家是否胜利
int isWin(int winRate, int diff)
{
	int r = rand() % 100 + 1;
	if (r <= winRate)
	{
		//胜利
		return diff*10;
	}
	else
	{
		//失败
		return 0;
	}

}


//查看玩家信息
void PRINT_GAME1(void* palyer)
{
	pl* py = (pl*)palyer;

	printf("name:%s exp:%d level:%d\n", py->name, py->exp, py->level);
}

//离开游戏
void EXIT_GAME1(void* player)
{
	if (player == NULL)
	{
		return;
	}
	free(player);
	player = NULL;
}

main.c

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include "complany1.h"

//初始化游戏 参数1 人物指针 参数2 人物姓名
typedef void(*INIT_GAME)(void** player, char* name);

//核心战斗
typedef int(*FIGHT_GAME)(void* player,int gameDiff);

//查看玩家信息
typedef void(*PRINT_GAME)(void* palyer);

//离开游戏
typedef void(*EXIT_GAME)(void* player);


//甲方功能实现
void playGame(INIT_GAME init, FIGHT_GAME fight, PRINT_GAME printGame, EXIT_GAME exitGame)
{
	void* player;

	//初始化游戏
	printf("请输入玩家姓名:\n");
	char buf[1024];
	scanf("%s", buf);
	init(&player, buf);

	int diff = -1;


	//战斗
	printf("选择游戏的难度\n");
	printf("1.简单\n");
	printf("2.中等\n");
	printf("3.困难\n");
	scanf("%d", &diff);
	int ret = fight(player, diff);
	if (ret == 0)
	{
		printf("游戏结束,请再接再厉\n");
	}
	else
	{
		printf("恭喜胜利\n");
		//查看玩家状态
		printf("玩家当前的状态为:\n");
		printGame(player);
	}

	//退出游戏
	exitGame(player);

}


int main()
{
	playGame(INIT_GAME1, FIGHT_GAME1, PRINT_GAME1, EXIT_GAME1);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值