【C语言期末不挂科——指针初阶篇】(下)

简介: 【C语言期末不挂科——指针初阶篇】(下)

【C语言期末不挂科——指针初阶篇】(上)https://developer.aliyun.com/article/1386228

指针运算

  我们已经理解指针的基本功能了,除了上面的基本功能,指针还有一个很重要的东西———指针运算。

  1)指针 + - 整数

  我们前面已经学过,指针加上整数就是跳过整数倍指针类型个字节,就像:

#include<stdio.h>
int main()
{
  int a = 0x44332211; 
  char *p = (char *)&a;
  printf("%x\n", *p);
  printf("%x\n", *(p + 1));
  printf("%x\n", *(p + 2));
  printf("%x", *(p + 3));
  return 0;
 }

  指针加上整数除了可以进行读取数据以外,还可以连续的存储数据,我们看下面代码:

#include<stdio.h>
#define N 5//数组元素个数
int main()
{
  int arr[N] = { 0 };
  int *parr = NULL;//指针用来保存数组首元素地址
  for(parr = &arr[0] ; parr < &arr[N] ; )//将数组首元素地址赋值、小于判断条件就一直执行
  {
    *parr++ = 1;//指针解引用对内存空间进行赋值,随后+1指向下一个位置
  }
  int i = 0;
  for(i = 0 ; i < N ; i++)//打印出来数组里的值看看是否改变
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

  我们用指针解引用访问对应的内存空间从而完成了赋值操作。这种是指针parr的位置一直在变化,如果不想要指针的位置,我们可以这样写:

#include<stdio.h>
#define N 5
int main()
{
  int arr[N];
  int *parr = &arr[0];
  int sz = sizeof(arr) / sizeof(arr[0]);//sizeof(数组名)求出整个数组的字节大小
//然后再除上一个元素的大小,就是数组元素的个数。sizeof(arr) == N
  int i = 0;
  for(i = 0 ; i < sz ; i++)
  {
    *(parr + i) = 1;
  }
  for(i = 0 ; i < N ; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

  这样就能控制指针的地址不变,而完成数组元素的赋值了。

  总结:

  指针加减整数的意义就是指针跳过了 指针类型大小*整数 个字节,进而访问对应的内存空间。


  2)指针-指针

  我们来看下面的代码:

#include<stdio.h>
int main()
{
  int arr[10] = { 0 };
  printf("%d\n", &arr[9] - &arr[0]);
  return 0;
}

  这里两个指针相减,你来思考一下,得出的结果是多少?相信聪明的你能很快的得出正确的答案。我们直接来看结果:

  答案是9,不知道你想对了没有,我们取了数组元素的第10个元素地址,与第一个元素地址作差,得出来的结果是9,正好就是两个数组元素的距离。

  但是如果是这两种情况:

#include<stdio.h>
int main()
{
  int arr[10];
  double a = 1;
  int *ptr1 = &arr[0];
  double *ptr2 = &a;
  printf("%d", ptr1 - ptr2);
}

  如果采用了不用类型的参数进行相减,就会报错,而且最好是像数组这种连续的内存空间使用指针相减,否则相减出来的值是几乎没有什么意义的。

  总结:

  1、指针-指针得到的就是指针和指针之间元素的个数。

  2、两个指针相减的前提是他们的类型必须相同。

  3、指针相减的到时元素的个数,所以在连续的内存空间下相减是比较有意义的,不推荐两个毫不相关的指针相减,因为几乎没什么意义。


  3)指针的关系运算

  我们的地址是有大小的,有高低地址之分,而指针的关系运算就是比较指针的大小。我们来看下面的代码:

#include<stdio.h>
#define N 5
int main()
{
  int arr[N] = { 1 };
  int *parr = NULL;
  for(parr = &arr[5] ; parr > &arr[0] ; )
  {
    *--parr = 0;
  }
  int i = 0;
  for(i = 0 ; i < N ; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

  数组一共有5个元素,将数组元素全部初始化为1,随后我们将数组的最后一个元素的地址放进指针变量parr里面,我们准备使数组中的元素从后往前进行赋值,将数组中的元素全部赋值为0。

  详细的工作原理如下:

  当然你的for循环里也可以这样写:

for(parr = &arr[5] ; parr >= &arr[0] ; parr--)//这两种方式都是相同的
{
  *parr = 0;
}

  得到的结果同样是:

  指针的比较还有一个要点:就是只能向后比较,但是不能向前比较,如下图:

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


数组和指针

  指针和数组是什么关系呢?我们前面也使用了数组名作为首元素地址,那么数组与指针究竟有着什么样的渊源呢?大型纪录片之《指针与数组的故事》持续为您播出…

  指针变量就是指针变量,不是数组指针变量的大小为4/8个字节,专门用来存放地址的。数组也就是数组,不是指针,数组有一块连续的内存空间,可以存放1个或多个类型相同的数据

  我们来看下面的代码:

#include<stdio.h>
int main()
{
  int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  int len = sizeof(arr)/sizeof(arr[0]);
  int *p = arr;//指向数组首元素地址
  int i = 0;
  for(i = 0 ; i < len ; i++)
  {
    printf("%p == %p\n",p + i, &arr[i]);
  }
  return 0;
}

  我们可以看到,数组与指针的地址全部都是对应的,一模一样,所以说,数组名就是首元素地址,那么以后你就可以不使用[]来访问数组的内容了,可以使用指针 + 偏移量 的方式来访问数组元素:

int a[1000];
int *p = arr;
for(int i = 0 ; i < sizeof(arr)/sizeof(arr[0]) ; i++)
{
  *(p + i) = 1;//i就为偏移量
}

  数组与指针的联系

  前面我们也用到了,数组名就是首元素地址,数组名 == 地址 == 指针 ,当我们知道数组首元素的地址的时候,又因为数组是连续存放的,所以通过指针就可以遍历访问数组,前面也演示过了,数组可以通过指针来访问

  数组名就是数组的首元素地址,但是在这两个情况下是例外的:

//sizeof(数组名)  sizeof数组名是直接得到整个数组的字节大小
//& 数组名   &数组名 如果进行+1操作是直接跳过一整个数组

  其余的情况数组名就是首元素地址。


二级指针

  指针的基本用法我们大概了解了,但是我们了解的是 “一级指针” 的用法,其实还存在着二级指针、三级指针…多级指针,因为二级指针用的最多,所以我们在这里主要阐述二级指针,其他指针的情况类比就行了。

  那么究竟什么是二级指针呢?我们先来看我们日常所说的一级指针:

int a = 0;
int *pa = &a;//这里的指针pa就是一级指针

  我们再来看看二级指针:

int a = 0;
int *pa = &a;//这里的指针pa就是一级指针
int **ppa = &pa;//这里为二级指针

  我们要理解一个东西,指针变量也是变量 啊,既然是变量,那么就一定有内存空间来存储指针变量,而二级指针就是取一级指针变量的地址 的指针。如下图:

  这里二级指针的两个*可以这么来理解:int **是种类型,而我们可以把int ** 看成int* * 前面的int*是指向变量的类型,也就是一级指针(一级指针的类型为int *),而你本身是二级指针,指针的类型必须是int *,加上一级指针的类型int* 就是int ** 。

  同样,三级整形指针的返回类型就为int ***,多级指针以此类推…我们来看下面代码:

int a = 0;
int *p = &a;//一级指针
int **pp = &p;//二级指针

  现在我们想通过二级指针来修改变量a的值,我们该如何做?我们是如何由一级指针访问变量内存的?使用解引用来访问:

*p = 1;//以上面代码为续接

  那我们二级指针解引用就找到了一级指针的地址,然后我们在解引用一次,不就可以访问变量a了吗?

* *pp = 100;//两次解引用,第一次解引用找一级指针内存,第二次解引用就访问到变量a了

  以上就是二级指针的具体用法了,多级指针以此类推。


指针数组

  在我们C语言中存在着这样一个东西————指针数组 ,那么请你思考一下,指针数组究竟是指针呢还是数组呢?

  答:是一个数组,用来存放指针的数组。

  我们知道,数组有不同的类型,有int型数组,double型数组、char型数组…

  那么我们的指针数组呢?刚才我们也回答了,指针数组里面存放的都是指针变量,那这里的数组名其实就是二级指针了。

  我们来看下面代码:

#include<stdio.h>
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "talk is cheap";
  char arr3[] = "show me code";
  char *parr[] = { arr1, arr2, arr3 };
  char **p = parr;
  return 0;
 }

  我们可以通过指针p来访问数组中的元素的指向。

  字符指针数组的每个值的类型都是char*,而数组名就为数组的首元素地址,首元素为指针,所以数组名就是二级指针。

 ; 这里我们用二维数组的方式打印出各个数组里的字符串,我们只需要:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
  char arr1[] = "abcdef       ";
  char arr2[] = "talk is cheap";
  char arr3[] = "show me code ";
  char *parr[] = { arr1, arr2, arr3 };
  char **p = parr;
  int len = strlen(arr1);
  int i = 0;
  for(i = 0 ; i < 3 ; i++)
  {
    int j = 0;
    for(j = 0 ; j < len ; j++)
    {
      printf("%c",parr[i][j]);//通过[]来访问下标
    }
    printf("\n");
  }
  return 0;
 }

  我们可以看到,完全可以用二级指针来模拟二维数组。其实在第二层的for循环里面我们可以这样改:

for(j = 0 ; j < len ; j++)
{
  printf("%c",*(parr[i] + j));
}
printf("\n");

  先由数组名可以访问每个元素,而每个元素的类型都是char*所以我们可以使用偏移量来对每个指针变量所指向的数组进行访问。当然,这里还有其他的写法可以支持访问,大家可以自由的探索。


  感谢你能看到这,这期就到这里啦,如果还想看后续内容就给博主点点关注!C语言指针篇正在持续更新~~

相关文章
|
29天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
48 0
|
27天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
17 2
|
28天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
28天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
27天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
19 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
10天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
27 6
|
30天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
36 10
|
24天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
29天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
59 7