一、指针是什么?
在计算机科学中,指针是编程语言中的一个对象。
可以利用地址,找到它指向存在电脑存储器中另一个地方的值。由于通过地址能找到变量单元,所以可以简单的说,地址指向该变量单元。
因此,将地址形象化的称为指针。可以通过指针找到以它为地址的内存单元。
-
我们可以这样理解:
内存:存储在硬盘中的数据。
指针:是一个可以找到该数据的地址,由变量存储这个地址。
-
对应到代码中:
先定义一个变量 a,系统会在内存中开辟一块空间;
然后使用 & 操作符,取出 a 的地址;
最后定义指针变量 p 来存放 a 的地址。
#include <stdio.h>
int main() {
int a = 10;
int* p = &a;
return 0;
}
- 总结:指针就是变量,用来存放地址的变量。(存放在指针中的值被当成地址处理)
二、指针和指针类型
- 我们都知道变量有不同的类型,整型、浮点型等。那指针有没有类型呢?都有哪些类型?
当有这样的代码:
int num = 10;
p = #
要将 &num 保存到 p 中,我们知道 p 就是一个指针变量,那它的类型应该是什么?
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
定义指针的方式都是:类型 + * + 指针名。
其实,char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
- 那指针类型的意义是什么?类型变量的地址可以用不同类型的指针存放吗?
让我们看看下面的代码:
#include <stdio.h>
int main() {
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
总结: 看结果输出,可以发现,指针的类型决定了指针向前或者向后走一步有多大的距离。
- 指针的解引用
#include <stdio.h>
int main() {
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
//重点查看在调试过程中内存的变化
*pc = 0;
*pi = 0;
return 0;
}
总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char 的指针解引用只能访问一个字节,int 的指针解引用就能访问四个字节。
三、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确、没有明确限制的)。
野指针的成因
- 指针未初始化
#include <stdio.h>
int main() {
int* p; //指针变量未初始化,默认为随机值
*p = 20;
return 0;
}
- 指针越界访问
#include <stdio.h>
int main() {
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++) {
//当指针指向的范围超出 arr 的范围,p就是野指针
*(p++) = i;
}
return 0;
}
- 指针指向的空间被释放了
后续学习到动态开辟内存,指针指向自己开辟的内存。操作后忘记合理结束指针,导致指针还在指向之前的空间,变成野指针。
如何避免出现野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即设置指针为NULL
- 指针使用前检查有效性
#include <stdio.h>
int main() {
int* p = NULL;
int a = 10;
p = &a;
if (p != NULL) {
*p = 20;
}
return 0;
}
四、指针的运算
- 指针 & 整数运算
- 指针 & 指针运算
- 指针的关系运算
指针 & 整数运算
#define N_VALUES 5;
float *vp;
float values[N_VALUES];
for(vp=&values[0];vp<&values[N_VALUES];){
*vp++ = 0;
}
指针 & 指针运算
int my_strlen(char *s){
char *p = s;
while(*p != '\0')
p++;
return p-s;
}
指针的关系运算
for(vp=&values[N_VALUES]; vp>&values[0];){
*--p = 0;
}
这段代码这样写有可能会违法标准。
标准规定:
允许指向数组元素的指针与指针数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。
五、指针和数组
数组名是什么?我们看看个例子:
#include <stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
由运行结果我们得知:
数组名和数字首元素的地址是一样的。
所以我们可以得出结论:数组名表示的是数组首元素的地址。
既然可以把数组名当成地址存放到一个指针中,那么我们可以使用指针来访问数组。
例如:
#include <stdio.h>
int main() {
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++) {
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
运行结果:
p+i 其实是计算数组下标为 i 的地址。
那么我们可以直接使用指针来访问数组了。
如下:
#include <stdio.h>
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", *(p + i));
}
return 0;
}
六、二级指针
指针变量也是变量,是变量在内存中就有地址,那么指针变量的地址存放在哪里呢?这就是二级指针 (套娃)。
对于二级指针的运算有:
1. 对 ppa 的地址进行解引用,找到 pa。
int b = 20;
*ppa = &b;
相当于 pa = &b; 一个 * 就解了一层 ppa
2. **ppa 先通过 *ppa 找到 pa,然后对 pa 进行解引用操作:*pa,那就找到是 a。
**ppa = 30;
相当于 *pa = 30;
两个 ** 就解了两层,指针就指向 a 了。
七、指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们知道有整型数组,字符数组
int arr1[5];
char arr2[6];
那么指针数组是怎么样的?
int* arr3[5];
arr3 是一个数组,有五个元素,每个元素是一个整形指针。