2.1 C/C++ 使用数组与指针

C/C++语言是一种通用的编程语言,具有高效、灵活和可移植等特点。C语言主要用于系统编程,如操作系统、编译器、数据库等;C语言是C语言的扩展,增加了面向对象编程的特性,适用于大型软件系统、图形用户界面、嵌入式系统等。C/C++语言具有很高的效率和控制能力,但也需要开发人员自行管理内存等底层资源,对于初学者来说可能会有一定的难度。

定义并使用一维数组: 该数组是最通用的数组,也是最基本的.

#include <stdio.h>

void PrintArray(int *Array, int len)
{
for (int x = 0; x < 10; x++)
{
printf("%d \n", Array[x]);
//printf("%d \n", *(Array + x));
}
}

int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
PrintArray(Array, 10);

system("pause");
return 0;
}

定义并使用二维数组: 二维数组是相对于一维数组而言的,在内存中其都是一段线性的连续的存储空间.

#include <stdio.h>

int main(int argc, char *argv[])
{
int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
int *Pointer1 = &array[0][0];

printf("array[0][0]基址:%x \n", Pointer1);
printf("array[0][0]数据:%d\n", *(Pointer1));
printf("arrya[0][1]数据:%d\n", *(Pointer1 + 1));

int *Pointer2 = &array[1][2];
printf("array[1][2]基址: %x \n", Pointer2);

printf("数组元素个数:%d\n", sizeof(array) / sizeof(int));
return 0;
}

使用指针遍历数组: 使用指针定位数组,并输出数组元素.

#include <stdio.h>

int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr;

for (ptr = &Array; ptr < (Array + 10); ptr++)
{
printf("%d ", *ptr);
}

system("pause");
return 0;
}

动态数组的定义: 我们可以自己手动分配空间并定义一个动态数组.

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

int main(int argc, char* argv[])
{
int ** p = (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);

p[0][0] = 1;
p[0][1] = 2;
p[0][2] = 3;
p[1][0] = 4;

for (int x = 0; x < 3; x++)
printf("%d \n", p[0][x]);

system("pause");
return 0;
}

指针与数组之间的运算: 通过将两个指针做减法,我们就可以得到两个元素之间相差多少字节.

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

int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

int *ptr = Array;
ptr = &Array[10];

int ptr_len = ptr - &Array[0];
int arr_len = ptr - Array;
printf("当前Array数组总长度: %d %d\n", ptr_len, arr_len);

int *ptr1 = &Array[0];
int *ptr2 = &Array[7];

int ptr3_len = ptr2 - ptr1;
printf("Array[7] - Array[0] 相隔 %d 个元素 %d 个字节 \n", ptr3_len,ptr3_len * 4);

system("pause");
return 0;
}

定义一维指针数组: 所为指针数组就是说该数组是用来存放其他的变量地址的,故称为指针数组.

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

void PrintInt()
{
int x = 10, y = 20, z = 30;

int *Array[] = { &x, &y, &z }; // 定义指针数组(存放变量地址)
*Array[0] = 100; // 给x重新赋值
*Array[2] = 300; // 给z重新赋值

for (int x = 0; x < 3; x++)
printf("地址: %x --> 数值:%d \n", Array[x], *(Array[x]));
}

void PrintArray()
{
int x[] = { 1, 2, 3, 4, 5 };
int y[] = { 6, 7, 8, 9, 10 };

int *Array[] = { &x, &y };

printf("单独取地址: %x --> 取值: %d \n", (Array[0]+1),*(Array[0] + 1));

for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 5; y++)
{
printf("循环取地址: %x --> 数值: %d \n", Array[x] + y, *(Array[x] + y));
}
}
}

int main(int argc, char* argv[])
{
PrintArray();
system("pause");
return 0;
}

定义二维指针数组: 同理我们可以通过指针遍历到二维数组中的数据,三维四维以此类推.

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

void PrintArray(int(*Array)[3], int row, int col)
{
for (int x = 0; x < row; ++x)
{
for (int y = 0; y < col; ++y)
{
// Array[x][y] 等价于 => *(*(Array + x) + y)
printf("地址: %x --> 遍历数组: %d \n", (*(Array + x) + y), Array[x][y]);
}
}
}

int main(int argc, char* argv[])
{
int Array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

int(*pArray) = Array;

printf("寻找 Array[0][0] --> %d \n", (*pArray + 0));
printf("寻找 Array[0][1] --> %d \n", (*pArray + 1));
printf("寻找 Array[1][2] --> %d \n", (*pArray + 1) + 2);
printf("寻找 Array[1][3] --> %d \n", (*pArray + 1) + 3);

PrintArray(Array, 2, 3);

system("pause");
return 0;
}

数组实现逆序排列: 所谓数组逆序就是讲一个正向数组反向排列,并输出排序后的结果.

#include <stdio.h>

void Swap_Array(int Array[], int Number)
{
int x = 0;
int y = Number - 1;
while (x < y)
{
int tmp;
tmp = Array[x];
Array[x] = Array[y];
Array[y] = tmp;
x++; y--;
}
}

int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

Swap_Array(Array, 10);

for (int x = 0; x < 10; x++)
{
printf("%d ", Array[x]);
}

system("pause");
return 0;
}

用数组冒泡排序: 冒泡排序是经典的算法,也是学习数组必学知识点,这里总结一份冒泡排序.

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

void bubble(int *arr, int len)
{
int flag = 1;
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
flag = 0;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (flag)
return;
flag = 1;
}
}

int main(int argc, char* argv[])
{
int Array[] = {1,2,3,4,5,6,7,8,9,10};

int len = sizeof(Array) / sizeof(int);
bubble(Array, len);

for (int x = 0; x < len; x++)
printf("%d \n", Array[x]);

system("pause");
return 0;
}

定义常量指针: 一般为了高效传递数据,我们使用地址传递,这样就无须重复拷贝了,通常会加上常指针防止数据被误修改.

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

struct Student
{
int num;
char *name;
};

void MyPrint(const struct Student *stu)
{
// 常量无法修改,但是我们依然可以使用以下方法修改
struct Student *p = (struct Student *)stu;
p->num = 100;

printf("内部的打印: %d \n", stu->num);
}

int main(int argc, char* argv[])
{
struct Student stu = { 1, "lyshark" };
// 传递指针要比传递参数效率更高 MyPrint(stu) --> MyPrint(&stu);
MyPrint(&stu);
printf("外部的打印: %d \n", stu.num);

system("pause");
return 0;
}

多级指针与野指针: 多级指针就是指针变量中存储有另一个指针变量,野指针就是非法指针,该指针被指向一段非法内存.

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

void MyPrintA()
{
int number = 10;
int *ptr1 = &number; // 定义一级指针,指向number;
int **ptr2 = &ptr1; // 定义二级指针,里面存放一级指针的地址

printf("二级: %x --> 一级: %x --> 数据: %x (%d) \n",ptr2,ptr1,&number,number);

int age = 20;
int *ptr3 = &age; // 定义一级指针
int **ptr4 = &ptr3; // 定义二级指针
int ***ptr5 = &ptr4; // 定义三级指针
***ptr5 = 100; // 改变指针中的数据

printf("三级: %x --> 二级: %x --> 一级: %x --> 数据: %x (%d) \n", ptr5,ptr4,ptr3,&age,age);
}

void MyPrintB()
{
int number = 10;

int *ptr = &number; // 定义指针指向number

ptr = 0xffff; // 该指针是一个错误的地址(野指针)
*ptr = 200; // 尝试赋值会发生错误
}

int main(int argc, char* argv[])
{
MyPrintA();

system("pause");
return 0;
}

指针的步长与取值: 指针与指针之间可以强制类型转换通过步长索引元素,offsetof()可用于计算步长.

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

void MyPrintA()
{
char buf[1024] = { 0 };

int number = 10;
int name = 20;

// 在buf+1的内存地址处,将number里面的数据,拷贝进去
memcpy(buf + 1, &number, sizeof(int)); // 第一个数据位置 + 1
memcpy(buf + 5, &name, sizeof(int)); // 第二个数据位置 1+4 => 5

char *number_ptr = buf;
// (int *) => 代表要强制取出int => 4字节的数据
// (number_ptr + 1) => 代表要从此处向后取出4字节
int number_tmp = *(int *)(number_ptr + 1); // 找到第一个变量的地址
printf("取出buf里面的4字节(int) --> 地址: %x --> 数据: %d \n", &number_tmp, number_tmp);

char *name_ptr = buf;
int name_tmp = *(int *)(name_ptr + 5); // 找到第二个变量的地址
printf("取出buf里面的4字节(int) --> 地址: %x --> 数据: %d \n", &name_tmp, name_tmp);
}

void MyPrintB()
{
struct Person
{
int num; // 4 字节
char name[64]; // 64 字节
int age; // 4 字节
};

struct Person stu = { 1, "lyshark", 23 };
printf("age相对于Person首地址偏移量: %d \n", offsetof(struct Person, age));

// 定位到Age的内存 (char *)&stu + offsetof(struct Person,age)
// 整体括起来取首地址 ( (char *)&stu + offsetof(struct Person,age) )
// 强制取出4字节数据 (int *) ((char *)&stu + offsetof(struct Person,age))

int struct_age = *(int *)((char *)&stu + offsetof(struct Person, age));
printf("结构体Person中的 Age 的数据 --> %d \n", struct_age);

int struct_name = ((char *)&stu + offsetof(struct Person, name));
printf("结构体Person中的 Name 的数据 --> %s \n", struct_name);
}

int main(int argc, char* argv[])
{

MyPrintB();
system("pause");
return 0;
}

指针的间接赋值: 指针变量与普通变量赋值不同,指针代表一个地址,当该地址中的值被改变时,指向的变量都会发生变化.

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

void ChangeValue(int *ptr)
{ // 改变某个值
(*ptr) = 200;
}

void ChangePointer(int **val)
{ // 改变指针的值
*val = 0x09;
}

int main(int argc, char* argv[])
{
int number = 100;
ChangeValue(&number);
printf("改写后的数值: %d --> 当前number地址: %x \n", number, &number);

int *ptr = NULL;
ChangePointer(&ptr);
printf("改写后的数值: %d --> 当前ptr地址: %x \n", ptr,&ptr);

system("pause");
return 0;
}

定义Void万能指针: 万能指针就是什么数据类型的数据都能指,但是在指向其他数据时必须经过转换才可使用.

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

// 万能指针,必须经过转换才可使用
void MyPrintA()
{
int number = 10;
void *int_ptr = &number; // 万能指针

*(int *)int_ptr = 100; // 赋值必须进行强转(告诉它需要读取多大的字节)
printf("数值: %d --> 地址: %x \n", number, int_ptr);
}

// 针对数组类型的万能指针
void MyPrintB()
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

void *array_ptr = Array; // 赋值一个空指针

*(int *)array_ptr = 100; // 改变第一个值 Array[0]
*((int *)array_ptr + 1) = 200; // 改变第二个值 Array[1]

for (int x = 0; x < 10; x++)
printf("输出指针: %x 数据: %d \n", ((int *)array_ptr+x),Array[x]);
}

void MyPrintC()
{
int Array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

void *array_ptr = Array;

//*(((int *)array_ptr + 1) + 2) = 400; // Array[1][0]
//*(((int *)array_ptr + 2) + 2) = 500; // Array[1][1]

printf("Array[0][0] -> %d \n", *((int *)array_ptr + 0));
printf("Array[1][0] -> %d \n", *(((int *)array_ptr + 0) + 3)); // 步长为3
printf("Array[1][1] -> %d \n", *(((int *)array_ptr + 1) + 3)); // 一维数组大小为3
}

int main(int argc, char* argv[])
{
MyPrintC();

system("pause");
return 0;
}

Calloc 分配堆指针: calloc()函数,主要用于一次性分配内存空间,与之相似的函数有malloc()使用与其大同小异.

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

int main(int argc, char* argv[])
{
// 开辟 10 * sizeof(int) 大小的堆空间
int *ptr = calloc(10, sizeof(int));

// 循环将堆空间进行赋值
for (int x = 0; x < 10; x++)
ptr[x] = x + 1;

// 循环输出堆中数据
for (int x = 0; x < 10; x++)
printf("堆地址: %x --> 数据: %d \n", &ptr[x], ptr[x]);

// 释放堆空间
if (ptr != NULL)
{
free(ptr);
ptr = NULL;
}

system("pause");
return 0;
}

Realloc 扩充堆指针: 当堆空间容量不足时,我们就可以使用Realloc()函数对堆空间进行动态的扩容.

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

int main(int argc, char* argv[])
{
// 使用 malloc() 分配10个整数类型的数据
int *ptr = malloc(sizeof(int)* 10);

// 循环对堆空间初始化,并输出结果
for (int x = 0; x < 10; x++)
{
ptr[x] = x;
printf("堆地址: %x --> 数据: %d \n", &ptr[x],ptr[x]);
}

// 开始扩充堆空间
// realloc() 函数追加空间,如果空间够用则直接追加.
// 如果空间不够用了,那么将会重新分配一块空间,并将原数据拷贝到新的地址上
// 最后将原始的空间释放掉,只保留新的空间
int old_ptr = ptr;
ptr = realloc(ptr, sizeof(int) * 200);
int new_ptr = ptr;

if (old_ptr != new_ptr)
printf("原始空间: 0x%x --> 新空间: 0x%x \n", old_ptr, new_ptr);
else
printf("原始空间: 0x%x 没有发生变化.\n",old_ptr);

system("pause");
return 0;
}

定义指向数组的指针: 通过typedef()方式重定义指向数组的指针变量,并可以通过指针灵活的遍历输出.

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

// 第一种方式:间接定义数组指针类型
void MyPrintA()
{
// typedef 重定义一个数组指针类型
typedef int(Array_Type)[10];
// 相当于定义了 => int MyArray[10];
Array_Type MyArray;

for (int x = 0; x < 10; x++)
{
MyArray[x] = x;
printf("输出 MyArray --> %d \n", MyArray[x]);
}

// 定义数组指针,该指针是指向整个数组的指针
Array_Type *pArray = &MyArray;

// 由于 pArray 指向了整个数组,我们该如何遍历呢 ?
for (int x = 0; x < 10; x++)
{
int Number = *(*pArray + x);
printf("遍历 MyArray --> %d \n", Number);
}
}
// 第二种方式:直接定义数组指针类型
void MyPrintB()
{
// 直接定义Array数组指针类型,并将pArray_Point指向数组
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

typedef int(*Array_Point)[10]; // 直接定义指针类型
Array_Point pArray_Point = &Array; // 将指针指向数组首地址

for (int x = 0; x < 10; x++)
printf("遍历 pArray_Point --> %d \n", *(*pArray_Point + x));
}

// 第三种方式:直接定义数组指针变量
void PrintC()
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int(*pArray_Param)[10] = &Array; // 直接声明 pArray_Param

for (int x = 0; x < 10; x++)
printf("遍历 pArray_Param --> %d \n", *(*pArray_Param + x));
}

int main(int argc, char* argv[])
{
PrintC();
system("pause");
return 0;
}

用指针实现冒泡排序: 通过指针的方式实现冒泡排序算法,主要是练习一下指针的使用技巧.

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

void BubbleSort(int *Array, int len)
{
for (int x = 0; x < len - 1; x++)
{
for (int y = 0; y < len - 1 - x; y++)
{
if (*(Array + y) > *(Array + y + 1))
{
int tmp = *(Array + y);
*(Array+y) = *(Array + y + 1);
*(Array + y + 1) = tmp;
}
}
}
}

int main(int argc, char* argv[])
{
int Array[10] = { 4,7,8,2,1,8,9,3,4,10 };
int len = sizeof(Array) / sizeof(Array[0]);
int *ptr = Array;

BubbleSort(ptr, len);

for (int x = 0; x < 10; x++)
{
printf("%d ", *(ptr+x));
}

system("pause");
return 0;
}

用指针实现选择排序: 针对字符串的选择排序,从小到大排列,这里我们使用了二级指针作为函数参数.

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

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

void SelectSort(char **Array, int len)
{
for (int x = 0; x < len; ++x)
{
int min = x;
for (int y = x + 1; y < len; ++y)
{
if (strcmp(Array[y], Array[min]) < 0)
min = y;
}
if (min != x)
{
char *tmp = Array[min];
Array[min] = Array[x];
Array[x] = tmp;
}
}
}

int main(int argc, char* argv[])
{
char *pArray[] = { "ddd", "bbb","sss", "qqq", "yyy", "eee", "ooo" };
int len = sizeof(pArray) / sizeof(char *);

SelectSort(pArray, len);
PrintArray(pArray, len);

system("pause");
return 0;
}

用指针完成数组逆序: 用指针实现逆序存放数组元素值,所谓逆序就是将传入的数组顺序反向颠倒.

#include <stdio.h>

int ArrayReverse(int *Array, int len)
{
int *Ptr, *StartPtr, *EndPtr, Middle, Temporary;

StartPtr = Array; // 取出数组首地址
EndPtr = Array + len - 1; // 取出数组结束地址
Middle = (len - 1) / 2; // 计算出中位数
Ptr = Array + Middle; // 指向数组尾地址

for (StartPtr = Array; StartPtr <= Ptr; StartPtr++, EndPtr--)
{
Temporary = *StartPtr;
*StartPtr = *EndPtr;
*EndPtr = Temporary;
}
return 0;
}

int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };

ArrayReverse(&Array, 10);

for (int x = 0; x < 10; x++)
printf("%d ", Array[x]);

system("pause");
return 0;
}

用指针查找最大最小值: 使用指针查找数列中最大值/最小值,找到后分别返回到两个变量中.

#include <stdio.h>

int Lookup_Max_Min(int *Array, int len,int *max,int *min)
{
int *ptr;
*max = *min = *Array; // 假设最大最小都是Array

for (ptr = Array + 1; ptr < Array + len; ptr++)
{
if (*ptr > *max)
*max = *ptr;
else if (*ptr < *min)
*min = *ptr;
}
return 0;
}

int main(int argc, char* argv[])
{
int Array[10] = { 6,5,7,8,9,0,3,2,1,5 };
int Array_Max, Array_Min;

Lookup_Max_Min(&Array, 10,&Array_Max,&Array_Min);

printf("最大值: %d --> 最小值: %d \n", Array_Max, Array_Min);

system("pause");
return 0;
}

指针实现数组操作: 如下案例中演示了利用指针实现基本取值,基本替换,正反向遍历等操作.

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
int Array[] = {1,2,3,4,5,6,7,8,9,10};

DWORD *PEB = (DWORD *)Array;
DWORD *Ldr = *((DWORD **)((DWORD *)Array + 1));
printf("Ldr = > %x Value = %d \n", &Ldr,Ldr);

// 基本的取值
printf("PEB = %x &PEB = %x \n", PEB, &PEB);
Ldr = (DWORD *)&PEB;
printf("LDR = %x &LDR = %x *LDR = %x \n", Ldr, &Ldr, *Ldr);
printf("(unsigned char *)PEB = %x \n", (unsigned char *)PEB);
printf("(unsigned char *)PEB + 4 = %x \n", (unsigned char *)PEB + 4);
printf("取出第一个元素内存地址: %x \n", (DWORD **)((unsigned char *)PEB + 4));
printf("取出其中的值: %d \n", *(DWORD **)((unsigned char *)PEB + 4));

// 取值与替换值
DWORD ref = (DWORD)*((unsigned char *)Array + 4); // 取出第2个值
*((DWORD *)(Array + 1)) = 10; // 替换数组中第二个值
printf("取出的值: %d 替换后: %d \n", ref, *((DWORD *)(Array + 1)));

// 正向遍历元素
for (int x = 0; x < 10; x++)
{
DWORD ref1 = *((DWORD *)((unsigned char *)Array + (x * 4)));
printf("正向元素输出: %d \n", ref1);
}

// 反向输出元素
DWORD *Frist = NULL;
DWORD *Last = NULL;
for (int x = 0; x < 10; x++)
{
Frist = *(DWORD **)((DWORD *)Array + x);
Last = (DWORD *)((DWORD *)Array + (9 - x));
printf("反向输出: %d --> %d \n", Frist, *Last);
}

system("pause");
return 0;
}

指针定位多维数组: 以下案例通过指针定位二三维指针数组中的元素,并输出元素值.

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] };
DWORD *PEB;

// 这三种方式均可定位二级数组
PEB = *((DWORD **)((DWORD *)ArrayPtr + 1));
printf("%x %d \n", PEB,*PEB);

PEB = *((DWORD **)((DWORD *)(ArrayPtr + 1)));
printf("%x %d \n", PEB, *PEB);

PEB = *(DWORD **)((unsigned char *)(ArrayPtr) + (1*4));
printf("%x %d \n", PEB, *PEB);

PEB = *(DWORD **)ArrayPtr;
printf("得到ArrayPtr地址: %x --> 得到Array元素地址: %x --> 得到元素值: %x \n", &PEB,PEB,*PEB);

// 二级元素赋值操作
printf("得到第一个指针地址: %x \n", (DWORD)*(DWORD **)ArrayPtr);
printf("得到第二个指针地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + 1), *(*((DWORD **)ArrayPtr) + 1));

printf("原始数据为: %x \n", *ArrayPtr[1]);
*((DWORD *)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改数据为: %x \n", *ArrayPtr[1]);

printf("原始指针数据为: %x \n", *ArrayPtr[1]);
**((DWORD **)(ArrayPtr + 1)) = (DWORD)*(DWORD **)ArrayPtr;
printf("更改指针数据为: %x \n", *ArrayPtr[1]);

for (int x = 0; x < 5; x++)
{
printf("地址: %x --> 数据: %d \n", (*((DWORD **)ArrayPtr) + x), *(*((DWORD **)ArrayPtr) + x));
}
system("pause");
return 0;
}

同理三维指针的定位同样如此,只是在解析时需要多加一层即可取出数据.

#include <stdio.h>
#include <Windows.h>

int main(int argc, char * argv[])
{
DWORD Array[] = { 1, 2, 3, 4, 5 };
DWORD *ArrayPtr[] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
DWORD *ArrayPtrS[] = { ArrayPtr[0], ArrayPtr[1], ArrayPtr[2], ArrayPtr[3], ArrayPtr[4] };

// 输出三级指针中的数据
DWORD *PtrA = (DWORD *)((DWORD **)((DWORD ***)ArrayPtrS));
printf("获取到ArrayPtr[0]地址 = %x \t 获取到Array[0]地址 = %x \n", PtrA,*PtrA);

DWORD *PtrB = (DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)));
printf("获取到ArrayPtr[1]地址 = %x \t 获取到Array[1]地址 = %x \n", PtrB, *PtrB);

DWORD PtrC = *((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("获取到里面的数值: %d \n", *(DWORD *)PtrC);

// 三级指针 遍历指针数据
printf("ArrayPtrS => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", &ArrayPtrS[x]);
printf("\n");
printf("ArrayPtr => ");
for (int x = 0; x < 5; x++)
printf("0x%x ", ArrayPtr[x]);
printf("\n");

// 输出特定指针
DWORD *PtrBB = ((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1))));
printf("ArrayPtrS[1] => %x \n", PtrBB);

DWORD **PtrAA = *((DWORD ***)(ArrayPtrS)+1);
printf("ArrayPtr[1] => %x \n", PtrAA);

DWORD Ref = *(DWORD *) (*((DWORD *)((DWORD **)((DWORD ***)(ArrayPtrS + 1)))));
printf("原始数值 ArrayPtrS[1] = %x \n", Ref);

system("pause");
return 0;
}

第二章:使用函数与指针

指针作为函数参数传递: 将指针直接作为函数参数传递,此时函数中接收到的就是数组的首地址.

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

void MyPrintA(const char *String)
{
printf("输出指针内容: %s \n", String + 5);
}

void MyPrintB(const **String, int len)
{
// 此处的 *(String + x) => *(String + 0) == String[0]
for (int x = 0; x < len;x++)
printf("首地址: 0x%x 参数: %s \n", *(String + x), String[x]);
}

int main(int argc, char* argv[])
{
// 一级指针的传递方法
char *ptr = malloc(sizeof(char)* 100); // 开辟 char * 100
memset(ptr, 0, 100); // 初始化空间为0
strcpy(ptr, "hello lyshark"); // 拷贝数据到内存
MyPrintA(ptr);

// 二级指针的传递方法
char *String[] = { "admin", "guest", "lyshark", "root" };
int len = sizeof(String) / sizeof(String[0]);
MyPrintB(String, len);

system("pause");
return 0;
}

指针作为函数返回值: 当一个函数执行结束后,会返回一些值,我们可以通过指针的方式间接返回.

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

void AllocteSpace(char **Point)
{
char *ptr = malloc(100);
memset(ptr, 0, 100);
strcpy(ptr, "hello lyshark");
*Point = ptr; // 将分配的地址直接甩出去
}

int main(int argc, char* argv[])
{
char *recv_ptr = NULL;

AllocteSpace(&recv_ptr);

printf("指针地址: %x --> 输出: %s \n", &recv_ptr, recv_ptr);

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


system("pause");
return 0;
}

多维数组做函数参数: 将多维数组当作函数参数传递到函数内,并在函数内部进行强转,将指针强转为二维数组.

#include <iostream>
#include <Windows.h>

// 传入数组基地址以及一维元素个数
int GetArray(void* Pointer,int ArrayLen)
{
// 将指针转为二维数组
char(*ptr)[10];
ptr = (char(*)[10])Pointer;

int count = 0;
for (int x = 0; x < ArrayLen; x++)
{
// 判断字符串是否为空
if (strlen(ptr[x]) != 0)
{
count = count + 1;
}
}
return count;
}

int main(int argc, char* argv[])
{
char array[5][10] = {"zhangsan","lisi","wangwu"};

// 定义指针
void* Pointer = &array[0][0];

// 统计一维数组元素个数
int ref = GetArray(Pointer, 5);
std::cout << "元素个数: " << ref << std::endl;
return 0;
}

多级指针做函数参数: 如下代码中PrintArray()函数传递了一个常量形式的二级指针作为参数,常量不可被修改.

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

void PrintArray(const int **point)
{
for (int x = 0; x < 10; x++)
printf("地址: %x --> 数据: %d \n", &point[x],*point[x]);
}

int main(int argc, char* argv[])
{
int ary[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 堆区分配指针,接收指针(地址)的指针
int **pArray = malloc(sizeof(int *) * 10);

for (int x = 0; x < 10; x++)
// *(pArray + x) => pArray[x] = &ary[x];
*(pArray + x) = &ary[x];

PrintArray(pArray);

system("pause");
return 0;
}

同理将多级指针进行封装,函数只需传递指针即可实现分配与释放,更易于使用.

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

// 分配空间并初始化
void allocateSpace(int **Point)
{
int * Array = malloc(sizeof(int)* 10);
for (int x = 0; x < 10; x++)
Array[x] = x;
*Point = Array;
}
// 输出元素
void PrintArray(int *Point)
{
for (int x = 0; x < 10; x++)
printf("地址: %x --> 数据: %d \n", &Point[x],Point[x]);
}
// 释放空间
void freeSpace(int **Point)
{
free(*Point);
*Point = NULL;
Point = NULL;
}

int main(int argc, char* argv[])
{
int *pArray = NULL;

allocateSpace(&pArray);
PrintArray(pArray);
freeSpace(&pArray);

system("pause");
return 0;
}

分配二级堆指针: 二级堆指针的含义是首先通过malloc开辟一级指针,然后在一级指针里面开辟二级指针.

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

void PrintArray(const int **tmp)
{
for (int x = 0; x < 10; x++)
printf("%d ", *tmp[x]);
}

int main(int argc, char* argv[])
{
// 开辟二级指针空间,里面用于存放一级指针
int **pArray = malloc(sizeof(int *)* 10);

for (int x = 0; x < 10; x++)
{
pArray[x] = malloc(4); // 动态开辟一级整数空间
*(pArray[x]) = x; // 给整数空间赋值
}

PrintArray(pArray);

// 释放堆内存
for (int x = 0; x < 10; x++)
{
if (pArray[x] != NULL)
{
free(pArray[x]); // 先释放小的空间
pArray[x] = NULL; // 小空间指针初始化
}
free(*pArray); // 最后释放大空间
*pArray = NULL; // 大空间指针初始化
}

system("pause");
return 0;
}

上方我们可以很直观的看出二级指针的分配方式,其实字符串指针默认就是二级指针,只是看起来像是一级而已.

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

int main(int argc, char* argv[])
{
char *p[] = { "package", "housing", "pace", "unleash" };
char **ptr = (char **)malloc(sizeof(char*) * 4);

// 开辟空间并将字符串拷贝到空间中
for (int x = 0; x < 4; x++)
{
ptr[x] = (char*)malloc(10 * sizeof(char));
sprintf(ptr[x], "%s", p[x]);
}
// 输出指针ptr指向的数据
for (int x = 0; x < 4; x++)
{
printf("地址: %x --> 数据: %s \n", &ptr,*ptr);
ptr++;
}

system("pause");
return 0;
}

指向函数的指针(函数指针): 函数指针通常用于指向一个函数首地址,通过指针可以调用该函数.

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

int MyPrintA(int x,int y)
{
printf("MyPrintA --> %d --> %d \n", x, y);
return 1;
}

int main(int argc, char* argv[])
{
printf("函数名入口: %d \n", &MyPrintA);

// 直接定义并调用函数指针
int *FuncAddr = (int *) &MyPrintA; // 获取函数地址
int(*MyFunc)(int,int) = FuncAddr; // 定义函数指针
int result = MyFunc(10,20); // 调用函数指针

// 定义函数类型,并通过类型定义函数指针
typedef int(Func_Type)(int, int); // 先定义类型
Func_Type *pFunc = MyFunc; // 定义函数指针
pFunc(100, 200); // 调用(1)
(*pFunc)(1000, 2000); // 调用(2)

// 直接定义函数类型,并使用(1)
typedef int(*Func_Ptr)(int, int);
Func_Ptr pFunc2 = MyFunc;
pFunc2(10000, 20000);

// 直接定义函数类型,并使用(2)
int(*pfunc)(int, int) = NULL;
pfunc = MyFunc;
pfunc(100000, 200000);

// 如果拿到了一个地址,我们该如何将其转换为函数指针,并调用?
// 此处应该关闭基址随机化 ASLR
printf("函数名入口: %x \n", &MyPrintA);
// (int (*)(int, int)) 0x4110e6;
int(*Print)(int, int) = 0x4110e6; // 转换为函数指针 MyPrintA
Print(1, 2); // 直接调用

system("pause");
return 0;
}

函数指针的参数传递: 将一个函数地址进行参数传递,实现动态的函数值传递,可作为函数定制使用.

#include <stdio.h>

int add(int x, int y) { return x + y; }

int sub(int x, int y) { return x - y; }

// 函数可以作为另外一个函数的参数
void doLogic(int(*pFunc)(int, int),int x,int y)
{
int result = pFunc(x, y);
printf("X与Y进行运算后的结果是: %d \n", result);
}

int main(int argc, char* argv[])
{
// 手动将指针指向add函数,然后调用
int(*ptr)(int, int) = add;
int result = ptr(10, 20);
printf("两个数相加的结果是: %d \n", result);

// 通过使用dologic函数实现间接调用
doLogic(&sub, 100, 10);
doLogic(&add, 100, 10);

system("pause");
return 0;
}

函数指针传递数组: 除了传值以外,函数指针同样可以实现传递一个指针数组,并依次循环调用函数.

#include <stdio.h>

int add(int x, int y)
{
int result = x + y;
printf("x + y => %d ", result);
return result;
}

int sub(int x, int y)
{
int result = x - y;
printf("x - y => %d ", result);
return result;
}

int main(int argc, char* argv[])
{
int(*func_array[2])(int, int); // 定义函数指针数组

func_array[0] = add; // 分别给与不同的函数地址
func_array[1] = sub; // 并将函数地址存入函数指针数组

// 循环调用两个函数,并以依次传入 x = 200 ,y=20
for (int x = 0; x < 2; x++)
{
int z = func_array[x](100, 20);
printf(" -> %d \n", z);
}

system("pause");
return 0;
}

函数指针绑定回调函数: 函数指针也可以实现绑定回调函数,当函数执行到指定位置时,执行回调输出一些数据.

#include <stdio.h>

// 该函数用于输出数据.
void Print_Array(void *Array, int eleSize, int len, void(*print)(void *))
{
char *start = (char *)Array; // 转为每次读取一个字节
for (int x = 0; x < len; ++x)
{
//printf("数据: %d \n", start + x * eleSize);
print(start + x * eleSize);
}
}

// 此处就是我们定义的回调函数.
void MyPrint(void *recv)
{
int *ptr = (int*)recv; // 指针强转为4字节
printf("回调函数 --> 输出地址: %x --> 数据: %d \n", ptr,*ptr);
}

void MyPrintPerson(void *recv)
{ // 指针强转为结构体类型,才能读数据.
struct Person *stu = (struct Person *)recv;
printf("回调函数 --> 输出地址: %x \n", stu);
}

int main(int argc, char* argv[])
{
// 第一种传递数组的方式,实现回调
int arry[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Print_Array(arry, sizeof(int), 10, MyPrint);

// 第二种传递结构体的方式实现的回调
struct Person
{
char name[64];
int age;
};

struct Person stu[] = { { "admin", 22 }, { "root", 33 } };
Print_Array(stu, sizeof(struct Person), 2, MyPrintPerson);

system("pause");
return 0;
}