• 浅谈C语言指针


    1 指针

    指针是什么?与内存地址的关系是什么?

    下图是 4G 内存中每个字节单元和它的编号:

    • 每个字节单元可以存储8个二进制位,即1个字节大小的内容;
    • 字节单元也被称为内存单元;
    • 为了方便寻址,每个内存单元都有唯一的编号,这个编号就是内存地址(Address)或者指针(Address)
    • 地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0x00000000,最大的地址为 0xFFFFFFFF

    【答】

    • 指针是内存单元的编号;
    • 指针变量的值是一个内存地址。用*取指针变量的值得到的就是该指针变量指向的内存地址存储的值。

    1.1 如何创建指针

    #include <stdio.h>
    
    int main() {
      int a = 10;
      int* p1 = &a;
      int *p2 = &a;
      int** pp = &p1;
    
      printf("%p
    ", &a);
      printf("%p
    ", p1);
      printf("%p
    ", p2);
      printf("%p
    ", pp);
    
      system("pause");
      return 0;
    }
    

    一次执行中,运行结果如下:

    我们画图来表示:

    • 但在编写代码的过程中,我们认为变量名表示的是数据本身;例如,a 是 int 型,表示的是 4字节的整数。

    • a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;&a 表示就是该变量的首地址。

    • 指针可以认为是一种数据类型,int*就可以视为一个整体。p1 就是一个指针,这个指针指向变量 a 的首地址。

    • int* p = &a;int *p = &a; 写法略有差异,但是实际功能是相同的。因此, &a,p1,p2 表示的是同一个内存单元。

    • int** pp 就等同于 (int*)* pp,pp 是指针,指向一个内存单元,在32位系统中,int*占4个字节,所以 pp 存储的值等于 0x0133F7D4,这个值又是一个内存地址,或者说指针。

    【答】:定义指针用 int* p; 或者 int *p,个人更推荐前者。

    1.2 指针占多少字节

    我们先来看一个程序:

    #include <stdio.h>
    
    int main() {
      char* p1;
      short* p2;
      int* p3;
      long* p4;
      float* p5;
      double* p6;
    
      printf("%d
    ", sizeof(p1));
      printf("%d
    ", sizeof(p2));
      printf("%d
    ", sizeof(p3));
      printf("%d
    ", sizeof(p4));
      printf("%d
    ", sizeof(p5));
      printf("%d
    ", sizeof(p6));	 
      system("pause");
      return 0;
    }
    

    我们来看一下运行结果:

    • 我们发现任何类型的指针变量都是占用4个字节。
    • 因为我们的程序是用32位编译的,所以总是4个字节

    【答】
    指针的大小是固定的;指针所占的字节数跟编译的程序位数有关。如果是32位程序,指针的宽度就是4字节,如果是64位程序,指针的宽度就是8字节。

    1.3 指针变量的运算

    我们再来看一段程序:

    #include <stdio.h>
    
    int main()
    {
      char c = 'C';
      short s = 1;
      int i = 100;
      double d = 99.9;
      char* p1 = &c;
      short* p2 = &s;
      int* p3 = &i;
      double* p4 = &d;
      printf("size char=%6d short=%5d int=%7d double=%4d
    ", sizeof(char), sizeof(short), sizeof(int), sizeof(double));
      printf("Init p1=%8p p2=%8p p3=%8p p4=%8p
    ", p1, p2, p3, p4);
      // 加法运算
      p1++;p2++;p3++;p4++;
      printf("p++  p1=%8p p2=%8p p3=%8p p4=%8p
    ", p1, p2, p3, p4);
      // 减法运算
      p1-=2;p2-=2;p3-=2;p4-=2;
      printf("p-=2 p1=%8p p2=%8p p3=%8p p4=%8p
    ", p1, p2, p3, p4);
    
      system("pause");
      return 0;
    }
    

    执行结果如下:

    • 自增:p1,p2,p3,p4每次加1,他们的地址分别增加1、2、4、8个字节,这个增量正好等于 char,short,int,double 类型的长度。
    • 减法:p1,p2,p3,p4每次减2,他们的地址分别减少2、4、8、16个字节,这个减少量正好等于 char,short,int,double 类型的长度的2倍。

    1.4 单目操作符&*

    首先,定义指针变量void* p 或者 void *p,以及定义引用变量 int& a 或者 int &a;在定义变量时,会比 &* 单纯作为单目运算符要多一个类型声明。

    首先,作为单目运算符,它的特点就是只有一个操作数:

    int a = 1;
    int* p = &a;
    *p = 10;
    
    • 首先定义了一个变量a
    • 接着,定义了指针变量p,并为它赋值。赋值时,用上了单目运算符&来取得变量a的首地址,并赋值给指针p。
    • 最后,再次使用了单目运算符*来获取指针p指向的数据,并修改了数据内容。

    & 作为 单目运算符 使用时,是取地址符
    * 作为 单目运算符 使用时,是解引用符

    2 指针常量和常量指针

    我之前在学习指针的时候,总是会把指针常量和常量指针混淆起来。最主要的原因是,按照一般的习惯,我们是从左向右读,所以我们会理所当然地认为const int* p常量指针。但是实际上你倒过来念就是正确的。

    2.1 指针常量

    int a = 0;
    int b = 1;
    
    // 指针常量
    const int* p = &a;
    
    // *p = 10; // 错误,内容不可以修改
    p = &b;     // 正确,指针可以修改
    

    记忆方法:按照英文反着念 int*,const: 指针常量。中文的习惯,最终定性为“常量”,所以指针指向的数据是个常量,不可以被修改。

    2.2 常量指针

    int a = 0;
    int b = 1;
    
    // 常量指针
    int* const p = &a;
    
    *p = 10;   // 正确,指针指向的数据可以修改
    // p = &b; // 错误,指针地址不可以修改
    

    记忆方法:反着念定义关键字 const,int*:常量指针。中文的习惯,最终定性为“指针”,常量修饰“指针”一词,所以指针地址不可以被修改。但是,指针指向的数据可以被修改。

    2.3 地址和数据都不许改

    int a = 0;
    int b = 1;
    
    const int* const p = &a;
    
    // *p = 10;  // 错误,指针指向的数据不可以修改
    // p = &b;   // 错误,指针地址不可以修改
    

    3 指针与数组

    3.1 访问数组中的元素

    int arr[] = {1,2,3,4,5};
    int* start = arr;
    
    • 首先,初始化了整型数组 arr;
    • 接着,把数组名赋值给了指针start;

    函数名、字符串名和数组名表示的是代码块或数据块的首地址

    我们想要遍历数组,首先需要计算数组的元素个数:

    int len = sizeof(arr) / 4;
    

    然后,不使用指针时,我们访问数组的是这样:

    for (int i = 0; i < len; i++) {
      printf("%d
    ", arr[i]);
    }
    

    使用指针访问数组是这样的:

    for (int i = 0; i < len; i++) {
      printf("%d
    ", *(start+i));
    }
    

    3.2 通过char型指针访问short数组会发生什么?

    short arr[] = {1, 2, 3};
    char* p = reinterpret_cast<char *>(arr);
    
    for (int i = 0; i < 3; i++) {
      printf("%d
    ", *(p+i));
    }
    

    运行结果:

    4 函数指针与指针函数

    4.1 函数指针

    函数指针,其本质是一个指针变量,该指针指向某个函数。

    #include <stdio.h>
    void sayHello();
    
    int main()
    {	
      void (*p)();
      p = sayHello;
      (*p)(); // 效果等同于调用 sayHello();
      p();    // 效果等同于调用 sayHello();
    
      system("pause");
      return 0;
    }
    
    void sayHello() {
      printf("Hello World!");
    }
    

    利用函数指针进行调用:

    • (*p)()
    • p()
      很久很久以前C语言只允许前者,后来大家觉得这么写太麻烦就规定了后者能达到同样效果。
      后者在编译时和前者做相同的事情。

    类似的语法上的便利还有:

    • p->h 通过指针访问结构成员,等价于 (*p).h
    • p[n] 通过指针访问数组元素,等价于 *(p+n)

    4.2 指针函数

    指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。形如

    int *fun(int x,int y);
    

    4.3 typedef函数指针

    改造一下4.1函数指针中的例子:

    void sayHello();
    
    // typedef 函数指针
    typedef void (*HI)();
    HI funHi;
    
    int main()
    {
      funHi = sayHello;
      funHi();    // 效果等同于调用 sayHello();
      (*funHi)(); // 效果等同于调用 sayHello();
      return 0;
    }
    
    void sayHello() {
      printf("Hello World!");
    }
    

    参考文档

    《C语言指针是什么?1分钟彻底理解C语言指针的概念》阅读

    《函数名与函数指针(了解)》阅读

    《typedef函数指针用法》阅读

  • 相关阅读:
    bzoj 3992: [SDOI2015]序列统计【原根+生成函数+NTT+快速幂】
    bzoj 3771: Triple【生成函数+FFT+容斥原理】
    poj 2891 Strange Way to Express Integers【扩展中国剩余定理】
    hdu 1573 X问题【扩展中国剩余定理】
    bzoj 2023: [Usaco2005 Nov]Ant Counting 数蚂蚁【生成函数||dp】
    hdu 1521 排列组合【指数型生成函数】
    JavaScript数据类型的检测
    JavaScript数据类型
    原生JS模拟jQuery $
    String基础
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/14860370.html
Copyright © 2020-2023  润新知