当我应该而且不应该在C中使用“const”时感到困惑

时间:2009-04-02 17:14:22

标签: c pointers const

我的字典是这样的:

typedef struct dictNode {
    int key;
    char *value;
    struct dictNode *next;
} Dict;

一个 get()函数,如下所示:

char *get(const Dict *dict, int key) {
    if(!dict) return NULL;

    Dict *currPtr = dict;

    while(currPtr) {
        if(currPtr->key == key) {
            return currPtr->value;
        }

        currPtr = currPtr->next;
    }
}

编译此代码会产生以下错误:
dict.c:85:警告:初始化会丢弃指针目标类型的限定符

此警告涉及以下行:

Dict *currPtr = dict;

如果我在该行之前添加“const”,如下所示:

const Dict *currPtr = dict;

警告消失了......

1)首先我不明白的是:我在 get()中将添加到 dict参数中编译器警告我,如果我尝试更改dict指向的地址,否则我将无法访问主程序中的字典,因为我丢失了我指向的地址。现在,我正在创建一个新指针 currPtr ,指向与 dict 相同的位置。这样,我使用这个指针来遍历字典并保持 dict 指针不变。为什么我还需要为 currPtr 设置 const

2)我不明白的第二件事是:行 currPtr = currPtr-> next; 正在改变 currPtr 指针,所以,如果我将 const 添加到 currPtr ,为什么编译不会警告我?

然后我有一个 del()函数,如下所示:

Dict *del(const Dict *dict, int key) {
    if(!dict) return NULL;

    Dict *currPtr = dict;
    Dict *prevPtr = dict;

    while(currPtr) {
        if(currPtr->key == key) {
            prevPtr->next = currPtr->next;
            free(currPtr);
        }

        prevPtr = currPtr;
        currPtr = currPtr->next;
    }

    return dict;
}

请注意我知道这个删除功能代码不完整,如果我想删除第一个元素,它就无法正常工作。没关系,我稍后会完成,这足以证明我的问题......

3)因此,在 get()函数中,我需要将 const 添加到 currPtr ,但是这个 del()函数,我不需要将 const 添加到 currPtr ,也不需要 prevPtr ?在这两个函数中,我正在更改 currPtr 指针,在 del()函数的情况下,我也正在更改 prevPtr 指针。为什么 get()函数要求我在 currPtr del()函数之前添加 const 不要求我在 currPtr prevPtr 之前添加 const

我基本上可以将整个帖子恢复为:我的 get() del()函数的确切时间和地点应该使用 const 以及为什么,以及何时何地我不应该?

11 个答案:

答案 0 :(得分:13)

没有指针,你会有

const Dict currPtr

这是一个常数Dict。现在,如果你使它成为指针

const Dict *currPtr

是指向常量Dict的指针。这并不意味着指针是常量。但它确实意味着Dict指向被视为常数

currPtr->key = 10; // error, dict is treated as constant. 

但指针不是

currPtr = otherPtr; // possible: the pointer is not constant

如果使指针保持不变,第二种情况会出错。保持指向的dict也不变,这看起来像这样

const Dict * const currPtr = init;

现在你不能将currPtr设置为指向不同的东西,因为指针现在是常量,而不仅仅是指针所指向的那样。有些人喜欢看看const是否总是正确的东西,它使const。这看起来像这样

Dict const * const currPtr = init;

与上一个代码段相同。如果你从右到左阅读它,它会告诉你它是什么“const指向const Dict的指针”。如果您有类型,则无论您如何订购说明符

int const a = 10;
const int b = 10;

两者都是常数整数。这就是为什么我们可以把const放在Dict类型说明符的右边。

现在,如果你有一个指针,你总是可以假装你指向一个常量对象,即使该对象没有被声明为const。但是,如果您指向的是const对象,则不能假装使用非const对象:

int const *p = NULL;
// doesn't work without a cast. Will at least provoke a warning
int *pn = p;

int *p = NULL;
// always works: pretending to point to something const doesn't harm.
int const *pc = p;

请注意,如果将指针本身设为const,则规则与此不同。它们类似于应用于其他类型的const:

int const i = 0;
int j = i; // works. we only read the value of i. its const doesn't matter. 

int * const p = NULL;
int * q = p; // works: we only read the value of p (a null pointer).

将值复制到新变量(无论是否为指针)后,新变量不会以任何方式连接到另一个变量,因为读取的值与首先创建值的方式没有关联。另一个变量的const无关紧要。

答案 1 :(得分:7)

“我在get()中的dict参数中添加了”const“,以便编译器在我尝试更改地址时警告我dict指向”

在这种情况下,您的意思是Dict *const dict,而不是const Dict *dict。您已将结构声明为const,而不是指向它的指针。

“为什么我还需要const为currPtr”

因为否则你可以使用currPtr来修改字典结构本身,它应该是这个函数中的const。

“行currPtr = currPtr-> next;正在改变currPtr指针,所以,如果我向currPtr添加一个const”

同样的原因:如果const位于您放置的位置,则不是错误。如果const在currPtr的另一个地方就可以了。

否则我将无法访问主程序中的字典,因为我丢失了我指向的地址

否 - 当主程序调用get时,它会将指针值传递给例程,这将成为get函数中dict的值。但是get有自己的指针变量,与主程序中的变量分开。即使它们恰好都被称为“dict”,改变一个指针也不会改变另一个指针。

两个变量都指向同一个结构,因此如果您使用该变量来修改结构中的任何字段,那么结果当然会影响两个代码位。通常,名为“get”的函数是只读操作,因此您创建参数const Dict *是正确的。但我认为这是正确的,与你的想法略有不同。这是为了保护字典内容不被改变,而不是保护你的地址记录。

然而,对于像这样的链接列表,const-safety略显尴尬。即使你有一个const Dict,它的“next”字段仍然不指向const。因此,尽管您无法修改第一个节点,但const-system不会保护您不会修改列表中的其他节点。 C ++通过函数重载来解决这个问题,所以你可以有一个“getnext”函数,如果输入是const,则返回const输出,如果输入是非const,则返回非const输出。您可以使用“getnext”和“getnext_c”在C中实现类似的功能,后者具有const参数并返回。但大多数人都不打扰,包括标准库函数,如strchr,它在C中采用const char *但返回非const char *。因此,strchr可以在C中意外使用,将const字符串转换为非const字符串,而不会发出任何警告。或故意,在这种情况下,它有点像通过“合法”的业务洗钱; - )

del()函数不要求我在currPtr和prevPtr之前添加一个const?

我承认我很难过。我期待与“获取”相同的警告 - 你确定不存在吗?但del的参数无论如何都不应该是const,因为你可能会修改字段。

答案 2 :(得分:3)

const Dict * dict表示这是一个指向常量Dict值的指针

你可以修改指针,但不能修改值;

答案 3 :(得分:2)

我认为你的主要困惑来自误解

的含义
const char* p;

这定义了一个指向常量字符的指针。 p中的地址可以更改,但值指针不能。编译器禁止的是更改* p,而不是p。

答案 4 :(得分:2)

向后读取指针声明可能会有所帮助:

const Dict *dict

装置

'dict'是指向Dict的指针,它是常量。

您可以移动它指向的地址,但它指向的结构被视为常量。

答案 5 :(得分:1)

const是一种程序员文档。它告诉自己,您不会尝试修改const限定传递的任何内容。

您的get函数应该const Dict *del函数不应该currPtrDict是助行器 - 它不会更改getconst对象的逻辑状态,因此它也应该del合格。但是,在const中,它会更改结构 - 此处没有{{1}}。

答案 6 :(得分:1)

Ad 1& 2:原型中的const适用于Dict,这意味着

char *get(const Dict *dict, int key)

读取...函数,其中一个(非常量)参数类型指针指向常量Dict ...,这意味着您可以更改dict,但不能更改。 dict->next

如果你想获得你可能想要的效果,请将其声明为

char *get(Dict * const dict, int key)

规则是:const适用于它左边的单词,除非它是类型名称中的第一个,然后它适用于下一个单词。

此外,更改dict不会更改主程序中的指针,因为指针是按值传递的。

当分配给currDict指针时,编译器检查类型是否兼容,在这种情况下,这意味着您只能添加顶级const限定符,而不是像您一样删除

你应该在哪里使用const:

get()函数中,你应该在所有Dict上使用const(就像你一样),因为你可能不想在那里更改字典。

del()函数中,我不会使用任何const,因为要在那里更改数据。一个很好的签名是:

char *get(Dict ** dict, int key)

这样你就可以改变调用函数的指针来删除第一个元素。

答案 7 :(得分:1)

请注意,const可能会出现在指针声明中*的任意一侧或两侧,并且具有语义意义......

const char * foo = bar;

不同
char * const foo = bar;

第一个说内容const,第二个说指针const

对于最大常数,有:

const char * const foo = bar;

答案 8 :(得分:1)

编译器正在跟踪什么是const和什么不是const。一开始,把它们弄好是很棘手的。

关于你的第一点,你传递的是const Dict *dict,这意味着你承诺你不会通过dict修改任何内容。然后将其分配给Dict * currptr。由于currptr不是const,您可以通过*currptr更改内容。编译器必须在某些时候警告你,即使它是const,你也试图修改某些东西,而正确的位置就是当你将const指针指向非const指针时。

对于你的第二点,你错过了const的确切内容。如果您有const Dict * currptr,则表示currptr指向const Dict,这意味着您可以更改currptr但不能更改*currptr。您可以使用currptr生成const Dict * const currptr,或使其成为带const Dick * const currptr的const值的const指针。

我实际上并不理解你的第三点。您应该在发布它时从del()获得编译器警告,因为您将指向const值的指针指定给指向值的指针。您需要prevptr非const,因为您正在使用它来修改值。函数签名应该是Dict *del(Dict *dict, int key),因为您承诺不会修改dict所指向的任何内容,并且无论如何都要这样做。

答案 9 :(得分:0)

我得到了人们所说的大部分内容,我现在没有得到的,是这个......

考虑以下 list()功能:

void list(Dict *dict) {
    if(!dict) return;

    while(dict) {
        printf("KEY: %d\n", dict->key);
        printf("VALUE: %s\n", dict->value);

        dict = dict->next;
    }
}

(使用这一个而不是get()一个更简单)

在我的主要内容中,我有两个对此函数的调用,如:

list(dict);
list(dict);

当然, dict 插入了一些条目......

根据我的理解,只有第一个列表()应输出内容,因为列表()功能中的 dict指针正在更改并最终将指向 NULL (否则我们将在list()调用中处于无限while循环中)。发生的事情是第二个list()调用也输出相同的字典,当我认为它不应该输出任何东西因为dict应该是NULL,不是吗?

答案 10 :(得分:0)

“假人傻瓜指南”:

尽可能使用它。