目录
一、指针是什么
在计算机科学中,指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑储存器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化地称为“指针”,意思是通过它能找到以它为地址的内存单元。
基础知识补充:
整型常量的不同进制表示:
计算机中只能储存二进制数,即0,1。
为了更方便观察内存中二进制的情况,计算机还提供了八进制、十进制、十六进制。
二进制:以0b开头标示,数位变化为0,1。
八进制:以0开头标示,数位变化范围为0~7。
十进制:以0开头标示,数位变化范围为0~9。
十六进制:以0x开头标示,数位变化范围为0~9和a~f,a代表10,f代表15。
计算机内存单位:
位:bit,简写为b,位是最小的内存单位,可以储存0和1。
字节:byte,简写B,字节是常用的计算机储存单位,1字节为8位。一个ASCII码用一个字节表示,一个汉字用两个字节表示。
字:word,简称字,设计计算机时给定的自然储存单位,最初一个字长只有8位,慢慢增至16位,32位,直到现在的64位。
扩展的储存单位:KB,MB,GB,TB,用来表示计算机各种存储介质(例如内存、硬盘、光盘等)的存储容量。
1 Byte=8 Bit
1 KB=1024 Byte
IMB = 1024KB;
1GB = 1024MB;
1TB = 1024GB;
1TB=1024GB=10242MB=10243KB=10244B=8*10244位
int main()
{
int a = 10;//a是int类型,占4个字节
int* pa = &a;//a占4个字节,a有4个地址,取到的是首地址
*pa = 20;
printf("%d\n", a);
return 0;
}
- 我们可以通过&(取地址操作符)取出变量的内存地址,把地址可以存放到一个变量中,这个变量就是指针变量。
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时所说的指针,通常指的是指针变量,是用来存放内存地址的变量。
总结:
- 指针变量,就是用来存放地址的变量。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
二、指针和指针类型
int main()
{
int * pa;
char * pb;
float * pc;
printf("%d\n", sizeof(pa));
printf("%d\n", sizeof(pb));
printf("%d\n", sizeof(pc));
return 0;
}
输出:4 4 4
无论是什么类型的指针,大小都是4个字节。
a的值为0x11223344,为什么在内存中显示44332211呢?因为英特尔的CPU采用了小端方式进行数据储存,因此低位在前,高位在后。
指针定义的方式是 :type *
int * 类型的指针,是为了存放int类型变量的地址。
char * 类型的指针,是为了存放char类型变量的地址。
short * 类型的指针,是为了存放short类型变量的地址。
指针类型的意义是什么呢?
- 指针类型决定了:指针解引用的权限有多大。
- 指针类型决定了:指针向前或者向后走一步的步长。
指针+整数
指针的类型决定了指针向前或者向后走一步有多大距离。
指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
char*的指针解引用只能访问1个字节,int* 的指针解引用能访问4个字节。
三、野指针
野指针成因
1.指针未初始化
int main()
{
int* p;
*p = 20;
return 0;
}
局部变量指针未初始化,默认为随机值。
相当于我们在酒店订了一间房,房号随机, 我们不知道是那间房,因此我们也不能往房间里放行李。如果随便找了一间房放进去,因为并不是我们订的那间随机房,就会构成非法访问。
2.指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p ++) = i;
}
return 0;
}
3.指针指向的空间释放
举个简单的例子说明:
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
*p = 20;
return 0;
}
形参只有在函数被调用的过程中才实例化(分配内存),函数调用完成后自动销毁。
a是进入test这个函数后才被创建的,函数结束后销毁,空间已经被释放了。
再将20赋给*p的时候,找不到*p的地址了
如何规避野指针?
- 指针初始化
- 小心指针越界
- 指针指向空间释放,即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
注:当不知道p应该初始化为什么地址的时候,直接初始化为NULL
例:int * p = NULL;
四、指针运算
指针+整数
指针-指针
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n",arr[9]-arr[0]);
return 0;
}
输出:9
那下列代码可行吗?
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
char ch[5] = { 0 };
printf("%d\n",arr[9]-ch[2]);
return 0;
}
虽然有输出,但是并不可行。
指针和指针相减的前提是:两个指针指向同一块空间。
//指针实现my_strlen
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
int len = my_strlen("abc");
printf("%d\n", len);
return 0;
}
那指针+指针有意义吗?
没有意义。就像日期,相减可以得到天数,相加却没有意义。
指针的运算关系
标准规定:允许指向数组元素的指针,与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素的那个内存位置的指针进行比较。
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[0]; vp > &values[0];)
{
*--vp = 0;
}
return 0;
}
理论上来说,下面的写法大部分编译器也行得通。
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float* vp;
for (vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
return 0;
}
但是由于访问到values[0]前面一个元素内存的时候,与标准规定不一致,并不一定可行,应该避免这种写法。
五、指针和数组
数组名表示数组首元素的地址。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
//p存放的是数组首元素的地址
return 0;
}
那这样写代码就是可行的。
既然可以把数组名当成地址存放在指针中,那通过指针访问数组也就有可能实现。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
//p存放的是数组首元素的地址
int i = 0;
for (i = 0; i < 10; i++)
{
printf("&arr[%d]=%p <==> p+%d=%p\n", i, &arr[i], i, p + i);
}
return 0;
}
所以,p+i 其实计算的就是数组arr下标 i 的地址。
那就可以直接通过指针直接访问数组。
补充:
[ ] 是一个操作符,arr、2 是它的两个操作数。
同加法有交换律一样,a+b可以写成b+a,那 arr[2] 也可以写出 2[arr] 。
arr[2] --> *(arr+2)--> *(2+arr)--> 2[arr]
arr[2] --> *(arr+2) --> *(p+2) --> *(2+p) --> *(2+arr) --> 2[arr]
六、二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针。
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
printf("%p\n", &a);
printf("%p\n", &pa);
printf("%p\n", &ppa);
return 0;
}
a的地址存放在pa中,pa的地址存放在ppa中。
pa是一级指针,ppa是二级指针。
七、指针数组
指针数组-存放指针的数组。
int main()
{
int arr[10];
//整型数组-存放整型的数组就是整型数组
char ch[5];
//字符数组-存放字符的数组就是字符数组
//指针数组-存放指针的数组
int* parr[5];
//整型指针的数组
return 0;
}