程序片段(01):01.二级指针.c
内容概要:二级指针
#include <stdio.h>
#include <stdlib.h>
//01.二级指针:
// 1.使用场景:
// (1).跨函数修改一级指针变量的数据-->DLL注入技术!
// (2).指针数组作为函数形参,将会转化为二级指针-->函数形参!
// 2.使用规律:
// 如何快速定位取值运算符所操作的数据是什么?
// 指针变量(定义级数-取值级数)<==>访问级数!
int main01(void)
{
int a = 10;
int b = 15;
int * p = &a;
int ** pp = &b;
*pp = &b;
**pp = 3;
//a-->b-->*p-->**pp
system("pause");
}
程序片段(02):01.反外挂.c
内容概要:游戏反外挂
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
//01.关于赋值运算符的使用特点:
// 赋值运算符针对于普通变量具备自动类型转换特性;
// 赋值运算符针对于指着变量不具备自动类型转换的特性
//02.指针类型与指针变量类型的重要性!
// 只要涉及到指针的操作,一定要时刻注意类型的重要性
// 注:数据类型<=>解析(步长+方式)
//03.数组名以及指向数组首元素的指针区别:
// 1.数组名和指向数组首元素的指针的数值层面一样
// 2.数组名和指向数组首元素的指针都存在内存实体
// 数组名:内存实体不可修改
// 常量指针变量的指向不可改变,但是指向的数据可以改变
// 指向数组首元素的指针(分变量指针和常量指针的情况)
// 视指针变量的const修饰关键字使用情况而定
//04.通过一级指针变量来模拟反外挂原理:
// 一级指针变量当中的指针指向数组当中的某个血量刻度;
// 通过修改一级指针变量的指针指向来获取具体的血量刻度;
// 从而实现血量不可修改,只能修改指向(实现血量的只读权限)
int main01(void)
{
double arr[100];
for (int i = 0; i < 100; ++i)
{
arr[i] = i + 1;//1.0~100.0
}
double * p = arr + 60;//类型很重要,类型决定了(解析步长+解析方式)-->直接的一个数组名只是具备该数组首元素的内存首地址(首个字节)
printf("&p = %p \n", &p);
printf("&arr = %p \n", arr);
printf("%f \n", *p);
while (1)
{
printf("此时的血=%lf \n", *p);
Sleep(1000);
}
system("pause");
}
程序片段(03):01.挂.c
内容概要:挂
_declspec(dllexport) go01()
{
double * p1 = 0x2ef4a4;
double * p2 = 0x2ef4bc;
//p1 = p2 + 92;//整数倍
}
//01.动态库函数的导出接口:
// _declspec(dllexport)
//02.二级指针变量的用途:
// 注入技术修改二级指针变量的数据
//03.区分数值意义层面的整数
// 没有数据类型的特点
//04.指针变量的指针如果只是加上
// 一个普通的数值,那么最好是该指针所
// 指向内存块儿的内存字节整数倍!
// 避免数据解析失败!
//05.无论是自动类型转换还是强制类型转换
// 都只是在对内存当中的二进制数据副本进行操作;
// 原本数据不会被修改!
_declspec(dllexport) go()
{
double ** pp = 0x0018FAE4;//这里的pp+1<=>pp+1*(所存储的一级指针变量所占用的内存尺寸)
//double * p = 0xB9F5D4;//p+1任何指针类型的变量都可以存储一个地址,地址本身无类型的情况之下,只能当做一个普通数据
//*pp = 0xB9F5D4 + 0x50;//p+3普通的整数没有类型可言,只是一个数值层面的意义
//*pp = *pp + 2;//修改方式1(明确类型)
//*pp = 0x0018F794 + 3 * sizeof(double);//修改方式2(不明类型)-->错误,因为sizeof();会返回有类型的整型数值
*pp = 0x0018FAFC + 3 * 8;//修改方式2(正确方式)
//int a = 10;
}
程序片段(04):01.指针类型.c
内容概要:指针类型
#include <stdio.h>
#include <stdlib.h>
//01.指针的类型以及指针变量的类型重要性:
// 1.数据类型:决定了(解析方式+解析步长)
// 2.关于数据类型所决定的解析方式一共有三大类:
// (有符号整型+无符号整型+浮点型),这三种类型
// 即使解析步长一样,如果解析方式不一样,解析结果照样异常!
// 3.指针的类型决定了通过取值运算符进行取值运算的时候
// 需要通过指针的数据(等同于内存首地址),向前解析的内存字节数
// 4.printf();函数不会进行数据类型的转换,只是根据格式控制符所描述
// 的解析方式对内存当中的二进制数据进行对应的解析
// 注1:赋值号对于指针变量没有类型转换的意义,只有数值层面的意义!-->数值意义(地址数值,没有类型概念)
// 赋值号只对于普通变量才存在着类型转换的意义!-->类型意义
// 注2:指针变量的类型决定了取值运算符对指针变量当中所存储的指针的解析细节!
int main01(void)
{
int num = 10;
int * p1 = #
void * p3 = #//空类型的指针变量只是用于存储具备地址意义的数值,不含解析细节!
double * p2 = #
printf("%p, %p \n", p1, p2);//指针变量所存储的地址数值都一样
printf("%lf, %d \n", *p2, *p1);//但是指针变量的类型决定了取值运算福对指针变量当中的指针解析方式
system("pause");
}
//02.任何类型的变量进行赋值的时候,
// 都应当注意数据类型是否一致!
int main02(void)
{
int a = 10;
int * p1 = &a;
int * p2 = p1;//类型一致的指针变量指针可以进行指针的赋值操作
printf("%p, %p \n", p1, p2);//格式控制符只是描述对于内存二进制数据的解析方式
printf("%d, %d, %d \n", *p1, *p2, a);
//*p1 = 4;
a = 2;
printf("%d, %d, %d \n", *p1, *p2, a);
system("pause");
}
//03.只要指针变量的类型和指针类型不一致
// 就绝对不能正确的解析到结果
int main03(void)
{
int num = 10;
double db = 10.9;
int * p1 = #
double * p2 = p1;//获取p2这个指针所指向的数据的时候,由于该指针变量(p2)的所描述的解析(方式+步长)不正确,因此解析出来的结果不可预料
//赋值号,指针除了指针变量以外的其他变量,都会存在自动类型转换机制,唯独指针类型的变量特殊(只有地址意义的数值)
//int a = 10.9;
void *px = p1;
px = p2;//空类型的指针变量只是用于临时存储地址意义的变量
printf("%d \n", (int)db);//printf();不会进行自动类型转换,会导致解析某些数据失败
printf("%lf \n", (float)num);
//double * p = #
//printf("%lf \n", *p);
//*p = &db;
//p = &db;
//printf("%lf \n", *p);
int *p = &db;
printf("%d \n", *p);
system("pause");
}
//04.sizeof关键字用于求取实际占用的内存字节数!
// 返回内存字节数类型为unsigned int
//05.严格注意:
// 是具体给指针变量赋值还是给普通变量赋值
// 注:通过指针变量+取值运算符访问到的可能是普通内存实体!
// 赋值运算符针对于普通内存实体也是具备自动类型转换机制的!
//06.关于自动类型转换机制:
// 两种处理方式
// 同为整型:直接截断(图形化操作二进制位)
// 不同类型:根据解析方式+解析步长进行决断!
int main04(void)
{
int num = 100;
int * p = #//sizeof(int)--int **pp-->sizeof(int *)
*p = 10.9;
printf("%d \n", num);
printf("%u \n", sizeof(int *));
printf("%d \n", sizeof(double *));
system("pause");
}
//07.三个不同类型的指针变量所存储的地址是一样的
// 但是由于指针变量的类型不一致,也就导致了指针类型不一致
// 因此,对于同一个地址所对应的内存实体的解析方式也就不一样!
//08.对局部变量的查询方式:
// 局部变量的值+根据局部变量的值进行解析后的结果
int main05(void)
{
int num = -1;
int * p1 = #
unsigned int * p2 = #
float * p3 = #
system("pause");
}
程序片段(05):01.Test.cpp+02.指针.c
内容概要:指针的运算
///01.Test.cpp
#include <stdio.h>
#include <stdlib.h>
//01.区分:变量指针和常量指针以及指针常量!
// 变量指针:可以修改的指针变量
// 常量指针:不可以修改的指针变量,不能够修改指向,例如数组名
// 指着常量:具备地址层面意义的地址数值!
//02.数组名的本质:
// 如果数组是一维数组,那就是指向数组当中收个元素的常量指针
// 如果数组是高维数组(含二维),那就首先将该数组看作为一个一维数组
// 而该数组名的本质就是指向(看做成一维数组)的首个元素的常量指针
int main01(void)
{
int arr[5] = { 1, 2, 3, 4, 5 };
int * p = arr;//变量指针=常量指针
int * const px = arr;//数组名的本质就是数组当中第一个元素(所有数组都看作为一维数组进行处理!)的指针常量(地址常量|数值常量)
*(arr + 3);
//arr = 1;//常量指针
//px = 1;//const所修饰的常量指针,其指向不可以进行直接修改!
system("pause");
return 1;
}
///02.指针.c
#include <stdio.h>
#include <stdlib.h>
//01.指针和指针变量都必须严格注重数据类型
//02.指针变量的运算必须在同一个数组的情况之下,才具有意义!
// 1.指针变量+|-一个整数:
// 在数组当中向前或者向后移动几个数组元素
// 2.指针变量-指针变量:
// 求取两个指针变量之间相差了多少个数组元素
// 注:指针变量没有乘法和乘法方面的运算,因为运算结果毫无意义!
int main02(void)
{
int a = 10;
int b = 20;
int * p1 = &a;
int * p2 = &b;
p1 = p2;//同一个t类型的一级指in针变量之间可以进行相互赋值
int arr[5];
p1 = arr;
p2 = &arr[2];//指针变量赋值运算,必须注意类型的统一性(而且:指针变量的赋值操作只具备数值层面的操作意义)
p1 - p2;//得到的结果是在同一个数组当中的两个数组元素之间所相差的数组元素个数
system("pause");
}
//03.C语言和C++语言的编译器特点:
// C语言属于"弱类型"语言;C++语言属于"强类型"语言
// 注:强弱类型就是对数据类型是否精准匹配的严格校对!(编译时期校对)
//04.关于指针变量和整型变量之间的赋值:
// 1.类型不匹配,C语言允许
// 2.给指针变量赋予的整型变量的数据具有地址层面的意义
// 注:不可以对指针变量进行随意赋值操作!(唯恐赋予不规范内存地址)
int main03(void)
{
int * p = 0x123123;//指针变量一般不允许直接赋值常规整数,因为不可预知(因为该地址所对应的内存块儿不知道是否有系统进行维护!)
*p = 1;//该指针变量当中的指针指向了操作系统所使用的内存,因此发生内存访问错误情况!
int num = p;
//指针变量和整型变量之间可以进行赋值,可以编译,但是整数表示的是具备地址意义层面的数值!
printf("%x \n", num);
system("pause");
}
void select(int const * p)
{
//*p = 123;
printf("银行有%d元! \n", *p);
}
int main04(void)
{
int arr[5] = { 1, 2, 3, 4, 5 };
int const * p;//指针变量的指向可以改变,但是指向的数据数据不可以改变!
int num1 = 101;//银行的钱
select(&num1);
int num2 = 102;//银行的钱
select(&num2);
system("pause");
}
int main05(void)
{
int num1 = 101;//银行的钱
int num2 = 102;//银行的钱
const int * p = &num1;
p = &num2;
//*p = 10;
system("pause");
}
int main06(void)
{
int num1 = 101;//银行的钱
int num2 = 102;//银行的钱
int * const p = &num1;
*p += 3;
//p = &num2;
//int * const p;//指针变量的指向可以修改,但是所指向的数据不能修改
system("pause");
}
//04.关于const关键字做权限控制的几种情况:
// const在*右边:跨函数数据只读权限,账户访问权限
// 指针变量的指向不可修改,但所指向的数据可以修改
// const在*左边:跨函数账户只读权限,数据访问权限
// 指着变量的指向可以修改,但所指向的数据不可修改
// 双const情况:跨函数账户不可修改,数据不可修改
// 指针变量的指向不可修改,所指向的数据不可修改
// 注:严格区分账户情况和数据情况
int main07(void)
{
int num = 10;
int num2 = 100;
const int * const p = #
//*p = 101;
//p = &num2;
system("pause");
}
//05.关于const做权限控制的解决方案:
// const在*右边:
// 1.指向变量数据的常量指针
// 2.账户只读权限,数据访问权限
// const在*左边:
// 1.指向常量数据的变量指针
// 2.账户访问权限,数据只读权限
// const在双边:
// 1.指向常量数据的常量指针
// 2.账户只读权限,数据只读权限(代理查询)
// 注:函数形参控制账户+函数实体控制数据
程序片段(06):01.Run.c
内容概要:算数运算以及其他运算
#include <stdio.h>
#include <stdlib.h>
//01.针对于数组的两种常用遍历方式:
// 索引遍历+指针遍历
// 注:编译实质arr[i]<=>*(arr+i)
int main01(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
//arr[i]的编译实质:*(arr+i)
for (int i = 0; i < 10; ++i)
{//下标遍历
printf("%p, %p, %d \n", &arr[i], arr + i, arr[i]);
}
for (int * p = arr; p < arr + 10; ++p)
{//指针遍历
printf("%p, %d \n", p, *p);
}
system("pause");
}
//02.运算符的优先级:
// 1.接触生效原理(谁先接触谁优先)
// 2.自变运算符(++)的优先级大于取值运算符(星号:"*")
int main02(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int * p = &arr[3];
p += 3;
//printf("%d \n", *p++);//7
//printf("%d \n", *(p++));//7
//printf("%d \n", ++p);//8
//printf("%d \n", *(++p));//8
system("pause");
}
//03.指针变量的相等与不等比较特点!
int main03(void)
{
int num = 10;
int * p1 = #
int * p2 = &num + 1;
if (p1 == p2)
{
printf("情敌! \n");
}
else
{
printf("非情敌! \n");
}
system("pause");
}
//04.指针变量的关系运算:
// 1.数组内:索引前后关系
// 2.数组外:栈内存所属位置
int main04(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int * p1 = arr;
int * p2 = arr + 3;
//p2>p1:p2的下标比p1的下标大,数组内部可以说明排序
system("pause");
}
//05.编译器的两种编译模式:
// Debug模式:栈结构的开口向上
// Debug情况之下:VC和GCC两个编译器的结果相同
// Release模式:栈结构的开口向下
// Release情况之下:存在着代码优化机制
// 注:(代码优化机制:VC和GCC编译器都会根据变量的使用频率,自动优化该变量在栈结构当中的所属位置
// 某变量的使用频率越高,那么该变量在Release模式下越有几率被存放于栈结构开口处!)
//06.几种进栈实质区分:
// 1.代码进栈
// 2.函数形参:形参变量进栈
// 3.函数实体:局部变量进栈
int main05(void)
{
int a;
int b;
int c;
printf("&a = %p, &b = %p, c = %p \n", &a, &b, &c);
system("pause");
}
程序片段(07):01.Test.c+02.Run.c
内容概要:指针运算的优先级
///01.Test.c
#include <stdio.h>
#include <stdlib.h>
int main01(void)
{
char * p;
printf("%d \n", sizeof(p));//所有指针类型享有共同内存尺寸,只是区分编译器位数直接决定
system("pause");
}
///02.Run.c
#include <stdio.h>
#include <stdlib.h>
//01.两个指针变量的减法说明:
// 1.必需在同一个数组当中两个指针变量进行减法操作才具有实际意义!
// 2.任意两个指针变量的减法结果推导!
// 减法结果=((指针变量1的指针所属地址数值)-(指针变量2的指针所属地址数值))/sizeof(指针变量1的所属类型);
// 注:编译器不会报错!但是实际开发过程当中严谨这样进行操作!
int main02(void)
{
int arr[5] = { 1, 2, 3, 4, 5 };
int * p = arr;
int * p1 = &arr[4];
printf("%d \n", p1 - p);//相减等同于相差的数组元素个数
//double * px = &arr[4];//2
system("pause");
}
//02.内容概要:
// 1.变量指针+常量指针
// 2.指针减法=(指针1-指针2)/sizeof(*指针1);
//03.指针变量的优先级和结合性:
// 1.接触生效
// 2.++的优先级高于*
int main03(void)
{
int arr[5] = { 1, 2, 3, 4, 5 };
int * p = arr;
int * px = &arr[4];
printf("%d \n", px - p);
char * px1 = arr + 3;
char * px2 = arr + 4;//数组名arr的数据类型int * const p;
printf("%d \n", px1 - px2);//4=(地址1-地址2)sizeof(char)
*p++;//++的优先级比*的优先级高
(*p)++;
++*p;//优先级接触生效
for (int i = 0; i < 5; ++i)
{
printf("%d \n", arr[i]);
}
system("pause ");
}
程序片段(08):01.Point.c
内容概要:指针与数组
///01.Point.c
#include <stdio.h>
#include <stdlib.h>
//01.数组名+&数组名+*数组名:
// 数组名:
// 将所有数组看做为一维数组进行处理
// 然后数组名就是指向该一维数组的常量指针
// &数组名:
// 数组指针常量,是一个具有(地址+类型)意义的数值
// *数组名:
// 根据数组名的特点进行区分
//02.各种维度的指针区别:
// 0维指针:指向一列
// 1维指针:指向一行
// 2维指针:指向一面
// 3维指针:指向一体
int main01(void)
{
int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 , 0 };
int intArrArr[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
printf("intArr = %p, &intArr = %p \n", intArr, &intArr);
printf("intArr+1 = %p, &intArr + 1 = %p \n", intArr + 1, &intArr + 1);
//intArr作为一个常量指针,指向数组当中的首个元素
//&intArr作为一个指针常量,指向整个数组
prinrf("intArrArr = %p, &intArrArr = %p, *intArrArr \n", intArrArr, &intArrArr, *intArrArr);
printf("intArrArr+1 = %p, &intArrArr+1 = %p, *intArrArr+1 = %p \n",intArrArr + 1, &intArrArr + 1, *intArrArr + 1);
//intArrArr作为一个常量指针,指向数组当中的首个元素(将所有数组当做为一维数组进行看待的处理结果)
//&intArrArr作为一个指针常量,指向一个面儿
//*intArrArr作为一个指针常量,指向一个列
system("pause");
}
程序片段(09):01.P.cpp
内容概要:指针数组与数组指针
#include <stdio.h>
#include <stdlib.h>
//01.指针数组与数组指针
// 指针数组:
// 1.是一个数组
// 2.存储的都是变量指针
// 数组指针:垂直指向
// 1.是一个指针
// 2.存储的是一维数组的首地址
// 注:凡是对数组名进行取地址操作,所获取到的
// 都是指针都是数组指针(指针常量)
int main01(void)
{
int arr[10];
int * pArr[10];//40-->指针数组:用于存储指针变量的数组
int (*p)[10];//4-->数组指针:指向含有10个数组元素的的数组的指针!
printf("%d, %d \n", sizeof(pArr), sizeof(p));
system("pause");
return 1;
}
//0 9 2 8 3 7 4 5 5 10
//0 9 2 8 3 7 4 6 10 5
//10 9 2 8 3 7 4 6 1 5
//02.指针数组的常见用途:
// 1.批量管理指针(内存地址)
// 2.便于分块数组模型的构建
//注:指针数组所存储的指针类型通常都是(char*)类型
// 因为便于进行内存检索!+字符串存储于代码区常量池
//03.赋值运算符针对于指针变量不具备类型转换的特性
// 通常只是具备地址层面意义的数值
//04.比较指针变量所存储的指针所指向数据实体
// 交换指针变量所存储的指针!-->防止修改原始存储数据顺序!
int main02(void)
{
int arr[10] = { 0, 9, 2, 8, 3, 7, 4, 6, 5, 10 };
int * pArr[10];//指针数组:该数组当中的每一个变量都是指针变量,用于批量管理内存地址[指针]
for (int i = 0; i < 10; ++i)
{
pArr[i] = arr + i;//变量指针存储常量指针的地址数值
}
//正向的冒泡排序算法
//for (int i = 0; i < 10 - 1; ++i)
//{
// for (int j = 0; j < 10 - 1 - i; ++j)
// {
// if (*pArr[j] < *pArr[j + 1])
// {
// int * pTemp = pArr[j];
// pArr[j] = pArr[j + 1];
// pArr[j + 1] = pTemp;
// }
// }
//}
//反向的冒泡排序算法
for (int i = 10 - 1; i > 0; --i)
{
for (int j = 10 - 1; j > 10 - 1 - i; --j)
{
if (*pArr[j] < *pArr[j - 1])
{
int * pTemp = pArr[j];
pArr[j] = pArr[j - 1];
pArr[j - 1] = pTemp;
}
}
}
for (int i = 0; i < 10; ++i)
{
printf("%d, %d \n", *pArr[i], arr[i]);
}
system("pause");
return 1;
}
int main03(void)
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (int * p = arr; p < arr + 10; ++p)
{//指针遍历
printf("%3d", *p);
}
printf("%p, %p \n", arr, &arr);
printf("%p , %p \n", arr + 1, &arr + 1);
int (*p)[10];//p-->p+1-->40//&一维数组名的本质
p = &arr;
int * px = arr;//px-->px+1-->4
system("pause");
}
程序片段(10):01.批量挂.c
内容概要:指针数组挂
#include <Windows.h>
//01.通过指针数组实现批量处理指针变量:
// 跨进程修改内存实体-->需要内存实体的所属地址!
_declspec(dllexport) go()
{
int * pArr[5];
pArr[0] = 0xadfc70;
pArr[1] = 0xae0720;
pArr[2] = 0xae0850;
pArr[3] = 0xae05f0;
pArr[4] = 0xae04c0;
while (1)
{
for (int i = 0; i < 5; ++i)
{
if (*pArr[i] < 100)
{
*pArr[i] = 101;
}
}
Sleep(1000);
}
}
程序片段(11):01.二维数组.c+02.二维数组轮询.c
内容概要:指针与二维数组
///01.二维数组.c
#include <stdio.h>
#include <stdlib.h>
//01.关于线性存储的数组的解析方式:
// 1.可以采用指针变量的遍历方式
// 2.该指针变量直接从数组的首个数组元素开始进行遍历!
// 线性遍历:遍历的指针尺寸是数组当中的不可分割的数据类型!
// 3.线性数组的奥数操作方式!
// 外层变化快+内层变化慢!
//注:可以直接将二维数组当中的每个数组元素看做为单个列指针变量的
// 指针所指向!
int main01(void)
{
int arrArr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0 };
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 5; ++j)
{
printf("%4d", arrArr[i][j]);
}
printf("\n");
}
for (int * p = &arrArr[0][0]; p < *arrArr + 15; ++p)
{//线性排列-->线性遍历
printf("%4d, %p \n", *p, p);
}
printf("\n\n");
for (int i = 0; i < 15; ++i)
{//索引遍历-->性能优化
printf("%4d, %p \n", arrArr[i / 5][i % 5], &arrArr[i / 5][i % 5]);
//00 01 02 03 04
//10 11 12 13 14
//20 21 22 23 24
}
system("pause");
}
//02.关于一维数组二维数组的概念区别:
// 一维数组:int arr[5]
// arr:int * const-->常量指针-->有内存实体
// &arr:int (*pArr)[5]-->数组指针-->没有内存实体-->属于指针常量
// *arr:int-->零维指针-->有内存实体-->获取数值
// 二维数组:int arrArr[4][5]
// arrArr:int (*pArr)[5]-->数组类型的常量指针-->行指针-->有内存实体
// &arrArr:int (*pArr)[4][5]-->数组类型的指针-->面指针-->没有内存实体-->属于指针常量
// *arrArr:int *-->普通变量的指针-->列指针-->有内存实体
int main02(void)
{
//int arr[5];
int arrArr[3][5];
printf("%p, %p \n", arrArr, arrArr + 1);//20个字节
printf("%p, %p \n", *arrArr, *arrArr + 1);//4个字节
//int ** p = arrArr;//数据类型不匹配
int(*parrArr)[5] = arrArr + 0;//二维数组名的本质:将二维数组看做为一维数组之后,指向其中每个元素的常量指针类型
//arrArr = 1;//数组名一定是常量指针,绝对不可以进行改变
int * px = *(arrArr + 0);//*arrArr-->行指针转化为列指针(就是指向普通变量的一级一级指针)-->只是对内存数据的转换
int(*py)[3][5] = &arrArr;//&a-->一个指向固定面积的二维数组的指着变量,二维数组指针
system("pause");
}
///02.二维数组轮询.c
#include <stdio.h>
#include <stdlib.h>
//01.关于二维数组的指针操作方式:
// 例如:int arrArr[3][4]
// arrArr:行指针
// arrArr+i<=>&arrArr[i]:行指针
// arrArr[i]+j<=>*(arrArr+i)+j;
// *(arrArr[i]+j)<=>*(*(arrArr+i)+j)
// 注:
// 数组名的本质:
// 将数组看做为一维数组,数组名
// 就是指向该一维数组的常量指针
// *行指针<=>列指针
// 列指针的实质就是一级常量指针
int main03(void)
{
int arrArr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 , 0, 0, 0, 0, 0 };
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 5; ++j)
{
printf("%4d, %p", arrArr[i][j], &arrArr[i][j]);
}
printf("\n");
}
printf("\n\n");
for (int i = 0; i < 3; ++i)
{
printf("arrArr + %d = %p, %p \n", i, arrArr + i, &arrArr[i]);
}
for (int j = 0; j < 5; ++j)
{
printf("%p, %p \n", arrArr[0] + j, *(arrArr + 0) + j);
printf("%p, %p \n", *(arrArr[0] + j), *(*(arrArr + 0) + j));
}
system("pause");
}
程序片段(12):01.Show.c
内容概要:WebShow二维数组
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("Content-Type:text/html \n\n");//01.必须使用一个明显的空行(需要两个"\n\n")进行表示
int arrArr[3][5];
int num = 0;
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 5; ++j)
{
arrArr[i][j] = num;
++num;
printf("%3d",*(*(arrArr+i)+j));
printf("%3d", arrArr[i][j] = ++num);
}
printf("<br />");//02.html换行标签,不能使用C语言的换行!
}
//system("pause");//03.不能使用系统函数
}
程序片段(13):01.动态数组.c
内容概要:动态数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
//01.动态数组采用什么类型的指针变量进行接收:
// 就表明动态数组名称属是什么类型!(变量指针|)
// 简而言之:接收动态数组的指针变量就是动态数组名的本质!
//注:严格区分变量指针和常量指针
//02.区分释放内存和回收内存之间的区别:
// 个人观点:释放内存+数据清除=回收内存
// 仅仅的释放内存:不会自动发生数据清除操作!
int main01(void)
{
//arr[N];
int N;
scanf("%d", &N);
int * p;
p = (int *)malloc(N * sizeof(int));
for (int i = 0; i < N; ++i)
{
p[i] = i;
printf("%d \n", p[i]);
}
free(p);//回收内存
//free(p);//释放内存,针对于同一片儿内存的指针,对该指针进行释放的操作不可重复进行!
system("pause");
}
//02.在堆内存开辟模拟栈内存的二维数组的关键点在于:
// 指针变量的类型-->决定对内存块儿的解析方式!
//注:可以对动态数组的数组名执行类似于栈内存二维数组的操作!
int main02(void)
{
int arrArr[3][10];//开辟堆内存动态数组模拟这个栈内存二维数组,关键在于指针
int(*p)[10] = malloc(30 * sizeof(int));//p就是堆内存当中的动态数组数组名
int num = 0;
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 10; ++j)
{
printf("%3d", p[i][j] = num++);//原理:p[i][j]<=>*(*(p+i)+j));
}
printf("\n");
}
system("pause");
}
//03.关于动态内存分配的注意事项:
// 1.参数按照无符号类型进行解析的!
// 因此不能使用特殊值-1-->会解析为极大的数据
// 无符号类型解析方式:内存二进制全部看做为补码进行逆向解析
// 2.取特殊内存字节数0可以分配成功,但是并没有
// 实际意义!
//注:解析方式尤其重要!
int main03(void)
{
//int * p = malloc(-1);//分配失败为0,-1补码-->所分配的内存空间比较大
int * p = malloc(0);
printf("%p \n", p);//0能分配成功,但是没有实际意义!
system("pause");
}
//04.动态内存开辟诀窍:
// 数组首元素的指针!
// 注:回收内存的操作只能执行一次,为了软件规范
// 只要对指针变量当中所存储的指针执行了回收操作
// 就应当置为空指针,防止重复回收内存错误!
int main04(void)
{
int N;
scanf("%d", &N);
int * p;
p = malloc(N * sizeof(N));
printf("p = %p \n", p);
for (int i = 0; i < N; ++i)
{
p[i] = i;
printf("%d", p[i]);
}
free(p);//内存回收!
printf("p = %p \n", p);
//free(p);回收内存,回收内存的操作只能执行一次,不可以进行重复回收动作
p = NULL;//被妹子回收了,为了防止迷途指针的产生,需要设定指针变量为空
free(p);//回收空指针不会出错!
//*p = 123;
system("pause");
}
//05.一定要在存储指针的指针变量消亡之前
// 执行指针所指向内存区块儿回收操作,否则会导致内存泄露
void run()
{
void * p = malloc(10);//指针变量p本身存储与栈内存
//内存泄露,存储地址的指针消亡,就无法进行回收动作了
free(p);
}
//06.如何定义一个三维动态数组?
// 采用面儿指针<=>指向二维数组的指针
//注:定义N维数组需要(N-1)维的数组的指针
// 任何N维数组都可以看做为一个一维数组,内部的(N-1)维数组只是一个元素!
int main05(void)
{
int(*p)[3][5] = malloc(sizeof(int) * 15 * 2);
int num = 0;
for (int z = 0; z < 2; ++z)//纵坐标-->控制面
{
for (int x = 0; x < 3; ++x)//横坐标-->控制行
{
for (int y = 0; y < 5; ++y)//纵坐标-->控制列
{
printf("%3d", p[z][x][y] = num++);
}
printf("\n");
}
printf("\n\n");
}
system("pause");
}
程序片段(14):01.动态内存.c
内容概要:动态内存
#include <stdio.h>
#include <stdlib.h>
//01.静态数组:
// 位于栈内存:在编译时期就已经确定了该数组的所含有的元素个数
int main01(void)
{
int arr[10];//栈内存-->静态数组-->在编译时期就已经确定了数组的元素个数
system("pause");
}
//02.动态数组:
// 位于堆内存:在运行时期动态确定该数组所占有的内存尺寸!
int main02(void)
{
int n;
scanf("%d", &n);
int * p = malloc(n * sizeof(int));
for (int i = 0; i < n; ++i)
{
p[i] = i;
printf("%d \n", p[i]);
}
system("pause");
}
//03.区分软访问与硬访问:
// 软访问和硬访问的默认最小单位是字节
// 软访问可以实现的极限最下单位是二进制位
int main03(void)
{
while (1)
{
void * p = malloc(1024 * 1024 * 10);
}
system("pause");
}
//04.使用动态内存的原因:
// 静态内存(栈内存)使用尺寸过小!
int main04(void)
{
//int a[1024 * 1024 * 1024];//栈内存的大小,不能太大,设置很大不但耗费CPU,还耗费内存
int a;
system("pause");
}
程序片段(15):main.c
内容概要:Stack
#include <stdio.h>
#include <stdlib.h>
//01.内存地址的高低必须区分模式:
// 模式:Debug+Release
//02.不同的模式:
// 栈内存的开口方向不一样
// 程序优化程度不一样(例如:经常访问的变量会定义在栈口!)
int main01()
{
int a;
int b;
int c;
printf("a=%p\nb=%p\nc=%p", &a, &b, &c);
system("pause");
return 0;
}
()
时间: 2024-10-13 16:10:42