常量指针的意义是什么?

我指的不是指向常量值的指针,而是常量指针本身。

我正在学习 C 和 C + + 的基础知识,直到今天我才意识到指针是通过值传递给函数的,这很有意义。 这意味着在函数内部,我可以让复制的指针指向其他值,而不会影响来自调用者的原始指针。

那么,如果函数头部写着:

void foo(int* const ptr);

在这样一个函数中,你不能让 ptr 指向其他的东西,因为它是 const,你不希望它被修改,但是一个像这样的函数:

void foo(int* ptr);

这工作做得一样好!因为指针无论如何都会被复制,而且即使修改了副本,也不会影响调用方中的指针。那么常量的优势是什么呢?

34832 次浏览

你说得对,对打电话的人来说,这完全没有区别。但是对于函数的作者来说,它可以是一个安全网“好吧,我需要确保我不会把这一点说错”。不是很有用,但也不是没用。

这基本上等同于程序中有一个 int const the_answer = 42

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

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

向函数传递常量指针没有什么意义,因为它总是通过值传递。这只是通用语言设计允许的事情之一。仅仅因为它没有意义就禁止它只会使语言规范变得更糟。更大。

如果你在一个函数中,这当然是另一种情况。拥有一个不能改变其指向内容的指针是一个使代码更加清晰的断言。

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

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

我特别强调了使用 只有 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有需要也不是,但是它的函数比 有需要稍微大一点,这样反复地避免了我的错误。

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

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

与普通常数变量一样,常数指针必须在声明时初始化为一个值,并且不能更改其值。

这意味着常量指针总是指向相同的值。在上述情况下,ipPtr 将始终指向 iVal 的地址。但是,由于所指向的值仍然是非常数的,因此可以通过解除对指针的引用来更改所指向的值:

* ipPtr = 6;//allow,因为 pnPtr 指向一个非常数 int

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

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

即使它不改变功能,添加 const也会在您做一些不想做的事情时产生编译器错误。想象一下以下错误:

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

如果您使用 int* const,这将生成一个编译器错误,因为您正在将值更改为 ptr。一般来说,通过语法添加限制是一件好事。只是不要走得太远——您给出的例子是大多数人都懒得使用 const的情况。

你的问题涉及到一些更普遍的问题: 函数参数应该是常量吗?

值参数(如指针)的常量是一个 实施细节,它的 没有形式是函数声明的一部分。这意味着你的函数总是这样:

void foo(T);

它完全取决于函数的 执行者,无论它是以可变的方式还是以常量的方式使用 function-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放在声明(头部)中,如果不想或不需要修改变量,则将它放在定义(实现)中。

对于任何其他类型(不仅仅是指针)也可以提出同样的问题:

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

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

(模仿)它真的没有意义作为默认值。更合理的默认值是作为不可重新分配的指针(int* const arg)传递。也就是说,我更希望作为参数传递的指针被隐式声明为 const。

那么常量的优势是什么呢?

这样做的好处是,修改参数指向的地址非常容易,有时候还不清楚,这样就可以在不太容易引入 bug 的情况下引入 bug。更改地址是不正常的。如果您的意图是修改地址,那么创建局部变量会更清楚。同样,原始指针操作也是引入 bug 的简单方法。

因此,当你想要修改参数指向的地址时,通过不可变地址并创建一个副本(在非典型情况下)会更清楚:

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

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

在更高的层次上: 如果将参数作为数组进行操作,那么客户机将参数声明为容器/集合通常会更清晰,错误也更少。

通常,将 const 添加到值、参数和地址是一个好主意,因为您并不总是意识到编译器会强制执行的副作用。因此,它和 const 在其他几种情况下一样有用(例如,这个问题类似于“为什么我要声明值 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 永远不会被修改,类似于当前引用的工作方式) ,那么它就非常适用。如果您希望用户拥有完全访问权并修改它,那么您可以使用 non-const。

编辑:

在注意到 okorz001的注释由于 GetArray ()是右值操作数而不能进行赋值之后,他的注释完全正确,但是如果你要返回指针的引用(我假设 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
}

将返回第一个,导致错误:

Error: 赋值只读位置‘ Temp.TestA: : GetArray ()’

但第二种情况将愉快地发生,尽管潜在的后果在下面。

很明显,这个问题会被提出‘为什么要返回对指针的引用’?有些情况下,您需要将内存(或数据)直接分配给有问题的原始指针(例如,构建您自己的 malloc/free 或 new/free 前端) ,但在这些情况下,它是非常量引用。对常量指针的引用,我还没有遇到过这样的情况(除非是声明的常量引用变量而不是返回类型?).

考虑一下我们是否有一个接受 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 中的错误产生如下消息:

Error: 只读参数‘ Data’的递减

这很好,因为我们可能不想这样做,除非我们想引起注释中提到的问题。如果我们编辑掉 const 函数中的递减项,会发生以下情况:

非康斯特: A
康斯特: B

显然,即使 A 是“ Data [1]”,它也被视为“ Data [0]”,因为 NonConst 指针允许减法操作。随着 const 的实现,正如其他人所写的那样,我们在潜在的 bug 发生之前就捕捉到了它。

另一个主要考虑因素是,常量指针可以用作伪引用,因为引用指向的内容不能更改(有人想知道,也许这就是它的实现方式)。考虑一下:

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,这似乎没有什么用处,但是考虑一下,如果您有一个图形应用程序的单一立场,其中您需要一个不可修改的指针来引用它,以便您可以传递它。

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

您的问题实际上更多地是关于为什么将任何变量定义为常量而不仅仅是函数的常量指针参数。这里的规则与将任何变量定义为常量时的规则相同,如果它是函数、成员变量或局部变量的参数。

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

const关键字有很多,它是一个相当复杂的关键字。一般来说,在你的程序中加入大量的常量被认为是很好的编程实践,在网上搜索“常量正确性”,你会发现很多关于这方面的信息。

Const 关键字是所谓的“类型限定符”,其他的是 volatilerestrict。至少易失性与常量遵循相同的(混淆的)规则。


首先,const 关键字有两个用途。最明显的一个是通过使数据(和指针)成为只读来保护数据(和指针)免遭有意或意外的误用。编译器会在编译时发现任何修改常量变量的尝试。

但是,在任何有只读内存的系统中,还有另一个目的,即确保在这种内存中分配某个特定的变量——例如,它可以是 EEPROM 或 flash。这些被称为非易失性存储器,NVM。在 NVM 中分配的变量当然仍将遵循常量变量的所有规则。

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

声明一个常量变量

这可以作为

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

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

第二行被认为是不好的样式的原因,可能是因为“存储类说明符”,如 static 和 extern 也可以声明为 之后的实际类型,int static等。但是对存储类说明符这样做被 C 委员会标记为过时的特性(ISO 9899 N1539草案,6.11.5)。因此,为了保持一致性,也不应该以这种方式编写类型限定符。它没有别的目的,只是为了迷惑读者。

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

const int* ptr = &X;

这意味着不能修改“ X”的内容。这是像这样声明指针的正常方式,主要作为“常量正确性”函数参数的一部分。因为‘ X’实际上不需要声明为 const,它可以是任何变量。换句话说,您总是可以将一个变量“升级”为 const。从技术上讲,C 语言还允许通过显式的类型转换将 const 降级为普通变量,但这样做被认为是糟糕的编程,编译器通常会对此提出警告。

声明一个常量指针

int* const ptr = &X;

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

void func (int*const* ptrptr)

我怀疑很多 C 程序员都不能正确地理解 const 和 * 。我知道 不行,我得和海湾合作委员会核实一下。我认为这就是为什么您很少看到指针到指针的语法,即使它被认为是良好的编程实践。

常量指针还可以用来确保指针变量本身是在只读内存中声明的,例如,您可以声明某种基于指针的查找表并在 NVM 中分配它。

当然,正如其他答案所指出的,常量指针也可以用来强制执行“常量正确性”。

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

const int* const ptr=&X;

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

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

由于这是标记为 C + + 的,我还应该提到,您可以将类的成员函数声明为 const。这意味着函数在被调用时不允许修改类的任何其他成员,这既防止了类的程序员发生意外错误,又告诉成员函数的调用方,他们不会因为调用它而搞砸任何事情。语法是:

void MyClass::func (void) const;

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

一个只读的16位外围芯片寄存器可能看起来像这样:

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

这样你就可以很容易地读取硬件寄存器,而不必使用汇编语言:

input_word = *peripheral;

声明任何变量的类型,如-
(1)声明常量变量。
数据类型常数变量名;

 int const x;
x=4; //you can assign its value only One time
(2)Declare a pointer to a constant variable
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