理解指针

指针就是地址,地址就是指针,地址就是内存单元的编号(变量在内存中所占的存储单元的地址称为指针)。

指针变量就是存放内存地址的变量,存储的是地址(很多情况下人们说的指针p其实代表的是指针变量p)。

指针和指针变量是两个不同的概念,但要注意的是,通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。

eg:指针里存的是100. 指针:地址 – 具体。

指针里存的是地址,指针:指针变量 – 可变。

img

知识点1【类型】

数据类型的本质作用:合理的利用空间

1
2
3
4
5
6
7
8
9
10
void test01()
{
//基本类型char short int 1ong f1oat double
// char 占1字节空间
char ch;
printf("sizeof(ch)=d\n" ,sizeof(ch)); //1字节

int num;
printf("sizeof (int)=%d\n",sizeof (int)); //4字节
}

1B ==8位b(二进制位)

1b只能存放0或1

img

知识点2【内存】

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

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

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

图示  描述已自动生成img

知识点3【指针变量】

图示  描述已自动生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void test02()
{
int num = 100;
//取变量的地址用&
//&num 代表变量num的起始地址
printf("%p\n",&num) ;

//需求:定义一个指针变量保存num的地址
//在定义的时候:*说明p是指针变量而不是普通变量
int *p=NULL;
printf("sizeof(p)=%d\n", sizeof(p));

//num的地址与p建立关系
p = #
printf("num = %d\n", num) ;//100
//使用中:p表示取p保存的地址编号对应空间的内容
printf("*p = %d\n",*p);//100
}

知识点4【指针P、*P、&P】

&是取地址运算符;(地址)是取值运算符*

  • p:p是一个指针变量的名字,表示此指针变量指向的内存地址

  • *p:*p表示此指针指向的内存地址中存放的内容

  • &p:&是取地址运算符,&p就是取指针p的地址

  • *&p:等价于*(&p)= &*p: 等价于&(*p)=p

文本  中度可信度描述已自动生成

知识点5【指针变量两种类型】

  • 自身的类型:在指针变量定义的时候将变量名拖黑剩下啥类型指针变量就是啥类型

    e.g: p自身的类型就是int *

  • 指向的类型:在指针变量定义的时候将变量名和离它最近的一个*一起拖黑剩下啥类型指针变量指向的类型就是啥类型额

    e.g: 指向的类型是int

指针变量指向的类型的作用:决定了指针变量所取空间内容的宽度,决定了指针变量+1跳过的单位跨度

图形用户界面, 文本  描述已自动生成

img

需求:定义一指着变量p4提取 “0x02 0x01”

1
2
3
4
5
short *p4=#

p4+=1;

printf(“*p4=%#x\n”,*p4);//ox01012

图片包含 图示  描述已自动生成

知识点6【值传递、指针传递和引用传递】

  1. 值传递就是最普通的传递方式,比如函数定义为fun(int a),在调用的地方有int x=6, 使用fun(x)就可以了。这种方式在fun(int a)函数内部的对a的修改 不能 导致外部x的变化。

  2. 指针传递其实也就是地址传递,函数定义为fun(int *a),形参为指针,这就要求调用的时候传递进去一个参数的地址,例如int x=6; fun(&x)。 这种方式在fun(int a)函数内部的对a的修改 导致外部x的变化。

  3. 引用传递只有C++支持,相比前两种方式用的比较少,但也非常有用。引用传递函数定义为fun(int &a),这里&符号是引用而不是取地址的意思,调用方式和值传递类似,例如int x=6; fun(x)。 但是这种方式在fun(int a)函数内部的对a的修改 导致外部x的变化。

image-20220706234401086

知识点7【指针型结构体顺序表定义】

1
2
3
4
5
6
7
8
9
// InitList(SqList *&L):初始化顺序表
// DestroyList(SqList *&L):释放顺序表L
// ListEmpty(SqList *&L):判断顺序表L是否为空表
// ListLength(SqList *&L):返回顺序表L的元素个数
// DispList(SqList *&L):输出顺序表L
// GetElem(SqList *&L,int i,ElemType &e):获取顺序表L中的第I个元素
// LocateElem(SqList *&L,ElemType e):在顺序表L中查找元素e
// ListInsert(SqList *&L,int i,ElemType e):在顺序表L中第i个位置插入元素e
// ListDelete(SqList *&L,ElemType &e):从顺序表中删除第i个元素

image-20220706235305541

知识点8【二级指针】

二级指针也是一个普通的指针变量,只是它里面保存的值是另外一个一级指针的地址

二级指针的定义:指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。

假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:

image-20220706235634469

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdlib.h>
int main( )
{
int a=1;
printf ( "&a=%d\n",&a);
int *p=&a;
printf ( "p=%d\n",p);
printf ( "&p=%d\n",&p);
int **q=&p;
printf("*q=%d\n",*q);
printf("&*q=%d\n", &*q);
printf ( "q=%d\n",q);
printf ( "&q=%d\n",&q);
return( 0);
}
1
2
3
4
5
6
7
&a=-925866372
p=-925866372
&p=-925866384
*q=-925866372
&*q=-925866384
q=-925866384
&q=-925866392

image-20220707104042526

  • 多级指针

C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*

1
2
3
4
5
int a = 666;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
int ****p4 = &p3;

p1 是一级指针,指向普通类型的数据;

p2 是二级指针,指向一级指针 p1;

p3、p4同理……

  • 二级指针形象例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
int main(void){
int guizi2 = 888; //存枪的第 2 个柜子
int *guizi1 = &guizi2; //存第 2 个柜子地址的第一个柜子
int **liujian = &guizi1; //手握第一个柜子地址的刘建
printf("刘建打开第一个柜子,获得第二个柜子的地址:0x%p\n", *liujian);
printf("guizi2 的地址:0x%p\n", &guizi2);
int *tmp;
tmp = *liujian;
printf("访问第二个柜子的地址,拿到枪:%d\n", *tmp);
printf("刘建一步到位拿到枪:%d\n", **liujian); //缩写成 **liujian
system("pause");
return 0;
}

image-20220707000146428

  • 二级指针的用途

二级指针作为函数参数的作用:在函数外部定义一个指针p,在函数内给指针赋值,函数结束后对指针p生效,那么我们就需要二级指针。

看看下面一段代码:有两个变量a,b,指针q,q指向a,我们想让q指向b,在函数里面实现。

1.先看看一级指针的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>

using namespace std;

int a= 10;
int b = 100;
int *q;

void func(int *p)
{
cout<<"func:&p="<<&p<<",p="<<p<<endl; //note:3
p = &b;
cout<<"func:&p="<<&p<<",p="<<p<<endl; //note:4
}

int main()
{
cout<<"&a="<<&a<<",&b="<<&b<<",&q="<<&q<<endl; //note:1
q = &a;
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl; //note:2
func(q);
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl; //note:5

system("pause");
return 0;
}

这么写有什么问题?为什么*q不等于100?我们看一下输出便知:

1
2
3
4
5
&a=0032F000,&b=0032F004,&q=0032F228
*q=10,q=0032F000,&q=0032F228
func:&p=0018FD24,p=0032F000
func:&p=0018FD24,p=0032F004
*q=10,q=0032F000,&q=0032F228

我们看输出:

step1:a,b,q都有一个地址.

step2:q指向a.

step3:参数p的地址变了,跟q不一样了,参数传递是制作了一个副本,也就是p和q不是同一个指针,但是指向的地址0x0032F000(a的地址)还是不变的.

step4:p重新指向b.

step5:退出函数,p的修改并不会对q造成影响。

结论:

编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 q,编译器使 p = q(但是&p != &q,也就是他们并不在同一块内存地址,只是他们的内容一样,都是a的地址)。如果函数体内的程序修改了p的内容(比如在这里它指向b)。在本例中,p申请了新的内存,只是把 p所指的内存地址改变了(变成了b的地址,但是q指向的内存地址没有影响),所以在这里并不影响函数外的指针q。

这就需要二级指针操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>

using namespace std;

int a= 10;
int b = 100;
int *q;

void func(int **p)
{
cout<<"func:&p="<<&p<<",p="<<p<<endl;
*p = &b;
cout<<"func:&p="<<&p<<",p="<<p<<endl;
}


int main()
{
cout<<"&a="<<&a<<",&b="<<&b<<",&q="<<&q<<endl;//&a=0032F000,&b=0032F004,&q=0032F228
q = &a;
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;//*q=10,q=0032F000,&q=0032F228
func(&q);
cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl;

system("pause");
return 0;
}

因为二级指针p记录的是一级指针q的地址,即p=&q,所以修改*p为b的地址时就是将q地址指向内容也修改了。

image-20220707105109943

彩蛋🎮

image-20220707014428679

参考


理解指针
https://blog.baixf.tk/2022/07/05/c/理解指针/
作者
白小飞
发布于
2022年7月5日
许可协议