【C++】CONST、mutable关键字和成员初始化列表

C++中的CONST

  • const是伪关键字,不会改变什么
  • const是常量

例1

#include <iostream>
#include <string>

int main()
{
 const int MAX_AGE = 50;
 
 int* a = new int;

 *a = 2;
 std::cout << *a << std::endl;

 a = (int* )&MAX_AGE;
 std::cout << *a << std::endl;

 std::cin.get();

}
  • 打印:
    2
    50
  • 可以做两件事:
  1. 改变指针的内容,即指针指向的内存的内容
  2. 改变指针指向的地址
  • const int* a = new intint const * a = new int效果一样`:意味着不能修改指针指向的内容;*a = 2会报错; 改变a本身则不会报错,即地址不是常量
  • int* const a = new int : 可以改变指针指向的内容,但是不能把实际的指针本身重新赋值
  • const int* const a = new int : 指针本身不能被赋值,指针指向的内容也不能被改变
  • 理解const: const的理解、const指针、指向const的指针

const的含义

例2

class Entity
{
private:
 int m_X, m_Y;
public:
 int GetX() const 
 {
  return m_X;
 }
};
  • const在方法名之后,且只能在类中使用
  • 这个方法不会修改任何实际的类,不能修改成员变量
  • 如果在GetX()函数添加m_X = 2,会报错
  • 只能读取,不能改写
class Entity
{
private:
 int* m_X, m_Y;
public:
 const int* const GetX() const 
 {
  return m_X;
 }
 void SetX(int x)
 {
  m_X = x;

 }
};
  • 三个const
  • 意思是GetX函数返回了一个不能被修改的指针,以及指向的内容也不能修改的指针
    并且这个函数承诺不修改实际的Entity类
  • int* m_X, m_Y; : 这样只是让m_X成为整型指针,如果想让所有变量是指针,要在每个变量前加*

为什么要声明函数是常量

例3

class Entity
{
private:
 int m_X, m_Y;
public:
 int GetX() const 
 {
  return m_X;
 }
 void SetX(int x)
 {
  m_X = x;

 }
};
void PrintEntity(const Entity& e)
{
 e = Entity();
 std::cout << e.GetX() << std::endl;
}
  • 用常量引用来传递参数
  • 参考const Entity* e的效果,可以e = nullptr改变指针e本身的地址,但不能*e修改e指针指向的内容
  • 那么const Entity& e的效果类似,不能修改e的赋值e = Entity(), 因为引用就是内容,所以就是不能修改内容本身
class Entity
{
private:
 int m_X, m_Y;
public:
 int GetX() 
 {
  m_X = 2;
  return m_X;
 }
 void SetX(int x)
 {
  m_X = x;

 }
};
void PrintEntity(const Entity& e)
{

 std::cout << e.GetX() << std::endl;
}
  • 跟上面相比,去掉了GetX函数的const,此时e.GetX()不能用
  • 因为GetX函数已经不能保证它不会碰到(写入)Entity了
  • 这是因为GetX函数是可以修改Entity类的,比如在函数中加m_X = 2;,但是PrintEntity的传入参数已经规定const Entity& e,即不能改变类本身内容
  • int GetX() const { return m_X; } int GetX() { return m_X; } : 所以一般有两种版本的GetX函数的写法
  • 所以,如果函数实际上没有修改类或者不应该修改类的时候,应该总是标记方法为const;否则,在有常量引用的情况下,就用不了方法
  • 但是有时候又需要修改一下变量:比如只是为了调试,不会真正影响程序;可以用mutable,意味着变量是可以改变的;允许函数是常量的情况,还可以修改变量
class Entity
{
private:
 int m_X, m_Y;
 mutable int var;
public:
 int GetX() const
 {
  var = 2; 
  return m_X;
 }
 int GetX()
 {
  return m_X;
 }
 void SetX(int x)
 {
  m_X = x;

 }
};

C++中的mutable关键字

两种用法:

  1. 与const一起;
    1. 在lambda中使用

与const使用

例4

class Entity
{
private:
 std::string m_Name;
public:
 const std::string& GetName() const   
 {
  return m_Name;
 }
};

int main()
{
 const Entity e;
 e.GetName();
 std::cin.get();
}
  • 不允许修改实际的类成员,返回的字符串是不能修改,即必须返回字符串常量
  • 让方法GetName为const的原因是,在定义一个常量类的时候,即const Entity e,e是可以调用其方法GetName,因为GetName已经承诺不会改变实际的类了
  • 在某种情况,需要碰到某个极特殊的变量,比如在调试的时候,需要传入一个Debug计数,这个时候可以使用mutable
class Entity
{
private:
 std::string m_Name;
 mutable int m_DebugCount = 0;
public:
 const std::string& GetName() const   
  return m_Name;
 }
};

在lambda使用

例5

int x = 8;
 auto f = []()
 {
  std::cout << "Hello" << std::endl;
 };
 f();
  • 假设我们想打印x,而不是“Hello”
    这样是可以的:
auto f = [&x]()
 {
  std::cout << x << std::endl;
 };

这样也是可以的:

auto f = [&]()
 {
  std::cout << x << std::endl;
 };

这样也是可以的:

auto f = [x]()
 {
  std::cout << x << std::endl;
 };

这样也是可以的:

auto f = [=]()
 {
  std::cout << x << std::endl;
 };

但是,这样不行;因为x++做的操作实际上是创建一个新的变量y, 赋值给y,然后修改x:

auto f = [=]()
 {
  x++;
  std::cout << x << std::endl;
 };

跟这个有点像:

int y = x;
  y++;
  std::cout << y << std::endl;
  • 令lambda是mutable
    这样是可以的:
int x = 8;
auto f = [=]() mutable
 {
  x++;
  std::cout << x << std::endl;
 };
 f();

通过值传递的变量,比如x,是可以被改变的
打印:8
为什么不是9呢?
因为是通过=,即值来传递参数进lambda,而不是引用;所以在x++时,创建了y,并将x+1赋值给y,而原来的x其实还没动过,所以x打印出来还是8。

C++的成员初始化列表

  • 构造函数初始化列表,在构造函数中初始化类成员(变量)的一种方式
  • 当编写一个类并向该类添加成员时,通常需要用某种方式对这些成员(变量)进行初始化,通常是在构造函数中

两种方法

例6

#include <iostream>
#include <string>

class Entity
{
private:
 std::string m_Name;
public:
 Entity()  // 默认构造函数
 {
  m_Name = "Unknown";
 }

 Entity(const std::string& name)
 {
  m_Name = name;
 }
 const std::string& GetName() const { return m_Name; }

};
int main()
{
 Entity e0;
 std::cout << e0.GetName() << std::endl;

 Entity e1("Cherno");
 std::cout << e1.GetName() << std::endl;

 std::cin.get();
}

打印:
Unknown
Cherno

#include <iostream>
#include <string>

class Entity
{
private:
 std::string m_Name;
public:
 Entity()
  : m_Name("Unknown")
 {

 }


 Entity(const std::string& name)
 {
  m_Name = name;
 }
 const std::string& GetName() const { return m_Name; }

};
·
int main()
{
 Entity e0;
 std::cout << e0.GetName() << std::endl;

 Entity e1("Cherno");
 std::cout << e1.GetName() << std::endl;

 std::cin.get();
}
  • 在构造函数和参数之后,可以添加一个冒号,通常喜欢写在下一行,然后列出想要初始化的成员,然后给它值
  • 如果有两个变量,可以直接加逗号;注意:如果要这样初始化成员列表,一定要按顺序写这些变量
class Entity
{
private:
 std::string m_Name;
 int m_Score;
public:
 Entity()
  : m_Name("Unknown"), m_Score(0)
 {

 }
  • 对于有参数的构造函数,也是一样的这样进行初始化:
Entity(const std::string& name)
  :m_Name(name)
 {
 }

为什么要用成员初始化列表

  • 因为在构造函数里如果一行一行编写初始化,然后后面还会接其他的操作,会显得比较凌乱
  • 这样编写代码看着更加整齐
class Entity
{
private:
 std::string m_Name;
 int m_Score;
 int x, y, z;
public:
 Entity()
  : m_Name("Unknown"), m_Score(0), x(0), y(0), z(0)
 {

 }
  • 这样会使得m_Name构造两次,一个是使用默认构造函数,一个使用初始化的构造函数:
class Entity
{
private:
 std::string m_Name;
 int m_Score;
 int x, y, z;
public:
 Entity()
  :  m_Score(0), x(0), y(0), z(0)
 {
  m_Name = "Unknown";
 }

实际上发生这样:m_Name = std::string("Unknown")

两次初始化构造函数

例 7

#include <iostream>
#include <string>

class Example
{
public:
 Example()
 {
  std::cout << "Created Entity!" << std::endl;
 }

 Example(int x)
 {
  std::cout << "Created Entity with " << x << "!" << std::endl;
 }
};
class Entity
{
private:
 std::string m_Name;
 Example m_Example;

public:
 Entity()
 {
  m_Name = std::string("Unknown");
  m_Example = Example(8);
 }


 Entity(const std::string& name)
  :m_Name(name)
 {
 }
 const std::string& GetName() const { return m_Name; }
 

};

int main()
{
 Entity e0;


 std::cin.get();
}

打印:
Created Entity!
Created Entity with 8!

  • 一个是在private: Example m_Example产生的, 相当于这样:一次无参Example, 一次有参Example,所以会初始化两次
public:
 Entity()
 {
 Example m_Example;
  m_Name = std::string("Unknown");
  m_Example = Example(8);
 }
  • 如果改成这样,只创建了一个实例:
public:
 Entity()
  : m_Example(Example(8))
 {
  m_Name = std::string("Unknown");
 }

打印:
Created Entity with 8!

  • 甚至可以写成:
public:
 Entity()
  : m_Example(8)
 {
  m_Name = std::string("Unknown");
 }

例8

#include <iostream>
#include <string>

class Example
{
public:
 Example()
 {
  std::cout << "Created Entity!" << std::endl;
 }

 Example(int x)
 {
  std::cout << "Created Entity with " << x << "!" << std::endl;
 }
};
class Entity
{
private:
 std::string m_Name;
 Example m_Example;

public:
 Entity()
 {
  m_Name = std::string("Unknown");
  m_Example = Example(8);
 }


 Entity(const std::string& name)
  :m_Name(name)
 {
 }
 const std::string& GetName() const { return m_Name; }

};
int main()
{
 Entity e0;
 std::cout << e0.GetName() << std::endl;

 Entity e1("Cherno");
 std::cout << e1.GetName() << std::endl;


 std::cin.get();
}

打印:
Created Entity!
Created Entity with 8!
Unknown
Created Entity!
Cherno

  • 如果改成这样:
public:
 Entity()
  : m_Example(Example(8))
 {
  m_Name = std::string("Unknown");
 }

打印:
Created Entity with 8!
Unknown
Created Entity!
Cherno

应该到处使用成员初始化列表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值