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));  //4printf("%d\n", sizeof(pi));  //4printf("%d\n", sizeof(ps));  //4printf("%d\n", sizeof(pl));  //4 printf("%d\n", sizeof(pf));  //4printf("%d\n", sizeof(pd));  //4return 0;
}

(1)指针 + - 整数

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

#include <stdio.h>int main()
{int arr[10] = { 0 };int* p = arr;char* pc = arr;printf("%p\n", p);          //0095F9C0printf("%p\n", p + 1);      //0095F9C4printf("%p\n", pc);         //0095F9C0printf("%p\n", pc + 1);     //0095F9C1return 0;
}
#include <stdio.h>int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);      //010FF890printf("%p\n", pc);      //010FF890printf("%p\n", pc + 1);  //010FF891printf("%p\n", pi);      //010FF890printf("%p\n", pi + 1);  //010FF894return 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]);  //结果:9printf("%d\n", &arr[0] - &arr[9]);  //结果:-9return 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);   //递归}elsereturn 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://xiahunao.cn/news/2809768.html

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

相关文章

【文末送书】世界顶级名校计算机专业,都在用哪些书当教材?

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

基础复习(IDA调试器)

1.选择IDA调试后端 在顶部有一个下拉菜单&#xff0c;选择调试器后端位置 很多用户实际上使用的是Windows版本的IDA&#xff0c;该IDA可以直接调试Windows下32bit和64bit的程序 2.本地调试启动方法 载入IDA后&#xff0c;程序实际上在对程序内置的一个字符串进行base64解码…

PyTorch中Tensor(张量)数据结构内部观察

上图中是一个张量embeds&#xff0c;打开其内部存储空间&#xff0c;我们可以看到内部的构成。在PyTorch中&#xff0c;Tensor 具有许多属性和方法。以下是其中一些关键的属性和方法&#xff1a; 属性&#xff1a; H&#xff1a; 在标准的PyTorch API中并没有直接表示为 .H 的…

nginx.conf配置文件详解、案例,Nginx常用命令与模块

目录 一、Nginx常用命令 二、Nginx涉及的文件 2.1、Nginx 的默认文件夹 2.2、Nginx的主配置文件nginx.conf nginx.conf 配置的模块 2.2.1、全局块&#xff1a;全局配置&#xff0c;对全局生效 2.2.2、events块&#xff1a;配置影响 Nginx 服务器与用户的网络连接 2.2.3…

Python爬虫-爬取豆瓣高分电影封面

本文是本人最近学习Python爬虫所做的小练习。如有侵权&#xff0c;请联系删除。 页面获取url 代码 import requests import os import re# 创建文件夹 path os.getcwd() /images if not os.path.exists(path):os.mkdir(path)# 获取全部数据 def get_data():# 地址url "…

YOLOv9来了,可编程梯度信息与广义高效层聚合网络 助力全新检测SOTA前沿

本文首发&#xff1a;AIWalker 欢迎关注AIWalker&#xff0c;近距离接触底层视觉与基础AI技术 摘要 当今的深度学习方法侧重于如何设计最合适的目标函数&#xff0c;以便模型的预测结果最接近真实情况&#xff1b;与此同时&#xff0c;必须设计一个适当的架构&#xff0c;以便…

企业进行数字化转型的优势有哪些?

企业进行数字化转型的优势主要包括&#xff1a; 提高企业效率&#xff1a;通过自动化和高效化&#xff0c;减少重复性工作&#xff0c;提高工作效率和精度。 创新业务模式&#xff1a;更好地了解客户需求&#xff0c;快速响应市场变化&#xff0c;开发新的业务模式。 提升客户…

分享从零开始学习网络设备配置--任务5.1 组建直连式二层无线局域网

任务要求 &#xff08;1&#xff09;组建直连式二层无线局域网&#xff0c;网络拓扑图如图 &#xff08;3&#xff09;路由器、交换机和AC等网络设备端口IP地址规划如表 &#xff08;4&#xff09;组建直连式二层无线局域网&#xff0c;配置AP上线、WLAN业务参数和实现STA能正…

面试经典150题——存在重复元素 II

​"The harder you work for something, the greater youll feel when you achieve it." - Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 该思路很简单&#xff0c;就是暴力的查找每一个元素&#xff0c;查看是否满足题目要求&#xff0c;满足就…

BUU [CISCN2019 华东南赛区]Web4

BUU [CISCN2019 华东南赛区]Web4 题目描述&#xff1a;Click to launch instance. 开题&#xff1a; 点击链接&#xff0c;有点像SSRF 使用local_file://协议读到本地文件&#xff0c;无法使用file://协议读取&#xff0c;有过滤。 local_file://协议&#xff1a; local_file…

算能RISC-V通用云开发空间编译pytorch @openKylin留档

终于可以体验下risc-v了&#xff01; 操作系统是openKylin&#xff0c;算能的云空间 尝试编译安装pytorch 首先安装git apt install git 然后下载pytorch和算能cpu的库&#xff1a; git clone https://github.com/sophgo/cpuinfo.git git clone https://github.com/pytorc…

比创达元启新程 共创新佳绩:2023年度总结暨迎新年晚会圆满收官!

新的一年&#xff0c;万象更新。回顾2023年&#xff0c;我们携手走过的岁月&#xff0c;喜悦伴着汗水&#xff0c;成功伴着艰辛&#xff0c;遗憾激励奋斗。在过去的一年时间里&#xff0c;每个行业都经历着前所未有的变革与困难。我们比创达人也凭借着人心齐泰山移的团结之力&a…

Spring Boot 项目集成camunda流程引擎

使用camunda开源工作流引擎有&#xff1a;通过docker运行、使用springboot集成、部署camunda发行包、基于源代码编译运行等多种方式。 其中&#xff0c;通过源代码编译运行的方式最为复杂&#xff0c;具体参考&#xff1a;https://lowcode.blog.csdn.net/article/details/1362…

图片录入设备、方式与质量对图片转Excel的影响

随着数字化时代的到来&#xff0c;图片已经成为人们日常生活中不可或缺的一部分。在各行各业中&#xff0c;图片的应用越发广泛&#xff0c;从而促使了图片处理技术的快速发展。然而&#xff0c;图片的质量对于后续数据处理和分析的准确性和可靠性有着至关重要的影响。本文将从…

Windows系统搭建Elasticsearch引擎结合内网穿透实现远程连接查询数据

文章目录 系统环境1. Windows 安装Elasticsearch2. 本地访问Elasticsearch3. Windows 安装 Cpolar4. 创建Elasticsearch公网访问地址5. 远程访问Elasticsearch6. 设置固定二级子域名 Elasticsearch是一个基于Lucene库的分布式搜索和分析引擎&#xff0c;它提供了一个分布式、多…

HTB-Bizness

一、信息收集 访问ip自动跳转域名&#xff0c;host绑定域名后访问 目录爆破 有一个登录目录&#xff0c;访问发现是apahce ofbiz登录页面 发现存在漏洞 二、漏洞利用 在github上找到了图形化利用工具 使用工具反弹shell 得到flag 三、权限提升 从本地利用python开启http服务…

Android RecyclerView 如何展示自定义列表 Kotlin

Android RecyclerView 如何展示自定义列表 Kotlin 一、前提 有这么一个对象 class DeviceDemo (val name: String, val type: String, val address: String)要展示一个包含这个对象的列表 bluetoothDevices.add(DeviceDemo("bb 9800", "LE", "32:…

蛇形矩阵3

题目描述 把数1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;…&#xff0c;N*N按照“蛇形3”放入N*N矩阵的中&#xff0c;输出结果。 下面是N6的蛇形3的图示 输入格式 第一行1个正整数&#xff1a;N&#xff0c;范围在[1,100]。 输出格式 N行&#x…

docker 容器访问 GPU 资源使用指南

概述 nvidia-docker 和 nvidia-container-runtime 是用于在 NVIDIA GPU 上运行 Docker 容器的两个相关工具。它们的作用是提供 Docker 容器与 GPU 加速硬件的集成支持&#xff0c;使容器中的应用程序能够充分利用 GPU 资源。 nvidia-docker 为了提高 Nvidia GPU 在 docker 中的…

Linux系统前后端分离项目

目录 一.jdk安装 二.tomcat安装 三.MySQL安装 四.nginx安装 五.Nginx负载均衡tomcat 六.前端部署 一.jdk安装 1. 上传jdk安装包 jdk-8u151-linux-x64.tar.gz 进入opt目录&#xff0c;将安装包拖进去 2. 解压安装包 这里需要解压到usr/local目录下&#xff0c;在这里新建一个…