目录
一、指针的相关概念
(一)地址与指针
一个变量的地址称为该变量的指针
地址常量:变量和数组的地址是系统分配的,在程序运行过程中不可被改变
int a;
&a = 666; // 不能用一个整数给一个指针变量赋值
scanf("%d", &a); // 取址符&:取变量的地址
// 需要知道变量的地址才能往对应的地址中放入数据
printf("%p\n", &a); // %p是地址格式占位符(十六进制地址)
char str[10];
printf("%p\n", str); // 数组名代表数组的首地址
(二)指针变量
本质就是一个变量
只不过是C语言中专门用于存储地址的一类型变量
1、定义指针变量
数据类型 *指针变量名(*修饰指针变量名)
指针变量名:需要符合标识符的命名规则
数据类型:指针变量定义时的数据类型称为这个指针的“基类型” ,指针变量的基类型就决定着它能够存储什么类型的地址
int a = 123;
int *p; // 定义了一个基类型为int类型的指针变量,名为p
p = &a; // 指针变量可以用于存储地址,地址是个常量不能修改
p = a; // 这是错误的,指针变量不能存储变量的值,也不能存储实际数据
2、初始化指针变量,避免指针变成野指针
若没有初始化指针变量,指针的指向是随机地址编号,指针变成野指针
指针需要有指向之后才能够引用地址中的值,如果没有合法的空间,可以初始化为*p=NULL
3、解引用
引用指针所指向地址中的值
*p表示取p所保存的地址对应的空间内容
int a = 123,*p = &a;
*p = 987; // *p:指针的“解引用”
printf("a=%d\n", a); // 987 可以通过指针间接改变变量的值
printf("*p=%d\n", *p); // 987
&取地址符和*指针解引用符区别
num的类型是 int 类型,&num的类型是 int * 类型
p的类型是 int *类型 ,*p的类型是 int 类型
高级总结:如果&和*同时存在可以相互抵消(从右‐‐>左)
论证:*p == num ,*p = *&num == num,&*&*&num == &num
int num = 10;
int *p;
p = #
printf("p=%p\n",p); // 0019FED8
printf("&*&**&p=%p\n",&*&**&p); // 0019FED8
4、指针所占用字节数
和其基类型无关
一般的指针类型所占字节数为4字节,因为指针变量只是用于存储地址而已,一个地址占用8个十六进制位,也就是32个二进制位,也就是4个字节
printf("&a=%d\n", &a); // 以十进制的形式输出a的地址
printf("p=%d\n", p); // p中存储的是a的地址&a
printf("&p=%d\n", &p); // 指针p也有自己的存储空间:指针的地址
// 指针变量也是变量,也会占用存储空间
printf("sizeof(a)=%d\n", sizeof(a)); // 4字节
// 指针变量占用的内存大小是多少?
printf("sizeof(p)=%d\n", sizeof(p)); // 4字节?4
char *q;
double *r;
printf("sizeof(q)=%d\n", sizeof(q)); // 1字节?4
printf("sizeof(r)=%d\n", sizeof(r)); // 8字节?4
5、指针变量的类型(取值宽度、跨度)
对于int *p;
指针变量自身类型:只将指针变量名拖黑剩下啥类型指针变量自身就是啥类型。 p自身的类型是int *
指针变量所指向的类型:将指针变量名和离它最近的*一起拖黑剩下啥类型就指向啥类型。p所指向的类型是int
例如int ***p;自身类型为int ***,所指向的类型为int **
指针变量的取值宽度,即取多少字节的内容出来
宽度:由指针变量所指向类型长度决定
计算机是小端存储,按照04030201顺序存储,按照01020304顺序读取
p指向int类型,宽度为4字节,输出0x1020304
int num = 0x01020304;
int *p;
short *p2;
char *p3;
p=#
p2=#
p3=#
printf("*p = %#x\n", *p);
printf("*p2 = %#x\n", *p2);
printf("*p3 = %#x\n", *p3);
指针变量的跨度,即将指针从首地址移动多少字节
跨度:由指针变量所指向类型长度决定
+1指的是跨过相应类型的字节数
int num = 0x01020304;
int *p;
short *p2;
char *p3;
p=#
p2=#
p3=#
printf("p=%u\n",p);
printf("p+1=%u\n",p+1); // 跨度是四个字节
printf("p2=%u\n",p2);
printf("p2+1=%u\n",p2+1);
printf("p3=%u\n",p3);
printf("p3+1=%u\n",p3+1);
应用1:自定义指针变量p,int num = 0x1020304,取出0x0102
思路:将指针移动两个字节,然后读取两个字节宽度
int num = 0x01020304; // 存储格式为04030201
short *p = #
printf("%#x\n",*(p+1)); // 0x102
应用2:自定义指针变量p,int num = 0x1020304,取出0x0203
思路:将指针移动1个字节,然后读取两个字节宽度
当宽度与跨度不等时需要进行强制类型转换,选择min(宽度、跨度)定义指针变量
int num = 0x01020304; // 存储格式为04030201
char *p = #
short *p2 = #
printf("%#x\n", *(short *)(p+1)); // 0x203
printf("%#x\n", *(short *)((char *)p2+1)); // 临时改为1字节,跨一字节,0x203
6、注意事项
-
void不能定义变量,void *可以定义变量p,但是对于p不能直接使用*p操作
void num; // 不知道num大小
void *p; // void *指针类型,32位平台系统知道指针类型的大小为4字节
// 对于p不能直接使用*p操作,需要对其进行强制类型转换
int num = 10;
void *p = #
printf("*p = %d\n", *p); // 错误,因为p的指向类型为void,系统确定不了宽度
printf("*p = %d\n", *(int *)p); // p临时的指向类型为int,系统确定宽度4B
-
定义一个指针变量p但没有初始化,p指向了一个未知空间,系统不允许取值*p操作
-
指针指向NULL,NULL是(void *)0地址,是内存的起始地址,受系统保护,不允许取值*p操作
-
不要给指针变量赋普通的数值
-
指针变量不要操作越界的空间,即类型需要匹配
char num=10;
int *p = #
// num只占1B空间 而p的指向类型为int 所以*p取值宽度为4B,所以越界3B
二、通过指针引用数组、字符串
(一)指向数组元素的指针
int *p;
p = arr; // 数组的数组名代表数组的首地址
p = &arr[0]; // 使指针p指向数组的首地址(数组第一个元素的地址)
-
指针带下标的形式访问数组元素
printf("%d\t", p[i]); // []自带解引用
-
指针指向偏移的形式访问数组元素
printf("%d\t", *(p + i));
1、指针指向的偏移
-
指针的指向向后或向前移动几个基类型的内存
-
如:p+1就是从p指针指向的当前位置向后偏移一个基类型的内存
-
指针本身的位置并没有变化
2、指针本身的偏移
-
很少用指针本身的偏移
-
指针本身向前或向后移动多个基类型的内存
-
以指针本身的偏移访问整个数组后,不能再继续偏移输出,因为指针移动的位数大于数组的大小,最后输出乱值
arr[]={0,1,2,3,4,5,6,7,8,9};
p = &arr[2];
printf("%d\n", *p); // 输出2
p++; // 3
p + 1;
printf("%d\n", *p); // 2 并没有改变指针p的值
3、数组的[]和*()的关系(重要)
本质:[ ]是*( )的缩写
缩写规则:+左边的值 放在[ ]左边,+右边的值 放在[ ]里面
例如:arr[1] == *(arr+1) == *(1+arr) == 1[arr]
arr代表的是第0个元素的地址(&arr[0])&arr[0] == &*(arr+0) == arr+0 == arr
// 数组名arr 作为类型 代表的是数组的总大小 sizeof(arr)
// 数组名arr 作为地址 代表的是首元素地址(第0个元素的地址)
int arr[5]={10,20,30,40,50};
int n = sizeof(arr)/sizeof(arr[0]);
int *p = NULL;
// p = &arr[0];
// arr == &arr[0]
p = arr;
4、arr和&arr的区别(了解)
arr:数组的首元素地址,arr+1:跳过一个元素
&arr:数组的首地址,&arr+1:跳过整个数组
arr与&arr虽然地址编号一样,但是类型完全不一样
数组名arr是一个地址常量,不能被赋值
int arr[5]={10,20,30,40,50};
printf("arr = %u\n",arr);
printf("arr+1 = %u\n",arr+1);
printf("‐‐‐‐‐‐‐‐‐‐‐‐‐\n");
printf("&arr = %u\n",&arr);
printf("&arr+1 = %u\n",&arr+1);
5、指向同一数组中两个元素的指针变量之间的关系
-
指向同一数组的两个指针变量
相减,返回的是相差元素的个数
可以比较大小 > < >= <= == !=
可以相互赋值
尽量不要相加,避免越界
指针变量里面在不越界的情况下可以为负数
int *p2 = arr+3;
printf("%d\n",p2[‐2]);
p2[-2]=*(p2-2)=*(arr+3-2)=*(arr+1)=arr[1]=20
void test11()
{
int arr[5]={10,20,30,40,50};
int *p1 = arr; // 10
int *p2 = arr+3; // 40
printf("%d\n",p2‐p1); // p2=p1+3 p2-p1=3
if(p2>p1)
{
printf(">\n");
}
else
{
printf("<=\n");
}
p1 = p2; // p1和 p2指向同一处
printf("p2=%u\n",p2);
// p1+1是移动一个数组元素空间,p1+p2是移动p2大小倍的数组元素空间,越界了
printf("%d\n",p2[‐2]); // p2[-2]=*(p2-2)=*(arr+3-2)=*(arr+1)=arr[1]=20
}
6、实例
-
++、*同一优先级,结合性从右至左
int arr[5]={10,20,30,40,50};
int *p = arr;
printf("%d\n", *p++); // 10
printf("%d\n", (*p)++); // 20
printf("%d\n", *(p++)); // 21
-
*p++:因为后置++的特性(先使用后加减),所以这里p没有移动,输出10,然后指针右移
-
(*p)++:*p是20,输出20,然后令(*p)=(*p)+1,数组中第二个元素20被改为21
-
*(p++):p先与*结合使用(小括号没起作用),输出21,然后再指针右移
(二)通过指针引用字符串
char *q = "abcedfg";
puts(q); // 字符串输出
char str[10] = "123456";
q = str;
puts(q);