• C/C++心得理解指针


          上一篇笔者用自己那不是怎么好理解的逻辑介绍了内存和C中的基本数据类型,现在笔者再根据自己重新所学来说说C语言中的指针。

          理解指针才能真正的算C语言入门。也许是我大学期间太关注前端UE,也许是当初开始学C语言的时候没怎么认真;直到毕业后的某一天我才“懂”指针,才算理解C语言的独特。如果有初学C语言的同行对指针有困惑,希望我这浅薄的认识能帮助你。

    1、简介

          指针在原英文中为pointer,个人觉得翻译过来后针的含义不如指的含义好理解,pointer还可翻译为指示器,如果是初学者的话,笔者建议在学习过程中多琢磨指的含义。

          指针可以说是一种特殊的数据类型,前面的内存篇介绍过,程序运行时的数据基本上都存储在内存中,内存中用数字标识不同数据,在各种计算机语言中,把这种数字标识成为地址。计算机系统并不认识C语言,程序编译就是把C语言翻译为操作系统可识别的语言。比如“我”翻译英文为I,翻译过后已经看不到“我”这个字了。我们在程序中定义了整型变量i,赋值为5,那么经过编译运行,操作系统同样看不到i,对操作系统而言,i被翻译为某个内存地址上的整型值。

    2、基本使用

          要使用指针首先要认识两个符号:一个是'&',可以在程序中取得变量的内存地址;一个是'*','*'在定义变量的时候标明该变量为指针,在已定义的变量前面使用的时候,表示获取(设置)该指针变量所在内存地址中存储的值。

     1 #include <stdio.h>
     2 
     3 int main(int arg, char * args[])
     4 {
     5     int i = 5; // 定义整型变量i并赋值为5
     6     int * srcI = &i; // &为取地址符号,取得i的内存地址,并赋值给int *的整型指针类型变量srcI
     7     printf("srcI:%d,%X\n", srcI, srcI); // 内存地址可以转化为数据打印出来,一般使用16进制查看
     8     printf("*srcI:%d\n", *srcI); // 在定义变量的时候,符号*表示定义指针,
     9                                 // 在对已定义的变量的前面加上*的时候,符号*表示取得该变量所标地址中变量的值
    10     *srcI = 7;
    11     printf("i:%d\n", i); // 可以通过地址设置变量的值
    12     getchar(); // 起暂停作用
    13     return 0;
    14 }

          上面代码执行结果如下(每次程序运行时变量的内存地址可能会不同):

    1 srcI:7665592,74F7B8
    2 *srcI:5
    3 i:7

          笔者认为理解指针重点就在理解'*'这个符号,这里强调下,在定义变量时,'*'表示后面定义的变量是指针类型,在已定义的指针前'*'表示获取(设置)后面变量存储的内存地址中实际存储的值,专业点说就是获取(设置)后面变量所指向的值。下面列举一个未理解'*'含义时会犯的错误:

     1 #include <stdio.h>
     2 
     3 int main(int arg, char * args[])
     4 {
     5     int i = 5, j = 6;
     6     int * src = &i; 
     7     *src = &j; // 本意是把src的地址由i的地址改为j的地址,但这里弄错了*的含义
     8     printf("*src:%d\n", *src);
     9     printf("i:%d\n", i); // 实际结果改变了变量i的值
    10     getchar();
    11     return 0;
    12 }

          上述代码编译运行都没有报错,本意是想把src存储的地址改为变量j的内存地址,而实际却变成设置变量i的值,这就是对'*'错误的理解后果。这段代码还说明了一件事情:第7行地址可以赋值给整型变量,这也证实了前面的话:内存中用数字标识不同数据,该数据标识就是内存地址。

      下面再通过代码说明const指针

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 int main(int arg, char *args)
     5 {
     6     int num = 5;
     7     const int * p1 = &num; // *前面 const int 和 int const 等价,这样定义的指针表示不可修改其地址指向的值
     8     int * const  p2 = &num; // 表示指针地址不可修改,但是可以修改其地址指向的值
     9     const int * const p3 = &num; // 指针地址和地址指向的值都不可修改
    10     p1 = NULL;
    11     //* p1 = 0; // 报错
    12     //p2 = NULL;// 报错
    13     *p2 = 0;
    14     //p3 = NULL;// 报错
    15     //*p3 = 0; // 报错
    16     return 0;
    17 }

    3、指针高级

          之前只使用了整型作为示例,而实际上'&'可以用来取得所有数据类型的地址,如char,float,double等等;用'*'定义指针的时候,同样可以定义出 char *,float *,double *这些指针类型,不同的指针类型在读取数据的时候的不同点就在于其解释地址的方式不同。如int *类型,定义后使用*取值,会从int *所代表的地址开始,取sizeof(int)位地址的数据,然后以int的方式解析出数据,使用其他数据类型以此类推。

          指针类型可以存储所有数据类型的地址,那么指针是否可以存储指针的地址呢?确实可以,有关多级指针的问题,这里举个可能不是很恰当的例子:在班级课堂上你需要一支笔,于是你向你同桌要,同桌说ta后面的人有笔,然后你再向同桌后面的人要,同桌后面的人说ta旁边的人有,于是你再向同桌后面旁边的人要......如果需要的话,这个场景可以循环下去。多级指针大概就是这么个意思,你读取这个地址发现里面还是个地址,于是再读里面的地址......当然现实中遇到这种借笔借半天的问题肯定很坑,但是对于计算机指针来说,只是多一个符号的问题。下面做个简单示例:

     1 #include <stdio.h>
     2 
     3 int main(int arg, char * args[])
     4 {
     5     int i = 6;
     6     int * src = &i;
     7     int ** srcsrc = &src; // 多级指针无非就是多个*,多次&
     8     int *** srcsrcsrc = &srcsrc;
     9 
    10     printf("*src:%d\n", *src);
    11     printf("**srcsrc:%d\n", **srcsrc);
    12     printf("***srcsrcsrc:%d\n", ***srcsrcsrc);
    13 
    14     // 同样可以设置值
    15     *src = 8;
    16     printf("i:%d\n", i);
    17     **srcsrc = 10;
    18     printf("i:%d\n", i);
    19     ***srcsrcsrc = 12;
    20     printf("i:%d\n", i);
    21     getchar();
    22     return 0;
    23 }

    执行结果如下:

    *src:6
    **srcsrc:6
    ***srcsrcsrc:6
    i:8
    i:10
    i:12

    4、指针运用

           说了这么多,指针在实际使用中有何作用?首先,你发现指针读取的都是操作系统内存地址,所有程序的数据都存在系统内存中,如果能读取设置他们所有的值,那么也就可以通过内存地址修改系统其他程序的数据,这就是修改器、外挂的部分原理,实际想这么做还要考虑如何找地址,如何通过系统的内存保护(注入)。

           首先介绍下指针与数组。定义一个容量为6的整型数组int array[6],其实此时array就是一个指针,其地址就是该数组中第一个元素的地址。关于数组和指针可以看下面代码及其注释:

     1 #include <stdio.h>
     2 
     3 int changeArr(int * arr)
     4 {
     5     arr[0] = 20; // 正常方式赋值数组元素
     6     *(arr + 1) = 40; // 指针方式赋值数组元素
     7 }
     8 
     9 int main(int arg, char *args)
    10 {
    11     int i = 0;
    12     int array[6] = { 1, 2, 3, 4, 5, 6 };
    13     printf("*array:%d\n", *array); // 可以直接通过*取得首元素值
    14 
    15     // 正常方式遍历数组
    16     printf("array:");
    17     for (i = 0; i < sizeof(array) / sizeof(int); i++)
    18     {
    19         printf("%3d ", array[i]);
    20     }
    21     printf("\n");
    22     // 指针方式遍历数组
    23     printf("array:");
    24     for (i = 0; i < sizeof(array) / sizeof(int); i++)
    25     {
    26         printf("%3d ", *(array + i));
    27     }
    28     printf("\n");
    29 
    30     // 通过带指针参数的函数对数组值进行修改
    31     changeArr(array);
    32     // 指针方式遍历数组
    33     printf("after change\narray:");
    34     for (i = 0; i < sizeof(array) / sizeof(int); i++)
    35     {
    36         printf("%3d ", *(array + i));
    37     }
    38     printf("\n");
    39     getchar();
    40     return 0;
    41 }

    执行结果如下:

    1 *array:1
    2 array:  1   2   3   4   5   6
    3 array:  1   2   3   4   5   6
    4 after change
    5 array: 20  40   3   4   5   6

          上面所用示例基本都是栈上的内存空间,在直接定义变量的时候会申请栈上的内存空间,实际程序所能申请使用的栈空间很小,所以在处理大一些的数据的时候,应手动申请堆上的内存。在C语言中,手动申请需要了解几个C语言函数,这里先列举它们的函数名:malloc,calloc,realloc,free,_alloca。

          这里先以malloc和free两个函数做个示例(对数据类型内存空间有疑问的可以看看之前的内存的随笔):

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 int main(int arg, char *args)
     5 {
     6     // malloc函数原型为 void *malloc(size_t size); 其中size_t指无符号整数,也就是非负数,返回值数据类型为无类型指针地址(void *)
     7     // 分配失败的时候该地址值为0,C语言中NULL就是0的别称。
     8     int * a = malloc(sizeof(int)); // sizeof函数可以获取数据类型已经变量所占用的内存空间,这里需要一个整型的空间
     9     if (a == NULL)
    10     {
    11         // 分配内存失败,基本不会执行到这里,一般出现该问题基本都是系统内存不足,或者系统出现大问题
    12         printf("malloc failed\n"); 
    13         return -1;
    14     }
    15     *a = 5;
    16     printf("%d,%X\n", *a, a); // 分别打印出指针a地址指向的值及a地址
    17 
    18     if (a != NULL)
    19     {
    20         free(a); // 用free可释放已申请的内存空间
    21         // 有些内存已经释放,但是如果还有指针指向这篇被释放的内存很比较危险,这种情况叫野指针
    22         // 一般都会在释放后将其置为NULL
    23         a = NULL; 
    24     }
    25 
    26     getchar(); // 暂停作用
    27     return 0;
    28 }

    执行结果为:

    5,1099538

    其他内存分配相关函数用的并不是很多,这里只做简介记录

    realloc:对已分配内存的指针进行重新分配,同样需要free手动释放内存,以应用举例:原来分配的内存可能过小,这时可用realloc,申请一片新的内存空间,然后将之前的内存空间复制过去,如果新空间比原空间小会导致数据丢失。可用realloc做C语言中的动态数组(可变容量)

    原型:void *realloc(void *mem_address, unsigned int newsize);

    calloc:申请两个数乘积大小的空间,同样需要free手动释放内存。比如我想申请一个含6个元素的整型数组空间:calloc(6,sizeof(int));

    原型:void *calloc(size_t n, size_t size);

    _alloca:在栈上申请空间,用完自动释放

    原型:void * __cdecl _alloca(size_t);

    二级指针应用和三级指针应用一般在开发动态库的时候才会用得到,有兴趣的可以自行查查相关资料,本文中暂不赘述。

    5、函数指针引子

          一般来说,一个人能运用函数指针说明其水平正从入门走向熟练,笔者自己用的基本都是在一些系统调用下才会用到,一般用于回调,这里就做个引子,简单说说其定义及使用。请看下图中代码及注释:

     1 #include <stdio.h>
     2 
     3 void func()
     4 {
     5     printf("hello func pointer\n");
     6 }
     7 
     8 float addFloat(float a, float b)
     9 {
    10     return a + b;
    11 }
    12 
    13 
    14 int main(int arg, char *args[])
    15 {
    16     void (*f)() = func; // 定义函数指针并初始化
    17     float(*addf)(float a, float b) = addFloat; // 带参数的函数指针
    18     f(); // 可通过该函数指针直接执行
    19     printf("%.1f+%.1f=%.1f\n", 3.0f, 4.0f, addf(3.0f, 4.0f));
    20     getchar();
    21     return 0;
    22 }

    执行结果如下:

    1 hello func pointer
    2 3.0+4.0=7.0

    我对于指针的部分理解及运行暂时就先到这里,如果有问题还请大家指出,谢谢!

  • 相关阅读:
    在keil中使用svn源代码管理工具
    用VS2010写一个SL4的双工通讯的Demo
    Jenkins+Gitlab手动构建一个小项目
    快速建立ssh互信(一主两从)
    Ansible剧本(rsync+inotify+sersync)
    41设计模式学习资料
    Bad Request Invalid Hostname HTTP Error 400. The request hostname is invalid.
    cf交互题怎么写||算法学习方法论||codeforces700ABC总结 cf||引用大佬的博客||开篇
    codeforces699 div2 ABC | cf699 div2 abc | 贪心,贪心,还是贪心
    Pycharm破解
  • 原文地址:https://www.cnblogs.com/yaoh/p/4343201.html
Copyright © 2020-2023  润新知