首页 文章

常量指针有什么意义?

提问于
浏览
142

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

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

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

void foo(int* const ptr);

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

void foo(int* ptr);

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

17 回答

  • 4

    指针没有什么特别之处,你永远不会想要它们是const . 就像你可以让类成员常量 int 值一样,你也可以出于类似的原因使用常量指针:你想确保没有人改变指向的内容 . C引用有点解决这个问题,但指针行为是从C继承的 .

  • 3

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

  • 8

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

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

    即使它没有改变功能,添加 const 会产生编译器错误,而're doing things you didn' t意味着要做 . 想象一下以下拼写错误:

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

    如果使用 int* const ,则会生成编译器错误,因为您将值更改为 ptr . 通过语法添加限制通常是件好事 . 只是不要't take it too far -- the example you gave is a case where most people don'打扰使用 const .

  • 3

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

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

    请注意,这仅在函数定义中有意义 . 它不属于声明,这是用户看到的 . 并且用户不关心我是否在函数内使用 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 . 既不是必要的,但功能甚至略大,这反复使我免于犯错误 .

  • 5

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

    值参数的常量(如指针)是 implementation detail ,它确实是 not 的一部分,是函数声明的一部分 . 这意味着您的功能始终如下:

    void foo(T);
    

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

    // 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 放在声明( Headers )中,如果您不想或不需要修改变量,请将其放在定义(实现)中 .

  • 75

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

  • 4

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

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


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

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

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

    Declare a constant variable.

    这可以作为

    const int X=1; or
    int const X=1;
    

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

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

    Declare a pointer to a constant variable.

    const int* ptr = &X;
    

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

    Declare a constant pointer

    int* const ptr = &X;
    

    这意味着指针本身是不变的 . 您可以修改它指向的内容,但不能修改指针本身 . 这没有's address changed while passed as parameter to a function. You' ll必须写一些不太可读的东西:

    void func (int*const* ptrptr)
    

    我怀疑很多C程序员可以在那里得到const和* . 我知道我可以't - I had to check with GCC. I think that'为什么你很少见到指针到指针的语法,尽管它被认为是很好的编程习惯 .

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

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

    Declare a constant pointer to constant data

    const int* const ptr=&X;
    

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

    Declare a read-only member function (C++)

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

    void MyClass::func (void) const;
    
  • 16

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

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

  • 3

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

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

    那么const的优势是什么?

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

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

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

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

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

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

  • 194

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

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

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

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

    input_word = *peripheral;

  • 14

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

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

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

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

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

    /* 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";
    }
    
  • 13

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

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

  • 4

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

    如果你在一个函数内部,那当然是另一种情况 . 有一个指针不能改变它所指向的是一个断言,使代码更清晰 .

  • 5

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

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

  • 6

    因此可以证明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适合您 .

    Edit:

    在注意到由于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 ,因此对于整数来说这似乎没什么用 . 但是考虑一下你是否有一个图形应用程序的单一姿态你想要一个不可修改的指针,它指的是你可以传球

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

  • 5

    Types of declaring any variables like-
    (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

相关问题