【问题标题】:How is this const being used?这个 const 是如何使用的?
【发布时间】:2020-10-15 04:05:08
【问题描述】:

我正在研究 Herbert Schildt 的“C 完整参考”,并被指针 * 与 const 解释同时使用的“const”解释卡住了。 这是他使用的代码:

#include <stdio.h>

void dash(const char *str);

int main()
{
    dash("this is a test");
    return 0;
}

void dash(const char *str)
{
    while (*str)
    {
        if (*str == ' ')
        {
            printf("%c", '-');
        }
        else
        {
            printf("%c", *str);
        }
        str++;
    }
}

我试图搜索指针 * 并得到了一些关于地址的答案,但他为什么在这个例子中使用它?他的书没有解释这一点,我也没有找到其他类似使用指针 * 的例子。 另一个问题是,如果没有条件,为什么循环“while (*str)”是正确的?

【问题讨论】:

  • 条件只不过是一个表达式。任何表达式都是有效条件。 0 为假,其他一切为真。
  • 正如@klutt 所说,'0' 或任何可以评估为'0' 的东西都是假的,其他的都是真的。 所以,while(*str) 是真的,直到str++ 达到字符串的NULL 值。

标签: c constants


【解决方案1】:

这是一种保证指针指向的内容不会被改变的方式。这也是一种无需显式强制转换即可抑制警告的方法。

考虑一下:

void dash(char *str) // Removed const
{
    // Code
}

int main() {
    const char p[] = "this is a test";
    dash(p);
}

现在编译器会发出这个:

k.c: In function ‘main’:
k.c:23:10: warning: passing argument 1 of ‘dash’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
   23 |     dash(p);
      |          ^
k.c:4:17: note: expected ‘char *’ but argument is of type ‘const char *’
    4 | void dash(char *str)
      |           ~~~~~~^~~

由于您没有写信给它,因此无需担心此警告。但最好避免警告。在这种情况下,我们有两种选择。该函数可以修改字符串,也可以不修改。如果它无法修改它,那么就没有理由向编译器和读者解释确实如此。

旁注。字符串文字,如@9​​87654324@,如果你修改它们会有未定义的行为,所以程序可能会崩溃(或不崩溃)。但是,它们的类型是 (char*) 类型,没有 const。原因是向后兼容。在 C++ 中,它们的类型是 const char*

请注意,const 是约定俗成的承诺,而不是编译器的承诺。此代码将修改原始字符串并编译时不会出现警告:

#include <stdio.h>

void foo(const char *str)
{
    // Casting comes with great responsibility
    // You're just saying to the compiler
    // "Trust me and shut up"
    char *ptr = (char*) str;
    ptr[2]='A';
    ptr[3]='T';
}

int main()
{
    const char p[] = "this is a test";
    foo(p);
    puts(p);
}

输出:

$ ./a.out 
thAT is a test

正如我所说,上面的代码将在没有警告的情况下编译。如果你移除演员表,你会得到这个:

k.c:5:17: warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
    5 |     char *ptr = str;
      |                 ^~~

请注意,由于p 被声明为const,这是未定义的行为。但是,您改为这样写main

int main()
{
    char p[] = "this is a test";
    foo(p);
    puts(p);
}

那么,程序是完全有效的。即使您将可写字符串传递给函数foo,您也希望它不会改变,因为foo 将常量指针作为参数。但是如你所见,这样的事情是可以绕过的。

小心使用空指针

请注意,这对于任何类型 T 都是完全有效的:

T x;
T *p;
p = (void*) &x;

这是因为您可以安全地将指针转换为 void 并返回。但是,这在一般情况下无效:

T x;
Q *p;
p = (void*) &x;

但是,由于演员阵容,您不会收到警告。但是这段代码调用了未定义的行为。

道德课

强制转换不是警告的 g​​oto 解决方案。相反,你应该仔细考虑你的演员是否符合你的意图。如果您的意图是摆脱警告,正确的解决方案是删除参数的const。如果您打算添加演员表是“我知道这个函数承诺不会修改参数,但我有充分的理由承诺并立即打破该承诺”,那么演员表是正确的。

现实世界的例子

只是举一个真实世界的例子来说明它是如何出错的。我在this question 中看到了这个:

void * func_return();
void (*break_ptr)(void) = (void *)func_return;

我告诉 OP 演员阵容是错误的。我得到的回应是,如果没有演员表,编译器就会抱怨。好吧,它抱怨是因为指针错误。函数原型声明了一个函数,该函数采用未指定数量的参数并返回一个 void 指针。函数指针是一个指向不带任何参数的函数的指针。所以在这种情况下,正确的指针声明和初始化应该是这样的:

void * func_return();
void *(*break_ptr)() = func_return;

但这可能会更好:

void * func_return(void);
void *(*break_ptr)(void) = func_return;

请注意,任何类型的指针都可以安全地转换为void* 并返回。但在这种情况下,OP 并没有将其退回,而是转换为另一种类型。如果 OP 做得正确,演员表会很混乱,但在这种情况下,它确实隐藏了 REAL 错误。

【讨论】:

    【解决方案2】:

    * 与指针有关,但它有两个用途。

    在声明中,*用于声明指针类型,如:

    const char *str;
    

    其中str 是指向const char 的指针(或按顺序存储的多个const char,C 不关心区别)。

    在表达式中,* 用于解引用指针,获取它指向的值。如:

    printf("%c", *str);
    

    其中*str 是指针str 所指向的const char 本身。

    与指针有关,还有&amp; 则相反。它获取您存储在内存中的任何值的指针。

    这里const 的重要性与指针无关,它与您将字符串文字传递给dash() 的事实有关。与存储在堆或堆栈中的字符串不同,字符串字面量不能修改,由于其不变性,应将其视为const

    【讨论】:

    【解决方案3】:

    很多人刚开始学C的时候很迷茫

    const char *ptr
    

    它是一个引用 const char 的指针。可以修改指针。但是您是否尝试写入编译器会抱怨的引用对象:https://godbolt.org/z/d9znF-

    例子:

    const char c;
    const char *ptr = &c;
    
    *ptr = 'p';  // -- illegal - the compiler will complain
    ptr++;       // -- legal 
    

    声明指向非常量对象的常量指针:

    char * const ptr;
    

    现在 ptr 无法更改,但引用的对象可以:https://godbolt.org/z/h7WWex

    char c;
    char * const ptr = &c;
    
    *ptr = 'p';  // -- legal
    ptr++;       // -- illegal - the compiler will complain
    

    声明指向 const 对象的 const 指针

     const char * const ptr;
    

    现在不能修改指针和引用的对象:https://godbolt.org/z/x2xBcZ

    const char c;
    const char * const ptr = &c;
    
    *ptr = 'p';  // -- illegal - the compiler will complain
    ptr++;       // -- illegal - the compiler will complain
    

    【讨论】:

    • const char *ptr = &amp;c; ... ptr++; 指向变量的指针的指针运算?合法吗?
    • @DavidRanieri 是的。可以形成指针“通过”但不取消引用它。
    • @P__J__ 它仅用于说明目的是的,我不是吹毛求疵,只是问
    • @DavidRanieri 前一段有“对于这些运算符的目的,指向不是数组元素的对象的指针与指向长度数组的第一个元素的指针相同一种以对象的类型作为其元素类型。” C17dr § 6.5.6 7
    • 如果指针空间是[0...P_MAX],那么char c; 不能有P_MAX 的地址,因为过去 规则——最后一个内存字节丢失给C 使用.如果在N &gt; 1ptr + N &gt; ptr 需要保持为真的情况下允许ptr += N,则可用空间会变少。 C 选择在 1 处止损。
    【解决方案4】:

    在 c 中,我们可以像指针一样操作数组,像他使用的那样用右指针算术运算,我们可以像数组一样操作它!

    const char *str
    

    是指向 const char 或 const char 数据类型数组的指针!

    在函数中,所有参数都是按值传递的(数​​组也不例外)。当您在函数中传递数组时,它“衰减为指针”。当您将数组与其他东西进行比较时,它再次“衰减为指针”

    所以我们可以用不同的方式再次编写while循环:

    void dash(const char *str)
    {
        int i = 0;
        while (str[i])
        {
            if (str[i] == ' ')
            {
                printf("%c", '-');
            }
            else
            {
                printf("%c", str[i]);
            }
            ++i;
        }
    }
    

    现在,第一种语法(使用指针 deref 运算符 * 比数组语法更有效)。

    一般数组名或数组第一个元素的地址(任何类型),都可以衰减为相同数据类型的指针!

    在他的实现中,他将str 表现为const char pointer,在while 循环中他取消引用指针(如str[i],带括号),在最后一行(str++)他是移动指针指向下一个 char 元素(通常称为 pointer arithmetics)。

    【讨论】:

      【解决方案5】:

      参数声明中的const char *str 表示函数不会尝试修改str 指针指向的值。这意味着您可以使用常量字符串调用该函数。如果声明中没有const,则说明函数可能会修改字符串,所以只能用可写字符串调用。

      例如,像strcpy() 这样的函数声明在第二个参数(源字符串)上有const,但在第一个参数(目标)上没有。它可以(并且通常会)修改目标,但不能修改源。

      【讨论】:

      • If you don't have const in the declaration, it means that the function might modify the string, so you can only call it with writable strings. 您可以传递任何字符串,但如果您尝试修改它并且字符串不可写,则它是一个 UB。没有 const(有时是限制)可能会阻止某些代码优化。
      • @P__J__ 我想我在那里考虑的是 C++,它对 const 正确性的限制更大。
      【解决方案6】:

      在这种情况下,从右到左阅读定义:

      const char *str // str is a pointer to a const char
      

      str的地址可以改变,char指向的地址不能改变。

      为回答您的其他问题,while (*str) 将继续互动直到*str == '\0''\0' 用于标记 C 中字符串的结尾。

      如果您不确定,该程序的作用是打印它,将' ' 替换为'-'。在您的示例中,将打印 "this-is-a-test"。注意:字符串"this is a test"没有被修改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-24
        • 2015-05-09
        • 2017-09-23
        • 1970-01-01
        相关资源
        最近更新 更多