C语言指针(一)

时间:2024-06-02 07:38:12

大家好,我是小张同学!

当有人问起,什么是指针时,我会毫不犹豫地回答,指针变量存放的是地址!然后呢,好像也说不出什么了,今天就再来详细看一下指针吧。

目录

1. 指针变量

1.1 定义指针变量 

1.2 通过指针变量取得数据

2. 未初始化和非法的指针

3. NULL指针

4. void 指针

5. 指针的指针

首先要明白几点:

  • 每个字节都有自己唯一的地址,就像门牌号一样,根据地址可以准确地找到某个字节

  • 如果知道一个变量的存储地址,那么就可以根据这个地址得到这个变量的值。但是这很难,因为很难知道某个变量的具体地址,所以一般都是使用变量名字而不是地址来访问内存的位置

  • 变量名让我们用更方便的方式记住地址,实际上,变量名和地址之间的对应关系是由编译器来实现的,硬件仍然是通过地址访问内存位置

  • 除变量名之外,函数名、数组名、字符串名都是一样的,都为我们提供了方便,在编程的过程中不用直接面对二进制地址

1. 指针变量

C语言中可以使用一个变量来存放地址,这种变量称为指针变量,这里的地址可以是变量、数组、字符串、函数的地址,也可以是另一个指针变量的地址。

1.1 定义指针变量 

int a = 100;
int b = 200;
char c = 'A';
int *p1 = &a;        //定义指针变量并初始化
char *p2 = &c;
p1 = &b;             //修改指针变量的值

比如上面的代码,* 表示这是一个指针变量,int *p1 = &a 表示 p1是一个指向 int 类型数据的指针变量,并且在定义的同时用 a 的地址对它进行初始化,&是取地址符。

p1的类型就是 int*,以此类推,p2的类型就是 char*。

指针变量可以连续定义,如下:

int *a, *b, *c;     //a、b、c的类型都是 int*
int *a, b, c;       //a的类型是 int*,b、c的类型是int

1.2 通过指针变量取得数据

需要用到指针运算符 *,来取某个地址上的数据,比如:

int a = 100;
int *p1 = &a;
printf("%d, %d\n", a, *p1);

在上面这段代码中,a 和 *p1的结果是一样的,p1本身的值是一个地址,*p1表示获取该地址上的值,也就是变量a的值,这个过程也被称为解引用。

在不同的场景下有不同的作用:

  1.  在指针变量的定义中,表明这是一个指针变量,和普通变量区分开
  2. *在使用指针变量时,表示获取指针指向的数据

 还可以通过指针变量修改值,如下:

int a = 100;
int *p1 = &a;
*p1 = 200;        //此时 a 的值变为200

虽然通过 *p1 和 a 获取到的数据是一样的,但是它们的运行过程稍有不同

前面说过,变量名称在编译后,都会被替换为地址,假设变量 a 和变量 p1 的地址分别为 0x1000、0x2000,那么它们的指向关系如下:

 

使用*p1,首先通过地址0x2000获取变量p1本身的值,即变量a的地址,然后再通过这个值取得变量a的数据

使用a,可以直接通过地址0x1000获取它的数据

2. 未初始化和非法的指针

int *a;
...
*a = 12;

在这段代码里,声明了一个名叫a的指针变量,后面那条赋值语句把12存储在a所指向的内存位置。

但是,a究竟指向哪里呢,声明了变量a,但未对它进行初始化,所示我们没办法预测12这个值将被存储在什么地方,由此可以看出,指针变量和其他变量是一样的,如果变量是静态的,会初始化为NULL,如果变量是自动的,就不会初始化,无论是静态还是自动变量,声明一个指向整型的指针都不会创建用于存储整型值的内存空间

如果程序执行这个赋值操作,a的地址很可能是个非法地址,就会提示segmentation fault,提示程序正在访问一个并未分配给程序的内存位置。即便a的地址是合法的,那么位于这个地址上的值也会被修改,这种类型错误会非常难发现。

因此,对指针进行间接访问之前,必须确保已经初始化

3. NULL指针

NULL指针是一个特殊的指针变量。

对指针进行解引用可以获取它所指向的值,但NULL指针未指向任何东西,因此,对一个NULL指针进行解引用是非法的,在对指针进行解引用之前,必须确保它并非NULL指针

NULL指针未指向任何东西,这种说法是不准确的,但是可以先这么理解。

 所以,应该对所有指针变量都进行显式的初始化,如果已经知道指针将被初始化为什么地址,就把它初始化为该地址,否则就初始化为NULL。

NULL是在 stdio.h中定义的一个宏,具体内容为:

#define NULL ((void*)0)

(void*)0是把数值0强制转换为 void* 类型,这样看来,NULL指针指向了地址为0的内存

4. void 指针

void用在函数定义中可以表示函数没有返回值或者没有形参,用在这里表示指针指向的数据类型是未知的。

void* 表示一个实实在在的指针,它也指向了某个数据,只是这个数据的类型不确定,在后续的使用过程中一般要进行强制类型转换。

比如malloc函数的返回值就是 void*类型,在使用时要进行强制类型转换,如下:

char *str = (char*)malloc(sizeof(char)*30);

5. 指针的指针

前面提到,指针变量可以存放变量、数组、字符串、函数的地址,也可以是另一个指针变量的地址。如果一个指针指向的是另一个指针,就称它为二级指针,或者指向指针的指针。

int a = 100;
int *p1 = &a;
int **p2 = &p1;   //指针变量也是一种变量,可以使用&获取它的地址

在上面这段代码中,p2就是一个二级指针,当然还可以对p2取地址,得到三级指针,但是实际开发中经常用到的就一级指针和二级指针,几乎用不到高级指针。

 关于指针,一篇文章肯定是写不完的,今天就写到这儿吧,下回接着写指针。