chrono C++笔记
【24】枚举ENUM(enumeration)
本质上是一个数值集合,不管怎么说,这里面的数值只能是整数。
枚举默认是32位整型,同样也可以在后面规定整数的类型。但是不能用float,因为float不是整数。
- 枚举变的名字一般以大写字母开头(非必需)
- 默认情况下,编译器设置第一个 枚举变量值为 0,下一个为 1,以此类推(也可以手动给每个枚举量赋值),且 未被初始化的枚举值的值默认将比其前面的枚举值大1。 )
- 枚举量的值可以相同
- 枚举类型所使用的类型默认为int类型,也可指定其他类型 ,如 unsigned char。
- 枚举类型中的每个成员都被称为枚举量(enumerator),其实际上就是整数常量。
- 枚举变量是使用枚举类型定义的变量。它可以存储枚举类型中任意一个枚举量的值。与普通变量不同,枚举变量只能存储枚举类型中声明的枚举量的值,不能存储其他任何值。
在 C++ 中,枚举变量(使用 enum 声明的变量)只能被赋值为其定义的枚举量中的一个。这是因为枚举类型的本质是整数常量,每个枚举量代表了一个特定的整数值。因此,枚举变量只能存储枚举量的值,不能存储其他任何值。
例如,考虑以下枚举类型:
c++
enum Color {
RED, // 枚举量
GREEN,
BLUE
};
在该枚举类型中,RED 的值为 0,GREEN 的值为 1,BLUE 的值为 2。因此,只有枚举变量可以被赋值为 RED、GREEN 或 BLUE 中的一个,例如:
Color color = RED; // 合法,color 的值为 0
如果试图将枚举变量赋值为非枚举量的值,则会导致编译错误。例如:
Color color = 1; // 错误,不能将整数值赋给枚举变量
需要注意的是,在 C++11 中引入了强化的枚举类型(enum class),它允许开发人员指定枚举量的底层类型,并提供更加严格的类型检查。强化的枚举类型不允许将枚举变量隐式转换为整数类型,可以通过 static_cast 显式地将其转换为整数类型。例如:
enum class Color : int {
RED = 1,
GREEN = 2,
BLUE = 3
};
Color c = Color::RED;
int i = static_cast<int>(c); // 合法,将枚举变量转换为整数类型
总之,在 C++ 中,只有枚举量中定义的值可以被赋给枚举变量。这种限制可以防止开发人员错误地为枚举变量赋予无效值,提高了程序的可维护性和可读性。
【25 】类的构造函数
class Entity
{
public:
float X, Y;
void Print()
{
std::cout << X << "," << Y << std::endl;
}
};
int main() //程序的入口
{
Entity e;
e.Print();
std::cin.get();
}
//输出 -1.07374e+08,-1.07374e+08
程序仍然可以执行,仍有结果输出,因为当实例化Entity时,自动分配内存,并没有初始化那个内存,意味着得到了哪个内存空间原来的内容。
所以需要一个初始化的方法。需要定义这个Init方法,每当我们想在代码中创建一个实例,可以直接运行这个初始化代码。所以需要一个构造函数。
Entity()
{
X = 0.0f;
Y = 0.0f;
}
构造函数是一种特殊类型的方法,是一种每次构造一个对象时都会调用的方法。
- 主要用途是初始化该类,当创建一个新对象实例时,构造函数初始化变量。
- 构造函数没有返回类型
- 构造函数的命名必须和类名一样
- 如果你不指定构造函数,你仍然有一个构造函数,这叫做默认构造函数(default constructor),是默认就有的。这个默认的构造函数,内部什么都没有,可以删除该默认构造函数
- 在c++中必须要手动初始化所有变量。
同样可以给构造函数传参数。
Entity(float x, float y)
{
//把参数赋值给成员变量
X = x;
Y = y;
}
默认的构造函数也可以删掉
class Log
{
public:
Log() = delete; //删除默认的构造函数
static void Write()
{
}
};
- 构造函数不会在你没有实例化对象的时候运行,所以如果你只是使用类的静态方法,构造函数是不会执行的。
- 当你用new关键字创建对象实例的时候也会调用构造函数。
【26】类的析构函数
- 析构函数是在你销毁一个对象的时候运行。
- 析构函数同时适用于清理栈和堆分配的内存。如果用new关键字创建一个对象(存在于堆上),然后你调用delete,析构函数就会被调用;如果你只有基于栈的对象,当跳出作用域的时候这个对象会被删除,所以这时侯析构函数也会被调用。
//显式写出析构函数,跟构造函数的区别就是前面多了一个~
~Entity()
{
std::cout << "Destoryed" << std::endl;
}
void Function()
{
Entity e;
e.Print();
}
int main() //程序的入口
{
Function();
std::cin.get();
}
- 因为这是栈分配的,我们会看到当main函数执行完的时候析构函数就会被调用
- 析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
在 C++ 中,类的析构函数(destructor)用于释放对象所占用的内存空间和资源。当对象被销毁时,系统自动调用析构函数来进行清理工作。通常情况下,编译器会生成一个默认的析构函数,它会自动调用成员变量的析构函数,然后释放对象的内存空间。不过,在以下情况下需要显式定义析构函数:
- 类中存在指针或动态分配内存
- 类中存在其他资源
除了内存之外,类可能还使用了其他的资源,例如打开的文件、网络连接等。在对象销毁时,需要显式释放这些资源,以避免资源泄漏。
【27】继承
派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public。需要注意的是:在派生类的各个成员函数中,不能访问基类的private成员。
class Entity
{
public:
float X, Y;
void Move(float xa, float ya)
{
X += xa;
Y += ya;
}
};
class Player : public Entity
{
public:
const char* Name;
void PrintName()
{
std::cout << Name << std::endl;
}
};
int main() //程序的入口
{
std::cout << sizeof(Player) << std::endl;// 12 Entity的8字节,加上Player的4字节
Player player;
player.X = 2;
player.Move(5, 5);
std::cin.get();
}
【28】虚函数
- 虚函数可以让我们在子类中重写方法。
- 虚函数因与了一种Dynamic Dispatch(动态联编)的东西,通常通过v表实现,它包含基类中所有虚函数的映射。这样我们可以在它运行时,映射到正确的覆写(override)函数。
以下的例子中,如果不使用虚函数,最后输出结果是Entity,Entity
class Entity
{
public:
std::string GetName() { return "Entity"; }
};
class Player : public Entity
{
private:
std::string m_Name;
public:
// Player类的构造函数传递的参数是 常量字符串类型的引用 &表示引用,为m_Name初始化传递参数
// 引用(reference)是一种别名或另一个对象的名称,它是在声明时被初始化的,并且必须始终引用同一个对象,不能更改它所引用的对象。
Player(const std::string& name)
: m_Name(name) {}
std::string GetName() { return m_Name; }
};
void PrintName(Entity* entity)
{
std::cout << entity->GetName() << std::endl;
}
int main() //程序的入口
{
//e 是一个指向 Entity 类型对象的指针,可以通过 e 指针访问堆上分配的对象,并调用其成员方法或修改其成员变量。
//例如 e->GetName() 可以调用 Entity 类中的 GetName() 方法,获取对象名字
Entity* e = new Entity();
PrintName(e);
Player* p = new Player("zzy");
PrintName(p); // 输出的仍是Entity。因为PrintName的参数是Entity类指针,在调用GetName函数时,只能调用到Entity中的GetName方法,只能输出“Entity”
std::cin.get();
}
如果希望输出Player类的方法,需要用到虚函数,格式也很简单
在父类的方法中声明vitual
class Entity
{
public:
virtual std::string GetName() { return "Entity"; }
};
在子类中写入override关键字,可以帮助看出bug
class Player : public Entity
{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name) {}
std::string GetName() override { return m_Name; }
};
有两个与虚函数相关的运行成本,
- 基类中要有歌成员指针指向这个v表, 需要额外的内存来存储v表,这样可以分配到正确的函数。
- 每次调用虚函数时,需要遍历这个表,来确定映射到哪个函数。
- 虚函数的例子,通常有三步。
- 第一步,定义基类,声明基类函数为 virtual 的。
- 第二步,定义派生类(继承基类),派生类实现了定义在基类的 virtual 函数。
- 第三步,声明基类指针,并指向派生类,调用virtual函数,此时虽然是基类指针,但调用的是派生类实现的基类virtual 函数。
虚函数表(v table)是一个指向虚函数的指针数组,用于存储类对象的虚函数地址。
在 C++ 中,当一个类定义了虚函数后,编译器会为其生成一个对应的虚函数表,该表包含了该类所有带有 virtual 关键字修饰的虚函数的地址。每个类对象都包含了一个指向该虚函数表的指针成员变量(通常为 _vptr 或类名 + _vtable),用于动态解析虚函数的地址。
【29】c++接口interface(纯虚函数)
纯虚函数允许我们在基类中定义一个没有实现的函数,然后强制子类去实现这个函数。
纯虚函数是一种没有函数体的虚函数,在类中声明但不予实现,它的作用是强制子类对该函数进行实现,并且可以通过基类指针或引用调用子类中的实现代码。纯虚函数通常用于定义抽象类或接口类,不能被直接实例化,而只能作为其他类的基类或接口使用
在面向对象程序设计中,创建一个只包含未实现方法然后交由子类去实际实现功能的类是非常普遍的,这通常被称为接口。接口就是一个只包含未实现的方法并作为一个模板的类。并且由于此接口类实际上不包含方法实现,所以我们无法实例化这个类。
class Printable //创建Interface,本质上就是一个class
{
public:
virtual std::string GetClassName() = 0; // 只不过拥有一个纯虚函数
};
class Entity : public Printable //让Entity实现interface
{
public:
virtual std::string GetName() { return "Entity"; }
std::string GetClassName() override { return "Entity"; }
};
class Player : public Entity //继承类
{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name) {}
std::string GetName() override { return m_Name; }
std::string GetClassName() override { return "Player"; }
};
void Print(Printable* obj)
{
std::cout << obj->GetClassName() << std::endl;
}
int main() //程序的入口
{
Entity* e = new Entity();
Player* p = new Player("zzy");
Print(e);
Print(p);
std::cin.get();
}
【30】visable 可见性
- 可见性是一个属于面向对象编程的概念,它指的是类的某些成员或方法实际上是否可见。可见性是指:谁能看到它们,谁能调用它们,谁能使用它们,所有这些东西。
- 可见性是对程序实际运行方式、程序性能或类似的东西没影响。它只单纯的是语言层面的概念,让你能够写出更好的代码或者帮助你组织代码。
- C++中有三个基础的可见修饰符(访问修饰符):private、protected、public
- private:只有自己的类和它的友元才能访问(继承的子类也不行,友元的意思就是可以允许你访问这个类的私有成员)。
- protected:这个类以及它的所有派生类都可以访问到这些成员。(但在main函数中new一个类就不可见,这其实是因为main函数不是类的函数,对main函数是不可访问的)
- public:谁都可见。