利他才能利己


  • 首页

  • 标签

  • 归档

  • 搜索

Java 自动装箱、拆箱引起的耗时

发表于 2019-04-07 | 分类于 Java |

1

耗时问题

在说 Java 的自动装箱和自动拆箱之前,我们先看一个例子。

这个错误我在项目中犯过(尴尬),拿出来共勉!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static long getCounterResult() {
Long sum = 0L;
final int length = Integer.MAX_VALUE;
for (int i = 0; i < length; i++) {
sum += i;
}

return sum;
}

public static void main(String[] args) {
long startCountTime = System.currentTimeMillis();
long result = getCounterResult();
long endCountTime = System.currentTimeMillis();
System.out.println("result = " + result + ", and take up time : " + (endCountTime - startCountTime) / 1000 + "s");
}

在我的电脑(macOS 64位系统,配置较高),打印结果如下:

1
result = 2305843005992468481, and take up time : 12s

居然使用了 12s,是可忍叔不可忍,再正常不过的代码怎么会耗时这么久呢?如果在配置差一点的电脑上运行耗时会更久(惊呆了.jpg)。

我们不妨先阅读下面的内容,再来分析、解决上述耗时的问题。

基本概念

自从 jdk1.5 之后就有了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)。

自动装箱,就是 Java 自动将原始(基本)类型转换成对应的封装器(对象)类型的过程,比如将 int 的变量转换成 Integer 对象,这个过程叫做装箱。

自动拆箱,就是 Java 自动将封装器(对象)类型转换成基本类型的过程,如将 Integer 对象转换成 int 类型值,这个过程叫做拆箱。

之所以称之为自动装箱和拆箱,是因为这些操作并非人工(程序猿)操作的,而是 Java 自带的一个特性。

下表是 Java 中的基本类型和对应的封装类型的对应表:

基本类型|封装器类
—|—|—
int|Integer
byte|Byte
long|Long
float|float
double|Double
char|Character
boolean|Boolean

自动装箱示例:

1
2
int a = 3;
Integer b = a;

自动拆箱示例:

1
2
Integer b = new Integer(7);
int a = b;

Integer/int 自动拆箱和装箱

下面这段代码是 Integer 的源码中 valueOf 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
// 如果i的值大于-128小于127则返回一个缓冲区中的一个Integer对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];

// 否则返回 new 一个Integer 对象
return new Integer(i);
}

我们在执行下面的这句代码,如下:

1
Integer i = 100;

上面的代码等同于下面的代码:

1
Integer i = Integer.valueOf(100);

结合上面的源码可以看出来,如果数值在 [-128,127] 之间(双闭区间),不会重新创建 Integer 对象,而是从缓存中(常量池)直接获取,从常量池中获取而不是堆栈操作,读取数据要快很多。

我们再来看一下常见的基础面试题(请给出打印结果),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
// ⓵
Integer a = new Integer(121);
Integer b = new Integer(121);
System.out.println(a == b);

// ⓶
Integer c = 121;
Integer d = 121;
System.out.println(c == d);

// ⓷
Integer e = 129;
Integer f = 129;
System.out.println(e == f);

// ⓸
int g = 50;
Integer h = new Integer(50);
System.out.println(g == h);
}

分析结果:

⓵: false, 两个对象进行比较分别指向了不同堆内存
⓶: true, 自动装箱且数值在 [-128,127] 之间(双闭区间)
⓷: false, 自动装箱且数值不在 [-128,127] 之间(双闭区间)
⓸: true, 自动拆箱且数值在 [-128,127] 之间(双闭区间)

解析耗时问题

类 Long 对应的也有一个 valueof 方法,源码如下:

1
2
3
4
5
6
7
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}

这个和 Integer 的很像,道理上面说过,这里不再赘述。

在开篇的例子中,getCounterResult 方法有下面这句代码,如下:

1
Long sum = 0L;

很明显我们声明了一个 Long 的对象 sum,由于自动装箱,这句代码并没有语法上面的错误,编译器当然也不会报错。上面代码等同于如下代码:

1
Long sum = Long.valueof(0);

在 for 循环中,超过 [-128,127] 就会创建新的对象,这样不断的创建对象,不停的申请堆内存,程序执行自然也就比较耗时了。

修改一下代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static long getCounterResult() {
// 修改为普通的基本类型数据
long sum = 0L;
final int length = Integer.MAX_VALUE;
for (int i = 0; i < length; i++) {
sum += i;
}

return sum;
}

public static void main(String[] args) {
long startCountTime = System.currentTimeMillis();
long result = getCounterResult();
long endCountTime = System.currentTimeMillis();
System.out.println("result = " + result + ", and take up time : " + (endCountTime - startCountTime) / 1000 + "s");
}

执行时间大大缩短。


优柔寡断,是人生最大的负能量。对,别犹豫了赶紧扫码关注~

dumpdecrypted 砸壳:导出头文件

发表于 2019-04-07 | 分类于 iOS |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。

导出头文件

在 iOS 逆向: dumpdecrypted 砸壳记中已经给大家分享了对 ipa 文件如何砸壳了, 接下来就可以导出其头文件了。

这里需要用到 class-dump 工具。如果你使用 class-dump, 出现如下错误:

1
Error: Cannot find offset for address 0xxxxxxxxx in stringAtAddress

报这种错误说明你即将 dump 的文件是 OC 和 Swift 混编的项目,class-dump 这个工具对它们无能为力。

还好, 伟大的程序员们给我们解决了这个问题。

去获取 class-dump 改进版 源码, 然后使用 Xcode 编译即可.

编译完成后生成的 class-dump 就可以拿来使用了.

改变其可执行的权限:

1
chmod +x class-dump

查看 decrypted 文件的 arm 架构:

1
class-dump --list portkey-prod.decrypted 

显示为:arm64

开始 dump:

1
2
3
mkdir portkey-header

class-dump -H portkey-prod.decrypted -o portkey-header/

遇到问题

dumpdecrypted 砸壳出现了问题, 错误信息如下:

1
2
dyld: could not load inserted library 'dumpdecrypted.dylib' because no suitable image found.  Did find:
dumpdecrypted.dylib: required code signature missing for 'dumpdecrypted.dylib'

解决方案, 重新签名 dumpdecrypted 后将其拷贝到 Documents 目录.

1
2
3
4
5
## 列出可签名证书, 找到 mac 上面已经安装的证书
security find-identity -v -p codesigning

## 为 dumpecrypted.dylib 签名
codesign --force --verify --verbose --sign "iPhone Developer: xxx xxxx (xxxxxxxxxx)" dumpdecrypted.dylib

注意:iPhone Developer: xxx xxxx (xxxxxxxxxx) 为你自己本机安装的开发者证书名称.

看到的工具

  • class-dump-swift 针对 Swift 的工具。这个需要自己先编译 llvm,然后才能使用.

  • dumpdecrypted, 比较自动化的一个版本,但我使用这个没有砸壳成功(估计是需要针对这个文件进行签名)。

  • Clutch

越狱系列文章

基于 iOS10.3.1 进行的实践。

  • iOS 逆向: dumpdecrypted 砸壳记

  • iOS 逆向: 砸壳

  • iOS 逆向: 查看系统文件目录和结构

  • iOS 逆向: 越狱使用 SSH

  • dumpdecrypted 砸壳:导出头文件

一个神奇的二维码~

oh-my-zsh 配置

发表于 2019-04-03 | 分类于 MacOS |

喜欢使用终端的朋友都知道在 linux、macOS 上面默认使用的是 bash shell,虽然 bash 比较强大,但是比起 zsh 还是稍逊一筹。zsh 虽然好用但配置比较复杂,于是乎 oh-my-zsh 就诞生了,它统一管理 zsh 的配置。

macOS 上面安装 oh-my-zsh 后,感觉敲命令更爽了,特别是在进行 git 操作的时候,很方便。

安装比较简单,可以在终端通过 curl 也可以通过 wget 方式,安装方法如下。

1、通过 curl 方式

1
sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

2、通过 wget 方式

1
sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"

oh-my-zsh 支持 linux、macOS 和 windows,但是在 linux 和 macOS 上面支持最好。

安装成功后,原来在系统 shell 配置的环境变量需要让 oh-my-zsh 知道,配置一下 ~/.zshrc 文件,在该文件中的 User configuration 下面增加下面两行配置就可以了。

1
2
source ~/.bash_profile
source /etc/profile

配置完成后保存文件,在终端执行:

1
source ~/.zshrc

这样之前配置的环境变量在 oh-my-zsh 下就生效了。

oh-my-zsh 支持三方插件和主题配置,默认的配置已经够用了,有兴趣的可以自行探索。


工欲善其事必先利其器

Follow your heart

发表于 2019-03-24 | 分类于 随笔 |

今天不聊技术,聊聊人生~

最近天气像疯了一样,气温反复无常,时冷时热,让人有点应接不暇。

正好刚搬完家,家里很多东西需要购买,首先考虑的是给自己买对舒服的桌椅。实体店里面的桌椅实在是贵的离谱,动辄告诉我说是实木哟,现在低价处理之类的,还好我不是很傻。最后索性就在网上购买了,性价比也比较高,我也算是来了一把沉浸式的疯狂购物体验。

现在物流服务很是贴心,关注对应的公众号就可以及时告诉你当前物流的进度。相比之前,无论是送货速度还是服务都有显著提升,主要是实惠。

网上淘的这个桌子比较重,要想自己把这个桌子弄到家还是相当费劲的。快递员看出了我的窘境,说:“我这个推车反正现在也闲着,你先拿去用吧!”,我连忙道谢,有了这个推车搬起来就容易多了。

不到十分钟的时间,我就把货送到家了。心理一直想别人也许这会需要急着用车,就赶紧下楼,走到楼下拐角的时候,突然被一个人喊住,我回头一看是一个陌生的快递员。

他语调比较高:“喂,先生,你那个车子放到我这里就可以了,是我们的!”。
我瞄了他一眼,说道:“你是哪个快递公司的,我不是从你手上拿的车呀!”,这哥们依旧强势:“我是xx快递,这车就是我们的。”。

我心想这两个快递员是不是认识,这个车子原本就是眼前这个气势汹汹的人的?

心理正在犯着嘀咕,决定还是要物归原主。

我没有再理他,继续朝着原来收快递的地方走去,把车还给了主人。我顺便问道:“刚才有个xx快递的小哥说车子是他的?”,他说:“可能是他看错了,你看这车子上面有我们公司的标记呢”。我仔细一看果然是,庆幸自己没有把车交给别人。

人生中,仿佛也有很多类似上面的场景,假如你遇到了会怎么处理呢?

就拿婚姻来说,你认定的自己的另一半就是这辈子要娶或者要嫁的那个人,但是你身边的朋友或者家人并不看好你们,甚至他们压根不承认你的另一半,你又会怎么办?

我觉得遇到这样的事情,要听从自己的内心(Follow your heart),不要因为别人的三言两语,你就轻易放弃当初的选择,而应该更加理性的做出判断,相信自己的判断不会错,只有这样你才不会后悔,至少爱过。

学习、工作和生活也是一样,既定了目标就应该勇往直前,在路上遇到的任何困难那是再正常不过的,别人的冷嘲热讽也好,打击刺激也罢,你要做的就是坚守你自己的内心,一帆风顺得到的幸福终究会昙花一现。

~ 充满酸甜苦辣的人生才值得回味,不是吗?


扫码关注,期待与你的交流~

Java 枚举的本质

发表于 2019-03-16 | 分类于 Java |

概要

本文跟大家一起探讨一下 Java 枚举的本质,这篇文章的内容是我在 2012年09月05日 发布到 CSDN 上面的一篇博文 Java 枚举:理解枚举本质,虽然已经不在 CSDN 上面耕耘了,但偶尔也会去看看朋友们的留言,毕竟感情在那里!今天偶然看到有小伙伴评论这篇文章,一时兴起就想再次分享给大家。

1

学习编程语言,会用只是最基本的要求,了解和熟悉其实现、运行机制才使得你有别于常人!

C 枚举

在 C 语言中,可以这样来定义枚举,如下示例:

1
2
3
enum color {
RED=0, GREEN, BLUE, YELLOW
} col;

关键字 enum 定义枚举,在定义枚举的同时,声明该枚举变量 col.

注意:C 语言中枚举成员的值是根据上下文自动加 1 的(GREEN = 1,BLUE = 2 等)。

C 语言中 switch 语句支持枚举类型,如下示例:

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
#include<stdio.h>

int main()
{
enum color {
RED=0, GREEN, BLUE, YELLOW
} col;

int cl;

printf("0=red, 1=green, 2=blue, 3=yellow. seclect:\n");

scanf("%d",&cl);

col=(enum color) cl;

switch(col) {
case RED:
printf("the color is red\n");
break;
case GREEN:
printf("the color is green\n");
break;
case BLUE:
printf("the color is blue\n");
break;
case YELLOW:
printf("the color is yellow\n");
break;
defalut:
printf("no this color\n");
break;
}

return 0;
}

Java 枚举

那么,Java 里面的枚举与其类似,但是又不是完全一样。Java 语言中定义枚举也是使用 enum 关键字,如下示例是 Java 语言的枚举:

1
2
3
public enum Color {
RED, GREEN, BLUE, YELLOW;
}

上述定义了一个枚举类型 Color(可以说是类,编译之后是 Color.class).

上面的定义,还可以改成下面的这种形式:

1
2
3
public enum Color {
RED(), GREEN(), BLUE(), YELLOW();
}

到这里你可能会觉得迷茫(如果你是初学者的话),为什么这样子也可以,why?

其实,枚举的成员就是枚举对象,只不过它们是静态常量而已。

使用 javap 命令(javap 文件名<没有后缀.class>)可以反编译 class 文件,如下:
​​​​1

我们可以使用普通类来模拟枚举,下面定义一个 Color 类,如下:

1
2
3
4
5
6
public class Color {
private static final Color RED = new Color();
private static final Color GREEN = new Color();
private static final Color BLUE = new Color();
private static final Color YELLOW = new Color();
}

结合上图反编译的结果,做一下对比,你是否看出了一点端倪(坏笑),如果没有看出来,那就接着往下看吧。

如果按照这个逻辑,是否还可以为其添加另外的构造方法?答案是肯定的!

1
2
3
4
5
6
7
8
9
10
11
12
public enum Color {
RED("red color", 0), GREEN("green color", 1),
BLUE("blue color", 2), YELLOW("yellow color", 3);

Color(String name, int id) {
_name = name;
_id = id;
}

String _name;
int _id;
}

为 Color 声明了两个成员变量,并为其构造带参数的构造器。

如果你这样创建一个枚举:

1
2
3
4
public enum Color {
RED("red color", 0), GREEN("green color", 1),
BLUE("blue color", 2), YELLOW("yellow color", 3);
}

编译器就会报错:

1
The constructor EnumDemo.Color(String, int) is undefined

到此,你应该看明白了,枚举和普通的 Java 类很像。

对于类来讲,最好将其成员变量私有化,并且为成员变量提供 get、set 方法。

按照这个原则,可以进一步写好 enum Color,如下示例:

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
public enum Color {
RED("red color", 0), GREEN("green color", 1),
BLUE("blue color", 2), YELLOW("yellow color", 3);

Color(String name, int id) {
_name = name;
_id = id;
}

private String _name;
private int _id;

public void setName(String name) {
_name = name;
}

public void setId(int id) {
_id = id;
}

public String getName() {
return _name;
}

public int getId() {
return _id;
}
}

但是 Java 设计枚举的目的是提供一组常量,方便开发者使用。如果我们冒然的提供 set 方法(外界可以改变其成员属性),好像有点违背了设计的初衷。

那么,我们应该舍弃 set 方法,保留 get 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum Color {
RED("red color", 0), GREEN("green color", 1),
BLUE("blue color", 2), YELLOW("yellow color", 3);

Color(String name, int id) {
_name = name;
_id = id;
}

private String _name;
private int _id;

public String getName() {
return _name;
}

public int getId() {
return _id;
}
}

对于普通的基本类可以将其实例化,那么,能否实例化枚举呢?

在回答这个问题之前,先来看看 Color.class 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static enum Color {
RED("red color", 0), GREEN("green color", 1),
BLUE("blue color", 2), YELLOW("yellow color", 3);

private String _name;
private int _id;

private Color(String name, int id) {
this._name = name;
this._id = id;
}

public String getName() {
return this._name;
}

public int getId() {
return this._id;
}
}

可以看出,编译器淘气的为其构造方法加上了 private,那么也就是说,我们无法实例化枚举。

所有枚举类都继承了 Enum 类的方法,包括 toString、equals、hashcode 等方法。因为 equals、hashcode 方法是 final 的,所以不可以被枚举重写(只可以继承),但可以重写 toString 方法。

文末的附录中提供了 Enum 的源码,有兴趣可以查看阅读!

那么,使用 Java 的类来模拟一下枚举,大概是这个样子:

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
package mark.demo;

import java.util.ArrayList;
import java.util.List;

public class Color {
private static final Color RED = new Color("red color", 0);
private static final Color GREEN = new Color("green color", 1);
private static final Color BLUE = new Color("blue color", 2);
private static final Color YELLOW = new Color("yellow color", 3);

private final String _name;
private final int _id;

private Color(String name, int id) {
_name = name;
_id = id;
}

public String getName() {
return _name;
}

public int getId() {
return _id;
}

public static List<Color> values() {
List<Color> list = new ArrayList<Color>();
list.add(RED);
list.add(GREEN);
list.add(BLUE);
list.add(YELLOW);
return list;
}

@Override
public String toString() {
return "the color _name=" + _name + ", _id=" + _id;
}
}

附录

Enum.java

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
package java.lang;

import java.io.Serializable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;

/**
* This is the common base class of all Java language enumeration types.
*
* @author Josh Bloch
* @author Neal Gafter
* @version %I%, %G%
* @since 1.5
*/
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
/**
* The name of this enum constant, as declared in the enum declaration.
* Most programmers should use the {@link #toString} method rather than
* accessing this field.
*/
private final String name;

/**
* Returns the name of this enum constant, exactly as declared in its
* enum declaration.
*
* <b>Most programmers should use the {@link #toString} method in
* preference to this one, as the toString method may return
* a more user-friendly name.</b> This method is designed primarily for
* use in specialized situations where correctness depends on getting the
* exact name, which will not vary from release to release.
*
* @return the name of this enum constant
*/
public final String name() {
return name;
}

/**
* The ordinal of this enumeration constant (its position
* in the enum declaration, where the initial constant is assigned
* an ordinal of zero).
*
* Most programmers will have no use for this field. It is designed
* for use by sophisticated enum-based data structures, such as
* {@link java.util.EnumSet} and {@link java.util.EnumMap}.
*/
private final int ordinal;

/**
* Returns the ordinal of this enumeration constant (its position
* in its enum declaration, where the initial constant is assigned
* an ordinal of zero).
*
* Most programmers will have no use for this method. It is
* designed for use by sophisticated enum-based data structures, such
* as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
*
* @return the ordinal of this enumeration constant
*/
public final int ordinal() {
return ordinal;
}

/**
* Sole constructor. Programmers cannot invoke this constructor.
* It is for use by code emitted by the compiler in response to
* enum type declarations.
*
* @param name - The name of this enum constant, which is the identifier
* used to declare it.
* @param ordinal - The ordinal of this enumeration constant (its position
* in the enum declaration, where the initial constant is assigned
* an ordinal of zero).
*/
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}

/**
* Returns the name of this enum constant, as contained in the
* declaration. This method may be overridden, though it typically
* isn't necessary or desirable. An enum type should override this
* method when a more "programmer-friendly" string form exists.
*
* @return the name of this enum constant
*/
public String toString() {
return name;
}

/**
* Returns true if the specified object is equal to this
* enum constant.
*
* @param other the object to be compared for equality with this object.
* @return true if the specified object is equal to this
* enum constant.
*/
public final boolean equals(Object other) {
return this==other;
}

/**
* Returns a hash code for this enum constant.
*
* @return a hash code for this enum constant.
*/
public final int hashCode() {
return super.hashCode();
}

/**
* Throws CloneNotSupportedException. This guarantees that enums
* are never cloned, which is necessary to preserve their "singleton"
* status.
*
* @return (never returns)
*/
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}

/**
* Compares this enum with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.
*
* Enum constants are only comparable to other enum constants of the
* same enum type. The natural order implemented by this
* method is the order in which the constants are declared.
*/
public final int compareTo(E o) {
Enum other = (Enum)o;
Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}

/**
* Returns the Class object corresponding to this enum constant's
* enum type. Two enum constants e1 and e2 are of the
* same enum type if and only if
* e1.getDeclaringClass() == e2.getDeclaringClass().
* (The value returned by this method may differ from the one returned
* by the {@link Object#getClass} method for enum constants with
* constant-specific class bodies.)
*
* @return the Class object corresponding to this enum constant's
* enum type
*/
public final Class<E> getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? clazz : zuper;
}

/**
* Returns the enum constant of the specified enum type with the
* specified name. The name must match exactly an identifier used
* to declare an enum constant in this type. (Extraneous whitespace
* characters are not permitted.)
*
* @param enumType the <tt>Class</tt> object of the enum type from which
* to return a constant
* @param name the name of the constant to return
* @return the enum constant of the specified enum type with the
* specified name
* @throws IllegalArgumentException if the specified enum type has
* no constant with the specified name, or the specified
* class object does not represent an enum type
* @throws NullPointerException if <tt>enumType</tt> or <tt>name</tt>
* is null
* @since 1.5
*/
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum const " + enumType +"." + name);
}

/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}

private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}

/**
* enum classes cannot have finalize methods.
*/
protected final void finalize() { }
}

扫码关注,你我就各多一个朋友~

json-c

发表于 2019-03-10 | 分类于 C/C++ |

简介

json-c 是 C 语言写的一套构建和解析 JSON 的库。

1
2
JSON-C implements a reference counting object model that allows you to easily construct JSON objects in C, output them as JSON formatted strings and parse JSON formatted strings back into the C representation of JSON objects. 
It aims to conform to RFC 7159.

使用 C 语言编写的 JSON 库还有很多,可以在 介绍 JSON 中查找到,除了 C语言的还有其他语言编写的 JSON 开源库。

所有发布的 json-c 库版本在 这里 都可以找到,本篇编译的是 json-c-0.13.1-20180305 这个版本。

编译

一、下载、解压

直接下载最新版本 json-c-0.13.1-20180305,截止到本文发布该版本为最新版本。

解压刚才下载好的文件,解压后将文件夹重命名为 json-c-src,当然你也可以不重名它。

打开 macOS 终端,进入 json-c-src 文件夹

1
cd json-c-src

二、源码编译

在编译之前,确保你已经在 macOS 中安装了下面的工具:

1、gcc, clang, 或者其他 C 编译器;
2、libtool 工具,版本不能低于 2.2.6b;
3、autoconf 工具,版本不能低于 2.64 (autoreconf);
4、automake工具,版本不能低于 1.13;

如果没有安装上面的工具,可以使用 Homebrew 进行安装。

在桌面新建个文件夹 json-c,这个文件夹用来放待会编译后的文件(库和头文件)。

执行下面的命令开始配置和编译,如下:

1
2
3
4
5
./configure --prefix=/Users/man/Desktop/json-c

make

make install

注意:/Users/man/Desktop/json-c 要写绝对路径,否则编译报错。

如果要支持多线程,可以加上 --enable-threading 这个选项,即:

1
2
3
4
5
./configure --enable-threading --prefix=/Users/man/Desktop/json-c

make

make install

编译成功后,在 json-c 会生成对应的库和头文件,如下:

使用 json-c

编译成功后,可以试用一下 json-c 这个库了,我把 libjson-c.a 和对应的 include 文件夹放进 macOS 工程,编译无法通过,报错,然后换为 libjson-c.4.dylib 编译通过但是运行报错,意思是无法加载该库。

同样道理,在 iOS 工程上面直接使用这两个库也是无法使用。

于是我就使用 lipo 来查看一下库所支持的架构,看一下下面的结果。

1
2
3
4
5
6
7
$ lipo -info libjson-c.a 

Non-fat file: libjson-c.a is architecture: x86_64

$ lipo -info libjson-c.4.dylib

Non-fat file: libjson-c.4.dylib is architecture: x86_64

可以看出两个库均可以支持 x86_64,按道理是可以支持 macOS 和 iOS 模拟器运行的,macOS 上面可以使用 set | grep "MACHTYPE" 命令查看其操作系统架构。

libjson-c.a 是静态库,libjson-c.4.dylib 是动态库,可以使用 file 命令查看。

1
2
3
4
5
6
7
$ file libjson-c.a 

libjson-c.a: current ar archive

$ file libjson-c.4.dylib

libjson-c.4.dylib: Mach-O 64-bit dynamically linked shared library x86_64

注意:显示 ar archive 表示是静态库。

最后自己没有找到好的办法,只能采取第二个办法,源码直接放进 Xcode 工程里面编译使用。

在放进 Xcode 工程之前,需要将下载的源码进行配置操作,即:

1
./configure

这样会产生对应 config.h 等文件,然后将源码中所有 .h 和 .c 的文件拷贝至工程中即可编译使用了。

注意:如果不执行 ./configure 操作,拷贝的源文件不全,无法通过编译。

简单例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "ViewController.h"
#import "json-c/json.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];

json_object *jsonObj = NULL;

jsonObj = json_tokener_parse("{ \"uid\": 12, \"name\": \"foobar\", \"isComer\": 1, \"age\": 21, \"extras\": [ 11, 52, 3, \"unknow\", 75 ] }");

const char *jsonString = json_object_to_json_string(jsonObj);

printf("obj.to_string()=%s\n", jsonString);
}

@end

对应输出结果,如下:

1
obj.to_string()={ "uid": 12, "name": "foobar", "isComer": 1, "age": 21, "extras": [ 11, 52, 3, "unknow", 75 ] }

疑问

Objective-C 已经提供了关于 JSON 的库了,干嘛还折腾 json-c 呢?

的确,自从 iOS 5.0+,macOS 10.7+ 之后,Foundation 框架已经提供了 NSJSONSerialization,我主要是好奇 json-c 这个库,看他的跨平台能力而已。

关于 NSJSONSerialization 可以查看 API 文档。


本次分享,告辞!


扫码关注,你我就各多一个朋友~

不见得你会计算C字符串长度

发表于 2019-03-07 | 分类于 C/C++ |

最近在看人工智能相关的知识,无意中发现了一个巨牛的 人工智能教程,分享一下给大家。

教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点 这里 可以直接看教程。

C 字符串

在 C 语言中,字符串实际上是使用字符 '\0' 终止的一维字符数组。

以下几种方式表示的都是 C 字符串的正确表达方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 要以 '\0' 结尾
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 要以 '\0' 结尾
char greeting[] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 默认会在末尾增加'\0'
char greeting[] = {"Hello"};

// 上面的简写形式
char greeting[] = "Hello";

// 默认会在末尾增加'\0'
char *greeting = "Hello";

看下面另外一种声明方式:

1
2
3
char greeting[] = {'h', 'e', 'l', 'l', 'o'};

printf("greeting: %s\n", greeting);

输出结果:

1
greeting: hello\376

这个结果在不同编译器下面可能还会不一样,总之输出都不是我们想要的结果。这种方式创建的字符串没有 '\0',不算是真正的 C 字符串,所以建议大家在声明 C 字符串的时候使用字符指针(char *)的方式。

string.h 里面声明了很多关于操作 C 字符串的库函数。

字符串长度

这里在说计算字符串长度的前提是字符编码都是按照UTF-8(中文占用3个字节,英文占用1个字节)的编码形式为前提的。我们先来看下面这个例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
char *greeting1 = "hello";
char greeting2[] = {'h', 'e', 'l', 'l', 'o'};
char greeting3[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char greeting4[] = "hello";

printf("greeting1 sizeOf: %ld, strlen: %ld\n", sizeof(greeting1), strlen(greeting1));

printf("greeting2 sizeOf: %ld, strlen: %ld\n", sizeof(greeting2), strlen(greeting2));

printf("greeting3 sizeOf: %ld, strlen: %ld\n", sizeof(greeting3), strlen(greeting3));

printf("greeting4 sizeOf: %ld, strlen: %ld\n", sizeof(greeting4), strlen(greeting4));

如果你能说出上面 printf 的结果,基本上关于计算字符串长度的问题就迎刃而解了。

按照 UTF-8 编码,上面例子的输出结果如下所示:

1
2
3
4
greeting1 sizeOf: 8, strlen: 5
greeting2 sizeOf: 5, strlen: 7
greeting3 sizeOf: 6, strlen: 5
greeting4 sizeOf: 6, strlen: 5

如果输出结果令你无法相信,可以选择继续往下看或者你自己写代码试试。

sizeof、strlen

在 linux.die 可以查到 strlen 的说明,如下:

1
2
3
4
5
6
7
8
9
Synopsis:
#include <string.h>
size_t strlen(const char *s);

Description:
The strlen() function calculates the length of the string s, excluding the terminating null byte (aq\0aq).

Return Value:
The strlen() function returns the number of bytes in the string s.

函数 strlen 返回字符串里的字符数,不包括终止字符 '\0',这里注意 strlen 是一个 C 的函数,而 sizeof 只是一个操作符。

我们知道,sizeof 操作符的参数可以是数组、指针、类型、对象、函数等,函数 strlen 的参数只能是字符串。

对于 sizeof, 其参数不同时,其返回的值也不一样,如下:

1、数组:编译时分配的数组空间大小;
2、指针:存储该指针所用的空间大小(32位机器上是4,64位机器上是8);
3、类型:该类型所占的空间大小;
4、对象:对象的实际占用空间大小(这个指的是在 C++ 中);
5、函数:函数的返回类型所占的空间大小。函数的返回类型不能是 void 类型;

那我们再回头看看上面的例子,我把要说明的写在注释上面了。

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
// 注意这里是指针
char *greeting1 = "hello";

// 没有结束符 '\0',其 strlen 结果不确定
char greeting2[] = {'h', 'e', 'l', 'l', 'o'};

char greeting3[] = {'h', 'e', 'l', 'l', 'o', '\0'};
char greeting4[] = "hello";

/* 结果是 8、5 */
/* greeting1是指针,sizeOf计算的是其存储该指针所用的空间大小,因为我使用的是64位 macOS,所以输出是8 */
/*strlen 计算的是字符个数但是不包括结束符 '\0'*/
printf("greeting1 sizeOf: %ld, strlen: %ld\n", sizeof(greeting1), strlen(greeting1));

/* 结果是 5、7 */
/* sizeof 计算的是编译时分配的数组空间大小,这里是5 */
/* greeting2没有结束符,strlen 的计算结果不确定 */
printf("greeting2 sizeOf: %ld, strlen: %ld\n", sizeof(greeting2), strlen(greeting2));

/* 结果是 6、5 */
/* sizeof 计算的是编译时分配的数组空间大小,这里是6,因为多了结束符 */
/*strlen 计算的是字符个数但是不包括结束符 '\0'*/
printf("greeting3 sizeOf: %ld, strlen: %ld\n", sizeof(greeting3), strlen(greeting3));

/* 结果是 6、5,这里类似上面的情况,不再赘述 */
printf("greeting4 sizeOf: %ld, strlen: %ld\n", sizeof(greeting4), strlen(greeting4));

小结:

1、sizeof 是一个操作符,而 strlen 是 C 语言的库函数。

2、sizeof 的参数可以是任意数据类型或者表达式,而 strlen 只能以结尾为 '\0' 的字符串作参数。

3、sizeof 的结果在编译时就计算出了,而 strlen 必须在运行时才能计算出来。

4、sizeof 计算数据类型占内存的大小,strlen 计算字符串实际长度,要记住 strlen 计算出来的结果不包括结束符 '\0'。

5、sizeof 反应的并非真实字符串长度而是所占空间大小,所以memset 初始化字符串的时候用 sizeof 较好。

6、系统函数返回值是 char * (字符指针)类型的会在末尾加上结束符 '\0'。

7、无论是 sizeof 还是 strlen 计算结果的单位都是字节。

我们还需要注意一点,strlen 函数,当数组名作为参数传入时,实际上数组就退化成指针了。举个例子,如下图所示:

可以看出传入进来的参数会被退化为指针。

探索无止境

在文章的开始,我给出了几种 C 字符串的正确表达方式,那我们再来看另外一种。

1
char greeting[4] = "blog";

这种方式看起来好像很完美的样子,其实是不对的,写个例子给大家,如下:

1
2
3
4
5
6
7
8
9
int main(int argc, const char *argv[])
{
char greeting[4] = "blog";
size_t len = strlen(greeting);
printf("greeting len: %ld\n", len);
printf("greeting: %s\n", greeting);

return 0;
}

编译运行,结果如下:

1
2
greeting len: 10
greeting: blog\330\365\277\357\376

苍天呀,这结果让人无语。。。

对于 char greeting[4] = "blog" 其实是定义一个长度为 4 的字符数组,但是字符串 "blog" 实际是要包括结束符 \0 的,也就是说下面的代码

1
char greeting[4] = "blog";

本质和下面代码是一样的,如下:

1
char greeting[] = {'b', 'l', 'o', 'g'};

显然是不正确的,那我们修改一下代码,如下:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char *argv[])
{
// 注意这里是 5
char greeting[5] = "blog";
size_t len = strlen(greeting);
printf("greeting len: %ld\n", len);
printf("greeting: %s\n", greeting);

return 0;
}

或者这样写:

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char *argv[])
{
// 这里干脆不要写数字
char greeting[] = "blog";
size_t len = strlen(greeting);
printf("greeting len: %ld\n", len);
printf("greeting: %s\n", greeting);

return 0;
}

这样修改后,再编译运行结果就对了,如下:

1
2
greeting len: 4
greeting: blog

我们知道的东西是有限的,我们不知道的东西则是无穷的。

iOS 使用 libcurl

发表于 2019-03-03 | 分类于 iOS |

libcurl 简介

libcurl 是用C语言写的一套 开源 库,是为网络客户端提供数据传输功能的函数库。

libcurl 支持 SMTP、HTTP、HTTPS、FTP、TELNET 等协议和各种 SSL 安全认证,支持 Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS 等平台,在 Android 和 iOS 上面也可以使用 libcurl 这个库。

下面是官网的英文简介:

1
2
3
4
5
6
libcurl is a free and easy-to-use client-side URL transfer library, supporting DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP. 
libcurl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, Kerberos), file transfer resume, http proxy tunneling and more!

libcurl is highly portable, it builds and works identically on numerous platforms, including Solaris, NetBSD, FreeBSD, OpenBSD, Darwin, HPUX, IRIX, AIX, Tru64, Linux, UnixWare, HURD, Windows, Amiga, OS/2, BeOs, Mac OS X, Ultrix, QNX, OpenVMS, RISC OS, Novell NetWare, DOS and more...

libcurl is free, thread-safe, IPv6 compatible, feature rich, well supported, fast, thoroughly documented and is already used by many known, big and successful companies.

分享内容

今天跟大家分享 libcurl 在 iOS 上面如何使用,主要分享内容如下:

1、iOS 工程集成 libcurl以及集成注意事项。
2、利用 libcurl 发送 HTTP GET 和 POST 请求。
3、使用 springboot 搭建本地服务,这个只是为了演示不是分享的重点。

搭建本地服务

本地服务采用 Spring Boot 开发,开发语言是 Java,JDK 版本1.8,Spring Boot 版本是 v2.1.3.RELEASE,集成 Web 组件即可,比较简单。关于如何搭建 Spring Boot的开发环境,大家自行搜索解决,也可以直接使用我的 git 工程,猛戳前往 即可获取完整代码示例。

工程结构如下图所示:

关键代码如下:

该工程只是为了配合 libcurl 的使用而诞生的,没有什么难度。

集成 libcurl

1、新建 iOS 工程

这里工程为 tutorial_libcurl_iOS。

2、准备库文件、头文件

libcurl 可以自己编译,也可以使用别人编译好的二进制文件。我使用的是 curl-android-ios 这个工程里面编译好的文件。

3、给工程 tutorial_libcurl_iOS 添加库和头文件

将上面的文件拷贝至工程目录即可,现在工程目录如下:

4、设置路径

在 xcode 中 Building Settings 找到 User Header Search Paths 为如下内容:

1
$(SRCROOT)/tutorial_libcurl_iOS/Classes

这一步不是必须的,我个人比较喜欢这样整理和设置目录。

5、添加 libz.tbd

如果不添加这个库,编译无法通过,会显示如下错误:

发送 HTTP 请求

下面说一下如何使用 libcurl 来发送 HTTP 的 GET 和 POST 请求。只给出核心示例代码,其余的大家去 tutorial_libcurl 获取完整示例代码,包括 Spring Boot 的。

ViewController.m

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
#import "ViewController.h"
#import "curl/curl.h"

// 我本机的 IP 和端口,实际你要换成你自己的
#define HOST_URL @"http://172.20.10.2:8080/user"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];
}

- (IBAction)doHTTPGETToFileAction:(id)sender
{
NSString *reqUrl = [NSString stringWithFormat:@"%@%@", HOST_URL, @"?id=1&name=veryitman"];
const char *url = [reqUrl cStringUsingEncoding:NSUTF8StringEncoding];
http_get_req(url);
}

- (IBAction)doHTTPPOSTToFileAction:(id)sender
{
const char *url = [HOST_URL cStringUsingEncoding:NSUTF8StringEncoding];
const char *data = "id=2&name=veryitman";
http_post_req(url, data);
}

#pragma mark - C

void http_get_req(const char *url)
{
CURL *curl;

const char *fpath = rspDataPath(@"http_get_rsp_data.log");

FILE *fp;
fp = fopen(fpath, "wt+");

struct curl_slist *headers = NULL;
//增加HTTP header
headers = curl_slist_append(headers, "Accept:application/json");
headers = curl_slist_append(headers, "Content-Type:application/json");
headers = curl_slist_append(headers, "charset:utf-8");

//初始化
curl = curl_easy_init();

if (curl) {
//改协议头
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, url);

curl_easy_setopt(curl, CURLOPT_POST, url);

//wt+:读写打开或着建立一个文本文件;允许读写
if (NULL != fp) {
// 请求结果写入到文件当中
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
}

CURLcode rsp_code = curl_easy_perform(curl);
if (CURLE_OK == rsp_code) {
NSLog(@"请求返回成功");
} else {
NSLog(@"请求返回失败,返回码是 %i", rsp_code);
}

curl_slist_free_all(headers);

curl_easy_cleanup(curl);
}

fclose(fp);
}

void http_post_req(const char *url, const char *req_data)
{
const char *fpath = rspDataPath(@"http_post_rsp_data.log");

FILE *fp;
fp = fopen(fpath, "wt+");

CURL *curl;
curl = curl_easy_init();

if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url);

NSLog(@"length: %ld", strlen(req_data));

/* size of the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(req_data) + 1);

/* pass in a pointer to the data - libcurl will not copy */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_data);

if (NULL != fp) {
// 请求结果写入到文件当中
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
}

CURLcode rsp_code = curl_easy_perform(curl);
if (CURLE_OK == rsp_code) {
NSLog(@"请求返回成功");
} else {
NSLog(@"请求返回失败,返回码是 %i", rsp_code);
}

curl_easy_cleanup(curl);
}

fclose(fp);
}

@end

启动 Spring Boot 项目,启动成功后,再运行 xcode 工程,可以测试。

点击对应的按钮就可以发送 GET 和 POST 请求了。

请求返回的结果被写入到了沙盒文件中,可以在终端使用 cat 命令查看对应的响应结果。

GET 响应结果:

1
cat ~/Library/Developer/CoreSimulator/Devices/BA882AC3-7977-49C7-8B0D-65EFD1541B68/data/Containers/Data/Application/3A21CFC5-3044-4FC0-9BFA-B311A59187AF/Documents/http_get_rsp_data.log
1
user info: id=2 name=veryitman

POST 响应结果:

1
cat ~/Library/Developer/CoreSimulator/Devices/BA882AC3-7977-49C7-8B0D-65EFD1541B68/data/Containers/Data/Application/86E7D457-97B1-4D8C-80D5-E8179B691F76/Documents/http_post_rsp_data.log
1
user info: id=2 name=veryitman

后续为大家分享如何使用回调来接收 HTTP 响应数据、其他网络请求的情况以及 Android 上面如何使用 libcurl 库。


扫码关注,期待与你的交流~

Emscripten:JS 调用 C、C++

发表于 2019-03-02 | 分类于 C/C++ |

在 了解 Emscripten 中,给大家简单分享了 Emscripten 是什么以及其使用场景。今天继续分享如何使用 JS 调用 C/C++ 代码。

神奇的 main 函数

下面是 了解 Emscripten 中的例子代码,只有一个 main 函数。

1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char ** argv)
{
printf("Emscripten show in browser...\n");
}

使用 Emscripten SDK 编译后生成了对应的 html、js 和 wasm 文件。

第一次编译会较慢, 编译完成后会在 ~/.emscripten_cache 生成缓存目录和缓存文件, 以后再次编译就比较快了.

可以在火狐或者 Chrome 或者 Safari 上面运行 h_emcc.html 文件.

这里在火狐浏览器上面可以直接打开 mz.html 文件, 如果是在 Chrome 或者 Safari 需要执行下面命令:

1
emrun mz.html 

或者指定浏览器打开该文件.

1
emrun --browser chrome mz.html

关于 emrun 的其他用法,可以使用 emrun --help 来查看。这里在浏览器可以看到对应 main 函数的输出,说明 Emscripten 生成的代码默认会调用 main 函数。

EMSCRIPTEN_KEEPALIVE

既然 Emscripten 生成的代码默认会调用 main 函数,那么如果想使用其他函数怎么办呢?

我们可以在函数前添加 EMSCRIPTEN_KEEPALIVE,它在emscripten.h 文件中有声明,这个可以通过源码查看。

下面还是举个例子来说明。

my.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!doctype html>
<html lang="en-us">

<!-- 省略... -->

<body>
document.querySelector('.mybutton').addEventListener('click', function(){
alert('检查控制台');
var result = Module.ccall('sum', // name of C function
null, // return type
null, // argument types
null); // arguments
});

</script>
<script async type="text/javascript" src="hello3.js"></script>
</body>
</html>

里面引用了 hello3.js 并且使用 Module.ccall 调用了 C 函数 sum。

hello.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {

printf("Hello emcc\n");
}

#ifdef __cplusplus
extern "C" {
#endif

int EMSCRIPTEN_KEEPALIVE sum() {

printf("sum = %i\n", 100);

return 1;
}

#ifdef __cplusplus
}
#endif

编译 hello.c

1
emcc -o hello.html hello.c -O3 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']"

注意:EXTRA_EXPORTED_RUNTIME_METHODS 设置了 Module 的导出函数,不导出 ccall 的话,会报以下错误:

1
'ccall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)

现在可以运行 my.html

1
emrun my.html

可以修改一下 sum 函数,使其带参数,修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <emscripten/emscripten.h>

int main(int argc, char ** argv) {

printf("Hello World emcc\n");
}

#ifdef __cplusplus
extern "C" {
#endif

int EMSCRIPTEN_KEEPALIVE sum(int a, int b) {

printf("sum = %i\n", (a+b));

return 1;
}

#ifdef __cplusplus
}
#endif

需要修改 my.html 文件,修改内容如下:

1
2
3
4
var result = Module.ccall('sum', // name of C function
null, // return type
['number'], // argument types
[12, 13]); // arguments

注意 argument types 和 arguments 的填写,运行可以看到预期效果。


扫码关注,你我就各多一个朋友~

冷笑话的启示

发表于 2019-02-24 | 分类于 随笔 |

几乎所有人都比较喜欢听或者看笑话故事,笑话故事可以让你开心,也可能让你随时哈哈大笑。下面几则笑话故事,在笑的同时可以想想其中蕴藏的道理。


女浴室起火,里面人乱作一团,赤裸身体往外跑,只见大街上白花花一大群,一老者大喊“快捂住”,众裸女突然醒悟,但身上要紧部位有三处,手忙脚乱捂不过来,不知所措。这时老者又大喊:“捂脸就行,下面都一样!”

😆 在特殊情况下抓工作不可能面面俱到,要抓住重点。


少妇报案:“我把钱放在胸衣内,在拥挤的地铁内被一帅哥偷走了…”警察纳闷:“这么敏感的地方你就没觉察到?”少妇红着脸答:“谁能想到他是摸钱呢?”

😆 让客户的钱在愉快体验中不知不觉地被摸走,是商业模式的最高境界。


某富翁想娶老婆,有三个人选,富翁给了三个女孩各一千元,请她们把房间装满。第一个女孩买了很多棉花,装满房间的1/2。第二个女孩买了很多气球,装满房间的3/4。第三个女孩买了很多蜡烛,让光线充满房间。 最终,富翁选了胸部最大的那个。

😆 把握客户内心猥琐但真实的需求至关重要。


很多年前认识一小三,当然是别人的小三,但这小三算是有本领的,不仅人长的漂亮,长发飘飘,也很有脑。爱上某金融界人士,但人家已有妻室,这小三不屈不挠,投其所好,知道男人热爱古诗词,小三楞是用了不长的时间将唐诗三百首倒背如流,小三便转正了。

😆 没有拆不散的夫妻,只有不努力的小三!


一男子在狱中闲来无事训练蚂蚁,蚂蚁可在他指令下倒立、翻跟头 、鞠躬…. 出狱后,男子迫不及待去酒吧炫耀他的绝活,他点了一杯啤酒,然后,掏出蚂蚁放在桌上对服务员说:看,这只蚂蚁。服务员转身一掌拍死了蚂蚁,抱歉地对他说说:对不起先生,我马上给您换一杯!

😆 遇事要三思而后行。

从前有只羊,它一天干10个小时的活。有一天它的主人告诉它,你好好干,多干活有奖励,于是它照做了。接下来每个月它的主人把它身上的羊毛剪了三分之一 ,年底到了,给它织了件毛衣,然后告诉它:诺,这是你的奖励,恭喜你,继续努力吧!

😆 这笑话告诉我们:羊毛出在羊身上。


赵四小姐从十六岁开始跟张学良。跟一年,属奸情;跟三年,算{敏感词};跟六十年,便成为千古爱情!

😆 很多事情不看做不做,而看你做多久。


民国初名妓小凤仙,如果跟了民工,就属于扫黄对象;她跟了蔡锷,则千古留芳了;倘若她跟了孙中山,那便可能成为国母。

😆 不在于你干什么,而看你跟谁干。


组织几个人收保护费,那是黑社会。朱元障组织几百万人抢下王位,就是伟大的皇帝。武则天睡了公公睡儿子,虽属乱*伦,但乱的够大,故成为女皇。

😆 不在你干没干坏事,而在于干多大!


一公司在小便池上贴上条:“往前一小步,文明一大步”,结果地上仍有许多尿渍。后来公司认真吸取教训,重新设计成:“尿不到池里说明你短;尿到池外说明你软”,结果地上比以前干净许多。

😆 给客户的投资建议一定要具体,确切,中要害。


某日,女秘书神色凝重地说:王总,我怀孕了。 王继续低头看文件,然后淡淡一笑:我早结扎了。 女秘书楞了一会媚笑道:我和您开玩笑呢! 王抬起头看了她一眼,喝了口茶说:我也是。

😆 在江湖上混的人,遇事不要慌,先让子弹飞一会。


男子去提亲,女方家长:请自我介绍。

A说:我有一千万;

B说:我有一栋豪宅,价值两千万;

家长很满意。就问C,你家有什么?

C答:我什么都没有,只有一个孩子,在你女儿肚子里。

AB无语,走了。

😆 核心竞争力不是钱和房子,是在关键的岗位有自已的人。


一男干部怕吃苦不愿援藏,谎称眼睛突然失明。领导闻听,出面让一美女脱光站在他面前,问“看见了吗”?答:看不见。领导飞起一脚给他臀部:狗日的,老二都直了还看不见?收拾行李,明天进藏。

😆
1.人性化才能真正了解人。
2.组织比个人高明。
3.本能会出卖你


一小朋友问一富翁说 叔叔你为什么这么有钱。富翁摸摸小朋友的头说:小时候我爸给了我一个苹果,我卖掉了它有了两个苹果,后来我又赚到了四个苹果。小朋友若有所思的说 哦…叔叔,我好像懂了。富翁说,“你懂你妹阿,后来我爸死了,我继承了他的财产…

😆 不要痴迷于从阅读成功人士的传记,从中寻找经验,这些书大部分经过了精致的包装,很多重要的事实不会告诉你。例如盖茨的的书不会告诉你他母亲是IBM董事,是她给儿子促成了第一单大生意,巴菲特的书只会告诉你他8岁就知道去参观纽交所,但不会告诉你他国会议员的父亲带他去的,是高盛的董事接待的。


参考 lupaworld


扫码关注,期待与你的交流~

<1…8910…20>

193 日志
16 分类
163 标签
© 2024 veryitman