当前位置: 首页 > news >正文

民办生从零学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)。这种越界访问会导致未定义行为,可能读取到随机值或引发程序崩溃。

相关文章:

  • 辛格迪客户案例 | 华道生物细胞治疗生产及追溯项目(CGTS)
  • Qt内置图标速查表
  • 编译原理:由浅入深从语法树到文法类型
  • TMI投稿指南(三):共同作者
  • Unity-粒子系统:萤火虫粒子特效效果及参数
  • GPU虚拟化实现(四)
  • [实战] IRIG-B协议详解及Verilog实现(完整代码)
  • 【重走C++学习之路】22、C++11语法
  • vim粘贴代码格式错乱 排版错乱 缩进错乱 解决方案
  • C++(初阶)(十四)——多态
  • 程序进程多任务线程
  • el-dialog弹窗关闭时调了两次刷新数据的接口
  • Linux文件的一般权限
  • 在Spark集群中搭建Standalone
  • 2025“钉耙编程”中国大学生算法设计春季联赛(8)10031007
  • 嵌入式开发学习日志Day11
  • 【403 Error】Atcoder Beginner Contest 403 题解
  • Redo log,Undo log和binlog
  • 系统思考提升培训效能
  • 培养一个输出型的爱好
  • 腾讯重构混元大模型研发体系:成立大语言和多模态模型部,提升AI长期技术作战能力
  • 哈莉·贝瑞、洪常秀等出任戛纳主竞赛单元评委
  • 青海省林业和草原局副局长旦增主动投案,正接受审查调查
  • A股三大股指收跌:地产股领跌,银行股再度走强
  • 民生访谈|宝妈宝爸、毕业生、骑手……上海如何为不同人群提供就业保障
  • 商务部:4月份以来的出口总体延续平稳增长态势