C语言(基于Ubuntu)指针专项学习(二)

前言

        昨天我们学习了指针的基础知识以及基本的指针使用方法。指针相关知识内容庞大,今天我们来到第二部分:指针与数组。

        这是指针专项学习第二篇,之后的博客中还会有指针专项学习的更深入的内容。

        与昨天所述同理:指针学习需要有一定的C基础,如果有基础不足的情况,可以参考我之前的博客或自行学习补充。

        另外,需要说明的是:由于指针理论的复杂,本博客对理论的解释其实并不够详实,主要原因在于个人认为编程语言需要更多地基于代码去学习。如果前面有未理解的地方,可以通过后文的代码进行理解。

概述

        1.基础

        2.基础代码

        3.练习题

1.基础

        这一部分内容文字较为枯燥,后面有手写笔记以及思维导图

1.1基础理论

  1. 内存相关
    • 虚拟内存与物理内存
      • 虚拟内存(内核:用户 = 1:3),物理内存包括固态硬盘(ARM)和虚拟内存映射(./a.out)
      • 虚拟内存映射通过/hello./a.out实现
    • 内核与用户空间
      • 内核:内含计算机系统。
      • 用户空间:包括栈区、堆区和静态区,涉及手动申请、释放内存
  2. C语言代码示例及说明
    • 字符指针、字符串
      char str = "hello"; 
      char* p = str; // 指针可改变(段错误) 
      char* p = "hello"; // 指针不可变(段错误)
    • 数组与指针
      int a[2][3]; 
      &arr[0][0] ↔ a[0] // 一级地址 列偏移 
      arr ↔ &arr[0] //二级地址 行偏移 
      &arr  // 三级地址 页偏移

      特别注意多级指针与偏移的对应关系,这在代码解读题中会有体现。

      ...图片较小,请下载查看
       

    • 指针偏移:&p

      另外:a[i][j] = *((p+i)+j)

1.2数组指针:

  • int arr[2][3];
  • int (*p)[3] = arr; //指针的第一行.

1. ( ) > [ ] > * :小括号不可省略.

2. 数组指针本质上是特殊的二级指针:即将二维数组一维化,再指向第一行或某一行。

3. 数组指针类型:int (*)[]:占8 byte(64位系统)

另外:指针数组:

  • int * p[4];
    • int * p[4] = {&a, &b, &c, &d};        //具体操作等同一维数组
       

1.3外部参数:

  • int argc:外部传入参数个数
  • const char *argv[]:在命令行部传入的字符串.
  • 注意乘法要用“\*”。(eg:./a.out 8 \* 4)

1.4手写笔记:

1.5思维导图

2.基础代码

2.1 strcmp的重写:指针的使用

2.2 杨氏三角:数组指针的使用

2.3 求最大值:数组指针的使用

2.4 打印多个数组:指针数组:

2.5:外部传参的使用:

3.练习题

3.1:3个例题:

例题1:求输出结果

#include <stdio.h>

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}

答案:1

解析:

  • 代码中使用的是逗号运算符 (0, 1),不是{1,0};

    • (0, 1) 的结果是 1(逗号运算符返回最后一个表达式的值)

    • 因此数组实际初始化为:{1, 3, 5}。

    • 输出1。

例题2:求输出结果

#include<stdio.h>
int main()
{
    int a[5][5];
    int (*p)[4];
    p = a;
    printf("%p, %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

答案:0xfffffffffffffffc,-4

解析:

这段代码涉及指针运算和数组内存布局的关键概念。我们需要逐步分析 ap 的内存关系,以及 &p[4][2] - &a[4][2] 的计算过程。


3.2.1. 变量定义
int a[5][5];      // 5x5 的二维数组(共 25 个 int)
int (*p)[4];      // p 是指向长度为 4 的 int 数组的指针
p = a;            // 将 a 的首地址赋给 p(类型不匹配,但 C 允许)

关键点:

  • a 是 int[5][5],每行有 5 个 int

  • p 是 int(*)[4],它认为每行只有 4 个 int(尽管 a 的实际行宽是 5)。

  • p = a 会导致指针 p 以“每行 4 个 int”的方式解释 a 的内存。


3.2.2. 内存布局

假设 a 的起始地址为 0x1000,则内存布局如下(每个 int 占 4 字节):

地址对应 a 的元素对应 p 的行划分
0x1000a[0][0]p[0][0]
0x1004a[0][1]p[0][1]
0x1008a[0][2]p[0][2]
0x100Ca[0][3]p[0][3]
0x1010a[0][4]不属于 p[0]
0x1014a[1][0]p[1][0]
.........
0x10F0a[4][4]超出 p 的映射范围
p 的行划分:
  • p 认为每行只有 4 个 int(16 字节),而 a 的实际行宽是 5 个 int(20 字节)。

  • 因此,p 的行会逐渐与 a 的行错位。


3.2.3. p[4][2] 和 a[4][2] 的地址计算
(1) a[4][2] 的地址:
  • a 是 int[5][5]a[4][2] 的偏移量为 4 * 5 * sizeof(int) + 2 * sizeof(int) = 22 * 4 = 88 字节。

  • 地址 = 0x1000 + 88 = 0x1058

(2) p[4][2] 的地址:
  • p 是 int(*)[4]p[4] 的偏移量为 4 * 4 * sizeof(int) = 64 字节。

  • p[4][2] 的偏移量是 64 + 2 * 4 = 72 字节。

  • 地址 = 0x1000 + 72 = 0x1048

地址关系:
&p[4][2] = 0x1048
&a[4][2] = 0x1058
  • &p[4][2] - &a[4][2] 是两个指针的减法运算,结果是它们之间的 元素个数差(以 int 为单位)。

  • 0x1048 - 0x1058 = -0x10(即 -16 字节),转换为 int 的个数是 -16 / 4 = -4


3.2.4. 输出结果
  • %p 输出指针差值的十六进制形式:

    • -4 的补码表示(64 位系统)是 0xFFFFFFFFFFFFFFFC,所以输出 0xfffffffffffffffc

  • %d 直接输出 -4


3.2.5关键结论
  • 指针减法的结果是两个地址之间的 元素个数差(不是字节差)。

  • 由于 p 的错误类型(int(*)[4]),p[4][2] 的地址比 a[4][2] 的地址 小 4 个 int,因此差值为 -4

  • %p 打印的是地址差的补码形式(-4 的补码是 0xFFFFFFFC)。

例题3:求输出结果

#include <stdio.h>

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

答案:10,5

解析:

        由于与昨天的题目类似,这里的解析相对简洁。

  1. 数组初始化

    • aa[2][5] 是一个 2 行 5 列的二维数组,初始化为 { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10} }

  2. 指针操作

    • &aa + 1&aa 是整个数组的地址,+1 会跳过整个数组(即 2x5=10 个 int),指向数组末尾的下一个位置。

    • *(aa + 1)aa + 1 是第二行的地址,* 解引用后得到第二行的首元素地址(即 &aa[1][0])。

  3. 指针转换

    • ptr1 = (int*)(&aa + 1):将数组末尾的地址转换为 int* 类型。

    • ptr2 = (int*)(*(aa + 1)):将第二行的首地址转换为 int* 类型。

  4. 指针运算

    • ptr1 - 1:指向数组的最后一个元素 aa[1][4](即 10)。

    • ptr2 - 1:指向第一行的最后一个元素 aa[0][4](即 5)。

输出结果

  • *(ptr1 - 1) 输出 10

  • *(ptr2 - 1) 输出 5

3.2:练习题:

题目:

1.设有说明语句:char a[]=”It is mine”;char *p=”It is mine”;则以下不正确的叙述是              

A)a+1表示的是字符t的地址                  

B)p指向另外的字符串时,字符串的长度不受限制

C)p变量中存放的地址值可以改变

D)a中只能存放10个字符

2.若有定义:int a[2][3];则对a数组的第i行第j列元素值的正确引用是         

A)*(*(a+i)+j)         B)(a+i)[j]             C)*(a+i+j)          D)*(a+i)+j 

3.若有定义:int a[2][3];则对a数组的第i行第j列元素地址的正确引用是        

A)*(a[i]+j)           B)(a+i)              C)*(a+j)          D)a[i]+j

4.若有程序段:int a[2][3],(*p)[3]; p=a;则对a数组元素地址的正确引用是        

A)*(p+2)            B)p[2]               C)p[1]+1             D)(p+1)+2

5.若有程序段:int a[2][3],(*p)[3]; p=a;则对a数组元素的正确引用是            

A)(p+1)[0]           B)*(*(p+2)+1)        C)*(p[1]+1)           D)p[1]+2

6.若有定义:int (*p)[4];则标识符p             

A)是一个指向整型变量的指针

B)是一个指针数组名

C)是一个指针,它指向一个含有四个整型元素的一维数组

D)定义不合法

7.以下与int *q[5];等价的定义语句是             

A)int q[5]           B)int *q              C)int *(q[5])           D)int (*q)[5]

8.以下正确的说明语句是              

A)int *b[]={1,3,5,7,9} ;

B)int a[5],*num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

C)int a[]={1,3,5,7,9}; int *num[5]={a[0],a[1],a[2],a[3],a[4]};

D)int a[3][4],(*num)[4] ; num[1]=&a[1][3];

9.若有定义:int b[4][6],*p,*q[4];且0≤i<4,则不正确的赋值语句是            

A)q[i]=b[i];         B)p=b;               C)p=b[i];              D)q[i]=&b[0][0]

                                                        

10.下面程序段的输出是              

int a[ ]={2,4,6,8,10,12,14,16,18,20,22,24},*q[4],k;

for (k=0; k<4; k++) q[k]=&a[k*3];

printf(“%d\n”,q[3][0]);

A)22               B)16             C)20         D)输出不合法

11.若有定义int a[4][6];则能正确表示a数组中任一元素a[i][j](i,j均在有效范围内)地址的表达式              

A)&a[0][0]+6*i+j      B)&a[0][0]+4*j+i

C)&a[0][0]+4*i+j      D)&a[0][0]+6*j+I

12.下面程序的运行结果是              

main ( )

{  int x[5]={2,4,6,8,10}, *p, **pp ;

   p=x , pp = &p ;

   printf(“%d”,*(p++));

   printf(“%3d”,**pp);

}

A)4  4            B)2  4              C)2  2                D)4  6

13.若有定义int x[4][3]={1,2,3,4,5,6,7,8,9,10,11,12}; int (*p)[3]=x ; 则能够正确表示数组元素x[1][2]的表达式是             

A)*((*p+1)[2])                           B)(*p+1)+2

C)*(*(p+5))                             D)*(*(p+1)+2)

练习题参考答案与部分解析

12345
DADCC
678910
CABBC
111213
ABD

1.设有说明语句:char a[]=”It is mine”;char *p=”It is mine”;则以下不正确的叙述是              

A)a+1表示的是字符t的地址                  

B)p指向另外的字符串时,字符串的长度不受限制

C)p变量中存放的地址值可以改变

D)a中只能存放10个字符

易错:C(地址可变,但字符串不可变)

D

2.若有定义:int a[2][3];则对a数组的第i行第j列元素值的正确引用是         

A)*(*(a+i)+j)         B)(a+i)[j]             C)*(a+i+j)          D)*(a+i)+j 

A

3.若有定义:int a[2][3];则对a数组的第i行第j列元素地址的正确引用是        

A)*(a[i]+j)           B)(a+i)              C)*(a+j)          D)a[i]+j

易错:A(问的是地址,A是值)

D

4.若有程序段:int a[2][3],(*p)[3]; p=a;则对a数组元素地址的正确引用是        

A)*(p+2)            B)p[2]               C)p[1]+1             D)(p+1)+2

C

5.若有程序段:int a[2][3],(*p)[3]; p=a;则对a数组元素的正确引用是            

A)(p+1)[0]           B)*(*(p+2)+1)        C)*(p[1]+1)           D)p[1]+2

C

6.若有定义:int (*p)[4];则标识符p             

A)是一个指向整型变量的指针

B)是一个指针数组名

C)是一个指针,它指向一个含有四个整型元素的一维数组

D)定义不合法

C

7.以下与int *q[5];等价的定义语句是             

A)int q[5]           B)int *q              C)int *(q[5])           D)int (*q)[5]

C

注意:D是数组指针,而题目是指针数组。

8.以下正确的说明语句是              

A)int *b[]={1,3,5,7,9} ;//错

B)int a[5],*num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};//对

C)int a[]={1,3,5,7,9}; int *num[5]={a[0],a[1],a[2],a[3],a[4]};//错

D)int a[3][4],(*num)[4] ; num[1]=&a[1][3];//错

B

9.若有定义:int b[4][6],*p,*q[4];且0≤i<4,则不正确的赋值语句是            

A)q[i]=b[i];         B)p=b;               C)p=b[i];              D)q[i]=&b[0][0]

B

注意指针的等级

                                                    

10.下面程序段的输出是              

int a[ ]={2,4,6,8,10,12,14,16,18,20,22,24},*q[4],k;

for (k=0; k<4; k++) q[k]=&a[k*3];

printf(“%d\n”,q[3][0]);

A)22               B)16             C)20         D)输出不合法

C

11.若有定义int a[4][6];则能正确表示a数组中任一元素a[i][j](i,j均在有效范围内)地址的表达式              

A)&a[0][0]+6*i+j      B)&a[0][0]+4*j+i

C)&a[0][0]+4*i+j      D)&a[0][0]+6*j+I

A

12.下面程序的运行结果是              

main ( )

{  int x[5]={2,4,6,8,10}, *p, **pp ;

   p=x , pp = &p ;

   printf(“%d”,*(p++));

   printf(“%3d”,**pp);

}

A)4  4            B)2  4              C)2  2                D)4  6

B

13.若有定义int x[4][3]={1,2,3,4,5,6,7,8,9,10,11,12}; int (*p)[3]=x ; 则能够正确表示数组元素x[1][2]的表达式是             

A)*((*p+1)[2])                           B)(*p+1)+2

C)*(*(p+5))                             D)*(*(p+1)+2)

D

结语

        指针学习属实需要耐力,再接再厉!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值