【C语言】万字速通初阶指针 zero → One

简介: 携带地址信息,否则电脑不知道读写那块内存。不管程序是用什么语言写的,要运行数据和代码必须驻留内存,CPU要执行指令必须有一个"指针"程序计数器指向内存的代码块,如果某个指令要操作内存数据,该指令必须携带额外的地址信息 🌹指针的缺点→指针可以操作任何东西,所以指针很灵活、很强大,但


指针是C语言的显著的优势之一,其中使用起来是十分的灵活。而且是能够高效率的提高程序的使用,但是,如果使用使用不恰当的话程序是很容易被"挂死"的往往都是错误导致指针造成的。之所以C语言到现在还能够适应时代其中指针是不可或缺的 🎄


那再来说说指针的优缺点吧。


指针的优点→是标识一块内存。电脑内存上的每一个字节都具有一个编号,称为地址(可以简单理解为指针),任何读写内存的指令都必须携带地址信息,否则电脑不知道读写那块内存。不管程序是用什么语言写的,要运行数据和代码必须驻留内存,CPU要执行指令必须有一个"指针"程序计数器指向内存的代码块,如果某个指令要操作内存数据,该指令必须携带额外的地址信息 🌹


指针的缺点→指针可以操作任何东西,所以指针很灵活、很强大,但也引入了复杂性 🎋


🍁 前言

指针!指针!指针!重要的事情说三遍,之所以这样说是因为指针对于我们学习C语言真的是特别特别的重要。可以说会指针和不会指针那就是天壤之别 🤐


你想要成为"C语言大佬"指针就必须玩的起来,这样就是你成为大佬的第一步,相比之前的内容,指针会难上一点,但只要肯下功夫,多多打磨、多去理解、多去上手练习,迟早你就能把指针玩开了 😋


有些初始C语言的小伙伴们,可能一遇到指针就会放弃或者对指针不够重视。千万不能有这样的想法,你想学习C语言到后面的话指针是你一定要跨越的"山峰",当你跨过这段"山峰"的时候到达顶端时候,你就会感慨值了",所以加油,干就完事了 😤


指针是C语言的显著的优势之一,其中使用是十分灵活的而且能提高某些程序的效率,但是如果使用不当则很容易造成系统错误。许多程序"挂死"往往都是错误地使用指针造成的 😱  


⚔ 内存

计算机当中所有的数据都是必须要放在内存当中的,不同类型的数据占用的字节数不一样。


如果当我们买回来的计算机当中有 4g 内存或者 8g 内存空间,那么我们因该如何去使用它们呢🤔


解释:内存(空间)的使用跟我们"现实"生活当中有非常相似的地方,在我们"现实"生活当中国土面积总共有960万平方公里,就像是当我们真的是去访问这些内存空间的时候,都给了它们的一个有效的地址。比如:这个时候我们想去找到一个地方,有省,其次市、县、镇、乡这样不同的规划。然后找到你人在哪里,而我们现实生活中市怎么样找到这一块空间的那就是通过地址,而这个地址又是跟我们一个个"房间"是相互关联的。我们是不是跟每一个房间都整理了编号,然后通过地址就可以找到房间⇩


那么其实对于内存也是一样的,内存是一块大的空间,如下流程图所示⇩


image.png


当我把内存划分成这样一个长方体的格子之后, 那么其实就是和生活当中的房间是一样我们现实生活当中给每一个房间都编了号。


而我们内存空间也是一样,划分着每一个格子也相应的进行编号。这就是内存空间的管理方式。


🗡 地址与指针

概述:在计算机中,所有的数据都是存放存储器内存当中。一般把存储器中的一个字节称为一个 内存 占用的内存单元数不等,如整型量占 4 个单元,字符量占 1 个单元等,在前面已有详细的介绍。为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确的找到该内存单元,内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元可以用一个通俗的例子来说明 它们之间的 到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 取款的金额。在这里,帐号就是存单的指针, 存款数 是存单的内容。对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元 的内容。 语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针 🤩


Ⅰ地址就是内存区中对每个字节的编号。


Ⅱ指针看作是内存中的一个地址,多数情况下,这个地址是内存中另一个变量的位置。


在程序中定义一个变量,在进行编译的时候就会给改变了在内存当中分配一个地址,通过访问这个地址可以找到所需的变量,这个变量的地址称之为该变量的 "指针"


🍊指针就是用来存储内存变量的当中的地址!①个内存单元 == ①字节 。


🍊地址就是用来通过内存区的编号找到变量,然后再把自己内存区的编号赋值给指针。


从上述的话就可以看出来,为什么也会说其实指针无非就是地址,地址无非就是指针了。


🗡 变量与指针

变量的地址是 变量 和 指针 二者之间纽带,如果一个变量包含了另一个变量的地址,则可以理解成第一个变量指向第二个变量。所谓 "指向" 就是通过地址来进行实现的。 因为指针变量是指向一个变量的地址,所以将一个变量的地址值 赋值给这个指针就 "指向" 了该变量 😜


例如:将变量 i 的地址值赋值给指针变量 p 中,p 就指向 i,其关系如下所示↓


image.png


在程序代码中是通过 变量名 对 内存 单元进行存取操作的,但是代码经过编译后已经将变量名转换为该变量在内存中存放的地址,对变量值的存取都是通过地址进行的。如下代码所示


a+b

其含义是:根据变量名与地址的对应关系,找到变量 a 的地址。假设 1000,找到变量 i 的地址 1000,然后从 1000 开始读取 4 个字节(整形类型)存放在 CPU 的寄存器当中,再找到变量 j 的地址 1004 ,从 1004 开始读取 4 个字节数据存放在 CPU 另一个寄存器当中,通过 CPU 的加减法从中计算出来 🙄


⚔ 定义指针变量

对指针变量的定义包括 ③ 个内容↓


指针类型说明,即为变量为一个指针变量。

指针变量名。

变量值(指针)所指向的变量的。

定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号(*) 格式如下所示↓


datatype *name;或者 datatype *name = value;

解释:* 表示一个指针变量,datatype 表示该 指针变量所指向的数据的类型。如↓

int *p;// %p打印地址

表示 p 这是一个指针变量,变量名即为指针的变量名,类型说明符表示本指针变量所指向的变量的数据类型

p 是指向int 类型数据的指针变量,至于究竟指向哪一份数据,应该由赋予它的值决定。如下↓代码所示。

int *p1;   // p1 是指向整形变量的指针
float *p2; // p2 是指向浮点变量的指针
char *p3;  // p3 是指向浮点变量的指针

🔥注意:个指针变量只能是指向同类型的变量。

1. int a = 100;    
2. int *pa = &a;

(1) a在内存中要分配空间4个字节。

(2) 取出a的地址赋值给指针变量papa说明执行对象是int类型。

🔥注意→取地址a并不会拿出4个字节的地址,只会拿出第一个字节地址。

1字节 = 8比特位,按照十六(0x)进制的方式来的。


💣有效声明指针

有效指针,顾名思义就是可以有效的在程序当中运行不会出现错误的指针类型 😐

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

🗡使用指针

使用指针时会频繁进行以下几个操作:定义一个指针变量把变量地址赋值给指针访问指针变量中可用地址的值。这些是通过使用一元运算符(*)来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作如下↓

#include<stdio.h>
int main(void)
{
    int a = 20;                //a在内存中要分配空间4个字节
  int *pa = &a;              //取出a的地址赋值给指针变量pa, pa说明执行对象是int类型!
  printf("无改变:%d\n", *pa);
  *pa = 30;//进行解引用操作符 *pa 就是通过解引用(pa)里边的地址来找到地址a的值。
  printf("改变的:%d\n", *pa);//解引用操作符是可以改变取地址原来的值的!
  return 0;
}

运行结果🖊


a = 20


b = 30


知识内容→上面的结果也是通过指针变量取得数据, 然后再通过解引用(*)操作符改变取地址原来的值。指针的解引用可以获取地址赋值给指针变量从而获取数值的大小(这个是初学者有时候不明白的地方,不懂可以多看几遍或者自己上手代码进行调试)


💣指针变量初始化

指针变量初始化是非常重要的,很多初学指针的小伙伴们很容易就会把指针没有进行指针变量的初始化。


如下代码所示↓

#include<stdio.h>
int main(void)
{
    int a = 10; 
    int *p = &a;
    printf("*p = %d\n",p);
    return 0;
}

🔥注意→上述代码是错误的,在打印printf的时候,p并没有对其进行解引用操作(找不到p的数值) 此时p是找不到a的地址的,也可以说并没有指向&a。

如下代码所示↓

#include<stdio.h>
int main(void)
{
    int a = 10; 
    int *p = &a;
    printf("*p = %d\n",*p);
    return 0;
}

此时,对其中打印 printf 进行解引用操作找到 a 的地址。运行结果为:*p = 10

🔥注意→在使用指针的时候,是必须要给指针变量进行初始化的,不然就会是野指针。关于野指针是什么这个在后面会说的。  


💣赋值语句的方法

如下代码所示↓

1. int a;
2. int *p;
3. p= &a;

不允许把一个数赋予指针变量,故下面的赋值是错误的。

1. int * p;
2. p = 10;
被赋值的指针变量前不能再加“*”说明符,如写为 *p=&a 也是错误的。

另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向。


🗡指针变量的大小

如下代码所示

#include<stdio.h>
int main(void)
{
    printf("%d\n", sizeof(int *));
  printf("%d\n", sizeof(long *));
  printf("%d\n", sizeof(long long*));
  printf("%d\n", sizeof(float *));
  printf("%d\n", sizeof(double *));
  printf("%d\n", sizeof(short *));
  return 0;
}

运行结果🖊


④(全部)


从上述结果可以看出指针变量都是④个字节。


为什么不同类型的变量的地址所占的字节数都是一样的呢。


原因是:他们数据类型都是指针类型,切记(☆-v-)


🔥注意→这个是在32位的操作系统 = 4字节,64位的操作系统上 = 8字节。


⚔ 上述总结

指针就是变量,用来存放地址的变量。(存放在指针当中的值都是会被当做是地址来处理)


还有下列②个问题如下↓


一个小的单元到底是多大(①个字节)

如何进行编址。

经过仔细的计算,一个字节(⑧比特位)对应其一个地址是比较合适的(①字节等于①地址)


指针就是用来存储地址的,地址是唯一表示一块地址空间。


指针大小在32位的平台上是④个字节,在64位上的平台是⑧个字节。


在32位平台上产生的地址线就是由 32个0&1组成的地址线,在64位平台上产生的地址线就是由64个二进制0&1组成的地址线。


💣 * 和 & 认识

假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a和&*pa分别是什么意思呢?


*&a可以理解为*(&a),&a表示取变量 a 的地址(等价于 pa),*(&a)表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,*&a仍然等价于 a


&*pa可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa


💣 对解引用(*)认识

表示乘法,例如 int a = 3, b = 5, c;  c = a * b; 这是最容易理解的。


表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;


表示获取指针指向的数据,是一种间接操作,例如 int a, b, *p = &a; *p = 100; b = *p;


表示获取指针指向的数据,是一种间接操作。这里我来举出一个代码例子↓

#include<stdio.h>
int main(void)
{
  int a = 10;
  int *p = &a;
  printf(" a = %d\n", a);
  printf("*p = %d\n", *p);
    //注意:解引用改变p,同时也会改变指向a的地址(改变a的值同样p也会跟着改变)
  *p = 50;
  printf(" a = %d\n", a);
  printf("*p = %d\n", *p);
  int b = *p;
  printf(" b = %d", b);
  return 0;
}

运行结果🖊

a = 10
*p = 10
a = 50
*p = 50
b = 50

这里大家可以好好思考下为什么,为什么运行结果是这样看看能不能说出来。这样你才能真正的掌握这些知识点。


🗡 " * & 的应用"

如下所示👇

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main(void)
{
  int i = 0;
  int* p = &i;
  printf("请输入数字:");
  scanf("%d", &i);
  printf("numbers1 = %d\n", *&i);
  printf("numbers1 = %d\n", i);
  printf("numbers1 = %d\n", *p);
  return 0;
}

行结果

三个 printf() 打印的值都是一样的(同样可以思考下这个是为什么)


🗡 通过指针交换变量值

既然都看到这里了,那么我们就来尝试做一道题目吧,看看你是否掌握这些知识点了。

题目内容:用指针交换两个值。

#include <stdio.h>
void swap(int *pa, int *pb)
{
  int temp = *pa; //10  
  *pa = *pb;      //20
  *pb = temp;     //10
}
int main(void)
{
  int a = 10, b = 20;
  int *pa = &a, *pb = &b;
  printf("交换前的值:a=%d, b=%d\n", a, b);
  swap(pa,pb);
  printf("交换后的值:a=%d, b=%d\n", a, b);
  return 0;
}

运行结果🖊


交换前的值:a = 10, b = 20


交换后的值:a = 20, b = 10


对程序进行说明如下↓


swap()是用户定义的函数,它的作用是交换两个变量(a 和 b)的值。swap 函数的形参 pa、pb 是指针变量。程序运行时,先执行 main 函数,已知 a 和 b 的值。然后将 a 和 b的地址分别赋给指针变量 pa 和 pb,使 pa 指向 a,pb 指向 b


用图形表示如下如下↓

image.png


image.pngimage.pngimage.png

目录
相关文章
|
1月前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
36 1
|
24天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
23天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
23天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
23天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
30天前
|
存储 C语言
C语言32位或64位平台下指针的大小
在32位平台上,C语言中指针的大小通常为4字节;而在64位平台上,指针的大小通常为8字节。这反映了不同平台对内存地址空间的不同处理方式。
|
29天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
30天前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
30天前
|
C语言
C语言指针(3)
C语言指针(3)
11 1
|
30天前
|
C语言
C语言指针(2)
C语言指针(2)
13 1