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://www.mzph.cn/news/698328.shtml

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

相关文章

四川尚熠电子商务有限公司专注抖店开店服务

在数字化浪潮席卷全球的今天&#xff0c;电子商务成为了企业发展的重要引擎。四川尚熠电子商务有限公司凭借其专业的抖店开店服务&#xff0c;成为了众多商家进军电商市场的得力助手。本文将深入探讨四川尚熠电子商务有限公司抖店开店服务的优势与特点&#xff0c;为商家们提供…

全球游戏市场回暖,Flat Ads推动海外获客增长

摘要:热门游戏品类分析,解读新兴市场与赛道 近日,中国音数协游戏工委发布了《2023年中国游戏出海研究报告》,据报告数据显示,2023年,全球游戏市场规模11773.79亿元,同比增长6.00%,呈现增长回暖趋势。 图源:伽马数据 1.SLG和RPG游戏热度居高不下,休闲游戏增长势头强劲 目前,S…

第四十天| 343. 整数拆分、96.不同的二叉搜索树

Leetcode 343. 整数拆分 题目链接&#xff1a;343 整数拆分 题干&#xff1a;给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。 思考&#xff1a;动态规划。…

状态模式:灵活应对对象行为变化,实现状态驱动的智能设计

文章目录 **一、技术背景与应用场景****为何使用状态模式&#xff1f;****典型应用场景包括但不限于&#xff1a;** **二、状态模式定义与结构****三、使用步骤举例****四、优缺点分析****总结** 一、技术背景与应用场景 状态模式是一种行为设计模式&#xff0c;用于处理一个对…

mybatis 集成neo4j实现

文章目录 前言一、引入jar包依赖二、配置 application.properties三、Mybatis Neo4j分页插件四、Mybatis Neo4j自定义转换器handler五、MybatisNeo4j代码示例总结 前言 MyBatis是一个基于Java语言的持久层框架&#xff0c;它通过XML描述符或注解将对象与存储过程或SQL语句进行…

Qt+VTK鼠标拾取点生成拉伸闭合三维体

程序示例精选 QtVTK鼠标拾取点生成拉伸闭合三维体 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《QtVTK鼠标拾取点生成拉伸闭合三维体》编写代码&#xff0c;代码整洁&#xff0c;规则&…

java——特殊文件日志技术

目录 特殊文件Properties文件XML文件XML文件有如下的特点XML的作用和应用场景解析XML文件 日志技术概述日志技术的体系结构Logback日志框架概述快速入门核心配置文件logback.xml日志级别项目中使用日志框架 特殊文件 Properties文件 后缀为.properties的文件&#xff0c;称之…

k8s(2)

目录 一.二进制部署k8s 常见的K8S安装部署方式&#xff1a; k8s部署 二进制与高可用的区别 二.部署k8s 初始化操作&#xff1a; 每台node安装docker&#xff1a; 在 master01 节点上操作; 准备cfssl证书生成工具:&#xff1a; 执行脚本文件&#xff1a; 拉入etcd压缩包…

一文读懂!2024年NCDA数字绘画竞赛:参赛、要求、获奖作品

第12届未来设计师全国高校数字艺术设计大赛&#xff08;NCDA&#xff09; H类&#xff1a;数字绘画命题选项 命题信息 参赛对象&#xff1a; 1. 大学生组&#xff1a;分①研究生组②本科生组③专科生组&#xff0c;三组分别评审 2. 教师组&#xff1a;普通高校教师&#xf…

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

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

windows11安装VMware----创建多虚拟机教程

一、centOS下载 1、官网地址&#xff1a;https://www.centos.org/ 2、阿里镜像站&#xff1a;https://mirrors.aliyun.com/centos 3、清华镜像源&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/centos/ 3.、CentOS搜狐镜像&#xff1a;http://mirrors.sohu.com/centos/…

Linux-基础知识(黑马学习笔记)

硬件和软件 我们所熟知的计算机是由&#xff1a;硬件和软件组成。 硬件&#xff1a;计算机系统中电子&#xff0c;机械和光电元件等组成的各种物理装置的总称。 软件&#xff1a;是用户和计算机硬件之间的接口和桥梁&#xff0c;用户通过软件与计算机进行交流。 而操作系统…

个人博客系列-项目部署-nginx(3)

使用Nginx uwsgi进行部署django项目 一. 检查项目是否可以运行 启动项目 python manage.py runserver 0.0.0.0:8099输入ip:8099 查看启动页面 出现上述页面表示运行成功 二. 安装uwsgi并配置 2.1 下载uwsgi pip install uwsgi新建文件test.py写入内容&#xff0c;测试一…

SpringBoot中Redis缓存的使用

目录 1 前言 2 实现方法 2.1 查询数据时 2.2 修改数据 1 前言 对于一些不常改变&#xff0c;但又经常查询的数据&#xff0c;我们可以使用Redis缓存&#xff0c;来缓解数据库的压力&#xff0c;其中的逻辑如下&#xff1a; 2 实现方法 2.1 查询数据时 一般在控制类查询方…

Python列表:灵活多变的数据结构

文章目录 一、列表1.创建列表2.访问列表元素3.修改列表元素4.添加元素5.删除元素 二、列表脚本操作符1.连接运算符 2.重复运算符 * 三、列表函数&方法1.函数1.1 len() 函数1.2 max() 函数1.3 min() 函数1.4 sum() 函数1.5 list() 函数 2.方法2.1 append() 方法2.2 extend()…

MacBook的nginx出现13: Permission denied 的问题分析和解决办法

同样的项目代码&#xff0c;电脑从Windows更换到了MacBook&#xff0c;发现网站的样式都没有了&#xff0c;直接访问CSS文件 http://crm.ms-test.cc/toolstatic/css/bootstrap.min.css 发现无法访问。查看Nginx错误日志&#xff1a; 说明是nginx没有权限访问这个CSS文件&#…

mysql 分表实战

本文主要介绍基于range分区的相关 1、业务需求&#xff0c;每日160w数据&#xff0c;每月2000w;解决大表数据读写性能问题。 2、数据库mysql 8.0.34&#xff0c;默认innerDB;mysql自带的逻辑分表 3、分表的目的:解决大表性能差&#xff0c;小表缩小查询单位的特点(其实优化的精…

鼠标右键助手专业版 MouseBoost PRO for Mac v3.3.6中文破解

MouseBoost Pro mac版是一款简单实用的鼠标右键助手专业版&#xff0c;MouseBoost Pro for Mac只要轻点你的鼠标右键&#xff0c;就可以激活你想要的各种功能&#xff0c;让你的工作效率大幅度提高&#xff0c;非常好用。 软件下载&#xff1a;MouseBoost PRO for Mac v3.3.6中…

Gemma模型论文详解(附源码)

原文链接&#xff1a;Gemma模型论文详解&#xff08;附源码&#xff09; 1. 背景介绍 Gemma模型是在2023.2.21号Google新发布的大语言模型, Gemma复用了Gemini相同的技术(Gemini也是Google发布的多模态模型)&#xff0c;Gemma这次发布了了2B和7B两个版本的参数&#xff0c;不…

如何快速卸载windows电脑的一些软件?

本系列是一些电脑常规操作的普及&#xff0c;有需要借鉴即可 注&#xff1a;每个电脑都会有差异&#xff0c;参考即可。 其实大部分软件你删除桌面上的图标不等于删除&#xff0c;因为桌面上的那个图标就是一个简单的快捷方式而已。 在这里插入图片描述 那如何正确的卸载软件呢…