JNI开发系列之C语言
什么是JNI
- JNI全称Java native interface
- JNI可以看作是翻译,实际上就是一套协议、接口
为什么要使用JNI?
- Java的特点是一处编译到处运行,即跨平台
- Java运行在虚拟机上,JNI可以扩展Java虚拟机的能力,让Java代码可以调用驱动
- Java是解释型语言,运行效率相对较低,C/C++的效率要高很多,通过JNI把耗时、好性能的操作放在C/C++可以提高Java运行效率
- Java代码编译成的.class文件安全性较差,可以通过JNI把重要的业务逻辑放到C/C++去实现,C/C++反编译比较困难,安全性较高
- C历史悠久1972年就诞生了,在这几十年已经有了现成的优秀C库,比如FFMPEG、OpenGL等等,我们可以使用JNI来调用这些库,而没必要再重复造轮子。
怎么使用JNI
- 学习C、C++语法
- 学习JNI开发流程
- 学习使用NDK开发工具,在Android Studio已经推荐使用CMake了
C的基本语法
- 第一个C语言程序,Hello World
#include<stdio.h> //include相当于Java的import,stdio.h标准的输入输出(standard io)
#include<stdlib.h> //stdlib.h标准函数库,相当于Java的com.java.lang
main() {
printf("Hello World\n"); //相当于System.out.println("Hello World");
system("pause"); //system执行Windows系统命令,表示暂停。比如在CMD中执行notepad一样。
}
- Java的基本数据类型:
byte(1)、char(2)、short(2)、int(4)、long(8)、float(4)、double(8)、boolean(1) C的基本数据类型:
char(1)、short(2)、int(4)、long(4)、float(4)、double(8)、signed(表示正负)、unsigned(只能是正数)、void。- signed和unsigned只能用来修饰整型变量,占用大小随整型类型走。
- 没有boolean和byte类型,其中boolean用0表示false,用非0表示true。
需要注意的是不同编译器所占用字节也不一样,没必要去死抠,知道这么回事就可以了。
#include<stdio.h> //include相当于Java的import,stdio.h标准的输入输出(standard io)
#include<stdlib.h> //stdlib.h标准函数库,相当于Java的com.java.lang
/**
C语言中的基本数据类型:char(1)、short(2)、int(4)、long(4)、float(4)、double(8)、signed、unsigned(占用大小随整型类型走)
*/
main() {
char c = 'a';
printf("char占用%d个字节\n", sizeof(c));
short s = 12;
printf("short占用%d个字节\n", sizeof(s));
int i = 12345;
printf("int占用%d个字节\n", sizeof(i));
long phone = 18672;
printf("long%d字节\n", sizeof(phone));
float price = 3.14;
printf("float占用%d个字节\n", sizeof(price));
double PI = 3.1416926;
printf("double占用%d个字节\n", sizeof(PI));
signed ss = -100;
printf("signed占用%d字节\n", sizeof(ss));
signed char unss = -99;
printf("%d\n", unss);
printf("unsigned占用%d字节\n", sizeof(unss));
system("pause");
}
C的输出函数
#include<stdio.h> //include相当于Java的import,stdio.h标准的输入输出(standard io)
#include<stdlib.h> //stdlib.h标准函数库,相当于Java的com.java.lang
/**
printf("输出的内容[格式化符号]", 变量);
%d --> int
%ld --> long
%lld --> long long
%hd --> short
%c --> char
%f --> float,默认输出的是6位长度的小数,不够补0,超出的四舍五入。想手动指定加上.X,如%.4f表示4位的小数
%lf --> double,默认输出的是6位长度的小数,不够补0,超出的四舍五入。想手动指定加上.X,如%.8f表示4位的小数
%u --> unsigned
%x[X] --> 十六进制输出int、long int或者short,为了区分可以在前面加上前缀,如%#x
%o --> 八进制输出int、long、short
%s --> 字符串(注意没有字符串类型)
*/
main() {
char c = 'z';
short s = 123;
int i = 12346;
long l = 1234567;
float f = 1.2346789;
double d = 3.1415926;
printf("c = %c\n", c);
printf("s = %hd\n", s);
printf("i = %d\n", i);
printf("l = %ld\n", l);
printf("f = %f\n", f);
printf("f = %.4f\n", f);
printf("d = %lf\n", d);
printf("d = %.9lf\n", d);
printf("十六进制的输出i:%x\n", i);
printf("十六进制的输出i,带前缀的:%#x\n", i);
printf("八进制的输出s:%o\n", s);
printf("八进制的输出s,带前缀的:%#o\n", s);
//字符串其实就是字符数组
char charArray[] = {'a', 'b', 'c', 'd', '\0'}; //C语言中字符串必须加上结束符\0,这样它就能识别结束,否则无法正常输出
printf("charArray的输出:%s\n", charArray);
char str[] = "这是我写的Hello World"; //这种是自带结束符的,并且是可以有非英文字符,上面是不可以的
printf("str的输出是:%s\n", str);
system("pause"); //system执行Windows系统命令,表示暂停。比如在CMD中执行notepad一样。
}
C的输入函数
#include<stdio.h>
#include<stdlib.h>
/**
C语言的输入输出函数:
printf输出函数
scanf输入函数
*/
main() {
printf("请输入你的年龄:\n");
int age;
scanf("%d", &age); //&表示取地址符
printf("您的年龄是:%d\n", age);
printf("age的内存地址%d,占用内存大小:%d\n\n", &age, sizeof(age));
//输入字符串
char charArray[4];
//注意C的数组是不检测越界问题,你输入多少就能接收多少,因此建议合理使用大小
printf("请输入姓名:\n");
scanf("%s", &charArray);
printf("您的姓名是:%s\n", charArray);
printf("charArray的内存地址%d,占用的内存大小:%d\n\n", &charArray, sizeof(charArray));
printf("您的年龄是%d,您的姓名是%s", age, charArray);
/*
请输入你的年龄:
12
您的年龄是:12
age的内存地址6487628,占用内存大小:4
请输入姓名:
555
您的姓名是:555
charArray的内存地址6487616,占用的内存大小:4
您的年龄是12,您的姓名是555
--------------------------------
需要注意的是charArray的内存地址是6487616,而age的内存地址是6487628,中间相差12个字节。在这里我有点奇怪的是为啥是12个字节,我的系统是64位的。那么这里其实牵扯到一个问题,就是如果charArray的输入超过了12字节(即12个字符)那么你会发现输出的age值不是你最开始输入的值。原因是因为charArray所输入的字符地址超过了范围,这就造成后面变量的值就被改写了,说白了就是数组越界了,因此我们应该合理的 使用数组大小,比如下面的结果我输入了12个字符(abcdefghijkl)加一个'\0'字符就是13个字符,已经超出了12个字节了,其age的输出结果是0:
请输入你的年龄:
15
您的年龄是:15
age的内存地址6487628,占用内存大小:4
请输入姓名:
abcdefghijkl
您的姓名是:abcdefghijkl
charArray的内存地址6487616,占用的内存大小:4
您的年龄是0,您的姓名是abcdefghijkl
*/
}
内存地址的概念
CPU主要有3个总线来控制着:
1. 地址总线:标识地址,地址总线的大小表示能支持多大的内存,如32系统最大支持2^32个byte即4G内存
2. 数据总线:传输数据,比如3.14
3. 控制总线:读写操作,写一个数据或读一个文件
指针
我们一般说的指针其实是指针变量,而指针变量只能用来保存变量的地址。
#include<stdio.h>
#include<stdlib.h>
main() {
int num = 123;
//一般计算机使用16进制来表示内存地址
printf("num的值:%d,num的内存地址:%#x\n", num, &num);
/*
1、* pointer表示是int类型的指针变量,指针类型的变量只能用来存放内存地址。
2、使用&符号,可以获取变量的地址。
3、把&num赋给指针变量表示指针变量指向了num的地址
*/
int *pointer = #
printf("pointer的值:%#x\n", pointer);
//使用* pointer可以获取该地址所存放的值
int value = *pointer;
printf("value的值:%d\n", value);
*pointer = 99;
printf("对指针变量修改值,那么num的值:%d\n", num);
}
- 需要注意的是C语言中是不允许修改没有赋值(即没有初始化)的指针变量的值,否则就崩溃。这种没有赋值的指针称之为野指针,原因是野指针可能是内存中某一块地址,如果自己没有初始化的话直接赋值可能会出现问题,因此系统不允许你这么做。
int *age;
printf("指针变量age的地址:%#x\n", age);
//对野指针(没有初始化)进行赋值会崩溃
*age = 666;
- 指针变量只允许指向同类型的变量,比如int类型的指针变量只允许指向int类型的变量,否则会错误。
double pi = 3.14;
/*
不过我是用Dev-C++来编译的时候过不去,提示“cannot convert 'double*' to 'int*' in initialization”
*/
int *number = π
printf("*number的值:%d\n", *number);