C ++中“ const”有多少种用途?


129

作为C ++的新手程序员,有些构造对我来说仍然很模糊,其中之一是const。您可以在许多地方使用它,并具有许多不同的效果,对于初学者来说,几乎不可能活着。一些C ++专家会永远解释一次各种用法以及是否和/或为什么不使用它们吗?


正是在寻找这个问题:D
alamin

Answers:


100

尝试收集一些用途:

绑定一些临时到引用常量,以延长其寿命。引用可以是基础-析构函数不需要是虚拟的-正确的析构函数仍称为:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

说明,使用代码:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

该技巧在Alexandrescu的ScopeGuard实用程序类中使用。一旦临时范围超出范围,将正确调用Derived的析构函数。上面的代码遗漏了一些小细节,但这很重要。


使用const告诉其他方法不会更改此对象的逻辑状态。

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

将const用于写时复制类,以使编译器帮助您确定何时以及何时不需要复制。

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

说明:复制某些内容时,只要原始对象和复制对象的数据保持相同,您可能希望共享数据。一旦对象之一更改了数据,您现在就需要两个版本:一个用于原始版本,一个用于副本。也就是说,您在写入时复制到任何一个对象,因此它们现在都具有自己的版本。

使用代码

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

上面的代码段在我的GCC上打印了相同的地址,因为使用的C ++库实现了写时复制std::string。尽管这两个字符串是不同的对象,但它们的字符串数据共享相同的内存。使bnon-const优先于的非const版本,operator[]并且GCC将创建后备内存缓冲区的副本,因为我们可以更改它,并且它一定不会影响a!的数据。

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

为了使复制构造函数从const对象和临时对象进行复制

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

使常量不变

double const PI = 3.1415;

通过引用而不是通过值传递任意对象 -防止可能的昂贵或不可能的按值传递

void PrintIt(Object const& obj) {
    // ...
}

2
您能否在示例中解释第一种和第三种用法?
tunnuz

“为了保证被调用者该参数不能为NULL”,我看不到const与该示例有什么关系。
洛根·卡帕尔多

哎呀,我太失败了。我以某种方式开始写有关参考的文章。非常感谢您的
mo吟

3
请解释第一个例子。对我来说没有多大意义。
筑波

28

在C ++中,const实际上有2个主要用途。

常量值

如果值的形式为变量,成员或参数,在其生命周期内不会(或不应)对其进行更改,则应将其标记为const。这有助于防止对象发生突变。例如,在以下函数中,我不需要更改传递的Student实例,因此将其标记为const。

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

至于为什么要这样做。如果您知道基础数据无法更改,则对算法进行推理就容易得多。“ const”有帮助,但不能保证一定会实现。

显然,将数据打印到cout并不需要太多考虑:)

将成员方法标记为const

在前面的示例中,我将Student标记为const。但是C ++如何知道在Student上调用GetName()方法不会使对象变异?答案是该方法被标记为const。

class Student {
  public:
    string GetName() const { ... }
};

将方法标记为“ const”有两件事。首先,它告诉C ++该方法不会使我的对象变异。第二件事是所有成员变量现在都将被视为标记为const。这有帮助,但不会阻止您修改类的实例。

这是一个非常简单的示例,但希望它将有助于回答您的问题。


16

注意了解这4个声明之间的区别:

以下两个声明在语义上是相同的。您可以更改其中 CCP1和CCP2点,但你不能改变他们指向的东西。

const char* ccp1;
char const* ccp2;

接下来,指针是const,因此要有意义,必须将其初始化以指向某物。您不能使其指向其他内容,但是可以更改它指向的内容。

char* const cpc = &something_possibly_not_const;

最后,我们将两者结合在一起-因此所指向的对象无法修改,并且指针无法指向其他任何地方。

const char* const ccpc = &const_obj;

顺时针旋转规则可以帮助解开声明http://c-faq.com/decl/spiral.anderson.html


以回旋处的方式,是的!顺时针螺旋规则可以更好地描述它-从名称(kpPointer)开始,并绘制一个贯穿令牌的顺时针螺旋,然后说出每个令牌。显然,kpPointer右边没有任何内容,但它仍然有效。
史蒂夫·弗利

3

作为一点说明,正如我在此处阅读的那样,请注意

const 适用于其最左端的任何内容(如果没有,则适用于其最右端的任何内容)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.