【C语言航路】第六站:指针初阶(下)

简介: 【C语言航路】第六站:指针初阶

(3)指针指向的空间释放

我们看这个代码

#include<stdio.h>
int* test()
{
  int a = 10;
  return &a;
}
int main()
{
  int* p = test();
  printf("%d", *p);
  return 0;
}

这个代码也是,a是一个局部变量,返回的时候a已经被销毁了,此时的这个地址就是一个野指针。

当然我们可以运行一下,我们会发现仍然是10,这是因为之前的函数栈帧还没有被破坏掉。我们加上个代码就能破坏掉这个函数栈帧,导致结果不一样

2.如何规避野指针

(1) 指针初始化

#include<stdio.h>
int main()
{
  int a = 0;
  int* pa = &a;//指针的初始化
  int* pc = NULL;//空指针,专门用来初始化指针
  return 0;
}

如上代码所示,如果不知道该初始化成什么,可以初始化成NULL,当然要注意,空指针不可以解引用。解引用空指针会使程序崩溃。因为0地址处是不能让用户使用的

(2) 小心指针越界

(3) 指针指向空间释放,及时置NULL

(4) 避免返回局部变量的地址

(5) 指针使用之前检查有效性

四、指针运算

指针的运算共有三种

1.指针+- 整数

2.指针-指针

3.指针的关系运算

1.指针+-整数

这快的内容在前文中已经涉及过,这里在简单的讲解一个案例

#define N_VALUES 5
#include<stdio.h>
int main()
{
    float values[N_VALUES];
    float* vp;
    //指针+-整数;指针的关系运算
    for (vp = &values[0]; vp < &values[N_VALUES];)
    {
        *vp++ = 0;
    }
    return 0;
}

这段代码唯一需要注意的就是一个表达式*vp++。++的优先级比较高,但是由于他是前置++,所以先解引用vp,然后vp++。最终的效果就是将这个数组置零。

2.指针-指针

指针-指针有个前提

两个指针要指向同一个空间,并且两个指针类型相同

指针-指针的绝对值是两个指针之间的元素个数

我们看这个代码

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

运行结果为

我们之前讲解过自己实现一个求字符串长度的解法,一种是计数器,另外一种是递归的思想,今天我们在采用一种指针-指针的方法,使用\0处的指针-起始点

代码如下

#include<stdio.h>
int my_strlen(char* arr)
{
  int* str = arr;
  while (*arr != '\0')
  {
    arr++;
  }
  return arr - str;
}
int main()
{
  char arr[] = "abcdef";
  int len = my_strlen(arr);
  printf("%d", len);
  return 0;
}

当然,我们也可以将*,和++进行合并

#include<stdio.h>
int my_strlen(char* arr)
{
  int* str = arr;
  while (*arr++ != '\0')
    ;
  return arr - str - 1;
}
int main()
{
  char arr[] = "abcdef";
  int len = my_strlen(arr);
  printf("%d", len);
  return 0;
}

3.指针的关系运算

#define N_VALUES 5
#include<stdio.h>
int main()
{
  float values[N_VALUES];
  float* vp;
  for (vp = &values[N_VALUES]; vp > &values[0];)
  {
    *--vp = 0;
  }
}

这段代码与前面一段代码很相似,功能就是将数组置零。这个是先--vp然后在解引用,最终vp停留的位置就是数组元素的起点

我们在看一下这段代码

#define N_VALUES 5
#include<stdio.h>
int main()
{
  float values[N_VALUES];
  float* vp;
  for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--)
  {
    *vp = 0;
  }
}

这段代码,是将上面的*和--混用的部分代码给拆开写了。这样写确实可以实现我们的功能,但是要注意的是,第二种写法在绝大多数编译器是没有问题的,但是然而我们还是应该避免这样写,因为标准并不保证它可行。在少部分的编译器上还是会出问题的。

因为这个段代码最后会出现数组首元素的前一个地址与这个数组首元素地址进行比较。

而我们的标准是这样规定的:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

五、 指针和数组

1.指针和数组是不同的对象

指针是一种变量,存放地址的,大小4/8字节的

数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型的

2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中

可以通过指针访问数组

比如下面的代码就可以实现指针访问数组

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //赋值
    for (i = 0; i < sz; i++)
    {
        *p = i + 1;
        p++;
    }
    //打印
    p = arr;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

或者也可以这样写

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //赋值
    for (i = 0; i < sz; i++)
    {
        *(p + i) = i + 1;   
    }
    //打印
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(p+i));
    }
    return 0;
}

还有一点需要说明的是

我们在这里出现了

int arr[10];

int* p=arr;

这里说明arr是一个首元素地址

而地址就可以解引用。所以我们知道arr[i]--->*(arr+i)

而加法是满足交换律的,所以进而推出*(i+arr)

进而推出    i[arr]

事实上,这个确实是没有问题的,可以正常使用的,因为 [ ]他只是一个操作符,i和arr是这个操作符的操作数而已。在我们电脑里面arr[i]也会被翻译成*(arr+i)。

六、二级指针

1.什么是二级指针?

我们在之前说过的指针是这样的int* pa=&a;也就是将a的地址放入pa中,pa的类型是int*。

那么pa这个指针其实应该也有一个地址,我们如果取出他的地址,将他放入另外一个变量,这就叫做二级指针。

也就是int**ppa=&pa。其中第二颗*代表着他是一个指针,前面的int*代表着他指向的类型是一个int*类型的数据

如下代码所示

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;
  int** ppa = &pa;
  return 0;
}

如下图示关系

2.二级指针的解引用

对于二级指针的解引用需要两颗*

有如下等式成立

**ppa==*pa=a

举个例子

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
  int a = 10;
  int* pa = &a;
  int** ppa = &pa;
  printf("%d\n", **ppa);
  **ppa = 50;
  printf("%d\n", **ppa);
  return 0;
}

运行结果为

七、指针数组

1.指针数组的概念

在这里,我们首先需要了解指针数组是数组还是指针呢?

其实,是数组,从语言的角度来思考,指针是修饰词,数组才是主语

比如字符数组,他是一个数组,里面存放的都是字符

比如整型数组,他是一个数组,里面存放的都是整型

所以我们便能猜测到,指针数组,他是一个数组,里面存放的都是指针

我们举一个例子

#include<stdio.h>
int main()
{
  int a = 10;
  int b = 20;
  int c = 30;
  int d = 40;
  int e = 50;
  int* arr[5] = { &a,&b,&c,&d,&e };
  int i = 0;
  for (i - 0; i < 5; i++)
  {
    printf("%d ", *(arr[i]));
  }
  return 0;
}

运行结果为

图解为

2.尝试模拟一个二维数组

我们了解了指针数组的概念以后,我们可以利用其模拟一个二维数组,假设我们要模拟三行四列的二维数组

我们是这样想的,先定义出三个一维数组a,b,c。然后定义一个指针数组,令指针数组的每个元素是这些一维数组的首元素地址。然后我们就可以通过两次遍历就能模拟出来这个二维数组了

代码如下

#include<stdio.h>
int main()
{
  int a[] = { 1,2,3,4 };
  int b[] = { 2,3,4,5 };
  int c[] = { 3,4,5,6 };
  int* arr[] = { a,b,c };
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 4; j++)
    {
      printf("%d ", *(arr[i]+j));
    }
    printf("\n");
  }
  return 0;
}

运行结果为

当然,如果我们将*(arr[i]+j)改为arr[i][j]也是正确的,因为这两种是可以相互转换的


总结

本节课我们主要详细讲解了指针与内存对于他们理解,指针和指针类型,指针类型的意义,野指针的成因,以及如何规避野指针,指针的三种运算,指针与数组的关系,二级指针,以及指针数组

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

相关文章
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
107 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
93 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
53 7
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
180 3
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
57 1
|
14天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
52 23
|
14天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
44 15
|
14天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
53 24

热门文章

最新文章