【C语言初阶(17)】初阶指针

news2024/11/26 16:29:06

文章目录

  • Ⅰ指针的概念
  • Ⅱ 指针和指针类型
    • ⒈指针 +- 整数
    • ⒉指针的解引用
  • Ⅲ 野指针
    • ⒈野指针成因
    • ⒉规避野指针
  • Ⅳ 指针运算
    • ⒈指针 +- 整数
    • ⒉指针 - 指针
    • ⒊指针的关系运算
  • Ⅴ 指针和数组
  • Ⅵ 二级指针
  • Ⅶ 指针数组

Ⅰ指针的概念

指针的两个要点

  1. 指针是内存中一个最小单元的编号,也就是地址
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
  • 总结:指针就是地址,口语中说的指针通常指的是指针变量。

内存是如何存放变量的?

  • 要彻底搞懂指针的概念,首先要知道内存是如何存放变量的。由于内存的最小索引单元式 1 字节,所以可以把整个内存想想为一个超级大的字符数组。
  • 数组有索引下标,内存也是,只是我们把它称为地址。每个地址可以存放 1 字节的数据,所以对于占 4 字节的整型变量来说,就需要使用 4 个连续的存储单元来存放。
  • 因为编译器知道具体每一个变量名对应的存放地址,所以当读取某个变量的时候,编译器就会找到变量名所在的地址,并根据变量的类型读取相应范围的数据。
    • 如下图所示:找到变量名 f 所在的地址,并根据变量 f 的类型连续读取四个字节的地址。

在这里插入图片描述

指针和指针变量

  • 通常我们所说的指针,就是地址的意思。C 语言中有专门的指针变量用于存放指针,跟普通变量不同,指针变量存储的是一个地址
  • 指针变量,里边存放的是地址,而通过这个地址,就可以找到一个内存单元。

地址的产生

  • 访问内存首先得有地址,有了地址才能找到内存单元,那地址是哪里来的? 地址是从地址线上传过来的
  • 32 位系统中,有 32 根地址线,每根地址线上过来的信号有 0/1 两种情况;

在这里插入图片描述

  • 32 根地址线所产生的所有二进制序列的组合就有 232 ,也就是会产生 232 个地址;

在这里插入图片描述

  • 232 个地址,1个地址管理1个字节,总共能管理 232 个字节(4GB)的空间。

  • 同样的,在 64 位系统中,就有 264 个地址。

总结

  • 指针变量是用来存放地址(指针)的,地址是唯一标示一块地址空间的。
  • 指针得我大小在 32 位机器上是 4 个字节,在 64 位上是 8 个字节。

Ⅱ 指针和指针类型

  • 指针变量也有类型,它的类型就是存放的地址指向的数据类型。
  • 如下图所示,变量 a ~ g 都是普通变量,其中变量 a ~ e 和 变量 g 都是字符变量,它们所在的地址存放的都是字符类型的数据,只占 1 个字节;
  • 变量 f 是整型变量,存放的数据是一个整型,占 4 字节的的空间;
  • 还有两个指针变量—— pa 和 pf,这两个变量存放的数据是地址,在这里它们分别存放了 变量 a 和变量 f 的地址。

在这里插入图片描述

指针的具体类型

  • 当有这样的代码:
int num = 10;
p = #
  • 要将 &num(num的地址)保存到 p 中,我们知道 p 就是一个指针变量,我们在定义指针变量 p 的时候就需要给它相应的类型了。
  • 各类型指针变量的定义:
char*	pc = NULL;//char* 就是 pc 的类型
int*  	pi = NULL;
short* 	ps = NULL;
long*	pl = NULL;
float*	pf = NULL;
double*	pd = NULL;
  • 可以看出,指针的类型就是指针所指向的变量类型+ *
    • short* 说明了 ps 是一个指向 short 类型的变量的指针,*表示 ps是一个指针。
    • int* 说明了 pi 是一个指向 int 类型的变量的指针。

指针类型的意义

  • 不管是什么类型的指针,在 32 位机器上都是 4 个字节,那么定义不同的指针类型好像就显得很憨了。
  • 然鹅,C 语言并没有把所有的指针类型都整合成一个同一的类型,那么指针类型自然是有其存在的意义的:指针 ± 整数、指针的解引用

⒈指针 ± 整数

先说结论

  • 指针的类型决定了指针 ± 1 操作的时候跳过几个字节
    • int* 类型的指针 + 1 会让地址向后走 4 个字节;
    • char* 的 + 1 会向后走 1 个字节;其余同理。

举个栗子

#include <stdio.h>

int main()
{
	int a = 0;
	char b = 'c';
	int* pa = &a;
	char* pb = &b;

	printf("%p\n", &a);
	printf("%p\n", pa);
	printf("%p\n", pa + 1);//int* 类型的指针一步跨 4 字节
	printf("----------------\n");
	printf("%p\n", &b);
	printf("%p\n", pb);
	printf("%p\n", pb + 1);//char* 类型的指针一步跨 1 字节

	return 0;
}

在这里插入图片描述

⒉指针的解引用

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
    • 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
  • 对 char* 类型的指针解引用只能修改一个字节的内存(只将一个字节的空间置为 0)。

在这里插入图片描述

  • 对 int* 类型的指针 pi 解引用就能将 4 个字节的空间全部改为 0。

在这里插入图片描述

Ⅲ 野指针

野指针的概念

  • 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

举个栗子

#include <stdio.h>

int main()
{
	int* a;
	*a = 123;

	return 0;
}
  • 类似于上面这样的代码是很危险的,因为指针变量 a 到底指向哪里,我们没办法知道。就和访问未初始化的变量一样,它的值是随机的。这在指针变量里是很危险的,因为后边代码对一个未知的地址进行赋值,那么就可能会覆盖到系统的一些关键代码。
  • 偶尔这个指针变量里随机存放的是一个合法的地址,那么接下来的赋值会导致那个位置的值莫名其妙的被修改。
  • 这种类型的 BUG 是非常难以排查的,所以在对指针进行解引用操作时,必须确保它们已经被正确的初始化了。

⒈野指针成因

1. 指针未初始化

  • 指针没有初始化,就意味着指针没有明确的指向。
  • 一个局部变量不初始化的话,放的是随机值。指针也一样,只不过放的时随机的地址。
#include <stdio.h>

int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;//非法访问内存,将随机一个地址中存的值改成 20

	return 0;
}

2. 指针越界访问

  • 一般访问数组元素都是通过下标来访问的;
  • 但有时候也会使用指针来访问,这种情况就有可能发生指针的越界访问了。
int main()
{
	int i = 0;
	int arr[10] = { 0 };	//数组名就是首元素地址 arr = &arr[0]
	int* p = arr;			//p 指向了数组的第一个元素,p + 1 指向第二个元素

	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;				//弄到最后会让 p + 10 相当于 arr[10],直接就越界了
	}

	return 0;
}

3. 指针指向的空间释放

int* test()
{
	int a = 10;
	return &a;

	//因为 a 是局部变量,当把 a 的地址返回之后,变量 a 自动销毁
	//原来的地址存放的就不再是变量 a 了,
}

int main()
{
	int* p = test();

	//用 p 来接收返回的 a 的地址
	//当接收了 p 的地址后,再往下使用 p 就成了野指针
	//当变量 a 销毁之后,p 还是记得传过来的地址,但此时这个地址已经不再指向 a 了
	//当 p 用这个地址往回找的时候,就不晓得找的到底是谁了

	return 0;
}

⒉规避野指针

  1. 指针初始化。
  2. 小心指针越界。
  3. 指针指向空间释放及时置NULL。
  4. 避免返回局部变量的地址。
  5. 指针使用之前检查有效性。

指针初始化

  • 在使用指针的时候,如果知道要给指针变量什么值,就一定要把这个值赋给它。
int a = 110;
int* p1 = &a;//明确把 a 的地址赋给 p1
  • 有时候确实不知道该让某个指针指向哪里的时候,一定要将其置为空指针。
int* p2 = NULL;//p2 哪都没指向,它是个空指针

指针指向空间释放及时置NULL

free(p);	//释放 p 所指向的空间
p = NULL;	//释放完之后要及时将该指针置空

指针使用之前检查有效性

if(p != NULL)
{
	*p = 100;//不是空指针就可以对 p 进行解引用
}

Ⅳ 指针运算

⒈指针 ± 整数

  • 当指针指向数组元素的时候,允许对指针变量进行加减运算,这样做的意义相当于指向举例指针所在位置向前或向后第 n 个元素。
    • 例如:p + 1 表示指向 p 指针所指向的元素的下一个元素,p - 1 则表示指向上一个元素。

在这里插入图片描述

⒉指针 - 指针

  • 指针之间也可以进行减法运算。
  • 指针 - 指针得到的绝对值是指针和指针之间元素的个数
  • 注意:指向同一块空间(同个数组)的两个指针才能相减。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

指针 - 指针的用途

  • 求字符串长度
  • 只要拿到 \0 的地址,以及首字符的地址,两个指针相减,就是字符的长度。

在这里插入图片描述

⒊指针的关系运算

  • 指针之间也可以比较大小。
#define N_VALUES 5
float values[N_VALUES];
float *vp;

for (vp = &values[0]; vp < &values[N_VALUES];)
{
     *vp++ = 0;
}
  • 当 vp 指向的地址小于 values[N_VALUES] 的地址时,就会让 vp 一直+1,指向数组的下一个元素。

在这里插入图片描述

Ⅴ 指针和数组

  • 指针和数组在很多方面都可以相互替换,给人的感觉它们似乎是一样的东西。
  • 然而,指针终归是指针,数组终归还是数组。

指针和数组的区别

  • 数组:一组相同类型元素的集合。
  • 指针变量:是一个变量,存放的是地址。

举个栗子

  • 下面代码试图计算一个字符串的字符个数:
int main()
{
	int count = 0;
	char str[] = "hello word!";

	while (*str != '\0')
	{
		str++;//数组名是个常量,不可以自增
		count++;
	}
	printf("总共有 %d 个字符\n", count);

	return 0;
}
  • 当试图运行的时候你可以看到,编译器毫不留情的报错了。

在这里插入图片描述

  • 编译器提示了自增运算符的操作对象需要一个左值,这个表达式的自增运算符的操作对象是 str ,str 实际上是数组名,不是一个左值。

左值的定义

  • 如果是左值的话,有两点要求:
  1. 首先要是一个用于是不你诶和定位一个存储位置的标识符。
  2. 其次这个值必须是可修改的。
  • 第一点数组名是满足的,因为数组名就是定位一个数组的位置。第二点就无法满足了,因为数组名不是变量,它只是一个地址常量,没办法修改
  • 如果按照这个思路来写代码,应该这样修改:
int main()
{
	int count = 0;
	char str[] = "hello word!";
	char* target = str;

	while (*target != '\0')
	{
		target++;//指针是个左值(变量),可以修改
		count++ ;
	}
	printf("总共有 %d 个字符\n",count);

	return 0;
}

在这里插入图片描述

结论

  • 数组名只是一个地址,而指针是一个左值(变量),可以存放地址

Ⅵ 二级指针

  • 指针变量也是变量,是变量就有地址,那么自然也有用来存放指针变量的地址的变量。
  • 我们管这种指针叫做:指向指针的指针(二级指针)

1. 二级指针的定义

int a = 10;
int* pa = &a;	//pa  是一级指针变量,存放整型变量 a 的地址
int** ppa = &pa;//ppa 是二级指针变量,存放指针变量 pa 的地址

在这里插入图片描述

2. 二级指针的类型

  • 二级指针的类型应该是指向的指针变量类型+*
  • 如:int** 就是二级指针 ppa 的类型。
char**	ppa = NULL;
int**	ppb = NULL;
short**	ppc = NULL;
long**	ppd = NULL;
float**	ppe = NULL;
double**ppf = NULL;

3. 二级指针解引用

  • 已经知道了对一级指针解引用一次可以找到原来变量里存的值,那么同样的,对二级指针进行两次解引用也可有找到原来变量里边存的值。

在这里插入图片描述

4. 二级指针的用途

  • 二级指针变量是用来存放一级指针变量的地址的。

Ⅶ 指针数组

  • 指针数组本质上是个数组,是用来存放指针的数组

1. 指针数组的定义

int* p[5];
//p 先和 [5] 结合,表明 p 是一个指针数组
//数组的每个元素都是一个 int* 类型的指针
  • 数组下标的优先级要比取值运算符的优先级高,所以先入为主,p 被定义为具有 5 个元素的数组。
  • 数组元素的类型是指向整型变量的指针。

在这里插入图片描述

  • p 是一个数组,有五个元素,每个元素是一个整形指针。

2. 指针数组的用途

  • 就像如果有很多 int 类型的值,可以放在一个整型数组里;
  • 同样的,如果定义的同类型的指针太多了,也可以放在指针数组里。
int a = 10;
int b = 20;
int c = 30;
......
int arr[5] = {10,20,30,......};
/可以用整型数组将多个同类型的值存储起来

int* pa = &a;
int* pb = &b;
int* pc = &c;
......
int* parr[5] = {&a,&b,&c,......};
//也可以用数组将多个同类型的指针存储起来

3. 指针数组的访问

  • 知道了怎么往指针数组里存东西之后,也要知道怎么把里面的东西拿出来。

在这里插入图片描述

  • 只要能找到数组下标为 0 的位置,就能拿到 a 的地址,再对这个地址进行解引用就可以找到 10 这个值了。其余同理
  • 先取出对应下标内存放的地址,然后再解引用
  • 现在我只想拿到前三个地址所指向的元素,请看代码:
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* parr[5] = { &a,&b,&c };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(parr[i]));
		//找到数组对应下标内存放的地址,然后解引用
	}
	putchar('\n');

	return 0;
}

在这里插入图片描述

4. 使用指针数组模拟二维数组

  • 一般的一维数组的数组名就是首元素的地址,那么如果把数组名放到指针数组里自然就能形成二维数组的效果。
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,2,3,4 };
int arr3[4] = { 3,2,3,4 };

int* parr[3] = { arr1,arr2,arr3 };
  • 将三个一维数组关联起来,造成一种二维数组的感觉。

在这里插入图片描述

  • 想把这些元素打印出来,依然可以使用二维数组的方式。
int main()
{
	int arr1[4] = { 1,2,3,4 };//第一行
	int arr2[4] = { 2,2,3,4 };//第二行
	int arr3[4] = { 3,2,3,4 };//第三行

	int* parr[3] = { arr1,arr2,arr3 };
	//parr[i],访问指针数组的每个元素的时候,
	//就相当于拿到了上面三行每一行的第一个元素

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
		}
		putchar('\n');
	}

	return 0;
}

在这里插入图片描述

  • 此处可能有人会好奇了,为什么不解引用呢?
  • 因为 [ ] 就是解引用:arr[i] <==> *(arr + i)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/764592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 torch.stft 进行短时傅里叶变换

python 常规的 stft 都是在 cpu 上进行计算&#xff0c;如果网络训练是在 GPU 上进行&#xff0c;那么就涉及到数据传输的问题&#xff0c;降低计算效率&#xff1b;而 torch 自带的 stft 可以直接在 GPU 上进行计算&#xff0c;因此可以节省计算时间。 import torch import t…

简单版本视频播放服务器V3-前端优化-播放器在左,列表在右侧【推荐】【完成】

做个家用版本的家庭影院&#xff0c;通过这个服务器可以给电脑&#xff0c;平板&#xff0c;手机等设备提供直接播放电影的作用&#xff0c;通过离线下载电影放入目录里就是就可以给全家提供电影播放了&#xff0c;通过浏览器就是可以访问电脑里面的视频&#xff0c;实现简单的…

Html基础知识学习——圣杯布局、margin负值、等高布局

文章目录 圣杯布局margin负值等高布局 圣杯布局 两边页面固定中间页面宽度随着浏览器大小自适应 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-widt…

代码随想录算法训练营day6 | 242. 有效的字母异位词,349. 两个数组的交集,202. 快乐数,1. 两数之和

目录 242. 有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和 242. 有效的字母异位词 242. 有效的字母异位词 难度&#xff1a;easy 类型&#xff1a;哈希表 思路&#xff1a; 代码&#xff1a; class Solution {public boolean isAnagram(String s, St…

计算机网络 day9 DNAT实验

目录 DNAT DNAT策略的典型应用环境 DNAT策略的原理 在网关中使用DNAT策略发布内网服务器 DNAT实验&#xff1a; 实验环境&#xff1a; DNAT网络规划拓扑图&#xff1a; 步骤&#xff1a; 1、创建linux客户端Web网站&#xff08;go语言&#xff09;&#xff0c;实现Web服…

二、DDL-1.数据库操作

一、查询 1、查询所有数据库 show databases; MySQL自带的默认的数据库有四个&#xff1a; 二、创建 1、创建一个新的数据库itcast&#xff08;不区分大小写&#xff09;&#xff1a; create database itcast; 查询所有数据库&#xff1a;多了itcast 2、再创建同名的数据库…

Latex合并多个公式且居中

要求&#xff1a;1&#xff1a;多个公式居中对齐 2&#xff1a;多个公式组合只有一个编号。 结果类似于这一种&#xff1a; 代码&#xff1a;使用gathered可以。 \begin{equation}\begin{gathered}\vspace{0.6em}{E} {A(I)}\\\vspace{0.6em}{F} Conv(\sum_{i1}^3{M_i}) \\{…

可移植性测试包括哪些

可移植性测试 可移植性是指应用程序能够安装到不同的环境中&#xff0c;在不同的环境中使用&#xff0c;甚至可以移动到不同的环境中。当然&#xff0c;前两者对所有系统都很重要。就PC软件而言&#xff0c;鉴于操作系统、共存和互操作应用程序、硬件、带宽可用性等方面的快速…

AutoSAR EM执行管理模块01

1执行管理的作用&#xff1f; 管理应用AA的生命周期&#xff0c;啥时候启动&#xff0c;啥时候shut down。管理板子的启动和关闭&#xff0c;这里的板子跑的程序是最基本的程序&#xff0c;不包含业务逻辑的AA&#xff0c;可以理解为最小系统的程序。根据定义的执行依赖关系加载…

Appium Android ——利用 TestNG 并行执行用例

目录 前言&#xff1a; 一、测试类 二、连接两个 Android 设备或启动两个虚拟机 三、项目路径下新建两个 testng.xml 四、开启两个 appium server 五、导出依赖 六、执行测试 七、查看报告 前言&#xff1a; Appium是一个流行的移动应用自动化测试工具&#xff0c;…

PHP与Golang对战:两种语言的比较与应用场景探讨

引言 在软件开发领域&#xff0c;选择一种合适的编程语言对于项目的成功至关重要。而在今天的文中&#xff0c;我们将探讨两个备受争议的编程语言——PHP与Golang之间的对战。通过比较它们的优势和应用场景&#xff0c;帮助开发者更好地了解如何选择适合自己项目的语言。 PHP的…

vue3和vue2主要的一些区别?

一、Vue3介绍 关于vue3的重构背景&#xff0c;尤大是这样说的&#xff1a; 「Vue 新版本的理念成型于 2018 年末&#xff0c;当时 Vue 2 的代码库已经有两岁半了。比起通用软件的生命周期来这好像也没那么久&#xff0c;但在这段时期&#xff0c;前端世界已经今昔非比了 在我…

Spring 框架——事件驱动模型

目录 1.概述2.三种角色2.1.事件角色2.2.事件监听者角色2.3.事件发布者角色 3.示例 1.概述 &#xff08;1&#xff09;Spring 事件驱动模型是 Spring 框架中的一种编程模型&#xff0c;也被称为发布/订阅模型&#xff0c;通过使用观察者模式和事件机制&#xff0c;实现了组件之…

Flask_自定义flask的cmd命令

创建自定义命令 from flask import Flaskapp Flask(__name__)app.cli.command() def hello():"""命令说明写这里"""print("hello python")if __name__ __main__:app.run() 执行flask --help 可以在命令查看定义的命令 注意事项&a…

网络工程基础框架3层次模型 ,1接入层2 汇聚层 3核心层

网络工程基础框架3层次模型 ,1接入层2 汇聚层 3核心层_「已注销」的博客-CSDN博客 CISCO有自己的3层层次模型 1&#xff0c;接入层 2&#xff0c;汇聚层 3&#xff0c;核心层 区别&#xff1a;通常将网络中直接面向用户连接或访问网络的部分称为接入层&#xff0c;将位于接入层…

盛元广通科研院所实验室安全管理系统LIMS

实验室的管理与安全直接影响着教学与科研质量&#xff0c;从科研角度出发&#xff0c;实验室安全风险特点与生产现场安全风险特点存在较大差异&#xff0c;危险源种类复杂实验内容变更频繁&#xff0c;缺乏有效监管&#xff0c;实验室安全运行及管理长期游离于重点监管领域外&a…

抖音seo矩阵源码SaaS搭建代码分享-可二开

场景&#xff1a;适用于抖音seo源码&#xff0c;抖音矩阵源码&#xff0c;短视频seo源码&#xff0c;短视频矩阵源码&#xff0c;抖音短视频seo矩阵系统源码开发搭建等。 抖音seo源码优化逻辑 抖音SEO是通过一系列的技术手段和优化策略来提升视频内容在抖音平台内的曝光率和排名…

机器学习实战系列:工业蒸汽量预测

背景介绍 火力发电的基本原理是&#xff1a;燃料在燃烧时加热水生成蒸汽&#xff0c;蒸汽压力推动汽轮机旋转&#xff0c;然后汽轮机带动发电机旋转&#xff0c;产生电能。在这一系列的能量转化中&#xff0c;影响发电效率的核心是锅炉的燃烧效率&#xff0c;即燃料燃烧加热水…

vue项目中实现3D万花筒和3D文字旋转效果

一、万花筒 1、html部分 //万花筒html <div class"carousel" data-gap"368"><figure><div class"carouselItem" v-for"(item,index) in exhibitionList" :key"index"><div class"itemContent&q…

动态内存函数详解-【malloc,calloc,realloc,free】

动态内存函数详解 malloc一、malloc的简介1.malloc函数的定义&#xff1a;2.参数&#xff1a;3.返回值&#xff1a;4.功能&#xff1a; 二、malloc函数的使用 free函数free函数的简介free函数使用注意事项 calloccalloc函数的简介二、calloc函数的使用calloc函数的注意事项 rea…