Day 1 (2022.4.26)
// 在#后面的都是预处理语句,编译器收到源文件后首先处理预编译语句(发生在实际编译之前)
// #include
// main函数不写return则默认返回0
// 重载运算符其实是函数
std::cout.print("Hello World!").print(std::endl);
// std::cin.get() 等待我们按下enter
// 预处理->编译生成目标文件(.obj)->链接为可执行文件(.exe)
// 链接
// Log.cpp
#include
void log(const char* message) {
std::cout <
// Main.cpp
#include
void log(const char* message);
int main() {
log("Hello World!");
std::cin.get();
}
Day 2 (2022.4.27)
// 一个源文件连同通过#include包含的所有头文件和源文件一起被称为预处理翻译单元,
// 预处理翻译单元经过预处理之后被称为翻译单元
// 预编译处理命令 if true 就会编译中间的内容
#if
程序段
#endif
// 如果 xxx 被#define定义过,则编译程序段1,否则2
#ifdef
程序段1
#else
程序段2
Day 3 (2022.5.8)
// 每一个exe文件都要有一个入口函数,不一定是main,可以设置
// 静态函数是只会在当前file文件用到。如果在main函数中没有用到它,则不会对它进行link
// Log.cpp
#include
void Logg(const char* message)
{
std::cout <
// Math.cpp
#include
void Log(const char* message);
static void Multiply(int a, int b)
{
Log("Multiply");
}
int main()
{
std::cin.get();
}
// 下面代码会有重复定义的error,有三种解决办法
// static(每个cpp都有一份copy) inline(每个cpp直接是实现代码) 声明放在头文件,实现放在CPP
// Log.h
#pragma once
void Log(const char* message)
{
std::cout <
// Log.cpp
#include
#include
void InitLog()
{
Log("Initialized Log");
}
// Math.cpp
#include
#include
static void Multiply(int a, int b)
{
Log("Multiply");
}
int main()
{
Multiply(3, 4);
std::cin.get();
}
Day 4 (2022.5.14)
// 可以使用引号 " " 来指定编译器包含目录的相对路径里面的文件,尖括号 <> 只用于编译器包含
// 路径 (include)
Day 5 (2022.5.15)
// 在断点(breakpoint)处,右键点击转到反汇编(go to disassembly)
// Visual Studio 的最佳设置
// Header Files, Source Files 是一种虚拟的组织方案
// 点击 show all files 可以看到实际的文件组织情况,你可以自己
// 新建src文件夹,从visual studio中拖动文件放进去
// (debug模式), Visual Studio 把中间文件放到了项目
// 目录中一个名为debug的文件夹中,而实际最终可执行的
// 二进制文件在solution目录下的一个名为Debug文件夹
// 现在我们来修改这个设置,在项目的属性中,将输出文件路径和中间文件路径改为如下,然后右键项目
// clean solution,当然这个clean清理不干净,需要我们再自己去文件夹中将不要的文件删除
// 指针就是地址,是一个保存内存地址的整数
// 类型与这个无关,只是让编译器知道放在这个地址的是什么东西
// 引用是指针的语法糖,是指针的伪装
// 在定义引用后不能再更改它
// 使用指针修改变量
void Increment(int* value)
{
(*value)++;
}
int main()
{
int a = 5;
Increment(&a);
return 0;
}
// 使用引用修改变量
void Increment(in&* value)
{
value++;
}
int main()
{
int a = 5;
Increment(a);
return 0;
}
// m_xxx, m_ 指私有类成员变量
// s_xxx, s_ 指静态变量/函数
// 静态变量或函数只在当前翻译单元内部链接
// 静态变量或函数意味着,当需要将这些函数或变量与实际定义的符号链接时,
// 链接器不会在这个翻译单元的作用域之外,寻找那个符号定义
// 如果将 Static.cpp中的static取消,会报重复定义s_Variable的错误
// 加static可以使得自己对其它翻译单元隐藏
// 也可也去掉static,再在Main.cpp中改成 external int s_Variable;
// Static.cpp
static int s_Variable = 5;
// Main.cpp
#include
int s_Variable = 10;
int main()
{
std::cout <
}
// 静态成员变量是所有实例共享的,但是其只是在类中进行了声明,
// 并未定义或初始化(分配内存),类或者类实例就无法访问静态成员变量,
// 这显然是不对的,所以必须先在类外部定义
// 如果不加int Entity::x,会报错为解析的外部引用变量
struct Entity
{
static int x;
void Print()
{
std::cout <
}
int Entity::x;
int main()
{
Entity e;
e.x = 2;
return 0;
}
Day 6 (2022.5.16)
// 用static实现单例类,调用Get一直都是同一个对象
// 也可用静态初始化函数创建所有对象,然后使用静态方法Get之类的。
#include
class Singleton
{
public:
static Singleton& Get()
{
static Singleton instance;
return instance;
}
void Hello() {}
};
int main()
{
Singleton::Get().Hello();
return 0;
}
// 在类中,enum是静态的
#include
class Log
{
public:
enum Level
{
LevelError, LevelWarning, LevelInfo
};
private:
Level m_Loglevel = LevelInfo;
public:
void SetLevel(Level level)
{
m_Loglevel = level;
}
void Error(const char* message)
{
if (m_Loglevel >= LevelError)
std::cout <<"[ERRR]: " <
};
int main()
{
Log::Level tmp = Log::LevelError;
std::cout <
return 0;
}
// 当对象是在栈上创建时,退栈的时候就会调用析构函数
// 如果在堆上手动分配了任何类型的内存,那么就需要手动清理 (在堆上创建的对象)
// 如果在类使用中或者构造中分配了内存,就要删除它们
Day 7 (2022.5.17)
// 继承的写法:class Player : public Entity
// 在接受父类的地方都可以传递子类进去
// C++ 虚函数
// 虚函数允许我们在子类中重写方法
// 虚函数需要额外的内存来存储虚函数表,
// 每次调用虚函数时,需要遍历这个表,来确定映射到哪个函数
// 如果不是虚函数就会打印两次Entity
#include
#include
class Entity
{
public:
virtual std::string GetName() {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;}
}
void PrintName(Entity* entity)
{
std::cout <
int main()
{
Entity* e = new Entity();
PrintName(e);
Player* p = new Player("Xin");
PrintName(p);
return 0;
}
Day 8 (2022.5.18)
// C++ 接口(纯虚函数)
// 如果一个类只包含纯虚函数,不能实例化它。其子类必须实现这个纯虚函数,才能被实例化
virtual std::string GetName() = 0;
// C++ 可见性
// private 当前类和它的友元类/函数可访问
// protected 只能在当前类和其继承类中可访问
// public 都可以访问
// C++ 数组
int a[5]; //是在栈中创建的内存,在花括号结束时自动回收
int* a = new int[5] //是在堆上创建,在程序销毁前都处于活跃状态,需要用delete删除
//因此,某个函数返回的数组必须要是用new创建的
//间接寻址
//用int example[5],当看e的地址时会看到5个连续的2
//new会看到一个新的地址,这个新地址里有5个连续的2
class Entity
{
public:
int example[5];
// int* example = new int[5];
Entity()
{
for (int i = 0; i <5; i++)
example[i] = i;
}
};
int main()
{
Entity e;
return 0;
}
// C++ 字符串字面值:是在双引号之间的一串字符
// 字符串字面值只保存在内存的只读区域
char* name = "cherno"; // 这是未定义的行为
name[2] = 'p'; // name的类型实际是const (在常量区),不支持这样修改
char name[] = "cherno"; // 需要修改的话可以这样写
const char* name = "cherno"; // 否则
std::wstring name0 = L"cherno";
const char* name = "cherno";
const wchar_t* name1 = L"cherno";
const char16_t* name = u"cherno";
const char32_t* name = U"cherno";
// const 关键字
int const* a =
int* const a = new int; // 指针a是个常量
const int* a = new int; // (int* a)是个常量,即a的内容是个常量,不能给a赋值
// 由const修饰的类的方法,不对类进行任何修改
// 如果GetX不加const,在e.GetX()会报错,const Entity只能使用const方法
class Entity
{
private:
int x;
public:
int GetX() const
{
return x;
}
}
void PrintEntity(const Entity& e)
{
std::cout <
// mutable 允许函数是常量方法,但可以修改变量
// 修饰lambda表达式,值捕获时可以直接操作传入参数。(并非引用捕获,依旧值捕获,不修改原值)
class Entity
{
private:
int x;
mutable y;
public:
int GetX() const
{
y = 2;
return x;
}
}
// C++ 成员初始化列表
// 初始化成员列表的时候,要与成员变量声明时的顺序一致
// 会初始化Example两次,调用两个构造函数,使用初始化成员列表则不会出现这个问题
// 内置变量也不会出现这个问题
class Example
{
public:
Example()
{
std::cout <<"Created Example!" <
Example(int x)
{
std::cout <<"Created Example: " <
}
class Entity
{
private:
std::string m_Name;
Example m_Example;
public:
Entity()
{
m_Example = Example(8);
return x;
}
}
int main()
{
Entity e;
}
Day 9 (2022.5.19)
// 创建并初始化C++对象
// 在栈上创建的对象会在{}结束自动销毁
// 在堆上创建对象比在栈上创建慢,且必须手动释放被分配的内存
// 加不加括号是一样的,对于默认构造函数
Entity* e = new Entity;
Entity e;
// 创建一个对象数组
Entity* e = new Entity[50];
// C++ new 关键字
// new 不仅分配内存,还调用了构造函数
Entity* e = new Entity();
Entity* e = (Entity*)malloc(sizeof(Entity));
delete e;
// C++ 隐式转换和explicit关键字
// 隐式构造函数
// 在构造函数前加上explicit就不能够进行隐式转换
#include
#include
class Entity
{
private:
int m_Age;
std::string m_Name;
public:
Entity(int age) : m_Age(age) {}
Entity(std::string name) : m_Name(name) {}
};
void PrintEntity(const Entity& entity)
{
}
int main()
{
Entity e = 27;
PrintEntity(std::string("Xin"));
return 0;
}
// C++ 运算符及其重载
// 运算符其实就是函数
Vector2 Operator+(const Vector& other) const
{
return Vector2(x + other.x, y + other.y);
}
// cout 的重载,不在类里面重载
std::ostream& operator<<(std::ostream& stream, const Vector2& other)
{
stream <
}
// C++ this
// 通过 this 可以访问类成员函数
#include
#include
void PrintEntity(const Entity& e);
class Entity
{
public:
int x, y;
Entity(int x, int y)
{
this->x = x;
this->y = y;
PrintEntity(*this);
}
};
void PrintEntity(const Entity& entity)
{
}
Day 10 (2022.5.23)
// 栈作用域生存期
// 这样写是错误的,一旦返回,数组将被销毁
// 有2个解决方法,1,在堆上创建数组(new),2,传参
int* CreateArray()
{
int array[50];
return array;
}
// 作用域指针
// 它基本上是一个类(指针的包装器),在构造时用堆分配指针,析构时删除指针,其可以
// 自动化new和delete
// 仍想在堆上分配,但在作用域结束后自动删除
class Entity
{
};
class ScopedPtr
{
private:
Entity* m_Ptr
public:
ScopedPtr(Entity* ptr) : m_Ptr(ptr) {}
~ScopedPtr() {delete m_Ptr;}
}
int main()
{
{
// 在{}作用域结束后会自动销毁Eneity,因为ScopedPtr是在栈中分配的
ScopedPtr e = new Entity();
}
}
// C++ 的智能指针,它们可以避免你忘记使用delete
// unique_ptr 是作用域指针,超出作用域时,它会被自动销毁
// 不能够复制 unique_ptr,当你复制它的时候,相当于两个unique_ptr指向同一个区域,如果其中一个
// 死掉了,这块区域的内存会被释放,则另一个指针就指向了被释放的内存
#include
#include
class Entity
{
public:
Entity()
{
std::cout <<"Construct Entity!" <
~Entity()
{
std::cout <<"Destroyed Entity!" <
void Print() {}
};
int main()
{
{
// std::unique
// 出于异常安全的缘故,最好使用make_unique
// 当构造函数有异常的时候,不会得到一个没有引用的悬空指针,从而造成内存泄漏
std::unique
entity->Print();
}
}
// shared_prt 引用计数
// shared_ptr 需要分配另一块内存,叫做控制块,用来存储引用计数
// 因此,用new的话需要先后分配Entity和控制内存块的两次分配,比std::make_shared要慢
// 当把shared_prt 赋值给另一个shared_ptr会增加引用计数,然而,赋值给一个weak_ptr则不会
Day 11 (2022.5.24)
// C++ 的复制与拷贝构造函数
// 当我们 copy 这个类的时候,C++ 将它的所有类成员变量(这些成员变量组成了类(实例的内存空间))
// 这种是浅拷贝,它们共用m_Buffer,因此{}结束后,它被析构两次,所以报错
// String string = "Xin";
// String secOnd= string;
// 深拷贝是拷贝对象里面的所有成员
// 可以使用拷贝构造函数来进行深拷贝
// 默认的拷贝构造函数如下
// String(const String& other) : m_Buffer(other.m_Buffer), m_Size(other.m_Size) {}
// 如果不需要拷贝构造函数(不允许拷贝)
// String(const String& other) = delete;
// 尽量用 const xxx& 传递对象会效率更高
#include
class String
{
private:
char* m_Buffer;
unsigned int m_Size;
public:
String(const char* string)
{
m_Size = strlen(string);
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = 0;
}
~String()
{
delete[] m_Buffer;
}
String(const String& other)
: m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
}
char& operator[](unsigned int index)
{
return m_Buffer[index];
}
friend std::ostream& operator<<(std::ostream& stream, const String& string);
};
std::ostream& operator<<(std::ostream& stream, const String& string)
{
stream <
}
int main()
{
String string = "Xin";
String secOnd= string;
second[2] = 'm';
std::cout <
return 0;
}
Day 12 (2022.5.27)
// C++ 箭头操作符
#include
#include
class Entity
{
public:
int x;
public:
void Print() const { std::cout <<"Hello" <
class ScopedPtr
{
private:
Entity* m_Obj;
public:
ScopedPtr(Entity* entity)
: m_Obj(entity)
{
}
~ScopedPtr()
{
delete m_Obj;
}
Entity* operator->()
{
return m_Obj;
}
};
int main()
{
ScopedPtr entity = new Entity();
entity->Print();
std::cin.get();
return 0;
}
// 得到偏移量
// "指针->属性"访问属性的方法实际上是通过把指针的值和属性的偏移量相加,
// 得到属性的内存地址进而实现访问。
// 而把指针设为nullptr(0),然后->属性就等于0+属性偏移量。
// 编译器能知道你指定属性的偏移量是因为你把nullptr转换为类指针,
// 而这个类的结构你已经写出来了(float x,y,z),float4字节,
// 所以它在编译的时候就知道偏移量(0,4,8),所以无关对象是否创建
struct Vector3
{
float x, y, z;
}
int main()
{
int offset = (int)&(((Vector3*)nullptr)->y);
}
Day 13 (2022.5.29)
// C++ 动态数组 (std::vector)
int main()
{
std::vector
vertices.push_back({1, 2, 3});
vertices.push_back({1, 2, 3});
// cout的v是Vertex的copy,这样会多一个copy操作
for (Vertex v : vertices)
std::cout <
std::cout <
// c++ std::vector的使用优化策略
#include
#incldue
#include
strcut Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{}
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout <<"Copied!" <
}
int main()
{
// 这种情况会调用6次拷贝构造函数
// vertex的三次初始化是在main的内存中初始化的,把他们放入vertices中,有三次copy
// vertices增容3次,copy 3次
std::vector
vertices.push_back(vertex(1, 2, 3));
vertices.push_back(vertex(4, 5, 6));
vertices.push_back(vertex(7, 8, 9));
// std::vector
// 而且会构造三个vertex对象,所以会报错
// reserve 提前申请内存,避免动态申请开销
// emplace_back 不是传递我们已经构建的vertex对象,我们只是传递了
// 构造函数的参数列表,它会在vertices内存中构建对象
std::vector
vertices.reserve(3);
vertices.emplace_back(vertex(1, 2, 3));
vertices.emplace_back(vertex(4, 5, 6));
vertices.emplace_back(vertex(7, 8, 9));
std::cin.get();
}
// C++ 中使用库(静态链接)
// 我们将以二进制文件的形式进行链接,而不是或去实际依赖库的源代码并自己进行编译,
// 对于大多数严肃的项目,我推荐实际构建源代码(添加一个项目,该项目包含您的依赖库的源代码
// ,然后将其编译为静态或动态库),如果你拿不到源代码或者这只是一个快速项目,那么我倾向于
// 链接二进制文件,因为它会更快更容易。
// 今天我们只讨论处理二进制,确切的说,是GLFW库
// 典型的C++库的组织结构通常包含include(包含目录)和library(库目录)
// include是一堆我们需要使用的头文件,这样我们就可以实际使用预构建的二进制文件中的函数
// lib目录有那些预先构建的二进制文件,有动态库和静态库(并不是所有库都提供静态动态两种)
// 但GLFW提供了2种,因此我们可以选择静态链接还是动态链接
// 静态链接意味着这个库会被放在我的可执行文件中,在我的exe文件中,或者其它操作系统的可执
// 文件。而动态链接库是在运行时被链接的(程序运行时,装载动态链接库,在WindowsAPI中有个
// 叫loadLibrary的函数,会载入我的动态库,从中拉出函数,然后调用它,也可以在程序启动时
// 加载我的dll文件。
// 主要区别是,库文件是否被编译到exe或者链接到exe文件中,还是只是一个单独的文件,
// 在运行时你需要把它放在你的exe文件旁边或者某个地方,然后你的exe文件可以加载它。
// 静态链接更快,编译器或链接器可以在执行链接时优化,通常静态链接更好。
// include和lib文件都需要设置,对于编译器必须把它指向头文件(包含文件),这样就知道哪些
// 函数是可用的,然后就有了这些函数声明。然后还要将连接器指向库文件,这样就可以得到正确
// 的函数定义。
// lib-vc2015, lib-mingw 这些是不同编译器编译出来的版本(大概率任何一个版本都可以兼容,
// 只是尽量选择最接近的)
// lib-vc2015下有3个文件,glfw3.dll, glfw3.lib, glfw3dll.lib
// glfw3dll.lib实际上是一种静态库,它与dll是一起用的,这样我们就不需要实际询问dll
// 我们有一堆指向所有这些函数的函数指针吗,也就是说dll.lib实际上包含了.dll中所有
// 函数、符号的位置,所以我们可以在编译时链接他们
// C++创建一个动态链接库,编译后会生成两个可用的文件一个是lib文件一个是dll文件,
// 那么这个lib文件是干嘛的呢?
// 在使用动态库的时候,往往提供两个文件:一个引入库和一个DLL。引入库包含被DLL导
// 出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需
// 要链接引入库,DLL中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去
// 加载DLL,访问DLL中导出的函数。
// 1. Load-time Dynamic Linking 载入时动态链接
// 这种用法的前提是在编译之前已经明确知道要调用DLL中的哪几个函数,编译时在目标文
// 件中只保留必要的链接信息,而不含DLL函数的代码;当程序执行时,利用链接信息加载
// DLL函数代码并在内存中将其链接入调用程序的执行空间中,其主要目的是便于代码共享。
// 2. Run-time Dynamic Linking 运行时动态链接
// 这种方式是指在编译之前并不知道将会调用哪些DLL函数,完全是在运行过程中根据需要
// 决定应调用哪个函数,并用LoadLibrary和GetProcAddress动态获得DLL函数的入口地址。
// 关于用尖括号还是用引号的区别
// 实际上没有区别,弱国是引号的话,会先检查相对路径,如果没有找到任何相对于这个文
// 件的东西,它会检查编译器的include路径。我选这个的方法是,如果源文件在visual studio
// 中,GLFW.h 在我的解决方案中的某个地方,就用引号。如果完全是一个外部依赖,或外部的库
// 不在visual studio 中和我实际解决方案一起编译,那就用尖括号,表示它实际上是外部的
// 指定头文件目录
// 指定库(library)目录
// 指定相对库目录的库文件的名称
// 在当前文件写一个函数的声明,它会自动在lib文件夹中的所有文件找它的实现
// 即使没有include它的头文件(头文件通常只是用来声明的)
//
// 你自己声明一个c语言函数(不包含它的头文件,只想用这个函数),extern "C" int glfwInit();
Day 14 (2022.5.30)
// C++ 中使用动态库
// "静态的"动态库的版本,应用程序现场需要这个动态链接库,已经知道里面有什么函数,
// 可以使用什么。另一种版本是,想要任意加载这个动态库,甚至不知道里面有什么,但
// 想取出一些东西,或者想用它做很多事。
//
// 1、 _declspec(dllexport)与_declspec(dllimport)是相互呼应,只有在DLL内部用
// dllexport作了声明,才能在外部函数中用dllimport导入相关代码。
// 2、如果vs中的dll项目内部的函数前面都没有 _declspec(dllexport),则无法生成导
// 出库lib,只有dll生成。
// 3、lib库原理上应该是不做链接过程的,我在一个lib项目上,故意犯下链接错误,编译
// 并不报错。另外,我的一个lib项目,想把opencv的dll库包进来,成为一个包含了opencv
// 功能的lib库,是不行的,因为在这个lib项目中,在vs编译器中都找不到linker选项,再
// 次说明lib库不做链接。
// 4、回到cherno的问题,因为这个项目已经用了引导库lib,所以不需要_declspec(dll
// import),就已经能够使用dll库了。lib就是起到定位dll的作用,所以用不用这个
//_declspec(dllimport)都无所谓。但如果此项目不用lib引导库,那必须使用
// _declspec(dllimport)来定位dll中的导出函数。
//
//预处理器中设置GLFW_DLL就相当于在源文件里面添加了#define GLFW_DLL,同时由于作者是
// WIN32平台就会有_WIN32这个宏定义,(在GFLW头文件那里面是一串关于GLFWAPI的宏定义)
// 所以这种情况下就会将GLFWAPI定义为_declspec(dllimport),相当于在声明函数时加上了
// _declspec(dllimport),如果不加GLFW_DLL的宏定义就相当于啥都没有。作者就是想问声明
// 函数时无论加不加_declspec(dllimport),为什么都能正常运行,具体原因就是作者在链接
// 器配置里面已经附加了glfw3dll.lib所以加不加_declspec(dllimport)效果一样都能正常运
// 行,glfw3dll.lib的作用作者在前面有具体说明,当然推荐还是加上GLFW_DLL的定义(前面的
// 注释说了,如果你的应用程序要链接这个DLL最好确保加上这个GLFW_DLL定义)
// 将dll文件与exe放在一起
// C++ 创建与使用库
// 如何在Visual Studio 中建立多个项目,以及如何创建一个库让所有项目都能使用
// 一个解决方案下有两个项目
// 设置Game
// 设置Engine
// 为了在game中使用engine,需要设置game的property
// 把engine生成的lib作为链接器的输入,但我们不需要这么做
// Visual Studio可以为我们自动做这个
// Aplication.cpp (Game project)
#include "Engine.h"
#incldue
int main()
{
engine::PrintMessage();
return 0;
}
// Engine.h (Engine project)
#pragma once
namespace engine{
void PrintMessage();
}
// Engine.cpp (Engine project)
#include "Engine.h"
#include
namespace {
void PrintMessage()
{
std::cout <<"Hello World!" <
}
Day 15 (2022.6.1)
// C++ 模板
#include
#include
// 这不是一个实际的函数,只有我们调用这个函数的时候,基于传递的参数
// 这个函数才被创建出来,并作为源代码被编译
// 也可也用class T,一样
template
void Print(T value)
{
std::cout <
// 将类转化成一个模板?
template
class Array
{
private:
T m_Array[N];
public:
int GetSize() const { return N; }
};
int main()
{
Print(5);
Print
Print("555");
Array
std::cin.get();
return 0;
}
// C++ 堆与栈内存的比较
// 在栈上分配内存是按地址连续分配的
// new 关键字实际上调用了一个叫做malloc的函数,memory allocate,你的程序会跟踪一个叫做空闲
// 列表的东西,它会跟踪哪些内存块是空闲的。
// 在堆上分配内存是一堆事情(慢),而在栈上分配内存只需要一条cpu指令(快)
// C++ 宏
#include
#include
#define LOG(x) std::cout <
{
LOG("Hello");
std::cin.get();
}
// 在Debug下添加宏PR_DEBUG 到 Preprocessor Definitions
// 在Release就不会输出消息
// 反斜杠 \ 是换行符,反斜杠后必须直接换行
#include
#include
#ifdef PR_DEBUG
#define LOG(x) std::cout <
#define LOG(x)
#endif
int main()
{
LOG("Hello");
std::cin.get();
}
Day 16 (2022.6.2)
// C++ 静态数组 std::array
// 静态数组可以返回数组大小 size(),有边界检查(可以关掉)
// vector是在堆上创建的,array是在栈上创建的
// 方法1
#include
template
void Print(const std::array
{
for (int i = 0; i
template
void Print(const T& arr)
{
for (int i = 0; i
int main()
{
std::array
a[0] = 1;
a[1] = 2;
Print(a);
return 0;
}
Day 17 (2022.6.3)
Day 18 (2022.6.3) Day 19 (2022.6.6) Day 20 (2022.6.7) Day 20 (2022.6.9) Day 21 (2022.6.10) Day 22 (2022.6.12) Day 22 (2022.6.15) Day 23 (2022.6.18) Day 24 (2022.6.19) Day 25 (2022.6.21) Day 26 (2022.6.22) Day 27 (2022.6.25) Day 28 (2022.6.26) Day 29 (2022.6.27) Day 28 (2022.6.28) Day 29 (2022.6.29) -----------------------------------------------------------------------------------------------// 原始函数指针
// 将一个函数赋值给一个变量的方法
void HelloWorld()
{
std:: cout <<"Hello World!" <
int main()
{
// function是函数指针的名字,第二个括号是参数列表
// auto function = HelloWorld;
// 也可也这样写,HelloWorld是函数地址(隐式转换),相当于&HelloWorld
void(*function)() = HelloWorld;
// 使用typedef
typedef void(*HelloWorldFunction)();
HelloWorldFunction function = HelloWorld;
function();
}
#include
#include
void PrintValue(int value)
{
std::cout <<"Value: " <
void ForEach(const std::vector
{
for (int value : values)
func(value);
}
int main()
{
std::vector
ForEach(values, PrintValue);
return 0';
}
// lambda 本质上就是一个普通函数,只是它不像普通函数那样声明,像匿名函数
// [] 叫做捕获方式,也就是如何传入传出参数, () 参数列表,{} 函数体
ForEach(values, [](int value) {std::cout <<"value:" <// C++ lambda的使用
int a = 5;
auto func = [=]() mutable {a = 1; std::cout <<"a:" <// 如果要捕获变量,则函数参数要转换为
#include
void ForEach(const std::vector
#include
#include
#incldue
int main()
{
std::vector
auto it = std::find_if( values.begin(), values.end(), [](int value) { return value > 3;} );
std::cout <<*it <// using namespace
// 绝对不要在头文件中使用
// 如果同时用两个 using namespace, 可能会存在两个命名空间有同样的函数,这样调用就可能会出现问题// c++ 命名空间
namespace apple
{
namespace function
{
void print(string& str) {};
}
}
int main()
{
// using apple::function::print
// namespace a = apple::function;
using namespace apple;
using namepsace function;
}// C++ 线程
// join 在当前线程等待这个线程完成工作(阻塞当前线程,直到另一线程完成)
#include
#include
static bool s_Finished = false;
void DoWork()
{
using namespace std::literals::chrono_literals;
while (!s_Finished)
{
std::cout <<"Working...\n";
std::this_thread:sleep_for(1s);
}
}
int main()
{
std::thread worker(DoWork);
std::cin.get();
s_Finished = true;
worker.join();
std::cout <<"Finished." <
std::cin.get();
return 0;
}// c++ 计时
#include
#include
#include
struct Timer
{
std::chrono::time_point
std::chrono::duration
Timer()
{
start = std::chrono::high_resolution_clock::now();
}
~Timer()
{
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout <<"Timer took " <
};
void Function()
{
Timer timer;
for (int i = 0; i <100; i++);
}
int main()
{
/*using namespace std::literals::chrono_literals;
auto start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(1s);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration
std::cout <
std::cin.get();
return 0;
}// C++ 多维数组
// 多维数组就是指针的指针
// 分配一个5 * 5的二维数组,假如访问完第一个指针的5个整数后
// 开始访问第二个指针,这个时候存在缓存不命中(这两个指针指向的内存不连续)
// 会导致我们从ram中获取数据,速度比一维数组慢
// new int*[50] 是指有50个指针,每个指针都有50个整数
int main()
{
int** a2d = new int*[50];
for (int i = 0; i <50; i++)
a2d[i] = new int[50];
int*** a3d = new int**[50];
for (int i = 0; i <50; i++)
{
a3d[i] = new int*[50];
for (int j = 0; j <50; j++)
{
int** ptr = a3d[i];
a3d[i][j] = new int[50];
}
}
// 删除操作
for (int i = 0; i <50; i++)
{
delete[] a2d[i];
}
delete[] a2d;
}// C++ 排序
#include
#include
#include
#include
int main()
{
std::vector
// 如果返回true,a在b前面
std::sort(values.begin(), values.end(), [](int a, int b)
{
return a });
for (int value : values) std::cout <
return 0;
}// C++ 类型双关
// 假设一直使用整数类型,但现在需要把这段内存当作double或class类型
// 我们可以很容易的绕过类型系统
// 假设我们有一个类,我们想把它写成一个字节流,假设它是一个基本类型的结构,
// 并且没有指向内存中其它地方的指针,那么我们就可以重新解释整个结构或者类,
// 将它作为一个字节数组,然后写出来
int a = 50;
double value = *(double*)&a;
// 不想创建一个全新的变量,但这个操作是很危险的
// 如果赋值 value = 0.0,那么会对a的八个字节内存而不是
// 4个进行操作
int a = 50;
double& value = *(double*)&a;
#include
struct Entity
{
int x, y;
};
int main()
{
Entity e = {5, 8};
int* position = (int*)&e;
std::cout <
int y = *(int*)((char*)&e + 4);
std::cin.get();
}// C++ union(联合体)
// 通常人们用联合体来做的事情是和类型双关紧密联系的
// 通常union是匿名使用的,但是匿名union不能含有成员函数
#include
int main()
{
struct Union
{
// 如果它是匿名的,它只是一种数据结构,并没有添加任何东西
union
{
float a;
int b;
};
};
Union u;
u.a = 2.0f;
// 把2的字节当成float输出
std::cout <
return 0;
}
// ******************************************* //
#include
struct Vector2
{
float x, y;
};
struct Vector4
{
union
{
struct
{
float x, y, z, w;
};
struct
{
Vector2 a, b;
};
};
};
void PrintVector(const Vector2& vector)
{
std::cout <
int main()
{
Vector4 vector = { 1.0f, 2.0f, 3.0f, 4.0f };
PrintVector(vector.a);
PrintVector(vector.b);
std::cout <
return 0;
}// C++ 虚析构函数
// 类B派生于类A,我们想把类B引用为类A,但它实际上是类B
// 然后你想要删除它,你希望运行的是B的析构函数,而不是A的
//
// 普通方法标记为virtual,那么它就可以被覆写,
// 析构函数标记为virtual,则意味着加上一个析构函数
#include
class Base
{
public:
Base() { std::cout <<"Base constructor!\n"; }
~Base() { std::cout <<"Base destructor!\n"; }
void print() { std::cout <<"a"; }
};
class Derived : public Base
{
public:
Derived() { std::cout <<"Derived constructor!\n"; }
~Derived() { std::cout <<"Derived destructor!\n"; }
void print() { std::cout <<"b"; }
};
int main()
{
Base* base = new Base();
delete base;
std::cout <<"\n";
Derived* derived = new Derived();
derived->print();
delete derived;
std::cout <<"\n";
Base* poly = new Derived();
delete poly;
std::cin.get();
return 0;
}// C++ 类型转换
// C 和 C++ 两种风格转换
// C 风格
double value = 5.25;
int a = (int)value;
// C++ 风格
double value = 5.25;
int a = static_cast
// 主要有四种cast,他们不做任何C风格不能做的事情
// static_cast, reinterpret_cast, dynamic_cast, const_cast
// static_cast static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相
// 转换,不能用于指针类型的强制转换
// reinterpret_cast 用于进行各种不同类型的指针之间强制转换
// const_cast 仅用于进行去除 const 属性的转换
// dynamic_cast 不检查转换安全性,仅运行时检查,如果不能转换,返回null// C++ 条件与操作断点
// 操作断点可以在控制台输出你想要输出的东西,击中断点时不中断程序(可以选择是否继续)
// 条件断点,满足特定情况才输出// C++ 中的安全
// 智能指针本质上是围绕原始指针做了额外的辅助代码,以便自动化所有事情(本质是删除和释放内存)// C++ 预编译头文件
// 预编译的头文件实际上是让你抓取一堆头文件,并将他们转换成编译器可以使用的格式,
// 而不必一遍又一遍地读取这些头文件
// 不要将频繁改变的东西放入预编译头(如stl,windows.h)
// Visual Studio 通常将预编译头文件写成 stdafx.h,你自己建的话可以随便取名字// 对于C++来说,我们需要创建一个包含头文件的CPP文件
// 然后点击这个CPP文件的properties选择use precompiled headers
// 然后到整个项目的properties下选择ues
//// C++ dynamic_cast
// dynamic_cast 是专门用于沿继承层次结构进行的强制类型转换
// 用dynamic_cast 的类必须要有虚函数// C++ 基准测试
#include
#include
#include
class Timer
{
public:
Timer()
{
m_StartTimepoint = std::chrono::high_resolution_clock::now();
}
~Timer()
{
Stop();
}
void Stop()
{
auto endTimepoint = std::chrono::high_resolution_clock::now();
auto start = std::chrono::time_point_cast
auto end = std::chrono::time_point_cast
auto duration = end - start;
std::cout <
private:
std::chrono::time_point
};
int main()
{
int value = 0;
{
Timer timer;
for (int i = 0; i <1000000; i++)
value += 2;
}
std::cout <
}// C++ 结构化绑定(Structured bindings, C++17)
#include
#include
#include
struct Person
{
std::string Name;
int Age;
};
std::tuple
{
return { "Cherno", 24 };
}
Person CreatePersonS()
{
return{ "Cherno", 24 };
}
int main()
{
/*auto person = CreatePerson();
std::string& name = std::get<0>(person);
int age = std::get<1>(person);
std::string name;
int age;
std::tie(name, age) = CreatePerson();*/
auto p = CreatePersonS();
std::cout <
return 0;
}
#include
#include
#include
std::tuple
{
return { "Cherno", 24 };
}
int main()
{
auto[name, age] = CreatePersonS();
std::cout <
return 0;
}// C++ 如何处理Optional数据 (C++ 17)
// 三种不同的新类型(Optional是其中之一),可以处理那些可能存在,也可能不存在的数据
#include
#include
#include
std::optional
{
std::ifstream stream(filepath);
if (stream)
{
std::string result;
// read file
stream.close();
retrun result;
}
return{};
}
int main()
{
std::optional
if (data) // if (data.has_value())
{
// std::string& result = *data;
std::string result = data.value();
// 其中xxx作为默认值,如果存在数据返回数据,不存在返回xxx
// std::string result = data.value_or(xxx)
}
else{}
return 0;
}// C++ 单一变量存放多种类型的数据
// std::variant 允许你列出所有可能的类型,然后你可以决定它是什么
// data.index() 会告诉我们数据当前存储在哪个索引中
// 是检查的类型就返回其指针,否则返回空指针
// auto value = std::get_if
#include
#include
int main()
{
std::variant
data = "Cherno";
std::cout <
data = 1;
std::cout <
}// 如何在 C++ 单个变量中存储任意类型的数据 (std::any, C++ 17)
#include
#include
int main()
{
std::any data;
data = 2;
data = "Cherno";
// 如果不能这样转换会抛出异常
std::string str = std::any_cast
std::cin_get();
}// C++ 如何让C++运行的更快
// std::async & futures 这个感觉讲的太快了,还是自己搜教程吧