C++ 与 C 语言相比有着更强的类型检查,包括四种 cast,左值右值之分,reference,以及最重要的——对 const 的要求。

const 是一个相当麻烦的要求,比如其强大的“传播性”——只要在一个地方使用,就可能蔓延到各个角落,出现各种编译错误。但编程实践证明 const 的使用是值得的(甚至 Rust 语言已经将 const 作为基本要求了),可以提高代码的安全性和可理解性。

这种要求会体现在很多方面。

char a[] = "Hello";
char *p = a;
const char *q = a;
char *const r = a;
const char *const s = a;

你能说出它们有什么区别吗? (顶层 const 和底层 const

同时在函数的参数、返回值中也要考虑 const

参数

bool longerThan1(const string &s1, const string &s2) {
    return s1 > s2;
}
bool longerThan2(string &s1, string &s2) {
    return s1 > s2;
}
string a = "Hello";
longerThan1(a, "World"); // Yes
longerThan2(a, "World"); // No

不加 const 然编译器认为你想要对参数做出某些改变,而 "World" 可不是一个真正的 string 对象,自然不能让你改变它。

在这里有人认为可以使用下列函数

bool longerThan3(string s1, string s2) {
    return s1 > s2;
}
longerThan3(a, "World"); // ?

然而这是欠缺考虑的形式

  • 对于 a 来说,longerThan3()a 完完全全地复制了一遍,而 longerThan1() 使用的 const reference 实质是一种指针,不会复制 alongerThan3() 花费了无用的时间和空间。
  • 对于 "World" 来说,longerThan1(), longerThan3() 都使用 "World" 创建了一个新的 string 对象,在这一点上是一样的;但 longerThan1() 创建的 s2 是带有 const 的,这防止了对 s2 无谓的修改。

因此在任何时候,能用 const 就用 const

还要考虑最特别的参数 this,最好的实践同样是是:不改变成员的函数全都使用 const this。同时,对某些函数可以使用重载,比如

class Array
{
public:
    const int &operator[](size_t index) const {
        return a[index];
    }
    int &operator[](size_t index) {
        return a[index];
    }

private:
    int *a;
};

void printArray(const Array &a) {
    for (size_t i = 0, x = a.size(); i != x; ++i) cout << a[i];
}

const ArrayArray 可以使用有 const this 的函数,这点在传递 const Array &a 作为其他某个函数的参数的时候使用。

例外

不过如果真的遇到了需要用到用值传递的函数参数,则应该避免使用 const,例如

void foo(int x);       // Yes
void foo(const int x); // No

因为这属于多此一举,用值传递的参数只有函数内可以看见和使用,不会影响到原来的那个变量。这属于限制了函数的创建者,却没影响到函数的调用者。整个函数的逻辑都应该被创建者掌握,又何必多加限制呢。

返回值

对于返回值也是这样,只要稍加考虑就能明白,在绝大多数情况我们的返回值都应该是常量。因为返回值通常被赋值给另一个对象,亦或是弃之不用。考虑如下函数:

class B
{
public:
	B(int bb) : b(bb) {}
	B() : B(0) {}
	friend B operator+(const B &lhs, const B &rhs) {
        return B(lhs.b + rhs.b);
    } // 只是在类里面实现friend函数罢了

private:
	int b;
};

result = B(3) + B(5);

看上去,resultoperator+() 的返回值,然而实际情况却是这样

__t = B(3) + B(5);
result = __t;

__t 才是返回值,然后被赋值给 result。因此完全有可能出现这种情况。

B(3) + B(5) = 5;

虽然做法比较离奇,但谁知道调用者会怎么做呢(也有可能是 == 打成了 =),还是使用 const 为返回值加上限制吧。 friend const B operator+(const B &lhs, const B &rhs) { return B(lhs.b + rhs.b); }

例外

考虑 STL 的实践

vector<int> v{1, 2, 3};
for (auto p = ++v.begin(), q = --v.end(); p != q; ++p) cout << *p;

这里的 ++ -- 就说明没有为 begin(), end() 的返回值加上 const,这是为了方便使用者调整。

const this 和成员

值得注意的是,对于上述的 class Array,下面这样的函数是可行的。

int &operator[](size_t index) const {
    return a[i];
}

这是因为,const this 所代表的是:这个类成员不能变动,而 a(指针)是 Array 的成员,但 a[index](指针所指的位置)却不是 Array 的成员

至于此时 const 该不该加则需要一些考虑(在 bitwise constnesslogical constness 之间)。对于上面的这种情况,则应该还是要加的,因为这里 Array 是数组类。虽然编译器不认为 a[index] 是成员,但从逻辑上讲,考虑到我们实现 Array 的目的——作为一个容器类,a[index] 是成员。

而在另外的一些情况则可以不加。不仅如此,C++ 还提供了一个关键字 mutable 让你去掉 const 限制,也就是说,即使在是成员的情况下也可以修改某些量。加或不加,这是一个问题。

const_cast

const_cast 最大的作用就是在加或不加 const 的重载函数间提供方便。

比如 class Array 中的 operator[]() 可以改写成

const int &operator[](size_t index) const {
    return a[index];
}
int &operator[](size_t index) {
    return const_cast<int &>(const_cast<const A &>(*this)[index]);
}

比起原来的是不是还要麻烦许多?但是你看看这个

const int &operator[](size_t index) const {
	if (index < 0 || index >= size())
		throw std::invalid_argument("index out of bound");
#ifdef DEBUG
// recording
#endif // DEBUG
    return a[index];
}
int &operator[](size_t index) {
    return const_cast<int &>(const_cast<const A &>(*this)[index]);
}

就不用写两遍了,减少了很多麻烦。必须指出的是,只可能在 non-const 函数中用这种方法调用 const 函数,反之则不行。

constexpr 的关系

实际上两者没有什么关系,对于 constexpr,我称为真正的常量。在编译器就可以展开,可以用在 static_assert 中。用法有三种

  • 变量
  • 函数
  • 构造函数
函数

对函数使用后,能在编译期就可以直接算出值,但也有要求

  • 所有相关变量、函数都在编译期可以求得。这是总要求,其他要求都是衍生。
  • 参数是常量;
  • 除了 using, typedef, static_assert 只能有 return 语句;
  • 调用的函数也得是 constexpr
  • 不能使用全局变量等可能在运行期改变的量;
  • 返回常量。
constexpr int add(int x, int y) { return x + y; }

int main() {
	int k = add(3, 5);   // 常量版本
	int t = add(k, 2);   // 非常量版本
}

看看反汇编,可以被理解为常量的就直接就算出来了

0x000000000000073a <+0>:     push   %rbp
0x000000000000073b <+1>:     mov    %rsp,%rbp
0x000000000000073e <+4>:     sub    $0x10,%rsp
0x0000000000000742 <+8>:     movl   $0x8,-0x8(%rbp)       ; k 在这
0x0000000000000749 <+15>:    mov    -0x8(%rbp),%eax
0x000000000000074c <+18>:    mov    $0x2,%esi
0x0000000000000751 <+23>:    mov    %eax,%edi
0x0000000000000753 <+25>:    callq  0x7c0 <_Z3addii>      ; 只调用一次
0x0000000000000758 <+30>:    mov    %eax,-0x4(%rbp)       ; t 在这
0x000000000000075b <+33>:    mov    $0x0,%eax
0x0000000000000760 <+38>:    leaveq 
0x0000000000000761 <+39>:    retq   

要注意的是,定义要放在调用前,像下面这样就不能识别了

constexpr int add(int x, int y);

int main() {
	int k = add(3, 5);
	int t = add(k, 2);
}

constexpr int add(int x, int y) { return x + y; }
0x000000000000073a <+0>:     push   %rbp
0x000000000000073b <+1>:     mov    %rsp,%rbp
0x000000000000073e <+4>:     sub    $0x10,%rsp
0x0000000000000742 <+8>:     mov    $0x5,%esi
0x0000000000000747 <+13>:    mov    $0x3,%edi
0x000000000000074c <+18>:    callq  0x7cb <_Z3addii>
0x0000000000000751 <+23>:    mov    %eax,-0x8(%rbp)            ; k
0x0000000000000754 <+26>:    mov    -0x8(%rbp),%eax
0x0000000000000757 <+29>:    mov    $0x2,%esi
0x000000000000075c <+34>:    mov    %eax,%edi
0x000000000000075e <+36>:    callq  0x7cb <_Z3addii>
0x0000000000000763 <+41>:    mov    %eax,-0x4(%rbp)            ; t
0x0000000000000766 <+44>:    mov    $0x0,%eax
0x000000000000076b <+49>:    leaveq 
0x000000000000076c <+50>:    retq   
变量

对变量使用,变成常量,没什么好说的,主要注意在类中的情况

class B
{
    constexpr int           a = 10;   // Wrong: need to be static
    static constexpr int    b = 10;   // Right
    static constexpr double c = 10;   // Right
    static const int        d = 10;   // Right
    static const double     e = 10;   // Wrong: non-integral type
    static const double     f;        // Right
};
const B::f = 10;

使用 constexpr 可以直接在类中初始化 double 常量

构造函数

可以像内置类型一样定义常量,其他要求大致和函数一样。

class B
{
public:
	constexpr B(int bb) : b(bb) {}
private:
	int b;
};

int foo() {
    constexpr B b(55);
}

C++17、C++20 还有更多用法,不过就不讲了,现在用的最多的还是 C++14。

原文地址:http://www.cnblogs.com/violeshnv/p/16831724.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性