常量指针有什么意义?

时间:2011-10-10 15:48:02

标签: c++ c pointers const

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

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

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

void foo(int* const ptr);

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

void foo(int* ptr);

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

17 个答案:

答案 0 :(得分:199)

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

  

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

即使它没有更改功能,添加const会在您执行您不想做的事情时生成编译器错误。想象一下以下错字:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

如果您使用int* const,则会产生编译器错误,因为您将值更改为ptr。通过语法添加限制通常是件好事。只是不要把它放得太远 - 你给出的例子是大多数人不使用const的情况。

答案 1 :(得分:75)

我指出只使用 const参数,因为这会启用更多编译器检查:如果我不小心在函数内部重新分配了一个参数值,编译器会咬我。

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

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

示例:

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

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

答案 2 :(得分:20)

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

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

void foo(T);

完全取决于函数的实现者是否要以可变或持续的方式使用functions-scope参数变量:

// 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()) { /* ... */ }
}

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

答案 3 :(得分:16)

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

答案 4 :(得分: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;

这两种形式完全等效。后一种风格被认为是不好的风格,不应该使用。

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

声明指向常量变量的指针。

const int* ptr = &X;

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

声明一个常量指针

int* const ptr = &X;

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

void func (int*const* ptrptr)

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

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

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

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

const int* const ptr=&X;

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

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

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

void MyClass::func (void) const;

答案 5 :(得分:13)

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

它与您的程序中int const the_answer = 42基本相同。

答案 6 :(得分:8)

  

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

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

  

那么const的优点是什么?

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

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

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

补充说本地实际上是免费的,它可以减少出错的几率,同时提高可读性。

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

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

答案 7 :(得分:6)

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

只读16位外设芯片寄存器可能类似于:

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

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

input_word = *peripheral;

答案 8 :(得分:5)

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

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

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

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

答案 9 :(得分: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";
}

答案 10 :(得分:5)

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

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

答案 11 :(得分:4)

将const指针传递给函数没有多大意义,因为无论如何它都将通过值传递。这只是通用语言设计所允许的事情之一。禁止它只是因为它没有意义只会使语言规范。大。

如果你在一个功能中,那当然是另一种情况。有一个指针无法改变它所指向的是一个使代码更清晰的断言。

答案 12 :(得分:4)

我想一个优点是编译器可以在函数内执行更积极的优化,因为知道这个指针不能改变。

它也避免了例如。将此指针传递给接受非常量指针引用的子函数(因此可以像void f(int *&p)那样更改指针),但我同意,在这种情况下,实用性有限。

答案 13 :(得分:4)

因此可以证明const指针高度适用的示例。考虑到你有一个带有动态数组的类,并且你想要将用户访问权限传递给数组,但是没有授予他们更改指针的权限。考虑:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

产生:

  

输入数据
    放数据

但如果我们尝试这个:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

我们得到:

  

错误:作为左操作数分配所需的左值// Drat再次被挫败!

很明显我们可以修改数组的内容,但不能修改数组的指针。如果要在将指针传递回用户时确保指针具有一致状态,那就很好。但是有一个问题:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

我们仍然可以删除指针的内存引用,即使我们无法修改指针本身。

因此,如果您希望内存引用始终指向某些内容(IE永远不会被修改,类似于引用当前的工作方式),那么它非常适用。如果您希望用户具有完全访问权限并对其进行修改,那么非const适合您。

修改

注意到由于GetArray()是一个右值操作数而无法分配的okorz001注释,他的注释完全正确,但如果你要返回对指针的引用(我想我是假设GetArray引用了一个引用,例如:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

将在第一次返回时导致错误:

  

错误:分配只读位置'Temp.TestA :: GetArray()'

但是,尽管对底层产生了潜在的后果,但第二次会很快发生。

显然,问题会被提出'你为什么要返回对指针的引用'?在极少数情况下,您需要将内存(或数据)直接分配给相关的原始指针(例如,构建您自己的malloc / free或new / free前端),但在这些情况下,它是非const引用。对const指针的引用我没有遇到过保证它的情况(除非可能是声明的const引用变量而不是返回类型?)。

考虑一下我们是否有一个带有const指针的函数(而不是一个指针):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it's relative to Data's position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

const中的错误会产生消息:

  

错误:递减只读参数'数据'

这很好,因为我们可能不想这样做,除非我们想要引起评论中指出的问题。如果我们编辑const函数中的减量,则会发生以下情况:

  

NonConst:A
  Const:B

显然,即使A是'Data [1]',它也被视为'Data [0]',因为NonConst指针允许递减操作。随着const的实现,正如另一个人写的那样,我们会在它发生之前捕获潜在的bug。

另一个主要的考虑因素是,const指针可以用作伪引用,因为引用所指向的东西不能被改变(一个奇迹,如果可能这是它的实现方式)。考虑:

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

尝试编译时,会产生以下错误:

  

错误:分配只读变量'B'

如果想要持续引用A,这可能是一件坏事。如果B = NULL已被注释掉,编译器将很乐意让我们修改*B,因此修改A.这对于int可能看起来不太有用,但考虑一下您是否只需要一个图形应用程序的单一姿态不可修改的指针,指的是你可以传递它。

它的用法是可变的(原谅意外的双关语),但正确使用,它是框中的另一个工具,可以帮助编程。

答案 14 :(得分:3)

声明任何变量的类型,如 -
    (1)声明一个常数变量          DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2)声明一个指向常量变量的指针 const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified

答案 15 :(得分:3)

我相信这会阻止代码在函数体内递增或递减指针。

答案 16 :(得分:3)

指针没有什么特别之处,你永远不会想要它们是const。正如您可以拥有类成员常量int值一样,您也可以出于类似的原因使用常量指针:您希望确保没有人改变指向的内容。 C ++引用在某种程度上解决了这一问题,但指针行为继承自C。