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

坚持就是胜利

文章目录

  • 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.mfbz.cn/a/405097.html

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

相关文章

Vue+SpringBoot打造生活废品回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&资源品类模块3.3 回收机构模块3.4 资源求购/出售/交易单模块3.5 客服咨询模块 四、免责说明 一、摘要 1.1 项目介绍 生活废品回收系统是可持续发展的解决方案&#xff0c;旨在鼓…

FreeBSD如何进行系统升级

为什么要升级 原因是当前在使用的版本是12.4&#xff0c;官方已经停止维护了&#xff0c;而且找了几个国内的镜像站也不维护了&#xff0c;导致pkg工具都无法安装&#xff0c;干脆直接进行系统升级。 FreeBSD的系统升级 目前打算升级到13.2-RELEASE版本 获取目前版本需要的…

前端快速网格布局

直接进去CSS Grid Generator 真的好方便&#xff1a;

Python实战:读取MATLAB文件数据(.mat文件)

Python实战&#xff1a;读取MATLAB文件数据(.mat文件) &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅…

go RPC编程

1、golang中如何实现RPC golang中实现RPC非常简单&#xff0c;官方提供了封装好的库&#xff0c;还有一些第三方的库 golang官方的net/rpc库使用encoding/gob进行编解码&#xff0c;支持tcp和http数据传输方式&#xff0c;由于其他语言不支持gob编解码方式&#xff0c;所以gol…

【某机构vip教程】Requests(6):Requests模块_超时设置

超时设置 Requests模块可以设置接收数据的超时时间&#xff0c;超出设定的时间还没有数据返回&#xff0c;就抛出异常。超时设 置有两种类型表达&#xff1a;float 、tuple timeout():以秒为单位 如果远端服务器很慢&#xff0c;你可以让 Request 永远等待&#xff0c;传入一…

【LeetCode: 889. 根据前序和后序遍历构造二叉树 + DFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

研学活动报名平台系统功能清单

中小学生社会实践活动、研学旅行等素质教育活动报名与管理平台&#xff0c;功能包含&#xff1a;活动分类&#xff0c;活动管理&#xff0c;在线报名缴费&#xff0c;扫码核销&#xff0c;会员特权体系&#xff0c;在线商城&#xff0c;研学互动。系统支持入驻老师自行创建研学…

jenkins配置ssh的时候测试连接出现Algorithm negotiation fail

背景&#xff1a;当jenkins升级后&#xff0c;同时ssh插件也升级&#xff0c;测试ssh连接的时候 出现的问题&#xff1a; com.jcraft.jsch.JSchAlgoNegoFailException: Algorithm negotiation fail: algorithmName"server_host_key" jschProposal"ecdsa-sha2-n…

QSettings使用示例

解决的问题&#xff1a; 平常要存储一些临时数据&#xff0c;或者ini的系统参数数据&#xff0c;以下是源码解析 如何实现&#xff1a; 实现的UI如下 主要功能&#xff1a; 初始化&#xff1a; m_settings new QSettings("DParamSetting.ini", QSettings::IniFo…

springboot+vue项目基础开发(13)vue的注册页面

1创建页面 在view下面创建Login.vue 编写login.vue的代码 <script setup> import {User,Lock} from @element-plus/icons-vue import {ref } from vue&#

【JavaEE】_Servlet程序的编写方法

目录 1. 创建项目 2. 引入依赖 3. 创建目录结构 3.1 在main目录下创建一个webapp目录 3.2 在webapp目录下创建一个WEB-INF目录 3.3 在WEB-INF目录下创建一个web.xml文件 3.4 在web.xml中进行代码编写 4. 编写代码 4.1 在java目录下创建类 4.2 打印"hello world&…

代码随想录算法训练营第二十六天|39. 组合总和、40.组合总和II、131.分割回文串

39. 组合总和 刷题https://leetcode.cn/problems/combination-sum/description/文章讲解https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html视频讲解https://www.bilibili.com/video/BV1KT4y1M7HJ/?vd_sourceaf4853e80f89e28094a5fe1e220d9062 回溯…

vue+django+python办公耗材网上商城采购库存管理系统

办公耗材采购信息管理是信息行业业务流程过程中十分重要且必备的环节之一&#xff0c;在信息行业业务流程当中起着承上启下的作用&#xff0c;其重要性不言而喻。但是&#xff0c;目前许多信息行业在具体的业务流程处理过程中仍然使用手工操作的方式来实施&#xff0c;不仅费时…

C语言运用中断子系统用驱动控制led实验,c语言串口led点灯实验(驱动+应用层)

中断子系统用驱动控制led实验 驱动代码 #include <linux/init.h> #include <linux/module.h>#include<linux/interrupt.h> #include<linux/gpio.h> #include<linux/timer.h>#include<linux/of.h> #include<linux/of_irq.h> #inclu…

MySQL MGR 恢复(从库维度)

集群信息 一主两从 端口 角色 3307 主 3309 备 3303 备 从库故障 关掉 3303 从库 删除所有数据&#xff0c;模拟故障 从库恢复还原(物理备份恢复) 备份另一台 处于组关中的 从库的数据&#xff0c;端口为 3309 物理备份 xtrabackup --defaults-file/etc/my3309.cnf …

idea在工具栏中显示快速创建包和类的图标

一、效果图 点击需要创建包或者类的位置&#xff0c;在点击对用的图标就可以快速创建类或者包了。 二、设置 步骤一 View-->Appearance-->Toolbar 步骤二 File-->Settings-->Appearance & Behavior-->Menus and Toolbars-->Main Toolbar-->----…

路飞项目--06

redis介绍和安装 # 数据库&#xff1a; 关系型数据库&#xff1a;mysql、oracle、postgrasql、sqlserver、sqlite IBM&#xff1a;服务器 Oracle&#xff1a;数据库 达梦 EMC&#xff1a;存储 非关系型数据库: redis、mongodb、es…

数据库增删改查

DDL: 数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库、表、字段&#xff09;DML: 数据操作语言&#xff0c;用来对数据库表中的数据进行增删改DQL: 数据查询语言&#xff0c;用来查询数据库中表的记录DCL: 数据控制语言&#xff0c;用来创建数据库用户、控制数…

高录用快见刊【最快会后两个月左右见刊】第三届社会科学与人文艺术国际学术会议 (SSHA 2024)

第三届社会科学与人文艺术国际学术会议 (SSHA 2024) 2024 3rd International Conference on Social Sciences and Humanities and Arts *文章投稿均可免费参会 *高录用快见刊【最快会后两个月左右见刊】 重要信息 会议官网&#xff1a;icssha.com 大会时间&#xff1a;202…
最新文章