6 指针
目录
一、指针是什么
-- 指针是地址 (也是一种数据类型)
(1)地址是什么?
a. 内存中用于cpu标识的一组编号 -- 是以十六进制的形式存储的
b. cpu 要么是32位,要么是64位的地址针
注:这里电脑是64位,而空间是16G,这是因为电脑还有内存条,一个内存条有8G,所以总共是16G。这里的RAM是动态内存,是正在使用的内存,如果关闭正在使用的内存,那么动态内存就会被释放。
c. 32位,地址是4字节,-- 4G 64位,地址是8字节, -- 8G
(2)为什么要用指针?
a. 数据传递时,容易出现值传递
输出a的值依旧是10
b. 嵌入式是底层程序,操作内容也是以地址的形式进行操作
c. 地址如何操作?
-- 程序没有能力直接操作内存地址 --c语言提供一种数据类型来操作地址 -- 指针
指针是一种数据类型,保存地址的数据类型
二、指针的意义
3、-- 保存变量空间的地址
三、指针的表现形式
4、指针类型: 类型 *
四、指针的空间大小?
5、-- 固定8字节
指针里面只保存地址,空间大小与类型无关,固定8字节 -- 因为电脑是64位
指针类型: "类型 *"
五、指针的运用
6、指针变量
(1)定义
a. 类型 *标识符 = 初始值
注:这里因为是指针类型,所以类型是类型 *,所以指针类型的变量就是类型 *标识符 = 初始值。如果是数组类型就是类型 标识符[长度],所以数组类型的变量就是类型 标识符[长度] = 初始值。
b. 标识符 组成:数字、字符、下划线
-- 不能是数字开头,不能是关键字,区分大小写,有意义
c. 初始值
-- 无初始化 - int *p -- 野指针(没有指向的空间) -- 避免使用,因为可能更改重要的地址(野指针不能被赋值)
-- 指定地址初始化 --int num = 10; int *p = &num --&取地址符,此时p中存放的是num空间的地址。
-- 指定空地址 -- int *p = NULL; -- 空指针; NULL -- 空地址-nil =》只读的地址。
(2)应用
A. 给指针变量赋值 -- &取地址符
-- int num = 10; int *p = &num --此时p中存放的是num空间的地址。
B. 通过指针变量操作地址空间 -- * -- 解空间
-- *的用法:
- * -- 乘法 -- a * b
- * -- 指针类型 -- int *p= NULL;
- * -- 解地址 -- *p -- 解p中存放的地址,得到地址空间的内容.
-- 赋值:*p = 10; -- *p表示指针变量中地址所指向的空间。
-- 取值:int b = *p; -- *p表示指针变量中地址所指向空间的值。
-- *p等同于变量名num
C. 一个指针指向多个地址空间 --指针是变量
注:const 修饰的是* p,那么* p的值不能改变,就是值不能改变,而p可以改变,也就是地址可以改变。
D. 多个指针指向一个地址空间
-- 其中一个指针改变该地址的值,另一个指针获取时也会改变
E. 指针偏移 -- 指针运算(重点)
注:首先,所有的常量都来自常量区,int num = 1;就是把常量区的值赋值到栈区,而栈区开辟空间默认是连续的,也就是说 int num1 =10;int num2=10;中num1和num2这两个空间的地址是连续的,所以地址偏移后,就是相邻的地址。
1)指针偏移一位,物理地址偏移一个数据类型的空间大小
-- p+1 -- 指针偏移一位 -- 因为指针p是int类型,int类型的空间大小是4字节,所以p的地址+4
-- (long)p 强转long整型数据
注:这里有个技巧就是,强转成什么类型,后面就以什么类型的长度为偏转。
2)* p++ -- ++*p -- *++p的区别
首先, * 和++运算符的优先级是相同的,而且是从右往左的执行顺序。例如 * p++,是先执行++运算符,这里的++运算符是指p的运算,这里的p++是先运行后++,也就是先把p给前面的运算符使用,先解地址得到值,并执行printf,而p的值也会改变,会在原来的编号后面加4字节,这里的编号不是真正的物理地址,相当于一个寄存器对物理地址的编号,一位是一个字节,所以直接加4
-- ++ * p,按照从右往左执行的顺序,先解地址得到值,后++,而++*p的特点是先++后赋值,这里的++运算符是指 * p的运算,所以 * p+1是11.
-- * ++p,按照从右往左执行的顺序,先++后解地址,而这里的++是++p,是对p先++后运行,所以P的地址+1后,再解地址得到20
3)指针与一维数组
tip:地址偏移:需要满足地址连续和是同样的数据类型,这两种条件都满足的数据类型是数组。所以这里将指针和数组放在一起研究。
-- 数组名代表数组的首地址 - 首个元素的地址
-- 使用数组名代替指针变量操作数组元素 -- 指针偏移
注:不能使用数组名++的形式,进行地址偏移 例如int num[10];-- num是数组的首地址,num++是错误的,因为num++有赋值特性,将首地址改变了,所以数组名用不了++。
-- 使用指针变量代替数组名操作数组元素 -- 数组名[下标]
int *p = num; p存放了num的首地址,所以p[0]就是num[0],p[1]就是num[1]
注:p[i] --元素空间 --[] 可以理解为解地址 --scanf("%d",&p[i]) --scanf("%d",p+i);
4)指针与字符数组
-- char ch[1024] = "hello";
char *p = ch; => char *p = "hello";
注:这里char *p= "hello";p存的是首个元素的地址,“hello”是常量,p的地址是常量区中存放“hello”的首地址,常量区的值是不可以被改变的,例如 * p = ‘H’; 是错误的。
5)数组指针与指针数组
a. 数组指针 - 是一个指针 - 是一个数组类型的指针 - 指向数组中的指针
-- 数组指针定义: 类型 (*标识符)[长度];
这里必须要加(),如果不加,先读的就是类型 *,就是指针类型了。
注:num是数组名,他只是指向首个元素的地址,而&num就是指向整个数组的地址,p1+1是加了一整个数组的空间大小。
b. 指针数组 -- 是一个数组 -- 指针类型的数组 -- 存放的是数组元素的地址
注:p[0]存的是地址,地址都是8字节的。 -- p存的是首个元素的地址,而这个元素也是地址,所以**q = P+2,q指向的是“rose”的地址。
c. p[0] --代表指针数组中第一个元素的值 --地址 --空间大小: 8
d. p --代表数组名 --首个元素的地址,首个元素是地址 --p的类型是地址的地址 --是个二级地址 --char **表示
6)指针与二维数组
注:二维数组相当于一维数组类型的一维数组,元素是一维数组
注:a是数组名,是int a[3]数组的首个元素的地址,而&a是整个数组的地址,相当于数组类型的指针int (*)[3]。 c[0] = a,c代表是首行元素的地址,上面说到[]有解地址的作用,这里c[]就代表是单个元素的地址,c[0]就是数组的首个元素的地址, c[0][0]是首个元素,而&[0][0]是首个元素的地址。
a. 如何获取二维数组的元素 -- 四种方式
-- 纯数组形式 - num[i][j]
-- 纯指针形式 - *( * (num+i)+j)
tip:
num --首行元素的地址
num+i --第i行元素的地址
*(num+i) --第i行首个元素的地址
*(num+i)+j --第i行第j个元素的地址
*( * (num+i)+j) --第i行第j个元素的值
-- 行数组列指针 -- *(num[i]+j)
tip:
num --首行元素的地址
num[i] --第i行元素的地址
num[i]+j --第i行第j个元素的地址
*(num[i]+j) --第i行第j个元素的值
-- 行指针列数组 -- (*(num+i))[j]
tip:
num --首行元素的地址
num+i --第i行元素的地址
*(num+i) --第i行首个元素的地址
( * (num+i))[j] --第i行第j个元素的值
理解 tip:int num[i][j] = {a,b}; a和b分别是一维数组,而由于int a[]={}; int b[]={}; ,所以a和b分别代表数组首元素的地址,num[0]=a,num[1]=b,这是行数组的形式。如果行是指针的话,那么num代表着数组首元素的地址,也就是a的地址,num+1就是b的地址,*num就是a,*(num+1)就是b,也就是b的首元素的地址。记住指针存的是地址,而[]有解地址的作用。
7)多级指针
tip:p指向变量的地址(存放),*q指向一级指针的地址(存放),**w指向二级指针的地址。每一个变量生成后都有地址,包括指针。