【34】Const
const就是做出承诺某些东西不会改变
const与修饰的指针的位置关系也影响了指针或者指针指向的内容能否改变。
如下代码中,前两者相同,都不能修改 a。而第三者不能修改 a,最后一个是a,a都不能修改。我认为就看const和 *a,a的关系,const在 *a 前就是 a (地址指向的值)不能修改,在 a 前就是指针本身不能修改。而 const int const a; 就是 *a,a 都被 const 修饰,都不能修改,因此第四个还能写成这种形式:int const * const a = new int;。
const int* a; //不能修改a指向的内容,即a*
int const* a; // 不能改变a指向的内容,即a*
int* const a; //不能改变a
const int* const a; //a、a*都不能改变
在类和函数中使用const
在类方法后加上const
{
private:
int m_X, m_Y; //如果是int* m_X ,m_Y ;则表示m_X是指针、m_Y仍然是int
public:
int GetX() const // 这个方法不会修改任何实际的类,不能修改类成员变量;只能读取数据
{
return m_X;
}
};
在函数后加上const
void PrintEntity(const Entity& e)
{
// e = Entity(); 报错 对引用进行const修饰,就不能改变其本身的内容
std::cout << e.GetX() << std::endl;
}
当有一种情况下,虽然标记了 const ,但是你是真的想要修改数据,那么可以使用 mutable,意思是可以被更改的(可变的)(允许 const 的方法修改变量)。例如下面这样
class Entity
{
private:
int m_X,m_Y;
mutable int var;
public:
int GetX() const
{
var = 2;
return m_X;
}
【35】 mutable关键字
mutable表示的就是可以改变的变量
mutable共有两种用法,一种就是上面的和类里面的 const 一起用,另一种就是用在 lambda 表达式中(或者同时包含两种情况)
【36】成员初始化列表
构造函数初始化列表,是指在构造函数中初始化类成员(变量)的一种方式。
当编写一个类并向该类添加成员是,通常需要用某种方式对这些成员变量进行初始化。
不使用初始化列表的方法
class Entity
{
private:
std::string m_Name;
public:
Entity()
{
m_Name = "Unknow";
}
Entity(const std::string& name)
{
m_Name = name;
}
const std::string& GetName() const
{
return m_Name;
}
使用成员初始化列表
class Entity
{
private:
std::string m_Name;
public:
Entity()
: m_Name("Unknow") // 冒号后的,每次执行这个函数都会先执行一次
{
}
Entity(const std::string& name)
: m_Name ("name")
{
}
const std::string& GetName() const
{
return m_Name;
}
};
//与上面是一样的效果
使用成员初始化变量的好处不仅仅是使代码结构清晰,还可以节省空间,提高性能。
有一件要注意的事:在成员初始化列表里需要按顺序写。这很重要,因为不管你怎么写初始化列表,他都会按照定义类的顺序进行初始化。
比如上图在class Entity中定义m_Score,再是m_Name,所以成员初始化列表也要按照这个顺序执行。如果打破这个顺序就会导致各种各样的依赖性问题,所以你要确保你做成员初始化列表时,要与成员变量声明时的顺序一致。
使用成员初始化列表,除了直观好看外,还有一个好处就是避开了一层性能浪费。如果是直接在构造函数中赋值,实际上的过程是先构造,之后再赋值。
【37】三元运算符
实际上是if的语法糖
【38】创建并初始化c++对象
C++ 中创建一个对象,可以分为两种,一种是在栈上创建,一种是在堆上创建。其中栈上创建的对象其作用域只在当前作用域中存活,如果离开了该作用域栈,那么函数栈上的内存空间就会被释放,变量以及其所占用的内存空间都一并被释放掉。但如果是在堆上创建对象,只要你没有将其所占用的内存空间主动删去,即使堆内存的指针已经被作用域结束后释放掉,但堆上的内存空间依旧会存在。
一般的初始化方法,可以用于大多数情况,只要可以像下面这样创建对象,就这样创建
using String = std::string;
class Entity
{
private:
String m_Name;
public:
Entity()
: m_Name("Unknow") // 冒号后的,每次执行这个函数都会先执行一次
{}
Entity(const String& name)
: m_Name ("name")
{}
const String& GetName() const
{
return m_Name;
}
};
int main()
{
Entity entity; //实例化类,在栈上
std::cout << entity.GetName() << std::endl;
std::cin.get();
}
以下是在堆上创建变量
int main()
{
Entity* e;
{
Entity* entity = new Entity("Cherno"); //堆上创建entity
//储存entity内存地址,当复制entity对象时,只复制内存地址。
e = entity;
std::cout << (*entity).GetName() << std::endl;
}
std::cin.get();
delete e;
}
如果希望创建的对象比较大,或者希望能显式的控制对象的生存周期,就下堆上创建对象。
【39】new关键字
使用 new 关键字来创建对象时,他会判断该类型需要多大的空间,例如 int 类型,需要 4 个字节的空间。然后其会向 C 标准库申请内存空间。也就是说我们需要在内存中寻找有 4 个字节大小的连续内存空间。一旦找到了合适的空间,就会返回该空间的地址(指针),并在该空间存储数据、读写访问等等。
在堆内存中寻找空间实际上是通过堆内存中的一个空闲列表来进行:通过维护这个列表来为创建的新变量寻找适合的内存地址。
**new就是找到一个足够大的内存块以满足我们的需求,然后它给我们一个指向那块内存的指针 **
new返回你分配内存的指针
new不仅可以分配内存,还可以调用构造函数
int* a = new int;
int* b = new int[50];
delete a;
delete[] b;
用完new,一定要用delete
所谓的placement new,这就是要决定前面的内存来自哪来,所以你并没有真正的分配内存。在这种情况下,你只需要调用构造函数。在这种情况下,你需要调用构造函数,并在一个特定的内存地址中初始化你的Entity,可以通过写new()然后指定内存地址,比如:
int* b = new int[50];
Entity* entity = new(b) Entity();
【40】隐式转换与explicit
Entity a("Cherno")
Entity b(22);
Entity a = std::string("cherno");
Entity b = 22; //隐式的将22转换为一个Entity,构造出一个Entity
explicit 关键字只能用于修饰构造器函数,被修饰的函数意味着禁止进行隐式转化,使用该参数类型的构造器时必须以显式的方式创建对象。
class Entity{
public:
explicit Entity(int age){}
Entity(std::string name){}
}
Entity a(12);//no
Entity b = 12;//no