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

Why do we need (char **) to cast strings in comparator function?

提问人:obanby 提问时间:7/8/2023 最后编辑:Vlad from Moscowobanby 更新时间:7/8/2023 访问量:94

问:

上下文

我正在尝试学习 C,并遇到了一个用于对字符串数组进行排序的示例。qsort

问题:

我正在努力理解以下内容:

  1. 为什么这两个 return 语句不同?
  2. 为什么我们需要将 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);
}
c 排序 铸造 比较 qsort

评论

0赞 new Q Open Wid 7/8/2023
这回答了你的问题吗?C语言中的qsort:比较字符串
2赞 John Bollinger 7/8/2023
接收的比较函数作为其参数,指向要比较的项的指针,转换为类型。如果项的类型为 ,则指向它们的指针的类型为 。问题中提出的函数对于该用途是错误的,但如果使用注释掉的语句而不是实际使用的语句,那就没问题了。这些并不等同。qsort()void *char *char **CompareWords()return
0赞 obanby 7/8/2023
@JohnBollinger谢谢!那么,是否有效地比较了指针而不是实际的字符串?如果是这样的话,dereferencing 应该是 的确切地址吗?strcmp(c1, c2);*str1c1

答:

0赞 2 revschux - Reinstate Monica #1

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

不需要演员表本身。
演员表的作用是失去 -ness。
可以在没有强制转换的情况下制作类似的代码。
const


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 语句不同?

它们服务于不同的目标。使用哪种方法取决于使用方式。可能是正确的,因为传递给的 compare 函数期望对象的地址进行比较。qsort()return strcmp(*str1, *str2)qsort()

5赞 Eric Postpischil 7/8/2023 #2

例程不知道您正在排序什么。它接受要排序为数组的事物,您可以为其提供每个元素中的字节数和元素数。qsort

由于知道每个元素中的字节数,因此可以计算每个元素的地址。但它不知道每个元素是什么类型。因此,当它希望您的例程比较两个元素时,它会使用 type 将两个元素的地址传递给该例程。是实际类型的替代品,并且意味着那些地址上的数据不打算更改,只是比较。qsortconst void *voidconst

您的例程确实知道元素的类型。数组包含指向字符的指针。具体来说,字符的类型为 ,指向它们的指针是 。所以传递 u 的地址其实是 a 的地址,即:。除了向你传递一个指向数据的指针,因此该数据是 ,而指向它的指针是 。请注意,这是指向常量指针的指针,而不是指向常量指针的指针。charchar *qsortchar *char * *qsortconstchar * constchar * const *char * const *charchar

回顾:

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

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

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

当您以这种方式正确使用时,您不需要石膏。编译器将允许在初始化中隐式转换,因为允许从对象类型隐式转换为其他指针,但不允许隐式删除。移除需要石膏。但是,使用强制转换手段可能会被意外删除,因此应避免使用强制转换,最好在初始化中使用隐式转换,而不是使用强制转换。constvoid *constconstconst

除了不更改指向 的指针之外,此比较例程也不会更改 ,因此我们可以为它们添加,作为有助于避免错误的安全功能:charcharconst

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

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

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 *)。

用于排序的数组是指向 的指针数组,并将比较例程指针传递给这些指针。它本身不会传递指针。qsortcharqsort

(它无法传递指针本身,因为它不知道数组中的元素是什么类型。它不知道它们是指针,并且在 C 中无法传递您不知道其类型的对象的值。传递对象的值需要从内存中获取对象的字节并解释它们,并且在不知道它们的类型的情况下无法解释它们。

1赞 Vlad from Moscow 7/8/2023 #3

为什么这两个 return 语句不同?为什么我们需要铸造 指向 (char **) 而不是 (char *) 的 void 指针。

您可以在比较函数中使用任一 return 语句。正确的选择取决于排序的内容。

函数 qsort 的声明器如下所示

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

第一个函数参数是传递数组的第一个元素的地址。第二个参数指定传递的数组中的元素数。第三个参数说明传递数组的元素的大小。最后,第四个参数指定比较函数。

该函数将指向排序数组元素的指针作为 void 指针传递给比较函数,这些指针用于比较比较函数中指针指向的数组的原始元素。qsort

例如,如果你有一个整数数组,例如

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

然后,用于比较数组的一对元素的函数(例如第一个和第二个元素)将以下表达式和 .因此,您可以通过将指针强制转换为类型并取消引用获得的指针来比较比较函数中的元素。qsort( const void * )&a[0]( const void * )&a[1]const int *

Npw 让我们考虑一个指向字符串文字的指针数组。

它可能看起来像

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 类型。char [6]&s1[0]( * )[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

所以从逻辑上讲,cpmparison 函数的调用可以想象如下

( 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]