简体   繁体   English

在qsort函数中使用const

[英]Use of const in qsort function

The following comes from a qsort function implementation given as a solution to one of the K&R challenge questions. 以下内容来自作为解决K&R挑战问题之一的qsort函数实现。 The challenge is to read a list of words and sort them according to the number of occurrences. 面临的挑战是阅读单词列表并根据出现的次数对其进行排序。 The struct Word is also shown. 还显示了结构字。 Link to the full code: http://clc-wiki.net/wiki/K%26R2_solutions:Chapter_6:Exercise_4 链接到完整代码: http : //clc-wiki.net/wiki/K%26R2_solutions : Chapter_6 : Exercise_4

typedef struct WORD
{
  char *Word;
  size_t Count;
  struct WORD *Left;
  struct WORD *Right;
} WORD;

...

CompareCounts(const void *vWord1, const void *vWord2)
{
  int Result = 0;
  WORD * const *Word1 = vWord1;
  WORD * const *Word2 = vWord2;

  assert(NULL != vWord1); 
  assert(NULL != vWord2); 

  /* ensure the result is either 1, 0 or -1 */
  if((*Word1)->Count < (*Word2)->Count)
  {
    Result = 1;
  }
  else if((*Word1)->Count > (*Word2)->Count)
  {
    Result = -1;
  }
   else
  {
    Result = 0;
  }

  return Result;
}

My question is about these lines: 我的问题是关于以下几行:

WORD * const *Word1 = vWord1;
WORD * const *Word2 = vWord2;

Is this a declaration of a constant pointer to a constant variable? 这是对常量变量的常量指针的声明吗? Or something else? 或者是其他东西? And why does it have to be defined this way for the sort to work? 为何必须以这种方式定义排序才能起作用?

The source code you linked to is a small application that reads in a text sample, generates a tree data structure that contains word frequency (how many times each word appears in the text sample), and then prints out the list of words from most frequent to least frequent. 链接到的源代码是一个小型应用程序,该应用程序读取文本样本,生成包含词频(每个词在文本样本中出现多少次)的树状数据结构,然后从最常见的词中打印出词表最少。

/*

  Chapter 6. Structures

          Write a program that prints out the distinct words in its 
          input sorted into decreasing order of frequency of occurrence.
          Precede each word by its count.

  Author: Bryan Williams

*/

The pattern used in this application has a classical and elegant K&R feel about it. 此应用程序中使用的图案具有古典典雅的K&R感觉。 The first step is to process the text sample generating a tree structure in which each node contains a piece of text (a word from the text sample) along with a frequency count of how many times the piece of text is found. 第一步是处理文本样本,以生成树结构,其中每个节点包含一条文本(文本样本中的一个单词)以及找到该文本的次数的频率计数。 The second step is to then sort the tree nodes by the frequency counts. 第二步是根据频率计数对树节点进行排序。 The third step is to print the sorted tree nodes in order of frequency to provide a list of the text pieces found along with how many times the text piece was found in the text sample. 第三步是按频率顺序打印排序的树节点,以提供找到的文本片段的列表以及在文本样本中找到该文本片段的次数。

The tree used is a binary tree and the tree nodes have the following structure: 使用的树是二叉树 ,并且树节点具有以下结构:

typedef struct WORD
{
  char *Word;          // pointer to the text piece, a word of text
  size_t Count;        // frequency count for this word
  struct WORD *Left;   // pointer to tree node child left
  struct WORD *Right;  // pointer to tree node child right
} WORD;

The tree structure is used in order to be efficient about either determining if a text piece has already been found and just incrementing the count or adding the text piece with a count of one to our data storage if it does not. 使用树结构是为了有效地确定是否已找到文本片段,并仅增加计数,或者将没有计数的文本片段添加到我们的数据存储中。

However the sorting step uses a different criteria for the order of items in the tree than for the processing of the text sample. 但是,排序步骤对树中项目的顺序使用不同于对文本样本进行处理的标准。 The text sample uses the text pieces as the way to order the tree nodes but for the actual output we need an order based on the frequency counts. 文本样本使用文本片段作为对树节点进行排序的方式,但对于实际输出,我们需要基于频率计数的排序。 So we need to sort the nodes of the tree on the frequency counts. 因此,我们需要根据频率计数对树的节点进行排序。

Since this is an in memory tree, the first thought for the program would be to create a list of the tree nodes in an array and then sort the list. 由于这是内存中的树,因此程序的第一个想法是在数组中创建树节点的列表,然后对列表进行排序。 However sorting an array usually requires moving array elements around except for the special case of the array already being sorted. 但是,对数组进行排序通常需要移动数组元素,除非已对数组进行排序的特殊情况除外。 This approach would also double the amount of memory used for the tree nodes since a copy of the nodes is being made. 由于正在复制节点,因此该方法还将使用于树节点的内存量增加一倍。

Rather than making a copy of the tree nodes and then sorting that list, instead the program creates a list of pointers to the tree nodes and then sorts the list of pointers by referencing the tree nodes the pointers point to. 该程序没有创建树节点的副本然后对该列表进行排序,而是创建了指向树节点的指针列表,然后通过引用指针所指向的树节点对指针列表进行排序。

A Bit About the qsort() interface 关于qsort()接口的一点点

The function definition of CompareCounts(const void *vWord1, const void *vWord2) means that vWord1 is a pointer to a const variable whose type is unknown or could be anything. CompareCounts(const void *vWord1, const void *vWord2)的函数定义表示vWord1是指向类型未知或可能是任何类型的const变量的指针。

If we look at the qsort() function declaration it looks like: 如果我们看一下qsort()函数声明,它看起来像:

void qsort (void* base, size_t num, size_t size, int (*comparator)(const void*,const void*));

So the comparison function used with qsort() must have a compatible argument list or interface description or a modern C compiler will issue an error. 因此,与qsort()一起使用的比较函数必须具有兼容的参数列表或接口描述,否则现代C编译器将发出错误。

Traditionally with the comparison function used with qsort() as well as bsearch() , we would have a comparison function that would look like: 传统上,与qsort()bsearch()一起使用的比较函数,我们将有一个比较函数,如下所示:

CompareCounts(const void *vWord1, const void *vWord2)

In the comparison function we would then take the void * arguments and cast them to a pointer of the the actual type that is to be used in the comparison. 然后,在比较函数中,我们将采用void *参数并将其强制转换为要在比较中使用的实际类型的指针。 After that we then use the local, properly typed variables to do the comparison operation. 之后,我们然后使用适当类型的局部变量进行比较操作。

What qsort() does is to take two elements of the array that it wants to compare and calls the comparison function with pointers to those two elements. qsort()作用是获取要比较的数组的两个元素,并使用指向这两个元素的指针调用比较函数。 The use of void * is to work around the type checking of the C compiler. 使用void *可以解决C编译器的类型检查。

The interface specified that the void * pointer is pointing to something that is const because qsort() doesn't want you to change the data that it is providing. 接口指定void *指针指向的是const因为qsort()不想让您更改其提供的数据。 It is asking you to test the two data items provided and indicate which is greater or lesser or equal in the collating sequence you are using to sort the data. 它要求您测试所提供的两个数据项,并指出用于排序数据的整理顺序中哪个大于或小于或等于。

The reason for the void * pointer is because the qsort() function does not know what the data is or how the data is structured. 使用void *指针的原因是因为qsort()函数不知道数据是什么或数据的结构。 qsort() only knows the number of bytes, the size, of each data element so that it can iterate through the array of items, element by element. qsort()仅知道每个数据元素的字节数,大小,以便它可以逐元素遍历项目数组。 This allows the array to be any size of struct or other type of data. 这允许数组为任意大小的struct或其他类型的数据。

The Specific Example Comparison Function 具体示例比较功能

The interface, how the arguments were casted, for CompareCounts() looked strange to me until I reviewed the source code you linked to. 在我查看您链接到的源代码之前,对于CompareCounts() ,接口,参数的CompareCounts()对我来说似乎很奇怪。 That program generates a tree structure then generates an array of pointers which point to the actual nodes in the tree. 该程序生成一个树结构,然后生成一个指针数组,这些指针指向树中的实际节点。 It is this array of pointers to nodes that is passed to qsort() to sort. 指向节点的指针数组是传递给qsort()进行排序的。

So the array of data provided to qsort() is an array each element of which points to a tree node which is a WORD element stored in the tree data structure. 因此,提供给qsort()的数据数组是一个数组,每个元素指向一个树节点,该树节点是存储在树数据结构中的WORD元素。 The array of pointers are sorted based on the data the pointers point to. 指针数组根据指针指向的数据进行排序。

In order to access a particular node by using the array passed to the qsort() function we have to take the array element and dereference it to get the actual tree node. 为了使用传递给qsort()函数的数组访问特定的节点,我们必须获取array元素并将其取消引用以获取实际的树节点。

Since qsort() passes a pointer to the array element, the void *vWord1 is a pointer to an array element and we have to dereference that pointer to get the actual array element, a pointer to a tree element. 由于qsort()将指针传递给数组元素,因此void *vWord1是指向数组元素的指针,我们必须取消引用该指针以获得实际的数组元素,即指向树元素的指针。 However it is not the pointer values we want to use as the sorting criteria but rather what the pointers point to. 但是,不是我们要用作排序标准的指针值,而是指针指向的内容。 This requires us to dereference the pointer of the array element in order to access the data of the WORD element in the tree we want to compare. 这要求我们取消引用数组元素的指针,以便访问我们要比较的树中WORD元素的数据。

WORD * const *Word1 = vWord1; does a cast of the void * pointer vWord1 to be a pointer to a const pointer to a WORD . void *指针vWord1转换为指向WORD的const指针的指针。 What this means is that Word1 is a pointer, which qsort() uses to point to the item to be sorted, that is a pointer which is const (the array element itself which qsort() does not want you to change) and the const pointer that Word1 points to, points to a WORD (the pointer to the tree node which is the data that the array contains). 这意味着Word1是一个指针, qsort()用来指向要排序的项目,这是一个指针,它是constqsort()不想更改的数组元素本身)和const Word1指向的指针指向WORD (指向数组包含的数据的树节点的指针)。

What each node of the tree contains is a text word along with a count as to how many times that word is found in a sample of text. 树的每个节点所包含的是一个文本单词以及一个在文本样本中找到该单词的次数的计数。 The qsort() function is being used to sort the nodes of the tree which results from examining the text sample input from most frequent to least frequent. qsort()函数用于对树的节点进行排序,这是通过检查从最频繁到最不频繁的文本样本输入得出的。 The list of node pointers is what is provided to qsort() . 节点指针的列表是提供给qsort()

So the sort is not sorting the array based on the array values but rather sorting based on what the array values, tree node pointers, point to. 因此,排序不是根据数组值对数组进行排序,而是根据数组值(树节点指针)指向的内容进行排序。

By the way When I try a sample compile, I am seeing a warning warning C4090: 'initializing': different 'const' qualifiers with Visual Studio 2015 for the lines: 顺便说一句,当我尝试一个示例编译时,我看到一条警告warning C4090: 'initializing': different 'const' qualifiers Visual Studio 2015中warning C4090: 'initializing': different 'const' qualifiers针对以下行:

WORD * const *Word1 = vWord1;
WORD * const *Word2 = vWord2;

However with the following change the compiler warning goes away: 但是,通过以下更改,编译器警告消失了:

const WORD * const * Word1 = vWord1;
const WORD * const * Word2 = vWord2;

This change is actually in line with what qsort() is asking, that none of the data should be changed whether the pointer from the array element or the data that the pointer from the array element points to. 此更改实际上与qsort()的要求一致,无论数组元素的指针还是数组元素的指针指向的数据都不应更改任何数据。

Anyway, the expression (*Word1)->Count is taking the pointer to the array element provided by qsort() , dereferencing it to get the pointer to the tree node, and then dereferencing the pointer to the tree node in order to get the member of the WORD struct we want to sort against. 无论如何,表达式(*Word1)->Count会使用指向qsort()提供的数组元素的指针,将其解引用以获取指向树节点的指针,然后将其解引用为指向树节点的指针以获取指针。要排序的WORD结构的成员。 The sort uses the frequency count of the words, stored in Count as the sorting criteria. 排序使用单词的频率计数,存储在Count作为排序标准。

Addendum topic: Playing with const 附录主题:使用const

A question raised is that with this kind of complex definition, const WORD * const * Word1 = vWord1; 提出的问题是,使用这种复杂的定义, const WORD * const * Word1 = vWord1; what are various ways to generate compiler errors by breaking const and seeing when using const can provide an additional safeguard against inadvertently modifying something that should not be modified. 通过破坏const并查看何时使用const可以生成编译器错误的各种方式有哪些,可以提供额外的保护措施,以防止意外修改不应修改的内容。

If we use this definition without the const modifier we would have WORD * * Word1 = vWord1; 如果使用不带const修饰符的此定义,则将具有WORD * * Word1 = vWord1; which means that we have a pointer variable Word1 that has a meaning of something like: 这意味着我们有一个指针变量Word1 ,其含义类似于:

Word1 -> ptr -> WORD Word1-> ptr-> WORD

where Word1 is our pointer variable which points to some unknown pointer which in turn points to a variable of type WORD . 其中Word1是我们的指针变量,它指向某个未知的指针,该指针又指向WORD类型的变量。

Lets look at several different variations of definitions. 让我们看一下定义的几种不同变体。

WORD * * Worda = vWord1;         // no const
WORD * * const Wordb = vWord1;   // Wordb is const pointer which points to a non-const pointer which points to a non-const WORD
WORD * const * Wordc = vWord1;   // WordC is a non-const pointer which points to a const pointer which points to a non-const WORD
const WORD * const * Wordd = vWord1;  // Wordd is a non-const pointer which points to a const pointer which points to a const WORD
const WORD * const * const Worde = vWord1;  // Worde is a const pointer which points to a const pointer which points to a const WORD

In the source code of the question with a definition of WORD * const * Word1 = vWord1; 在问题的源代码中,定义为WORD * const * Word1 = vWord1; then Word1 is a non-const pointer which points to a const pointer which points to a non-const WORD . 那么Word1是一个非常量指针,它指向一个指向非常量WORD的常量指针。 So lets look at several different kinds of assignments: 因此,让我们看一下几种不同的分配:

Word1 = vWord2;   // replace the value of the pointer Word1, allowed since non-const
(*Word1)++;       // Error: increment value of pointer pointed to by Word1, not allowed since pointer pointed to is const so compiler error
(*Word1)->Count++;  // increment value of variable pointed to by the pointer pointed to by Word1, allowed since non-const

Is this a declaration of a constant pointer to a constant variable? 这是对常量变量的常量指针的声明吗?

No, it's just a pointer to a constant variable, not a constant pointer. 不,它只是指向常量变量的指针, 而不是常量指针。

To make it clear, you are sorting an array of WORD* , for this specific problem, this WORD* pointer is a whole: 为了清楚起见,您正在对WORD*数组进行排序,对于此特定问题,此WORD*指针是一个整体:

typedef WORD *wordptr;

Then the following declaration is more clear: 那么下面的声明就更清楚了:

wordptr const *Word1 = vWord1;

And why does it have to be defined this way for the sort to work? 为何必须以这种方式定义排序才能起作用?

This is to make sure that a comparator won't modify the content of the pointer. 这是为了确保比较器不会修改指针的内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM