zoukankan      html  css  js  c++  java
  • 浅谈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函数指针用法》阅读

  • 相关阅读:
    二分图最大匹配的K&#246;nig定理及其证明
    HDOJ 2389 Rain on your Parade
    HDOJ 1083 Courses
    HDOJ 2063 过山车
    POJ 1469 COURSES
    UESTC 1817 Complete Building the Houses
    POJ 3464 ACM Computer Factory
    POJ 1459 Power Network
    HDOJ 1532 Drainage Ditches
    HDU 1017 A Mathematical Curiosity
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/14860370.html
Copyright © 2011-2022 走看看