C语言——指针——第1篇——(第19篇)

news2024/12/28 22:09:48

坚持就是胜利

文章目录

  • 1.指针是什么
  • 2.指针和指针类型
    • (1)指针 + - 整数
    • (2)指针 的 解引用
  • 3.野指针
    • (1)野指针成因
      • 1.指针未初始化
      • 2.指针越界访问
      • 3.指针指向的空间释放
    • (2)如何规避野指针
      • 1.指针初始化
      • 2.小心指针越界
      • 3.指针指向的空间被释放时,及时置NULL
        • `NULL 讲解`
      • 4.避免返回局部变量的地址
      • 5.指针使用之前检查有效性
  • 4.指针运算
    • (1)指针 + - 整数
    • (2)指针 - 指针
      • (1) 指针 - 指针 得到的数值的绝对值:是指针和指针之间的元素个数
      • (2) 指针 - 指针 相减的前提条件是:指针和指针指向了同一块空间。
      • (3)用函数写 计数器
    • (3)指针的关系运算

1.指针是什么

在计算机科学中,指针是编程语言中的一个对象,利用指针,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为”指针“。意思是:通过它能找到以它为地址的内存单元。

指针是个变量,存放内存单元的地址(编号)。指针 = 地址 = 编号

# include <stdio.h>

int main()
{
	int a = 10;  //在内存中开辟一块空间
	int* p = &a;  //这里我们对变量 a ,取出它的地址,可以使用 & 操作符
	              //将 a 的地址存放在 p 变量中,p 就是一个指针变量
	return 0;
}

总结:指针就是变量,用来存放地址的变量。(存放在 指针中的值 都被当成 地址 处理)

回答以下两个问题:
1.一个小的单元到底是多大?
答:1 个字节,1 Byte  
2.如何编址?
答:1 个字节给 1 个对应的地址(见以下内容)

经过仔细的计算和权衡,我们发现 一个字节 给 一个对应的地址 是比较合适的。
对于 32位 的机器,假设有 32根 地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)。
那么 32 根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001

11111111 11111111 11111111 11111111
这里就有 2 的 32 次方个地址。

每个地址标识 1 个字节,那我们就可以给 4GB的空闲进行编址。

2^32Byte = 2^32/1024KB= 2^32/1024/1024MB = 2^32/1024/1024/1024GB = 4GB)

同样的方法,那64位机器,就非常非常大了。

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

总结:
1.指针是用来存放地址的,地址是唯一标示一块空间的。
2.指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节。

2.指针和指针类型

#include <stdio.h>

int main()
{
	char* pc = NULL;  //用来存放 char 类型变量的地址
	int* pi = NULL;   //用来存放 int 类型变量的地址
	short* ps = NULL;
	long* pl = NULL;
	float* pf = NULL;
	double* pd = NULL;
	printf("%d\n", sizeof(pc));  //4
	printf("%d\n", sizeof(pi));  //4
	printf("%d\n", sizeof(ps));  //4
	printf("%d\n", sizeof(pl));  //4 
	printf("%d\n", sizeof(pf));  //4
	printf("%d\n", sizeof(pd));  //4
	return 0;
}

(1)指针 + - 整数

总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	char* pc = arr;
	printf("%p\n", p);          //0095F9C0
	printf("%p\n", p + 1);      //0095F9C4
	printf("%p\n", pc);         //0095F9C0
	printf("%p\n", pc + 1);     //0095F9C1
	return 0;
}
#include <stdio.h>

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

	printf("%p\n", &n);      //010FF890
	printf("%p\n", pc);      //010FF890
	printf("%p\n", pc + 1);  //010FF891
	printf("%p\n", pi);      //010FF890
	printf("%p\n", pi + 1);  //010FF894
	return 0;
}

(2)指针 的 解引用

总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如:char* 的指针解引用就只能访问 1 个字节,而 int* 的指针的解引用就能访问 4 个字节。

#include <stdio.h>

int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	int* pi = &n;  
	*pc = 0;     //重点在调试的过程中,观察内存的变化
	             //由于是 char* 类型,只能改变 1 个字节的内存大小
	*pi = 0;     //重点在调试的过程中,观察内存的变化
				 //由于是 int* 类型,则可以改变 4 个字节的内存大小
	return 0;
}

在这里插入图片描述

由于是 char* 类型,只能改变 1 个字节的内存大小

在这里插入图片描述

由于是 int* 类型,则可以改变 4 个字节的内存大小

在这里插入图片描述

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	char* p = arr;    //由于 char* ,只能一个字节一个字节去访问内存

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = 1;
	}
	return 0;
}

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

3.野指针

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

(1)野指针成因

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 <= 11; i++)
	{
		*(p+i) = i;  //这样做不会改变  p 中的地址
	}
	
	int j = 0;
	for (j = 0; j <= 11; j++)   //arr数组只有10个空间,已经超出了
	{
		printf("%d\n", *(p + j));
	}
	return 0;
}
#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 9; i++)
	{
		*(p++) = i;  //这样做改变了 p 中的地址
	}
	p = arr;  //所以要将 p 中的地址保存为 arr 的 初始地址
	int j = 0;
	for (j = 0; j <= 9; j++)
	{
		printf("%d\n", *(p + j));
	}
	return 0;
}
#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p = i;
		p++;
	}
	int j = 0;
	for (j = 0; j < 10; j++)
	{
		printf("%d\n", arr[j]);  //直接输出数组中的元素
	}
	return 0;
}

3.指针指向的空间释放

原先指针是指向这块空间的,但是后来这块空间被释放了。
这里放在动态内存开辟的时候讲解,这里可以简单提示一下。

//当程序运行完 int* p = test();时,由于 a 只是局部变量,它的空间就被释放了,
//但是赋给 p 的地址却是已经被释放了的空间地址,此时,p 就成了“野指针”

#include <stdio.h>

int* test()   //由于返回的是 &a ,所以返回类型是 int*    //int 表明指针所指对象的类型是 int 类型    //* 表明是指针
{
	int a = 10;
	return &a;  //变量 a 的空间,是进入函数创建,出函数还给操作系统。
}

int main()
{
	int* p = test();   //当程序运行完 int* p = test();时,由于 a 只是局部变量,它的空间就被释放了,但是赋给 p 的地址却是已经被释放了的空间地址,此时,p 就成了“野指针”
	printf("%d\n",*p);
	return 0;
}

虽然最后程序的返回值是正确的,得到10,但是这种访问是非法的。

在这里插入图片描述

(2)如何规避野指针

1.指针初始化

(1)明确知道指针应该初始化为谁的地址,就直接初始化。
(2)不知道指针初始化为什么值时,暂时初始化为 NULL 。

2.小心指针越界

3.指针指向的空间被释放时,及时置NULL

当前不知道 指针p 应该初始化为什么地址的时候,直接初始化为 NULL

NULL 讲解

通过查看 NULL 的定义:

#define NULL 0
else if
#define NULL ((void *)0)

本质:NULL 就是 0
#include <stdio.h>

int main()
{
	int* p = NULL;  //当前不知道 p 应该初始化为什么地址的时候,直接初始化为 NULL
	                //p 是一个空指针,没有指向任何有效的空间。这个指针不能直接使用。
	//........
	int a = 10;
	p = &a;
	if (p!=NULL)
	{
		*p = 20;
	}
	return 0;
}

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

//当程序运行完 int* p = test();时,由于 a 只是局部变量,它的空间就被释放了,
//但是赋给 p 的地址却是已经被释放了的空间地址,此时,p 就成了“野指针”

#include <stdio.h>

int* test()   //由于返回的是 &a ,所以返回类型是 int*    //int 表明指针所指对象的类型是 int 类型    //* 表明是指针
{
	int a = 10;
	return &a;  //变量 a 的空间,是进入函数创建,出函数还给操作系统。
}

int main()
{
	int* p = test();   //当程序运行完 int* p = test();时,由于 a 只是局部变量,它的空间就被释放了,但是赋给 p 的地址却是已经被释放了的空间地址,此时,p 就成了“野指针”
	printf("%d\n",*p);
	return 0;
}

虽然最后程序的返回值是正确的,得到10,但是这种访问是非法的。

在这里插入图片描述

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

#include <stdio.h>

int main()
{
	int* p = NULL;  //p 是一个空指针,没有指向任何有效的空间。这个指针不能直接使用。
	*p = 10;   //指针使用之前检查有效性。
	           //这样使用就是错误的。
	return 0;
}
#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int* pend = arr + 9;  //指向数组arr的最后一个元素
	while (p <= pend)  //地址
	{
		printf("%d\n", *p);
		p++;
	}
	return 0;
}

int arr[10];
int* p=arr;
*(p+i) == arr[i];
*(arr+i) == arr[i];
这两行代码好好理解。

arr[i] == (arr+i)==(i+arr)==i[arr]

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = arr;
	for (i = 0; i < 10; i++)
	{
		printf("%d  ", i[arr]);  //这里强调:"[]"只是操作符而已,i和arr只是"[]"两端的操作数而已
		                         //就像,a+b,a和b只是操作数而已
		                         // i[arr]  ==  arr[i]
		printf("%d  ", i[arr]);
		printf("%d  ", *(p+i));
		printf("%d  ", *(arr+i));
	}
	return 0;
}

"[ ]“只是操作符而已,i和arr只是”[ ]"两端的操作数而已
就像,a+b,a和b只是操作数而已
i[arr] == arr[i]

4.指针运算

(1)指针 + - 整数

	*vp++ = 1;   //虽然 ++ 的运算优先级 高于 * ,
	             //但是,++ 是后置的,
	             //先进行:*vp  再进行:vp++

    *--vp = 0 ;   //先是  --vp
                  //再是 *(--vp)
#define N_value 5
#include <stdio.h>

int main()
{
	int* vp;
	int arr[N_value] = { 0 };
	for (vp = &arr[0]; vp < &arr[N_value];)
	{
		*vp++ = 1;   //虽然 ++ 的运算优先级 高于 * ,
		             //但是,++ 是后置的,
		             //先进行:*vp  再进行:vp++
	}

	vp = arr;  //在第一个循环中,vp 中的地址已经改变了,所以要将地址重新赋值 arr 的初始地址

	int j = 0;
	for (j = 0; j < N_value; j++)
	{
		printf("%d\n", *(vp + j));
	}
	return 0;
}
#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int* pend = arr + 9;  //指向数组arr的最后一个元素
	while (p <= pend)  //地址
	{
		printf("%d\n", *p);
		p++;
	}
	return 0;
}

(2)指针 - 指针

(1)得到的是“两个指针之间的元素个数。
(2)前提:两个指针指向同一块内存空间。
(3)指针 - 指针:有意义---------两者之间的元素个数
指针 + 指针 :无意义
类比:日期 + 日期:无意义
日期 - 日期:天数
在这里插入图片描述

(1) 指针 - 指针 得到的数值的绝对值:是指针和指针之间的元素个数

//指针 - 指针 得到的数值的绝对值:是指针和指针之间的元素个数

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);  //结果:9
	printf("%d\n", &arr[0] - &arr[9]);  //结果:-9
	return 0;
}

(2) 指针 - 指针 相减的前提条件是:指针和指针指向了同一块空间。

//指针 - 指针 相减的前提条件是:指针和指针指向了同一块空间。

#include <stdio.h>

int main()   //此代码无法运行
{
	int arr[10] = { 0 };
	char ch[5] = { 0 };
	printf("%d\n", &ch[4] - &arr[1]);
	return 0;
}

(3)用函数写 计数器

#include <stdio.h>

int my_strlen(char* s)
{
	int count = 0;
	while (*s != '\0')
	{
		count++;
		s++;
	}
	return count;
}

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

int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;  //到最后 str 指着 '\0';
		        //当 str - start 时,得到的是 “两个指针之间的元素个数”
	}
	return str - start;
}

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

int my_strlen(char* str)
{
	if (*str != '\0')
	{
		return 1 + my_strlen(str + 1);   //递归
	}
	else
		return 0;
}

int main()
{
	char arr[10] = "abcdef";
	printf("%d\n", my_strlen(arr));
	return 0;
}

(3)指针的关系运算

地址是有大小的。
指针的关系运算,就是:比较指针的大小

#define N_VALUES 5
float values[N_VALUES];
float* vp;

for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 0;
	}

在这里插入图片描述

代码简化,这将代码修改如下:

这么做是错误的,原因如下:

	for (vp = &values[N_VALUES - 1]; vp >= &values[0];vp--)
	{
		*vp = 0;
	}

实际在绝大多数的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许指向第一个元素之前的那个内存位置的指针进行比较。
在这里插入图片描述

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

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

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

相关文章

Diehl EDI 项目案例

代傲Diehl 是一家拥有120多年历史的德国科技企业&#xff0c;凭借其多元化的产品在不同工业领域的各个业务线中备受好评。 由于Diehl的合作伙伴遍及全球&#xff0c;如何管理来自全球各地不同标准、不同格式的业务数据成为一大难题。EDI&#xff08;Electronic Data Interchan…

win系统下安装php8.3版本并配置环境变量的详细教程

本篇文章主要讲解在win系统下安装和配置php8.3版本&#xff0c;并配置环境变量的详细教程。 日期&#xff1a;2024年2月22日 作者&#xff1a;任聪聪 一、下载php8.3版本包 php8.3版本官方下载地址&#xff1a;https://windows.php.net/download#php-8.3 步骤一、打开下载地址…

四、矩阵的分类

目录 1、相等矩阵 2、同形矩阵 3、方阵&#xff1a; 4、负矩阵、上三角矩阵、下三角矩阵&#xff1a; 5、对角矩阵&#xff1a;是方阵 ​编辑7、单位矩阵&#xff1a;常常用 E或I 来表示。它是一个方阵 8、零矩阵&#xff1a; 9、对称矩阵&#xff1a;方阵 1、相等矩阵 …

JAVA IDEA 项目打包为 jar 包详解

前言 如下简单 maven 项目&#xff0c;现在 maven 项目比较流行&#xff0c;你还没用过就OUT了。需要打包jar 先设置&#xff1a;点击 File > Project Structure > Artifacts > 点击加号 > 选择JAR > 选择From modules with dependencies 一、将所有依赖和模…

docker部署seata1.6.0

docker部署seata1.6.0 Seata 是 阿里巴巴 开源的 分布式事务中间件&#xff0c;解决 微服务 场景下面临的分布式事务问题。需要先搭建seata服务端然后与springcloud的集成以实现分布式事务控制的过程 &#xff0c;项目中只需要在远程调用APi服务的方法上使用注解 GlobalTransa…

1.1_1 计算机网络的概念、功能、组成和分类

文章目录 1.1_1 计算机网络的概念、功能、组成和分类&#xff08;一&#xff09;计算机网络的概念&#xff08;二&#xff09;计算机网络的功能&#xff08;三&#xff09;计算机网络的组成1.组成部分2.工作方式3.功能组成 &#xff08;四&#xff09;计算机网络的分类 总结 1.…

2023 H1 中国边缘公有云服务市场 Top2,百度智能云加速推动分布式云智能化升级

近期&#xff0c;IDC 发布了《中国边缘云市场跟踪研究 2023 H1》。报告显示&#xff0c;2023 上半年&#xff0c;中国边缘公有云服务市场规模 24.3 亿元&#xff0c;同比增速达到 41.8%。 其中&#xff0c;百度智能云以 15.7% 的市场份额位列中国边缘公有云服务市场第二&#…

力扣经典题目解析--旋转图像(字节二面)

题目 原题地址: . - 力扣&#xff08;LeetCode&#xff09; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1…

216699-36-4,6-Rhodamine X NHS ester,具有良好的脂溶性

117491-83-5&#xff0c;1890922-83-4&#xff0c;216699-36-4&#xff0c;6-Rhodamine X NHS ester&#xff0c;ROX SE, 6-isomer&#xff0c;6-ROX NHS 活化酯 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;117491-83-5&#xff0c;1890922-83-4&#xff0c;21…

【四川省计算机学会主办 | 中国科协重要学术会议】人工智能与大数据国际会议(ICAIBD 2024)

ICAIBD 2024https://www.icaibd.org/ 会议简介&#xff1a; 第七届人工智能与大数据国际会议(ICAIBD 2024)将于2024年5月24-27日在中国▪四川▪成都召开。七年来&#xff0c;ICAIBD 2024由四川省计算机学会主办&#xff0c;四川省科学技术协会作为指导单位&#xff0c;四川大…

【VRTK】【Unity】【VR开发】使用注意事项-Simulator没反应

【背景】 建立一个基本的VRTK项目后&#xff0c;用Simulator Rig模拟运行&#xff0c;移动鼠标后发现Simulator Rig没有任何反应。 【分析】 Console中的报错信息类似于没有启用Legacy unity input package&#xff0c;Legacy的意思是经典的&#xff0c;所以应该是指没有在p…

[linux]进程间通信(IPC)———共享内存(shm)(什么是共享内存,共享内存的原理图,共享内存的接口,使用演示)

一、什么是共享内存 共享内存区是最快的&#xff08;进程间通信&#xff09;IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。注意&#xff1a;…

kubernetes的网络flannel与caclio

flannel网络 跨主机通信的一个解决方案是Flannel&#xff0c;由CoreOS推出&#xff0c;支持3种实现&#xff1a;UDP、VXLAN、host-gw udp模式&#xff1a;使用设备flannel.0进行封包解包&#xff0c;不是内核原生支持&#xff0c;上下文切换较大&#xff0c;性能非常差 vxlan模…

第3.5章:StarRocks数据导入——Broker Load

注&#xff1a;本篇文章阐述的是StarRocks-3.2版本的Broker Load导入机制 一、概述 Broker Load导入方式支持从HDFS类的外部存储系统&#xff08;例如&#xff1a;HDFS、阿里OSS、腾讯COS、华为云OBS等&#xff09;&#xff0c;支持Parquet、ORC、CSV、及 JSON 四种文件格式&a…

adb-常用命令

1. 连接设备&#xff1a;adb connect ip地址 2. 查询已连接设备列表&#xff1a;adb devices 3. 进入手机端后台&#xff1a;adb shell 4. 退出手机端后台&#xff1a;exit 5. 将手机端文件复制到pc端&#xff1a;adb pull 手机端文件路径/文件 pc端路径 6. 将pc端文件复制到…

根据前序和后序遍历构造二叉树

1.题目 这道题是2024-2-22的签到题&#xff0c;题目难度为中等。 考察知识点为递归。 题目链接&#xff1a;889. 根据前序和后序遍历构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 给定两个整数数组&#xff0c;preorder 和 postorder &#xff0c;其中 preorder 是一…

petalinux_zynq7 驱动DAC以及ADC模块之二:petalinux

petalinux_zynq7 C语言驱动DAC以及ADC模块之一&#xff1a;建立IPhttps://blog.csdn.net/qq_27158179/article/details/136234296在上一篇&#xff0c;建立了ADC和DAC两个IP。这里继续。本文在 petalinux默认配置的基础上&#xff0c;添加了python和qt。再编译出sdk可以给x86主…

Flask——基于python完整实现客户端和服务器后端流式请求及响应

文章目录 本地客户端Flask服务器后端客户端/服务器端流式接收[打字机]效果 看了很多相关博客&#xff0c;但是都没有本地客户端和服务器后端的完整代码示例&#xff0c;有的也只说了如何流式获取后端结果&#xff0c;基本没有讲两端如何同时实现流式输入输出&#xff0c;特此整…

统计图扇形图绘制方法

统计图扇形图绘制方法 常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图。 前几类图比较容易绘制&#xff0c;饼图环形图绘制较难。 还有一种扇形图的绘制也较难&#xff0c;扇形图的各个变类&#xff0c;饼图、环形图、半圆图、玫瑰图等都是统计图扇形的变…

k8s-heml管理 17

Helm是Kubernetes 应用的包管理工具&#xff0c;主要用来管理 Charts&#xff0c;类似Linux系统的 yum。Helm Chart 是用来封装 Kubernetes 原生应用程序的一系列 YAML 文件。可以在你部署应用的时候自定义应用程序的一些 Metadata&#xff0c;以便于应用程序的分发。 对于应用…