C语言——指针

一、定义

指针也就是内存地址,指针变量是用来存放内存地址的变量。

将内存以一个字节分为一个个内存单元,每个内存单元都进行编号,这个编号就是地址,也就是指针。

	int b = 1;
	int *pb = &b;
	//这里的pb变量是一个整型指针变量,用来存放整型变量b的地址

我们可以通过&(取地址操作符)得到一个变量的地址,然后将地址存到一个指针变量中,可以用这个指针变量来访问那个变量。

二、指针的大小

对于32位机器,假设CPU与内存之间有32条地址线,每一根寻址线在工作时会产生高电平(代表1)和低电平(代表0),则这个机器可以产生

00000000 00000000 00000000 00000000

11111111 11111111 11111111 11111111

这么多的地址,一共是2 ^ 32 个地址,一个地址指向的内存单元是一字节,所以这么多地址可以指向大约4GB的内存。

对于64位机器,假设CPU与内存之间有64条地址线,每一根寻址线在工作时会产生高电平(代表1)和低电平(代表0),则这个机器可以产生

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111

这么多的地址,一共是2 ^ 64 个地址,一个地址指向的内存单元是一字节,所以这么多地址可以指向更大的内存。

这里我们发现,地址的大小是取决于地址线的多少的(或者是系统位数),对于32位机器,地址是32位的,也就是32bit的大小,所以是4字节的大小,所以32位机器的地址大小是4字节;相应的64位机器的地址是8字节。

#include <stdio.h>

int main()
{
	int b = 1;
	int *pb = &b;
	printf("%zu\n", sizeof(pb));
	return 0;
}

64位平台输出结果:

32位平台输出结果:

三、指针的类型

1、指针变量的类型

  • int *:指向整型数据的指针
  • char *:指向字符数据的指针
  • float *:指向浮点数据的指针
  • double *:指向双精度浮点数据的指针
  • void *:通用指针,可以指向任何类型的数据
  • int **:指向int *指针的指针
  • char ***:指向char **指针的指针的指针

等等。

2、指针类型的意义

对于不同类型的指针,只要平台位数是一定的,则指针的大小是一定的。

	int* pa = NULL;
	double* pb = NULL;
	char* pc = NULL;
	printf("%zu\n", sizeof(pa));
	printf("%zu\n", sizeof(pb));
	printf("%zu\n", sizeof(pc));

运行结果:

那既然说这样的话,不同类型指针的大小是相同的,为什么不只设定一种指针呢?

(1)指针解引用

这是因为不同类型指针指向的变量类型不同,而不同变量的大小有不同,我们知道一个变量的指针指向的是变量的第一个地址,例如int类型有4个字节,则这4个字节有4个指针,而指向这个int类型变量的指针是这个变量的首地址,如果只有一种指针的话,那我们通过指针就不能正确地访问不同的变量了。

不同类型的指针虽然在内存中占用相同大小的空间(在特定的平台上),但它们指向的数据类型不同,这就要求指针在解引用(dereferencing)时能正确地解释所指向的内存区域的大小和类型。当你解引用指针获取它所指向的值时,指针的类型决定了从指针指向地址开始的内存中读取多少数据,以及如何解释这些数据。例如,整型指针会读取4个字节(在大多数现代平台上),而字符指针只会读取一个字节。

	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;//通过解引用经由整型指针变量pa对整型变量a操作

对于变量a,它占4个字节,通过解引用经由整型指针变量pa对整型变量a操作后,直接改变了4个字节的内容。

这里我们强行将整型变量a的地址存在char型指针变量中:

	int a = 0x11223344;
	char* pc = (char*)&a;//强行将整型变量a的地址存在char型指针变量中
	*pc = 0;//通过解引用经由整型指针变量pc对整型变量a操作

对于变量a,它占4个字节,这次通过解引用经由整型指针变量pc对整型变量a操作后,只改变了1个字节的内容。

这足以体现指针变量类型的重要性。

(2)指针算数

以及指针在进行指针算数时,指针类型还决定了执行指针算术时的行为,或者说决定了指针的步长。比如说,对于指向一个整数(通常占4个字节)的指针int *p,执行p + 1时,地址会增加sizeof(int)个单位,确保p + 1指向下一个整数的起始位置。而对于指向一个字符(通常占1个字节)的指针char *c,执行c + 1时,地址只会增加sizeof(char)个单位。

	int a = 1;
	int* pa = &a;
	char* pc = (char*)&a;
	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	printf("----------------\n");
	printf("%p\n", pc);
	printf("%p\n", pc + 1);

运行结果:

对于int*类型指针的步长是4字节,而char*类型指针的步长是1字节。

(3)类型安全

不同类型的指针帮助语言保持类型安全,确保不会将整型数据解释为浮点数,或者反过来。这有助于避免许多类型相关的错误。

	int a = 1;
	int* pi = &a;
	*pi = 100;

这里正常将整型指针变量指向整型变量,然后通过解引用经由整型指针变量pi对整型变量a操作后,a中的值是正确的:

	int a = 1;
	float* pf = (float*)&a;
	*pf = 100.0f;

这里我们将浮点数指针变量指向整型变量,然后通过解引用经由浮点数指针变量pf对整型变量a操作后,a中的值是错误的:

(4)其他意义

还有一些原因:

  1. 函数指针:函数指针是另一种特别的数据类型,它们的大小也是统一的,但是它们指向的是函数而不是普通的数据类型。它们允许程序动态地调用不同的函数,并且为了安全和正确地执行函数调用,需要对应的类型信息。

  2. 抽象和接口设计:在面向对象的编程中,特别是在使用多态的情况下,不同类型的指针可以指向一个继承体系中的不同对象。这样,同一个函数可以接受不同类型的对象作为参数,根据对象的实际类型来调用相应的方法。

  3. 数据对齐:某些类型的数据需要在内存中特定的对齐方式。类型化指针确保了正确的对齐,这对于硬件访问是很重要的,因为某些硬件架构要求特定类型的数据在内存中的特定对齐。

通过使用不同类型的指针,编程语言提供了一种丰富的方法来操作内存中的数据,同时也确保了访问这些数据的正确性和效率。

这些同样也体现了指针类型的重要意义。

四、野指针

野指针是指那些没有被初始化的指针,或者说它们的值是随机的,因此它们指向的是不确定的内存位置。访问野指针指向的内存同样会导致不可预料的行为或程序崩溃。

(1)未初始化的指针

当一个指针变量被声明但没有被显式初始化时,它将包含一个随机的内存地址,这种指针是野指针。

	int* p;//未初始化的指针,没有明确指向,是野指针

(2)指针操作越界

指针在进行算术运算时超过了其所指向的缓冲区或数组边界,可能会导致指针指向一个非法区域。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//这里的整型指针指向的是数组首元素
	printf("%d\n", p + 10);//这样操作是访问了数组的第十一个元素,而这个数组只有十个元素,所以是越界访问,p + 10是野指针

(3)其他情况

  1. 指针赋值错误: 由于编程错误,指针可能被赋值为一个意外的非法地址,例如指针类型转换错误等。

使用野指针进行操作是非常危险的,因为它们可能导致程序崩溃、数据损坏或安全漏洞。因此,最佳实践是始终确保指针在使用前已正确初始化,避免野指针的出现。

五、悬空指针

悬空指针(Dangling Pointer),也称为悬挂指针或迷失指针,是指向一块曾经分配过的内存的指针。但由于某些原因,这块内存已经不再有效,指针仍然指向那个地址,此时的指针被称为悬空指针。使用悬空指针访问数据是危险的,因为原来的内存可能已经被重新分配或释放,其内容可能已发生变化或不再属于程序的地址空间。以下是造成悬空指针的一些常见情况:

 (1)指向栈内存的指针在函数返回后

如果一个指针指向了一个函数内的局部变量(栈内存),而该函数返回后该变量的生命周期结束,这时候这个指针也会变成悬空指针。

#include <stdio.h>

int* test()
{
	int a = 0;
	return &a;
}

int main()
{
	int* p = test();//整型指针变量p接受的值是a的地址,所以整型指针变量p指向a,函数返回后,整型变量a的生命周期结束,然而p依旧指向a原来的地址,p变成野指针
	return 0;
}

(2)已释放的内存指针

当使用freedelete释放了某个指针指向的内存后,如果没有将该指针置为NULL,它仍然包含释放内存的地址,这种指针也成为悬空指针。

int *ptr = malloc(sizeof(int));
*ptr = 1;
free(ptr); // ptr现在是悬空指针。

最佳的做法是在释放指针指向的内存后,立即将指针设置为NULL,这有助于防止悬空指针的出现,因为NULL指针的解引用是确定性的行为,通常会导致程序的安全终止。

六、如何避免野指针和悬空指针

1、在创建指针变量后初始化

	int a = 0;
	int* p = &a;//初始化p的值为a的地址

如果没有明确的值去初始化,则初始化为NULL:

	int* p = NULL;//初始化p的值为NULL

因为NULL指针的解引用是确定性的行为,通常会导致程序的安全终止。

2、其他方法

(1)小心数组越界

(2)指针指向的空间释放时,及时置为NULL

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

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

七、指针的算数运算

1、指针加法 (ptr + n)

一个指针与一个整数相加时,结果是一个新的指针,它指向相对于原指针向后移动n个元素的位置。这里的n是与指针类型对应的数据类型的大小的倍数。例如,如果你有一个指向int的指针int *p;,并且int占用4个字节,则p + 1会得到一个新的地址,比p的地址高4个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	printf("%p\n%p\n", p, p + 1);

地址相差四个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];//指向数组首元素的地址
	if (*(p + 1) == arr[1])//数组第二个元素
	{
		printf("相等!\n");
	}

p + 1直接跳到数组的下一个元素。

2、指针减法 (ptr - n)

一个指针与一个整数相减时,结果是一个新的指针,它指向相对于原指针向前移动n个元素的位置。与指针加法类似,移动的实际字节数取决于指针类型对应的数据类型的大小。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[1];
	printf("%p\n%p\n", p, p - 1);

地址相差四个字节。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[1];//指向数组第二个元素的地址
	if (*(p - 1) == arr[0])//数组首元素
	{
		printf("相等!\n");
	}

p - 1直接跳到数组的上一个元素。

3、指针间的减法 (ptr1 - ptr2)

两个类型相同的指针相减时,结果是它们之间相隔的元素个数。如果ptr1ptr2指向同一个数组的不同元素,则ptr1 - ptr2将得到一个整数,指示它们之间的距离。这个结果通常用于确定数组中的位置或计算偏移量。指向同一块内存空间的两个指针相减才有意义。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = &arr[0];
	int* p2 = &arr[9];
	printf("%d\n", p2 - p1);

第1个元素与第10个元素之间相差9个元素。

4、指针递增和递减

使用++--运算符可以使指针向前或向后移动一个元素。例如,++ptr将指针移向下一个元素,而--ptr将指针移向前一个元素。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p;
	for (p = &arr[0]; p <= &arr[9]; )
	{
		*p = 0;
		p++;
	}

对数组的每个元素操作,将数组的每个元素赋值为0。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p;
	for (p = &arr[9]; p >= &arr[0]; )
	{
		*p = 10;
		p--;
	}

对数组的每个元素操作,将数组的每个元素赋值为10。

5、指针比较

指针可以使用关系运算符(>, <, >=, <=, ==, !=)进行比较。这些运算符通常用于比较同一数组或内存块内的指针位置。尝试比较不同数组或内存块的指针是未定义行为。

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	if (p == &arr[0])
	{
		printf("相等!\n");
	}

八、指针和数组

1、数组名

数组名在大多数情况下是首元素地址,两种情况除外,详见我之前的文章《C语言——数组》。

九、二级指针

二级指针是存放指针变量的指针变量。

在C语言中,二级指针是指向指针的指针,也就是说,它存储的是另一个指针的地址。二级指针通常用于动态多维数组的分配、函数中修改指针本身的值、以及处理指针数组等场景。

#include <stdio.h>

int main()
{
	int var = 10;      //普通的整型变量
	int* ptr = &var;   //一级指针,指向整型变量var的指针
	int** pptr = &ptr; //二级指针,指向一级指针ptr的指针
	printf("var = %d\n", var);
	printf("*ptr = %d\n", *ptr);
	printf("**pptr = %d\n", **pptr);//使用二级指针访问var的值
	return 0;
}

十、指针数组

1、介绍

在C语言中,指针数组是一个数组,其每个元素都是一个指针。换句话说,指针数组是用来存储指针的数组。这种数据结构经常被用来存储字符串数组或者动态分配的结构体数组的指针。

	int a = 0;
	int b = 1;
	int c = 2;
	int* parr[3] = { &a,&b,&c };//指针数组存放指针

2、用指针数组模拟二维数组

因为数组名是首元素地址,所以可以用指针数组存数组的首元素地址实现二维数组的模拟。

#include <stdio.h>

int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 3,4,5,6 };
	int arr3[4] = { 5,6,7,8 };
	int* parr[3] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", *(parr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

运行结果:

还可以这样:

#include <stdio.h>

int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 3,4,5,6 };
	int arr3[4] = { 5,6,7,8 };
	int* parr[3] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

运行结果:

这个版本与上一个是一样的作用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Databend 的安装配置和使用

介绍 Databend 是一个内置在 Rust 中的开源、弹性和工作负载感知的云数据仓库&#xff0c;为 Snowflake 提供了具有成本效益的替代方案&#xff0c;专门对最大的数据集进行复杂分析而设计。 性能&#xff1a; 在存储对象上&#xff0c;能快速进行数据分析。没有索引和分区&a…

CSS 放大翻转动画

<template><div class="container" @mouseenter="startAnimation" @mouseleave="stopAnimation"><!-- 旋方块 --><div class="box" :class="{ rotate-scale-up-hor: isAnimating }"><!-- 元素内…

vmware安装redhat 7.6 操作系统

vmware安装redhat 7.6 操作系统 1、下载redhat 7.6 操作系统镜像文件2、安装redhat 7.6操作系统3、配置redhat 7.6 操作系统3.1、配置静态IP地址 和 dns3.2、查看磁盘分区3.3、查看系统版本 1、下载redhat 7.6 操作系统镜像文件 链接: 盘盘 zwzg 文件名&#xff1a;rhel-serv…

LowB三人组(冒泡排序,插入排序,选择排序)(数据结构课设篇1,python版)(排序综合)

本章博客主要详细讲解一下LowB三人组排序&#xff0c;为什么叫LowB三人组呢&#xff1f;因为他们的时间复杂度都为O&#xff08;n^2&#xff09;。下篇博客会再讲解NB三人组&#xff08;堆排序&#xff0c;归并排序和快速排序&#xff09;&#xff0c;第三篇博客会讲解其他排序…

C语言全面学习基础阶段01—C生万物

如何学好 C 语言 1. 鼓励你&#xff0c;为你叫好。 C 生万物 编程之本 长远 IT 职业发展的首选 C 语言是母体语言&#xff0c;是人机交互接近底层的桥梁 学会 C/C &#xff0c;相当于掌握技术核心 知识点一竿子打通。 IT 行业&#xff0c;一般每 10 年就有一次变革 40 年间&a…

【GUI界面软件】抖音评论采集:自动采集10000多条,含二级评论、展开评论!

文章目录 一、背景说明1.1 效果演示1.2 演示视频1.3 软件说明 二、代码讲解2.1 爬虫采集模块2.2 软件界面模块2.3 日志模块 三、获取源码及软件 一、背景说明 1.1 效果演示 您好&#xff01;我是马哥python说&#xff0c;一名10年程序猿。 我用python开发了一个爬虫采集软件…

C语言学习NO.11-字符函数strlen,strlen函数的使用,与三种strlen函数的模拟实现

&#xff08;一&#xff09;strlen函数的使用 strlen函数的演示 #include <stdio.h> #include <string.h>int main() {char arr1[] "abcdef";char arr2[] "good";printf("arr1 %d,arr2 %d",strlen(arr1),strlen(arr2));return …

windows下使用Apache配置WebDav

1.Apache下载 我使用的Apache版本是2.4.58 大家可以在Apache官网下载自己需要的版本 Download - The Apache HTTP Server Project 2.Apache配置 解压Apache放到你想放置的目录&#xff0c;我是放在C盘&#xff0c;C:\Apache24 如图&#xff1a; 修改配置文件httpd.conf 此…

test dbtest-02-Liquibase 是一个数据库变更管理工具

拓展阅读 DbUnit-01-数据库测试工具入门介绍 database tool-01-flyway 数据库迁移工具介绍 什么是 Liquibase&#xff1f; Liquibase 是一种开源的数据库架构变更管理解决方案&#xff0c;它使你能够轻松地管理数据库变更的修订版本。 Liquibase使得参与应用程序发布流程的…

项目中对日期进行格式化的方法

方式一&#xff1a;在属性上添加注解进行格式化 需要引入jackson包 <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.2</version> </dependency>在属性上…

FreeRTOS移植

目录 一、FreeRTOS简介1.1 初识FreeRTOS1.2 FreeRTOS资料获取1.3 开发环境简介 二、FreeRTOS移植2.1 文件添加2.2 keil工程添加2.3 文件修改 一、FreeRTOS简介 1.1 初识FreeRTOS 首先看一下 FreeRTOS 的名字&#xff0c;可以分为两部分&#xff1a;“Free”和“RTOS”&#xf…

5分钟搞懂AI的可解释性

大家好啊&#xff0c;我是董董灿。 想象一下&#xff0c;如果有一天&#xff0c;有人跑过来突然告诉你&#xff0c;他搞懂了人类大脑记忆的运行机制&#xff0c;你会是什么反应&#xff1f; 你可能会和我一样&#xff0c;把他当做疯子。 因为我觉得这个课题太深奥了&#xf…

2023高级人工智能期末总结

1、人工智能概念的一般描述 人工智能是那些与人的思维相关的活动&#xff0c;诸如决策、问题求解和学习等的自动化&#xff1b; 人工智能是一种计算机能够思维&#xff0c;使机器具有智力的激动人心的新尝试&#xff1b; 人工智能是研究如何让计算机做现阶段只有人才能做得好的…

Jmeter 性能 —— 电商系统TPS计算

1、怎么计算得出TPS指标 ①第一个通过运维那边给的生产数据&#xff0c;看一下生产进件有多少&#xff0c;计算得来的&#xff0c;如果没有生产数据&#xff0c;或者不过就看如下的方法 ②第二个就是根据最近一个月的实际访问数据&#xff0c;比如每天调用了多少个接口&#…

应用系统如何集成和扩展开源工作流引擎

目前主流的开源流程引擎有activiti、flowable、camunda等&#xff0c;这几个开源流程引擎的版本很多&#xff0c;哪个开源流程引擎哪个版本的功能更多、性能更好&#xff0c;该如何选择请参考&#xff1a;https://lowcode.blog.csdn.net/article/details/116405594 无论您选择…

微信小程序使用mqtt开发可以,真机不行

以下可以解决我的问题&#xff0c;请一步一步跟着做&#xff0c;有可能版本不一样就失败了 一、下载mqtt.js 前往蓝奏云 https://wwue.lanzouo.com/iQPdc1k50hpe 下载好后将.txt改为.js 然后放入项目里 二、连接mqtt const mqtt require(../../utils/mqtt.min); let cli…

VUE部署到IIS中报404错误解决方案-配置URL重写

VUE部署到IIS中报404错误解决方案-配置URL重写 第一步&#xff0c;Windows服务器中开启IIS 可承载的web核心 1、添加角色和功能中安装iis 可承载web核心 第二步&#xff0c;下载url重写工具 官方网站下载地址&#xff1a; https://www.iis.net/downloads/microsoft/url-rewrit…

【JVM】类加载器ClassLoader

一、简介 在Java中&#xff0c;类加载器&#xff08;ClassLoader&#xff09;是一个关键的组件&#xff0c;它负责将字节码文件加载到内存并转换成Java类。Java的类加载器主要可以分成两类&#xff1a;系统提供的和由Java应用开发人员编写的。Java开发者可以根据需要创建自己的…

54、Softmax 分类器以及它的底层原理

下面开始介绍最后一个算法softmax。在前面介绍全连接算法或其他文章中,或多或少也提到了softmax。 在分类网络里,softmax的作用主要是将模型的原始输出映射到 0~1之间的概率分布。很多时候对于我们初学者而言,只知道softmax可以做概率映射,但并不了解它内部的原理是如何完…

【Linux Shell】8. test 命令

文章目录 【 1. 数值测试 】【 2. 字符串测试 】【 3. 文件测试 】 Shell中的 test 命令用于检查某个条件是否成立&#xff0c;它可以进行数值、字符和文件三个方面的测试。 【 1. 数值测试 】 参数作用-eq等于则为真-ne不等于则为真-gt大于则为真-ge大于等于则为真-lt小于则…