• Index

const 和 mutable

Reads: 7

const

我们在设计类的成员函数时,如果这个成员函数不需要改变成员变量的值时,应该用const修饰这个成员函数。如下面代码:

基础示例 1

#include <iostream> // std::cout std::endl
#include <utility> // std::pair

class point_t
{
public:
    point_t(int a, int b);

    // 这里加const说明这个成员函数内的操作不会改变成员变量
    std::pair<int, int> to_pair(void) const; // 在这里加const

public:
    int x;
    int y;
};

int main(void)
{
    const point_t point(222, 333);

    auto pt = point.to_pair();
    std::cout << "横坐标:" << pt.first << std::endl;
    std::cout << "纵坐标:" << pt.second << std::endl;

    return 0;
}

point_t::point_t(int a, int b)
    : x(a)
    , y(b)
{
}

// 将横坐标和纵坐标用std::pair保存并返回, 不改变成员变量的值
std::pair<int, int> point_t::to_pair(void) const // 这里也要加const
{
    // x = 100; // 去掉开头注释, 即当试图改变成员变量的值时, 编译将会报错
    return { x, y };
}

输出结果:

横坐标:222
纵坐标:333

基础讲解 1

在上面代码中,我们看到需要在成员函数的声明和分号;之间加上关键字const,并且也要在定义中参数括号)后面加上const。这样可以令这些成员函数内的操作都不能改变成员变量,从而使编译器知道这个成员函数不会改变成员变量的值。在to_pair成员函数中,当试图改变成员变量的值时,如上面代码中的x = 100;, 编译将会报错。

基础示例 2

当成员函数不用const修饰时:

#include <iostream> // std::cout std::endl
#include <utility> // std::pair

class point_t
{
public:
    point_t(int a, int b);
    std::pair<int, int> to_pair(void);

public:
    int x;
    int y;
};

int main(void)
{
    const point_t point(222, 333);
    // auto pt = point.to_pair(); // 去掉开头注释将会编译报错
    return 0;
}

point_t::point_t(int a, int b)
    : x(a)
    , y(b)
{
}

std::pair<int, int> point_t::to_pair(void)
{
    return { x, y };
}

基础讲解 2

当对象point使用const修饰,意味着point内所有成员变量都不能被修改。成员函数to_pair()虽然没有改变成员变量的值,但是由于它没有使用const修饰,编译器并不知道它会不会改变成员变量的值。因此当使用point.to_pair();时,编译将会报错。

基础拓展

建议:在我们设计成员函数的时候,就要确定哪些成员函数是不会改变成员变量的,这时候就需要使用const了。为成员函数加上const限定除了让编译器知道它不会改变成员变量,更重要的是让调用者知道这个成员函数是不会改变成员变量的值,让调用者放心调用。

mutable

当成员变量用关键字mutable修饰时,就算成员函数使用了const修饰,也能改变它的值。先看下面例子:

#include <iostream>
#include <string>

class testclass
{
public:
    void no_modify(void) const;
    std::u32string m_text;
    mutable std::u32string m_text2;
};

void testclass::no_modify(void) const
{
    std::u32string text = U"这是小古银的C++教程中的成员函数const限定";
    text = U"手动滑稽";

    // 去掉下面注释编译将会报错
    // m_text = text;

    m_text2 = text;
}

int main(void)
{
    const testclass obj;
    obj.no_modify();
    obj.m_text2 = U"abc"; // 在这里改它的值也是没有问题的
    // obj.m_text = text; // 但是改它的值就不行, 去掉开头注释编译将会报错
    return 0;
}

一般良好的设计会少用mutable,原因就是上面所说的让调用者放心。不过事实上也有不少情况需要用到mutable(例如互斥,访问设备等),只要你保证不会写错就没问题了。

巩固练习

  1. 完成simple_vectorinsert()erase()
  2. simple_vector内不需要修改成员变量的成员函数,使用const修饰。
  3. 将成员函数at()改成以下两个重载成员函数,使重载二可以改变对应元素的值:

     const int & at(std::size_t pos) const; // 重载一
     int & at(std::size_t pos); // 重载二
    

当完成以上步骤时,类simple_vector就已经算是完成了。

提示

insert():首先还是要判断指定的位置有没有越界,当pos等于m_size时就是在末尾添加元素,再考虑当m_size等于0的时候,这种也是适用的,那么这个问题就解决了。然后不用说,肯定是要先申请新的堆内存。将位置在pos前面的数据从m_array直接复制到newarray,在newarray的第pos位置处应该赋值新数据即value,然后在newarray接着就是从pos + 1的位置开始被赋值,而m_array就要从刚才中断的位置pos开始。复制完成后,释放旧内存,接着把新内存地址赋值给m_array并且m_size自增就完事了。

erase():首先要判断是否越界,由于数组索引是从0开始的,当pos等于m_size时,位置就是最后的元素的后一个位置,这个位置没有元素,所以当pos等于m_size时应该注意操作。然后还是申请新的内存空间。位置pos前的数据直接从m_array复制到newarray,然后跳过m_arraypos位置上的数据从pos + 1的位置开始继续复制,而newarray则是从刚才中断的位置开始被赋值。然后释放旧内存,接着把新内存地址赋值给m_array并且m_size自减就完事了。

温馨提示

当调用std::vectorinsert()erase()的时候,由于标准没有规定insert()erase()必须要检测是否越界,如果传入的位置是越界的,那么将会引发未定义行为,所以使用std::vectorinsert()erase()时,需要注意入的位置是否越界。这些都是明确写在说明文档里面的,看文档时需要留心。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.