【C语言】深入浅出理解指针及内存与指针的关系(详细讲解+代码展示)上

简介: 笔记

概述


指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的储存空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。


为了了解指针,我们首先需要讲解一下内存的概念。


内存

内存含义

存储器:在计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。

内存:内部存贮器,暂存程序/数据——掉电丢失 SRAM、DRAM、DDR、DDR2、DDR3。

外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND、NOR)、硬盘、光盘。


4.jpeg


内存是沟通CPU与硬盘的桥梁


内存作用:

暂存放CPU中的运算数据

暂存与硬盘等外部存储器交换的数据

5.jpeg


物理存储器和存储地址空间

有关内存的两个概念:物理存储器和存储地址空间。


物理存储器:实际存在的具体存储器芯片。

主板上装插的内存条

显示卡上的显示RAM芯片

各种适配卡上的RAM芯片和ROM芯片

我们所有的数据都存在内存中,对每一个物理存储单元,分配一个单元,我们称之为编码,可以根据分配的号码找到相应的存储单元,也称为寻址 。


存储地址空间:对存储器编码的范围。

编码:对每个物理存储单元(一个字节)分配一个号码 。

寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写 。

6.jpeg


内存地址

在这里,我们将内存抽象成一个很大很大的一维字符数组。编码就是对内存的每一个字节去分配一个32位或64位的编号(位数与自己的处理器有关)。儿这个内存编号我们称其为内存地址。       内存中的每一个数据都会分配相应的地址,例如:char:占一个字节,分配一个地址 。int:占四个字节分配四个地址等。

7.png



指针和指针变量

内存区中的每一个字节都是有一个编号的,这个编号就是“地址”。如果我们在程序中定义了一个变量,在对这个程序进行编译或者运行的时候,系统会自动给这个变量分配内存单元,确定其地址。


我们将要学习的指针就是内存单元的编号,而指针变量就是存放地址的一个变量。我们平时通常会把指针变量称作指针,但是指针变量与指针的含义是完全不同的。

8.png



从这张图中就可以看出,b_point是指针,其存放了变量b的内存地址,也就是指针变量。


指针基础知识

上面我们了解完了内存以及内存和指针关系之后,那么接下来我们就来开始指针的学习吧!


指针变量的定义和使用

首先我们不要将指针想象的那么特殊,指针就是是一种数据类型,而指针变量也是一种变量,指针变量只想谁,就把谁的地址赋值给指针变量。我们使用“ * ”操作符操作指针变量指向的内存空间。而我们平时使用指针,主要是因为使用指针往往可以生成更高效、更紧凑的代码。


声明一个指针

上面说到指针就是一个变量所以,指针的声明方式与一般的变量声明方式没太大区别:

int *p ;        // 声明一个 int 类型的指针 p
char *p ;      // 声明一个 char 类型的指针 p
int *arr[10]   // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指针。
int (*arr)[10] // 声明一个数组指针,该指针指向一个 int 类型的一维数组
int **p;       // 声明一个指针 p ,该指针指向一个 int 类型的指针,也就是我们的二级指针。

初始化一个指针

声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化,或是使他指向现有的内存,或者是给他动态分配内存,否则我们并不知道指针指向哪儿,这将是一个很严重的问题,也是我们后面所提到的野指针。所以我们在定义指针后一定要进行初始化,而初始化操作具体如下


方法1:使指针指向现有的内存  

int n = 1;
int *p = &n;  // 指针 p 被初始化,指向变量 x ,其中取地址符 & 用于产生操作数内存地址

方法2:动态分配内存给指针

int *p ;
p = (int *)malloc(sizeof(int) * 10) ;    // malloc 函数用于动态分配内存
free(p) ;    // free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用。

总结:

#include <stdio.h>
int main()
{
  int a = 0;
  char b = 100;
  printf("%p, %p\n", &a, &b); //输出a, b的地址
  //int * 代表是一种数据类型,int*指针类型,p才是变量名
  int *p;
  p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
  printf("%d\n", *p);//p指向了a的地址,*p就是a的值
  char *p1 = &b;
  printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
  return 0;
}

附: &可以取得一个变量在内存中的地址。但是不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

所以这里将上面所讲的部分总结为代码,也希望大家都可以去看懂并理解这些内容。

#include <stdio.h>
int main()
{
  int a = 0 ;
  int b = 11 ;
  int *p = &a ;
  *p = 100 ;
  printf("a = %d, *p = %d\n", a, *p) ;
  p = &b;
  *p = 22;
  printf("b = %d, *p = %d\n", b, *p) ;
}

运行结果:9.png


指针大小

测量指针大小时我们需要使用sizeof()。


sizeof()是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小。


sizeof 运算符可用于获取类、结构、共用体和其他用户自定义数据类型的大小。其使用语法如下:


sizeof (data type)

其中,data type 是要计算大小的数据类型,其实包括类、结构、共用体以及其他用户自定义数据类型。


并且sizeof()测的是指针变量指向存储地址的大小,在32位平台,所有的指针(地址)都是32位(4字节),同理在64位平台,所有的指针(地址)都是64位(8字节) 。

#include <stdio.h>
int main()
{
  int *p1;
  int **p2;
  char *p3;
  char **p4;
  printf("sizeof(p1) = %d\n", sizeof(p1));
  printf("sizeof(p2) = %d\n", sizeof(p2));
  printf("sizeof(p3) = %d\n", sizeof(p3));
  printf("sizeof(p4) = %d\n", sizeof(p4));
  printf("sizeof(double *) = %d\n", sizeof(double *));
}

运行结果10.png

其中我们可以看出,我们在定义了多级指针之后,通过输出发现,其内存大小是相同的,所以内存大小是不受指针级数的影响。


相关文章
|
10天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
22 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
10天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
58 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
10天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
34 9
|
10天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
32 7
|
10天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
36 6
|
11天前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
11天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
39 5
|
13天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
34 6
|
13天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
12天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
36 1