Cherno CPP学习笔记-04-高级特性

2024-04-08 16:20

本文主要是介绍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-高级特性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/886078

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件