前言

C语言有一个最重要的知识,是很多小白容易卡住并放弃的一个知识,并且是衡量一个C/C++程序员是否优秀的指标,那就是指针。
指针是C语言的核心,大家需要好好的学习。
本章内容是带领大家走进指针这个类型,希望大家学习后有很好的收获。

一、什么是指针

在学习C语言前大家都知道一个概念,就是低级语言和高级语言的区别,而C语言是属于高级语言,但又有低级语言的特点,那就是可以直接操作计算机底层,那C语言怎么可以操作底层的数据呢?就是靠C语言中的指针类型来进行操作。
而我们说的指针其实是属于C语言中的一个类型,和之前学习的类型一样,但只不过指针比较特殊,它是用来存放地址的一个类型。
比如前面学习的类型

int 变量;
float 变量;
double 变量;
char 变量;

int 就是存放整型的数据, floatdouble 存放浮点型的数据,char 存放的就是字符数据,每一个数据都能存放特定的内容。
指针类型也能存放一个具体的类型,那就是 地址
通过操作地址来实行对底层的操作。

二、一级指针的使用

在学习一个东西时,最重要的是知道如何使用和创建,在使用中学习,所以先对指针的创建进行一下了解。

1.一级指针的创建

在上面说过,指针是属于一种特殊的数据类型,所以创建的时候和数据类型的创建一样,特殊点就在于指针也是分类型的,有整型指针、浮点数指针、字符型指针等等==(还有一些高级指针操作,等后面高级指针再说)==
也就是说现在的数据类型都可以创建为指针类型。
比如说最常见的 int 型的数据,它的指针的创建就是

int*  变量名;

float 类型的指针

float* 变量名;

而上面的 int*float* 就是指针类型,分别表示整型指针和浮点数指针。
通过上面的创建可以推出以下的结论:

指针的创建:类型* 变量名;
C语言中的所有数据类型都有相应的指针类型
在创建指针类型时记得加上 *

2.指针的赋值

现在学会了创建指针类型,那如何对指针进行赋值就是这一小节需要学习的任务。
在前面对变量进行赋值时,使用的是下面的语句

类型 变量名 = 数值;

通过上面的语句可以对基础类型进行赋值,那对指针类型也采用上面的方法进行赋值可以吗?
可以想想,指针存储的是地址,该怎么给它地址,换句话来说,你怎么知道一个值的地址来给指针,显然没办法使用上面的方法进行赋值,因为上面给变量的都是具体类型的值,而给指针是需要地址,而不是 1234 这样的数。
所以指针的赋值方式是如下的

类型* 变量名 = &变量;

切记一定是变量名,而不是具体的值,如果使用具体的值会报错,下面演示一下。
用一个 int 型的指针来存放一个 int 型的变量

int a = 10;
int* pa = &a;

在电脑上跑一下程序

可以看到编译是没有任何问题的,如果使用具体的值会怎么样。
还是一样的,但我直接 &10 看看会发生什么


看到了吧, & 后必须是一个变量而不是一个数值,这样会报错的。

3.&是什么

可能会有很多人不知道 & 是什么东西,其实这个符号在使用输入语句时用到过,只不过没有细讲,这里说一下 & 到底是干嘛的。
& 其实是取址符,取它后面变量的地址的,比如上面2-1的代码
int* pa = &a;
的意思是将 a 变量的地址取出来然后赋值给指针变量 pa
大家记住即可。

4.一维指针的使用

4.1 变量 和 *变量

在前面数据类型 + * 就变成了指针类型,后面跟着的变量名是用来存储地址的。
所以直接使用 变量 得到的是地址,而 *变量 得到的是地址对应的值。

4.2 输出指针变量内容

先通过指针类型来获得地址处对应的值,代码如下

int a = 10;
int* pa = &a;
printf("%d\n", *pa);

从4.1知道,指针变量名对应的是地址,而*变量名得到的是对应的值,运行一下该代码看看是不是

发现真的输出了对应的值,那如果输出 pa 会变成什么结果呢


发现这个输出的是一个负数,我们看不懂,其实这个地方输出的是它存储的地址,只不过是将十六进制翻译成了10进制。
如果想看它里面存放的地址,可以将转义符 %d变成 %p ,这样就可以知道里面存放的地址了


4.3 改变存储内容

先看一个例子:交换内容
在没学指针和函数之前,代码是写成这样的

#include <stdio.h>
int main(){
    int a = 5, b = 6, temp;
    temp = a;
    a = b;
    b = temp;
    printf("%d %d\n", a, b);
    return 0;
}

在学了函数后写的代码为:

#include <stdio.h>
void swap(int a, int b){
    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("%d %d\n", a, b);
}
int main(){
    int a = 5, b = 6;
    swap(a, b);
    return 0;
}

但这个在函数运行结束后就被释放,不是真正的交换。
而现在在学指针,就可以使用指针的方式来写
指针的写法

#include <stdio.h>
void swap(int* a, int* b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
int main(){
    int a = 5, b = 6;
    swap(&a, &b);
    printf("%d %d\n", a, b);
    return 0;
}

使用指针的写法就可以完成数据的真正交换,执行一下程序


可以看到完成了交换,那为什么会这样呢?
用图的方式给大家解释一下,当把地址传入函数后,在函数 swap 中会有下面的形式

a下是存放着地址,b下也是存放地址,然后运行下一个代码 int temp = *a

在内存中创建了一个空间给 temp 用来存放指针a对应的值,也就是5
再进行下一步

将指针a地址对应的空间中的值改变成b地址对应的空间中的值

然后再对b指针对应的值进行一下修改
该函数只是对变量的地址进行操作,函数释放后只是把在函数中创建的指针a和b释放了,但地址还是存在的,所以在交换完后输出该地址中存放的元素就是交换后的元素。

三、野指针

从上面的介绍,大家是不是对指针有很大的喜爱,觉得指针的使用很好,但有没有思考过一个问题,为什么现在的编程语言没有指针这个知识了?
因为指针这个东西是把双刃剑,用得好就万事大吉,但用不好你根本就找不到你的程序到底错哪了。
导致这个原因的很大一个因素就是野指针。

1.野指针的危害

在前面学数据类型的时候,创建一个变量

int a;

如果没有进行赋值,它里面的值不可能永远是不可预知的是一个随机值,大家不信的话可以去调试一下。
那指针类型也是一样的,如果你只创建了指针变量,但没有给它赋值,它会产生一个随机值,指向内存中的未知空间。
而C语言可以直接操作底层,如果指向了一个重要的参数的空间,而你又不清楚直接就直接对其修改会造成程序或则是系统不可逆的错误。

2.野指针的产生

要避免野指针的产生最重要的是知道野指针是如何产生的。
野指针的产生最主要的三点

  • 初始化指针没赋值
  • 指针越界
  • 指针指向的空间释放
    指针没赋值在上面已经说过了,那剩下两条是什么呢
2.1 指针越界

在数组中最容易出现指针越界的情况的,看下面的代码

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

已经超出了数组的范围,然后指向了数组后的空间,这个空间是未知的,使用就造成了野指针

2.2 空间释放

在使用指针指向一个函数中的一个变量,使用完函数,函数就被释放,函数中的变量的空间也会释放,并且地址也会重新分配,那这个时候指向函数中变量的指针也就变成了野指针。

3.避免野指针

这里给大家四条建议

  • 指针初始化
  • 小心指针越界
  • 指针指向空间释放即用null占位
  • 指针使用之前检查有效性

四、指针和数组

学数组的时候知道,数组名就是首元素的地址,在查找数组中的元素时用的是索引 [] 位置,也可以用 + 的移动来找到元素。
很多人说数组就是指针,其实不对的,但你可以理解成指针,更多数组和指针的内容在后面。

五、指针的关系运算符

1.指针大小

每个类型都有每一个类型的大小,比如 char 是一个字节, int 为4个字节,
每个类型的指针也有大小的。

int* pa;//4个字节
float* pa;//4个字节
double* pa;//8个字节
char* pa;//1个字节

2.指针的移动

有了上面的知识我们可以对指针进行移动,移动的方法就是 +移动的长度-移动的长度
例如有一个 int 型的指针需要让它向前移动一位,就可以 +1 ,如果要让它后退一位就 -1
int 型进一位是移动4个字节
char 型进一位是移动1个字节

总结

  • 数据类型 * 变量 = &变量
  • *变量 是指针中存放的具体的值,而变量存放的是地址
  • 每个类型移动的字节是不一样的,要注意类型的长度
  • 避免野指针的产生