• 关于C语言中的数组指针、指针数组以及二级指针


    概念解释

    数组指针:首先它是一个指针,它指向一个数组,即指向数组的指针;在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关。 
    指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称,即每个元素都是指针。 
    二级指针 : 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

    实例解释

    判断哪个为指针数组哪个为数组指针?

    int *p1[10];
    int (*p2)[10];

    解析

    1. “[]”的优先级比“”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int 修饰的是数组的内容,即数组的每个元素.因此这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组
    2. “()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。因此p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针 
      内存布局

    关于p2的定义问题

    平时我们定义指针不都是在数据类型后面加上指针变量名么?这个指针p2 的定义怎么不是按照这个语法来定义的呢?也许我们应该这样来定义p2: 
    int (*)[10] p2; 
    int (*)[10]是指针类型,p2 是指针变量。这样看起来的确不错,不过就是样子有些别扭。其实数组指针的原型确实就是这样子的,只不过为了方便与好看把指针变量p2 前移了而已。

    利用指针遍历数组元素

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        int arr[] = { 1, 3, 5, 7, 9};
        int len = sizeof(arr) / sizeof(int);  //求数组长度
        int i;
        for(i=0; i<len; i++)
        {
            printf("%d  ", *(arr+i) );  //*(arr+i)等价于arr[i]
        }
        printf("
    ");
        return 0;
    } 
    1. (arr+i)这个表达式,arr 是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。其中arr 是int*类型的指针,每次加 1 时它自身的值会增加 sizeof(int),加 i 时自身的值会增加 sizeof(int) * i
    2. 还可以如此表示
     int arr[] = { 1, 3, 5, 7, 9};
     int *p = arr;

    arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

    利用数组指针遍历数组

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        int arr[] = { 1, 3, 5, 7, 9};
        int len = sizeof(arr) / sizeof(int);  //求数组长度
        int i, *p = arr;
        for(i=0; i<len; i++)
        {
            printf("%d  ", *(p+i) );
        }
        printf("
    ");
        return 0;
    }
    
    1. 数组在内存中只是数组元素的简单排列,没有开始和结束标志,在求数组的长度时不能使用sizeof(p) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
    2. 根据数组指针不能逆推出整个数组元素的个数,以及数组从哪里开始、到哪里结束等信息。不像字符串,数组本身也没有特定的结束标志,如果不知道数组的长度,那么就无法遍历整个数组。
    3. 对指针变量进行加法和减法运算时,是根据数据类型的长度来计算的。如果一个指针变量 p 指向了数组的开头,那么 p+i 就指向数组的第 i 个元素;如果 p 指向了数组的第 n 个元素,那么 p+i 就是指向第 n+i 个元素;而不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素

    更改上面的代码,让 p 指向数组中的第二个元素:

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        int arr[] = { 1, 3, 5, 7, 9};
        int *p = &arr[2];  //也可以写作 int *p = arr + 2;
        printf("%d, %d, %d, %d, %d
    ", *(p-2), *(p-1), *p, *(p+1), *(p+2) );
        return 0;
    }

    会发现结果和上面的一致

    总结

    引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。 
    1. 使用下标 
    也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。 
    2. 使用指针 
    也就是使用 (p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 (arr+i) 来访问数组元素,它等价于 *(p+i)。 
    不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

    借助自增运算符来遍历数组元素

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        int arr[] = { 1, 3, 5, 7, 9};
        int i, *p = arr, len = sizeof(arr) / sizeof(int);
        for(i=0; i<len; i++)
        {
            printf("%d  ", *p++ );
        }
        printf("
    ");
        return 0;
    }

    解释

    p++ 应该理解为 (p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的

    关于数组指针的几个问题

    假设 p 是指向数组 arr 中第 n 个元素的指针,那么 p++、++p、(*p)++ 分别是什么意思呢? 
    1. *p++上面已经叙述 
    2. ++p 等价于 (++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值 
    3. (*p)++ 会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 1,执行完该语句后,第 0 个元素的值就会变为 2

    实例中的指针数组和二级指针

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        int a = 1, b = 2, c = 3;
        //定义一个指针数组
        int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]
        //定义一个指向指针数组的指针,即二级指针
        int **parr = arr;
        printf("%d, %d, %d
    ", *arr[0], *arr[1], *arr[2]);
        printf("%d, %d, %d
    ", **(parr+0), **(parr+1), **(parr+2));
        return 0;
    }
    
    1. arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组很类似。
    2. parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int (*parr),括号中的表示 parr 是一个指针,括号外面的int 表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int ,所以在定义 parr 时要加两个 *,即可称parr为二级指针,或者指向指针的指针
    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        char *lines[5] =
        {
        "COSC1283/1984",
        "Programming",
        "Techniques",
        "is",
        "great fun"
        };
        char *str1 = lines[1];
        char *str2 = *(lines + 3);
        char c1 = *(*(lines + 4) + 6);
        char c2 = (*lines + 5)[5];
        char c3 = *lines[0] + 2;
        printf("str1 = %s
    ", str1);
        printf("str2 = %s
    ", str2);
        printf("  c1 = %c
    ", c1);
        printf("  c2 = %c
    ", c2);
        printf("  c3 = %c
    ", c3);
        return 0;
    }
    

    运行结果

    str1 = Programming
    str2 = is
      c1 = f
      c2 = 9
      c3 = E

    为了更加直观,将上述代码改成下面的形式

    #include <stdio.h>
    #include<iostream>
    using namespace std;
    int main()
    {
        char *string0 = "COSC1283/1984";
        char *string1 = "Programming";
        char *string2 = "Techniques";
        char *string3 = "is";
        char *string4 = "great fun";
    
        char *lines[5];
        lines[0] = string0;
        lines[1] = string1;
        lines[2] = string2;
        lines[3] = string3;
        lines[4] = string4;
        char *str1 = lines[1];
        char *str2 = *(lines + 3);
        char c1 = *(*(lines + 4) + 6);
        char c2 = (*lines + 5)[5];
        char c3 = *lines[0] + 2;
        printf("str1 = %s
    ", str1);
        printf("str2 = %s
    ", str2);
        printf("  c1 = %c
    ", c1);
        printf("  c2 = %c
    ", c2);
        printf("  c3 = %c
    ", c3);
        return 0;
    }
    
    1. char *lines[5]; 定义了一个指针数组,数组的每一个元素都是指向char类型的指针。最后5行,为数组的每一个元素赋值,都是直接赋给指针。
    2. 而lines,是一个指向指针的指针,它的类型为 char **,所以 *lines 是一个指向字符的指针,**lines是一个具体的字符。这一点很重要,一定要明白。
    3. 指针是可以进行运算的,lines 为lines[5]数组的首地址,即第0个元素的地址;lines+0, lines+1, lines+2 ... 分别是第0, 1, 2 ...个元素的首地址,*(lines+0)或lines[0], *(lines+1)或lines[1], *(lines+2)或lines[2] ... 分别是字符串 str0, str1, str2 ... 的首地址。所以:
    *lines == *(lines+0) == lines[0] == str0
    *(lines+1) == lines[1] == str1
    *(lines+2) == lines[2] == str2

    注意 
    lines为指向指针的指针,所以* (lines+n)为指针,**(lines+n)才为具体的字符。

    解析

    1. lines[1]:它是一个指针,指向字符串string1,即string1的首地址。  
    2. *(lines + 3):lines + 3 为lines[5]数组第3个元素的地址,  *(lines + 3)为第3个元素,它是一个指针,指向字符串string3。
    3. *(*(lines + 4) + 6):*(lines + 4) + 6 == lines[4] + 6 == string4 + 6,为字符串string4第6个字符的地址,即 f 的地址,*(*(lines + 4) + 6) 就表示字符 f。
    4. (*lines + 5)[5]:*lines + 5 为字符串 string0 第5个字符的地址,即 2 的地址,(*lines + 5)[5]等价于*(*lines + 5 + 5),表示第10个字符,即9。
    5. *lines[0] + 2:*lines[0] 为字符串string0 第0个字符的地址,即C的地址。字符与整数运算,首先转换为该字符对应的ASCII码值,然后再运算,所以 *lines[0] + 2 = 67 + 2 = 69。不过要求输出字符,所以还要转换成69所对应的字符,即E。
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    输入5个国名并按字母顺序排列后输出

    #include<stdio.h>
    #include<iostream>
    using namespace std;
    
    void sort(char *name[],int n)
    {
        char *pt;
        int i,j,k;
        for(i=0;i<n-1;i++)
        {
            k=i;
            for(j=i+1;j<n;j++)
                if(strcmp(name[k],name[j])>0)
                    k=j;
                if(k!=i)
                {
                    pt=name[i];
                    name[i]=name[k];
                    name[k]=pt;
                }
        }
    }
    
    void print(char *name[],int n)
    {
        int i;
        for (i=0;i<n;i++)
            printf("%s
    ",name[i]);
    }
    
    int main()
    {
        static char *name[]={ "CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};
        int n=5;
        sort(name,n);
        print(name,n);
        return 0;
    } 
    

    说明: 
    1. 在以前的例子中采用了普通的排序方法,逐个比较之后交换字符串的位置。交换字符串的物理位置是通过字符串复制函数完成的。反复的交换将使程序执行的速度很慢,同时由于各字符串(国名)的长度不同,又增加了存储管理的负担。用指针数组能很好地解决这些问题。把所有的字符串存放在一个数组中,把这些字符数组的首地址放在一个指针数组中,当需要交换两个字符串时,只须交换指针数组相应两元素的内容(地址)即可,而不必交换字符串本身。 
    2. 本程序定义了两个函数,一个名为sort完成排序,其形参为指针数组name,即为待排序的各字符串数组的指针。形参n为字符串的个数。另一个函数名为print,用于排序后字符串的输出,其形参与sort的形参相同。主函数main中,定义了指针数组name 并作了初始化赋值。然后分别调用sort函数和print函数完成排序和输出。值得说明的是在sort函数中,对两个字符串比较,采用了strcmp函数,strcmp函数允许参与比较的字符串以指针方式出现。name[k]和name[j]均为指针,因此是合法的。字符串比较后需要交换时,只交换指针数组元素的值,而不交换具体的字符串,这样将大大减少时间的开销,提高了运行效率。 
    3. 这题用algorithm中的sort()也可以很好的解决。

    原:http://blog.csdn.net/u014265347/article/details/54882661

  • 相关阅读:
    SQL中内连接和外连接
    MySQL执行计划解读
    排序算法
    Edge浏览器安装sci-hub插件及使用教程
    MATLAB R2020B 使用教学——窗口布局设置
    PHP半年了,已经可以独立支撑项目,几点心得记录
    看1000行代码不如自己写10行代码
    PHP逻辑运算符中的and和&&以及or和||是有区别的
    自学PHP的野方法
    PHP中SQL查询语句的id=%d解释
  • 原文地址:https://www.cnblogs.com/yuwei0911/p/7128319.html
Copyright © 2020-2023  润新知