文章目录
前言
指针(pointer)是一个值为内存地址的变量(或数据对象),也可以说,指针就是地址
地址运算符:&
解引用运算符:* ;注意禁止解引用未初始化的指针
指针类型决定了指针解引用的权限有多大;决定了指针走一步有多大,指针加 1 指的是增加一个存储单元
一、指针
1.指针就是地址
内存怎么编号:
32 位——32 根地址线——通电下,0/1 组成二进制序列——2 的 32 次方个内存单元
一个内存单元就是一个字节(1 byte),那么 2 的 32 次方 byte 换算后就是 4GB
这也同时解释了为什么 32 位操作系统所支持的最大内存为 4GB
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址
2.地址运算符 &
用地址运算符 & 给出变量的存储地址
& 取一个变量的地址,只会拿出这个变量所占空间的第一个字节的地址
这里来调试观察一下一个 int 型变量的地址
将 8 赋给变量 num(int型,占4字节),这时 &num 取到的地址为 0x004FF760,也就是 num 的四个字节中第一个字节的地址(存放着8)
3.解引用运算符 *
*:解引用运算符,访问指针指向的地址存放的数据
指针变量用于存放地址(指针),同时内存会给指针变量开辟一个空间
例如:
int main()
{
int a = 8;
int* pa = NULL;//1、声明 int* 型变量 pa,并初始化为 NULL
pa = &a;//2、通过取地址符&,获取 a 的地址,然后赋值给指针变量 pa
printf("%d", *pa);//3、解引用 pa,获取指针指向的内容
//打印结果为 8
return 0;
}
注意不能解引用未初始化的指针,如
int* pa;
*pa = 3;//错误,这时 pa 成了野指针
取地址符 & 和解引用运算符 * 的共同点:
运算符的优先级都相同,都为2,且都是右结合性的
二、指针类型
1.不同类型的指针的大小是相同的,占4字节
2.指针类型决定了指针解引用的权限有多大(能操作几个字节)
int 型指针解引用访问 4 个字节,char 型指针解引用只访问 1 个字节**
int main()
{
int i = 0x11223344;
int* pi = &i;
*pi = 0;
printf("%#x\n", i);
int a = 0x11223344;
char* pc = &a;
*pc = 0;
//char* 型指针变量,访问 int 型变量 a 的第一个字节的数据,即 44
printf("%#x\n", a);
return 0;
}
3.指针类型决定了指针走一步有多大
int* 型指针走一步即 4 字节,char* 的指针走一步即 1 字节
指针加 1 指的是增加一个存储单元
int main()
{
int arr[10] = { 0 };
int* p = arr;
char* pc = arr;
//int*
printf("%p\n", p);
printf("%p\n\n", p + 1);//走 4 字节
//char*
printf("%p\n", pc);
printf("%p\n", pc + 1);//走 1 字节
return 0;
}
三、数组和指针的关系
数组和指针的关系十分密切
可以用指针表示数组的元素和获得元素的值
arr + 2 == &arr[2];
*(arr + 2) == arr[2];
体现了C语言的灵活性
可以这样理解 *(arr + 2)
:到内存的 arr 位置,偏移 2 个存储单元,访问那里的值
注: 指针加 1 指的是增加一个存储单元;对数组而言,这意味着加 1 后的地址是下一个元素的地址,而不是下一个字节的地址
四、二级指针
int main()
{
int a = 8;
int* p = &a;
int** pp = &p;
printf("a = %d\n", a);
printf("*p = %d\n", *p);
printf("**pp = %d\n", **pp);
return 0;
}
五、野指针
野指针指向的位置是不可知的(随机的、不确定的、没有明确限制的)
1.指针未初始化
int main()
{
int* p;
*p = 20;
//非法访问内存
//这时,p就是野指针
return 0;
}
这里局部指针变量没有初始化,则默认是随机值
这种情况如何避免野指针:
当不指定 p 应该初始化为什么地址的时候,直接初始化为NULL,例如 int p = NULL;
2.指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*p = i;
p++;
}
}
这里指针指向的范围超出了数组 arr 的范围;超出时,p 成为野指针
3.指针指向的空间释放
int main()
{
int a = 0;
int* p = NULL;
p = &a;
*p = 20;
{
int b = 30;
p = &b;
}//b的生命周期结束,b的内存销毁(注意不代表粉碎)
*p = 50;//b的内存已销毁,这时p指向的是一个无效的地址,p这时成为野指针
printf("%d\n", a);//20
printf("%d\n", *p);//50,虽然b对应的内存已经销毁,但仍然存在
return 0;
}
这种情况如何避免野指针:
指针指向空间释放时及时指向NULL
六、指针运算
1.指针和整数的加减运算
int i = 1;
int arr[5] = { 1,2,3,4,5 };
int* p = &arr[0];
//*p == arr[0] == 1
//p + i == arr[1] == 2
//++ p == arr[1]
//*(arr + 1) == arr[1]
//不能对 arr 进行自增(++)自减(--)的运算
假设一个存储整形数据的数组 arr 长度为 5,int* 型指针 p 指向 arr 数组的首元素
对于指针变量
- 指针和整数的加减法运算,如
p + i == arr[1]
,指针是按对应大小的存储单元偏移的;视指针所指向类型的大小为单位 - 指针变量的自增(++)自减(–)运算也是同样的运算过程,如
++ p == arr[1]
对于数组名(数组名是首元素的地址)
- 数组名加上一个整数后 * 运算就可以访问其他元素(不越界),如
*(arr + 1) == arr[1]
- 但不能对数组名自增(++)自减(–)的运算,因为数组名不等价于指针变量,自增和自减对数组名来说是非法的
2.指针 - 指针
<1>指针 - 指针 得到的是两个指针之间的元素个数
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);//9
return 0;
}
批注: 指针和指针相减的前提是两个指针指向同一块空间,即同一个数组中的元素
<2>例:用指针和指针相减的方法创建一个函数求字符串长度
思路: 字符串传参传的是首字母的地址,通过循环语句找到字符串末尾’\0’的地址,将这个地址减去首字母的地址即可求得字符串长度
int My_strlen(char* p)
{
char* p1 = p;
while (*p != '\0')
{
p++;
}
return p - p1;
}
int main()
{
int len = My_strlen("Hello");
//字符串传参传的是首字母的地址
printf("%d\n", len); //5
return 0;
}