共计 9471 个字符,预计需要花费 24 分钟才能阅读完成。
前言
看到这篇博客的同学们,到明天为止,咱们的 c 语言高级局部解说就完结了(可能有的同学好奇我的题目不是写的 15 天么,这才七天,哈哈,因为咱们接下来就要开始进入 c ++ 的世界了,算是 c 语言的进阶,我明天整顿公布的已经自学的笔记绝对 有些简单 ,波及指针高级运算, 明天的内容不求把握,只求简略了解就好,即便没懂,也没关系啦,楼主纯手动码字不易,还望珍惜。欢送关注,多和我交换。
0. 温习
0.1 构造体
是一种复合数据类型,能够将多个不同类型得变量给捏在一起。个别用于代表某一个整体得信息。
比方:学生信息有学生姓名,年龄,学号 …. 贪吃蛇的 速度 血量 长度 …..
语法:
struct 类型名
{
字段 1 类型 字段 1 名字; // 字段也叫做成员
字段 2 类型 字段 2 名字;
.....;};
struct 类型名 变量名 = {初始值};
C 语言中定义构造体变量的时候,须要加上 struct.c++ 不须要
C 语言的程序员为了不写这个 struct,有了一种类型定义的写法
typedef struct _类型名
{
字段 1 类型 字段 1 名字; // 字段也叫做成员
字段 2 类型 字段 2 名字;
.....;}类型名,* P 类型名;
typedef struct _STUDENT
{//}STUDENT,*PSTUDENT;
应用构造体的时候,依照成员自身的类型去应用。
0.2 联合体
和构造体语法是类型的,区别在于联结的所有成员是共享内存的。
比拟适宜用在 成员互斥的状况下(一个无效,其余的就都是有效的)
0.3 类型定义
typedef int INT; //INT 就是 int 的别名
0.4 堆空间
申请:malloc
开释:free
设置内存中的值:memset
拷贝内存:memcpy
一些概念:
悬空指针:开释之后,没有被置为 nullptr 的指针
野指针:没有初始化的指针
悬空指针和野指针都指向有效区域。
正在运行的程序,有 5 个内存区域:
静态数据区:全局变量,static 局部变量所在的区域
常量区:字符串常量所在的区域
代码区:代码所在的区域
栈区:局部变量和函数的参数都在栈区
堆区:malloc 申请的空间,是堆区的。
作用域:变量起作用的一个范畴
生存期:变量存在的一个期间
局部变量,参数:进入函数,无效,此时在栈区创立进去。来到函数,生效,此时主动销毁。
动态局部变量:进入函数之前就被创立。然而在函数里面是不能应用的。来到函数,也不会被销毁。
堆区:申请就存在,开释就销毁
1. 指针进阶
1.1 指针的算术运算
1.1.1 指针 +(-) 整数
#include <stdio.h>
int main()
{
int a = 100;
int* p = NULL;
p = (int*)100;
//1. 一个地址实质来说也是一个数字
// 然而地址个别都是由 & 失去的,或者 malloc 函数返回的
// 如果咱们轻易写个地址,这个地址通常都是不能拜访
//2. 做加法运算
//a+n 一个整型做加法运算,为了失去一个算术后果
//p+n 指针做加法运算,是为了失去偏移为 n 的元素的地位。// 有了这个地位,就能够通过 * 失去偏移为 n 的地位的数据
printf("%d\n", a);
printf("%d\n", a+2);
printf("%d\n", p);
printf("%d\n", p + 2);
//3. 这个个性有什么用呢???int arr[10] = {4,5,20,40,10,6,7,8,9,10};
p = arr;// 能赋值,阐明类型类似
for (int i = 0; i < 10; i++)
{printf("%x", p + i);
printf("%d \n", *(p + i));
}
//4. double* char* short* 构造体 *
typedef struct _TEST
{
int a;
double b;
int c;
}TEST,*PTEST;
double* p2 = (double*)100;
char* p3 = (char*)100;
short* p4 = (short*)100;
// 上面两种写法等价的
PTEST p5 = (PTEST)100;
TEST* p6 = (TEST *)100;
printf("%d\n", p2);
printf("%d\n", p3);
printf("%d\n", p4);
printf("%d\n", p5);
printf("%d\n", p6);
printf("%d\n", p2+1);//108
printf("%d\n", p3+1);//101
printf("%d\n", p4+1);//102
printf("%d\n", p5+1);//124
printf("%d\n", p6+1);//124
return 0;
}
1.1.2 指针 - 指针(大略理解即可)
要求:两个指针的类型必须是统一的。失去的后果 是两个地址之间可能存储下多少个此类型
1.2 指针和一维数组
指针和一维数组有十分多的相同点:
#include <stdio.h>
int main()
{
//1. 对于不类似的类型,C 语言是不让间接赋值的
int nNum1 = 100;
short sNum1 = 0;
sNum1 = nNum1;
int* p1 = NULL;
short* p2 = NULL;
p1 = &nNum1;
p2 = &sNum1;
// 类型十分不同,不能间接赋值的
//p = nNum1; 不行
//short* 和 int* 也是不能间接赋值的
// 运算规定不同
//p1 = p2;
// 指针和数组能够间接赋值
int arr[10] = {4,5,20,40,10,6,7,8,9,10};
int* p = arr;// 能赋值,阐明类型类似
for (int i = 0; i < 10; i++)
{printf("%d", *(p + i));
printf("%d", *(arr + i));
printf("%d", arr[i]);
printf("%d \n", p[i]);
}
return 0;
}
是否就能够说 指针就是数组呢???
1. 指针是一个变量,能够指向其余地位
2. 数组名是数组的起始地址,是一个常量,不能扭转的
从 sizeof 的角度说,他俩也不同。
在 32 位,任何一个指针,都是四个字节的变量。
1.3 一级指针的应用场景
1.3.1 通过函数去批改内部的变量
#include <stdio.h>
void Test(int* a)
{*a = 500;}
int main()
{
int nNum = 100;
Test(&nNum);
printf("%d", nNum);
return 0;
}
1.3.2 将数组作为一个参数进行传递的时候
#include <stdio.h>
// int* Test
// int Test[]
// int Test[100]
int GetAdd(int Test[10])
{
int s = 0;
printf("%d\n",sizeof(Test));
for (int i = 0; i < 10; i++)
{s += Test[i];
}
return s;
}
int main()
{
// 如果有一个数组
int arrTest[10] = {4,3,5,7,8,1,2,9,0,10};
// 通过函数求所有元素的和
// 数组名是一个地址,接管地址的只能是指针
printf("%d\n", sizeof(arrTest));
int n = GetAdd(arrTest);
printf("%d", n);
return 0;
}
1.3.3 应用堆空间的时候
#include <stdio.h>
#include <stdlib.h>
// int* Test
// int Test[]
// int Test[100]
int GetAdd(int Test[10], int nCount)
{
int s = 0;
printf("%d\n", sizeof(Test));
for (int i = 0; i < nCount; i++)
{s += Test[i];
}
return s;
}
int main()
{int* p = (int*)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++)
{printf("请输出一个数据:");
scanf_s("%d", &p[i]);
//scanf_s("%d", p+i);
}
int n = GetAdd(p, 5);
printf("和为 %d", n);
return 0;
}
1.4 指针和二维数组(对初学者来说难度很大,尽量了解就好,没有了解也没有关系,不用泄气)
#include <stdio.h>
int main()
{int arrTest1[3][4] =
{ 1,2,3,4,
5,6,7,8,
9,10,11,12 };
int arrTest2[10][4] =
{1,2,3,4,5,6,7,8,9,10,11,12};
int arrTest3[3][5] =
{1,2,3,4,5,6,7,8,9,10,11,12};
//printf("%p\n", arrTest1);
// 二维数组名也是地址
// 然而类型并非是一般的指针
//int* p1 = arrTest1;
// 类型和【数组指针】类似
int(*p1)[4] = NULL;
p1 = arrTest1;// 赋值胜利,类型的确类似
p1 = arrTest2;// 这个也能胜利,就是列数对上了就行
//p = arrTest3; 不能胜利
int(*p2)[5] = arrTest3;
// 数组指针有什么特点:// 数组指针 + 1 是加了一排
int(*p3)[4] = (int(*)[4] )100;
//printf("%d", p3);
//printf("%d", p3 + 1);
// 数组指针和二维数组加法规定统一
p1 = arrTest1;
printf("%p\n", p1);
printf("%p\n", p1+1);
printf("%p\n", p1 + 2);
printf("%p\n", arrTest1);
printf("%p\n", arrTest1 + 1);
printf("%p\n", arrTest1 + 2);
// 解一次援用
// 对于数组指针和二维数组而言
// 解一次援用,还是那个地址值不变
// 然而 类型产生了变动
printf("------------------\n");
printf("%p\n", p1); // 数组指针类型
printf("%p\n", *p1); // 一级指针类型,指向的是下标为 0 的那一排
printf("%p\n", p1 + 1); // 数组指针类型
printf("%p\n", *(p1 + 1)); // 一级指针类型,指向的是下标为 1 的那一排
printf("------------------\n");
printf("%p\n", arrTest1);
printf("%p\n", *arrTest1);
printf("%p\n", arrTest1 + 1);
printf("%p\n", *(arrTest1 + 1));
// 区别
printf("------------------\n");
printf("%p\n", p1+1);
printf("%p\n", p1 + 1+1);// 再往下找一排 +16
printf("%p\n", *(p1 + 1) + 1);// 在本排中找下一个元素 +4
printf("%p\n", *(*(p1 + 1) + 1));
return 0;
}
代码有点简短和绕,一张图了解总结下:
// 遍历二维数组
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 4; j++)
{printf("%d", p1[i][j]);
printf("%d", *(p1[i]+j));
printf("%d", *(*(p1+i)+j));
printf("%d", (*(p1 + i))[j]);
printf("%d", arrTest1[i][j]);
printf("%d", *(arrTest1[i] + j));
printf("%d", *(*(arrTest1 + i) + j));
printf("%d", (*(arrTest1 + i))[j]);
}
}
1.5 应用场景
1.5.1 传参
1.5.2 应用堆空间
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
// int (*test)[4]
// int test[][4]
// int test[8][4]
int GetAdd(int(*test)[4],int nRowCount)
{printf("%d", sizeof(test));
int s = 0;
for (int i = 0; i < nRowCount; i++)
{for (int j = 0; j < 4; j++)
{s += test[i][j];
}
}
return s;
}
int main()
{
//1. 数组传参
int arr[3][4] = { 1,2,3,4,
5,6,7,8,
9,10,11,12 };
printf("%d", sizeof(arr));
GetAdd(arr,3);
//2. 应用堆空间,能够把堆空间当数组来用
int(*p)[4] = (int(*)[4])malloc(5 * 4 * sizeof(int));
memset(p, 0, 5 * 4 * sizeof(int));
p[1][1] = 10;
GetAdd(p, 5);
return 0;
}
1.6 数组指针和指针数组
// 数组指针 是指针
// 指针数组 是数组
#include<stdio.h>
#include <stdlib.h>
int main()
{
// 数组指针是一个指针
// 能够把一块内存区域,依照二维数组的拜访去应用
int(*p1)[4] = nullptr;
int arrTest[5][4] = {1,2,3};
p1 = arrTest;
// 指针数组
int nNum = 0;
int arr[3] = {1,2,3};
int* p2[4] = {NULL,NULL,NULL,NULL};
p2[0] = (int*)malloc(5 * sizeof(int));
p2[1] = &nNum;
p2[2] = arr;
p2[3] = (int*)malloc(6 * sizeof(int));
// 尽管两个语法是不同的概念
// 然而在应用的时候有相似性
arrTest[1][1] = 100;
// 这个代表 2 号指针 指向的地位 外面下标为 1 的中央
p2[2][1] = 20;
return 0;
}
字符类型
// 字符串
char arr1[3][20] = {"xiaoming","xiaobai","xiahui"};
printf("%s", arr1[0]);
printf("%s", arr1[1]);
arr1[0][1] = 'a';
const char* arr2[3] = {"xiaoming","xiaobai","xiahui"};
printf("%s", arr2[0]);
printf("%s", arr2[1]);
//arr2[0][1] = 'a';
1.7 构造体指针
int main()
{TEST stc = {10,2.5,20};
PTEST pstc = NULL;
pstc = &stc;
printf("%d %d\n", pstc->a, stc.a);
printf("%lf %lf\n", pstc->b, stc.b);
printf("%d %d\n", pstc->c, stc.c);
pstc = (PTEST)malloc(sizeof(TEST) * 3);
memset(pstc, 0, sizeof(TEST) * 3);
for (int i = 0; i < 3; i++)
{(pstc + i)->a = i * 10+1;
pstc[i].b = 3.3;
}
return 0;
}
1.8 二级指针
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
void Fun(int** p, int n)
{*p = (int*)malloc(n * sizeof(int));
memset(*p, 0, sizeof(int) * n);
}
int main()
{
int a = 100;
int* p1 = &a;
// 二级指针
int** p2 = &p1;
// 利用,如果说我心愿在一个函数中申请堆空间,出了函数
// 这个堆空间还能应用
int* p3 = NULL;
Fun(&p3, 5);
}
2. 文件操作
2.1 文件的简略分类
1. 文本文件(应用记事本可能间接关上并查看显示的问题),实质上存储的是文本的编码
2. 二进制文件(电影,音乐,图片,通常是给特定的软件去应用的)
2.2 对于门路的问题
咱们在 windows 中,去定位一个文件,通常都是应用门路的,去找到某一个文件
门路有两种模式:
1. 绝对路径
D:\Test\abc.txt
2. 相对路径
是从当前目录登程,去寻找某一个文件
. 代表当前目录
.. 代表上一级目录
例子:
.\abc.txt
..\test\abc.txt
什么是当前目录:
1. 当咱们 F5 调试运行的时候,代码所在的目录就是当前目录
2. 当咱们间接运行程序的时候,exe 文件所在的目录就是当前目录
2.3 文件操作的根本步骤
1. 关上文件 fopen_s
2. 读写文件
从文件中读取数据 往文件中写入数据
fgetc fputc
fgets fputs
fscanf_s fprintf
fread fwrite
文件读取写入函数 - 都是字符
#include <stdio.h>
int main()
{
FILE* pFile = nullptr;
////1. 关上一个文件,以 w 的形式,文件不存在就会创立一个
//// 这里的文件指针,就是代表一个关上的文件
//pFile = nullptr;
//fopen_s(&pFile, "..\\debug\\abc.txt", "w");
////2. 写入内容
//// 2.1 fputc
////fputc('A', pFile);
////fputc('B', pFile);
////fputc('C', pFile);
//// 2.2 fputs
//// fputs("hello world", pFile);
//// 2.3 fprintf
//fprintf(pFile, "%d %d %lf", 10, 20, 8.55);
////3. 敞开文件
//fclose(pFile);
// 读取
//1. 关上文件
pFile = nullptr;
fopen_s(&pFile, "..\\debug\\abc.txt", "r");
//2. 读取文件
//2.1 fgetc
//char cCh = 0;
//while (cCh!=EOF)
//{// cCh = fgetc(pFile);
// printf("%c", cCh);
//}
//2.2 fgets
//char buf[20] = {};
//fgets(buf,20,pFile);
//2.3 fscanf_s
int a = 0;
int b = 0;
double c = 0;
fscanf_s(pFile, "%d %d %lf", &a, &b, &c);
//3. 敞开文件
fclose(pFile);
}
文件读取写入函数 - 内存读写
#include <stdio.h>
int main()
{
FILE* pFile = nullptr;
////1. 关上一个文件,以 w 的形式,文件不存在就会创立一个
//// 这里的文件指针,就是代表一个关上的文件
//pFile = nullptr;
//fopen_s(&pFile, "..\\debug\\abc.txt", "w");
////2. 写入内容
//int arr[7] = {10,20,30,40,50,60,70};
//// 这个函数,是将内存中数据写入到文件中
//// 写入了 4 *7 也就是 28 个字节
//fwrite(arr, 4, 7, pFile);
////3. 敞开文件
//fclose(pFile);
//// 读取
////1. 关上文件
pFile = nullptr;
fopen_s(&pFile, "..\\debug\\abc.txt", "r");
////2. 读取文件
int arr[7] = { };
fread(arr, 4, 7, pFile);
////3. 敞开文件
//fclose(pFile);
}
3. 敞开文件 -fclose
2.4 关上文件的模式 加不加 b 的问题
所有的关上的模式,都能够增加一个 b, 比方:wb rb ab w+b r+b a+b。
有了这个 b 的模式,叫做二进制模式,没有 b 的就是文本模式。
文本模式,写入的时候,遇到了 0A 就会转为 0D 0A。读取的时候遇到了 0D 0A 就会转换为 0A 读取进来。
文本模式,遇到了 0A 转换为 0D 0A 如下:
因为在 windows 平台,\n 的 asc 码 是 0A,然而只有 \n 不能换行, 要显示换行 须要是 \r \n 就是 0D 0A。
如果是二进制模式,那么就不会转换
惟一的区别就在于会进行渺小的转换。
肯定要成对应用,写入的时候 加了 b,读取的时候也应该加
4. 补充函数:
fseek
ftell
有一个文件读写地位。在读取或者写入时会主动设置这个地位。
比方一个文件有 100 个字节。刚刚关上的时候,地位是在 0 这个中央,读取了 3 个字节,地位就会往后调整 3 个字节。
fseek 的作用:设置读写地位
#include <stdio.h>
int main()
{
FILE* pFile = nullptr;
////1. 关上一个文件,以 w 的形式,文件不存在就会创立一个
//// 这里的文件指针,就是代表一个关上的文件
//pFile = nullptr;
//fopen_s(&pFile, "..\\debug\\abc.txt", "wb");
////2. 写入内容
//fputs("Hello world", pFile);
////3. 敞开文件
//fclose(pFile);
fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
char cCh = fgetc(pFile);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
// 上面这个函数,可能调整读写的地位
//SEEK_END 以结尾为一个锚点
//SEEK_SET 以开始为锚点
//SEEK_CUR 以当初的地位为锚点
fseek(pFile, 0, SEEK_SET);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
}
ftell 的作用:是查看以后的读写的地位在哪??
#include <stdio.h>
int main()
{
FILE* pFile = nullptr;
////1. 关上一个文件,以 w 的形式,文件不存在就会创立一个
//// 这里的文件指针,就是代表一个关上的文件
//pFile = nullptr;
//fopen_s(&pFile, "..\\debug\\abc.txt", "wb");
////2. 写入内容
//fputs("Hello world", pFile);
////3. 敞开文件
//fclose(pFile);
fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
char cCh = fgetc(pFile);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
//fseek 这个函数,可能调整读写的地位
//SEEK_END 以结尾为一个锚点
//SEEK_SET 以开始为锚点
//SEEK_CUR 以当初的地位为锚点
fseek(pFile, 0, SEEK_SET);
cCh = fgetc(pFile);
cCh = fgetc(pFile);
//ftell 通知以后的文件读写地位在哪
int nSize = ftell(pFile);
cCh = fgetc(pFile);
nSize = ftell(pFile);
//fseek 和 ftell 配合能够获取文件大小
fseek(pFile, 0, SEEK_END);
nSize = ftell(pFile);
printf("以后文件大小就是 %d 字节", nSize);
}
留神:
应用这些函数的时候,最好成对应用。
写入的时候如果加了 b,读取的时候也肯定要加上。
在一次的关上和敞开之间,只进行一次读写操作