C语言指针与数组 进阶

本章主要是补充 指针和数组方面的指示,把前面指针的知识补充下。参考前面的C语言基础—指针

C语言指针与数组 进阶

  • 用一级指针访问二维数组
    • ❗易错点: 不能直接`指针变量=数组名`
  • 指向数组的指针
    • 1. 指向指针的指针
    • 2. 指向一维数组的指针 (*P)[4]—行指针
  • 二维数组名
  • 指针数组
    • ❗易错点: 区别行指针 (*ps)[10]— *ps[10] — *(ps[10])
    • ❗❗指针数组的作用
    • 指针数组表示二维数组的举例:p[i]=a[i]
  • 动态申请和释放二维数组
    • 动态一维数组实现
    • 用指针数组实现
      • - 为什么不用定义int *a[N]?
      • 动态分配内存的过程
      • 分配内存的图示
        • 定义指针数组
        • 动态申请指针数组
  • 用指针数组处理多个字符串
    • 二维字符数组存储多个字符串
    • 使用指针数组存储多个字符串
    • 下面举几个例子
    • 字符串排序 (指针数组)
      • 方法一: 二维字符数组
      • 方法二:使用指针数组处理
      • n个字符串排序(指针数组实现)

用一级指针访问二维数组

数组其实是按行排序在内存地址中的,所以可以省略行,不能省略列。
C语言指针与数组 进阶_第1张图片

具体解释见C语言数组——多维数组

利用这一特性,可以通过指针偏移遍历数组

比如假设a是一个M行N列的数组,M,N列是预定义的常量,定义如下:

int a[M][N];
int *p=&a[0][0];

下面的语句段可以求出数组a中所有元素的累加和:

sum=0;
for(i=0;i<M*N;i++,p++)
sum+=p;

也可以通过行列号做循环变量,使用双重循环来实现:

求所有上三角元素的和:

for(i=0;i<M;i++)
 for(j=i;j<N;j++)
   sum+=*(p+i*N+j); 
   //或者sum+= *(*(p+i)+j);

❗易错点: 不能直接指针变量=数组名

  • 也就是说不能p=&a[0][0]能写成p=a

二维数组名a可以被解释为指向第一行的指针,即a和&a[0]是等价的。
但是a并不是指向二维数组中第一个元素的指针,而是指向二维数组中第一行的指针。
因此,将指针p赋值为二维数组a是不正确的,因为它们指向的内存地址不同,不能直接相互赋值。

也就是说数组名在多维数组中指的是指向第一行的指针. a+1代表的是下一行的数据


指向数组的指针

1. 指向指针的指针

类型名 **变量名

比如下面:

int i,*p=&i;
int **dp;
dp=&p;

C语言指针与数组 进阶_第2张图片

dp中存储的是指针变量p的地址,我们说,dp指向指针变量p,dp是指向指针(p)的指针(变量)


2. 指向一维数组的指针 (*P)[4]—行指针

比如二维数组: int a[3][4]
定义了一个3行4列的二维数组,可以看成a[0] a[1] a[2]三个元素组层的一维数组,每个元素a[i]都是一个有4个int型元素的值,定义一个指针p,我们已经知道P+1是下一行
C语言指针与数组 进阶_第3张图片

  • 如何定义这种已经知道列的二维数组的指针呢?

可以这样写: int (*p)[4];

声明了p是一个指向数组的指针,指向的是int[4]类型,

  • 如何让p指向一维数组a[0]?

可以这样写:p=&a[0];

C语言指针与数组 进阶_第4张图片

 

  • (*p)[0] 即 a[0][0]的引用
  • (*p)[1] 即 a[0][1]的引用
  • 要想下一行,p+1

比如假设a是一个M行N列的数组,M,N列是预定义的常量,定义如下:

int a[M][N];
int *p=&a[0];

下面的代码可以求元素和:

sum=0;
for(i=0;i<M;i++,p++) //p++自增,将指向下一行
  for(j=0;j<N;j++)
    sum+=(*p)[j]; //p指向数组a[i],(*p)等价于a[i]

二维数组名

我们通过上面已经知道 a是指向一维数组a[0]的指针,类似这样 *(a+i)就是对a[i]的引用

我们知道数组在内存中是按竖着按行排列的

C语言指针与数组 进阶_第5张图片

所以 *(a+i)+j就是 第i行a[i]再+j 取得 a[i][j]的地址

也就是说如图:
指针 a[i]+1、 *(a+i)+1指向a[i][1]; *(a[i]+1)、*( *(a+i)+1)是对a[i][1]的引用


举例: 用指针访问二维数组 求元素和

#include 

int main() {
    int i,j,m,n;
    int sum = 0;
    scanf("%d%d",&m,&n);
     int a[m][n];
     printf("\n");

       for (int i = 0; i < m; i++) 
        for ( j = 0; j < n; j++)
            scanf("%d",*(a+i)+j); //这是指针
    
     
    for (int i = 0; i < m; i++)
        for ( j = 0; j < n; j++) 
            sum += *(*(a+i)+j);  //指针引用取值
    
    printf("三角元素的和为%d\n", sum);
    return 0;
}

在这里插入图片描述


指针数组

指针数组就是数组里面每个都是指针

格式:类型名* 数组名[数组长度]
数组元素是指针类型,用于存储地址

  • int a[10];
    a是一个数组,它有10个元素
    每个元素的类型都是整型

  • char *color[5];
    color是一个数组,它有5个元素
    每个元素的类型都是字符指针


❗易错点: 区别行指针 (*ps)[10]— *ps[10] — *(ps[10])

int *ps[10];
定义了含有10个元素的一维数组ps,每个元素都是int指针类型

区别于 int (*ps)[10],这个是一个指向10列的二维数组的一个指针变量.

因为[]的优先级高于*,所以int *ps[10];解释为int*(ps[10])这样


❗❗指针数组的作用

指针数组的主要作用是表示二维数组,尤其是字符串数组
用指针数组表示二维数组的好处是:
二维数组的每一行或字符串数组的每个字符串可以具有不同的长度.

在二维数组中,每一行可以具有不同的长度,但是使用传统的二维数组表示方式,每一行的长度都必须相同,这给程序的编写带来了一定的限制。而使用指针数组来表示二维数组,则可以轻松地实现每一行具有不同长度的效果。例如,可以使用指针数组来表示一个3行不等长的二维数组,代码如下:

int a1[] = {1, 2, 3};
int a2[] = {4, 5};
int a3[] = {6, 7, 8, 9};
int *p[] = {a1, a2, a3};

在上面的例子中,指针数组p中的每个元素都是一个指向int类型的指针,每个指针指向一个长度不同的int类型数组,即一个二维数组的一行。在上面的例子中,第一行有3个元素,第二行有2个元素,第三行有4个元素。

另外,在字符串数组中,每个字符串的长度也可以不同,使用指针数组来表示字符串数组可以更加灵活地处理不同长度的字符串。

因此,使用指针数组来表示二维数组或字符串数组,可以具有不同的长度,从而更加灵活地处理数据。

指针数组表示二维数组的举例:p[i]=a[i]

在表示二维数组时,指针数组可以被用来代替二维数组。二维数组是一组连续的内存空间,元素的地址是连续的。而指针数组则是一组指针,每个指针指向一个内存空间,这些内存空间不一定是连续的。因此,指针数组可以用来表示非连续的二维数组。

例如,可以使用指针数组来表示一个3行4列的二维数组,代码如下:

int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int *p[3];
for (int i = 0; i < 3; i++) {
    p[i] = a[i];
}

在上面的例子中,指针数组p中的每个元素都是一个指向int类型的指针,每个指针指向一个长度为4的int类型数组,即一个二维数组的一行,比如p[0]指向a[0]第一行的

在循环中,通过将指针数组p中的每个元素指向二维数组a的每一行,就可以使用指针数组p来表示整个二维数组。

使用指针数组表示二维数组的好处是,可以更加灵活地对数组进行操作。例如,可以通过修改指针数组中的元素来改变二维数组的大小和形状,而不需要创建一个新的二维数组。同时,指针数组的内存使用也更加灵活,可以根据需要动态分配内存。


动态申请和释放二维数组

输入整数n和n,然后输入m行n列的矩阵。然后输出矩阵。要求动态申请和释放二维数组

  • 方法1:用动态一维数组的方法存储二维数组
  • 方法2:用指针数组实现

动态一维数组实现

也就是申请的内存块是一维数组那样的,然后一个一个装进去。
C语言指针与数组 进阶_第6张图片
代码如下:

#include 
#include 

int main() {
    int n, m,i,j;
    scanf("%d %d", &n, &m);

    // 动态申请一维数组 m*n个内存块
    int *p = (int *) malloc(n * m * sizeof(int));
   
    for (i = 0; i < m; i++)
       { for (j = 0; j < n; j++)
         scanf("%d",p+i*n+j);//scanf("%d",&p[i*n+j]);
       }
           
    for (i = 0; i < m; i++)
 {       for (j = 0; j < n; j++)
            printf("%d ",p[i*n+j]);//指针也可以作为数组名
            printf("\n");
}
        
    return 0;
}

在这里插入图片描述


用指针数组实现

(1) 声明一个一维指针数组,存放每行的首地址

int **a; //指向指针数组的首地址
a=(int **)malloc(m*sizeof(int*));

(2) 用malloc为每一行元素申请空间

for(i=0;i<m;i++)
a[i]=(int*)malloc(n*sizeof(int));

(3) 读入m*n个元素
(4) 输出矩阵
(5) 释放每一个行指针所指空间

for(i=0;i<m;i++)
free(a[i]);

代码如下:

#include 
#include 
#define N 100

int main() {
    int n, m,i,j;
    // 读入矩阵的行列数
    printf("请输入矩阵的行数和列数:\n");
    scanf("%d %d", &m, &n);

     // 动态分配指针数组,存放每行的首地址
    int **a=(int **)malloc(m * sizeof(int *)); //申请存储m个指针的内存块
    
   
   // 动态分配每一行的空间
    for (i = 0; i < m; i++) {
        a[i] = (int *)malloc(n * sizeof(int));
    }
           
   // 读入矩阵元素
    printf("请输入矩阵的元素:\n");
    for (i = 0; i < m; i++) {
        for (j = 0; j < n; j++) {
            scanf("%d", &a[i][j]);
        }
    }

     // 输出矩阵
    printf("您输入的矩阵为:\n");
    for (i = 0; i < m; i++) {
        for (j = 0; j < n; j++) {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }

        // 释放每一行的空间
       for(i=0;i<m;i++)
      free(a[i]);

    return 0;
}

C语言指针与数组 进阶_第7张图片


下面解释下?

- 为什么不用定义int *a[N]?

实际上,定义 int *a[N] 可以用来定义一个指针数组,每个指针指向一个整型数组。在这个例子中,指针数组的长度为 N,但是在动态分配内存时使用的是一个 m 倍的空间,这可能会导致内存溢出。因此,指针数组的定义在这里并不适用。相反,需要使用一个指向指针的指针 int **a 来动态分配内存。

动态分配内存的过程

首先,通过调用 malloc() 函数来为存储指针的内存块分配空间。为了能够存储 m 个指针,需要分配 m * sizeof(int *) 字节的内存。然后,通过循环分配每一行的内存空间。每一行需要分配n * sizeof(int)字节的内存。

总结一下,a[i] 可以直接使用是因为它在数组 a 中被定义为指向整型数组的指针。在动态分配内存时,首先需要为存储指针的内存块分配空间,然后循环为每一行分配内存。

也就是系统自动分配了指针数组,因为通过分配m * sizeof(int *) 字节的内存相当于知道 a[i]的地址,然后再通过n * sizeof(int)分配每一行的列的内容的内存。


分配内存的图示

定义指针数组

C语言指针与数组 进阶_第8张图片

动态申请指针数组

C语言指针与数组 进阶_第9张图片

C语言指针与数组 进阶_第10张图片


用指针数组处理多个字符串

二维字符数组存储多个字符串

char ccolor[][7]={"red","blue","yellow","green","purple"};

一个字符占一位,列数取决于最长的字符串

C语言指针与数组 进阶_第11张图片

使用指针数组存储多个字符串

char *pcolor[]={"red","blue","yellow","green","purple"};

这时候指针数组里面每个指针指向字符串首地址

C语言指针与数组 进阶_第12张图片
优点是更节省内存空间


下面举几个例子

字符串排序 (指针数组)

{“red”,“blue”,“yellow”,“green”,“purple”}; 请排序后输出

方法一: 二维字符数组

#include  
#include 

int main(void) 
{ 
    char colors[][7] = {"red", "blue", "yellow", "green", "purple"}; int num_colors = sizeof(colors) / sizeof(colors[0]); //行

// Sort the colors
for (int i = 0; i < num_colors - 1; i++) {
    for (int j = i + 1; j < num_colors; j++) {
        if (strcmp(colors[i], colors[j]) > 0) {
            char temp[7];
            strcpy(temp, colors[i]);
            strcpy(colors[i], colors[j]);
            strcpy(colors[j], temp);
        }
    }
}

// Print the sorted colors
for (int i = 0; i < num_colors; i++) {
    printf("%s\n", colors[i]);
}

return 0;
}

在这里插入图片描述


方法二:使用指针数组处理

本质是改变指针数组中每个指针的地址,改变指向方向

C语言指针与数组 进阶_第13张图片

#include 
#include 

int main(void) { 
    char *colors[] = {"red", "blue", "yellow", "green", "purple"}; 
    int num_colors = sizeof(colors) / sizeof(colors[0]);

// Sort the colors
for (int i = 0; i < num_colors - 1; i++) {
    for (int j = i + 1; j < num_colors; j++) {
        if (strcmp(colors[i], colors[j]) > 0) {
            char *temp;
            temp = colors[i];
            colors[i] = colors[j];
            colors[j] = temp;
        }
    }
}

// Print the sorted colors
for (int i = 0; i < num_colors; i++) {
    printf("%s\n", colors[i]);
}

return 0;
}

在这里插入图片描述


n个字符串排序(指针数组实现)

输入n和n个字符串(每个字符串不超过80个字符)请排序后输出,要求使用指针数组(而不是二维字符数组)处理

可以使用指针数组和动态内存分配来实现,具体思路如下:

  1. 首先读入字符串的个数 n;
  2. 使用 malloc 函数动态分配一个指针数组 strArr,大小为 n;
  3. 然后使用 fgets 函数逐个读入字符串,将每个字符串的地址存储到指针数组中;
  4. 对指针数组中的字符串进行排序,可以使用 qsort 函数;
  5. 最后遍历指针数组,按照顺序输出每个字符串。
#include 
#include 
#include 

#define MAX_LENGTH 80

int cmp(const void *a, const void *b) {
    return strcmp(*(char **)a, *(char **)b);
}

int main() {
    int n;
    printf("请输入字符串的个数:");
    scanf("%d", &n);

    // 动态分配指针数组
    char **strArr = (char **)malloc(n * sizeof(char *));
    if (strArr == NULL) {
        printf("动态分配内存失败!\n");
        exit(1);
    }

    // 逐个读入字符串
    for (int i = 0; i < n; i++) {
        strArr[i] = (char *)malloc((MAX_LENGTH + 1) * sizeof(char));
        if (strArr[i] == NULL) {
            printf("动态分配内存失败!\n");
            exit(1);
        }
        printf("请输入第%d个字符串:", i + 1);
        getchar();  // 去掉回车
        fgets(strArr[i], MAX_LENGTH + 1, stdin);

        // 如果字符串最后一个字符是 '\n',将其替换为 '\0'
        int len = strlen(strArr[i]);
        if (strArr[i][len - 1] == '\n') {
            strArr[i][len - 1] = '\0';
        }
    }

    // 排序指针数组
    qsort(strArr, n, sizeof(char *), cmp);

    // 输出排序后的字符串
   for (int i = 0; i < n; i++) 
   { printf("%s\n", strArr[i]); }

   // 释放动态分配的内存
for (int i = 0; i < n; i++) {
    free(strArr[i]);
}
free(strArr);

return 0;
}

在这里插入图片描述


解释下:

cmp 函数是一个用于比较两个字符串指针的函数,它的作用是在使用 qsort 函数对指针数组进行排序时,指定排序的规则。

qsort 函数的原型为:

void qsort(void *base, size_t nmemb, size_t size, int (*compar)
(const void *, const void *));

其中,compar 参数是一个指向比较函数的指针, 它接受两个指针参数,分别指向数组中需要比较的两个元素。比较函数应该返回一个整数值,表示两个元素的大小关系

 

在本例中,cmp 函数的作用是比较两个字符串指针所指向的字符串,返回它们的大小关系。具体地,cmp 函数的参数类型为 const void *,也就是指向任意类型的指针。我们需要将它们转换为指向 char * 类型的指针,然后再进行字符串比较。

为了实现这个目的,我们首先将 a 和 b 转换为指向 char ** 类型的指针。然后使用 * 运算符来取出指针所指向的字符串,再使用 strcmp 函数进行比较。

因此,cmp 函数的实现如下:

int cmp(const void *a, const void *b) {
    return strcmp(*(char **)a, *(char **)b);
}

这样,在调用 qsort 函数时,我们可以将 cmp 函数的地址作为参数传递,qsort 函数会根据 cmp 函数的返回值来排序指针数组中的元素。


你可能感兴趣的:(C语言程序设计,c语言,算法,数据结构)