当前位置:   article > 正文

【c语言】 指针详解 【图文+代码】_c语言指针

c语言指针

前言

提示:编程的本质其实就是操控数据,而数据存放在内存中。因此,如果能更好地理解内存的模型,以及 C 如何管理内存,那么就引入了一个新的概念 “指针” ,大家都在书中看到过「指针存储的是变量的内存地址」这句话,但是什么是指针呢,下面我们开始直面了解指针


提示:以下是本篇文章正文内容,下面案例可供参考

一、指针是什么?

1.1 认识指针

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
在这里插入图片描述指针是个变量,存放内存单元的地址(编号)。说的形象一点就是指针就相当于一个门牌号 里面存的的是住户的编号

1.2 变量

   上面说指针是个变量 ,那么变量是什么。
  • 1

有了内存,接下来我们需要考虑,int、double 这些变量是如何存储在 0、1 单元格的。
在 C 语言中我们会这样定义变量:

int a = 999;
char c = 'c';
  • 1
  • 2

当你写下一个变量定义的时候,实际上是向内存申请了一块空间来存放你的变量。我们都知道 int 类型占 4 个字节,并且在计算机中数字都是用补码表示的。
999换算成补码就是:0000 0011 1110 0111
这里有 4 个byte,所以需要四个单元格来存储:
在这里插入图片描述
当然,这样就引出了大端和小端。
像上面这种将高位字节放在内存低地址的方式叫做大端
反之,将低位字节放在内存低地址的方式就叫做小端:
在这里插入图片描述
上面只说明了 int 型的变量如何存储在内存,而 float、char 等类型实际上也是一样的,都需要先转换为补码。

对于多字节的变量类型,还需要按照大端或者小端的格式,依次将字节写入到内存单元。

1.3 变量放在哪?

上面我说,定义一个变量实际就是向计算机申请了一块内存来存放。
那如果我们要想知道变量到底放在哪了呢?

可以通过运算符&来取得变量实际的地址,这个值就是变量所占内存块的起始地址。

printf("%x", &b);
  • 1

打印出来大概会是像这样的一串数字:0x7ffcad3b8f3c。

1.4 指针的本质

上面说,我们可以通过&符号获取变量的内存地址,那获取之后如何来表示这是一个地址,而不是一个普通的值呢?

也就是在 C 语言中如何表示地址这个概念呢?

对,就是指针,你可以这样:

#include <stdio.h>
int main()
{
 int a = 10;//在内存中开辟一块空间
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
   //将a的地址存放在p变量中,p就是一个之指针变量。
 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

大家都看到了 用int类型定义了一个指针p。
所以说到底,指针他的本质也就是一个变量,只不过是用来存放地址的变量。(存放在指针中的值都被当成地址处理)

就相当于刻在门牌上的号码,只能被当作门牌号处理了。

1.5 解引用

上面的问题,就是为了引出指针解引用的。

p中存储的是a变量的内存地址,那如何通过地址去获取a的值呢?

这个操作就叫做解引用,在 C 语言中通过运算符 *就可以拿到一个指针所指地址的内容了。
在这里插入图片描述

比如*p就能获得a的值。
形象的说
就相当于p里面是住户的门牌号(地址) *p就是你通过这个门牌号地址找到了里面的住户(内容)。

二、指针类型

2.1指针有类型吗?

指针的类型 我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢? 准确的说:有的。

上面不是说过 指针是一个变量 变量有类型 那么指针肯定也有类型。

我们给指针变量相应的类型。

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里可以看到,指针的定义方式是: type + * 。

         char* 类型的指针是为了存放 char 类型变量的地址。
         short* 类型的指针是为了存放 short 类型变量的地址。
          int* 类型的指针是为了存放int 类型变量的地址。
  • 1
  • 2
  • 3

2.2指针类型的意义是什么?

我们先看一段代码
在这里插入图片描述
可以看出 &n pc pi所得到的地址相同
&n 本来就是取n 的地址 而pc pi 是指针 保存了n的地址 有人会疑问说不是一个是char类型 一个是int类型 怎么会一样呢?

在这里插入图片描述
那么pc+1 和 pi+1 为什么会不同 这就和指针的类型有关了 pc是char*

类型, +1 相当于加一个char类型字节大小 , 为一个字节, 而pi是int*类型 ,+1相当于加一个整形类型字节大小, 为4个字节。所以会有所不同 , 这也就是为什么指针会有不同的类型了。

指针的类型决定了指针向前或向后走一步有多大。


三. 野指针

3.1什么是野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.2野指针成因

3.2.1指针未初始化

#include <stdio.h>
int main()
{ 
 int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
 return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

没有初始化造成了他的随机 不确定性 成了野指针。

3.2.2 指针越界访问

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
   return 0;
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

指针越界了 指向了不属于自己的,也会造成它的随机 不确定性 从而成为野指针

3.2.3 指针指向的空间释放了

这个是大家最容易出错的也最容易忽略的一个问题。比如

int main()
{
vector<int> v={ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);  //迭代器失效了 因为将2删除后 就不能在对其进行迭代
++it;
}
return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

原因就是指针指向那个空间时 将其删除后,没有给指针重新赋值,导致指针指向的位置又变成了未知的 随机的 成为野指针 从而造成程序崩溃。这个后边动态内存开辟在详细说这种情况。

3.3如何避免野指针

1.指针初始化
2.小心指针越界
3.指针所指空间释放了即设置NULL
4.指针使用之前检查其有效性 (断言)

四、 指针和数组

4.1数组名是什么

#include <stdio.h>
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果如下
在这里插入图片描述
我们会发现结果相同 我们就可以得出结论

数组名表示的是数组首元素的地址。
这样的话我们就可以这样写代码

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

4.2数组和指针的关系

数组是 C 自带的基本数据结构,彻底理解数组及其用法是开发高效应用程序的基础。

int array[10] = {10, 9, 8, 7};
printf("%d\n", *array);  // 	输出 10
printf("%d\n", array[0]);  // 输出 10

printf("%d\n", array[1]);  // 输出 9
printf("%d\n", *(array+1)); // 输出 9

int *p = array;
printf("%d\n", *p);  // 	输出 10
printf("%d\n", p[0]);  // 输出 10

printf("%d\n", p[1]);  // 输出 9
printf("%d\n", *(p+1)); // 输出 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
    第 0 个元素的地址称为数组的首地址,数组名实际就是指向数组首地址,
 当我们通过array[1]或者*(array + 1)去访问数组元素的时候。
 实际上可以看做 address[offset],address为起始地址,offset为偏移量,
 但是注意这里的偏移量offset不是直接和 address相加,而是要乘以数组类型所占字节数。
 也就是: address + sizeof(int) * offset   就是和指针的类型大小有关了。
  • 1
  • 2
  • 3
  • 4
  • 5

特别要注意是:

尽管数组名字有时候可以当做指针来用,但数组的名字不是指针。  
  • 1

比如

printf("%u", sizeof(array));
printf("%u", sizeof(p));
  • 1
  • 2

结果如下
在这里插入图片描述
一个是数组的大小 一个是指针的大小

     第一个输出 40,因为 array包含有 10 个int类型的元素,
     而第二个在 32 位机器上输出 4,也就是指针的长度。
  • 1
  • 2

另外说一下 指针就像门牌 它是一个标准的 在32位机器上永远就是 4

4.3数组和指针的关系

不要认为二维数组在内存中就是按行、列这样二维存储的,实际上,它在存储上和一维数组没有本质区别,举个例子:

int array[3][3] = {{1, 23}, {4, 56}{7, 8, 9}};
array[1][2] = 6;
  • 1
  • 2

或许你以为在内存中 array数组会像一个二维矩阵:

1		2		3
4		5		6
7		8		9
  • 1
  • 2
  • 3

其实是这个样子哒

1	2	3	4	5	6	7	8	9
  • 1

和一维数组没有什么区别,都是一维线性排列。
去访问的时候,编译器会怎么去计算我们真正所访问元素的地址呢?
假设数组定义是这样的:

   int array[n][m]  定义几行几列
访问: array[a][b]  比如要找array[1][2]
那么被访问元素地址的计算方式就是: array + (m * a + b)
  • 1
  • 2
  • 3

这个就是二维数组在内存中的本质,其实和一维数组是一样的,只是语法将其包装成一个二维的样子。


五、 二级指针

在这里插入图片描述

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa

int b = 20;
*ppa = &b;//等价于 pa = &b;
  • 1
  • 2

**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作:*pa,那找到的就是a

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
  • 1
  • 2
  • 3

六、 指针数组

  指针数组是 “指针” 还是“数组”
  答案 当然是数组 
  • 1
  • 2

下面用图形的方式看一下 就会一目了然。

int arr1[5];
char arr2[5]
int *arr3[5]
  • 1
  • 2
  • 3

在这里插入图片描述

arr3是一个数组,有五个元素,每个元素是一个整形指针
  • 1
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号