常量指针有什么意义?

R. *_*iz. 145 c c++ pointers const

我不是在讨论指向const值的指针,而是指向const指针本身.

我正在学习C和C++,超越了基本的东西,直到今天我才意识到指针是通过值传递给函数的,这是有道理的.这意味着在函数内部,我可以使复制的指针指向其他值,而不会影响来自调用者的原始指针.

那么有一个函数头是什么意思:

void foo(int* const ptr);
Run Code Online (Sandbox Code Playgroud)

在这样的函数里面你不能让ptr指向别的东西因为它是const并且你不希望它被修改,但是这样的函数:

void foo(int* ptr);
Run Code Online (Sandbox Code Playgroud)

工作也一样好!因为无论如何都会复制指针,即使您修改了副本,调用者中的指针也不会受到影响.那么const的优势是什么?

ten*_*our 201

const 是一个你应该用来追求一个非常重要的C++概念的工具:

通过让编译器强制执行您的意思,在编译时而不是运行时查找错误.

即使它没有改变功能,const当你做你不想做的事情时,添加会产生编译器错误.想象一下以下拼写错误:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}
Run Code Online (Sandbox Code Playgroud)

如果使用int* const,这将生成编译器错误,因为您要将值更改为ptr.通过语法添加限制通常是件好事.只是不要太过分 - 你给出的例子就是大多数人都不打扰使用的情况const.

  • "帮助编译器帮助你"是我通常会为此吟唱的口头禅. (25认同)
  • 谢谢,这是一个让我信服的答案.你把const放在编译器警告你自己的赋值错误.您的示例非常适合说明此概念,因为这是指针的常见错误.比你! (8认同)
  • 所以这就是你给"为什么让变量变成私有的,当你不能在课外使用它们时". (3认同)

Kon*_*lph 75

我强调 使用const参数,因为这会启用更多的编译器检查:如果我不小心在函数内部重新赋值参数值,编译器会咬我.

我很少重用变量,创建新变量来保存新值更简洁,所以基本上我的所有变量声明都是const(除了某些情况,例如循环变量const会阻止代码工作).

请注意,这仅在函数定义中有意义.它不属于声明,这是用户看到的.并且用户不关心我是否const在函数内部使用参数.

例:

// foo.h
int frob(int x);
Run Code Online (Sandbox Code Playgroud)
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}
Run Code Online (Sandbox Code Playgroud)

注意参数和局部变量都是如何const.既不是必要的,但功能甚至略大,这反复使我免于犯错误.

  • `+ 1`来自另一个`const`-痴迷的狂热分子.但是,我更喜欢我的编译器对我咆哮.我犯了太多错误,并且会受到严重损害. (17认同)
  • +1,我强烈推荐"`const`,除非有一个很好的理由"策略.但有一些很好的例外,例如[复制和交换](http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) (2认同)
  • 如果我正在设计一种新语言,默认情况下声明的对象将是只读的("const").您需要一些特殊的语法,可能是一个"var"关键字,以使对象可写. (2认同)
  • 我是从答案错误的角度争论的。可惜事实并非如此。现在我已经深入研究了标准 - 很高兴知道我错了。很酷的东西,除非您知道自己在寻找什么,否则很容易错过。唉,不幸的是,它暴露了另一个蠕虫:对于复制起来并不便宜的值,当您从 `const T &` 切换到 `T` 时,实现将会泄漏,而这些绝对与 ABI 不兼容:( (2认同)

Ker*_* SB 20

你的问题涉及更一般的事情:函数参数应该是const吗?

值参数(如您的指针)常量性是一个实现细节,它并没有形成函数声明的一部分.这意味着您的功能始终如下:

void foo(T);
Run Code Online (Sandbox Code Playgroud)

完全取决于函数的实现者是否要以可变或常量方式使用函数范围的参数变量:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

因此,const如果您不想要或不需要修改变量,请遵循简单规则从不放入声明(标题),并将其放在定义(实现)中.

  • 我同意这一点 - 但我对使声明和定义不同的想法感到有点不舒服.对于除`const`以外的东西,声明和定义的(原型部分)通常是相同的. (5认同)

Dav*_*eas 16

顶级const限定符在声明中被丢弃,因此问题中的声明声明完全相同的函数.另一方面,在定义(实现)中,编译器将验证如果将指针标记为const,则不会在函数体内修改它.


cni*_*tar 14

你是对的,对于来电者来说它完全没有区别.但对于该功能的作者来说,它可以是一个安全网"好吧,我需要确保我不会指出错误的事情".不是很有用但也没用.

它与int const the_answer = 42你的程序中的基本相同.


Lun*_*din 14

const关键字有很多,它是一个相当复杂的关键字.通常,在程序中添加大量的const被认为是很好的编程习惯,在网上搜索"const correctness",你会发现很多关于它的信息.

const关键字是所谓的"类型限定符",其他是volatilerestrict.至少volatile遵循与const相同(混乱)的规则.


首先,const关键字有两个目的.最明显的一个是通过将数据(和指针)设置为只读来保护数据(和指针)免于故意或意外滥用.编译器在编译时会发现任何修改const变量的尝试.

但是在任何具有只读存储器的系统中还有另一个目的,即确保在这样的存储器内分配某个变量 - 例如它可以是EEPROM或闪存.这些被称为非易失性存储器NVM.在NVM中分配的变量当然仍然遵循const变量的所有规则.

有几种不同的方法可以使用const关键字:

声明一个常量变量.

这可以作为

const int X=1; or
int const X=1;
Run Code Online (Sandbox Code Playgroud)

这两种形式完全相同.后一种风格被认为是不好的风格,不应该使用.

第二行被认为是坏样式的原因可能是因为"存储类说明符"(例如static和extern)也可以在实际类型之后声明int static等等.但是对存储类说明符这样做被标记为过时的特性由C委员会(ISO 9899 N1539草案,6.11.5).因此,为了保持一致性,不应该以这种方式编写类型限定符.它无其他目的,但无论如何都会使读者感到困惑.

声明一个指向常量变量的指针.

const int* ptr = &X;
Run Code Online (Sandbox Code Playgroud)

这意味着无法修改"X"的内容.这是声明像这样的指针的正常方式,主要作为"const正确性"的函数参数的一部分.因为'X'实际上不必声明为const,所以它可以是任何变量.换句话说,您始终可以将变量"升级"为const.从技术上讲,C还允许通过显式类型转换将const从const降级为普通变量,但这样做被认为是错误的编程,编译器通常会对其进行警告.

声明一个常量指针

int* const ptr = &X;
Run Code Online (Sandbox Code Playgroud)

这意味着指针本身是不变的.您可以修改它指向的内容,但不能修改指针本身.这没有太多的用途,有一些,比如确保指针指向(指针指针)在作为参数传递给函数时没有改变它的地址.你必须写一些不太可读的东西:

void func (int*const* ptrptr)
Run Code Online (Sandbox Code Playgroud)

我怀疑很多C程序员可以在那里得到const和*.我知道不能 - 我不得不与GCC核实.我认为这就是为什么你很少见到指针指针的语法,尽管它被认为是很好的编程实践.

常量指针也可用于确保指针变量本身在只读内存中声明,例如,您可能希望声明某种基于指针的查找表并将其分配到NVM中.

当然,正如其他答案所示,常量指针也可用于强制执行"const正确性".

声明一个指向常量数据的常量指针

const int* const ptr=&X;
Run Code Online (Sandbox Code Playgroud)

这是上面描述的两种指针类型的组合,它们的所有属性都是.

声明只读成员函数(C++)

由于这是标记的C++,我还应该提到您可以将类的成员函数声明为const.这意味着在调用该函数时,不允许该函数修改该类的任何其他成员,这既阻止了该类程序员的意外错误,又通知成员函数的调用者他们不会搞乱任何东西.通过调用它.语法是:

void MyClass::func (void) const;
Run Code Online (Sandbox Code Playgroud)


jus*_*tin 8

...今天我意识到指针是通过值传递给函数的,这是有道理的.

(imo)它作为默认值确实没有意义.更明智的默认值是作为不可重新分配的指针(int* const arg)传递.也就是说,我更倾向于将作为参数传递的指针隐式声明为const.

那么const的优势是什么?

优点是,当您修改参数所指向的地址时,它很容易并且有时不清楚,这样当您不容易修改时就可以引入错误.改变地址是非典型的.如果您的意图是修改地址,那么创建局部变量会更清楚.同样,原始指针操作是一种引入错误的简单方法.

因此,当您想要更改参数指向的地址时,传递不可变地址并创建副本(在那些非典型情况下)更清晰:

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}
Run Code Online (Sandbox Code Playgroud)

添加本地实际上是免费的,它减少了错误的机会,同时提高了可读性.

在更高级别:如果您将参数作为数组进行操作,则通常更清晰,并且更不容易让客户端将参数声明为容器/集合.

通常,将const添加到值,参数和地址是一个好主意,因为您并不总是意识到编译器乐于执行的副作用.因此,它与const在其他几个案例中一样有用(例如,问题类似于'我为什么要声明值const?').幸运的是,我们也有引用,不能重新分配.

  • 这是+1的默认值.像大多数语言一样,C++有错误的方法.它不应该有一个`const`关键字,它应该有一个`mutable`关键字(好吧,它有,但有错误的语义). (4认同)
  • 将const作为默认值的有趣想法.感谢您的回答! (2认同)

Dan*_*nes 6

如果您使用内存映射设备进行嵌入式系统或设备驱动程序编程,则通常使用两种形式的"const",一种用于防止指针被重新分配(因为它指向固定的硬件地址.),如果是外设注册它指向的是只读硬件寄存器然后另一个const将在编译时而不是运行时检测到许多错误.

只读16位外设芯片寄存器可能如下所示:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

然后,您可以轻松读取硬件寄存器,而无需使用汇编语言:

input_word = *peripheral;


小智 5

int iVal = 10; int*const ipPtr =&iVal;

就像普通的const变量一样,const指针必须在声明时初始化为一个值,并且它的值不能改变.

这意味着const指针始终指向相同的值.在上面的例子中,ipPtr将始终指向iVal的地址.但是,因为指向的值仍然是非const,所以可以通过解除引用指针来更改指向的值:

*ipPtr = 6; //允许,因为pnPtr指向非const int


pmg*_*pmg 5

可以询问任何其他类型(不仅仅是指针)的相同问题:

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}
Run Code Online (Sandbox Code Playgroud)


zar*_*zar 5

您的问题更多的是为什么将任何变量定义为const而不仅仅是函数的const指针参数.这里适用的规则与将任何变量定义为常量,如果它是函数或成员变量或局部变量的参数一样.

在您的特定情况下,在功能上它与许多其他情况一样,当您将局部变量声明为const但它确实设置了一个限制,您无法修改此变量.