二维数组及qsort的compar函数写法详解

本篇文章是在做LeetCode 题目#524时总结的,主要弥补了二维数组、多个字符串的存储方法、qsort的使用三方面的知识。

#524. 通过删除字母匹配到字典里最长单词

 

题目类型

双指针

做题总结

1. 关于二维数组的用法

  • 二维数组的概念:二维数组和一维数组差不多,只是其数组元素为一维数组,其实a[0]、a[1]...就是一维数组的首地址。要想理解好二维数组的概念,需要理解二维数组的内存模型,以int a[4][3]为例,
    [___|___|___][___|___|___][___|___|___][___|___|___]
    ^            ^            ^            ^               
    a           a+1           a+2          a+3          int **
    a[0]        a[1]          a[2]         a[3]         int *
    a[0][0]     a[1][0]       a[2][0]      a[3][0]      int
    下标与指针的关系和一维数组相同:            
    a[1] = *(a+1)  a[2] = *(a+2) ...                           
    a[1][1] = *(a[1]+1)  a[1][2] = *(a[1]+2) ...         
  • 注意,a[0]和数组元素a[0][0]的地址在数值上是相同的,但二者不是相同的地址(指针)类型
  • 二维数组的数组名:在int a[3][4]中,数组名a是指向第一个一维数组的指针,(a+1)指向第二个...而a[0] = *(a+0) = *a为第一个一维数组的数组名,a[1] = *(a+1)为第二个一维数组的数组名...
  • 如何声明一个指向整形数组的指针?:int (*p)[4],这里必须指明你要指向的数组的元素数量,这样p在运算时才能够进行正确的偏移,如p++会将地址增加4*sizeof(int)。可以使用指向数组的指针来对二维数组进行操作,如
    int a[3][4];
    int (*p)[4] = a;
    p++;
    ...
    如果想要一个指针来逐个访问二维数组中的元素,那么可以这样声明:int *p = &a[0][0]int *p = a[0]
  • 以多维数组作为函数参数时,对应函数形参怎么声明?:作为函数参数的多维数组名的传递方式和一维数组相同——实际传递的是一个指向数组第一个元素的指针。首先看一维数组的数组名作为函数参数的情况:
    int a[10];
    ...
    func1( a );
    这里func1的函数原型可以是以下2种中的任何一种:
    void func1( int *vec );
    void func1( int vec[] );
    再看二维数组的情况:
    int b[4][10]
    ...
    func2( b );
    这里func2的函数原型可做如下声明:
    void func2( int (*mat)[10] );
    void func2( int mat[][10] );
    注意以下声明是错误的:void func2( int **mat );。虽然二维数组名属于指向指针的指针,但其指向的是一维数组的指针(首地址),这里包含了一维数组的性质——元素个数,而int **mat;指向的是整形变量的指针(地址)。如果把二维数组名b传递给函数void func2( int **mat ),则在进行mat的运算操作时会产生问题:mat++只能将地址偏移sizeof(int),这相当于mat指向数组b[4][1],如果是void func2( int (*mat)[10] );,则mat++会将地址偏移10*sizeof(int)
  • 如何存储一组字符串?:有2种方法,一种是用指针数组,声明及初始化如下:
    char *ps[] = { "just", "do", "it" };
    char *ps[3] = { "just", "do", "it" };
    使用这种方法,数组元素为各个字符串的指针,占用空间大小为3*sizeof(char *)
    另一种方法是用二维数组,声明及初始化如下:
    char s[3][10] = { "just", "do", "it" };
    使用这种方法,字符串的内容全部存储在二维数组中,占用空间大小为3*10*sizeof(char),这种方法占用空间较多,尤其是字符串长短差异很大时,会有很多空间浪费产生。
  • sizeof(数组名)的值是多少?:数组名虽然是一个指针,但sizeof(数组名)并不是一个指针所占用的字节,而是整个数组占用的字节数。例如:
    char s[10][10];
    int i = sizeof( s );
    i的值为100,而不是32(由具体的指针位数决定)

2. 关于qsort中cmp函数的定义
qsort用于对数组元素进行排序,函数原型为void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));其中compar为比较函数,qsort函数会把要比较的2个数组元素的指针传递给它,并根据其返回值进行排序。由于传递到compar函数的为元素指针,因此其内部实现一般有如下形式:

int compareMyType (const void * a, const void * b)
{
  if ( *(MyType*)a <  *(MyType*)b ) return -1;
  if ( *(MyType*)a == *(MyType*)b ) return 0;
  if ( *(MyType*)a >  *(MyType*)b ) return 1;
}

即首先要把void *类型的指针转换回数组元素的指针类型,然后再进行比较,如整形数组的排序:

int compar( const void *a, const void *b )
{
    return ( *(int*)a - *(int*)b );  //从小到大排序
}
int a[4] = { 3, 1, 4, 5 };
qsort( a, 4, sizeof(int), compar );

又如字符串数组的比较:

int compar( const void *a, const void *b )
{
    //注意,由于比较的是二维数组的元素,因此qsort传递给
    //compar的是数组元素的指针,也就是各一维数组的指针,
    //其类型和二维数组名相同,为指向字符数组的指针,其
    //声明方式为:char (*p)[5],因此类型转换为(char (*)[5]),
    //由于strcmp需要的是字符串的指针,因此需要解引用。
    //强制转换后a的类型为指向字符数组的指针,因此解引用
    //后类型为字符数组的数组名(首地址),也是字符串指针
    //,于是就可以给strcmp使用了。
    return strcmp( *(char (*)[5])a, *(char (*)[5])b );
}
char a[3][5] = { "go", "to", "do" };
qsort( a, 3, 5*sizeof(char), compar );

又如字符指针(字符串的字面值)数组的排序:

int compar( const void *a, const void *b )
{
    //由于数组元素为字符指针类型,传递给
    //compar的是字符指针的指针(地址),
    //因此应先转换回类型char **
    return strcmp( *(char **)a, *(char **)b );
}
char *a[3] = { "go", "to", "do" };
qsort( a, 3, sizeof(char *), compar );

猜你喜欢

转载自www.cnblogs.com/uestcliming666/p/12728742.html