一篇文章带你深入了解“指针”(下)

简介: 一篇文章带你深入了解“指针”(下)

一篇文章带你深入了解“指针”(上)https://developer.aliyun.com/article/1583521?spm=a2c6h.13148508.setting.27.197b4f0eDwuZrP

野指针

野指针,即指针指向的位置是不可知的,随机的,不正确的,没有明确限制的.

野指针的形成原因

int main(void)
{
  int* p;
  *p = 10;
  return 0;
}

  • 指针未初始化
int main(void)
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int* pa = arr;
  int i = 0;
  for (i = 0; i < 12; i++)
  {
    *(pa++) = 0;
  }
  return 0;
}

  • 指针越界访问
int* num()
{
  int x = 2;
  return &x;
}
int main(void)
{
  int* p = num();
  printf("%d\n",*p);
  return 0;
}
  • 指针指向的空间释放(当调用完num函数时,num函数会被释放,而指针p指向的内容可能随时会被改变)

规避野指针

1.指针初始化

2.防止数组越界

3.指针不在使用时,应该及时置NULL

4.指针在使用之前应该及时检查其有效性

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

二级指针

int main(void)
{
  int a = 10;
  //取变量a的地址
  int* pa = &a;
  //取指针变量pa的地址
  int** ppa = &pa;
  printf("%p\n",&pa);
  printf("%p\n",ppa);
  return 0;
}

指针变量也存在地址,可以使用二级指针将指针变量的位置存储起来.

int main(void)
{
  int a = 0;
  int* pa = &a;
  int** ppa = &pa;
  //*ppa==pa
  printf("%p\n",*ppa);
  printf("%p\n", pa);
  return 0;
}

解引用二级指针,可以得到一次指针的地址

int main(void)
{
  int a = 10;
  int* pa = &a;
  int** ppa = &pa;
  //*ppa==pa
  printf("%d\n", **ppa);
  printf("%d\n", *pa);
  return 0;
}

解引用俩次二级指针得到的是初始变量的值,解引用一次一次指针得到的是初始变量的值

int main(viod)
{
  int a = 10;
  int* pa = &a;
  *pa = 20;
  printf("%d\n",a);
  int** ppa = &pa;
  **ppa = 30;
  printf("%d\n", a);
  return 0;
}

可以利用解引用俩次二级指针改变初始变量的值.

字符指针

int main(void)
{
  char ch= 'a';
  char* pc = &ch;
  *pc = 'w';
  printf("%c",ch);
  return 0;
}

当在char类型中存放的是字符时,和普通指针的用法相同,将字符的地址存入指针变量中,然后解引用即可.

int main(void)
{
  char* pc = "abcdef";
  printf("%p",pc);
  return 0;
}

这里值得注意的时,在使用指针变量存储字符串地址时,只会将字符串的首个元素的地址保存

同时,这里在指向字符串时,可能会认为字符串首先是没有被存储在某个内存中的.

但是在C\C++中,会把常量字符串先保存在单独的内存区域,而当几个指针同时指向一个字符串时,实际都是指向同一个内存块.

这与数组储存字符串不同,数组储存字符串,会将字符串的每个元素由高到低依次排放,每次出现一个数组,尽管字符串相同,系统都会开辟出一份空间给数组,这样打印数组的地址每次都会时不同的.

可以看下面的例子:

int main(void)
{
  char str1[] = "abcdef";
  char str2[] = "abcdef";
  char* str3 = "abcdef";
  char* str4 = "abcdef";
  printf("%p\n", str1);
  printf("%p\n", str2);
  printf("%p\n", str3);
  printf("%p\n", str4);
  return 0;
}

指针数组

int main(void)
{
  int a = 1;
  int b = 2;
  int c = 3;
  int* arr[3] = { &a,&b,&c };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%p ", arr[i]);
  }
  return 0;
}

和整型数组,字符数组相同,指针数组也是数组,指针数组是用来存储指针变量的数组,每个指针变量指向一个地址.

int main(void)
{
  int a = 1;
  int b = 2;
  int c = 3;
  int* arr[3] = { &a,&b,&c };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%d ", *arr[i]);
  }
  return 0;
}

使用解引用操作符也可以找到初始元素.

int main(void)
{
  int arr1[5] = { 1,1,1,1,1 };
  int arr2[5] = { 4,4,4,4,4 };
  int arr3[5] = { 3,3,3,3,3 };
  int* parr[3] = { arr1,arr2,arr3 };
  
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d ", *(parr[i] + j));
    }
  }
  return 0;
}

一个数组的名称代表首元素的地址,也可以通过保存数组首元素地址的指针数组找到每个数组中的每个元素.

int main(void)
{
  int arr1[5] = { 1,1,1,1,1 };
  int arr2[5] = { 4,4,4,4,4 };
  int arr3[5] = { 3,3,3,3,3 };
  int* parr[3] = { arr1,arr2,arr3 };
  
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d ", parr[i][j]);
    }
  }
  return 0;
}

这里也可以将 *(parr[i] + j)转换为parr[i][j],这俩个是完全等价的.

这里可以给大家介绍一下[]操作符:

[]这个操作符,是个双目操作符,i和arr都是这个操作符的操作数,就如同a + b一样,在左边和右边是一样的.

int main(void)
{
  int arr[3] = {1,2,3};
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    printf("%d ",i[arr]);
  }
  return 0;
}

数组指针

int main(void)
{
  int arr[3] = { 1,2,3 };
  int(*p)[3] = &arr;
  return 0;
}

数组指针式存放数组地址的指针,也是指向数组的指针

数组名----代表数组首元素的地址

&数组名----代表数组的地址

它们俩个在打印地址时,得出的数据相同,但是俩者的意义不同.数组名只是代表一个元素,数组名+1,只会跳过一个元素,而&数组名不同,它代表的是一个数组,&数组名+1会跳过一个数组.

int main(void)
{
  //指针数组
  int* p1[10];
  //数组指针
  int(*p2)[10];
  return 0;
}

我们要区分指针数组和数组指针.

  • []的优先级高于 * 的优先级,在使用数组指针时一定要使用()

int* p1[10];

  • 表示一个指针数组,数组10个元素,每个元素都是int*类型的

int(*p2)[10];

  • 表示一个数组指针,该指针指向一个数组,数组是个元素,每个元素int类型

在这里我们区分指针数组和数组指针时,我们应该知道,指针数组[10]里有10个指针,它会指向10个地址,而指针数组[10]仅仅只指向一个地址,这个地址是个数组,而且有10个元素

数组传参

一维数组传参

//传数组
void fun(int arr[]);
//传数组
void fun(int arr[10]);
//传地址
void fun(int arr);
int main(void)
{
  int arr[10] = { 0 };
  fun(arr);
  return 0;
}

指针数组传参

//传数组,数组中的每个元素都是int *类型
void fun2(int *arr2[20]);
//传这个指针数组的地址
void fun2(int** arr2);
int main(void)
{
  int* arr2[10] = { 0 };
  fun2(arr2);
  return 0;
}

二维数组传参

//传数组
void fun3(int arr[3][5]);
//可以不传行数,但是不能不传列数
void fun3(int arr[][5]);
//数组指针,传的是一个指针,接收到是第一行的指针
void fun3(int(*arr)[5]);
int main(void)
{
  int arr3[3][5] = { 0 };
  fun3(arr3);
  return 0;
}

指针传参

一级指针传参

将函数的参数部分变为一级指针

void fun(int* p);
int main(void)
{
  //取地址传参
  int a = 10;
  fun(&a);
  //一级指针传参
  int* p = &a;
  fun(p);
  //数组传参
  int arr[10] = { 0 };
  fun(arr);
  return 0;
}

二级指针传参

将函数的参数部分变为二级指针

void fun(int** p);
int main(void)
{
  int b = 10;
  //一级指针取地址传参
  int* p = &b;
  fun(&p);
  //二级指针直接传参
  int** pp = &p;
  fun(pp);
  //指针数组传参
  int* arr[10] = { 0 };
  fun(arr);
  return 0;
}

函数指针

int add(int x ,int y)
{
  return x + y;
}
int main(void)
{
  int a = 5;
  int b = 3;
  //创造一个函数指针,指针是指向add
  int (*pf)(int, int) = &add;
  //使用指针接收add函数的返回值
  int ret = (*pf)(a, b);
  printf("%d ",ret);
  return 0;
}

需要创建一个函数指针,首先需要有一个函数,然后&函数,然后将这个地址给一个指针即可.

这里需要提一下,在使用指针的时候,最重要的就是找到地址,找到地址的类型,使用一个指针变量即可.

例如这个函数指针:

add函数的的类型参数是(int,int),返回参数也是int,然后取地址,使用pf这个指针变量存放函数地址保存即可,

int add(int x ,int y)
{
  return x + y;
}
int main(void)
{
  int a = 5;
  int b = 3;
  //创造一个函数指针,指针是指向add
  int (*pf)(int, int) = add;
  //使用指针接收add函数的返回值
  int ret = (*pf)(a, b);
  printf("%d ",ret);
  return 0;
}

  • &函数名和函数名都是函数的地址
int add(int x ,int y)
{
  return x + y;
}
int main(void)
{
  int a = 5;
  int b = 3;
  //创造一个函数指针,指针是指向add
  int (*pf)(int, int) = add;
  //使用指针接收add函数的返回值
  int ret = pf(a, b);
  printf("%d ",ret);
  return 0;
}

  • 指针pf的解引用操作符 * 也可以省略

函数指针数组

int div(int x, int y)
{
  return x * y;
}
int mul(int x, int y)
{
  return x * y;
}
int sub(int x, int y)
{
  return x - y;
}
int add(int x, int y)
{
  return x + y;
}
int main(void)
{
  int a = 5;
  int b = 3;
  //创造一个函数指针数组,指针是指向四个函数
  int (*pf[4])(int, int) = { add, sub, mul,div };
  return 0;
}

同样的道理,创建函数指针数组,因为是数组,则需要多个指针,指针指针需要指向多个函数(这里的函数的类型都是相同的),根据函数类型即可写出这个函数指针数组

指向函数指针数组的指针

函数的参数类型和返回类型与上面相同,那么该如何写出这个"指向函数指针数组的指针"

1.首先这个一个指针,而不是多个指针

  • 这里肯定指针需要和解引用操作符 * 用()括起来

2.这个指针指向的是一个数组

  • 需要[ ]包含一个数组,[ ]里面是元素的个数

3.同时这是一个指向函数指针

  • 我们需要先写出函数指针,这里包括函数的参数类型和返回类型,以及这是指针
  • 假如是之前那个例子:
  • int (* )(int,int)

将三者结合起来:

  • int (* (*pp)[4])(int,int) = &p;
int(*(8pp)[4](int,int))= &p;

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

其实回调函数就是在调用一个函数时,这个函数的参数中包含一个指向函数的一个指针,在合理的情况下,函数会通过指针找到另外一个函数,进行使用

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
  return (*(int*)p1 - *(int*)p2);
}
int main()
{
  int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
  int i = 0;
  //快速排序
  qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
  //这里使用一个函数指针找到int_cmp函数
  return 0;
}

!!!好累啊!!!

写了快一万五的字数,劳烦各位大哥给个关注

我要去吃饭了…


相关文章
|
1月前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
18 0
|
2月前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
3月前
|
存储 C语言
一篇文章带你深入了解“指针”(上)
一篇文章带你深入了解“指针”(上)
|
存储 人工智能 算法
【C++算法图解专栏】一篇文章带你掌握尺取法(双指针)
【C++算法图解专栏】一篇文章带你掌握尺取法(双指针)
220 0
|
编译器 C++
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
156 0
|
5月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
25 0
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
3月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)