本文主要是介绍Cherno CPP学习笔记-04-高级特性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.14、打渔!
P45、C++的复制与拷贝构造函数
一个好习惯:通过const 引用来传递对象
拷贝是指要求复制数据,即复制内存。
- 当我们想要把一个对象或原语或一段数据从一个地方复制到另一个地方时,我们实际上有两个副本。
- 拷贝需要时间,如果尽可能避免多余的拷贝(复制),可以提升性能。
class String
{
private:char* m_Ptr;unsigned int m_Size;
public:String(const char* s){m_Size = strlen(s);m_Ptr = new char[m_Size + 1];memcpy(m_Ptr, s, m_Size);m_Ptr[m_Size] = 0;}~String() {delete[] m_Ptr;}//深拷贝String(const String& other): m_Size(other.m_Size){m_Ptr = new char[m_Size + 1];memcpy(m_Ptr, other.m_Ptr, m_Size + 1);}char& operator[](int index){return m_Ptr[index];}friend std::ostream& operator<<(std::ostream& stream, const String& str);
};std::ostream& operator<<(std::ostream& stream, const String& str)
{stream << str.m_Ptr;return stream;
}int main() {String string = "Cherno";String second = string; second[2] = 'a'; std::cout << string << std::endl;std::cout << second << std::endl;std::cin.get();//浅拷贝的话会运行崩溃
}
C++会默认提供一个拷贝构造函数,像如下两种(效果一样)
String(const String& other):m_Ptr(other.m_Ptr), m_Size(other.m_Size){}String(const String& other)
{memcpy(this, &other, sizeof(String));
}
P46、C++的箭头操作符
重载一下:
class Entity
{
public:Entity() { std::cout << "Created Entity!" << std::endl; }~Entity() { std::cout << "Delete Entity!" << std::endl; }//这里的const 可以让const ScopePtr 返回 const Entity* 最后调用此方法void Print() const {std::cout << "Print" << std::endl; }
};
class ScopePtr
{
private:Entity* m_Ptr;
public:ScopePtr(Entity* entity) :m_Ptr(entity) {}~ScopePtr(){delete m_Ptr;}Entity* operator->() {return m_Ptr;}const Entity* operator->() const{return m_Ptr;}
};int main() {{ScopePtr entity = new Entity();entity->Print();const ScopePtr con = new Entity();con->Print();}std::cin.get();
}
利用->获取内存中某个值的偏移量:
- 当把数据序列化一串字节流,想要计算某些东西的偏移量时很有用。(处理字节流)
struct Vector3
{float x, y, z;
};int main()
{int offset = (int)&((Vector3*) nullptr)->y;std::cout << offset << std::endl; //4std::cin.get();
}
P47、C++的动态数组(std::vector)
没什么东西。
P48、C++的std::vector使用优化
问题:
class Vertex
{
public: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!" << std::endl;}};int main()
{std::vector<Vertex> vertexs;vertexs.push_back({ 1,2,3 });//1次copy,将main栈中copy到vertexs中vertexs.push_back({ 4,5,7 });//2次copy,扩容//等价上面的方法vertexs.push_back(Vertex(6, 6, 6));//3次copy,扩容//共copy次数为6次std::cin.get();
}
改进1、reserve:预留空间但不构造
std::vector<Vertex> vertexs;
vertexs.reserve(3);
vertexs.push_back({ 1,2,3 });//1次copy
vertexs.push_back({ 4,5,7 });//1次copy
//等价上面的方法
vertexs.push_back(Vertex(6, 6, 6));//1次copy
//共3次copy
改进2、emplace_back:直接传参数列表,免去main栈到vertexs过程的copy
std::vector<Vertex> vertexs;
vertexs.reserve(3);
//注意传参
vertexs.emplace_back(1,2,3);//0次copy
vertexs.emplace_back(4,5,7);//0次copy
vertexs.emplace_back(6, 6, 6);//0次copy
P49、C++中使用库(静态链接)
库通常包括两部分:includes和library
- include目录是一堆我们需要使用的头文件
- lib目录有那些预先构建的二进制文件
GLFW提供了动态库和静态库,可以选择动态链接 or 静态链接。
主要区别是库文件是否被编译/链接到 .exe文件中。
-
静态链接意味着这个库会被放到你的.exe可执行文件中,或其他操作系统下的可执行文件
-
动态链接库是在运行时链接的,在程序运行时你可以动态装载一些链接
- 如windows API中的loadLibrary【没见过】
- 你也可以在程序启动时加载 .dll文件,这就是动态链接库。
-
静态链接是在编译时发生的
-
动态链接是在运行时发生的
- 意味着只有当真正启动可执行文件时,动态链接库才会被加载
静态链接在技术上可以产生更快的应用程序,因为它允许更多的优化发生
- 链接时我们知道如何去只链接我们需要的函数。
- 动态库必须保持它的完整。
练手操作:
1、配置编译头文件:
glfw.org官网下载32-bit的链接库
在.sln文件所在文件夹下创建Dependencies文件夹
进入Dependencies文件夹,创建GLFW文件夹
将include和lib-vc2022复制到该文件夹(其实版本无所谓)lib-vc2022中glfw3.lib是和glfw3dll.lib一起使用的后者包含了前者所有的函数、符号的位置,所以可以在编译时链接它【不懂】
在vs项目属性->c/c++ -> general(常规)填写附加包含目录:$(SolutionDir)Dependencies\GLFW\include其中$(SolutionDir)是.sln文件所在目录的宏
然后就可以在项目中使用 #include "GLFW/glfw3.h"
#include<iostream>
#include "GLFW/glfw3.h"int main()
{int a = glfwInit(); //此时可以通过编译,但还没有链接std::cin.get();
}
继续:
2、配置linker
右键项目属性->链接器->输入
在附加依赖项中添加glfw3.lib;
在链接器->常规->附加目录库键入$(SolutionDir)Dependencies\GLFW\lib-vc2022
链接成功 a值为1
另一种使用方法:
#include<iostream>
//加extern "C" 的原因是glfw实际上是一个C语言库
//语言混用导致混淆名字(Name-mangling)【不懂】
extern "C" int glfwInit();int main()
{int a = glfwInit();std::cin.get();
}
P50、C++中使用动态库(动态链接)
有个小课后作业
右键项目属性->链接器->输入
在附加依赖项中添加glfw3dll.lib; //删掉原来的glfw3.lib;
//此时直接运行会报错缺少.dll
需要将glfw3.dll文件复制到HelloWorld\bin\Win32\Debug\下,即与HelloWorld.exe同目录
#include<iostream>
#include<GLFW/glfw3.h>int main()
{int a = glfwInit();std::cout << a << std::endl; //1std::cin.get();
}
P51、C++中创建与使用库(VisualStudio多项目)
新建一个解决方案Game,会默认创建一个项目Game
在解决方案下新建项目Engine,配置Engine属性->常规->配置类型为 静态库(.lib)可以直接选择配置所有配置所有平台
设置Game项目的属性->C/C++ ->常规附加包含目录$(SolutionDir)Engine\src;
右键Game项目添加引用,选择Engine
项目展示:
//Engine.h
#pragma once
namespace engine
{void PrintMessage();
}
//Engine.cpp
#include "Engine.h"
#include <iostream>
namespace engine
{void PrintMessage(){std::cout << "HelloWorld!" << std::endl;}
}
//Application.cpp
//#include "../../Engine/src/Engine.h"
#include "Engine.h"
#include<iostream>
int main()
{engine::PrintMessage();std::cin.get();
}
P52、C++中如何处理多返回值
- 返回结构体(推荐) or array、vector、pair、tuple
- array在栈上创建(更快),vector在堆上创建
- 传参指针or引用
std::tuple<std::string, std::string> function()
{string fs = "ss";string ts = "tt";return std::make_pair(fs,ts);
}auto source = function();
std::string fs = std::get<0>(source);
std::pair<std::string, std::string> function()
{string fs = "ss";string ts = "tt";return std::make_pair(fs,ts);
}auto source = function();
std::string fs = source.first;
//结构体 推荐
struct ShaderSource
{std::string VertexSource;std::string FregmentSource;
}
function()
{string fs = "ss";string ts = "tt";return {fs,ts};
}
1.15、摸鱼!
P53、C++的模板
C++的模板对标其他语言中的“泛型”。(C#、Java)
templete 允许你定义一个可以根据你的用途进行编译的模板,可以让编译器基于一套规则帮你写代码。
模板本身不存在,直到我们调用它,它才会去创建一个函数。
template<typename T>
//或者 template<class T>
void Print(T value)
{std::cout << value << std::endl;
}int main()
{Print<int>(5);Print(5.5f);Print("hello");std::cin.get();
}
MSVC编译器不会对你不使用的模板进行报错;Clang会报错。
模板会在编译期被评估处理,所以可以利用模板实现“动态大小”的栈数组;
template<int N>
class Array
{
private:int m_Array[N];
public:int GetSize() const { return N; }
};int main()
{Array<5> array;std::cout << array.GetSize() << std::endl;std::cin.get();
}
可以进一步添加typename,使类中成员类型可变,有点像“元编程”;
template<typename T, int N>
class Array
{
private:T m_Array[N];
public:int GetSize() const { return N; }
};int main()
{Array<int, 5> array;std::cout << array.GetSize() << std::endl;std::cin.get();
}
游戏引擎中的日志系统、材质系统可以使用模板。
P54、C++的堆与栈内存的比较
栈和堆都在内存RAM中,所处位置一样。
-
栈内存分配
- 移动栈指针,返回栈指针的地址。仅此而已。仅一条CPU指令
-
堆内存分配
- 当启动程序时,程序会得到一定物理大小的RAM,程序会维护一个空闲列表的东西,它跟踪哪些内存块是空闲的,以及它们在哪里。
- 当需要动态内存的时候,使用malloc请求堆内存,程序可以浏览空闲列表,找到满足要求的内存块,返回指针。
- 同时记录分配内存的大小,分配情况等。
- 如果请求的内存大小超过了空闲列表所拥有的,即超过了操作系统给程序的初始分配。
- 这个时候你的程序需要询问你的操作系统,嘿,我需要更多的内存
int main()
{int value = 5;int* hvalue = new int;int* harray = new int[5];harray[0] = 1;harray[1] = 2;harray[2] = 3;harray[3] = 4;harray[4] = 5;delete hvalue;delete[] harray;std::cin.get();
}
汇编源码:
; 8 : int value = 5;mov DWORD PTR _value$[ebp], 5; 9 : int* hvalue = new int;push 4call ??2@YAPAXI@Z ; operator newadd esp, 4mov DWORD PTR $T4[ebp], eaxmov eax, DWORD PTR $T4[ebp]mov DWORD PTR _hvalue$[ebp], eax; 10 :
; 11 : int* harray = new int[5];push 20 ; 00000014Hcall ??_U@YAPAXI@Z ; operator new[]add esp, 4mov DWORD PTR $T3[ebp], eaxmov eax, DWORD PTR $T3[ebp]mov DWORD PTR _harray$[ebp], eax; 12 : harray[0] = 1;mov eax, 4imul ecx, eax, 0mov edx, DWORD PTR _harray$[ebp]mov DWORD PTR [edx+ecx], 1; 13 : harray[1] = 2;mov eax, 4shl eax, 0mov ecx, DWORD PTR _harray$[ebp]mov DWORD PTR [ecx+eax], 2; 14 : harray[2] = 3;mov eax, 4shl eax, 1mov ecx, DWORD PTR _harray$[ebp]mov DWORD PTR [ecx+eax], 3; 15 : harray[3] = 4;mov eax, 4imul ecx, eax, 3mov edx, DWORD PTR _harray$[ebp]mov DWORD PTR [edx+ecx], 4; 16 : harray[4] = 5;mov eax, 4shl eax, 2mov ecx, DWORD PTR _harray$[ebp]mov DWORD PTR [ecx+eax], 5; 17 :
; 18 : delete hvalue;mov eax, DWORD PTR _hvalue$[ebp]mov DWORD PTR $T2[ebp], eaxpush 4mov ecx, DWORD PTR $T2[ebp]push ecxcall ??3@YAXPAXI@Z ; operator deleteadd esp, 8cmp DWORD PTR $T2[ebp], 0jne SHORT $LN3@mainmov DWORD PTR tv87[ebp], 0jmp SHORT $LN4@main
$LN3@main:mov DWORD PTR _hvalue$[ebp], 33059 ; 00008123Hmov edx, DWORD PTR _hvalue$[ebp]mov DWORD PTR tv87[ebp], edx
$LN4@main:; 19 : delete[] harray;mov eax, DWORD PTR _harray$[ebp]mov DWORD PTR $T1[ebp], eaxmov ecx, DWORD PTR $T1[ebp]push ecxcall ??_V@YAXPAX@Z ; operator delete[]add esp, 4cmp DWORD PTR $T1[ebp], 0jne SHORT $LN5@mainmov DWORD PTR tv90[ebp], 0jmp SHORT $LN6@main
$LN5@main:mov DWORD PTR _harray$[ebp], 33059 ; 00008123Hmov edx, DWORD PTR _harray$[ebp]mov DWORD PTR tv90[ebp], edx
$LN6@main:; 20 :
; 21 : std::cin.get();
P55、C++的宏
知识回顾:
- 当我们编译C++代码时,首先发生的事情是,预处理器会首先检查一遍所有以 # 开头的C++语句。
- # 是预编译指令
- 当预编译器评估完代码后,会把评估后的代码给到编译器,进行实际的编译。
在预处理阶段,我们可以控制什么代码会实际喂给编译器,这就是宏的用武之地。
我们能做的就是写一些宏,它将代码中的文本替换为其他东西。
- 基本上就像遍历我们的代码然后执行查找和替换。copy and paste
宏与模板发生的时间不同:
- 模板评估的时间更晚一些;
- 宏只是预处理器的纯文本替换,在编译之前没有什么是不能被替换的。
//一个习惯不太好的例子
#define WAIT std::cin.get()int main()
{WAIT;
}
#define LOG(x) std::cout<< x <<std::endlint main()
{LOG("hello");
}
一个有用的例子:在debug模式下输出LOG信息,release模式下屏蔽LOG
分别在项目属性->C/C++ ->预处理器的Debug和Release配置下设置预处理器定义PR_DEBUG;和PR_RELEASE;
#ifdef PR_DEBUG
#define LOG(x) std::cout<< x <<std::endl
#else
#define LOG(x)
#endif
int main()
{LOG("hello");
}//不过更推荐用#if PR_DEBUG == 1的方法(#if更安全?):需要设置预处理器定义PR_DEBUG=1;
//使用反斜杠\可以控制宏的换行
这篇关于Cherno CPP学习笔记-04-高级特性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!