C++代码优化 Chapter2
本文将继续介绍一些常见的 C++ 代码优化技巧,本文为该系列的第二章,未来不定期更新。
非必要时使用常量引用在上一章我们介绍了在进行函数的参数传递时,尤其对于所占内存空间较大的参数的传递,为了避免拷贝所带来的不必要的性能开销,应当尽量使用引用传递。并且,当该函数不需要对传入的参数进行更改时,应当在参数类型前添加 const 关键字表示这是一个常量引用。
可能有朋友认为这里的 const 关键字的添加并不是必要的,但这样会带来两个问题:首先便是程序的可读性的下降,如果不添加 const 关键字,用户可能会认为该函数的变量是可更改的。其次它还将导致字面值将无法作为该函数的参数进行传递。所谓字面值,就是指代码中用数字或字符直接表示出来的常量(例如 1, "hello world"),这部分数据嵌入在程序中,当程序运行时被复制到内存的常量区,该区域为只读区域。示例如下:
int findChar(std::string& s, char target) {
for (int i = 0; i < s.size(); ++i) {
if ...
循环语句中指针赋值出错
最近在写人工智能作业的时候遇到了一点问题,就是在循环语句中对指针类型赋值出现错误,导致所有的结点的前驱指针最终指向自身。
问题描述以下使用一个简单的示例来模拟当时出现的问题。
MyStruct 为一个自定义结构体类型,包含数据成员 val 和前驱结点 pre。首先将初始结点(0,nullptr)加入队列 Q,随后在每次循环中,用变量 fs 接收队列 Q 的队首元素并将其出队,并根据该结点生成一个新结点,该新结点 val = fs.val + 1,且将其前驱结点设为 fs 并加入到队列 Q 中。直到 fs.val >= 5 时退出循环。
struct MyStruct
{
int val;
MyStruct* pre;
MyStruct(int v = 0, MyStruct* p = nullptr) : val(v), pre(p) {}
};
int main()
{
std::queue<MyStruct> Q;
Q.emplace(0, nullptr);
MyStruct target;
while ...
OpenGL入门 矩阵堆栈实现简单行星系统
本文介绍如何使用矩阵堆栈原理实现简单的行星运行系统。
原理有时我们需要在一个场景中绘制不同的模型,如果这些模型彼此间没有联系,即各模型的位置不会相互影响,那我们只需要单独为每个模型创建合适的变换矩阵,并经过渲染管线将其渲染即可。而对于一个位置会相互影响的系统而言,例如行星运行系统,地球围绕太阳公转,而月球围绕地球公转。处理这样问题的关键在于如何确定各物体变换矩阵,准确来说是模型-视图矩阵。而矩阵堆栈可以很好地将这问题简化。所谓矩阵堆栈,就一个用来存储变换矩阵的堆栈结构,栈顶矩阵为栈底矩阵乘上另一个矩阵变换而来,由此,栈底到栈顶形成一个逐步复杂的结构。通常来说,栈底的矩阵为视图矩阵,因为对于一个场景中的每个物体,它们都要经过视图矩阵的变换。逐步往上,由父物体的变换矩阵先入栈,利用栈顶矩阵作为该物体的模型-视图矩阵绘制物体后,再进入其子物体的管线,依次逐步进行。同时,对于不希望由父物体继承给子物体的变换矩阵可以在绘制完父物体后将其出栈。
实现代码实现对于本文所要研究的行星运行系统,共有三个物体:太阳、地球、月球,它们的依赖关系是:地球围绕太阳公转,而月球围绕地球公转。矩阵堆栈的变换情况如下 ...
子集生成算法
本文介绍生成一个集合子集的两种常见算法,借此从中深入理解搜索问题中常见的两种思路。
递归回溯思路对于集合中的每个元素,我们都有选择和不选择两种处理方式,这种思路类似于二叉树的遍历,每种情况都向下衍生出两种情况,最终当遍历到下标 index = nums.size() 时,将生成的子集保存。
由于此处我们使用一个数组的引用来保存子集元素,因此在递归回溯时,我们需要手动将上一步中加入添加的元素去除,来回溯到该元素未被选择的状态。
代码class Solution {
private:
std::vector<std::vector<int>> Sets;
void search(std::vector<int>& subset, std::vector<int>& nums, int index) {
if (index == nums.size()) {
Sets.emplace_back(subset);
return;
}
subset.emplace_back(nums[i ...
C++代码优化 Chapter1
本文将介绍一些常见的 C++ 代码优化技巧,本文为该系列的第一章,未来不定期更新。
使用前置递增(递减)运算符可能很多从 C 语言开始学习的朋友会对此感到困惑,觉得这两者在适用的情况下可以任意选择,如 for 循环语句写作 for (int i = 0; i < n; i++) 。这样做当然没错,但会造成一定程度上的资源浪费,因为后置递增运算符需要先将原本的变量值保存下来,再对其进行递增操作,而在此 for 循环中,我们并不需要使用原本的 i 值。并且 ++i 的写法相对来说更符合我们的意愿。
当然对于基本数据类型来讲,在经过编译器的优化之后,两者的效率可能并没有什么差别,但对于 STL 中的模板容器,或是自定义数据结构的迭代器来讲,前置运算符的效率显然要更高,因为单个对象所占的内存空间更大,使得拷贝暂存的开销也越大。
因此,除非必要情况,应该尽可能使用前置递增(递减)运算符。
使用引用传递引用类型变量并不是一个对象,它只是一个已存在对象的别名,因此在作为变量传递时不会经过拷贝构造的过程,能够显著地提升效率,以下为值传递的情况。
struct myStruct
{
...
C++复合类型的声明
声明语句的构成
在 C++ 中,一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成。
复合类型的声明定义多个变量对于基本数据类型变量的声明,声明符就是变量名。但对于复合类型来讲,例如指针类型和引用类型,变量名却只是声明符的一部分。例如 int *b; 需要注意的是, “*”运算符修饰的对象是变量名,而不是基本类型名。例如 int *a, b;,此处变量 b 的数据类型为 int,而非 int *。
指针的引用指针引用的正确定义形式如下:
int a = 1, * b = &a;
int*& c = b; // c为指针b的引用
int&* d = b; // 错误,d表示指向引用的指针,而引用本身并不是一个对象,无法使用指针进行指向
要想正确理解一个复杂的复合类型,可以从右向左进行阅读,离变量名最近的符号越接近该变量的真实类型,如上例中的引用符。
LeetCode周赛总结 第277场
本次周赛没想到比上周还要简单,前三题都可以用非常简单的方法快速解决,第四题如果想对了方向其实也比较简单。
元素计数题目链接元素计数
解题思路相当基础的题目,要同时具有一个严格较小元素和一个严格较大元素,只需要保证这个数 num 满足 num > minVal && num < maxVal即可。
解题代码class Solution {
public:
int countElements(vector<int>& nums) {
int minVal = INT32_MAX, maxVal = INT32_MIN;
for (int i = 0; i < nums.size(); i++) {
minVal = min(minVal, nums[i]);
maxVal = max(maxVal, nums[i]);
}
int res = 0;
for (int ...
使用OpenGL渲染一个立方体
本文将介绍如何使用最为常用的图形 API —— OpenGL 来渲染一个立方体,代码部分来自于《Computer Graphics Programming in OpenGL with C++》,并加入了自己的理解。
基本过程环境配置在编写程序之前,需要先配置好一些有助于程序编写的第三方库,本次实验需要用到的库有三个:用于窗口管理的 GLFW 库,扩展功能的 GLEW 库,以及用于数学运算的 GLM 库。
IDE 使用的是 Visual Studio 2019,并安装了 GLSL Language Integration 插件来实现 glsl 语言的代码高亮和自动补全。
具体的环境配置过程在此不过多赘述,本文主要聚焦于代码的实现。
窗口的创建要将渲染的图像显示出来,就需要创建一个特定的显示窗口,首先通过 glfwWindowHint() 指定 OpenGL 的版本号,再使用 glfwCreateWindow() 创建 GLFW 窗口。由于创建 GLFW 窗口并不会自动将它与当前 OpenGL 上下文关联起来,因此还需要调用 glfwMakeContextCurrent().
为了防止 ...
LeetCode周赛总结 第276场
本次周赛相对比较简单,前三题花的时间比较短,但无奈最后一题还是没思路。。。
将字符串拆分成若干长度为 k 的组题目链接将字符串拆分成若干长度为 k 的组
解题思路遍历字符串 s 的每个字符并加入到一个临时字符串中,当此临时字符串长度为 k 时,加入到结果数组中并清空此字符串。若此时遍历到字符串的最后一个字符且此时临时字符串长度没有达到 k 时,则向其末尾填入字符 fill 直到临时字符串长度达到 k,再加入到结果数组中。
解题代码class Solution {
public:
vector<string> divideString(string s, int k, char fill) {
vector<string> res;
string newStr;
for (int i = 0; i < s.size(); i++) {
newStr += s[i];
if (newStr.size() == k) {
...
2021年游戏总结 下
本文承接上文,继续聊聊 2021 年我所体验的一些游戏。
伊苏8:丹娜的陨涕日今年玩过最喜欢的 jrpg 作品,几乎满足了我对于一款优秀 jrpg 的全部幻想。
战斗方面爽快而又不失深度。伊苏系列向来以高速的即时制战斗著称,本作也不例外,角色与敌人的动作都相对较快,技能无冷却加之技能槽的回复速度很快,因此本作的战斗与大部分 jrpg 讲究策略的慢节奏战斗有很大不同。但这并不意味着本作战斗就十分无脑,弹反、极限闪避、弱点属性等机制的加入很好的提升了战斗系统的深度。
本作的剧情也很优秀。剧情悬念设置十分得当,给予玩家很强的故事驱动力,人物塑造极佳,进一步增强代入感。
本作最值得夸赞的还是地图探索方面的体验。本作采用的是一个非常典型的“银河恶魔城”式的地图设计,玩家需要探索地图的各个角落,从而解锁新的能力,再次进行更深一步的探索。同时本作探索不光局限于对于物品的获取,还包括了 npc 的发现,这一切与 jrpg 的强故事驱动相结合,使得探索欲望进一步上升。
最后讲讲我对于本作不满的地方。首先是本作的保卫战部分,这部分实属多余,不仅不有趣,还很大程度破坏了地图探索的连贯性与叙事节奏,纯粹 ...