指针是什么?

在计算机科学中,指针(Pointer)是编程语言的一个对象,利用地址。它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需要的变量单元,可以说,地址指向该变量单元。因此将地址形象化的简称为”指针”。意思是通过它能找到以它为地址的内存单元。

  1. #include "stdio.h"
  2. int main(){
  3. printf("hello world C");
  4. int a = 10;
  5. int* p = &a; //指针变量
  6. return 0;
  7. }

指针就是地址;地址就是指针

总结

指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)

那这里的问题是

  • 一个小的单元到底是多大? (一个字节)

指针类型的意义

  • 1.指针类型决定了指针进行指针应用的时候能访问几个字节
    • char* 一个字节
    • int* 四个字节
    • double* 八个字节
  • 指针决定了: 指针走一步多远(指针的步长)
    • int* p+1 -> 4
    • char* p+1-> 1
    • double* p+1 -> 8

逐条赋值

  1. #include "stdio.h"
  2. #include "add.h"
  3. int main() {
  4. int arr[SIZE] = { 0 };
  5. int* p = arr;
  6. int i;
  7. for (i = 0; i < SIZE; i++) {
  8. *(p + i) = 1;
  9. }
  10. return 0;
  11. }

点击断点后进入调试(调试->开始调试)(F11是逐行调试)
image.png
注: SIZE是add.h的宏定义: #define SIZE 10

当存储的类型不一样时:

  1. #include "stdio.h"
  2. #include "add.h"
  3. int main() {
  4. int arr[SIZE] = { 0 };
  5. char* p = arr;
  6. int i;
  7. for (i = 0; i < SIZE; i++) {
  8. *(p + i) = 1;
  9. }
  10. return 0;
  11. }

只跳了10个字节,但是作为40个字节的int arr[10]来说可以运行但是1赋值不全
调试内存显示:断点->开始调试->调试->显示->内存

野指针

概念: 野指针就是指针指向的为止时不可知的(随机的,不正确的,没有明确限制的)

野指针的原因

  • 1.指针未初始化

    1. #include "stdio.h"
    2. int main(){
    3. int *p; //局部变量指针未初始化,默认为随机值
    4. *p = 20;//内存里随便找了一个位置,把20放进去
    5. return 0;
    6. }
  • 2.指针越界访问

    1. #include "stdio.h"
    2. int main(){
    3. int arr[10] = {0};
    4. int* p = arr;
    5. int i = 0;
    6. for(i=0;i<=11;i++){
    7. //当指针指向的范围超出数组arr的范围的时候,p就是野指针
    8. *(p++) = i;
    9. }
    10. return 0;
    11. }
  • 3.指针指向的空间释放

    1. #include "stdio.h"
    2. int* test(); //函数声明
    3. int main(){
    4. int* p = test();
    5. *p = 20;//写这段的话,这个代码就出问题了
    6. return 0;
    7. }
    8. int* test(){
    9. int a = 10;
    10. return &a;
    11. }

    注意: a时局部变量,我们知道局部变量一旦进入函数范围创建出范围的时候销毁,销毁了就表示这个内存空间还给了操作系统了,你去使用的时候,这块a空间已经释放了。它指向的是已经释放的内存块,已经还给人家了

如何规避野指针

  • 指针初始化

    1. #include "stdio.h"
    2. int main(){
    3. int a = 10;
    4. int* pa = &a;//初始化
    5. int* p = NULL;//#define NULL ((void)0) : 其实就是0(NULL是用来初始化指针的,给指针赋值的)
    6. return 0;
    7. }
  • 小心指针越界(越界会程序报错)

    1. #include "stdio.h"
    2. int main(){
    3. int arr[10] = {0};
    4. int* pa = &a;//初始化
    5. int* p = NULL;//#define NULL ((void)0) : 其实就是0(NULL是用来初始化指针的,给指针赋值的)
    6. int i;
    7. for(i=0;i<sizeof arr/sizeof arr[0];i++){
    8. *(p + i) = 1;
    9. }
    10. return 0;
    11. }
  • 指针指向空间释放及时值为NULL

    1. #include "stdio.h"
    2. int main(){
    3. int a = 10;
    4. int* pa = &a;
    5. *pa = 20;
    6. //
    7. pa = NULL;
    8. return 0;
    9. }
  • 指针使用之前检查有效性

    1. if(pa != NULL){
    2. }

指针运算

指针加减整数
  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int sz = sizeof arr / sizeof arr[0];
  5. int* p = arr;
  6. int i;
  7. for(i = 0; i < sz; i++){
  8. printf("%d ",*p);
  9. p++;//p = p + 1
  10. }
  11. return 0
  12. }
  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int sz = sizeof arr / sizeof arr[0];
  5. int* p = arr;
  6. int i;
  7. for(i = 0; i < 5; i++){
  8. printf("%d ",*p);
  9. p += 2;//p = p + 2
  10. }
  11. return 0
  12. }
  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int sz = sizeof arr / sizeof arr[0];
  5. int* p = &arr[9];
  6. int i;
  7. for(i = 0; i < 5; i++){
  8. printf("%d ",*p);
  9. p -= 2;//p = p - 2
  10. }
  11. return 0
  12. }
  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int sz = sizeof arr / sizeof arr[0];
  5. int* p;
  6. for(p = &arr[0];p < &arr[10];){
  7. *p++ = 0;
  8. }
  9. return 0
  10. }

指针减去指针
  1. int my_strlen(char* s){
  2. char *p = s;
  3. while(*p != '\0){
  4. p++;
  5. }
  6. return p - s;
  7. }
  1. int main(char* s){
  2. int arr[10] = {1,2,3,4,5,6,7,8,9};
  3. printf("%d\n",&arr[9] - &arr[0]);//指针减指针得到的是中间元素的个数
  4. //printf("%d\n",&arr[0] - &arr[9]); //小地址减大地址的绝对值是中间元素的个数
  5. return 0;
  6. }
  1. int main(char* s){
  2. char ch[5] = {0};
  3. int arr[10] = {1,2,3,4,5,6,7,8,9};
  4. printf("%d\n",&arr[9] - &ch[0]);//err。有数值但是是乱的
  5. return 0;
  6. }

strlen-求字符串长度(模拟)
  1. #include "stdio.h"
  2. int my_strlen(char*);
  3. int main(){
  4. char arr[] = "bit";
  5. int len = my_strlen(arr);
  6. printf("%d\n",len);
  7. return 0;
  8. }
  9. int my_strlen(char* str){
  10. //char* start; //其实就是str
  11. char* start = str;
  12. char* end = str;
  13. while(*end != '\0'){
  14. end++; //end到\0(结尾)时跳出循环,此时end指向的地址就是\0
  15. }
  16. return end -start;
  17. }
  1. #define N_VALUE 5
  2. float valus[N_VALUE];
  3. float* vp;
  4. for(vp = &values[N_VALUE]; vp > &values[0];){
  5. *--vp = 0;
  6. }
  1. #define N_VALUE 5
  2. float valus[N_VALUE];
  3. float* vp;
  4. //因为C语言不保证它可行
  5. for(vp = &values[N_VALUE-1]; vp >= &values[0];vp--){
  6. *vp = 0;
  7. }

标准规定

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之间的那个内存位置的指针进行比较。

  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {0};
  4. printf("%p\n",arr);
  5. printf("%p\n",&arr[0]);
  6. //arr与arr[0]的地址相同,说明arr首地址(绝大部分)
  7. return 0;
  8. }
  1. //例外-&数组名-数组名不是首元素的地址-数组名表示整个数组- &数组名取出的是整个数组的地址
  1. //sizeof(arr) - sizeof(数组名)-数组名-数组名表示的是整个数组-sizeof计算的是整个数组的大小(单位:字节)
  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {0};
  4. printf("%p\n",arr);
  5. printf("%p\n",&arr[0]);
  6. printf("%p\n",&arr);
  7. return 0;
  8. }

如果地址是一样的?可以尝试以下代码

  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {0};
  4. printf("%p\n",arr);
  5. printf("%p\n",arr + 1);
  6. printf("%p\n",&arr[0]);
  7. printf("%p\n",&arr[0] + 1);
  8. printf("%p\n",&arr);
  9. printf("%p\n",&arr + 1);
  10. return 0;
  11. }

image.png

  1. #include "stdio.h"
  2. int main(){
  3. int arr[10] = {0};
  4. int* p = arr;
  5. int i;
  6. /*for(i = 0; i<10;i++){
  7. //内存地址相同
  8. printf("%p === %p\n",p+i,&arr[i]);
  9. }*/
  10. for(i = 0;i < 10;i++){
  11. //printf("%d ",arr[i]);
  12. printf("%d",*(p + i));
  13. }
  14. return 0;
  15. }

但是数组和指针不同

  • 数组可以存放n个数据
  • 指针存放的是一个数据的内存地址
  • 通过指针可以访问数组
  • 指针不止止是可以访问数组

二级指针

  1. /*Secondary.c -- 二级指针*/
  2. #include "stdio.h"
  3. int main(){
  4. int a = 10;
  5. int* pa = &a;//pa是一级指针
  6. return 0;
  7. }
  1. /*Secondary.c -- 二级指针*/
  2. #include "stdio.h"
  3. int main(){
  4. int a = 10;
  5. int* pa = &a;//pa是一级指针
  6. int** ppa = &pa;//ppa就是二级指针
  7. return 0;
  8. }
  1. /*Secondary.c -- 二级指针*/
  2. #include "stdio.h"
  3. int main(){
  4. int a = 10;
  5. int* pa = &a;//pa是一级指针
  6. int** ppa = &pa;//ppa就是二级指针
  7. int*** pppa = &ppa;//pppa就是三级指针,以此类推
  8. return 0;
  9. }

指针数组

指针数组本身是数组

指针数组是存放指针的数组

  1. #include "stdio.h"
  2. int main(){
  3. int a = 10;
  4. int b = 20;
  5. int c = 30;
  6. int* pa = &a;
  7. int* pb = &b;
  8. int* pc = &c;
  9. //如果有多个数组需要存放就要多个变量,那我们可不可以通过数组的形式存放呢?
  10. return 0;
  11. }

整型数组 - 存放整型

字符数组 - 存放字符

指针数组 - 存放指针
写法:

  1. int arr[10];
  2. int* arr2[3]; //指针数组
  1. /*ArrayProinterOne.c -- 指针数组*/
  2. #include "stdio.h"
  3. int main(){
  4. int a = 10;
  5. int b = 20;
  6. int c = 30;
  7. int* arr2[3] = {&a,&b,&c};
  8. int i;
  9. for(i=0;i<3;i++){
  10. printf("%d\n",*(arr2[i]));
  11. }
  12. return 0;
  13. }

数组指针

数组指针本身是指针