指针:

什么是指针:指针是一种数据类型(无符号整数,代表内存编号),使用它定义指针变量。
	0~4G(32个1)4294967295 byte
什么情况下使用指针:
	1、函数之间共享变量(全局变量有命名冲突,不会被释放,浪费内存)
	2、优化传递效率
		因为C语言采用的是值传递(内存拷贝),会随着变量字节数的增加而降低运行效率。
		而传递变量的地址,永远只拷贝4|8字节。
		void func(const int * p);
		但使用指针变量的值可能会被修改,可以配合const进行保护。	
	3、配合堆内存
如何使用指针:
	定义:类型 *变量名_p;
		1、与普通变量一样,默认值不确定,为了安全一般初始化NULL。
		2、一个*只能定义一个指针变量
			int *p1,*p2;
		3、指针变量与普通的用法不同,为了避免混用,一般从名字上加以区别。
		4、指针变量的类型决定了解决引用时访问的字节数。
	赋值:变量名_p = 地址;
		int* p = NULL;
		1、注意地址的类型
		2、void*可以与任意类型的指针进行自动转换(C++中不可以)
		3、要保障地址与物理内存有对应关系(映射过)。
	解引用:*p;
		根据指针变量中存储的内存编号,而访问内存中的数据。
		这个过程可以会有段错误,但这是由于赋值了有问题的地址。
使用指针要注意的问题:
	1、野指针:指向的目标不确定,解引用时不一定会出错,但未知的危险最可怕。
		而且野指针一旦产生就无法分辨,而预防的方法就是不制造野指针。
		1、定义指针时一定要初始化。
		2、指向的目标被释放后,要及时置空。
		3、不要指向随时可能被释放的目标。
	2、空指针:指针变量的值等于NULL,对这个地址解引用访问时,一定会产生段错误。
		因为它存储的是操作系统重启时所需要的数据。
		而预防的方法就是解引用前判断(来历不明) if(NULL == p)
指针的运算:
	指针+/-整数 = 指针+/-(宽度)*整数
	指针-指针 = (指针-指针)/宽度

指针与数组名:
	1、数组名就一个特殊的地址,它就代表数组的第一个元素的首地址,也能当指针使用。
		arr[i] <=> *(地址+i);
		因此指针也能使用[]运算符
	2、指针与目标内存是指向关系,而数组名是对应关系。
	3、数组当函数的参数就蜕变为了指针变量,长度丢失,安全性不保障。
		void fun(int* const arr,size_t len);
指针与const的配合使用:
	const int* p;
	int const * p;
	int * const p;
	const int * p;
	int const * const p;
指针的高级应用:
	指针数组:可以把无序的离散的数据,归纳到一起。
	数组指针:专门指向数组指针
	二级指针:指向指针的指针
	函数指针:指向函数的指针

字符串:

由字符组成的串型数据结构,它的结束标志是'\0'。
字符串存在的形式:
	字符数组:char arr[5] = {'a','b','c','d'};
		一般存储在栈,也可以存储在堆。
		要考虑'\0'的位置
	字符串字面值:由双引号包括的若干个字符,"hehe"。
		以地址形式存在,需要使用const char* str;指针指向。
		数据存在只读段,如果强行修改只会出现段错误。
		背后隐藏着'\0';
	char str[] = "hehe";
	一般使用字符串字面值来初始化字符数组。
字符串的输出:
	printf %s,puts,fprintf
字符串的输入:
	scanf %s:不能输入空格
	gets:不限制长度
	fgets:可能会接受到'\n',或者输入缓冲区中残留数据
字符串常见的操作:
	strlen/strcat/strcpy/strcmp
	strncat/strncpy/strncmp
	memset/memcpy/strstr/strchr
	sprintf/sscanf 用于拼接/解析字符串,非常好用
	字符数据 -> 数据 计算 数据 -> 字符数据

堆内存管理

C语言中没有内存管理的语句,只能借助标准库中的函数进行管理堆内存。
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);

当向malloc首次申请内存时,malloc手中也没有内存,malloc会向系统申请,系统会映射33页内存交给malloc管理,
之后再向malloc申请内存,malloc会直接从33页内存中分配,直到33页用完,再向操作系统申请。
	访问malloc分配的内存时,可以越界,但不要超过33页范围。

内存泄漏:
	1、指针管理失误,指向其他位置。
	2、free语句没有执行到。
	3、free语句忘记写。
	就内存没释放,有申请新内存,导致可用的内存越来越少,速度越来越慢。
	前提:程序没有结束,当程序结束后属于它的所有资源都会被系统回收。
内存碎片:
	已经释放的内存,但不能被再次使用,这叫内存叫做内存碎片。
   	内存碎片不是错误,它是由于内存的释放时间和分配时间不协调造成的。
   	内存碎片无法避免(天然形成的),只能尽量减少:
   		1、尽量使用栈内存,只有在数据量比较多的时候再使用堆内存。
   		2、尽量申请大块内存自己管理。
   		3、不要频繁的申请释放内存。

预处理指令:

把C代码翻译成标准的C代码叫预处理、负责翻译的程序叫预处理器、被翻译的代码叫预处理指令。
查看预处理的结果:
	gcc -E code.c 直接查看预处理的结果
	gcc -E code.c -o code.i 把预处理的结果保存到文件中
宏定义:
	宏常量:用一个有意义的单词代表一个字面值数据在代码中使用,在预处理时把单词替换成数据
		优点:提高可读性、安全、扩展方便
	宏函数:宏函数不是真正的函数,是带参数的宏,只是使用的方法类似函数
		预处理时参数会代入到表达式中,宏名会替换成后面的表达式。
		优点:运行速度快(没有参数传递),类型通用,只有极精简的代码段才适合定义宏函数
		缺点:不会进行类型检查,也没有返回值,只有一个计算结果,大量使用会增加代码段的冗余。
	预定义的宏:
		__FILENAME__
		__func__
		__DATE__
		__TIME__
		__LINE__
	条件编译:
		#if
		#elif
		#else
		#endif
		#ifndef
		#ifndef
		头文件卫士

复合数据类型:

结构 struct
设计数据类型
typedef struct Student
{
	char name[20];
	char sex;
	short age:1;
}Student;

定义结构变量
	Student stu;
	Student* stup = malloc(sizeof(Student));
访问结构成员
	stu.name,stu.sex,stu.age
	stup->name,stup->sex,stup->age
计算结构的字节数:
	注意:成员的顺序不同会影响结构的字节数。
	对齐:假定从零地址开始,每成员的起始地址编号,必须是它本身字节数的整数倍。
	补齐:结构的总字节数必须是它最大成员的整数倍。
	注意:在Linux系统下计算补齐、对齐时,成员超过4字节按4字节计算

联合 union
	从语法上来说与结构的用法基本类似,每个成员都从零地址开始,所有成员共有一块内存。
	1、使用联合判断大小端
	2、联合的总字节数计算,不需要对齐,但有补齐
枚举 enum
	值受限的int类型,把变量合法的值列举出来,除此以外不能等于其他的值
	枚举值是常量,可以直接使用在case后,常与switch语句配合使用

文件操作:

文件分类:
	文本文件:记录的是字符串的二进制
	二进制文件:直接把数据补码记录到文件中
文件打开:
	FILE *fopen(const char *path, const char *mode);
	"r"	以只读方式打开文件,如果文件不存在则打开失败,返回值为空。
	"r+" 在"r"的基础上增加写权限。
	"w" 以只写方式打开文件,如果文件不存在则创建,如果文件存在则把内容清空。
	"w+" 在"w"的基础上增加读取权限。
	"a" 以只写方式打开文件,如果文件不存在则创建,如果文件存在则把内容保留,与"w"区别是当有新数据写入,会追加到文件的末尾。
	"a+" 在"a"的基础上增加读权限。 
	"b" 在linux系统下没有用,表示以二进制格式打开文件。
		在Windows系统下不加b '\n' 写到文件中 系统会写入'\n\r',加b则写'\n'时只写入'\n'.
读写文本内容:
	int fprintf(FILE *stream, const char *format, ...);
	功能:把数据以文本形式写入到文件中
	stream:文件指针,fopen函数的返回值
	format:格式化控制符,点位符等
	...:要写入的变量。
	返回值:成功写入的变量个数。	
	int fscanf(FILE *stream, const char *format, ...);
	功能:从文件中读取数据到变量,要求文件的内容是字符。
	stream:文件指针,fopen函数的返回值
	format:格式化控制符,点位符等
	...:变量的地址
	返回值:成功读取到返回0,失败返回-1。		
读写二进制内容:
	size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
	功能:内存中的数据,以二进制形式写入到文件中。
	ptr:要写入的内存的首地址
	size:要写入的字节数
	nmemb:要写入的次数
	stream:文件指针,fopen函数的返回值
	返回值:成功写入的次数	
	size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	功能:从文件中以二进制方式读取数据到内存中。
	ptr:用来存放数据的内存首地址
	size:要读取的字节数
	nmemb:要读取的次数
	stream:文件指针,fopen函数的返回值
	返回值:成功读取的次数
文件位置指针:
	每个打开的文件系统都会用一个指针记录着它的读写位置,这个指针指向哪里,
	接下来对文件的读取就会从哪里继续,指针的位置会随着文件的读写自动发生变化
文件结构体中有一个成员记录文件的读写位置,称它位文件位置指针,有些情况下需要调整它的位置,获取到正确的数据。
	int fseek(FILE *stream, long offset, int whence);
	功能:根据基础位置+偏移值调整文件指针的位置。
	stream:文件指针,fopen函数的返回值
	offset:可以为正负,正往右(偏移值)
	whence:(基础位置)
		SEEK_SET 文件头
		SEEK_CUR 当前位置
		SEEK_END 文件尾
	long ftell(FILE *stream);
	功能:返回文件位置指针所在的位置。
	void rewind(FILE *stream);
	功能:把文件位置指针调整到开头
文件关闭:
	int fclose(FILE *fp);
	功能:把文件关闭,以释放相关资源,避免数据丢失。

多文件编程:

随着代码量的增加,不得不把代码分成若干个.c文件编写,这样便于代码重用、代码编译和代码管理。
但缺点是不方便编译,需要借助编译脚本。
如何进行多文件编译:根据功能、责任分成若干个.c文件,然后为每个.c文件配备一个辅助文件.h然后单独编译每个.c文件,
	生成目标文件.o,然后再把.o文件合并成可执行文件。
头文件中应该写什么:
	1、头文件卫士
	2、宏常量、宏函数
	3、结构、联合、枚举的设计
	4、变量、函数的声明
	5、static函数的实现

编译脚本:

把用于编译的命令记录到文件中(makefile/Makefile),在终端里执行make程序时,make程序会自动读取当前目录中的。。。
make程序会监控每个文件的最后修改时间,如果没有被修改的文件不需要重新编译,这样可以节约大量的时间

注意:一定要使用tab缩进

GDB调试:

1、设置ubuntu系统,当段错误时产生core
	ulimit -c unlimited
2、编译时增加-g参数
3、再次执行新编译的程序,重新产生core文件
4、gdb a.out core 进行调试
	run/where