小古银的官方网站(完整教程):http://www.xiaoguyin.com/
C++入门教程视频:https://www.bilibili.com/video/av20868986/
目录
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
(例如互斥,访问设备等),只要你保证不会写错就没问题了。
巩固练习
- 完成
simple_vector
的insert()
和erase()
。 - 对
simple_vector
内不需要修改成员变量的成员函数,使用const
修饰。 将成员函数
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_array
的pos
位置上的数据从pos + 1
的位置开始继续复制,而newarray
则是从刚才中断的位置开始被赋值。然后释放旧内存,接着把新内存地址赋值给m_array
并且m_size
自减就完事了。
温馨提示
当调用std::vector
中insert()
和erase()
的时候,由于标准没有规定insert()
和erase()
必须要检测是否越界,如果传入的位置是越界的,那么将会引发未定义行为,所以使用std::vector
的insert()
和erase()
时,需要注意入的位置是否越界。这些都是明确写在说明文档里面的,看文档时需要留心。