Fork me on GitHub

拦截 View 触摸事件,判断滑动方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
anyView.setOnTouchListener(object : View.OnTouchListener {
private var initialX = 0f
private var initialY = 0f
private var hasScrolled = false

override fun onTouch(v: View?, event: MotionEvent?): Boolean {
event ?: return false
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 记录初始触摸位置
initialX = event.x
initialY = event.y
hasScrolled = false
}
MotionEvent.ACTION_MOVE -> {
if (!hasScrolled) {
val diffX = event.x - initialX
val diffY = event.y - initialY

// 判断滑动方向,确保是水平滑动
if (abs(diffX) > abs(diffY)) {
val direction = if (diffX < 0) Direction.LEFT else Direction.RIGHT

handleHorizontalScroll(direction)
hasScrolled = true
}
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
hasScrolled = false
}
}

return false
}

private fun handleHorizontalScroll(direction: Direction) {
when (direction) {
Direction.LEFT -> { // 向左滑动
showToast("发生向左滑动")
// 在这里处理向左滑动的逻辑
}
Direction.RIGHT -> { // 向右滑动
showToast("发生向右滑动")
// 在这里处理向右滑动的逻辑
}
}
}

private fun showToast(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}

enum class Direction {
LEFT, RIGHT
}
}

TextView layout_constrainedWidth 属性

layout_constrainedWidthConstraintLayout中的一个属性,用于在处理宽度约束时提供更多的灵活性和控制。当你使用ConstraintLayout构建复杂的布局时,可能会遇到某些视图在其内容过长时需要被约束宽度的情况,以避免它们超出屏幕边界或影响其他视图的布局。layout_constrainedWidth属性就是为了解决这类问题而设计的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<androidx.constraintlayout.widget.ConstraintLayout 		         xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/buttonView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_bias="0"
android:text="长文本示例长文本示例长文本示例长文本示例长文本示例长文本示例长文本示例长文本示例长文本示例" />

<Button
android:id="@+id/buttonView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintHorizontal_bias="0"
android:text="按钮" />

</androidx.constraintlayout.widget.ConstraintLayout>

C++ 第一课-万物皆地址

作者:朕小猫与GPT4

怎么理解C++万物皆地址

在 C++ 程序中,几乎所有元素(包括变量、对象、函数等)都可以通过它们的内存地址来引用或操作。这一点归根结底是因为 C++ 是一种底层的编程语言,它允许直接访问内存和底层硬件资源。这种能力带来了极高的灵活性和控制能力,但同时也要求程序员对程序的内存使用有深入的理解。下面是如何通过内存地址来引用或操作元素的几个方面:

  1. 变量和对象的内存地址

    在 C++ 中,每个变量或对象都占据内存中的一个或多个连续位置。你可以使用 & 操作符获得一个变量的内存地址,这个地址可以赋给一个指针变量。通过指针,你可以读取或修改存储在该地址的数据。这对于数组和自定义数据结构尤其有用,因为可以通过指针算术进行高效的遍历和访问。

  2. 函数的地址

    C++ 允许获取函数的地址,并将其赋给函数指针。这使得程序可以在运行时决定调用哪个函数,提高了代码的灵活性和可重用性。函数指针广泛应用于回调机制、事件驱动编程和接口设计中。

  3. 引用

    C++ 的引用机制实际上是一种特殊的语法糖,它在内部使用地址来实现。引用允许你创建一个变量的别名,通过别名可以直接访问和修改原始变量,而无需显式解引用。这样不仅代码更加简洁,而且避免了指针可能带来的错误。

  4. 动态内存分配

    C++ 通过 newdelete 操作符提供了动态内存分配的能力。使用 new 分配的内存位于堆区,它会返回指向分配内存的指针。通过这个指针,程序可以在运行时动态地分配、访问和释放内存。

通过上述机制,C++ 程序可以精确地控制内存的使用方式,从而优化程序性能和资源管理。然而,这也意呀着程序员必须对指针、内存地址以及内存管理有充分的理解,才能有效地利用这些特性编写高效、安全的代码。

编写教程时,目标是清晰、简洁且具有实用性。以下是一个关于“如何在 C++ 中使用指针和地址”教程的示例内容。这段内容旨在介绍初学者到 C++ 的指针和地址概念,帮助他们理解和运用“万物皆地址”的原则。


C++ 指针和地址入门

在 C++ 中,理解指针和地址是掌握语言的基石。本教程将引导你通过简单的步骤来理解和使用指针和地址。

1. 理解地址

在 C++ 中,每个变量都存储在内存的一个特定位置,这个位置可以通过地址来访问。你可以使用 & 操作符来获取变量的地址。

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;

int main() {
int var = 10;
cout << "变量的地址: " << &var << endl;
return 0;
}

这段代码会输出变量 var 的内存地址。

2. 使用指针

指针是存储另一个变量地址的变量。在 C++ 中,你可以使用 * 来声明指针。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
int var = 10;
int *ptr = &var;
cout << "ptr 指向的地址: " << ptr << endl;
cout << "ptr 指向的地址上的值: " << *ptr << endl;
return 0;
}

在这个例子中,ptr 是一个指向 var 地址的指针。通过 *ptr,我们可以访问这个地址上的值。

3. 指针的使用场景

指针在 C++ 中的应用非常广泛,包括:

  • 动态内存管理:使用 newdelete 操作符分配和释放内存。
  • 函数参数传递:通过传递指针或引用来修改函数外的变量。
  • 构建复杂数据结构:如链表、树等。

4. 练习:使用指针交换两个变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}

int main() {
int a = 5, b = 10;
swap(&a, &b);
cout << "a: " << a << ", b: " << b << endl;
return 0;
}

这个练习演示了如何使用指针来交换两个变量的值。

C++ 通过地址修改值

在 C++ 中,通过地址修改值是指针操作的一个基本用途。指针是一种特殊的变量,其值为另一个变量的内存地址。
通过指针,你可以直接访问和修改它指向的内存位置上存储的数据。
这一过程涉及几个关键步骤:获取变量的地址、使用指针指向该地址、通过指针修改该地址上的值。
下面通过一个简单的示例来说明这一过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int main() {
int value = 10; // 定义一个变量value并初始化为10
int *ptr = &value; // 定义一个指针ptr,将其初始化为value的地址

cout << "Original value: " << value << endl; // 显示原始的value值

*ptr = 20; // 通过指针ptr修改value的值

cout << "Modified value: " << value << endl; // 显示修改后的value值

return 0;
}

在上述代码中:

  1. 我们首先定义了一个类型为 int 的变量 value 并初始化为 10。
  2. 然后,我们定义了一个类型为 int* 的指针 ptr 并将其初始化为 value 的地址(&value)。
  3. 通过 cout 输出语句,我们可以看到 value 的原始值。
  4. 接着,我们通过指针 ptr 来修改 value 的值。这里的 *ptr = 20; 表示将 ptr 指向的内存位置(即 value 的位置)上的数据修改为 20。*ptr 是解引用操作符的应用,它获取指针指向的内存地址上存储的值。
  5. 最后,再次通过 cout 输出 value 的值,可以看到它已经被修改为 20。

函数传参的内存地址

在 C++ 中,理解函数传参的内存地址涉及到两个主要概念:按值传递(Pass by Value)和按引用传递(Pass by Reference)。理解这些概念有助于深入理解 C++ 如何在函数调用中处理参数的内存地址。

按值传递(Pass by Value)

当函数参数是按值传递时,函数接收的是实参的一个副本。这意味着函数内部对参数所做的任何修改都不会影响到原始数据。在内存层面,这个过程涉及到将原始数据的值复制到新的内存地址中。这个新的地址是函数参数在函数调用栈上的局部地址。

优点

  • 保护了原始数据,避免了意外修改。
  • 对于基本数据类型,这种方式简单且效率较高。

缺点

  • 对于大型结构或类实例,复制可能导致性能下降。
  • 无法在函数外部反映函数内部对数据的修改。

按引用传递(Pass by Reference)

按引用传递意味着函数接收的是实参的引用(或者说是内存地址)。这样,函数内部对参数的任何修改都会直接影响到原始数据。在内存层面,这避免了数据的复制,函数参数直接使用了实参的地址。

优点

  • 可以直接修改原始数据。
  • 避免了大型数据结构的复制,提高了效率。
  • 可以通过返回多个结果值(通过修改传入的引用或指针参数)。

缺点

  • 如果不希望修改原始数据,需要谨慎操作。
  • 使用不当可能导致错误或数据损坏。

举个例子

假设我们有一个简单的函数,目的是修改一个整数的值。

1
2
3
4
5
6
7
8
9
10
11
// 按值传递
void addTenByValue(int number) {
number += 10;
// 这里修改的是number的副本,外部的原始变量不受影响
}

// 按引用传递
void addTenByReference(int &number) {
number += 10;
// 这里直接修改的是传入变量的值,外部的原始变量也会被修改
}

在这个例子中,addTenByValue 函数无法修改外部变量的值,因为它仅操作了参数的副本。而 addTenByReference 函数则直接操作了实参的内存地址,因此它能够修改外部变量的值。

Android Game Project 核心 Renderer.cpp

Renderer 类图组成:

  • 类名:Renderer

  • 属性

    • EGLDisplay display_: 用于OpenGL ES渲染的显示设备。它是一个与本地显示系统相关联的EGL显示连接。
    • EGLSurface surface_: OpenGL ES渲染的表面。这是一个EGL表面,代表可以渲染OpenGL ES图形的绘图目标。
    • EGLContext context_: OpenGL ES渲染的上下文。它是一个封装了OpenGL ES状态机的EGL渲染上下文。
    • int width_: 渲染表面的宽度,以像素为单位。
    • int height_: 渲染表面的高度,以像素为单位。
    • bool shaderNeedsNewProjectionMatrix_: 一个标志,指示是否需要为着色器生成新的投影矩阵。当渲染表面的大小改变时,这个标志会被设置为true
    • std::unique_ptr shader_: 指向当前使用的Shader对象的智能指针。Shader对象用于编译、链接和使用顶点和片段着色器。
    • std::vector models_: 包含所有要渲染的模型的容器。每个Model对象包含顶点数据、索引数据和纹理数据。

    方法

    • 析构函数 ~Renderer(): 清理Renderer对象,包括释放EGL资源(如显示设备、渲染表面和上下文)。
    • void render(): 执行渲染循环的一次迭代。这包括更新渲染状态、绘制模型和交换渲染表面的缓冲区。
    • void initRenderer(): 初始化渲染器,包括设置EGL上下文、选择EGL配置、创建渲染表面和上下文、初始化OpenGL ES状态和加载着色器。
    • void updateRenderArea(): 更新渲染区域的大小。如果渲染表面的大小发生变化,此方法更新width_height_属性,并标记需要为着色器生成新的投影矩阵。
    • void createModels(): 创建演示模型。这个方法加载模型的顶点、索引和纹理数据,然后将模型添加到models_容器中。
    • void handleInput(): 处理输入事件,如触摸和按键。这个方法从Android的输入事件队列中读取事件,并根据需要响应这些事件。
  • 关联和依赖关系

    • 依赖于Shader类:由于shader_属性和在initRenderer()方法中对Shader的调用。
    • 依赖于VertexIndex类型:在createModels()方法中使用这些类型来创建模型。
    • 依赖于TextureAsset类:在createModels()方法中加载纹理资源。
    • 使用了标准库中的类型,如std::vectorstd::unique_ptr

这个类图还可以展示Renderer类如何与Android的本地应用粘合层(native_app_glue)和OpenGL ES 3.0交互,特别是如何处理EGL上下文、表面创建和渲染循环。

下面是一个简化的类图表示,重点在于Renderer类及其直接关系:

1
2
3
4
5
6
7
8
[Renderer] --|> [EGLDisplay]
[Renderer] --|> [EGLSurface]
[Renderer] --|> [EGLContext]
[Renderer] ---> [Shader]
[Renderer] --* [Model]
[Model] --* [Vertex]
[Model] --* [Index]
[Model] ---> [TextureAsset]

说明:

  • --|> 表示拥有或创建关系。
  • ---> 表示依赖关系。
  • --* 表示包含或集合关系。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#include "Renderer.h"

#include <game-activity/native_app_glue/android_native_app_glue.h>
#include <GLES3/gl3.h>
#include <memory>
#include <vector>
#include <android/imagedecoder.h>

#include "AndroidOut.h"
#include "Shader.h"
#include "Utility.h"
#include "TextureAsset.h"

// 宏定义,执行glGetString并将结果输出到logcat
#define PRINT_GL_STRING(s) {aout << #s": "<< glGetString(s) << std::endl;}

// 宏定义,如果glGetString返回一个空格分隔的列表,则将每个元素打印在新行上
/*!
* @brief if glGetString returns a space separated list of elements, prints each one on a new line
*
* This works by creating an istringstream of the input c-style string. Then that is used to create
* a vector -- each element of the vector is a new element in the input string. Finally a foreach
* loop consumes this and outputs it to logcat using @a aout
*/
#define PRINT_GL_STRING_AS_LIST(s) { \
std::istringstream extensionStream((const char *) glGetString(s));\
std::vector<std::string> extensionList(\
std::istream_iterator<std::string>{extensionStream},\
std::istream_iterator<std::string>());\
aout << #s":\n";\
for (auto& extension: extensionList) {\
aout << extension << "\n";\
}\
aout << std::endl;\
}

// 定义一种颜色,玉米花蓝色。可以直接发送给glClearColor函数。
#define CORNFLOWER_BLUE 100 / 255.f, 149 / 255.f, 237 / 255.f, 1

// 顶点着色器和片段着色器的代码,通常这些会从资源文件中加载
static const char *vertex = R"vertex(#version 300 es
in vec3 inPosition;
in vec2 inUV;

out vec2 fragUV;

uniform mat4 uProjection;

void main() {
fragUV = inUV;
gl_Position = uProjection * vec4(inPosition, 1.0);
}
)vertex";

static const char *fragment = R"fragment(#version 300 es
precision mediump float;

in vec2 fragUV;

uniform sampler2D uTexture;

out vec4 outColor;

void main() {
outColor = texture(uTexture, fragUV);
}
)fragment";


// 投影矩阵的半高度,这将给你一个从-2到2的高度为4的可渲染区域
static constexpr float kProjectionHalfHeight = 2.f;


// 投影矩阵的近平面距离。由于这是一个正交投影矩阵,负值便于排序(避免在0处的z-fighting)
static constexpr float kProjectionNearPlane = -1.f;

// 投影矩阵的远平面距离。与近平面等距的设置便于处理。
/*!
* The far plane distance for the projection matrix. Since this is an orthographic porjection
* matrix, it's convenient to have the far plane equidistant from 0 as the near plane.
*/
static constexpr float kProjectionFarPlane = 1.f;

// Renderer析构函数,处理EGL上下文的清理工作。
Renderer::~Renderer() {
if (display_ != EGL_NO_DISPLAY) {
eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (context_ != EGL_NO_CONTEXT) {
eglDestroyContext(display_, context_);
context_ = EGL_NO_CONTEXT;
}
if (surface_ != EGL_NO_SURFACE) {
eglDestroySurface(display_, surface_);
surface_ = EGL_NO_SURFACE;
}
eglTerminate(display_);
display_ = EGL_NO_DISPLAY;
}
}

// 渲染函数,包括渲染过程中的各种状态更新和绘制调用。
void Renderer::render() {
// Check to see if the surface has changed size. This is _necessary_ to do every frame when
// using immersive mode as you'll get no other notification that your renderable area has
// changed.
updateRenderArea();

// When the renderable area changes, the projection matrix has to also be updated. This is true
// even if you change from the sample orthographic projection matrix as your aspect ratio has
// likely changed.
if (shaderNeedsNewProjectionMatrix_) {
// a placeholder projection matrix allocated on the stack. Column-major memory layout
float projectionMatrix[16] = {0};

// build an orthographic projection matrix for 2d rendering
Utility::buildOrthographicMatrix(
projectionMatrix,
kProjectionHalfHeight,
float(width_) / height_,
kProjectionNearPlane,
kProjectionFarPlane);

// send the matrix to the shader
// Note: the shader must be active for this to work. Since we only have one shader for this
// demo, we can assume that it's active.
shader_->setProjectionMatrix(projectionMatrix);

// make sure the matrix isn't generated every frame
shaderNeedsNewProjectionMatrix_ = false;
}

// clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);

// Render all the models. There's no depth testing in this sample so they're accepted in the
// order provided. But the sample EGL setup requests a 24 bit depth buffer so you could
// configure it at the end of initRenderer
if (!models_.empty()) {
for (const auto &model: models_) {
shader_->drawModel(model);
}
}

// Present the rendered image. This is an implicit glFlush.
auto swapResult = eglSwapBuffers(display_, surface_);
assert(swapResult == EGL_TRUE);
}

// 初始化渲染器,设置EGL上下文和OpenGL状态。
void Renderer::initRenderer() {
// Choose your render attributes
constexpr
EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};

// The default display is probably what you want on Android
auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, nullptr, nullptr);

// figure out how many configs there are
EGLint numConfigs;
eglChooseConfig(display, attribs, nullptr, 0, &numConfigs);

// get the list of configurations
std::unique_ptr < EGLConfig[] > supportedConfigs(new EGLConfig[numConfigs]);
eglChooseConfig(display, attribs, supportedConfigs.get(), numConfigs, &numConfigs);

// Find a config we like.
// Could likely just grab the first if we don't care about anything else in the config.
// Otherwise hook in your own heuristic
auto config = *std::find_if(
supportedConfigs.get(),
supportedConfigs.get() + numConfigs,
[&display](const EGLConfig &config) {
EGLint red, green, blue, depth;
if (eglGetConfigAttrib(display, config, EGL_RED_SIZE, &red)
&& eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &green)
&& eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &blue)
&& eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &depth)) {

aout << "Found config with " << red << ", " << green << ", " << blue << ", "
<< depth << std::endl;
return red == 8 && green == 8 && blue == 8 && depth == 24;
}
return false;
});

aout << "Found " << numConfigs << " configs" << std::endl;
aout << "Chose " << config << std::endl;

// create the proper window surface
EGLint format;
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
EGLSurface surface = eglCreateWindowSurface(display, config, app_->window, nullptr);

// Create a GLES 3 context
EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
EGLContext context = eglCreateContext(display, config, nullptr, contextAttribs);

// get some window metrics
auto madeCurrent = eglMakeCurrent(display, surface, surface, context);
assert(madeCurrent);

display_ = display;
surface_ = surface;
context_ = context;

// make width and height invalid so it gets updated the first frame in @a updateRenderArea()
width_ = -1;
height_ = -1;

PRINT_GL_STRING(GL_VENDOR);
PRINT_GL_STRING(GL_RENDERER);
PRINT_GL_STRING(GL_VERSION);
PRINT_GL_STRING_AS_LIST(GL_EXTENSIONS);

shader_ = std::unique_ptr<Shader>(
Shader::loadShader(vertex, fragment, "inPosition", "inUV", "uProjection"));
assert(shader_);

// Note: there's only one shader in this demo, so I'll activate it here. For a more complex game
// you'll want to track the active shader and activate/deactivate it as necessary
shader_->activate();

// setup any other gl related global states
glClearColor(CORNFLOWER_BLUE);

// enable alpha globally for now, you probably don't want to do this in a game
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// get some demo models into memory
createModels();
}

// 更新渲染区域的大小,如果有变化,则更新视口和投影矩阵。
void Renderer::updateRenderArea() {
EGLint width;
eglQuerySurface(display_, surface_, EGL_WIDTH, &width);

EGLint height;
eglQuerySurface(display_, surface_, EGL_HEIGHT, &height);

if (width != width_ || height != height_) {
width_ = width;
height_ = height;
glViewport(0, 0, width, height);

// make sure that we lazily recreate the projection matrix before we render
shaderNeedsNewProjectionMatrix_ = true;
}
}

// 创建演示模型的函数。
/**
* @brief Create any demo models we want for this demo.
*/
void Renderer::createModels() {
/*
* This is a square:
* 0 --- 1
* | \ |
* | \ |
* | \ |
* 3 --- 2
*/
std::vector <Vertex> vertices = {
Vertex(Vector3{1, 1, 0}, Vector2{0, 0}), // 0
Vertex(Vector3{-1, 1, 0}, Vector2{1, 0}), // 1
Vertex(Vector3{-1, -1, 0}, Vector2{1, 1}), // 2
Vertex(Vector3{1, -1, 0}, Vector2{0, 1}) // 3
};
std::vector <Index> indices = {
0, 1, 2, 0, 2, 3
};

// loads an image and assigns it to the square.
//
// Note: there is no texture management in this sample, so if you reuse an image be careful not
// to load it repeatedly. Since you get a shared_ptr you can safely reuse it in many models.
auto assetManager = app_->activity->assetManager;
auto spAndroidRobotTexture = TextureAsset::loadAsset(assetManager, "android_robot.png");

// Create a model and put it in the back of the render list.
models_.emplace_back(vertices, indices, spAndroidRobotTexture);
}

// 处理输入事件的函数,如触摸和按键事件。
void Renderer::handleInput() {
// handle all queued inputs
auto *inputBuffer = android_app_swap_input_buffers(app_);
if (!inputBuffer) {
// no inputs yet.
return;
}

// handle motion events (motionEventsCounts can be 0).
for (auto i = 0; i < inputBuffer->motionEventsCount; i++) {
auto &motionEvent = inputBuffer->motionEvents[i];
auto action = motionEvent.action;

// Find the pointer index, mask and bitshift to turn it into a readable value.
auto pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
aout << "Pointer(s): ";

// get the x and y position of this event if it is not ACTION_MOVE.
auto &pointer = motionEvent.pointers[pointerIndex];
auto x = GameActivityPointerAxes_getX(&pointer);
auto y = GameActivityPointerAxes_getY(&pointer);

// determine the action type and process the event accordingly.
switch (action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
aout << "(" << pointer.id << ", " << x << ", " << y << ") "
<< "Pointer Down";
break;

case AMOTION_EVENT_ACTION_CANCEL:
// treat the CANCEL as an UP event: doing nothing in the app, except
// removing the pointer from the cache if pointers are locally saved.
// code pass through on purpose.
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_UP:
aout << "(" << pointer.id << ", " << x << ", " << y << ") "
<< "Pointer Up";
break;

case AMOTION_EVENT_ACTION_MOVE:
// There is no pointer index for ACTION_MOVE, only a snapshot of
// all active pointers; app needs to cache previous active pointers
// to figure out which ones are actually moved.
for (auto index = 0; index < motionEvent.pointerCount; index++) {
pointer = motionEvent.pointers[index];
x = GameActivityPointerAxes_getX(&pointer);
y = GameActivityPointerAxes_getY(&pointer);
aout << "(" << pointer.id << ", " << x << ", " << y << ")";

if (index != (motionEvent.pointerCount - 1)) aout << ",";
aout << " ";
}
aout << "Pointer Move";
break;
default:
aout << "Unknown MotionEvent Action: " << action;
}
aout << std::endl;
}
// clear the motion input count in this buffer for main thread to re-use.
android_app_clear_motion_events(inputBuffer);

// handle input key events.
for (auto i = 0; i < inputBuffer->keyEventsCount; i++) {
auto &keyEvent = inputBuffer->keyEvents[i];
aout << "Key: " << keyEvent.keyCode << " ";
switch (keyEvent.action) {
case AKEY_EVENT_ACTION_DOWN:
aout << "Key Down";
break;
case AKEY_EVENT_ACTION_UP:
aout << "Key Up";
break;
case AKEY_EVENT_ACTION_MULTIPLE:
// Deprecated since Android API level 29.
aout << "Multiple Key Actions";
break;
default:
aout << "Unknown KeyEvent Action: " << keyEvent.action;
}
aout << std::endl;
}
// clear the key input count too.
android_app_clear_key_events(inputBuffer);
}

C++ 函数指针与指针函数,C++11 新特性

作者:朕小猫与GPT4

C++函数指针

在 C++ 中,函数指针是指向函数的指针,允许通过指针调用函数。这种特性让程序在运行时能够选择要调用的函数,增加了代码的灵活性和动态性。理解函数指针对于高级编程技巧,如回调函数、命令模式等是非常重要的。

定义函数指针

函数指针的定义需要指定函数的返回类型、指针名称以及函数参数的类型。定义函数指针的基本语法如下:

1
返回类型 (*指针变量名)(参数类型列表);

例如,如果你有一个返回类型为 int 并接受两个 int 类型参数的函数,你可以这样定义一个指向该函数的指针:

1
int (*funcPtr)(int, int);

使用函数指针

一旦定义了函数指针,就可以将其指向具有相应签名的任何函数,然后通过该指针调用函数。这里有一个使用函数指针的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

// 定义一个简单的函数
int add(int x, int y) {
return x + y;
}

int main() {
// 定义一个函数指针并使其指向add函数
int (*funcPtr)(int, int) = add;

// 通过函数指针调用函数
int sum = funcPtr(2, 3);

std::cout << "The sum is: " << sum << std::endl;

return 0;
}

函数指针的用途

函数指针最常见的用途包括:

  • 回调函数:允许库或框架调用在使用库的代码中定义的函数。
  • 事件处理:在发生特定事件时动态决定调用哪个函数。
  • 接口实现:通过函数指针数组或结构体实现类似于面向对象编程中接口或虚函数的功能。
  • 命令模式:将操作封装为对象,允许存储、传递和调用操作。

高级话题

随着 C++11 的引入,现代 C++ 更倾向于使用 std::function 和 lambda 表达式来替代裸函数指针,因为它们提供了更高的灵活性和更简洁的语法。例如,std::function 可以存储并调用任何可调用的实体,包括普通函数、lambda 表达式、以及其他具有 operator() 的对象。

举例子,说明函数指针使用的几种场景

函数指针在 C++ 中的应用非常广泛,提供了编程的灵活性和动态性。以下是几种典型场景,展示了函数指针的使用:

1. 回调函数

回调函数是由用户编写的,但由系统或库在适当的时候调用的函数。函数指针允许用户提供具体的回调函数实现,使得库或框架可以在运行时调用用户定义的代码。

示例: 设计一个简单的事件处理器,当发生某个事件时,调用用户提供的回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <vector>

// 定义回调函数类型
using Callback = void(*)(int);

// 事件处理器类
class EventProcessor {
public:
void registerCallback(Callback cb) {
callbacks.push_back(cb);
}

void triggerEvent(int eventData) {
for (auto& cb : callbacks) {
cb(eventData); // 调用回调函数
}
}

private:
std::vector<Callback> callbacks; // 回调函数列表
};

// 用户定义的回调函数
void onEventTriggered(int data) {
std::cout << "Event triggered with data: " << data << std::endl;
}

int main() {
EventProcessor ep;
ep.registerCallback(onEventTriggered); // 注册回调函数
ep.triggerEvent(42); // 触发事件,调用回调函数
return 0;
}

2. 函数指针数组

函数指针数组可以存储指向不同函数的指针,使得程序可以基于运行时决策调用不同的函数。

示例: 创建一个简单的菜单系统,用户输入不同的选项执行不同的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

void function1() {
std::cout << "Executing Function 1" << std::endl;
}

void function2() {
std::cout << "Executing Function 2" << std::endl;
}

void function3() {
std::cout << "Executing Function 3" << std::endl;
}

int main() {
void(*functions[3])() = {function1, function2, function3};

int choice;
std::cout << "Enter a choice (0-2): ";
std::cin >> choice;

if (choice >= 0 && choice < 3) {
(*functions[choice])(); // 调用选中的函数
} else {
std::cout << "Invalid choice" << std::endl;
}

return 0;
}

3. 接口实现和策略模式

通过函数指针,可以模拟面向对象编程中的接口或策略模式,允许在运行时选择不同的算法或行为。

示例: 设计一个简单的排序策略,用户可以选择不同的排序算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <algorithm>
#include <vector>
#include <iostream>

using SortAlgorithm = void(*)(std::vector<int>&);

void ascendingSort(std::vector<int>& data) {
std::sort(data.begin(), data.end());
}

void descendingSort(std::vector<int>& data) {
std::sort(data.begin(), data.end(), std::greater<int>());
}

void sortData(std::vector<int>& data, SortAlgorithm algorithm) {
algorithm(data); // 使用指定的排序算法
}

int main() {
std::vector<int> data = {4, 2, 5, 1, 3};

sortData(data, ascendingSort);
for (int n : data) std::cout << n << " ";
std::cout << std::endl;

sortData(data, descendingSort);
for (int n : data) std::cout << n << " ";
std::cout << std::endl;

return 0;
}

这些示例展示了函数指针在回调、决策和策略选择等不同编程场景中的应用,证明了其为 C++ 编程带来的灵活性和动态性。

使用 std::function 和 lambda 表达式来替代裸函数指针,举例子

std::function 和 lambda 表达式在 C++11 及之后的版本中提供了一种更加灵活和方便的方式来处理可调用对象。它们使得代码更加简洁且易于理解,特别是在需要函数作为参数或回调时。

Lambda 表达式

Lambda 表达式提供了一种匿名函数的实现,允许你在需要函数对象的地方快速定义一个函数。Lambda 表达式的基本语法如下:

1
2
3
[捕获列表](参数列表) -> 返回类型 {
函数体
};

使用 std::function 和 Lambda 表达式

std::function 是一个模板类,它可以用来存储任何可以调用的目标,如普通函数、Lambda 表达式、成员函数和函数对象。这提供了比裸函数指针更大的灵活性和功能。

下面是一个示例,展示了如何使用 std::function 和 Lambda 表达式来替代裸函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <functional> // 必须包含这个头文件

// 定义一个普通的函数
int add(int x, int y) {
return x + y;
}

int main() {
// 使用 std::function 存储普通函数
std::function<int(int, int)> func = add;
std::cout << "The sum using a normal function: " << func(2, 3) << std::endl;

// 使用 std::function 存储 Lambda 表达式
std::function<int(int, int)> lambdaFunc = [](int x, int y) -> int {
return x + y;
};
std::cout << "The sum using a lambda expression: " << lambdaFunc(2, 3) << std::endl;

return 0;
}

在这个示例中,我们首先定义了一个普通的函数 add。然后,我们创建了两个 std::function 对象,一个用来存储普通函数 add,另一个用来存储一个实现相同功能的 Lambda 表达式。通过这种方式,我们可以看到 std::function 和 Lambda 表达式如何提供了一种更加灵活的方式来处理可调用对象,而不必直接使用函数指针。

优点

  • std::function 可以存储任何类型的可调用对象,提供了极大的灵活性。
  • Lambda 表达式使得定义和使用匿名函数变得简单,特别是在作为一次性使用函数或局部函数时非常有用。
  • 使用 std::function 和 Lambda 表达式可以使代码更加清晰和易于维护,特别是在复杂的项目中。

通过这种方式,C++11 提供的这些特性让处理函数和回调等场景变得更加优雅和高效。

C ++ 指针函数

在 C++ 中,术语“指针函数”通常被误解或使用不当。实际上,人们通常是指返回指针的函数,或者简称为“函数返回指针”。这种类型的函数在使用时非常有用,尤其是当你需要从函数中返回动态分配的内存或数组、多级指针、或者指向不同数据结构的指针时。

定义返回指针的函数

要定义一个返回指针的函数,你需要在函数声明的返回类型前加上*来指明返回的是指针。这里有一个简单的例子,说明如何定义和使用返回指针的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

// 定义一个返回int指针的函数
int* getIntPtr() {
int* ptr = new int(10); // 动态分配内存
return ptr; // 返回指向int的指针
}

int main() {
int* myPtr = getIntPtr(); // 调用函数,获取返回的指针
std::cout << "Value: " << *myPtr << std::endl; // 使用指针访问值
delete myPtr; // 释放分配的内存
return 0;
}

在这个例子中,getIntPtr函数分配了一个int类型的动态内存,并返回一个指向这块内存的指针。在main函数中,我们调用了getIntPtr,并使用返回的指针来访问和操作内存。最后,不要忘记使用delete释放动态分配的内存,以避免内存泄露。

注意事项

  • 返回局部变量的地址是危险的,因为当函数执行完毕后,局部变量的存储空间会被回收,这样返回的指针会指向一个不再有效的内存区域,导致未定义行为。
  • 当你的函数返回指向动态分配内存的指针时,确保在适当的时候释放这些内存,以避免内存泄露。
  • 考虑使用智能指针(如std::unique_ptrstd::shared_ptr)来管理动态分配的内存,这样可以自动管理内存的生命周期,减少内存泄露的风险。

返回指针的函数提供了一种灵活的方式来创建和管理动态资源,但同时也需要谨慎处理指针和内存管理相关的问题。

如何使用智能指针(如std::unique_ptr或std::shared_ptr)来管理动态分配的内存

在 C++ 中,智能指针是一种模板类,用于自动管理动态分配的内存,确保资源在不再需要时被正确释放,从而避免内存泄露。C++11 引入了两种主要的智能指针类型:std::unique_ptrstd::shared_ptr,它们分别用于独占所有权和共享所有权的场景。

使用 std::unique_ptr

std::unique_ptr 是一种独占所有权的智能指针,保证同一时间只有一个 std::unique_ptr 指向特定的资源。当 std::unique_ptr 被销毁或被重新分配时,它指向的对象也会被自动删除。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <memory>
#include <iostream>

class MyObject {
public:
MyObject() { std::cout << "MyObject created\n"; }
~MyObject() { std::cout << "MyObject destroyed\n"; }
void myMethod() { std::cout << "MyMethod called\n"; }
};

int main() {
std::unique_ptr<MyObject> myUniquePtr = std::make_unique<MyObject>();
myUniquePtr->myMethod(); // 使用->操作符调用成员函数

// 不需要手动删除对象,当unique_ptr离开作用域时,对象会被自动销毁
return 0;
}

使用 std::shared_ptr

std::shared_ptr 是一种共享所有权的智能指针,允许多个 std::shared_ptr 实例指向同一个对象。内部使用引用计数来跟踪有多少个 std::shared_ptr 指向同一个资源,当最后一个这样的指针被销毁时,所指向的对象也会被删除。

基本用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <memory>
#include <iostream>

class MyObject {
public:
MyObject() { std::cout << "MyObject created\n"; }
~MyObject() { std::cout << "MyObject destroyed\n"; }
void myMethod() { std::cout << "MyMethod called\n"; }
};

int main() {
std::shared_ptr<MyObject> mySharedPtr1 = std::make_shared<MyObject>();
{
std::shared_ptr<MyObject> mySharedPtr2 = mySharedPtr1; // 共享所有权
mySharedPtr2->myMethod();
// 当mySharedPtr2离开作用域时,对象不会被销毁,因为mySharedPtr1仍然存在
}
// 只有当最后一个指向对象的shared_ptr(这里是mySharedPtr1)离开作用域时,对象才会被销毁
return 0;
}

注意事项

  • 使用智能指针可以减少内存泄露的风险,但仍需要注意循环引用问题,尤其是在使用 std::shared_ptr 时。循环引用会阻止引用计数达到零,导致内存泄露。解决循环引用问题通常使用 std::weak_ptr
  • std::unique_ptr 通过移动语义实现所有权的转移,不能被复制。
  • std::shared_ptr 适用于资源需要被多个所有者共享的情况,但增加了额外的开销(引用计数管理)。

智能指针是现代 C++ 管理动态资源的首选方式,相比裸指针,它们提供了更安全、更简洁的资源管理机制。

Android Game Project 项目结构

author: 朕小猫-GPT4

图片显示的是一个典型的Android项目目录结构,这个项目中集成了C++原生代码。以下是各个组件的结构和作用的简述:

  • src
    • main
      • assets
        • android_robot.png 一个图像资产,可能用于应用的用户界面或游戏图形。
      • cpp
        • AndroidOut.cppAndroidOut.h:C++源文件和头文件,可能用于原生代码中的日志或输出目的。
        • CMakeLists.txt:CMake配置文件,CMake是用于管理原生代码编译的构建系统。
        • main.cpp:主要的C++源文件,可能包含原生代码执行的入口点。
        • Model.h:很可能定义了一个数据模型或对象的头文件。
        • Renderer.cppRenderer.h:渲染相关的源文件和头文件,或许处理屏幕上图形的绘制。
        • Shader.cppShader.h:与着色器程序相关的源文件和头文件,用于高级图形效果。
        • TextureAsset.cppTextureAsset.h:处理原生代码中纹理资产的源文件和头文件。
        • Utility.cppUtility.h:在原生代码库中使用的工具函数或类的源文件和头文件。
      • java
        • com.jason.game
          • MainActivity
      • res
        • AndroidManifest.xml
  • .gitignore:Git的配置文件,指定在版本控制中忽略哪些文件或目录。
  • build.gradle.kts:用Kotlin脚本编写的Gradle构建系统的构建配置文件,指定依赖和构建设置。
  • proguard-rules.pro:ProGuard的配置文件,ProGuard是一个用于代码缩减和混淆的工具,以防止应用发布构建的反向工程。

这个结构表明这是一个复杂的Android项目,它使用Java/Kotlin来实现Android特定功能,并使用C++来处理性能密集型任务,可能用于游戏开发或计算密集型应用。