like a promise,but can be broken --Cherno
前言¶
在学习 C++的时候看过不少教程,但很难有把一个知识点完全讲透的。比如 Cherno 的 C++会告诉你怎么去用 How,而侯捷的 C++会告诉你 Why,但并没有完全把某个知识点讲干净。因此,该专栏用于记录和分享笔者在学习 C++过程中遇到并在网上整理集合的知识点,并讲清楚 How 与 Why,希望这些能对大家带来帮助。如有疑问和遗漏,也欢迎大家进行修正与补充。
常量¶
一般默认 const 是必须赋值的,使用 const 直接修饰变量,以下两种定义形式在本质上是一样的。此时,该变量变为了常量,在定义了之后无法对值进行修改。
const int MAX_IMAGE = 90;
int const MAX_IMAGE = 90;
extend 全局常量声明¶
将 const 改为外部连接,作用于扩大至全局,编译时会分配内存,此时可以不初始化,而只是声明,编译器认为在程序其他地方进行了定义,该常量可以从其他地方获取
extend const int ValueName = value;
强制转换¶
Const 类型转化为非 Const 类型的方法可以采用 const_cast 进行强制转换。
const_cast <type_id> (expression);
指针修改常量¶
C++可以通过强大的指针进行修改,下面的代码就是通过指针去修改常量的值。
const in MAX_IMAGE = 90;
int* a = new int;
a = (int*)&MAX_IMAGE; //可能会崩溃,但是实际上可以工作
*a = 2;
cout << *a <<" " << MAX_IMAGE;
2 99
看到网上的一个回答:
编译器在这里耍了一个小聪明,在编译的时候自动把 const 类型的变量替换成了字面数值了。所以输出那行实际上被改写成了
因此,输出的还是 99,但实际上常量的值已经被修改为 2 了std::cout << *a << " " << 90 << std::endl;
reference to const 对常量的引用¶
const int ci =1024;
const int &1 =ci;
int i = 42;
const int &r1 = i;//可以绑定,但是不可以通过R1修改i的值
const int &r2 = 42;
const int &r3 = r1*2;
int &r4 = r1* 2;//Error!
double dval = 3.14;
const int &ri = dval;
对与非常量的引用,编译器实际上做了这样的处理:
const int temp = dval;
const int & ri = temp;
常量与指针¶
const 的位置不同,修饰的东西也不同,我们需要牢记并区分常量的各种位置及其用法。与指针相关的常量一般会有下面的三种用法。
const int* a = new int;
int* const a = new int;
const int* const a = new int;
常量指针(指向常量的指针) const to pointer¶
不能改变指针指向的内容
const in MAX_IMAGE = 90; const int* a = new int; // *a =2; //这里会报错,不允许改变*a的值,即指针指向的内容 //但是可以改变a的内容。 a = (int*)&MAX_AGE;
指针常量 pointer const¶
CPP primer 把这个叫做指针常量
const in MAX_IMAGE = 90; int* const a = new int;// *a =2;//可以改变指针的值 //a = (int*)&MAX_AGE; //这里会报错,不允许改变a的值,即指针指向的方向 //a = nullptr //同样,报错
合并使用(指向常量的常量指针)¶
const int MAX_AGE = 90;
const int* const a = new int;
//这样下面都会报错
//*a = 2;
//a = (int*)&MAX_AGE;
//a = nullptr;
常量与函数设计¶
常量成员函数 const member functions¶
在成员函数后加 const,意味着该方法不会对成员变量造成影响
class Entity
{
private:
int m_X,m_Y;
public:
int GetX() const
{
return m_X;//该方法并没有对成员变量产生修改
}
//因为修改了成员变量,该方法不能使用const
void SetX(int x) /*const*/
{
m_X = x;
}
}
成员变量为指针¶
这里体现了三种 const 位置的使用
class Entity { private: int* m_X,*m_Y; public: //这意味着: //我们返回了一个指针不能变,指向地址不能变的值,且该成员函数不会被修改 const int* const GetX() const { return m_X;//该方法并没有对成员变量产生修改 } }
接下来我们根据第一个 Entity 类进行函数设计
指针设计 Const Pointer¶
对于参数传进来的是指针的函数 - 先来看看普通的函数
void PrintEntity(Entity* e)
{
*e = Entity();//可以修改指针指向的值
e = nullptr;//可以修改指针本身的地址
std::cout << e->GetX() << std::endl;
}
- 常量指针:我们可以修改它指向的方向,即指针本身的地址。但不能修改指针指向的值
void PrintEntity(const Entity* e) { //*e = Entity();不可以修改指针指向的值 e = nullptr; std::cout << e->GetX() << std::endl; }
- 指针常量:可以改值不能改地址
void PrintEntity(const Entity* e)
{
*e = Entity();
//e = nullptr;不可以修改指针地址
std::cout << e->GetX() << std::endl;
}
常量引用 Const Reference¶
对于函数传进来的常量引用参数来说 这个 e 是常量,不能将它重新赋值。 不过这里没有指针本身和指针指向内容的区别了,因为引用即内容,所以也不用考虑指针的问题。
void PrintEntity(const Entity& e)
{
//e = Entity(); 不能修改引用
std::cout << e.GetX() << std::endl;
}
常量与面向对象设计¶
我们使用方法的时候一般是不修改类的,类的设计者应该在设计之初就设计好正确的类来交给其他人使用,而不是遇到错误再去修改。
假设我们把 Entity 类的 const 去掉,我们的引用就无法获取到 GetX()。因为我们已经不能保证这个 GetX 会不会使 Entity 类修改了。如果类成员变量被修改,就意味着违反了 const 的设计原则。
class Entity
{
private:
int m_X,m_Y;
public:
int GetX()
{
return m_X;//该方法并没有对成员变量产生修改
}
//因为修改了成员变量,该方法不能使用const
void SetX(int x) /*const*/
{
m_X = x;
}
}
void PrintEntity(const Entity& e)
{
//e = Entity();
std::cout << e.GetX() << std::endl;//Error!!!程序报错!!
}
所以有时候我们会看到这种形式的函数。
这种情况下,就会分不同类型进行调用了。
类的设计者如果一开始没有设计 const,则需要补上一个 const 的成员函数。但是实际上,只要我们一开始设计的时候就使用了 const,便不会发生这种问题。class Entity { private: int m_X,m_Y; public: int GetX() const { return m_X;//该方法并没有对成员变量产生修改 } //当然,这段完全可以不加 int GetX() { return m_X; } //因为修改了成员变量,该方法不能使用const void SetX(int x) /*const*/ { m_X = x; } } void PrintEntity(const Entity& e) { //e = Entity(); std::cout << e.GetX() << std::endl; }
因为无论是常量引用,还是非常量引用,常量指针还是非常量指针,都可以调用常量成员函数!!!
侯捷老师在面向对象的设计上也讲过这一点,这里放个图供大家记忆: ![[Pasted image 20240318195858.png]]
C11-constexpr 和 常量表达式¶
- 定义:值不会改变并且在编译过程中就能得到计算结果的表达式
const int max_files = 20; const int limit = max_files + 1; const int sz = get_size();//不是常量表达式
C11 constexper 使用¶
constexpr int mf = 20;
constexpr int limit = mf+1;
constexpr int sz = size();//当且仅当size是一个constexpr函数时,才是一条正确的声明语句。
指针和 constexpr¶
声明 constexpr 时用到的类型为字面值类型。因此一个 constexpr 指针的初始值必须是: - nullptr 或 0; - 或一个存储于某个固定地址的对象。 此外,限定符 constexpr 仅对指针有效,与指针所指对象无关
const int *p = nullptr; //p是一个指向整型变量的指针(常量指针)
constexpr int *q = nullptr; //q是一个指向整数的指针常量(C++ Primer说的是常量指针,定义不太一样)
mutable 使用¶
为什么开头说 const 只是一种 promise(承诺),但是又可以 break(打破)呢?
这就不得不提到这个比较让人感觉到无语的关键字 multiple
,他的优先级会高于 const,当我们声明 mutable 的变量后,该变量是可以在 const 函数中改变的!!!
OMG,那为啥要整 const 函数啊啊啊?
笔者猜测的是由于后续修改的时候,又不得不在某个函数修改一些变量,所以整了个这个关键字。
好了,话不多说,上代码:
class Entity
{
private:
int m_X,m_Y;
mutable int var;
public:
int GetX() const
{
var = 2;//此时,可以改变var值
return m_X;//该方法并没有对成员变量产生修改
}
//因为修改了成员变量,该方法不能使用const
void SetX(int x) /*const*/
{
m_X = x;
}
}
void PrintEntity(const Entity& e)
{
//e = Entity();
std::cout << e.GetX() << std::endl;//Error!!!因为我们
}
const 使用原则¶
- 大胆使用 const,只要你能搞清楚为什么要用,那么能用则用。设计类时,任何不会修改数据成员的函数都应该声明为 const 类型
- 参数中使用 const 应该使用引用或指针,而不是一般的对象实例
- const 在成员函数中的三种用法(参数、返回值、函数)要很好的使用,但不要不要轻易的将函数的返回值类型定为 const(除了重载操作符外一般不要将返回值类型定为对某个对象的 const 引用)