写点什么

【C 语言】指针 Five 之 ["​⚔ 空指针 - NULL、💣 指针使用之前检查有效性、🗡 指针运算 💣 指针+- 整数、💣 指针 - 指针、指针关系运算、💣 标准规定、⚔ 指针和数组、⚔ 二级指针、⚔ 指针数组"]

作者:泽En
  • 2022 年 5 月 22 日
  • 本文字数:3392 字

    阅读完需:约 11 分钟

【C 语言】指针 Five 之 ["​⚔ 空指针 - NULL、💣 指针使用之前检查有效性、🗡 指针运算  💣 指针+- 整数、💣 指针 - 指针、指针关系运算、💣 标准规定、⚔ 指针和数组、⚔ 二级指针、⚔ 指针数组"]

⚔ 空指针 - NULL

NULL—— 空指针,用来初始化指针或者给指针赋值,可以转到定义看看。

*#define NULL    ((void )0) 本质上 NULL 其实就是 ( 0)

代码如下所示↓


int* p = NULL;
复制代码


当然,如果你不知道 p 应该初始化什么地址的时候,就直接初始化 NULL。

注意→如果当你这里 *p = 10 的时候依然会出现问题。


NULL ****是在计算中具有保留的值,用于指示指针不引用有效对象。程序通常使用空指针来表示条件,例如未知长度的列表结尾或未执行某些操作; 这种空指针的使用可以与可空类型和选项类型中的 Nothing 值进行比较。

空指针不应与未初始化的指针混淆:保证空指针与指向有效对象的任何指针进行比较。但是,根据语言和实现,未初始化的指针可能没有任何此类保证。它可能与其他有效指针相等; 或者它可能比较等于空指针。它可能在不同的时间做两件事。


#include<stdio.h>int main(void){  int a = 10;        int* pa = &a;
printf("one pa = %d\n", *pa);
*pa = 20; //此时当我们不想用它时候 pa = NULL; //就把 pa 指针置成 NULL
printf("two pa = %d\n",pa); return 0;}
复制代码


编译运行结果👇

  • one pa = 10

  • two pa = 0



💣 指针使用之前检查有效性

当你指针变量不可以用的时候就把它设置成 NULL,当你指针变量可以用的时候就不是 NULL

当我们对这个指针进行初始化的话,那么它就是有效的,如果没有初始化那么就是无效的。


  if (pa != NULL)  {    //进行使用  }  if (pa == NULL)  {    //不进行使用  }
复制代码


🗡 指针运算

💣 指针+- 整数

示例,代码如下所示👇


#define Macro 5  int values[Macro];  int* p;  for (p = &values[0]; p = &values[Macro];)  {    *p++ = 0;  }    return 0;
复制代码


这里的代码会使得 values 的数组下标结果全部初始化为 0。

在这里我们就运用到了 指针 + 整数 : *p++ = 0;

注意: p = &values[Macro];这个实际上就是指针的关系运算。指针比较了大小。随着数组的下标的变化,地址是由低到高进行变化的。

💣 指针 - 指针

如下代码所示↓


#include<stdio.h>int main(void){  int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };  printf("%d\n", *(&arr[9]) - *(&arr[0]));  return 0;}
复制代码


运行编译结果:9

从上面的编译结果我们可以知道→指针 - 指针 得到的两个指针之间的元素个数。

注意:指针和指针相减的前提是→两个指针类型必须指向同一块空间。



💣 指针关系运算

for 循环遍历数组元素全部初始化为 0

第①种代码


#define Macro 5int value[Macro];int* p;for(p = &value[macro];p > &value[0];){    *--p = 0;}
复制代码


第②种代码


#define Macro 5int value[Macro];int* p;for(p = &value[macro - 1];p > &value[0];p--){    *p = 0;}
复制代码


实际上绝大多数的代码编译器上是可以顺利完成的。

然而写代码的时候我们还是应该避免第二种写法,因为标准并不保证它是可行的。

💣 标准规定

允许指向数组元素的指针与指向数组最后一个元素的那个内存位置的指针进行比较,但是不允许指向第一个元素之前的那个内存位置的指针去进行比较,第二种就会导致数组越界。



⚔ 指针和数组

数组名是首元素的地址,如下代码所示↓


#include<stdio.h>int main(void){  int arr[10] = { 0 };    //这两种情况是等价得  printf("%p\n", &arr);  printf("%p\n", &arr[0]);  return 0;}
复制代码


代码运行结果所示↓

  • 004FFAD8 - 数组名

  • 004FFAD8 - 数组名首元素地址


正是因为数组名是首元素的地址,所以我们可以这样去理解。如下代码所示↓


#include<stdio.h>int main(void){  int arr[10] = { 0 };  int* p = arr;  int i = 0;  for (i = 0; i < 10; i++)  {    printf("%p--《======》--%p\n", &arr[i], p + i);  }  return 0;}
复制代码


上面的代码:p 是首元素的地址,首元素的地址 p + i,产生的就是下标为 arr[i] 的这个元素的地址。它们两个是一模一样的。

运行结果🖊

0x7ffe5a4ed550--《======》--0x7ffe5a4ed550

0x7ffe5a4ed554--《======》--0x7ffe5a4ed554

0x7ffe5a4ed558--《======》--0x7ffe5a4ed558

0x7ffe5a4ed55c--《======》--0x7ffe5a4ed55c

0x7ffe5a4ed560--《======》--0x7ffe5a4ed560

0x7ffe5a4ed564--《======》--0x7ffe5a4ed564

0x7ffe5a4ed568--《======》--0x7ffe5a4ed568

0x7ffe5a4ed56c--《======》--0x7ffe5a4ed56c

0x7ffe5a4ed570--《======》--0x7ffe5a4ed570

0x7ffe5a4ed574--《======》--0x7ffe5a4ed574


如果这个时候要用指针遍历数组首元素的下标的话那也是可以的,此时就可以通过解引用来找到下标 i 的元素,从而 i 放进去,再把每个元素所打印出来。


#include<stdio.h>int main(void){  int arr[10] = { 0 };  int* p = arr;  int i = 0;  for (i = 0; i < 10; i++)  {    *(p + i) = i;    printf("%d ", *(p + i));  }  return 0;}
复制代码


运行结果🖊

0 1 2 3 4 5 6 7 8 9



⚔ 二级指针

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。


int a = 10;    //(1)int* pa = &a;  //(2)int**ppa = &pa;//(3)
复制代码


在第②行当中:pa 是指针变量,一级指针。它就只是仅有一层指向关系。pa 指针对象是整形,因为它存储的是地址 &a。

在第③行当中:由于 pa 也是个变量,&pa 取出 pa 在内存当中的起始地址。ppa 首先是指针的话我需要写上 ppa,ppa 指向的对象是 pa,pa 整体的类型叫做是 int pa。所以,在这个 * 的前面,我还是需要写上 int*。ppa 是一个指针,而 int 是我所指向的指针变量。于是就是 int** ppa,这种的话也被称之为是:二级指针。

说的明白一点就是:ppa 有两层指向关系,1:pa,2:a 那么从这里我们不难发现 三级指针、四级指针、以及 N 级指针 都是这样的关系。不过二级指针之后就很少用到了,所以大家只需要了解二级指针的概念即可。

关系图如下所示↓




⚔ 指针数组

一个数组的元素值为指针则是指针数组,指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。

那么指针数组是怎么样的呢,如下代码所示↓


int* arr[5];//是什么?
复制代码


arr 是一个数组,有⑤个元素,每个元素是一个整形指针



注意:除了每个元素的不同,而指针数组和普通数组在其它方面其实都是一样的!


#include <stdio.h>int main(void){  int a = 12, b = 17, c = 55;
int *arr[3] = { &a, &b, &c };
int **parr = arr; printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]); printf("%d, %d, %d\n", **(parr), **(parr+1), **(parr + 2));
return 0;}
复制代码


运行结果🖊

12 17 55

12 17 55


arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。

parr 是指向数组 arr 的指针,确切地说是指向 arr0 个元素的指针(首元素的地址),它的定义形式应该理解为**int *(*parr)** ,括号中的 * 表示 parr 是一个指针,括号外面的**int *** 表示 parr 指向的数据的类型。arr0 个元素的类型为 **int *** ,所以在定义 parr 时要加两个 ***** 。

第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 *(解引用) 才能取得它指向的数据,也即 *arr[i] 的形式。

第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据。

指针还可以与字符串相互结合进行使用,如下代码所示↓


#include <stdio.h>int main(void){  char *arr[3] = { "C", "CSDN", "Electricity" };  printf("%s <==> %s <==> %s\n", arr[0], arr[1], arr[2]);    return 0;}
复制代码


点击并拖拽以移动

​编辑 需要注意的是,字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。

也只有当指针数组中每个元素的类型都是**char *** 时,才能像上面那样给指针数组赋值,其他类型不行。


改成下面的形式,与上面的数组是等价的,如下代码所示↓


#include <stdio.h>int main(void){  char *str0 = "C";  char *str1 = "CSDN";  char *str2 = "Electricity";  char **str[3] = { str0, str1, str2 };  printf("%s <==> %s <==> %s", str[0], str[1], str[2]);
return 0;}
复制代码


运行结果🖊

C <==> CSDN <==> Electricity





后续还会有高阶指针内容!喜欢还请多多支持吧 🌹

发布于: 刚刚阅读数: 2
用户头像

泽En

关注

一起happy! 2022.01.29 加入

谁也不知道旅途的终点是怎么样的,现在只不过是开始。便全力以赴!终点必将是星辰大海。 2021年度博客之星物联网与嵌入式开发TOP5 2021博客之星Top100 阿里云专家博主^星级博主 CSDN⇿掘金⇿InfoQ[创作者]

评论

发布
暂无评论
【C 语言】指针 Five 之 ["​⚔ 空指针 - NULL、💣 指针使用之前检查有效性、🗡 指针运算  💣 指针+- 整数、💣 指针 - 指针、指针关系运算、💣 标准规定、⚔ 指针和数组、⚔ 二级指针、⚔ 指针数组"]_5月月更_泽En_InfoQ写作社区