欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

字符指针和字符数组、多维指针和多维数组

程序员文章站 2024-03-04 15:41:11
...

指针的概念以及一维指针和一维数组相关的知识,我这里就不具体讲了,本文主要讲解字符指针和字符数组、多级指针和多级数组这些比较容易混淆的知识点。

  • 指针运算

    1. 赋值运算

      指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址

      int *px, *py, *pz, x = 10;
      //赋予某个变量的地址
      px = &x;
      //相互赋值
      py = px;
      //赋值具体的地址
      pz = 0x4000;
      
    2. 加减运算

      • 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。

        int arr[2] = {1, 2};
        printf("%p %p %p %p\n", &arr[0], arr, &arr, arr+1, &arr+1);
        -----------------------------------------------
        输出:62FE40 62FE40 62FE40 62FE44 62FE48
        //数值上:&arr[0]==arr==&arr
        //数组名arr表示数组首元素的首地址,即元素arr[0]的地址&arr[0]
        //&arr表示整个数组存储在内存中的首地址,数值上跟数组首元素的首地址相同,但表示的意义不同
        //arr+1和&arr+1运算,指针所移动的单元长度是不同,前者移动一个int类型的长度,后者移动2个int长度(即数组的长度)
        
      • 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。

    3. 关系运算

      假设有指针变量 px、py。

      • px > py 表示 px 指向的存储地址是否大于 py 指向的地址

      • px == py 表示 px 和 py 是否指向同一个存储单元

      • px == 0 和 px != 0 表示 px 是否为空指针

        //定义一个数组,数组中相邻元素地址间隔一个单元
        int num[2] = {1, 3};
        
        //将数组中第一个元素地址和第二个元素的地址赋值给 px、py
        int *px = &num[0], *py = &num[1];
        int *pz = &num[0];
        int *pn;
        
        //则 py > px
        if(py > px){
        	printf("py 指向的存储地址大于 px 所指向的存储地址");
        }
        
        //pz 和 px 都指向 num[0]
        if(pz == px){
        	printf("px 和 pz 指向同一个地址");
        }
        
        //pn 没有初始化
        if(pn == NULL || pn == 0){
        	printf("pn 是一个空指针");
        }
        
    4. 同类型指针相减

      同类型指针相减,返回值是个整数,其值可用下面的公式计算:

      (指针1的值 - 指针2的值)/指针所指变量占用的内存字节数

字符指针和字符数组

在 C 语言中本身没有提供字符串数据类型,但是可以通过字符数组和字符指针的方式存储字符串。

  1. 字符数组方式

    定义一个数组存储字符串的元素。

    char word[] = "Hello world!";
    printf("%s", word);
    
  2. 字符指针方式

    定义一个指向字符串的指针。指针方式操作字符串和数组操作字符串类似,可以把定义的指针看做是字符数组的数组名。

    //除了定义一个字符数组外,还可以直接定义一个字符指针存储字符串
    char *word = "Hello world!";
    
    //此时可以做字符串的操作
    //输出
    printf("%s\n", word);
    
    //通过下标取字符
    printf("%c\n", word[0]);
    
    //获取字符串长度,其中 strlen 是 string.h 库中的方法
    printf("%d\n", strlen(sentence));
    

    注:字符指针方式区别于字符数组方式,字符数组不能通过数组名自增操作,但是字符指针是指针,可以自增操作。

    字符指针和字符数组如何应用呢?

    我们知道数组是不能直接相互赋值的,下面使用字符指针将字符数组 sentence 中的内容复制到字符数组 word 中:

    //定义字符数组 sentence 和 word,给 sentence 赋初值
    char sentence[] = "Hello world!", word[13];
    
    //word = sentence; 会报错!
    
    //定义字符指针,指向 word
    char *ch = word;
    int i;
    
    //循环赋值
    for(i = 0; sentence[i] != '\0'; i++){
    	*(ch + i) = sentence[i];
    }
    
    //在当 i 等于 sentence 的长度(sentence 的长度不包含'\0')时,
    //i 继续自增,此时判断 sentence[0] != '\0'不符合,跳出循环,则 i 比 sentence 长度大 1
    *(ch + i) = '\0';
    
    //输出字符串,因为 ch 指向 word,所以输出结果是一样的
    printf("ch = %s, word = %s", ch, word);
    
  • 多级指针和多维数组

  1. 多级指针

    指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。我们先看看二级指针,它们关系如下:

    字符指针和字符数组、多维指针和多维数组其中 p 为一级指针,pp 为二级指针。

    具体定义和操作:

    //定义普通变量和指针变量
    int *pi, i = 10;
    //定义二级指针变量
    int **ppi;
    
    //给指针变量赋初值
    pi = &i;
    
    //给二级指针变量赋初值
    ppi = π
    
    //我们可以直接用二级指针做普通指针的操作
    //获取 i 的内容
    printf("i = %d", **ppi);
    //获取 i 的地址
    printf("i 的地址为%d", *ppi);
    
  2. 多维数组

    • 多维数组的地址

      拿二维数组举例:

      int nums[2][2] = {
      	{1, 2},
      	{2, 3}
      };
      

      字符指针和字符数组、多维指针和多维数组

    分析:

    • 将数组当成一种数据类型 x,那么二维数组就可以当成一个元素为 x 的一维数组。

    • 先把数组nums看作一维数组,有两个元素,nums[0]和 nums[1];

    • 将 nums[0]看做一个整体,作为一个名称可以用 a 替换。则 a [0]就是 nums[0] [0],其值为 1,以此类推。

    我们知道数组名即为数组首地址,上面的二维数组有两个维度。首先我们把按照上面理解,那么 nums 就是一个数组,则nums 就作为这个数组的首地址。第二个维度还是取 nums[0],我们把 nums[0]作为一个名称,其中有两个元素。我们可以尝试以下语句:

    printf("%d", nums[0]);
    

    发现此语句的输出结果为一个指针,在实验过后,发现就是 nums[0] [0]的地址。即数组第一个元素的地址。

    • 引申到三维数组
    //假设已初始化,二维数组数据类型设为 x,一维数组数据类型设为 y
    int a[2][2][2] = {
        {{1, 2},{3, 4}},
        {{5, 6},{7, 8}}
    };
    
    //此数组首地址为该数组名称
    printf("此数组首地址为%d\n", a);
    printf("a+1 = %d\n", a+1);
    printf("*(a+1) = %d\n", *(a+1));
    
    //此数组可以看做存储了两个 x 类型元素的一维数组,则 nums[0] = x1 的地址为
    printf("第二个维度的首地址为%p\n", a[0]);
    printf("a[0]+1 = %d\n", a[0]+1);
    printf("*(a[0]+1) = %d\n", *(a[0]+1));
    
    //而 x1 可以看做存储了两个 y 类型元素的一维数组,则 y1 = x1[0] = nums[0][0]
    printf("第三个维度的首地址为%d\n", a[0][0]);
    printf("a[0][0]+1 = %d\n", a[0][0]+1);
    printf("*(a[0][0]+1) = %d\n", *(a[0][0]+1));
    

    运行结果为:
    字符指针和字符数组、多维指针和多维数组

    于是三维数组实际存储形式为:

    字符指针和字符数组、多维指针和多维数组

    图中上面三层可以当作指针,第四层当作变量。a 、a [0] 、a[0] [0] 指向数组首元素的首地址,即&a[0] [0] [0],同理,它们只是数值上相等,性质是不同的;实际存储内容的为最内层维度,且为连续的。对于 a 来说,其个跨度为 4 个单元;对 a[0]、a[1] 来说,其跨度为 2 个单元;对 a[0] [0]、a[0] [1]、a[1] [0]、a[1] [1] 来说,跨度为 1 个单元,即越到内层,跨度越小

  3. 多维数组的指针

    我们知道指针数组元素可以指向一个数字,那么我们现在可以尝试用指针数组的每个元素指向一个数组:

    //定义一个二维数组
    int nums[2][2][2] = {
        {1, 2},
    	{2, 3}
    };
    
    //此时 nums[0]、和 nums[1]各为一个数组, 我们可以用指针数组 p 操作一个二维数组
    int *p[2] = {nums[0], nums[1]};
    
    //p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0]
    printf("nums[0][0] = %d\n", **p);
    
    //指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0]
    printf("nums[1][0] = %d\n", **(p + 1));
    
    //先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1]
    printf("nums[0][1] = %d\n", *(*p + 1));
    

    这里可能不能理解为什么*p + 1 = &nums[0] [1],而不是 nums[1]。p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以p + 1 = &nums[0] [1],而 p + 1 = nums[1]。

  • 最后来道题加深一下印象

    #include<stdio.h>
    
    int main()
    {
        char *str[]={"welcome","to","kaikeba","xinzhike"};
        char **p=str;
        str[0] = *p+3;
        str[1] = *++p+2;
        str[2] = p[1]+3;
        str[3] = p[2]+(str[2]-str[1]);
        printf("%s\n", str[0]);
        printf("%s\n", str[1]);
        printf("%s\n", str[2]);
        printf("%s\n", str[3]);
        return 0;
    }
    

    运行结果为:

    字符指针和字符数组、多维指针和多维数组
    分析一波:

    1、首先 str 是一个指针数组,数组的每个元素是字符串。str[0],str[1],str[2],str[3]分别指向每个字符串的首地址,其中str[0]比较特殊,即是第一个字符串的首地址,也是所以串(即整个数组)的首地址。

    2、二级指针p, 指向的是一个指针变量,这个指针变量str就是str[0],所以p指针指向的是 “welcome” 这个字符串,*p == “welcome” 的真实地址,**p == “welcome”的第一个字符 ‘w’ ,以此类推则有:

    **p[0] == str[0], p[1] == str[1], p[2] == str[2], *p[0] == ‘w’, *p[1] == ‘t’, p[2] == ‘k’, p[3]==‘x’

    3、str[0]= * p + 3; 首先取*p,也就是“welcome”, 取出“welcome”时指向的是“welcome”的起始地址('w’的地址), 然后+3向后移动三个字符指向了“come”, 所以str[0] == "come"

    4、str[1]= *++p+2; 首先++p, 因为p指向的是以 “welcome” 开头的后面所有字符串,自增后,p的指向由 str[0] 指向 str[1],然后 *++p == str[1],即"to" ,取出“to”时指向的是“to”的起始地址(‘t’的地址), 然后+2向后移动2个字符指向了"\0"(别忘了字符串最后还有一个空字符’\0’)。即str[1] == ""

    5、str[2]=p[1]+3; p是从指向的str[1]开始的所有字符串,所以p[0]指向str[1],p[1]指向str[2],p[1]+3就是指向str[2]的第4个字符,所以 str[2]==“keba”.

    6、str[3] = p[2] + (str[2] - str[1]); p[0]指向的是str[1], 则p[2] 指向 str[3],即"xinzhike" (注意str[1]和str[2] 已经变了哟) str[2] - str[1] = = 4 (str[2] 指向"kaikeba"的"keba",str[1] 指向"to"的"\0",因此他们之间差4个字符地址再/sizeof(char) == 4 ) ),所以在"xinzhike"首元素位置往后移动4个,所以str[3]=="hike"