1. 指针是什么?
指针是什么?
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
那我们就可以这样理解:内存
内存被划分为一个小小的内存单元,一个基本的内存单元的大小是一个字节。(一个内存单元一个字节)内存单元的编号就是地址 编号 ->地址->指针 (地址能够指向这块内存单元,地址称为指针)
1. #include <stdio.h> 2. int main() 3. { 4. int a = 10;//在空间内存创建一个变量a(占了4个字节)(a一共有4个地址,一个字节一个地址), 5. int* pa = &a;//int*是pa的类型(*说明他是一个指针,int说明指向那个a的变量是int类型) 创建一个指针变量pa &a拿的是起始地址 6. *pa = 20;//通过指针找到a,用*pa,改变pa的值就是改变a的值 7. printf("%d ", a); 8. return 0; 9. }
总结:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小(如上图中的pa)在32位平台是4个字节,在64位平台是8个字节(无论是int* 还是double*还是float*)
内存单元的编号是如何产生的:
对于32位的机器,有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电
平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
2. 指针和指针类型
这里我们在讨论一下:指针的类型
我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
但是指针变量在固定的平台上,指针变量的大小也是固定的,那指针类型有什么意义呢?
2.1 指针+整数
指针的类型决定了指针向前或者向后走一步,走多大距离(单位是字节)
pa指向的是int类型,所以加一走4个字节
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 0 }; 5. int* p = &arr; 6. int i = 0; 7. for (i = 0; i < 10; i++) 8. { 9. *(p + i) = i +1;//初始化 10. } 11. //倒着打印 12. int* q = &arr[9]; 13. for (i = 0; i < 10; i++) 14. { 15. printf("%d ", *q); 16. q--; 17. } 18. 19. 20. return 0; 21. }
打印的结果是:10 9 8 7 6 5 4 3 2 1
int arr[10] = { 0 };
char* pc = (char*)&arr;//强制类型转换
//强制类型转换地址不会发生变化//仅仅解引用时改变的字节发生改变,
2.2 指针的解引用
指针类型决定在解引用时一次能改变几个字节
F10 ——窗口——内存(可观察内存情况)
int* 在解引用时能改变4个字节,double*在解引用能改变8个字节,char*在解引用时能改变1个字节(虽然他们在固定的平台上,指针变量占用的字节都是相同的(4个或者8个)
3 野指针
3.1 野指针成因
3.1.1 指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
3.1.2 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;//数组名本来就是地址
int i = 0;
for(i=0; i<=10; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p+1) = i;
}
return 0;
}
3.1.3 指针指向的空间释放
上面这个代码时错误的,当函数结束时,就释放了内存,打印时,再用地址指向那块内存不一定会获取到1000这个值。
上面这个图就没有获取到1000这个值,说明是错误的代码。
3.2 如何避免野指针
1. 指针初始化 明确指针指向的位置,不知道指向谁的时候,就指向空指针 int* p = NULL 即可
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
NULL空指针,本质上NULL相当于0。int* pa = NULL; *pa = 100; 这种写法是错误的。下边这个代码才是正确的。
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int* p = NULL; 5. if (p != NULL) 6. { 7. *p = 100; 8. } 9. return 0; 10. }
3. 指针指向空间释放即使置NULL
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 0 }; 5. int* p = arr; 6. p = NULL;//当不想用这个指针变量的时候,定义为空指针即可 7. return 0; 8. }
4 指针运算
指针+- 整数
指针-指针
指针的关系运算
4.1 指针+-整数
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 0 }; 5. int* p = &arr; 6. int i = 0; 7. for (i = 0; i < 10; i++) 8. { 9. *(p + i) = i +1;//指针+整数的运算 10. } 11. //倒着打印 12. int* q = &arr[9]; 13. for (i = 0; i < 10; i++) 14. { 15. printf("%d ", *q); 16. q--;//指针-整数的运算 17. } 18. 19. 20. return 0; 21. }
4.2 指针-指针
指针-指针的前提是,两个指针指向同一块空间
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 0 }; 5. printf("%d\n", &arr[0] - &arr[9]); 6. printf("%d", &arr[9] - &arr[0]); 7. return 0; 8. }
调试结果:-9 9
地址是从小到大排列的,&arr[0]指向的是arr[0]的首地址(int类型,四个字节,一个字节一个地址),同理可得,&arr[9]也是,所以,中间一共有9个元素,36个字节,所以调试结果一个是负的,一个是正的。
求字符串长度的第三种方法:(前两种方法,在函数递归哪里写过,感兴趣的友友们可以去看看)
代码展示:
1. #include <stdio.h> 2. int my_strlen(char* s) 3. { 4. int* start = s; 5. while (*s != '\0') 6. { 7. s++; 8. } 9. return s - start; 10. } 11. int main() 12. { 13. char arr[] = "abcd"; 14. int ret = my_strlen(arr); 15. printf("%d ", ret); 16. return 0; 17. }
4.3 指针的关系运算
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
5 指针和数组
数组名表示的是数组首元素的地址。(2种情况除外,数组章节写了,友友们感兴趣的话,可以去看看)数组名当成地址存放到一个指针中
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int arr[] = {1,2,3,4,5,6,7,8,9,0}; 5. int *p = arr; //指针存放数组首元素的地址 6. int sz = sizeof(arr)/sizeof(arr[0]); 7. for(i=0; i<sz; i++) 8. { 9. printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i); 10. } 11. return 0; 12. }
p+i 其实计算的是数组 arr 下标为i的地址
1. int main() 2. { 3. int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 4. int *p = arr; //指针存放数组首元素的地址 5. int sz = sizeof(arr) / sizeof(arr[0]); 6. int i = 0; 7. for (i = 0; i<sz; i++) 8. { 9. printf("%d ", *(p + i)); 10. } 11. return 0; 12. }
6 二级指针
指针变量的地址就是二级指针
1. #include <stdio.h> 2. int main() 3. { 4. int a = 10; 5. int* pa = &a; 6. int** ppa = &pa;//ppa就是二级指针//多级指针存在 7. 8. return 0; 9. }
如何理解int** ppa;int*说明ppa指向元素的地址是int*类型,第二个*说明是它是一个指针。(多级指针都是这样理解的)
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int a = 10; 5. int* pa = &a; 6. int** ppa = &pa;//ppa就是二级指针//多级指针存在 7. **ppa = 20;//*ppa指的pa.**ppa指的是a,所以在a的值改变为20; 8. printf("%d ", a); 9. return 0; 10. }
7 指针数组
指针数组是 数组,是存放指针的数组。
整形数组,存放整形的的数组,字符数组,就是存放字符的数组。 那么整型指针数组就是存放整形指针的数组。
int* arr[5];
初阶指针就到此结束了。