2 创造你的物理世界

Nature of Code(Daniel Shiffman)是一本讲述怎样用Processing代码来模拟出我们周围的世界的书。

本文介绍其第一章的内容,使用Processing进行物理世界的抽象。

本文整理自http://natureofcode.com/book/

2.1创造物理世界 向量的世界

现在,我们将从基础的物理开始——苹果怎样从树上落下,钟摆怎样在空中摆动,地球环绕太阳运动等等。然而,所有的一切在编程实现时都会用到一个基础的概念——向量(Vector)。我们的故事就由此开始。

向量是一个同时拥有大小和方向的两(相对的是标量,标量只有大小)。

你可以把向量看成是两个点的差,想象你要告诉别人,怎么从一个地方走到另一个地方的时候。

向量通常用一个小箭头标示,箭头方向指示向量的方向,箭头的长度指示向量的大小。

说到这里,相信大家都知道我指的向量是什么东西了。按照书中的正常流程,我应该向大家介绍向量的加减乘除运算了,但是这种东西继续讲的话是在低估大家的智商,坚信大家在高中数学中或者高等数学中都已经学过了,天朝的教育你懂的(不懂的自行百度吧)。所以还是直奔主题吧。

2.1.1小弹球

在我们介绍有关向量的细节之前,让先我们来看几个例子,他们会为我们说明向量为什么这么有用。

Exp 1.1 小弹球(不使用向量)

float x = 100;

float y = 100;

float xspeed = 1;

float yspeed = 3.3;

void setup() {

size(640,360);

background(255);

}

void draw() {

background(255);

x = x + xspeed;

y = y + yspeed;

if ((x > width) || (x < 0)) {

xspeed = xspeed * -1;

}

if ((y > height) || (y < 0)) {

yspeed = yspeed * -1;

}

stroke(0);

fill(175);

ellipse(x,y,16,16);

}

在这个例子中,我们创造了一个非常简单的世界——一个小球在白画布上弹弹弹。

这个小球有一些属性:

位置    x和y

速度    xspeed和yspeed

我们能想象,在更复杂的sketch中,还可能用到:

加速度        xacceleration和yacceleration

目标位置    xtarget和ytarget

风力        xwind和ywind

……

清楚了吧?在我们构造的世界里面,几乎每个东西都有两个维度,我们需要两个变量。当然这只是在我们构造的二维世界里,如果是三维世界,我们需要三个变量,比如x,y,z,xspeed,yspeed,zspeed……

如果我们能将这两个或者三个变量简化成一个变量,是不是方便很多呢?

所以,我们将

float x;

float y;

float xspeed;

float yspeed;

改为:

Vector location;
Vector speed;

当然,我们使用向量不只是因为它能将多个变量集成到一个,还因为它能提供各种数学运算操作,让我们更方便的操控这些变量。

所以,在我们更进一步之前,我们先来看一下Processing为我们提供的一个向量类——PVector。

2.1.2PVector

PVector的结构可以简单描述为:

classPVector{
  float x;
  float y;
  PVector(float x_,float y_){
    x = x_;
    y = y_;
  }
}

使用一个PVector,要在全局中声明,并且在调用前实例化。如:

PVector p;
void setup(){
  p = new PVector(0,0);
}
void draw(){
    p.set(1, 1);
}

在之前的小球例子中,我们指定每一帧中,小球的位置都在横向和纵向上增加一定的量。

用物理的思维:

新位置 = 受到速度影响后的老位置

我们说向量是两个点之间的差,应用到这里来,速度就是一个向量,是新位置和老位置之间的差,位置也是可以一个向量,是原点到该点的差。

那么,顺理成章地:

float x =100;
float y =100;
float xspeed =1;
float yspeed =3.3;

变成了

PVector location =new PVector(100,100);
PVector velocity =new PVector(1,3.3);

有了这两个向量后,我们就可以在每帧中实现"新位置 = 受到速度影响后的老位置"的逻辑了:

x = x + xspeed;
y = y + yspeed;

将变成:

location = location + velocity;

然而,PVector中没有"+"这个操作符,而是提供add()方法来实现向量的加法。

voidadd(PVector v) {
y = y + v.y;
x = x + v.x;
}

我们要将这两个向量相加,要这样写:

location.add(velocity);

于是现在我们可以重写一下刚刚的小弹球程序了:

Exp 1.2 小弹球(使用向量!)

PVector location;

PVector velocity;

void setup() {

size(640,360);

location = new PVector(100,100);

velocity = new PVector(2.5,5);

}

void draw() {

background(255);

location.add(velocity);

if ((location.x > width) || (location.x < 0)) {

velocity.x *= -1;

}

if ((location.y > height) || (location.y < 0)) {

velocity.y *= -1;

}

stroke(0);

fill(175);

ellipse(location.x,location.y,16,16);

}

什么?你说这段代码明明变得更复杂了?(扶眼镜)好像是啊,阿拉不要在意这些细节啦~

是的,由于我们很多时候我们不能直接使用对象,必需要将向量的一方面的维度值取出来,,所以局部的代码会看起来很复杂(比如,例子中用到的画圆的ellipse函数,我们不能说"ellipse(location,16,16);",而是要说"ellipse(location.x,location.y,16,16);")。但是不要着急,从长远来看,PVector类为我们提供了更多的方法(接下来会介绍),可以让我们的代码变得更加简明可读。小弹球只是我们的第一步,向量的优势会渐渐显示出来。

下面是更多的PVector类的方法:

add() — 加

sub() — 减

mult() — 数乘

div() — 额,数除?

mag() — 取模

setMag() – 设置模。。。

normalize() — 单位化(就是变为该方向上的单位向量。。)

limit() — 限制模的大小

下面的高级用法请大家自己阅读

heading() — the 2D heading of a vector expressed as an angle

rotate() — rotate a 2D vector by an angle

lerp() — linear interpolate to another vector

dist() — the Euclidean distance between two vectors (considered as points)

angleBetween() — find the angle between two vectors

dot() — the dot product of two vectors

cross() — the cross product of two vectors (only relevant in three dimensions)

random2D() - make a random 2D vector

random3D() - make a random 3D vector

下面是几个使用示例帮助大家了解用法,对函数查询和使用方法比较了解的童鞋请酌情忽略之:

Exp 1.3 向量减法

void setup() {

size(640,360);

}

void draw() {

background(255);

PVector mouse = new PVector(mouseX,mouseY);

PVector center = new PVector(width/2,height/2);

PVector subtraction!

mouse.sub(center);

translate(width/2,height/2);

line(0,0,mouse.x,mouse.y);

}

Exp 1.4 向量数乘

void setup() {

size(640,360);

}

void draw() {

background(255);

PVector mouse = new PVector(mouseX,mouseY);

PVector center = new PVector(width/2,height/2);

mouse.sub(center);

mouse.mult(0.5);

translate(width/2,height/2);

line(0,0,mouse.x,mouse.y);

}

Exp 1.5 向量求模

void setup() {

size(640,360);

}

void draw() {

background(255);

PVector mouse = new PVector(mouseX,mouseY);

PVector center = new PVector(width/2,height/2);

mouse.sub(center);

float m = mouse.mag();

fill(0);

rect(0,0,m,10);

translate(width/2,height/2);

line(0,0,mouse.x,mouse.y);

}

Exp 1.6 向量单位化

void draw() {

background(255);

PVector mouse = new PVector(mouseX,mouseY);

PVector center = new PVector(width/2,height/2);

mouse.sub(center);

mouse.normalize();

mouse.mult(50);

translate(width/2,height/2);

line(0,0,mouse.x,mouse.y);

}

2.1.3Mover类

好了,对PVector类了解的够多了,现在我们要回到我们的物理世界来~

我们在小弹球中已经用PVector小试牛刀,我们了解到,屏幕上的一个物体有着它的位置和速度,位置说明了这一刻它将在哪个位置显示,速度说明了下一刻它的位置将改变多少。

每一帧中,位置向量将依据速度向量累加:

location.add(velocity);

然后,我们在该位置画出物体:

ellipse(location.x,location.y,16,16);

这是就我们的一个运动模式

1、将位置加上速度;

2、在该位置画出物体。

在小弹球的例子中,所有的行为都发生在Processing主tab中的setup函数和draw函数中。明显是一个面向过程的程序。现在我们要做的,就是将这个运动的小球(将它所有的运动逻辑)封装起来,成为一个类,一个表示运动的物体的类Mover。

为了构建这个类,我们要思考两个问题:

1、Mover类有什么数据成员?

2、Mover类有什么行为?

我们刚刚的总结帮我们回答了这两个问题。一个Mover类包含两个数据:位置和速度;而Mover的行为,则是更新位置(将位置加上速度),并且画出自己(在该位置画出物体)

classMover{
  PVector location;
  PVector velocity;
voidupdate() { 
}
voiddisplay() {
    stroke(0);
    fill(175);
    ellipse(location.x,location.y,16,16);
}
}

当然,由于成员变量里有PVector,我们需要在Mover类进行构造时也对它进行实例化。我们将这个操作写在Mover的构造函数里(这一次,我们为Mover设定一个随机的位置和速度):

Mover() {

location = new PVector(random(width),random(height));

velocity = new PVector(random(-2,2),random(-2,2));

}

最后,我们需要实现一个checkEdges函数,来检查我们的Mover是否到了画面的边缘,如果到了边上,我们要让它反弹回来:

void checkEdges() { 
if (location.x > width) {
      location.x = 0;
}

else if (location.x < 0) {
      location.x = width;
}
if (location.y > height) {
      location.y = 0;
}

else if (location.y < 0) {
      location.y = height;
}
}

至此,我们完成了我们的Mover类,那么怎么使用这个类呢?

首先,我们要在全局中声明一个Mover变量:

Mover mover;

然后在setup函数中将它实例化:

mover = new Mover();

最后在draw函数中调用它的几个方法:

mover.update();
mover.checkEdges();
mover.display();

Exp 1.7 运动(速度版)

Mover mover;

void setup() {

size(640,360);

Create Mover object.

mover = new Mover();

}

void draw() {

background(255);

mover.update();

mover.checkEdges();

mover.display();

}

class Mover {

PVector location;

PVector velocity;

Mover() {

location = new PVector(random(width),random(height));

velocity = new PVector(random(-2,2),random(-2,2));

}

void update() {

location.add(velocity);

}

void display() {

stroke(0);

fill(175);

ellipse(location.x,location.y,16,16);

}

void checkEdges() {

if (location.x > width) {

location.x = 0;

} else if (location.x < 0) {

location.x = width;

}

if (location.y > height) {

location.y = 0;

} else if (location.y < 0) {

location.y = height;

}

}

}

2.1.4加速度来了

好了。现在我们应该明白了向量是什么,还有我们怎么用它来实现一个运动Mover类。这真是令人激动的第一步,请大家为自己鼓掌(papapapa~)。但是在我们庆祝之前,我们还要再百尺竿头,更进一步,毕竟,只是看着一个小弹球看起来还是太无聊了——碰碰撞撞,无限循环,小球从不会加速或者减速,也不会转弯。为了有更有意思的效果,为了更加真实地模拟出真实世界,我们需要再给Mover类加一个向量——加速度acceleration。

很容易理解,加速度就是单位时间内速度的变化。很熟悉吧,加速度至于速度就好比速度之于位置。所以我直接把下面的代码甩给大家,大家也应该很容易理解了:

velocity.add(acceleration);
location.add(velocity);

所以我们要做的改动就是:

给Mover增加一个数据成员:

class Mover {
  PVector location;
  PVector velocity;
PVector acceleration;

在update函数里增加加速度的部分:

void update() { 
    velocity.add(acceleration);
    location.add(velocity);
}

快搞定了。现在就剩下在构造函数中实例化这些向量。

我们现在让小球一开始出现在画面中央,并且初始的速度为0(现在我们完全不用担心速度了,我们将它全然交给加速度来控制),那加速度呢?现在我们要尝试几种不同的加速度:

1、一个不变的加速度;

2、一个完全随机的加速度;

3、一个朝向鼠标的加速度。

定加速度

给Mover一个定价速度不怎么有趣,但是是最简单的方法。让我们从简单的开始。我们这次将加速度设为一个定值:

Mover() {
location = new PVector(width/2,height/2);
velocity = new PVector(0,0);
acceleration = new PVector(-0.001,0.01);
}

也许你会想"我去,这个加速度也太小了吧,要不要改大一点啊!"是,这个加速度是小,但是你要明白,加速度在速度上累加的频率是draw函数调用的频率,一般每秒10-30次(取决于这个sketch的帧频率frame rete,Processing用内置变量framerate来记录,你可以使用函数framerate()对它自定义),这样一累加,速度的值就很大了,速度过大出现的后果,就是小球还没到屏幕上来,就到屏幕外去了(请大家自行理清这句话的逻辑)。所以,不仅不能让加速度过大(会很快增大速度值),而且还要对速度值作一个限制。我们用limit函数来做到这一点:

velocity.limit(10);

这个函数要做的是:看看velocity的模有没有超过10。没有?那就好;如果超过了10,就将velocity减到10。

Exp 1.8 运动(速度+定加速度版)

class Mover {

PVector location;

PVector velocity;

PVector acceleration;

float topspeed;

Mover() {

location = new PVector(width/2,height/2);

velocity = new PVector(0,0);

acceleration = new PVector(-0.001,0.01);

topspeed = 10;

}

void update() {

velocity.add(acceleration);

velocity.limit(topspeed);

location.add(velocity);

}

//display() is the same.

void display() {}

//checkEdges() is the same.

void checkEdges() {}

}

大家将主tab补充完整,运行一下看看。。。

随机加速度

现在我们来看第二种加速度——完全随机的加速度。也就是说,我们每次(比如,在update函数中)都要选一个随机的加速度。

voidupdate() { 
acceleration = PVector.random2D();
    velocity.add(acceleration);
    velocity.limit(topspeed);
    location.add(velocity);
}

这里使用的random2D函数是返回一个长度为1,并且指向一个随机的方向的向量。

由于random2D给我们的是一个单位向量,所以我们还要试着给它赋一个模值:

(1)赋一个固定的模值

acceleration = PVector.random2D(); 
acceleration.mult(0.5);

(2)赋一个随机的模值

acceleration = PVector.random2D(); 
acceleration.mult(random(3));

这里要提到,加速度并不是代表速度的增加和减少,它代表的是速度在大小方向上的变化。这也许显而易见,但是认识到这一点确实很关键的。

Exp 1.9 运动(速度+随机加速度版)

voidupdate() {

acceleration = PVector.random2D();

velocity.add(acceleration);

velocity.limit(topspeed);

location.add(velocity);

}

静态方法和非静态方法

在我们尝试第三种加速度(指向鼠标的加速度)之前,我们要再了解一个关于PVector的重要的东西——静态(static)方法和非静态(non-static)方法。

先不看向量,让我们先看看这组代码:

float x = 0;

float y = 5;

x = x + y;

很简单吧?x开始为0,然后我们将y加到它上面,所以现在它应该是5。对应到向量上,我们也能很轻松地用我们刚刚的知识实现这种逻辑:

PVector v = new PVector(0,0);

PVector u = new PVector(4,5);

v.add(u);

向量v值为(0, 0),然后我们把u加到它上面去了,它就成了(4, 5),so easy对吧?

那么来看下一组例子:

float x = 0;

float y = 5;

float z = x + y;

x一开始为0,我们将它和y相加,然后把结果存在一个新变量z里面。注意X的值没有改变(y也没有)!这看起来是个不足为提的问题,非常显而易见,但是,我们转到PVector试试:

PVector v = new PVector(0,0);

PVector u = new PVector(4,5);

PVector w = v.add(u);

糟糕。。我们的add并不支持这种用法,来看看这个add函数的定义:

void add(PVector v) {

x = x + v.x;

y = y + v.y;

}

可以看到,这里的add函数是个无返回值的void函数,并且它会改变去访问这个函数的PVector的x和y值,它不能帮助我们达成目的。

为了将两个PVector相加,并且返回一个新的PVector,我们必须使用它的兄弟——静态的add方法。

使用类名调用的方法叫做静态方法(static function),相对的,使用对象名调用的方法则是非静态方法(non-static function)。举个例子:

PVector.add(v,u); //这就是一个静态方法

v.add(u); //这是一个非静态方法

静态的add方法的定义是这样的:

static PVector add(PVector v1, PVector v2) { 
 PVector v3 = new PVector(v1.x + v2.x, v1.y + v2.y);
return v3;
}

它与它的兄弟不同的地方在于:

1、在定义的开始有一个static关键字;

2、返回值不是void,而是一个PVector;

3、它创建一个新的PVector来保存要相加的两个向量的和,最后将它返回。

当你调用静态函数的时候,你不用使用一个具体的对象(如某个向量u,v或者w),而是直接使用类名调用:

PVector v = new PVector(0,0);
PVector u = new PVector(4,5);
PVector w = v.add(u);
PVector w = PVector.add(v,u);

PVector类中,add(),sub(),mult()和div()都有其对应的静态函数。

交互式加速度

最后我们要来完成一个稍微复杂一点点,但是好玩得多的效果:我们要根据鼠标的位置,动态地计算Mover的加速度——指向鼠标的加速度。

每当我们要考虑加速度的时候,我们都要考虑两方面的内容:加速度的大小和加速度的方向。

让我们从方向开始。

显然,加速度应该从Mover的位置指向鼠标的位置。我们假定Mover的位置是在点(x,y),而鼠标的位置是在(mouseX,mouseY)。

dx = mouseX - x

dy = mouseY - y

我们用PVector将这点表示出来:

PVector mouse = new PVector(mouseX,mouseY); 
PVector dir = PVector.sub(mouse,location);

现在我们就得到了一个从Mover的位置指向鼠标的位置的向量dir。但是如果我们现在就使用这个向量作为Mover的新加速度,那么Mover会立即(在下一帧)跑到鼠标的位置。这不是我们想要的效果。我们需要来确定一下这个加速度的大小。

为了设置我们的加速度大小,我们需要将这个dir向量 ? 一下。你说对了,"单位化"。我们将这个向量单位化,然后再乘上我们想要的值,就得到我们想要的大小的向量了,当然,这个向量也是我们想要的方向。

float anything = ?????
dir.normalize();
dir.mult(anything);

总结一下,我们经历了这四个步骤:

1、得到一个从物体位置指向目标位置的向量;

2、单位化这个向量;

3、乘以某个值,让它变成我们想要的长度;

4、将这个向量赋给加速度。

Exp 1.10 运动(前进吧!向着鼠标)

void update() {

PVector mouse = new PVector(mouseX,mouseY);

//Step 1: Compute direction

PVector dir = PVector.sub(mouse,location);

//Step 2: Normalize

dir.normalize();

//Step 3: Scale

dir.mult(0.5);

//Step 4: Accelerate

acceleration = dir;

velocity.add(acceleration);

velocity.limit(topspeed);

location.add(velocity);

}

大功告成,赶紧运行试试吧!

2.1.5来一堆小球怎么样?

所谓生命不息,创新不止。如果你觉得一个小球也无聊了,那么一堆小球怎么样?我们辛辛苦苦构建了一个Mover class,如果只使用一个Mover对象,着实有点屈才了。

下面这个程序用array创建了一大群小球:

Exp 1.11 向着鼠标运动的mover数组

Mover[] movers = new Mover[20];
void setup() {
  size(640,360);
  background(255);
for (int i = 0; i < movers.length; i++) { 
movers[i] = new Mover();
  }
}
void draw() {
  background(255);

for (int i = 0; i < movers.length; i++) {

    movers[i].update();
    movers[i].checkEdges();
    movers[i].display();
}
}
class Mover { 
  PVector location;
  PVector velocity;
  PVector acceleration;
float topspeed;
  Mover() {
    location = new PVector(random(width),random(height));
    velocity = new PVector(0,0);
    topspeed = 4;
}
void update() {
    PVector mouse = new PVector(mouseX,mouseY);
    PVector dir = PVector.sub(mouse,location);
    dir.normalize();
    dir.mult(0.5);
    acceleration = dir;
    velocity.add(acceleration);
    velocity.limit(topspeed);
    location.add(velocity);
}
void display() {
    stroke(0);
    fill(175);
    ellipse(location.x,location.y,16,16);
}
void checkEdges() {
    if (location.x > width) {
      location.x = 0;
}

    else if (location.x < 0) {
      location.x = width;
}
if (location.y > height) {
      location.y = 0;
}    else if (location.y < 0) {
      location.y = height;
}
}
}

Reference

1 Daniel Shiffman, THE NATURE OF CODE, http://natureofcode.com/book/chapter-1-vectors, 2012

转载于:https://www.cnblogs.com/mysunnytime/p/3441088.html

2 创造你的物理世界(1)相关推荐

  1. 盘点:2021年度物理学十大突破|《物理世界》

    来源:物理世界 作者:哈米什·约翰斯顿(Hamish Johnston) 译者:王晓涛.乔琦 2021年12月14日,<物理世界>(Physics World)编辑从其网站发表的近600项 ...

  2. 智能工厂4.0:数字世界和物理世界的融合【附下载】

    来源:专知 概要:自动化与控制发展至今,智能工厂逐渐获得关注,并成为制造企业追求的目标. 自动化与控制发展至今,智能工厂逐渐获得关注,并成为制造企业追求的目标.何为智能工厂?它应该是一个柔性系统,能够 ...

  3. 人眼感知到的颜色与真实物理世界的颜色有什么区别?

    ===========2014.08.17补充========== 1.人眼看到的颜色与可见光谱上的频率是怎样对应的?混和颜色可以代替单一颜色(频率的光)吗? 2.如何判断不同人眼中的同一颜色(频率的 ...

  4. 从物理世界到数字世界,陶闯的边界与跨界

    编者按: 东海之滨,浦江之畔,一代代浦东科技先锋人物为中国科技产业做出了杰出的贡献.他们或是行业引领者,或在细分领域中独占鳌头,且都以创新服务社会.促进经济发展为己任.他们的故事就是中国科创的缩影.为 ...

  5. 陶闯博士超级对话:互联网的终极进化——从物理世界到数实世界

    陶闯博士,维智科技创始人兼董事长,PGVerse 维享时空创始人,前微软虚拟地球部全球负责人,前加拿大空间信息国家首席研究教授,PPTV聚力传媒集团合伙人兼前CEO,GeoTango 地图公司创始人兼 ...

  6. 物理世界的互动之旅:Matter.js入门指南

    点击上方 前端Q,关注公众号 回复加群,加入前端Q技术交流群 本文简介 戴尬猴,我是德育处主任 欢迎来到<物理世界的互动之旅:Matter.js入门指南>. 本文将带您探索 Matter. ...

  7. 用计算机创造一个虚拟世界,人类世界是不是一个虚拟程序?一起来探索

    佛日:"色即是空,空即是色""凡所有相,皆是虚妄". 一切皆在总共33层的"三界六道"中轮回,大千世界皆是虚妄,只有经过"涅槃&q ...

  8. 张亚勤:新范式、新架构和新模态突破传统算力,推动物理世界走向数字化

    本文转自联想创投 近日,在联想创投2020 CEO年会上,清华大学讲席教授.智能产业研究院院长.美国艺术与科学院院士.百度前总裁张亚勤先生带来了<未来科技趋势展望>. 张亚勤表示,数字化的 ...

  9. cocos2d-x游戏开发 跑酷(四) 关联与物理世界

    原创.转载注明出处http://blog.csdn.net/dawn_moon/article/details/21451077 前面一节尽管实现了一个跑动的人物,可是他只不过一个精灵在运行一个跑动的 ...

  10. 从行业应用到智慧城市,升哲科技Alpha协议如何保障物理世界的数据传输

    随着国家<"十四五"信息通信行业发展规划>和<物联网新型基础设施建设三年行动计划(2021-2023年)>的政策出台,物联网的产业发展迎来了新一波浪潮.在农 ...

最新文章

  1. 其他算法-卡尔曼滤波器
  2. java io系列10之 FilterInputStream
  3. matlab读取格式重复,matlab - 为什么Xlsread以字符串形式读取(日期时间) - 堆栈内存溢出...
  4. tp5数组为什么要中括号_VBA数组与字典解决方案第7讲:为什么要采用数组公式(一)...
  5. c# export server 调用sql_[转]使用C#调用cmd来执行sql脚本
  6. 7.定义一个有80个元素的字符数组,从键盘输入一串字符,将其中的大写字母转换为小写字母,而将原来为小写的字母转换为大写字母,其他字符不变。
  7. 字符定长文件Linux怎么生成,Linux中的more命令-逐页显示长文本文件
  8. 前端学习(2050)vue之电商管理系统电商系统之实现node创建服务器
  9. 在 CCR 环境中使用 Exchange 命令行管理程序移动存储组和数据库
  10. Intel Core Solo/Duo处理器架构/微架构/流水线 - 前端/数据预取/SSE3
  11. MSSOAP与WebService
  12. spring中集成使用jedis(2)
  13. 计算机英语教学设计,英语教学设计doc范文精选
  14. 专访李智慧:架构是最高层次的规划和难以改变的决定
  15. 【精华】超详细的Win10安装步骤,菜鸟福音
  16. SqlServer存储过程中循环的使用
  17. 编写程序实现乐手弹奏乐器。乐手可以弹奏不同的乐器从而发出不同的声音。可以弹奏的乐器包括二胡、钢琴和琵琶。
  18. java读取zip文件,并将json中的反转义斜杆去除
  19. json数据导出到excel中
  20. FancyCache Volume 0.8.0

热门文章

  1. Windows系统操作快捷键---百度百科
  2. 计算机桌面出现临时文件,如何删除电脑中的临时文件 电脑屏幕一键放大方法分享...
  3. 欢迎使用CSDN-markdown编辑器恢复看电视剧弗兰克的说法
  4. Ansible(一) 配置安装
  5. python发邮件被认定为垃圾邮件_Python:脚本发送的邮件被Gmail标记为垃圾邮件
  6. 计算机创建修改ip知识,恢复系统后让每台计算机自动修改IP和计算机名的方法...
  7. java 生成二维码图片
  8. 迪赛智慧数——柱状图(多色柱状图):2021年动画电影票房排行榜
  9. 360修复高危漏洞可以修复吗_怎么关闭360高危漏洞修复提醒?
  10. QT教程:QT的基本了解