指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向 points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以 说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元.

那我们可以这样理解:

内存:

C语言初阶6 -指针_数组名

指针 :指针是个变量,存放内存单元的地址(编号)

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

总结 ∶ 指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

思考以下的问题∶

(1)一个小的单元到底是多大 ? (1个字节)

(2)如何编址 ?

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电 / 负电(1或者0)

那么32根地址线产生的地址就会是∶

00000000 00000000 00000000 00000000

0000000 00000000 00000000 00000001

…….

11111111 1111111111111111 11111111

这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给(2 ^ 32Byte == 2 ^ 32 / 1024KB == 2 ^

32 / 1024 / 1024MB == 2 ^ 32 / 1024 / 1024 / 1024GB == 4GB)4G的空闲进行编址。

同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。

这里我们就明白 :

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
  • 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结 :

指针是用来存放地址的,地址是唯一标示一块地址空间的。

指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针和指针类型

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(double*));
    return 0;
}

运行后发现不管什么类型的指针,大小都是4,这时候我们心里可能产生疑问,为什么大小都是4,还要区分不同类型的指针,

那这种区分是不是没有意义的?不同类型的指针是不是可以互用?

为了验证这些问题,请看以下代码及运行结果。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int a=0x11223344;
    int* pa=&a;
    char* pc=&a;    
    printf("%p\n",pa);
    printf("%p\n",pc);    //打印运行发现两地址一样
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int a=0x11223344;
    int* pa=&a;
    *pa=0;   //(运行状态下)在编译器(vscode2022)调试-窗口-内存中查看地址
    return 0;
}

C语言初阶6 -指针_数组_02

C语言初阶6 -指针_数组名_03

可以看到a的内容确实改变(全变成了0)了

现在我们将char * pc来重复上面的操作,看能否实现相应变更指针指向地址内容的功能。

C语言初阶6 -指针_笔记_04

我们发现这里仅前两个变成了00,后面几位没有变化。

通过这个我们可以发现指针的类型还是意义的,其意义在于解引用操作时,对字节的操作数量不同

比如int * 类型的指针,可以改动4个字节的内容,而char * 类型的指针只能更改1个字节的内容

  1. 指针的意义1:指针的解引用(用于访问)

总结:指针类型决定了指针进行解引用操作的时候,能够访问空间的大小

  • int* p : p能够访问4个字节
  • char* p : p能够访问1个字节
  • double* p : p能够访问8个字节

   b. 指针的意义2:指针 + - 整数

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int a=0x11223344;
    int* pa=&a;
    char* pc=&a;
    printf("%p\n",pa);      // 00E6F89C
    printf("%p\n",pa+1);    // 00E6F8A0 = 00E6F89C + 4
    printf("%p\n",pc);      // 00E6F89C
    printf("%p\n",pc+1);    // 00E6F89D = 00E6F89C + 1
    return 0;
}

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

  • int* p; p + 1 – > 往后跳4字节
  • char* p; p + 1 – >往后跳1字节
  • double* p; p + 1 – > 往后跳8字节

c. 指针两个意义的价值

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    //char* p = arr;    //数据混乱
    int* p = arr;    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        *(p + i) = i + 1;
    }
    return 0;
}

C语言初阶6 -指针_数组名_05

[1],[2]中的数并非我们想要

野指针

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

野指针成因

指针未初始化

#include<stdio.h>  
int main()    
{    
    int a;//局部变量不初始化,默认是随机值    
    int* p;//局部的指针变量不初始化,默认也会给随机值
    *p = 20;//这时候对指针进行解引用操作,实际上并不知道更改的内存是在哪里
    return 0;//十分危险
    }

指针越界访问

#include<stdio.h> 
int main()    
{    
    int arr[10] = { 0 };    
    int* p = arr;
    int i = 0;
    for (i = 0; i < 12; i++)
    {
    //当指针指向的范围超出数组arr的范围时,就越界访问了,此时p就是野指针
    *(p++)=1;
    } 
    return 0;
    }

指针指向的空间释放

#include<stdio.h>    
int* test()    
{    
    int a = 10;    
    return &a;
}
//1.a 创建的空间在函数结束的时候返回给系统了,不属于当前程序的内容

int main()
{
    int* p = test();
    //2.这时候通过*p 去访问一个返还系统的空间,该空间有可能已经存放其它内容了
    //3.所以p是一个野指针
    *p = 20;
    return 0;
}

那么我们如何能够避免野指针呢?

1.指针初始化

2.小心指针越界

3.指针指向空间释放,即置为(空指针) NULL

int* pa = NULL; //空指针

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

if (pa != NULL){    ...}

指针运算

  • 指针 + - 整数
  • 指针 - 指针
  • 指针的关系运算

指针 + - 整数

#include<stdio.h>
int main()
{    
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10};    
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr;
    for (i = 0; i < sz; i++)
    {
        printf("%d  ", *p);
        //p = p + 1; //指针 + 整数 
        p++;
    }
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define a 5
int main()
{
    float values[a];
    float* vp;
    for (vp = &values; vp < &values[a];)
    {
        *vp++=0;
    }
    
    return 0;
}

指针 - 指针

没有指针 + 指针,因为没有意义,就像日期 - 日期 计算天数,而 日期 + 日期 毫无意义

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define a 5
int main()
{
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    printf("%d\n",&arr[9]-&arr[0]);        //结果为9
    
    char ch[5]={0};
    printf("%d\n",&arr[9]-&ch[0]);            //结果不确定,指针...要使他们都指向一个内存空间
    return 0;
}

#include<stdio.h>
int my_strlen(char* str)
{
        char* start = str;
        while (*str)
            str++;
        return str - start;
}
 
int main()
{
        char arr[] = "abc";
        int len = my_strlen(arr);
        printf("%d\n", len);
     
        return 0;
}

指针的关系运算

关系运算,简单来说就是比较大小,有 > 、 >= 、 == 、 != 、 < 、 <=

#define N_VALUES 5
float values[N_VALUES];
float* vp;
int main()
{
    for (vp = &values[N_VALUES]; vp > &values[0];)
    {
        *--vp = 0;//先--vp后解引用 
    }
    return 0;
}

代码简化, 这将代码修改如下:

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{   
  *vp = 0;
}

实际在绝大部分的编译器上第二种是可以顺利完成任务的,然而我们还是应该避免这样第二种写,因为标准并不保证它可行。

标准规定∶

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

C语言初阶6 -指针_数组名_06

数组和指针

数组名是什么?

在之前的学习中,我们可能已经知道了数组名就是首元素的地址,这里我们验证一下这个说法是否正确,

请看下面这个例子:

C语言初阶6 -指针_数组名_07

但是,数组名就一直是首元素地址吗?

数组名在绝大多数情况下是首元素地址,但有两个例外:

1、& arr — &数组名 - 数组名不是首元素的地址,数组名表示整个数组 &数组名 , 取出的是整个数组的地址。

2、sizeof(arr)----sizeof(数组名)-- - 数组名表示的整个数组–sizeof(数组名)计算的是整个数组的大小。

C语言初阶6 -指针_数组名_08C语言初阶6 -指针_数组名_09

上面我们说了 数组名就是首元素的地址,那么我们通过使用指针来访问数组就成为了可能:

#include<stdio.h>
int main()
{    
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };    
    int* p = arr;    
    int i = 0;    
    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;
}

二级指针

指针变量也是变量,是变量就有存储空间,就有地址,那么指针变量的地址存放在哪里那?

这就是二级指针,指向内容是存放地址的指针。

通常我们所说的指向数组的指针是一级指针。

#include<stdio.h>
int main()
{    
  int a = 10;    
  int* pa = &a;//pa 一级指针    
  int** ppa = &pa;//ppa 二级指针 ......后面还有 三级指针、四级指针....n级指针    
  return 0;
}

这里我们进行画图演示:

C语言初阶6 -指针_笔记_10

好了,现在我们对二级指针应该理解清楚了,但是二级指针怎么应用呢?

如果我们对 ppa 进行解引用操作 * ppa就 能找到 pa 也就是a的地址,再次进行解引用操作** ppa,就能找到a了

#include<stdio.h>
int main()
{    
  int a = 10;    
  int* pa = &a;//pa 一级指针    
  int** ppa = &pa;//ppa 二级指针 ......后面还有 三级指针、四级指针....n级指针         
  **ppa=20;        //对a的操作    
  return 0;
}

指针数组

在我们学习指针和数组的概念时,会接触到两个这样的概念

  • 指针数组-- - 数组-- - 存放指针的数组
  • 数组指针-- - 指针
#include<stdio.h>
int main()
{    
    //整形数组,存放整形的数组    
    int arr1[10];    
    //字符数组,存放字符的数组    
    char arr2[10];    
    
//指针数组,存放指针的数组    
    //存放整形指针的数组
    int* arr3[5];
    //存放字符指针的数组
    char* arr4[10];
    
//例:
    int a = 10, b = 20, c = 30, d = 40, e = 50;
    int* arr[5] = {&a,&b,&c,&d,&e};
    for (int i = 0;i < 5;i++)
    {
        printf("%d ", *(arr[i]));
    }
    return 0;
}