民办生从零学C的第十二天:指针(1)

每日励志:拼搏十年,征战沙场,不忘初心,努力成为一个浑身充满铜臭味的有钱人

一.内存和地址

1.内存

计算机内存是一系列存储单元的集合,每个存储单元都有唯一的地址来标识。这些存储单元用于存储程序的数据和指令。内存可以被看作是一个巨大的数组,地址就像是这个数组的索引,通过地址可以找到对应的存储单元。内存分为一个一个的内存单元,每一个内存单元的大小是一个字节,一个字节等于8bit。(一个比特位可以存储一个2进制的位1或者0

2.地址

地址是内存单元的编号,用于定位内存中的数据。在 C 语言中,当我们声明一个变量时,系统会自动为该变量分配一块内存区域,变量的地址就是这块内存区域的起始位置。

可以类比为一个宿舍楼(内存),一个内存单元就是一个宿舍,而8bit就是8个人,同时,每一个宿舍号就是地址

3.指针

指针是一个变量,它的值是另一个变量的地址。

例如: int a   =   10             

            int *  p   =   &a;

这里,p 是一个指针变量,&a 表示变量 a 的地址。

指针 p 存储的就是变量 a 在内存中的地址。

通过指针,可以间接访问和操作内存中的数据,即通过指针可以修改变量 a 的值。

变量创建的实质是向内存申请相应空间来存储数据,例如定义int a = 10时,就是在内存里申请 4 个字节的空间去放置数值 10,而这 4 个字节的每个单元都有独一无二的地址编号。在程序中,我们所设置的变量名,其实主要是为了方便程序员自身识别和操作,编译器在实际处理时并不会识别变量名,它依据的是内存地址来精准定位并操作对应的内存单元。

二.指针变量

1.取地址操作符 & 与解引用操作符  * (间接访问操作符)

在C语言中创建交量其实就是向内存申请空问,例如我们在创建int a = 10时就是在内存里申请 4 个字节的空间去放置数值 10,每一个字节都有地址

这个时候通过便可以获得地址(第一个字节的地址),剩下的三个地址随之而然也可以得知

#include <stdio.h>
int main()
{int a = 10;int *pa = &a;*pa = 20;printf("%d\n",a);printf("%p\n",&a);return 0;
}//第一个printf输出的是20,这是因为通过指针pa修改了变量a的值。//第二个printf输出的是变量a的内存地址,不同设备和运行环境输出会有所不同

2.指针类型

例如: int a   =   10             

            int *  pa   =   &a;

            *pa = 20;

 int  * ,是在说明pa是指针变量,int 是在说明pa指向的是整型(int)类型的对象,  *pa 是表示指针pa指向的内存地址中存储的值,*pa = 20 是将pa指向的地址处存储的值改为20。

3.指针地址大小

32位平台下地址是32个bit位(即4个字节),而64位平台下地址是64个bit位(即8个字节)

指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。



三.指针的解引用

1.

#include <stdio.h>
int main()
{int n = 0x11223344;printf("初始值:n = 0x%X\n", n);printf("n的地址:&n = %p\n", &n);int *pi = &n;printf("解引用pi前:*pi = 0x%X\n", *pi);*pi = 0;printf("解引用pi后:*pi = 0x%X\n", *pi);// 分别输出每个字节的值unsigned char *byte_ptr = (unsigned char *)&n;printf("修改后n的每个字节值(按字节拆分):");for (int i = 0; i < sizeof(n); i++) {printf("0x%02X ", byte_ptr[i]);}printf("\n");return 0;
}

#include <stdio.h>
int main()
{int n = 0x11223344;printf("初始值:n = 0x%X\n", n);printf("n的地址:&n = %p\n", &n);char *pc = (char *)&n;printf("解引用pc前:*pc = 0x%02X\n", *pc);*pc = 0;printf("解引用pc后:*pc = 0x%02X\n", *pc);// 分别输出每个字节的值unsigned char *byte_ptr = (unsigned char *)&n;printf("修改后n的每个字节值(按字节拆分):");for (int i = 0; i < sizeof(n); i++) {printf("0x%02X ", byte_ptr[i]);}printf("\n");return 0;
}

我们可以观察到代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0

char * 的指针解引用就只能访问一个字节,而 int * 的指针的解引用就能访问四个字节。由此可知,指针的类型决定了指针解引用的时候能够一次操作几个字节

2.void * 指针

它是通用指针,不指向特定数据类型。它不能直接解引用,需强制转换为目标类型指针后才能操作。常用于函数参数部分,用来接收不同类型数据的地址。

#include <stdio.h>int main()
{int age = 520;double price = 13.14;char initial = 'A';// 使用void指针指向不同类型的变量void* ptr;// 将int变量的地址赋给void指针ptr = &age;printf("使用void指针访问int变量的值:%d\n", *(int*)ptr);// 将double变量的地址赋给void指针ptr = &price;printf("使用void指针访问double变量的值:%.2f\n", *(double*)ptr);// 将char变量的地址赋给void指针ptr = &initial;printf("使用void指针访问char变量的值:%c\n", *(char*)ptr);return 0;
}

四.const修饰指针

1.const修饰变量

变量是可以修改的,如果我们把变量的地址交给一个指针变量,我们也可以通过这个指针变量来修改这个变量,但如果我们不希望这个变量被修改,那么我们就引进  const  来限制

1.

int n = 0;

n = 20;

2.

const int m = 0;

m = 20;(编译器会报错)

const的限制是语法层面的限制,所以在2中如果对m进行修改就报错。但是我们可以通过指针绕过const的限制

#include <stdio.h>int main() 
{const int m = 0;printf("%d\n",m);int *pm = &m;*pm = 100;printf("%d\n",m);return 0;
}

同时,在const修饰变量的时候,这个被修饰的变量本质上还是变量,叫做变常量,只不过它不能被修改。

2.const修饰指针变量

                 可以放在 * 的左边,也可以故在 * 的右边,

a.const 放在指针声明的左边

当 const 放在指针声明的左边时,表示指针指向的数据是常量不能通过该指针修改其值。指针本身仍然可以指向其他地址。

#include <stdio.h>int main() 
{int num1 = 520;int num2 = 1314;const int *p = &num1; // 指针指向的值是常量printf("通过指针访问num1的值: %d\n", *p); // 可以访问值// *p = 100; // 错误:不能通过指针修改值// 可以改变指针指向的地址p = &num2;printf("通过指针访问num2的值: %d\n", *p);return 0;
}
b. const 放在指针声明的右边

当const放在指针声明的右边时,表示指针本身是常量不能改变它指向的地址。但是可以通过指针修改指向地址的值。

#include <stdio.h>int main() 
{int num1 = 520;int num2 = 1314;int *const p = &num1; // 指针本身是常量printf("通过指针访问num1的值: %d\n", *p); // 可以访问值*p = 100; // 可以通过指针修改值printf("修改后通过指针访问num1的值: %d\n", *p);// p = &num2; // 错误:不能改变指针指向的地址return 0;
}

五.指针运算

分别有三种:

  • 指针+-整数
  • 指针-指针
  • 指针的关系运算

1.指针 + / - 整数

  • 含义:指针和整数相加或相减,会改变指针的值,使其指向数组中前后的元素
  • 规则:指针指向某种数据类型,当指针和整数n相加或相减时,指针的值会增加或减少n个该数据类型所占的字节数。(由指针类型决定+-1的步长
#include <stdio.h>int main() 
{int arr[] = {10, 20, 30, 40, 50};int *p = arr; // 指向数组第一个元素printf("arr[0] = %d\n", *p); // 输出10p = p + 2; // 指向arr[2]printf("arr[2] = %d\n", *p); // 输出30p = p - 1; // 指向arr[1]printf("arr[1] = %d\n", *p); // 输出20return 0;
}
#include <stdio.h>int main()
{int arr[] = {1,2,3,4,5,6,7,8,9,10};//   数组索引 0 1 2 3 4 5 6 7 8 9int i =0;int sz = sizeof(arr)/sizeof(arr[0]);int * p = &arr[0];for(i = 0; i<sz;i++){printf("%d ",*p);p++;}return 0;
}

2.指针 - 指针

  • 含义:两个指针相减的绝对值,得到它们之间的元素个数

  • 规则:两个指针都指向同一种数据类型,相减的结果是它们在内存中所指向元素的个数之差。

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *p1 = arr; // 指向arr[0]int *p2 = arr + 3; // 指向arr[3]int difference = p2 - p1;printf("指针之间的元素个数: %d\n", difference); // 输出3return 0;
}

数组名其实是数组首元素的地址

为什么没有指针加指针呢?

因为指针加指针就像日期加日期一样没有意义

eg 还原 strlen函数

#include <stdio.h>size_t my_strlen(char*p)
{char* start = p;char* end = p;while(*end != '\0'){end++;}return end - start;
}int main()
{char arr[] = "abcdefg";//      a b c d e f g \0size_t len = my_strlen(arr);//数组名其实是数组首元素的地址 arr == &arr[@]printf("%zd\n",len);return 0;
}

3.指针的关系运算

  • 含义:比较两个指针的大小,判断它们指向的内存位置的前后关系

  • 规则:两个指针都指向同一种数据类型,可以使用关系运算符(==、!=、>、<、>=、<=)进行比较。

#include <stdio.h>int main() 
{int arr[] = {10, 20, 30, 40, 50};int *p1 = arr; // 指向arr[0]int *p2 = arr + 2; // 指向arr[2]if (p1 < p2) {printf("p1 < p2\n");} else if (p1 == p2) {printf("p1 == p2\n");} else {printf("p1 > p2\n");}int *p3 = arr + 1;if (p3 != p2) {printf("p3 != p2\n");}return 0;
}

六.野指针

它指的是一个指针指向了一个已经被释放或者无效的内存地址。野指针的存在会导致程序行为不可预测,可能会引发崩溃或数据损坏。

1.指针未初始化

#include <stdio.h>int main() 
{int *p; // 声明一个指针,但未初始化// 尝试访问指针指向的值printf("指针p指向的值: %d\n", *p);return 0;
}

当我们尝试访问 *p 时,程序可能会崩溃,或者输出一个随机的值,因为p指向的内存地址未被程序合法使用。

2.指针越界访问

#include <stdio.h>int main() 
{int arr[3] = {10, 20, 30}; // 定义一个大小为3的数组int *ptr = arr;            // 指针初始化为数组首地址// 正常访问数组元素printf("arr[0] = %d\n", *ptr);    // 输出10printf("arr[1] = %d\n", *(ptr + 1)); // 输出20printf("arr[2] = %d\n", *(ptr + 2)); // 输出30// 越界访问,尝试访问arr[3]printf("越界访问arr[3] = %d\n", *(ptr + 3)); return 0;
}

使用*(p + 3)尝试访问 arr[3],这超出了数组的实际大小(数组只有3个元素,索引为0、1、2)。这种越界访问会导致未定义行为,可能读取到随机值或引发程序崩溃。

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

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

相关文章

用Postman验证IAM Token的实际操作

当我们需要用Postman发送一个最简单的请求去验证Token的时候我们该怎么办&#xff1f; 【一、步骤】 步骤1&#xff1a;打开Postman&#xff0c;新建一个GET请求 请求地址填&#xff1a; https://iam.cn-north-4.myhuaweicloud.com/v3/auth/projects 解释一下&#xff1a;…

关于常量指针和指向常量的指针

关于指针&#xff0c;对于常量指针和指向常量的指针也是傻傻分不清。看到定义时&#xff0c;不知道是指针不能变&#xff0c;还是指针指向的内容不能变量。 先看形式&#xff1a; const char * A; char * const B; 这两种有什么区别&#xff1f;傻傻分不清。 A这种定义&am…

unity 读取csv

1.读取代码 string filePath Application.streamingAssetsPath "\\data.csv"; public List<MovieData> movieData new List<MovieData>(); private void ReadCSV(string filePath) { List<List<string>> data new List<…

安达发|高效智能塑料切割数控系统 - 全自动化软件解决方案

在当今的制造业中&#xff0c;塑料作为一种轻便、耐用且成本效益高的材料&#xff0c;被广泛应用于各个领域。随着科技的进步和市场需求的变化&#xff0c;塑料加工行业正面临着前所未有的挑战和机遇。为了提高生产效率&#xff0c;降低成本&#xff0c;并满足日益严格的质量标…

c#接口_抽象类_多态学习

c#接口_抽象类_多态学习 学习日志 关于&#xff1a;c#接口_抽象类_多态的学习记录。 一、概念 1. 多态&#xff08;Polymorphism&#xff09; 定义&#xff1a;同一操作作用于不同对象时&#xff0c;表现出不同的行为。实现方式&#xff1a; 继承 方法重写&#xff08;ov…

智能硬件行业售后服务管理:提升客户体验的关键所在

在当今数字化浪潮的推动下&#xff0c;智能硬件行业正以前所未有的速度蓬勃发展。从智能家居设备的普及&#xff0c;到智能穿戴产品的多样化&#xff0c;再到智能办公设备的广泛应用&#xff0c;智能硬件已经深入到我们生活的方方面面。据市场研究机构预测&#xff0c;未来几年…

Vue3 里 CSS 深度作用选择器 :deep()

&#x1f3af; 解释 在 Vue 组件里&#xff0c;CSS 默认是 scoped&#xff08;作用域限定的&#xff09;&#xff0c;只对当前组件生效。 如果你想在 scoped 样式里&#xff0c;穿透到子组件的内部元素&#xff0c;就要用 :deep()。 ✏️ 示例 比如&#xff0c;你有一个子组件…

仙宫云ComfyUI —【Wan2.1】AI视频生成部署

【Wan2.1】AI视频生成本地部署与使用技巧全面详解_哔哩哔哩_bilibili 所有模型下载&#xff1a;https://pan.quark.cn/s/9d793aa1b258 Runninghub本期课程工作流下载&#xff08;可获得1000RH币&#xff09;&#xff1a;https://www.runninghub.cn/?utm_sourcekol01-RH145 仙…

LabVIEW 在测控领域的深度开发与未来发展趋势研究报告 (2025-2030)(原创作品使用请注明出处,三连)

## LabVIEW 在测控领域的深度开发与未来发展趋势研究报告 (2025-2030) ### 引言 LabVIEW(Laboratory Virtual Instrument Engineering Workbench)自 1986 年由美国国家仪器公司(NI)发布以来,凭借其独特的图形化编程语言(G 语言)和强大的硬件集成能力,已成为全球工程师和…

政策支持与市场驱动:充电桩可持续发展的双轮引擎

随着全球能源转型加速&#xff0c;新能源汽车成为实现低碳交通的重要方向。然而&#xff0c;充电基础设施不足仍是制约其普及的关键瓶颈。当前&#xff0c;国际主流的充电桩运营模式包括政府推动、电网企业推动及汽车厂商推动三种模式&#xff0c;但单一模式均存在显著局限性。…

JVM——垃圾收集策略

GC的基本问题 什么是GC&#xff1f; GC 是 garbage collection 的缩写&#xff0c;意思是垃圾回收——把内存&#xff08;特别是堆内存&#xff09;中不再使用的空间释放掉&#xff1b;清理不再使用的对象。 为什么要GC&#xff1f; 堆内存是各个线程共享的空间&#xff0c…

用Java模拟打字:深入解析 java.awt.Robot 的键盘控制艺术

作为开发者&#xff0c;我们有时会遇到需要自动化用户界面交互的场景&#xff0c;比如自动化测试、脚本编写、或者制作一些辅助工具。而模拟键盘输入&#xff0c;尤其是“打字”&#xff0c;是这类自动化任务中非常基础且常见的一环。 在 Java 中&#xff0c;实现这一目标的利…

JavaScript 入门全讲解

JavaScript 入门全讲解 一、前言&#xff1a;为什么学习 JavaScript&#xff1f;二、JavaScript 简史与发展三、JavaScript 基础语法3.1 变量声明&#xff1a;var、let、const3.2 数据类型3.3 类型判断3.4 类型转换 四、运算符与表达式五、流程控制5.1 条件判断5.2 switch 语句…

python练习:求数字的阶乘

求数字的阶乘 eg:5的阶乘 54321 """ 求数字的阶乘 eg:5的阶乘 5*4*3*2*1 """count 1 for i in range(1,6):count count * iprint(count)运行结果&#xff1a;

传统农耕展陈如何突破?数字多媒体能否重构文化体验边界?

农耕文化是中华民族悠久历史的重要组成部分&#xff0c;它不仅承载着古代先民与自然和谐相处的智慧&#xff0c;也体现了人们对土地和自然的深厚情感。而今&#xff0c;如何有效地传承和展示这一传统文化&#xff0c;成为了一个重要的课题。今日&#xff0c;便让我们聚焦于农耕…

nginx代理websocket时ws遇到仅支持域名访问的处理

最终改造点 proxy_set_header Host 这一行 未改之前遇到的问题&#xff1a; nginx 日志显示 https://aaa.bbbb.cn:7413 被解析成了 IP 地址&#xff0c;这通常是因为 DNS 解析的结果被缓存或某些中间层&#xff08;如负载均衡器、防火墙等&#xff09;将域名替换为 IP 地址。…

YUM/DNF管理工具

YUM (Yellow dog Updater&#xff0c; Modified) &#xff0c; RHEL8 中默认使用的软件批量管理工具由原版本的 yum 换成了速度更快的 dnf &#xff08; DNF Dandified YUM &#xff09;&#xff0c;原有的 yum 命令仅为 dnf 的软链接&#xff0c;当然依旧可以使用。 [root…

易基因:何川团队开发新m6A测序方法 可温和条件下高分辨率/低背景噪声检测m6A修饰|Nature子刊

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 RNA和DNA中的化学修饰在多种生物过程中发挥着关键作用&#xff0c;包括转录调控、RNA降解、蛋白质翻译和免疫调节等。这些修饰已被新的测序方法以单碱基分辨率定量地绘制出来&#xff0c…

前后端分离: vue3+SpringBoot+ElementPlus+Axios+MyBatisPuls

前后端分离: vue3SpringBoot 项目介绍搭建Vue前端工程axios请求响应拦截跨域 搭建后端TableId,TableName分页显示配置Druid数据源带条件的分页查询后端校验lambda表达式说明 项目介绍 &#x1f31f;项目页面 &#x1f31f;技术栈: 1.前端技术栈: Vue3AxiosElementPlus 2.后端技…

序列密码算法ShanLooog512设计原理详解

序列密码算法ShanLooog512设计原理详解 ShanLooog512(闪龙512)为序列密码算法&#xff0c;内部状态为512比特&#xff0c;密钥长度为128或256比特&#xff0c;轮函数为FFFFFFFF&#xff0c;循环轮数为24轮&#xff0c;输出密钥流为512比特的状态。与Salsa20类似&#xff0c;内…