【C】指针初阶

指针

为了后续一些安排打基础,决定使用C/C++作为算法主语言,所以从这篇文章开始,从指针开始总结

指针 -> 指针进阶 -> 字符串函数 -> 自定义类型 -> 动态内存管理 -> 数据结构

还有C++一些基础语法的回顾(基于算法竞赛使用)

文章目录

  • 指针
    • 概念
      • 指针变量
    • 指针和指针类型
      • 指针+-整数
      • 解引用
    • 野指针
      • 成因
        • 指针未初始化
        • 指针越界访问
        • 指针指向的空间释放
      • 规避野指针
    • 指针运算
      • +-整数
      • 指针-指针
      • 关系运算
    • 指针和数组
    • 二级指针

概念

  1. 指针是内存中一个最小单元的编号,也就是地址

  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总的来说,指针就是地址,,口语中说的指针通常指的是指针变量。

指针变量

我们可以通过&(取地址操作符)取出变量的内存其实就是地址,把地址可以存放到一个变量中,这个变量就是指针变量

#include 
int main()
{
 int a = 10; //在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
             //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
 return 0;
}

指针和指针类型

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

指针±整数

#include 
//演示实例
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);//打印指针 pc 向后移动一个单位(1 字节)后的内存地址,使用 %p 格式说明符,并换行
 printf("%p\n", pi);
 printf("%p\n", pi+1);//打印指针 pi 向后移动一个单位(4 字节)后的内存地址,使用 %p 格式说明符,并换行。
 return  0;
}
//输出结果
00DDF7E8
00DDF7E8
00DDF7E9
00DDF7E8
00DDF7EC

**总结:**指针的类型决定了指针向前或者向后走一步有多大(距离)~

解引用

在C语言中,指针解引用操作使用*符号来访问指针所指向的内存位置的值。也就是说,使用*操作符可以获取指针所指向的内存位置的值,或者可以修改该内存位置的值。

#include 
int main()
{
 int n = 0x11223344;
 char *pc = (char *)&n;
 int *pi = &n;
 *pc = 0;   //重点在调试的过程中观察内存的变化。
 *pi = 0;   //重点在调试的过程中观察内存的变化。
 return 0;
}

另外一个例子:

#include 

int main() {
    int num = 10;
    int *ptr = #

    printf("Value of num: %d\n", num);      // 输出:Value of num: 10
    printf("Value of num using pointer: %d\n", *ptr);   // 输出:Value of num using pointer: 10

    *ptr = 20;    // 使用指针解引用操作修改指针所指向的内存位置的值

    printf("Updated value of num using pointer: %d\n", *ptr);   // 输出:Updated value of num using pointer: 20
    printf("Updated value of num: %d\n", num);      // 输出:Updated value of num: 20

    return 0;
}
//结果
Value of num: 10
Value of num using pointer: 10
Updated value of num using pointer: 20
Updated value of num: 20

综上的例子:我的理解就是:指针的解引用操作就是可以修改那个被解引用对象或者变量的所在的内存的值

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

野指针

所谓的野指针,就是指向无效内存地址的指针,会导致不可预测的行为~

成因

指针未初始化
#include 
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}

这段代码在声明了一个指针变量 p 后,没有将其初始化为有效的内存地址,而是直接对其进行解引用操作,将值 20 存储到目标地址中。

要修复这个问题,应该在使用指针之前先为其分配内存。可以使用 malloc() 函数来动态分配内存(后面会更新),或者将指针指向一个已有的变量或数组。以下是修复后的示例代码:

#include 
int main()
{
   int *p = malloc(sizeof(int)); // 分配内存用于存储 int 类型变量
   *p = 20; // 解引用指针并为其赋值
   // 使用指针的值
   printf("Value: %d\n", *p);
   // 释放内存
   free(p);
   return 0;
}
指针越界访问
#include 
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0;
}
指针指向的空间释放

后面在动态内存管理的博客中会再总结一次~

一般这种指针被叫做:悬空指针

当使用 free() 函数释放一个动态分配的内存块时,该内存块被标记为可重新使用,但指向该内存块的指针仍然保留着原来的地址。如果在释放内存之后继续使用该指针,它就成为了一个指向已释放内存的野指针。

下面给个示例代码:

#include 
#include 

int main()
{
    int *p;
    p = malloc(sizeof(int));
    *p = 10;
    free(p); // 释放了内存
    printf("Value: %d\n", *p); // 访问已释放内存的指针
    *p = 20; // 修改已释放内存的指针
    return 0;
}

在上述示例中,我们首先使用 malloc() 分配了一个整型的内存块,并将其赋值为 10。然后,我们用 free() 函数释放了该内存块,使得指针 p 成为了一个指向已释放内存的悬空指针。

接下来,我们尝试访问该悬空指针的值并打印,这个操作属于未定义行为,可能会输出错误结果、崩溃或其他不可预测的行为。

最后,我们尝试修改悬空指针的值为 20,同样也是未定义行为,可能会导致程序异常终止或产生其他意外结果。

为了避免这种问题,我们应该在释放内存后将指针设置为 NULL,并在使用指针之前检测其是否为 NULL

#include 
#include 

int main()
{
    int *p;
    p = malloc(sizeof(int));
    *p = 10;
    free(p);
    p = NULL; // 将指针设置为 NULL
    if (p != NULL) {
        printf("Value: %d\n", *p); // 避免访问已释放内存的指针
    }
    p = malloc(sizeof(int)); // 重新分配内存
    *p = 20; // 修改重新分配的内存的值
    free(p);
    return 0;
}

在修复后的示例中,我们在释放内存后将指针 p 设置为 NULL。然后,在使用指针之前,我们先检查指针是否为 NULL,以确保它指向有效的内存。我们还重新分配了内存并修改了相应的指针。

这个示例展示了正确处理指向已释放内存的指针的方式,以避免悬空指针问题的发生。

规避野指针

  1. 指针初始化

  2. 小心指针越界

  3. 指针指向空间释放,及时置NULL

  4. 避免返回局部变量的地址

  5. 指针使用之前检查有效性

指针运算

±整数

#include 

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr; // 指向数组的第一个元素

    // 使用指针的加法运算
    printf("Elements of array using pointer arithmetic:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *ptr);
        ptr++; // 指针向后移动一个 int 大小的位置
    }

    printf("\n");

    // 使用指针的减法运算
    ptr = &arr[4]; // 指向数组的最后一个元素

    printf("Elements of array (reverse order) using pointer arithmetic:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *ptr);
        ptr--; // 指针向前移动一个 int 大小的位置
    }

    printf("\n");
    
    return 0;
}
//输出结果
Elements of array using pointer arithmetic:
10 20 30 40 50
Elements of array (reverse order) using pointer arithmetic:
50 40 30 20 10

指针-指针

当两个指针相减时,其结果是两个指针之间的距离(以指针指向的类型大小为单位)。这个结果是一个整数类型。

指针-指针的操作通常用于比较两个指针之间的距离,或者计算数组中连续元素之间的距离。

#include 

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr1 = &arr[1]; // 指向数组的第二个元素
    int *ptr2 = &arr[4]; // 指向数组的最后一个元素

    // 计算指针之间的距离
    int diff = ptr2 - ptr1;

    printf("Distance between ptr1 and ptr2: %d\n", diff);

    return 0;
}
//输出结果
Distance between ptr1 and ptr2: 3

注意:

两个指针必须指向同一个数组(或同一块内存)才能进行指针-指针的操作。

如果两个指针指向不同的数组,或者一个指针指向数组的开始而另一个指针指向数组的结束,则结果是无效的。


同时,还可以使用指针-指针操作来比较两个指针的相对位置

#include 

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr1 = &arr[1]; // 指向数组的第二个元素
    int *ptr2 = &arr[3]; // 指向数组的第四个元素

    if (ptr1 < ptr2) {
        printf("ptr1 is before ptr2\n");
    } else if (ptr1 > ptr2) {
        printf("ptr1 is after ptr2\n");
    } else {
        printf("ptr1 and ptr2 are equal\n");
    }

    return 0;
}

注意

仅当两个指针指向同一个数组(或同一块内存)时,比较两个指针的相对位置才有意义。否则,结果是未定义的。

关系运算

#include 

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr1 = arr; // 指向数组的第一个元素
    int *ptr2 = &arr[2]; // 指向数组的第三个元素

    if (ptr1 > ptr2) {
        printf("ptr1 is greater than ptr2\n");
    }

    if (ptr1 < ptr2) {
        printf("ptr1 is less than ptr2\n");
    }

    if (ptr1 >= ptr2) {
        printf("ptr1 is greater than or equal to ptr2\n");
    }

    if (ptr1 <= ptr2) {
        printf("ptr1 is less than or equal to ptr2\n");
    }

    if (ptr1 == ptr2) {
        printf("ptr1 is equal to ptr2\n");
    }

    if (ptr1 != ptr2) {
        printf("ptr1 is not equal to ptr2\n");
    }

    return 0;
}

指针和数组

#include 
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}
00F3FBC8
00F3FBC8

可见数组名和数组首元素的地址是一样的

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。

例如:

#include 
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,0};
 int *p = arr;//p存放的是数组首元素的地址
 int sz = sizeof(arr)/sizeof(arr[0]);
   for(i=0; i<sz; i++)
  {
    printf("&arr[%d] = %p  <====> p+%d = %p\n", i, &arr[i], i, p+i);
  }
   return 0;
}
&arr[0] = 00AFF708  <====> p+0 = 00AFF708
&arr[1] = 00AFF70C  <====> p+1 = 00AFF70C
&arr[2] = 00AFF710  <====> p+2 = 00AFF710
&arr[3] = 00AFF714  <====> p+3 = 00AFF714
&arr[4] = 00AFF718  <====> p+4 = 00AFF718
&arr[5] = 00AFF71C  <====> p+5 = 00AFF71C
&arr[6] = 00AFF720  <====> p+6 = 00AFF720
&arr[7] = 00AFF724  <====> p+7 = 00AFF724
&arr[8] = 00AFF728  <====> p+8 = 00AFF728
&arr[9] = 00AFF72C  <====> p+9 = 00AFF72C

所以 p+i 其实计算的是数组 arr 下标为i的地址。

那我们就可以直接通过指针来访问数组。

int main()
{
 int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
 int *p = arr; //指针存放数组首元素的地址
 int sz = sizeof(arr) / sizeof(arr[0]);
 int i = 0;
 for (i = 0; i<sz; i++)
 {
 printf("%d ", *(p + i));
 }
 return 0;
}

二级指针

二级指针也称为指向指针的指针,它是一种较为复杂的指针类型。通过使用二级指针,我们可以操作指针的指针,即通过一个指针间接引用另一个指针。

二级指针常用于需要在函数之间传递和修改指针的地址的情况,它提供了更灵活的指针操作方式。

对于二级指针的运算有:

  • *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa *ppa 其实访问的就是 pa

    int b = 20;
    *ppa = &b;//等价于 pa = &b;
    
  • **ppa 先通过 *ppa 找到pa,然后对 pa 进行解引用操作:*pa,那找到的是 a .

    **ppa = 30;
    //等价于*pa = 30;
    //等价于a = 30;
    

    再看一下下面代码:

#include 
#include 

int main() {
    int val = 42;
    int* ptr = &val; // 一级指针,指向 val 的地址
    int** dptr = &ptr; // 二级指针,指向 ptr 的地址

    printf("Value: %d\n", **dptr); // 通过二级指针访问 val 的值

    int* newVal = (int*)malloc(sizeof(int)); // 动态分配内存
    *newVal = 99; // 通过一级指针修改 newVal 的值
    *dptr = newVal; // 通过二级指针修改 ptr 的值,使其指向 newVal

    printf("Value: %d\n", **dptr); // 通过二级指针访问 newVal 的值

    free(*dptr); // 释放内存
    *dptr = NULL; // 通过二级指针重置指针变量为 NULL

    return 0;
}

在上述示例中,我们首先声明了一个整型变量 val,然后声明了一个一级指针 ptr,该指针指向 val 的地址。然后,我们声明了一个二级指针 dptr,该指针指向 ptr 的地址。

在打印 **dptr 时,我们使用两次解引用操作符 *,可以间接访问 val 的值。

接下来,我们使用 malloc 函数动态分配了一个新的整型变量,并通过一级指针 newVal 修改了它的值。然后,我们通过二级指针 dptr 修改了一级指针 ptr 的指向,使其指向新分配的内存。

最后,我们使用 free 函数释放了动态分配的内存,并将指针变量重置为 NULL


至此,指针的初阶总结到此结束,接下来会持续感谢~

你可能感兴趣的:(C语言,c语言,数据结构,算法)