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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值