C语言 指针

news2024/11/16 18:01:39

C语言 指针

  • 引言
    • 1. 什么是指针
    • 2. 简单认识指针
    • 3. 取地址符 & 和解引用 * 符
  • 一、指针与内存
  • 二、指针类型的存在意义
    • 1. 指针变量的大小
    • 2. 指针移动
    • 3. 不同指针类型的解引用
  • 三、指针运算
    • 1. 指针加减整数
      • 程序清单1
      • 程序清单2
    • 2. 指针 - 指针
    • 3. 指针关系运算
  • 四、二级指针
  • 五、野指针
    • 程序清单1
    • 程序清单2
    • 如何避免野指针问题
  • 六、字符指针
    • 程序清单1
    • 程序清单2

引言

1. 什么是指针

1. 指针是内存中一个最小单元的编号,通俗的说,指针也就是地址。

2. 平时口语中说的指针,通常指的是指针变量,即存储一块内存地址的一个变量。

3. 在 32位 的机器上,地址是 32个 0或1 组成二进制序列,此时地址就得用 4 个字节的空间来存储,所以此时一个指针变量的大小就应该是 4 个字节。同样地,在 64位 的机器上,地址是 64个 0或1 组成二进制序列,此时地址就得用 8 个字节的空间来存储,所以此时一个指针变量的大小就应该是 8 个字节。

综上所述,指针即指针变量,它占用内存大小要么为 4,要么为 8.

2. 简单认识指针

经过上面的介绍,我们就来简单地认识下图的指针。

下面的两行代码,我画了一幅图来解释它。我们可以说,指针变量 pa 指向 整型变量 a. 也可以说,指针变量 pa 存储了变量 a 的地址。

备注: 0x11332244 是 a 的十六进制地址,0x00001111 是 pa 的十六进制地址,这两者不要混淆了。因为指针本质上也是一个变量,既然是变量,那么它在创建的时候,底层就会为其开辟内存。

1-1

3. 取地址符 & 和解引用 * 符

int a = 10;
int* pa = &a; // 将 a 的地址赋给 pa
*pa = 20; // 将 a 的值改为 20

① int* 表示 pa 是一个整型指针变量。
② *pa 表示解引用 指针变量 pa,*pa 就等价于 a.
③ 通俗的来说,解引用符和取地址符是可以充当 " 抵消的作用 " 。

*pa <==> *(&pa) <==> a

一、指针与内存

指针就是地址,有了地址,就能帮助我们快速地找到一块内存空间。

程序清单:

#include <stdio.h>

int main() 
{
	int a = 10;
	int* pa = &a; // 取出 a 的地址赋值给指针变量 pa
	*pa = 20; // *pa == a
	printf("%d\n", a);
	return 0;
}

// 输出结果:20

在上面的程序中,&a 表示取出 int变量 a 的地址 (取出的是 变量a 的第一个字节地址);*pa 表示解引用 pa,*pa 就等价于 a.

如下图所示:(假设虚拟地址空间为 32位)

1-2

注意事项:

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。在 C语言中,每创建一个变量就会在底层开辟地址。

① 内存会被划分为小的内存单元,一个内存单元的大小是1个字节。
② 每个内存单元都有编号,这个编号也被称为:地址 / 指针。
③ 地址 / 指针可以存放在一个变量中, 这个变量称为指针变量,指针变量也是一个变量,它也有自己的地址。
④ 通过指针变量中存储的地址,就能找到指针指向的空间。

二、指针类型的存在意义

1. 指针变量的大小

程序清单:

#include <stdio.h>

int main() 
{
	int a = 10;
	char ch = 'a';
	double d = 3.14;

	int* pa = &a;
	char* pc = &ch;
	double* pd = &d;

	printf("%d\n", sizeof(pa)); //4
	printf("%d\n", sizeof(pc)); //4
	printf("%d\n", sizeof(pd)); //4
	return 0;
}

结论:

指针变量是用来存放地址的。所以,地址的存放需要多大空间,指针变量的大小就应该是多大。

① 32位 机器,支持 32位 虚拟地址空间,其产生的地址就是 32位,所以此时指针变量就需要 32位 的空间存储,即 4字节。
② 64位 机器,支持 64位 虚拟地址空间,其产生的地址就是 64位,所以此时指针变量就需要 64位 的空间存储,即 8字节。

2. 指针移动

程序清单:

#include <stdio.h>

int main() {

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

	return 0;
}

输出结果:

1-3

总结:

从输出结果来看,指针类型决定了指针向前或者向后走一步有多大距离。当一个整型指针进行挪动的时候,移动 4 个字节;当一个字符指针进行挪动的时候,移动 1 个字节。这是一个很重要的知识点,因为这决定了一个指针一次性访问多少个字节。

1-4

3. 不同指针类型的解引用

① 对一个整型指针变量解引用后,并为之赋值。

1-5

② 对一个字符指针变量解引用后,并为之赋值。

1-6

总结:

从输出结果来看,指针类型也决定了指针进行解引用时能操作几个字节。当对一个整型指针变量解引用后,能操作 4 个字节;当对一个字符指针变量解引用后,能操作 1 个字节。

三、指针运算

1. 指针加减整数

程序清单1

#include <stdio.h>

int main() {

	int a = 3;
	int* pa = &a;

	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	printf("%p\n", pa - 1);

	return 0;
}

输出结果:

1-7

程序清单2

#include <stdio.h>

void print(int arr[]) {
	for (int i = 0; i < 10; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main() {

	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	print(arr);
	
	for (int i = 0; i < 10; i++) {
		*p = 0; // 将数组的每一个元素都设置成 0
		p++; 	// 将指针往后挪动一个元素
	}
	print(arr);

	return 0;
}

输出结果:

1-8

2. 指针 - 指针

程序清单:

#include <stdio.h>

int main() {

	int arr[10] = { 0 };
	int* p = NULL;
	
	printf("%d\n", &arr[8] - &arr[1]);
	printf("%d\n", &arr[1] - &arr[8]);
	printf("%d\n", &arr[8] - p);

	return 0;
}

输出结果:

1-9

注意事项:

① 从上面的输出结果来看," 指针 - 指针 " 运算适用于两个指针指向同一块空间才有意义。由于数组的内存地址是连续的,且由低到高变化,所以 " 指针 - 指针 " 运算就相当于数组下标之差。

② " 指针 - 指针 " 也可以理解为两个指针之间隔了多少个元素,其差值结果是一个数值,而不是字节。 这一点不能单纯的与指针变量 " 所占用内存的大小之差 " 的概念弄混淆了。

3. 指针关系运算

程序清单:

#include <stdio.h>

int main() {

	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };

	if (arr[1] >= arr[3]) {
		printf("haha\n");
	}
	else {
		printf("hehe\n");
	}

	// 指针关系运算
	// 随着数组下标增长,数组的地址由低到高变化
	if (&arr[1] >= &arr[3]) {
		printf("haha\n");
	}else {
		printf("hehe\n");
	}

	return 0;
}

// 输出结果:
// haha
// hehe

四、二级指针

二级指针即指针的指针,它存放的是指针变量的地址。一级指针的取地址、解引用等操作,也可以类比到此处的二级指针。

程序清单:

#include <stdio.h>

int main() {

	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	
	**ppa = 20;
	printf("%d\n", a);

	return 0;
}

// 输出结果:20

五、野指针

野指针:指针指向的位置是不可知的、随机的、不正确的、没有明确限制的。

程序清单1

#include <stdio.h>

int main()
{
	int* p;	 //局部变量指针未初始化,默认为随机值
	*p = 20;

	return 0;
}

程序清单2

指针访问数组越界。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 20; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}

如何避免野指针问题

1. 当指针在定义时,不知道指向谁,初始化为 NULL.
2. 预防指针越界。
3. 使用指针前,进行 assert 断言。

#include <stdio.h>
#include <assert.h>

int main() {

	int a = 10;
	int* pa = &a;
	int* p = NULL;

	assert(pa != NULL);
	assert(p != NULL); // 编译器直接提示报错信息

}

注意事项:

① assert 在使用时需要引入头文件 <assert.h>
② 如果 assert 括号内的条件为真,则程序正常执行;如果它括号内的条件为假,则会直接报错,并提示错误信息,精确到行。

六、字符指针

字符指针通常与字符串相关联,这里需要明确的是,字符指针通常存储的是字符串中的首个字符的地址,而不是整个字符串的地址。

程序清单1

#include <stdio.h>

int main() {

	char* p = "abcdef"; 

	// p 指向字符串的第一个字符
	printf("%c\n", *p);
	printf("%s\n", p);

	return 0;
}

// 输出结果:
// a
// abcdef

注意事项:

需要明确: 指针 p 指向 " abcdef " 的第一个字符 ’ a ’ 的地址,而不是整个字符串的地址。或者说,指针 p 中存放的字符 ’ a ’ 的地址。

② 针对上面的第二个输出结果,为什么对一个字符指针变量打印就能够输出整个字符串呢?原因在于:指针 p 指向第一个字符,就能够找到整个字符串后面的所有字符。这和顺藤摸瓜是一个道理。

③ 我们日常所说的字符串其实是一个常量字符串,放在常量区,不可被修改。所以当我们创建一个字符指针,用于指向一个字符串时,就可以将这个指针变量添加 const 修饰符,这样更加规范。

const char* p = "abcdef";

程序清单2

#include <stdio.h>

int main() {

	char* p1 = "abcdef";
	char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	if (p1 == p2) {
		printf("p1 == p2\n");
	}else {
		printf("p1 != p2\n");
	}

	if (arr1 == arr2) {
		printf("arr1 == arr2\n");
	}else {
		printf("arr1 != arr2\n");
	}

	return 0;
}

// 输出结果:
// p1 == p2
// arr1 != arr2

注意事项:

① 分析第一个输出结果,当我们创建两个字符指针时,它们指向的都是字符串的首字符地址,而字符串又是常量字符串,不可被更改,所以,p1 和 p2 都指向同一份 ’ a ’ 的地址。

② 分析第二个输出结果,当我们创建两个字符数组时,同样的常量字符串中的字符被放入了不同的数组,数组在栈区开辟了新的内存,所以两个数组首元素的地址是不同的。

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

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

相关文章

这个双11,我薅了华为云会议的羊毛

文章目录前言华为云会议悄然助力各行各业和其他云会议产品相比&#xff0c;华为云会议优势是什么&#xff1f;云端一体线下会议室和云会议互通专业会管与会控能力更安全华为云会议有哪些 AI 能力&#xff1f;华为云会议入门有多简单&#xff1f;下载步骤如下安装加入会议预约会…

原生js 之 (DOM操作)

Web API Web API是浏览器提供的一套操作浏览器功能和页面元素的API(BOM和DOM) JavaScipt由三部分构成 ECMAScript、DOM和BOM。 BOM浏览器对象模型&#xff0c;提供了与浏览器的交互方法和接口&#xff1b; DOM 文档对象模型&#xff0c;提供了处理网页内容、结构 、样式的方法…

【数据结构】图的遍历

深度优先遍历 深度优先遍历思想 对于图&#xff1a;选中一个结点&#xff0c;访问和其相邻的、未被访问过的点&#xff0c;全部访问完毕后回退到上一个结点&#xff0c;直至全部结点访问完毕&#xff0c;类似于图的先序遍历&#xff0c;如有邻接表&#xff0c;则按邻接矩阵的顺…

一文熟悉 Go 的基础语法和基本数据类型

一文熟悉 Go 的基础语法和基本数据类型前言Hello&#xff0c;World&#xff01;有关 main 函数的一些要点关键字package声明引入基本数据类型整形数据类型有符号整数类型无符号整数类型其他整数类型浮点数据类型字符类型布尔类型字符串类型基本数据类型的默认值常量和变量声明结…

swift指针内存管理-指针类型使用

为什么说指针不安全 我们在创建一个对象的时候&#xff0c;是需要在堆上开辟内存空间的 但是这个内存空间的声明周期是有限的 也就意味着如果使用指针指向这块内存空间&#xff0c;当这块内存空间的生命周期结束&#xff08;引用计数为0&#xff09;&#xff0c;那么当前的指针…

mac m1 配置goland debbug

大概率无法使用goland的debug功能&#xff0c;如果自己安装没选对路径&#xff0c;也无法使用。原因是&#xff1a; go env 配置不对&#xff0c;需要指向 ARM64&#xff1b; dlv版本不对&#xff0c;需要使用 arm64 系列&#xff1b; dlv路径不对&#xff0c;需要使用 macarm…

Linux服务器使用git clone命令时报错的解决方案

在往GitHub上上传项目时&#xff0c;使用git clone xxxxx.git时候报错&#xff1a; “gnutls_handshake() failed: the TLS connection was non-properly terminated” 由系统的 git 默认使用的 libcurl4-gnutls-dev 造成&#xff0c;可以使用openssl解决. 但是这个过程也很多…

2022亚太B题赛题分享

高速列车的优化设计 2022年4月12日&#xff0c;中国高铁复兴CR450多机组成功实现单列列车速度435 km/h&#xff0c;相对速度870 km/h&#xff0c;创造了高铁多机组列车穿越明线和隧道速度的世界纪录。新一代标准动车组“复兴”是中国自主研发的具有全知识产权的新一代高速列车。…

Doris 5 处理Sentinel-1 生成干涉图 interferogram

Doris 5 处理Sentinel-1 Step 0 创建文件夹 首先创建一个文件夹用来准备数据处理&#xff0c;例如 “Doris_test1”&#xff0c;然后在该文件夹下创建五个文件夹&#xff0c;用来存放数据&#xff0c;例如 AOI (研究区的shp文件), archive_data (已下载的Sentinel-1 SLC文件&…

手部IK,自制动画,蒙太奇——开门手臂自动弯曲、靠墙手自动扶墙

开门手臂自动弯曲 实现效果&#xff1a;人物做出抬手的开门动画时&#xff0c;若手臂碰到静态网格物体&#xff0c;拳头不会穿过物体&#xff0c;而是会产生手臂IK弯曲动画效果。 重要参考资料&#xff1a; 学习UE4动画蓝图&#xff1a;配置手部IK_YakSue的博客-CSDN博客_ue…

ImageProvider工作流程和AssetImage 加载流程

Flutter 学习&#xff1a;ImageProvider工作流程和AssetImage 的自动分辨率适配原理https://cloud.tencent.com/developer/article/1748045上面流程为ImageProvider工作流程细节&#xff0c;作者已经写的很详细了&#xff0c;非常受用&#xff0c;现在接着上面作者内容讨论下As…

【32-业务开发-基础业务-规格参数-保存数据-查询数据-更新操作之数据回显展示-更新操作-前后端项目交互整合与测试-总结收获】

一.知识回顾 【0.三高商城系统的专题专栏都帮你整理好了&#xff0c;请点击这里&#xff01;】 【1-系统架构演进过程】 【2-微服务系统架构需求】 【3-高性能、高并发、高可用的三高商城系统项目介绍】 【4-Linux云服务器上安装Docker】 【5-Docker安装部署MySQL和Redis服务】…

1532_AURIX_TriCore内核架构_中断

全部学习汇总&#xff1a; GreyZhang/g_tricore_architecture: some learning note about tricore architecture. (github.com) 中断一直是我想了解关注的一个功能模块&#xff0c;因为感觉不同的芯片上这部分的设计差异比较大。而这部分也跟我们嵌入式软件的设计模式直接相关。…

使用HikariCP连接池常用配置讲解及注意事项

使用HikariCP连接池常用配置讲解及注意事项常遇到的几种错误Possibly consider using a shorter maxLifetime valueConnection is not available, request timed out after xxxxxmsNo operations allowed after connection closed常见配置及注释说明&#xff0c;可以使用并根据…

每日刷题2——指针概念

更新不易&#xff0c;麻烦多多点赞&#xff0c;欢迎你的提问&#xff0c;感谢你的转发&#xff0c; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我…

Twins: Revisiting the Design of Spatial Attention in Vision Transformers

Twins: Revisiting the Design of Spatial Attention in Vision Transformers一、引言二、Twins-PCPVT三、Twins-SVT四、实验五、消融实验文章链接: https://arxiv.org/abs/2104.13840代码链接: https://github.com/Meituan-AutoML/Twins一、引言 在本文中&#xff0c;重新审…

基于Matlab计算经典CFAR阈值

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

基于开源 PolarDB-X 打造中正智能身份认证业务数据基座

一、公司及业务介绍 中正智能是全球领先的生物识别和身份认证公司之一。我们曾负责公安部指纹算法国家标准的起草、编写&#xff0c;具备从算法、终端、平台、设计、生产、交付全域自研的能力&#xff0c;拥有多项自主知识产权的产品&#xff0c;并积极与高校合作开展基础研发。…

【Rust 指南】并发编程|无畏并发的原因

文章目录前言1、线程1.1、通过 spawn 创建新线程1.2、join 方法2、move 强制所有权迁移3、使用消息传递跨线程传递数据3.1、Send 方法3.2 、Sync 方法前言 安全高效的处理并发是 Rust 诞生的目的之一&#xff0c;主要解决的是服务器高负载承受能力。 并发&#xff08;concurren…

石家庄正定县恢复种植 国稻种芯·中国水稻节:河北绘就画卷

石家庄正定县恢复种植 国稻种芯中国水稻节&#xff1a;河北绘就画卷 新华社记者 杨世尧 摄 河北日报 通讯员张 晓峰 摄影报道 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会…