C语言——指针(入门详解)

news2025/1/18 11:56:01

文章目录

  • 1.什么是指针?
    • 1.1.理解指针的两个要点:
    • 1.2.指针变量:
    • 1.3.内存是如何编址?
  • 2.指针和指针类型
    • 2.1指针的创建与初始化
    • 2.2.指针类型
  • 3.野指针
    • 3.1.什么视野指针?
    • 3.2.野指针成因
    • 3.3.规避野指针
  • 4.指针运算
    • 4.1.指针+-整数
    • 4.2.指针-指针
    • 4.3指针的关系运算
  • 5.指针与数组
  • 6.二级指针
    • 6.1.什么是二级指针
    • 6.2.二级指针的运算
  • 7.指针数组
  • 总结

1.什么是指针?

1.1.理解指针的两个要点:

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

在这里插入图片描述
补充:一个内存单元占一个字节。

1.2.指针变量:

通过&取地址操作符取出变量在内存中的起始地址,将取出的地址存放在一个变量中,这个存放地址的变量就是指针变量。

下面通过代码演示指针变量的创建与使用

int main()
{
	int a = 10;
	int* pa  = &a;//创建指针变量并初始化

	//打印验证
	printf("%p\n", &a);
	printf("%p\n", pa);

	//使用指针变量需要用*解引用操作符
	*pa = 20;//此时*pa等价与a
	printf("%d\n", *pa);
	printf("%d\n", a);


	return 0;
}

在这里插入图片描述
补充:

1、a在内存中占4个字节,&a取出的是整型变量a的第一个字节的地址(最小的地址)。
2.当需要通过地址访问来改变变量的值时,需要使用*解引用操作符对指针变量进行解引用操作。

1.3.内存是如何编址?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)。
在这里插入图片描述
每个地址标识1个字节,操作系统就会分配2的32次方个字节,也就是4GB的空间进行编址。同理可得64位机器,每个地址标识2个字节,操作系统就会分配2的64次方个字节,也就是8GB的空间进行编址。

32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

总结:

1、指针变量是用来存放地址的,地址是唯一标识一个内存单元的。
2、在32位平台下,地址的大小是4个字节,指针变量的大小是4个字节。在64位平台下,地址的大小是8个字节,指针变量的大小也是8个字节。

2.指针和指针类型

2.1指针的创建与初始化

我们都知道变量是分类型的,如整型、单精度浮点型、字符型等。那指针是否也分类型呢?答案是有的。
下面我通过代码举例

int main()
{
	int a = 10;
	//创建整型指针变量pa并初始化
	int* pa = &a;//将变量a的地址存放到指针变量pa中
	
	return 0;
}

上例中,我将变量a的地址存放到指针变量pa中。pa的类型是int*(整形指针)。由此我们可以得到
指针的定义方式是:

type(类型) + * +prt_name(指针变量名字)

其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。

让我们来看看第二个例子

int main()
{
	int a = 10;
	char* pc = &a;
	//char*类型的指针能否得存放int类型变量的地址呢?
	return 0;
}

答案是:能够存放。因为一个指针变量在32位环境下都是占4个字节。这里不要门缝里看char*类型指针,把它给看遍了。这也是我们需要注意的一个小细节。

既然指针变量的大小在32位平台下都是4个字节,那指针变量的类型存在的意义是什么呢?

2.2.指针类型

这里我依旧是通过两段代码进行举例

//例一
#include <stdio.h>

int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return  0;
}

在这里插入图片描述
通过上面的例子我们可以发现,指针的类型决定了指针+1后访问的步长。int类型指针+1向后移动了4个字节,char类型指针+1向后移动了1个字节。同理可得short类型指针+1向后移动2个字节,float类型指针+1向后移动4个字节。

总结

指针变量的类型决定了指针的步长(向前或向后走一步的距离)。

//例二

int main()
{
	int a = 0x11223344;//0x表示十六进制类型

	char* pc = (char*)&a;
//a是整型,需要对&a进行强制类型转换成char*后编译器才不会报错
	int* pi = &a;

	*pc = 0;   
	*pi = 0;   

	return 0;
}

先调试这段代码
在这里插入图片描述

让我们看看pc = 0的效果
在这里插入图片描述
接下来是
pi = 0;这句代码的效果
在这里插入图片描述
通过调试的内存窗口,可以发现当对char类型指针变量进行解引用操作后,访问的权限是1个字节,而对int类型指针变量进行解引用访问操作,访问的权限是4个字节。所以指针变量的类型其实是由意义的。

总结:

指针变量的类型决定了解引用操作后的指针的访问权限。int类型指针变量解引用操作访问权限是4个字节,char类型指针变量解引用操作访问权限是1个字节

3.野指针

3.1.什么视野指针?

野指针的概念

野指针指的是指向不可知的指针变量(随机的、不正确的、不可控的)。

3.2.野指针成因

1.指针变量未进行初始化

int main()
{
	int* pa;
	*pa = 20;
	return 0;
}

上例中,指针变量pa未进行初始化,所以pa是一个野指针,对pa进行仅引用操作是极其危险的,因为pa的指向是不可知的。

指针越界访问

int main()
{
	int arr[5]={1,2,3,4,5};
	int *p = arr;
	int i = 0;
	
	for(i = 0; i <= 5; i++)
	{
		*(p++) = i;
	}
	
	return 0;
}

当运行这段代码后,编译器会进行报错
在这里插入图片描述

这里编译器告诉我们因为指针访问越界,数组arr被破坏了。所以当p超出数组的合理范围后,p就是一个野指针,对野指针进行解引用操作是很危险的。

3.3.规避野指针

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

4.指针运算

4.1.指针±整数

代码举例如下

#include<stdio.h>

int main()
{
	int arr[5] = { 0 };
	int* pa = arr;
	int i = 0;
	//将数组内容赋值成1~5
	for (i = 0; i < 5; i++)
	{
		*pa = i + 1;
		pa = pa + 1;//指针加整数
	}
	//打印数组内容
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

4.2.指针-指针

指针-指针的前提条件是,两个指针变量指向同一块空间。

int main()
{
	int arr[10] = { 0 };
	int* pa = &arr[9];
	int* pb = &arr[0];
	//此时pa和pb都是指向数组arr的内容
	printf("%d\n",pa - pb);

	return 0;
}

在这里插入图片描述

指针-指针得到的绝对值是,两个指针之间相同类型的元素的个数。

下面我通过指针-指针的方式模拟实现库函数strlen

#include<assert.h>

size_t MyStrlen(const char* str)
{
	assert(str != NULL);

	char* start = str;//记录初始地址

	while (*str++)
	{
		;
	}
	//通过指针-指针返回字符串长度
	return str - start - 1;

}

int main()
{
	char str[] = "abcde";
	
	printf("%d\n",MyStrlen(str));

	return 0;
}

4.3指针的关系运算

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

在这里插入图片描述
将上边的代码稍作修改

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

在这里插入图片描述

代码修改后,看起来是更易读了,可是我们应该避免这样写代码。虽然上面的代码在市面上绝大部分编译器都可以正常运行,但是C语言标准中并不能保证它是对的。

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

5.指针与数组

指针和数组是不同的对象。指针是一种用来存放地址的变量,大小是4/8个字节。数组是一组相同类型元素的集合,可以放多个元素,大小取决于元素个数和元素类型的。数组的数组名是数组首元素的地址,地址是可以存放在指针变量中的。可以通过指针来访问数组。

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

在这里插入图片描述
由上例可知,数组名本质是首元素的地址(排除&数组名和sizeof(数组名)这两种情况)。既然数组名是数组首元素的地址,我们不妨将数组名存入一个指针变量中,再通过这个指针变量来遍历整个数组。

#include<stdio.h>

int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
    int* p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p + i);
    }
    return 0;
}

在这里插入图片描述
可以发现其实 p+i 是完全等价于 arr[ i ]的。接下来试试通过指针运算来便利整形数组并且打印数组

int main()
{
	int arr[10] = {0};
	int* pa = arr;
	int sz = sizeof(arr)/sizeof(arr[0]);
	int i = 0;
//改变数组内容1~10
	for(i = 0; i < sz; i++)
	{
		*pa = i + 1;
		pa++;
	}
	pa = arr;//重新指定指针的位置以避免越界
//打印数组内容
		for(i = 0; i < sz; i++)
	{
		printf("%d ",*pa);
		pa++;
	}

	return 0;
}
int main()
{
	int arr[10] = {0};
	int* pa = arr;
	int sz = sizeof(arr)/sizeof(arr[0]);
	int i = 0;
//改变数组内容1~10
	for(i = 0; i < sz; i++)
	{
		*(pa+i) = i + 1;
	}

//打印数组内容
		for(i = 0; i < sz; i++)
	{
		printf("%d ",*(pa+i));
	}

	return 0;
}

补充:
[ ]下标访问操作符两边,arr 和 i 是下标访问操作符的两个操作数。[ ]下标访问操作符是支持交换律的,所以 arr[ i ] 等价于 i[arr]。

6.二级指针

6.1.什么是二级指针

指针变量是用来存放地址的,那指针变量的地址该存放到哪里呢?这就要引出二级指针这个概念了。二级指针是用来存放指针变量的地址的。

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	return 0;
}

在这里插入图片描述

6.2.二级指针的运算

通过 * 解引用操作符对二级指针进行解引用操作。

int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 20;//*(*ppa) =*(pa) = a; 
	//通过对* *ppa进行解引用操作得到pa,再通过对pa进行解引用操作得到a
	//**ppa等价于a
	printf("%d\n", a);
}

在这里插入图片描述

7.指针数组

指针数组是什么呢?是数组?还是指针?答案是:指针数组是用来存放指针的数组。通过前面的学习,我们了解到的数组类型有字符数组、整型数组、单精度浮点型数组等。

int arr[] = {1,2,3,4,5};
char ch[] = {'a', 'b', 'c'};

在这里插入图片描述

int* arrp[] = {0x0012ff40, 0x0012ff41, 0x0012ff42};

在这里插入图片描述

下面我通过一个指针数组加三个一维数组来模拟实现二维数组

#include<stdio.h>

int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[3] = { 4,5,6 };
	int arr3[3] = { 7,8,9 };

	int* arrp[3] = { arr1,arr2,arr3 };

	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ",arrp[i][j]);//arr[i]相当于是arr[列号]。通过指针运算,就可以遍历整个数组。
		}
	printf("\n");
	}

	return 0;
}

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

通过了访问指针数组arrp中存放的三个整形数组的首元素地址,然后再通过指针运算,就可以模拟实现一个二维数组了。

总结

总有人说指针多难多难,在我看来只要搞清楚指针的概念以及指针的用法,理解指针也不是特别难的。这只需要我们多去思考和总结。学习没有捷径,只有你真的认真学了才能够真正的掌握知识。最后,也希望看完本篇文章的你有所收获,有什么问题也欢迎在评论区进行讨论。

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

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

相关文章

【App自动化测试】(十四)Android WebView测试方法

目录1. webview 架构与分析方法1.1 webview与Hybrid开发1.1.1 移动APP三种开发模式1.1.2 native原生开发1.1.2.1 native原生开发说明1.1.2.2 native原生开发组件1.1.3 Hybrid混合开发webview展示1.1.3.1 Hybrid混合开发中的app webview组件1.1.3.2 webview开发代码展示1.1.3.3 …

Reids实战——优惠券秒杀(全局唯一ID生成策略)

1 全局唯一ID生成策略 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a; 1. id的规律性太明显 2. 会受单表数据量的限制 全局ID生成器&a…

Android APP全局黑白化实现方案

在清明节时各大APP都会进行黑白化处理&#xff0c;当时在接到这个需求的时候感觉好麻烦&#xff0c;是不是又要搞一套皮肤&#xff1f; 然而在一系列搜索之后&#xff0c;找到了两位大神&#xff08;鸿洋、U2tzJTNE&#xff09;的实现方案&#xff0c;其实相当的简单&#xff…

NoSQL数据库之MongoDB

一、NoSQL数据库背景 传统关系型数据库遇到的问题 2008 年左右&#xff0c;网站 、 论坛、社交网络开始高速发展&#xff0c;传统的关系型数据库在存储及处理数据的时候受到了很大的挑战 &#xff0c;其中主要体现在以下几点&#xff1a; 难以应付每秒上万次的高并发数据写入…

hiveSql 相互关注问题

hiveSql 相互关注问题说明需求分析优化实现最后说明 普遍社交软件上会有关注功能&#xff0c;如何知道自己的关注是否也是关注了自己呢&#xff1f; 需求 求关注结果数据中&#xff0c;相互关注的用户对。 数据如下&#xff1a; follow表&#xff1b;from_user&#xff1a;关…

JMeter 扩展开发:扩展 TCP 取样器

前言 对基于 TCP/IP 协议的套接字应用进行性能测试是非常常见的测试场景。JMeter 提供的“TCP 取样器”大部分情况下可以满足测试的需求&#xff0c;但是也有它的局限性。如果希望实现更灵活的 TCP 套接字测试方式&#xff0c;可以通过对 JMeter 内置的 TCP 取样器进行扩展开发…

在数据工厂中刷新PowerBI数据集

一开始因为部门使用的是坚果云来同步资料&#xff0c;而坚果云同步SSIS工程总是会报错&#xff0c;一气之下就把所有的SSIS迁移到了Azure云上&#xff0c;本来部门使用的就是Azure SQL&#xff0c;所以迁到Data Factory&#xff08;数据工厂&#xff09;也不需要过多的配置。 …

传输线理论基础01——相关定义、信号速率、分布参数与电报方程

前言一直以来都对高频信号、信号完整性、传输线、分布参数这些概念似懂非懂&#xff0c;上学时没学过相关课程&#xff0c;这导致我对高频电路和PCB理解较差&#xff0c;这里新开一个专栏&#xff0c;补齐这方面知识。 一. 传输线相关定义1.1 传输线定义 传输线指的是传输信号…

PyTorch学习笔记-神经网络模型搭建小实战

1. torch.nn.Sequential torch.nn.Sequential 是一个Sequential 容器&#xff0c;能够在容器中嵌套各种实现神经网络中具体功能相关的类&#xff0c;来完成对神经网络模型的搭建。模块的加入一般有两种方式&#xff0c;一种是直接嵌套&#xff0c;另一种是以 OrderedDict 有序…

LabVIEW创建类 1

LabVIEW创建类 1 通过创建LabVIEW类&#xff0c;可在LabVIEW中创建用户定义的数据类型。LabVIEW类定义了对象相关的数据和可对数据执行的操作&#xff08;即方法&#xff09;。通过封装和继承可创建模块化的代码&#xff0c;使代码更易修改而不影响应用程序中的其它代码。 在…

Terraform 华为云最佳实践

目录划分如下&#xff1a;首先是环境&#xff0c;分为网络和service。global是全局的配置&#xff0c;也就是backend的配置&#xff0c;这次使用s3的存储作为backend的存储。最后就是模块做了一些封装。 在global里面的backend里面的main.tf去创建s3的存储。华为云支持s3存储&a…

[附源码]Python计算机毕业设计Django病房管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

RK3588平台开发系列讲解(USB篇)USB 外设 CONFIG

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、 Mass Storage Class CONFIG二、USB Serial Converter CONFIG三、USB HID CONFIG四、USB Net CONFIG五、USB Camera CONFIG六、USB Audio CONFIG七、 USB HUB CONFIG沉淀、分享、成长,让自己和他人都能有所收获!…

PG::Seppuku

nmap -Pn -p- -T4 --min-rate1000 192.168.81.90 nmap -Pn -p 21,22,80,139,445,7080,7601,8088 -sCV 192.168.81.90 查看7601端口的页面 对路径进行爆破 在/secret路径下得到了用户名和一个密码字典 尝试ssh爆破 得到密码 eeyoree ssh登录 这里使用sudo -l&#xff0…

FineReport表格软件- 计算操作符说明

1. 概述 FineReport 中使用函数需要用到很多的操作符。 操作符不仅包含很多运算符&#xff0c;还包括一些报表特有的操作符。 FineReport 11.0 优化了公式 2. 运算符类型 运算符用于指定要对公式中的元素执行的计算类型。有默认计算顺序&#xff0c;但可以使用括号更改此顺序…

企业表格软件-FineReport 数组函数概述

1. ADD2ARRAY ADD2ARRAY(array, insertArray, start)&#xff1a;在数组 array 的第 start 个位置插入 insertArray 中的所有元素&#xff0c;再返回该数组。 示例&#xff1a; ADD2ARRAY([3, 4, 1, 5, 7], [23, 43, 22], 3)返回[3, 4, 23, 43, 22, 1, 5, 7]。 ADD2ARRAY([…

将 AWS IAM Identity Center (SSO) SAML 与 Amazon OpenSearch Dashboard集成

Amazon OpenSearch Amazon OpenSearch Service 是一项 AWS 托管服务&#xff0c;可以让您运行和扩展 OpenSearch 集群&#xff0c;而不必担心管理、监控和维护您的基础设施&#xff0c;或者不必在操作 OpenSearch 集群方面积累深入的专业知识。 基于 SAML 的 OpenSearch Dash…

Json用法总结

1、忽略json JsonIgnoreProperties(value{“addressId”}) JSONField(serializefalse) JsonIgnore 2、 JsonFiled JsonProperty XStreamAlias Builder.Default 网上可以查询下相关资料 3、 JSON.parseObject(response, ***Response.class) JSONObject.parseObject(response, **…

LockSupport的使用

参考链接&#xff1a; LockSupport使用场景及原理详解 AQS的引入 LockSupport的使用 LockSupport是一个工具类&#xff0c;提供了基本的线程阻塞和唤醒功能&#xff0c;它是创建锁和其他同步组件的基础工具&#xff0c;内部是使用sun.misc.Unsafe类实现的。LockSupport和使用…

android分区概述

Android 设备包括几个分区&#xff0c;它们在启动过程中提供不同的功能。 1、 标准隔断 注意&#xff1a;支持无缝更新的设备每个分区需要一个插槽用于boot 、 system 、 vendor和radio 。 boot分区。此分区包含内核映像&#xff0c;并使用mkbootimg创建。您可以使用虚拟分区…