C++:常量正确性和指针参数

24

我知道可以有几种方式声明const指针:

const int * intPtr1; // Declares a pointer that cannot be changed.
int * const intPtr2; // Declares a pointer whose contents cannot be changed.

// EDIT: THE ABOVE CLAIMS ARE INCORRECT, PLEASE READ THE ANSWERS.

但是将同样的原则应用于函数参数有什么影响呢?

我认为以下代码是多余的:

void someFunc1(const int * arg);
void someFunc2(int * arg);

因为 someFunc1 和 someFunc2 对指针本身采用值传递,所以在函数调用中,someFunc1 无法更改原始指针的值。 举个例子:

int i = 5;
int * iPtr = &i;

someFunc1(iPtr); // The value of iPtr is copied in and thus cannot be changed by someFunc1.

如果这些条件成立,那么用'const int * ptr'类型的参数声明函数将没有任何意义,对吗?


15
你的声明方式有误。"const int *" 是一个可变指针,指向一个不可变的整数。"int * const" 是一个不可变指针,指向一个可变的整数。 - Alan Stokes
4
编辑了这个问题,以防止搜索结果匆忙的谷歌用户获取错误信息。我保留了错误,以便答案仍然具有该上下文。 - Ben
5个回答

39

你弄反了:

const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.

下面的const确实是不必要的,而且没有理由将其放在函数声明中:

void someFunc1(int * const arg);

然而,您可能希望将其放在函数实现中,原因与声明本地变量(或其他任何东西)const相同 - 当您知道某些内容不会更改时,实现可能更容易遵循。无论函数的任何其他声明是否声明为const,您都可以这样做。


现在我看起来像个傻瓜...感谢简明扼要的解释,并确认其中一个定义中确实存在不必要的const! - Ben
const int * intPtr1int const * intPtr1 之间有什么区别吗? - dynamic
10
完全没有区别。const 修饰其前面的内容,除非它在开头,此时它修饰第一项。因此,在这两种情况下,它都是修饰 int 而不是指针。如果写成 int * const 则会修饰指针。 - Mike Seymour
1
@MikeSeymour:哇,这么清晰明了,我想知道是否有人问过这个问题?(我发现你的评论可以解决这个问题。) - Kindred

28

这并不是给调用者准备的,而是为了someFunc1内部的代码。这样,someFunc1内部的任何代码都不会意外更改它,例如:

void someFunc1(int *arg) {
  int i = 9;
  arg = &i;  // here is the issue
  int j = *arg;
}

让我们做一些案例研究:

1)只需将指定的值声明为const

void someFunc1(const int * arg) {
int i = 9;
*arg = i; // <- compiler error as pointed value is const
}

2) 仅将指针声明为const

void someFunc1(int * const arg) {
int i = 9;
arg = &i; // <- compiler error as pointer is const
}

3) 如果所涉及的变量可以是 const,那么正确使用 const 的方法是什么:

void someFunc1(const int * const arg) {
    int i = 9;
    *arg = i; // <- compiler error as pointed value is const
    arg = &i; // <- compiler error as pointer is const
}

这应该能够消除所有的疑虑。所以我已经提到它是针对函数代码而不是调用者的,并且你应该使用我上面提到的三种情况中最严格的一种。

编辑:

  • 即使在函数声明中,声明const也是一个好习惯。这不仅可以增加可读性,还可以让调用者知道协议并更有信心地保证参数的不可变性。(这是必需的,因为通常会共享头文件,所以调用者可能没有您的实现c/cpp文件)
  • 即使编译器能够更好地指出如果声明和定义同步。

但这并不能解释为什么你要写 void someFunc1(const int * arg);。请注意,将 someFunc1 预声明为 void someFunc1(int * arg); 仍然允许你定义它为 void someFunc1(int * const arg) { ... }(如果你想的话),因为参数变量本身的 const 特性不会影响实际函数签名。 - ruakh
@ruakh 请查看更新后的回答。那应该可以回答您的疑虑了。 - havexz
抱歉,您更新的答案仍未回答问题。(请参见上面Mike Seymour的答案,以获取正确答案。) - ruakh
1
然而,出于同样的原因,您可能希望将其放在函数实现中,就像您可能想将局部变量(或其他任何内容)声明为const一样。我已经提到它是针对函数代码而不是调用者的...无论如何,重点是清晰的,这是底线... - havexz
我想我只是对那些真正回答问题的答案有一种奇怪的偏好! - ruakh
@ruakh :) 嗯,我和你的口味一样。从问题来看,这个人似乎知道 const,但想知道它在函数上下文中的作用。所以这就是答案的全部内容。无论如何,我编辑了一些内容以反映更多信息。 - havexz

7
你的逻辑是反过来的。应该从后往前读类型,所以const int *是指向const int的指针,int * const是指向intconst指针。
例子:
void foo() {
    int a = 0;
    int b = 0;

    int * const ptrA = &a;
    *ptrA = 1;
    ptrA = &b; ///< Error

    const int * ptrB = &a;
    *ptrB = 1; ///< Error
    ptrB = &b;

    const int * const ptrC = &a;
    *ptrC = 1; ///< Error
    ptrC = &a; ///< Error
}

为了详细说明为什么您希望将函数参数设置为const int *,您可能希望提示调用者必须传入一个int,因为作为函数的您需要更改该值。例如,请考虑以下代码:
void someFunc1(const int * arg) {
    // Can't change *arg in here
}

void someFunc2(int * arg) {
    *arg = 5;
}

void foo() {
    int a = 0;
    someFunc1(&a);
    someFunc2(&a);

    const int b = 0;
    someFunc1(&b);
    someFunc2(&b); ///< *** Error here. Must pass in an int not a const int.
}

2

是的,你说得没错(忽略了你把它们弄反了的事实)- 没有必要使用非引用 const 参数。此外,返回非引用 const 值也没有意义。


非引用const返回值甚至是有害的。 - Xeo
@Xeo:您能详细说明一下吗?我不知道它们有任何影响! - ruakh
@ruakh:移动语义之一。T const&&虽然您知道它很快就会被销毁,但无法从中移动。 - Xeo
@JamesMcNellis:谢谢!有趣的是,从那里的链接可以看出,Herb Sutter在使用按值返回时采取了与DeadMG和Xeo相反的立场:他认为对于非内置返回类型,你应该使用const - ruakh
1
@ruakh: 那篇文章是十多年前写的,所以当然在移动语义确立之前就已经存在了。 - James McNellis
显示剩余2条评论

2

你的理解有误:

const int * intPtr1; // Declares a pointer whose contents cannot be changed.
int * const intPtr2; // Declares a pointer that cannot be changed.

一般来说,在编写表达式时,如果稍微改变一下写法,就更容易理解constness: const int*int const * 是相同类型。在这种符号表示法中,规则更加清晰,const 总是应用于其前面的类型,因此:

int const * intPtr1; // Declares a pointer to const int.
int * const intPtr2; // Declares a const pointer to int.
int const * * const * complexPtr; // A pointer to const pointer to pointer to const int

当类型前面带有const时,const被处理为在第一个类型之后写入,因此const T*变成了T const *

void someFunc2(int * arg);

因此不是多余的,因为someFunc2可能会更改arg的内容,而someFunc1则不会。但void someFunc3(int * const arg);将是多余的(且含糊不清)。

常见技巧:从右到左阅读任何类C声明:int const * intPtr1 --> intPtr1 是一个指向常量(只读)整数的指针。 - gkhaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接