我的字典是这样的:
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()中将
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 以及为什么,以及何时何地我不应该?
答案 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
函数不应该currPtr
。 Dict
是助行器 - 它不会更改get
中const
对象的逻辑状态,因此它也应该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)
“假人傻瓜指南”:
尽可能使用它。