指针-从入门到放弃系列(基础篇)

C语言指针

1. 基础概念

1.1 指针的意义

通过指针可以间接的访问内存,假设你在一个大型的程序中,编写了很多函数,这些函数都需要同时访问,修改一块内存空间上的数据,而指针可以让这些函数共享同一块内存地址,从而指针像一个真正意义上的 “针” 一样,将我们的程序连接起来,实现对数据高效的共享。

题外话:比如有玩英雄联盟/王者荣耀的,你可以把整个峡谷当作一个内存空间,而你的屏幕就可以当作一个指针,这个指针指向内存空间的一小块,就是你的视角对应整个峡谷的视角,你在拉视野就等于你在修改指针的指向。打游戏要多看小地图,多拉视野,多支援队友啊喂。没有指针的话你就拉不动视野了,想想指针有必要不。

1.2 指针是一种数据类型

指针类型 和 int类型double类型一样,也是一种数据类型,但是指针是一种依附于其他数据类型一种数据类型。

内存地址都是有编号的,从0开始记录(题外话:在大多数操作系统上面,程序不允许访问地址为 0 ~ 255的内存,因为该内存是为了操作系统所保留的)一般的内存地址用16进制的编号表示,比如0x00000010;

而通过指针类型创建的指针变量,就是用来存放这个地址编号。
在这里插入图片描述

1.3 指针中的两个符号

在使用指针的时候我们会频繁的进行以下几个操作:

  • 定义一个指针变量,把普通变量的地址赋值给指针变量
  • 访问指针变量存放的内存地址上存放的变量

通过下面两个运算符我们可以进行这些操作

1.3.1 取地址符(&)
int a = 10;//创建一个普通的整型变量
int* p;//创建整型的指针变量 这里的 * 号表示 p 是一个指针变量
p = &a;//将普通的整型变量的在内存上的地址赋值给指针变量

在这里插入图片描述

1.3.2 解引用运算符(*)
*p = 20;//这里的 * 号是解引用操作,主要用来操作 p 存放的内存地址上存放的数

在这里插入图片描述

1.3.3 指针的大小
#include<stdio.h>
int main()
{
    print("sizeof char* = ",sizeof(char*));
    print("sizeof short* = ",sizeof(short*));
    print("sizeof int* = ",sizeof(int*));
    print("sizeof double* = ",sizeof(double*));//32位操作系统下:都打印 4 
    return 0;
}

在32位操作系统下,指针的大小都是四个字节,因为指针本身就是一种用来存放地址编号的数据类型,存放地址编号四个字节就行,而与指针指向的数据类型是什么没有关系。

2. 空指针和野指针

2.1 空指针

  • 空指针就是定义一个指针变量,指向地址编号为 0 的内存空间
int *p;
p = NULL;//p = nullptr;
  • 不可以访问空指针指向的内容
  • 空指针的主要用途是用于定义指针变量时的初始化

2.2 野指针

  • 指向非法的内存空间的指针
  • 未初始化的指针
int *p;
p = 0xffff;//0xffff这块地址空间从未申请
printf("%d\n",*p);

没有申请0xffff这块内存,但是我们直接定义一个指针变量指向这块内存,输出这块内存的内容,这是不合法的,指针p就叫野指针,同样,未初始化的指针指向那块内存空间是未知的,也是违法的,程序运行起来会崩溃;

3. void 和 万能指针(void*)

3.1 void的用途

  • 修饰函数的返回值类型
  • 修饰函数的形参

!!!void 不能创建变量

3.2 void* 的用途(万能指针)

#include<stdio.h>
int main()
{
    void* p1;
    void* p2;
    int a = 10;
    double b = 20;
    p1 = &a;
    p2 = &b;
    printf("%d",*(int*)p1);//void* 不可以直接解引用,需要强制转换之后才能使用
    printf("%lf",*(double*)p2);
    return 0;
}

例:

int* p1 = NULL;
char* p2 = NULL;
p1 = p2;//到这里会出现错误,需要把p2 强制转换成int* 
void* p3;
p3 = p1;//这里不会出错,不需要强制转换

void*可以保留任意数据类型的指针的地址编号,并且在使用的时候可以任意转换成我们想转换成的数据类型

4.const 修饰的指针

4.1 没有const的情况

int a = 10;
int *p;
p = &a;// p 指向 a
*p = 20; //a 可以改变
int b = 30;
p = &b;// p 指向 b

没有const限制的情况下,指针的指向可以改变,同时, 指针指向的数据也也可以改变;
在这里插入图片描述

总结:这是一个海王遇到了一个海女,两个都不专心的情况,最后海王又找了一个海女

4.2 const修饰 * 的情况

int a = 10;
int const *p = &a;//也可写为const int *p = &a;
*p = 20;//报错
int b = 20;
p = &b;//正确

const修饰 *p 的时候,*p是不可以更改的,但是p的指向是可以修改的

在这里插入图片描述

总结:这是一个海王祸害好女孩的情况

4.3 const修饰 p 的情况

int a = 10;
int * const p = &a;
*p = 20;//正确
int b = 20;
p = &b;//错误

const修饰 p 的时,p 指向的数据(*p)可以更改,但是 p 的指向不可以更改

在这里插入图片描述

总结:这是一个老实人爱上海女的情况

4.3 const修饰 * 和 p 的情况

int a = 10;
int const * const p = &a;
*p = 20;//错误
int b = 20;
p = &b;//错误

const修饰 * 和 p 的时候,指针指向的值和指针的指向都不可以改变

在这里插入图片描述

总结:这是真爱的情况

5. 不同类型指针的区别

先上图:(当初老师教的时候放过这张图[doge])

在这里插入图片描述

5.1 指针的算数运算不同

指针可以加上或者减去一个整数,但是这种指针的数值上面的加减和普通数值上的加减是不一样的。

可以设计一个程序测试:

#include<stdio.h>
int main()
{
    char* p1 = NULL;//char 类型为一个字节
    int* p2 = NULL;//int 为四个字节
    for (int i = 0; i < 5; i++)
    {
        printf("p1 = %d  ",p1);
        printf("p2 = %d\t\n",p2);
        p1++;
        p2++;
    }
    return 0;
}

最终的输出结果如下:

p1 = 0 p2 = 0
p1 = 1 p2 = 4
p1 = 2 p2 = 8
p1 = 3 p2 = 12
p1 = 4 p2 = 16

在程序中,我们给 p1p2 赋值,同时指向内存中的第0个字节,然后打印指针的值,同样是加一的操作,可以看出,指向char类型的指针 p1 每次加一,指针的值就加上了 sizeof(char),指向 int 类型的指针每次加一,指针的值就加上了 sizeof(int);通过上面的测试,其实你自己也可以设计一个程序,看看short型(2字节) 和 double型(8字节),看看是不是指针每次加一,对应的指针的值会加上指针指向的数据类型的字长;

同样的,减法操作和加法操作一样,指针的值也会减去指针指向的数据类型的字长;

5.2 指针的解引用不同

先看下面的代码:

int num = 0x01020304;//16进制的数字一个数字占 4bits,两个数字就是一个字节
int *p = &num;
short *p2 = &num;

有一个int型指针和一个short型指针,分别指向了num这个16进制的数字,以下为数字在内存中的存储方式(数据在内存中的大端小端存储方式有兴趣自行百度-

在这里插入图片描述

int型的指针在解引用的时候可以把内存中的首地址字节加上后面连着的三个字节给解出来,总共四个字节,而short型的指针只能将首地址字节加上后面一个字节解出来,总共两个字节。

所以设计程序如下:

#include<stdio.h>
int main()
{
    int num = 0x01020304;
    int* p = (int*)&num;
    short* p2 = (short*)&num;
    printf("*p = %#x\n", *p);
    printf("*p2 = %#x", *p2);
    return 0;
}

程序的输出为:

*p = 0x1020304
*p2 = 0x304

开头的0被省略了,实际上就是*p = 0x01020304,*p2 = 0x0304

抱着学习的态度,这个时候我们把p2 + 1,那么是不是可以把前面的0x0102解出来了呢?

说干就干

#include<stdio.h>
int main()
{
    int num = 0x01020304;
    int* p = (int*)&num;
    short* p2 = (short*)&num;
    printf("*p = %#x\n", *p);
    printf("*p2 = %#x\n", *p2);
    p2++;
    printf("*p2 = %#x", *p2);
    return 0;
}

程序输出结果为:

*p = 0x1020304
*p2 = 0x304
*p2 = 0x102

对应了指针加一,指针的值就会加上指针所值的数据类型的字节大小的情况。


「特别鸣谢」之也x指出文中错误,现已改正 2021/11/7

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值