为什么我们需要(char**)在comparator函数中转换字符串?

m0rkklqb  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(107)

Context

我正在学习C,遇到了一个使用qsort对字符串数组进行排序的例子。

常见问题:

我正在努力理解以下内容:
1.为什么这两个return语句不同?
1.为什么我们需要将void指针转换为(char**)而不是(char *)。

int CompareWords(const void *a, const void *b) {
    char **str1 = (char **)(a);
    char **str2 = (char **)(b);
    char *c1 = (char *)a;
    char *c2 = (char *)b;
   return strcmp(c1, c2);
//    return strcmp(*str1, *str2);
}

字符串

xqkwcwgp

xqkwcwgp1#

qsort例程不知道您正在排序什么。它接受要排序的东西作为一个数组,您可以为其提供每个元素中的字节数和元素数。
由于qsort知道每个元素的字节数,因此它可以计算每个元素的地址。但它不知道每个元素是什么类型。因此,当它希望您的例程比较两个元素时,它会使用类型const void *将两个元素的地址传递给该例程。void是实际类型的替代品,const意味着那些地址上的数据不打算被更改,只是比较。
你的程序知道元素的类型。数组包含指向字符的指针。具体来说,字符的类型为char,指向它们的指针为char *。因此,qsort传递给您的地址实际上是char *的地址,即:char * *。除了qsort传递给你一个指向const数据的指针,所以数据是char * const,指向它的指针是char * const *。请注意,char * const *是指向char的常量指针的指针,而不是指向常量char的指针。
回顾一下:

  • 数组中的每个元素都是char *
  • qsort传递给你数组中一个元素的地址,所以它传递给你一个char * *
  • 除了qsort添加了一个const,所以它传递给你一个char * const *
  • 但是qsort不知道你的元素类型,所以它将char *部分更改为void,并传递给你一个void const *,它与const void *相同。
  • 您必须将其转换为char * const *

您可以通过以下方式将qsort传递的地址转换为实际类型:

char * const *str1 = a;
char * const *str2 = b;

字符串
当您以这种方式正确使用const时,您不需要强制转换。编译器将允许在初始化中隐式转换,因为允许从void *隐式转换为指向对象类型的其他指针,但不允许隐式删除const。删除const需要一个铸件。但是使用强制转换意味着const可能会被意外删除,因此应该避免这种情况在初始化中使用隐式转换而不是强制转换。
除了不改变指向char的指针之外,这个比较例程也不会改变char,所以我们可以为它们添加const,作为一个安全功能,有助于避免错误:

const char * const *str1 = a;
const char * const *str2 = b;


接下来,我们得到了指向指针的指针,但我们需要使用的是后者。我们可以使用*str1*str2来获取str1str2指向的指针:

const char *c1 = *str1;
const char *c2 = *str2;


现在c1c2指向要比较的实际字符。
然后我们可以与return strcmp(c1, c2);进行比较,这使得整个例程:

#include <string.h>

int CompareWords(const void *a, const void *b)
{
    const char * const *str1 = a;
    const char * const *str2 = b;
    const char *c1 = *str1;
    const char *c2 = *str2;
    return strcmp(c1, c2);
}


写出c1c2的用法主要是为了说明。我们也可以将例程写成:

int CompareWords(const void *a, const void *b)
{
    const char * const *str1 = a;
    const char * const *str2 = b;
    return strcmp(*str1, *str2);
}


为什么这两个return语句不同?
问题中出现的return strcmp(c1, c2);是错误的,因为这些c1c2只是从传递的地址转换而来的值。它们是我们需要的指针的地址,而不是我们需要的指针。
为什么我们需要将void指针转换为(char**)而不是(char *)。
qsort排序的数组是指向char的指针数组,qsort将比较例程指针传递给这些指针。它不传递指针本身。
(It无法传递指针本身,因为它不知道数组中的元素是什么类型。它不知道它们是指针,在C中没有办法传递一个你不知道类型的对象的值。传递对象的值需要从内存中获取对象的字节并解释它们,如果不知道它们的类型,就无法解释它们。)

ujv3wf0j

ujv3wf0j2#

为什么这两个return语句不同?为什么我们需要将空指针转换为(char**)而不是(char *)。
您可以在比较函数中使用其中一个return语句。正确的选择取决于排序的内容。
函数qsort的declaratopn如下所示

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

字符串
第一个函数参数是所传递数组的第一个元素的地址。第二个参数指定传递的数组中的元素数。第三个参数说明符表示传递的数组的元素大小。最后,第四个参数规定了比较函数。
函数qsort将指向排序数组的元素的指针作为空指针传递给比较函数,所述空指针用于比较由比较函数内的指针所指向的数组的原始元素。
例如,如果您有一个整型数组,例如

int a[5] = { 5, 3, 4, 2, 1 };


则用于比较阵列的一对元素(例如第一和第二元素)的函数qsort将下面的表达式( const void * )&a[0]( const void * )&a[1]传递给比较函数。因此,您可以通过将指针强制转换为const int *类型并取消引用所获得的指针来比较比较函数中的元素。
让我们考虑一个指向字符串的指针数组。
它看起来像

char * s1[] = { "one", "two", "three", "four", "five" };


也就是说,数组的每个元素都具有char *类型。因此,函数qsort将指向数组元素(例如&s1[0]&s1[1])的指针传递给比较函数。这些指针的类型为char **
所以事实上你有

const void *a = &s1[0];
const void *b = &s1[1];


要获取排序数组的元素(即指针),您需要编写

char **str1 = (char **)(a);
char **str2 = (char **)(b);
return strcmp(*str1, *str2);


也就是说,你首先需要得到char **类型的原始指针,然后去引用这些指针,以得到char *类型的数组的元素的原始类型。
现在让我们考虑另一个数组。我们将声明一个二维数组,而不是指向字符串的指针,如

char s2[][6] = { "one", "two", "three", "four", "five" };


同样,函数qsort将指向其元素的指针传递给比较函数。
但现在数组元素的类型是char [6]。例如,在表达式&s1[0]中,指向这样的元素的指针依次具有类型char ( * )[6]
整个数组的地址值等于其第一个元素的地址值。
您可以使用以下代码片段来检查

char s2[][6] = { "one", "two", "three", "four", "five" };

 printf( "&s2[0]    = %p\n", ( void * )&s2[0] );
 printf( "&s2[0][0] = %p\n", ( void * )&s2[0][0] );


此代码段的输出可能如下所示

&s2[0]    = 0x7ffcbf1712d0
&s2[0][0] = 0x7ffcbf1712d0


因此,逻辑上可以按以下方式来调用cpmpresome函数

( const void * )a = &s2[0];
( const void * )b = &s2[1];

const char *c1 = a;
const char *c2 = b;


指针&s2[0]&s2[1]与指针c1c2具有相同的值。这些值是二维数组中所传递的子数组的第一个字符的地址。
这是一个演示程序。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int CompareWords1(const void *a, const void *b) {
    char **str1 = (char **)(a);
    char **str2 = (char **)(b);
    return strcmp(*str1, *str2);
}

int CompareWords2(const void *a, const void *b) {
    const char *c1 = ( const char * )a;
    const char *c2 = ( const char * )b;
   return strcmp(c1, c2);
}

int main( void ) 
{
    const char * s1[] = { "one", "two", "three", "four", "five" };
    const size_t N1 = sizeof( s1 ) / sizeof( *s1 );

    for ( size_t i = 0; i < N1; i++ )
    {
        printf( "\"%s\" ", s1[i] );
    } 
    putchar( '\n' );
    
    qsort( s1, N1, sizeof( *s1 ), CompareWords1 );

    for ( size_t i = 0; i < N1; i++ )
    {
        printf( "\"%s\" ", s1[i] );
    } 
    putchar( '\n' );
    
    putchar ( '\n' );

    char s2[][6] = { "one", "two", "three", "four", "five" };
    const size_t N2 = sizeof( s2 ) / sizeof( *s2 );

    for ( size_t i = 0; i < N2; i++ )
    {
        printf( "\"%s\" ", s2[i] );
    } 
    putchar( '\n' );
    
    qsort( s2, N2, sizeof( *s2 ), CompareWords2 );

        for ( size_t i = 0; i < N1; i++ )
    {
        printf( "\"%s\" ", s1[i] );
    } 
    putchar( '\n' );
}


程序输出为

"one" "two" "three" "four" "five" 
"five" "four" "one" "three" "two" 

"one" "two" "three" "four" "five" 
"five" "four" "one" "three" "two"


当然,您可以按以下方式编写函数CompareWords2

int CompareWords2(const void *a, const void *b) {
    const char ( *c1 )[6] = ( const char ( * )[6] )a;
    const char ( *c2 )[6] = ( const char ( * )[6] )b;
   return strcmp(*c1, *c2);
}


但是,当您需要排序另一个二维数组元素的其他型别(而非char[6]型别)时,使用函式并不是个好主意,因为当程式码的读者看到排序数组元素的实际型别不同时,这种函式只会让他们感到困惑。

hwamh0ep

hwamh0ep3#

为什么我们需要(char**)在comparator函数中转换字符串?
造型本身是不需要的。
演员表的作用是失去const-ness。
Like代码可以不进行强制转换。

int CompareWords(const void *a, const void *b) {
  const char *const*str1 = a;
  const char *const*str2 = b;
  const char *c1 = a;
  const char *c2 = b;
  return strcmp(c1, c2);
  // or
  return strcmp(*str1, *str2);
}

字符串
为什么这两个return语句不同?
它们服务于不同的目标。正确的使用取决于qsort()的使用方式。return strcmp(*str1, *str2)可能是正确的,因为传递给qsort()的比较函数期望比较对象的地址。

相关问题