繁体   English   中英

在qsort函数中使用const

[英]Use of const in qsort function

以下内容来自作为解决K&R挑战问题之一的qsort函数实现。 面临的挑战是阅读单词列表并根据出现的次数对其进行排序。 还显示了结构字。 链接到完整代码: 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;
}

我的问题是关于以下几行:

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

这是对常量变量的常量指针的声明吗? 或者是其他东西? 为何必须以这种方式定义排序才能起作用?

链接到的源代码是一个小型应用程序,该应用程序读取文本样本,生成包含词频(每个词在文本样本中出现多少次)的树状数据结构,然后从最常见的词中打印出词表最少。

/*

  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

*/

此应用程序中使用的图案具有古典典雅的K&R感觉。 第一步是处理文本样本,以生成树结构,其中每个节点包含一条文本(文本样本中的一个单词)以及找到该文本的次数的频率计数。 第二步是根据频率计数对树节点进行排序。 第三步是按频率顺序打印排序的树节点,以提供找到的文本片段的列表以及在文本样本中找到该文本片段的次数。

使用的树是二叉树 ,并且树节点具有以下结构:

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;

使用树结构是为了有效地确定是否已找到文本片段,并仅增加计数,或者将没有计数的文本片段添加到我们的数据存储中。

但是,排序步骤对树中项目的顺序使用不同于对文本样本进行处理的标准。 文本样本使用文本片段作为对树节点进行排序的方式,但对于实际输出,我们需要基于频率计数的排序。 因此,我们需要根据频率计数对树的节点进行排序。

由于这是内存中的树,因此程序的第一个想法是在数组中创建树节点的列表,然后对列表进行排序。 但是,对数组进行排序通常需要移动数组元素,除非已对数组进行排序的特殊情况除外。 由于正在复制节点,因此该方法还将使用于树节点的内存量增加一倍。

该程序没有创建树节点的副本然后对该列表进行排序,而是创建了指向树节点的指针列表,然后通过引用指针所指向的树节点对指针列表进行排序。

关于qsort()接口的一点点

CompareCounts(const void *vWord1, const void *vWord2)的函数定义表示vWord1是指向类型未知或可能是任何类型的const变量的指针。

如果我们看一下qsort()函数声明,它看起来像:

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

因此,与qsort()一起使用的比较函数必须具有兼容的参数列表或接口描述,否则现代C编译器将发出错误。

传统上,与qsort()bsearch()一起使用的比较函数,我们将有一个比较函数,如下所示:

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

然后,在比较函数中,我们将采用void *参数并将其强制转换为要在比较中使用的实际类型的指针。 之后,我们然后使用适当类型的局部变量进行比较操作。

qsort()作用是获取要比较的数组的两个元素,并使用指向这两个元素的指针调用比较函数。 使用void *可以解决C编译器的类型检查。

接口指定void *指针指向的是const因为qsort()不想让您更改其提供的数据。 它要求您测试所提供的两个数据项,并指出用于排序数据的整理顺序中哪个大于或小于或等于。

使用void *指针的原因是因为qsort()函数不知道数据是什么或数据的结构。 qsort()仅知道每个数据元素的字节数,大小,以便它可以逐元素遍历项目数组。 这允许数组为任意大小的struct或其他类型的数据。

具体示例比较功能

在我查看您链接到的源代码之前,对于CompareCounts() ,接口,参数的CompareCounts()对我来说似乎很奇怪。 该程序生成一个树结构,然后生成一个指针数组,这些指针指向树中的实际节点。 指向节点的指针数组是传递给qsort()进行排序的。

因此,提供给qsort()的数据数组是一个数组,每个元素指向一个树节点,该树节点是存储在树数据结构中的WORD元素。 指针数组根据指针指向的数据进行排序。

为了使用传递给qsort()函数的数组访问特定的节点,我们必须获取array元素并将其取消引用以获取实际的树节点。

由于qsort()将指针传递给数组元素,因此void *vWord1是指向数组元素的指针,我们必须取消引用该指针以获得实际的数组元素,即指向树元素的指针。 但是,不是我们要用作排序标准的指针值,而是指针指向的内容。 这要求我们取消引用数组元素的指针,以便访问我们要比较的树中WORD元素的数据。

WORD * const *Word1 = vWord1; void *指针vWord1转换为指向WORD的const指针的指针。 这意味着Word1是一个指针, qsort()用来指向要排序的项目,这是一个指针,它是constqsort()不想更改的数组元素本身)和const Word1指向的指针指向WORD (指向数组包含的数据的树节点的指针)。

树的每个节点所包含的是一个文本单词以及一个在文本样本中找到该单词的次数的计数。 qsort()函数用于对树的节点进行排序,这是通过检查从最频繁到最不频繁的文本样本输入得出的。 节点指针的列表是提供给qsort()

因此,排序不是根据数组值对数组进行排序,而是根据数组值(树节点指针)指向的内容进行排序。

顺便说一句,当我尝试一个示例编译时,我看到一条警告warning C4090: 'initializing': different 'const' qualifiers Visual Studio 2015中warning C4090: 'initializing': different 'const' qualifiers针对以下行:

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

但是,通过以下更改,编译器警告消失了:

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

此更改实际上与qsort()的要求一致,无论数组元素的指针还是数组元素的指针指向的数据都不应更改任何数据。

无论如何,表达式(*Word1)->Count会使用指向qsort()提供的数组元素的指针,将其解引用以获取指向树节点的指针,然后将其解引用为指向树节点的指针以获取指针。要排序的WORD结构的成员。 排序使用单词的频率计数,存储在Count作为排序标准。

附录主题:使用const

提出的问题是,使用这种复杂的定义, const WORD * const * Word1 = vWord1; 通过破坏const并查看何时使用const可以生成编译器错误的各种方式有哪些,可以提供额外的保护措施,以防止意外修改不应修改的内容。

如果使用不带const修饰符的此定义,则将具有WORD * * Word1 = vWord1; 这意味着我们有一个指针变量Word1 ,其含义类似于:

Word1-> ptr-> WORD

其中Word1是我们的指针变量,它指向某个未知的指针,该指针又指向WORD类型的变量。

让我们看一下定义的几种不同变体。

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

在问题的源代码中,定义为WORD * const * Word1 = vWord1; 那么Word1是一个非常量指针,它指向一个指向非常量WORD的常量指针。 因此,让我们看一下几种不同的分配:

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

这是对常量变量的常量指针的声明吗?

不,它只是指向常量变量的指针, 而不是常量指针。

为了清楚起见,您正在对WORD*数组进行排序,对于此特定问题,此WORD*指针是一个整体:

typedef WORD *wordptr;

那么下面的声明就更清楚了:

wordptr const *Word1 = vWord1;

为何必须以这种方式定义排序才能起作用?

这是为了确保比较器不会修改指针的内容。

暂无
暂无

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

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