前言:这篇文章是Cherno教学视频的笔记,看了这个系列的C++教程后,很多之前看代码时看不懂的和调试代码时解决不了的bug都豁然开朗,膜拜大佬啊
修改输出目录与中间目录:
添加插件:vsvim
unsolved external symbol
POD: plain old data
calling convention :函数调用约定 calling convention_安静平和的博客-CSDN博客
VC的编译环境默认是使用_cdecl调用约定,也可以在编译环境的Project Setting...菜单 -> C/C++ -> Code Generation项选择设置函数调用约定。也可以直接在函数声明前添加关键字__stdcall、__cdecl或__fastcall等单独确定函数的调用方式。在Windows系统上开发软件常用到WINAPI宏,它可以根据编译设置翻译成适当的函数调用约定,在WIN32中,它被定义为_stdcall。
class中一些调用约定(calling convention):
当我们看一个变量时要看两点:1、生命周期 2、作用域
reference 作为函数返回值, const int& function() 返回的reference 不能是函数内的临时变量。因为临时变量在函数结束后就会消失,因此返回的引用无效
outside class/struct :
inside class/struct :
class Entity
{
public:
static int x, y;
static void Print()
{
std::cout << x << "," << y << std::endl;
}
};
int Entity::x;
int Entity::y;
int main()
{
Entity::x = 1;
Entity::y = 2;
Entity::Print();
Entity::Print();
}
Singleton class单例类:
实现方式1
class Singleton
{
private:
static Singleton* s_Instance;
public:
static Singleton& Get() { return *s_Instance; }
void Hello() { std::cout << "Hello Singleton!" << std::endl; }
};
Singleton* Singleton::s_Instance = nullptr;
//the Singleton::s_Instance cannot be declared in the main scope, I do not know why
int main()
{
Singleton::Get().Hello();
}
实现方式2:
class SingletonStatic
{
public:
static SingletonStatic& Get()
{
static SingletonStatic* s_Instance;
return *s_Instance;
}
void Hello(){LOG("Hello SingletonStatic!") }
};
int main()
{
SingletonStatic::Get().Hello();
}
程序运行结果:
Hello SingletonStatic!
Hello Singleton!
1,2
1,2
[WARNING]: Hello!
(Polymorphism)
class EntityA
{
public:
virtual std::string GetName() { return "EntityA" ; }
};
class PlayerA : public EntityA
{
private:
std::string m_Name;
public:
PlayerA(const std::string& name)
:m_Name(name) {}
std::string GetName() override { return m_Name ; }
};
int main()
{
EntityA* ea = new EntityA();
std::cout << ea->GetName() << std::endl;
PlayerA* pa = new PlayerA("Cherno");
std::cout << pa->GetName() << std::endl;
EntityA* entitya = pa;
std::cout << entitya->GetName() << std::endl;
}
P28例子中将 EntityA 修改为
class EntityA
{
public:
virtual std::string GetName() = 0;//纯虚函数
};
Pure Virtual Function 要求子类(subclass)必须实现该函数(Pure Virtual Function force subclass implement it own definition for the function)
含有纯虚函数的类不能 实例化
In OOP programming it is quite commen for us to create a class that consists only of unimplemented methods and then force a subclass to actually implement them, this is something that ofen referred to as interface(c++抽象类)
Interface: A class that only consists of unimplemented methods ,it is not acutally possible to instantiate a interface class(抽象类)
private: Only* this class can access *A friend can acess also
protected: This class or subclass can access
数组大小必须在编译时定义,但是有个例外如下
int example[5]
//或者下面方法
static constexpr int size =5;
int example[size]
const int MAX_AGE = 90;
const int* a = new int;//const int* 这种定义下 指针所指向地址里的内容是const 不可改变的 可以改变指针 int const* a与之相同
*a = 2;//报错
a = (int*)&MAX_AGE;//不报错,指针可改变,新地址中也是const int类型
int* const a = new int;//int* const 这种定义下 指针是const的 不可改变指针 可以改变指针所指向地址的内容
*a = 2;//不报错,指针指向地址中的内容可以改变
a = (int*)&MAX_AGE;//报错,指针不可改变
//在类中使用const
class Entity
{
private:
int m_X, m_Y;
mutable int var;
public:
int GetX() const //正常情况下 在const 函数中不允许修改成员变量
{
var = 2; //由于var是mutable的 所以可以在const函数中修改
return m_X;
}
}
void PrintEntity(const Entity& e) //const引用作为形参
{
std::cout << e.GetX() << std::endl; //由于e是const的 则e.GetX()函数也必须是const 这样才能保证该引用不会修改e中的成员变量
}
s_Speed = s_Level >5 ? 10 : 5; //s_Level >5 则 s_Speed=10 否则 s_Speed=5
s_Speed = s_Level >5 ? s_Level > 10 ? 15 : 10 : 5; // 10 > s_Level >5 则 s_Speed=10 ,s_Level < 5 s_Speed=5 ,s_Level > 10 s_Speed=15 不建议如此使用
#include
#include
using String = std::string
class Entity
{
private:
m_Name;
public:
Entity() : m_Name("Unknown") {}
Entity(const String& name) : m_Name(name) {}
const String& GetName() { return m_Name; }
}
int main()
{
Entity* e;
Entity entity = Entity("Cherno);
Entity entity("Cherno");//这两种方式写法不同效果一样, 将entity建立再stack上 自动管理内存
e=&entity;//需不需要删除e????
Entity* entity = new Entity("Cherno"); //使用new生成的对象(object)建立在heap上,需要程序员手动删除
e=entity;
std::cout << entity->GetName() << std::endl;//类的指针使用->(arrow operator)使用类内方法
std::cout << (*eintiy).GetName() <
int main()
{
int* b = new int[50];//一个int 4 bytes int[50] 一共200 bytes
Entity* e = new Entity[50];//Entity 的大小 乘以 50
Entity* e = new Entity();//new 分配内参的同时调用默认构造函数
Entity* e = (Entity*)malloc(sizeof(Entity));//与new效果相同,但是不调用默认构造函数
delete e;//使用结束后要删除,delete 会调用析构函数(destructor)
free(e);//c函数,使用delete时会调用free(),free不会调用析构函数
delete[] b;//如果 new使用了[],则delete时也要加上[]
}
class Entity
{
private:
std::string m_Name;
int m_Age;
public:
Entity(const std::string& name)
: m_Name(name), m_Age(-1) {}
explicit Entity(int age)
: m_Name("Unknown"), m_Age(age) {}
int main()
{
Entity a = Entity("Cherno")
Entity a("Cherno");//推荐
Entity a = "Cherno";//implicit conversion OR implicit constructor
Entity b = Entity(22);
Entity b(22);//推荐
Entity b=22;//报错,因为构造函数已经是explicit的
}
class EntityB
{
private:
std::string m_Name;
int m_Age;
public:
EntityB(const std::string& name)
: m_Name(name), m_Age(-1) {}
explicit EntityB(int age)
: m_Name("Unknown"), m_Age(age) {}
};
struct Vector2
{
float x, y;
public:
Vector2(float x, float y)
: x(x), y(y) {}
Vector2 Add(const Vector2& other) const
{
return Vector2(x + other.x, y + other.y);
}
Vector2 Multiply(const Vector2& other) const
{
return Vector2(x * other.x, y * other.y);
}
Vector2 operator+(const Vector2& other) const
{
return Add(other);
}
Vector2 operator*(const Vector2& other) const
{
return Multiply(other);
}
bool operator==(const Vector2& other) const
{
return x == other.x && y == other.y;
}
bool operator!=(const Vector2& other) const
{
return !(*this == other);
}
};
std::ostream& operator<<(std::ostream& stream, const Vector2& other)
{
stream << other.x<< ',' << other.y << std::endl;
return stream;
}
int main()
{
Vector2 position(4.0f, 4.0f);
Vector2 speed(0.5f, 0.5f);
Vector2 powerup(1.1f, 1.1f);
Vector2 result1 = position.Add(speed.Multiply(powerup));
Vector2 result2 = position+(speed*(powerup));
std::cout << result2 << std::endl;
}
class EntityC
{
public:
int x, y;
EntityC(int x, int y)
{
this->x = x;
this->y = y;
}
int GetX() const
{
const EntityC& e = *this;
return e.x;
}
};
scope: {}curly bracket { 与 } 之间就是一个scope
SMART POINTERS in C++ (std::unique_ptr std::shared_ptr std::weak_ptr) P44
//我操了 妈个比 写完了没保存 有时间再写吧 气死我了 妈个比的 草草草草
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];//这个写法最后会显示乱码 因为字符串的结尾没有\0,因此无法正确的找到字符结束标志
memcpy(m_Buffer, string, m_Size);
}
friend std::ostream& operator<<(std::ostream& stream, const String& string);
};
std::ostream& operator<<(std::ostream& stream, const String& string)
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string("Cherno copy");
std::cout << string << std::endl;
}
修改成如下
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';//or = 0
}
friend std::ostream& operator<<(std::ostream& stream, const String& string);
};
std::ostream& operator<<(std::ostream& stream, const String& string)
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string("Cherno copy");
std::cout << string << std::endl;
}
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';//or = 0
}
~String()
{
delete m_Buffer;
}
friend std::ostream& operator<<(std::ostream& stream, const String& string);
};
std::ostream& operator<<(std::ostream& stream, const String& string)
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string("Cherno copy");
String second=string;//这里在程序运行结束后调用析构函数时会报错
//这种赋值方式是浅拷贝(shallow copy)second与string指向同一个内存地址,
//当程序结束时string已经调用析构函数将内存释放掉了,当second调用析构函数时该地址里已经是空的了因此会报错
//浅拷贝只将指针拷贝走了,因此scond与string的m_Buffer 指针值相同,也就是指针指向的地址相同
std::cout << string << std::endl;
std::cout << second << std::endl;
}
-> equal to (* ptr).
可以在类内重载->实现 class->
本来不想试视频里的代码了,但是! 这里大牛写了一个十分炫技的骚操作,忍不住记下来
//这段代码输出Vector3成员变量的偏移,x 0 y 4 z 8
class Vector3
{
public:
float x, y, z;
};
int main()
{
long offset = (long)&((Vector3*)nullptr)->y;
std::cout << offset << std::endl;
}
优化策略:直接将Vertex构造在vector(vertices)所在的内存中。
方法1:使用.resrve()
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
Vertex(const Vertex& other)//拷贝构造函数
:x(other.x), y(other.y), z(other.z)
{
std::cout << "Vertex Copied!" << std::endl;
}
};
int main()
{
std::vector vertices;
vertices.reserve(4);//假如已经知道了要添加多少变量,则可以提前规定好vector大小避免重复制
vertices.push_back( {1,2,3} );
vertices.push_back({ 4,5,6 });
vertices.push_back(Vertex(4, 5, 6));
vertices.push_back(Vertex(7, 8, 9));
std::cout << vertices[2].z << std::endl;
}
//这种方式不行,一样是10个copied
vertices.reserve(1);
vertices.push_back( {1,2,3} );
vertices.reserve(vertices.size()+1);
vertices.push_back({ 4,5,6 });
vertices.reserve(vertices.size()+1);
vertices.push_back(Vertex(4, 5, 6));
vertices.reserve(vertices.size()+1);
vertices.push_back(Vertex(7, 8, 9));
方法2:使用empalce_back()
struct Vertex
{
float x, y;
std::string z;
Vertex(float x, float y, std::string z)
: x(x), y(y), z(z)
{
}
Vertex(const Vertex& other)
:x(other.x), y(other.y), z(other.z)
{
std::cout << "Vertex Copied! " << z < vertices;
vertices.reserve(3);//注释掉后emplace_back也会调用拷贝构造函数
vertices.emplace_back(Vertex(1, 2, "test1"));//这种方式依然会调用拷贝构造函数
vertices.emplace_back(4, 5, "test2");
vertices.emplace_back(7, 8, "test3");
}
笔者使用cmake管理静态链接库,此处不赘述 可参考:《CMake实践》笔记一:PROJECT/MESSAGE/ADD_EXECUTABLE_Primeprime的专栏-CSDN博客
set(xxx_LIB_DIR ${CMAKE_SOURCE_DIR}/xxx/xxx)
target_link_directories(${xxx_LIB_DIR})
target_link_libraries(xxx xxx.lib)
set(xxx_LIB_DIR ${CMAKE_SOURCE_DIR}/xxx/xxx)
target_link_directories(${xxx_LIB_DIR})
target_link_libraries(xxx xxxdll.lib)
cmake 管理多工程项目 lib项目 又有exe项目 则内部调用lib时
内部lib直接写target name就OK,但是必须在同一个proj下
wsting wstring包含汉字,wcout如何输出_qq_24127015的博客-CSDN博客_wstring怎么输出
template
template does not really exist untill U call it
一个特殊的用处:get parameter at compile time
template
void PrintArray(std::array& data)
{
// for (size_t i = 0; i data;
data[0] = 0;
data[1] = 1;
data[2] = 2;
data[3] = 3;
data[4] = 4;
PrintArray(data);
stack and heap: 栈与堆 堆(heap)和栈(stack)有什么区别?? - 割肉机 - 博客园
这里说的栈与堆是操作系统中的概念与数据结构中的栈与堆不同。
heap:程序员操作,由malloc,memset之类函数分配的空间所在地。地址是由低向高增长的。 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收
stack:编译器自动操作,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。
std::array
template
void PrintArray(std::array& data)
{
// for (size_t i = 0; i data;
data[0] = 0;
data[1] = 1;
data[2] = 2;
data[3] = 3;
data[4] = 4;
PrintArray(data);
函数名是指针,既然是指针就可以作为形参(回调函数) 例:void (* FunctionPtr)(int,std::string)
void PrintValue(int value)
{
std::cout << "value:" << value << std::endl;
}
void ForEach(std::vector& values, void (*func)(int))
{
for (int value:values)
{
func(value);
}
}
int main()
{
std::vector values = { 1,2,3,4,5 };
ForEach(values, PrintValue);
}
[ ] capture group cppreference.com
void PrintValue(int value)
{
std::cout << "value:" << value << std::endl;
}
void ForEach(std::vector& values, void (*func)(int))
{
for (int value:values)
{
func(value);
}
}
int main()
{
std::vector values = { 1,2,3,4,5 };
ForEach(values, PrintValue);
//lambda 用法
ForEach(values, [](int value) {std::cout << "lambda:" << value << std::endl; });
}
Never Ever Use using namespace in header file (just in scope {} )
#include
static bool s_Finished = false;
void DoWork()
{
using namespace std::literals::chrono_literals;
std::cout << "Started thread ID = " << std::this_thread::get_id() << std::endl;
while (!s_Finished)
{
std::cout << "Working...\n" ;
std::this_thread::sleep_for(0.5s);//0.5s 是字面常量,C++可以定义字面常量
}
}
void DoWork1()
{
using namespace std::literals::chrono_literals;
std::cout << "Started thread ID = " << std::this_thread::get_id() << std::endl;
while (!s_Finished)
{
std::cout << "Another Working...\n" ;
std::this_thread::sleep_for(0.5s);
}
}
int main()
{
std::thread worker(DoWork);
//下面这个for循环会先执行 然后才会进入 worker 线程中for (size_t i = 0; i < 20; i++)
{
std::cout << "This is out of Therad\n";
}
std::thread worker1(DoWork1);
std::cin.get();
s_Finished = 1;
worker.join();//不加join() 退出程序时会报警 abort() 被调用
worker1.join();
std::cout << "Finished\n";
std::cout << "Started thread ID = " << std::this_thread::get_id() << std::endl;
}
包含头文件#include
方式1:直接使用
int main()
{
using namespace std::literals::chrono_literals;
auto start=std::chrono::high_resolution_clock::now();
//std::chrono::steady_clock::time_point start= std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(1s);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration duration = end - start;
std::cout << duration.count() << "s" << "\n";
}
方式2:利用类的构造函数与析构函数
struct Timer
{
//std::chrono::time_point start, end;
std::chrono::steady_clock::time_point start, end;
std::chrono::duration duration;
Timer()
{
start = std::chrono::high_resolution_clock::now();
}
~Timer()
{
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout << "Timer took " << 1000.0f * duration.count() << "ms\n";
}
};
void Function()
{
Timer timer;
for (size_t i = 0; i < 10; i++)
{
std::cout << "Hello chrono\n";
}
}
int main()
{
Function();
}
指向指针的指针 二维指针或者二维数组(int**)运行速度要比 一维指针速度慢很多(int*) 因为二维指针指向的地址在内存中不是连续的(指针在内存中是在连续地址存放的)要查询指针表才能知道指针指向哪里,可以使用一维指针来优化二维数组,详见下例 Timer 类 见上节
void a2d()
{
Timer timer;
int** a2d = new int* [5];
for (size_t i = 0; i < 5; i++)
{
a2d[i] = new int[5];
for (size_t j = 0; j < 5; j++)
{
a2d[i][j] = 2;
std::cout << a2d[i][j] << " ";
}
std::cout << "\n";
}
for (size_t i = 0; i < 5; i++)
{
delete[] a2d[i];
}
delete a2d;
}
void a2dIn1D()
{
Timer timer;
int* a2d1 = new int[5 * 5];
for (size_t i = 0; i < 5; i++)
{
for (size_t j = 0; j < 5; j++)
{
a2d1[i * 5 + j] = 1;
std::cout << a2d1[i * 5 + j] << " ";
}
std::cout << "\n";
}
}
int main()
{
a2d();
a2dIn1D();
Function();
}
结果: 速度提高一倍啊
#include
#include
int main()
{
std::vector values1 = { 3,5,6,2,4,1 };
std::sort(values1.begin(), values1.end(), std::greater());
for (int value : values1)
{
std::cout << value << "\n";
}
//使用lambda 代替greater()
std::sort(values1.begin(), values1.end(), [](int a, int b) {return a < b; });
for (int value : values1)
{
std::cout << value << "\n";
}
}
将一段地址强制转换为另一种类型地址,不推荐使用
int a=50;
double b=*(double*)&a
一个小例子:输出 2 3
struct EntityD
{
int x, y;
int* GetPosition()
{
return &x;
}
};
int main()
{
EntityD entityD = { 3,5 };
int* positionD = entityD.GetPosition();
positionD[0] = 2;
positionD[1] = 3;
std::cout << entityD.x << " " << entityD.y << "\n";
}
union中 不同变量名字占用 相同地址 例子略
基类中的析构函数最好要定义成为virtual ,这样可以允许在使用派生类实例(instance) 分配(assign)给 基类类型后,在删除该实例时调用 基类的 析构函数 Base* poly = new Derivate()
visual studio 骚操作
bababa...
PCH 将常用的 并且不会修改的 头文件放在同一个 文件当中,编译成二进制文件
balabala...
C++ 17新特性
tuple and pair : C++17 之 "结构绑定"_ding_yingzi的博客-CSDN博客