承接上篇
我们还需要知道更多关于c语言的一些知识.
- c语言中的字符串.
- c语言中用字符数组保存字符串,但需要在最后保存一个结束标志 ‘\0’.所以有效字符会比字符数组实际长度少1.
- c语言中的字符串由string库函数处理,需要< string.h > 声明.
- strlen(一个参数) 计算字符串的长度 .
- strcmp(两个参数) 比较字符串.
- 注意:
- 字符串长度不一定是数组容量-1.
- 字符串长度,不包括空字符.
- 字符串的比较:逐个字符比较,直到出现字符不相等.
- 了解堆区内存
下图大致表示了一个进程的内存分布
堆区内存使用malloc/free
#include <stdio.h> #include <stdlib.h> // malloc() free() int main(){ int num = 0; int i = 0; printf("输入数组长度: "); scanf("%d",&num); printf("num = %d\n",num); // int a[5] //空间大小 : num * sizeof(int) //数组名:int * int *ptr = (int *)malloc(num * sizeof(int)); //malloc是一个指针函数 , 后面会讲到 for(i = 0;i < num;i++){ printf("输入第%d个元素的值: ",i+1); scanf("%d",ptr + i); //等价于scanf("%d",&ptr[i]); //但是不能使用 scanf("%d",ptr++); 因为ptr自增之后,free(ptr)中的 ptr已经不是原来的ptr了 } for(i = 0;i < num;i++){ printf("id[%d] = %d\n",i,ptr[i]); } free(ptr);//释放ptr指向的堆区空间 ptr = NULL; return 0; }
c语言中的结构体
相当于java中的没有方法的对象,只有变量,但是可以通过函数指针变量指向一个函数.struct Student{ char name[20]; int age; float score; }s2; // struct Student 这里比较特殊,需要两个写在一起才算结构体类型名,和int * 有一点类似. // Student 结构体名 int main(){ struct Student s1; struct Student *ptr = &s1; struct Student * *pp = &ptr; s1.age = 20; //*ptr <=> s1 //(*ptr).age = 30; ptr->age = 40;//结构体指针访问其指向结构体变量中成员变量 //**pp <=> *ptr <=> s1 (**pp).age = 50; (*pp)->age = 60; return 0; }
函数指针
- 函数入口地址:函数保存在text代码段中起始位置, 函数名代表函数的入口地址
- 函数指针:函数指针变量保存函数的入口地址。
特别区分函数指针与指针函数.
- 函数指针指向函数, 返回值由函数决定 ,它是一个指针变量.
指针函数返回指针, 它是一个函数.
int add(int a,int b){ return a + b; } int sub(int a,int b){ return a - b; } int calc(int a,int b,int (*pfun)(int,int)){ return pfun(a,b); } int main(){ //int (int,int) 函数类型 //函数指针变量名要定义在 函数名的位置上,并且用括号括起来 int (*ptr)(int,int) = add; //正确的 函数指针 变量 定义 //int * ptr (int,int); 这是一个 指针函数,返回值类型是指针类型,不能混淆 //为了方便记忆, 星被括号括起来, 一般情况就可以看作是函数指针, ptr就是函数指针的变量名 int a = 10,b = 20; int result = 0; result = add(a,b);//直接调用 printf("result = %d\n",result); result = ptr(a,b); printf("result = %d\n",result); result = calc(a,b,add);//函数的回调 printf("result = %d\n",result); result = calc(a,b,sub); printf("result = %d\n",result); return 0; }
typedef
给已经存在的数据类型起别名,需要特别注意的是结构体指针类型int a;//a是一个标识符,a是一个变量名 typedef int a;//a是一个标识符,a成为int类型的别名 struct Student{ ...}s1; //s1是一个标符识,s1是一个变量名 typedef struct Student{ ...}s1; //s1是一个标符识,s1变成struct Student类型别名 struct Student * pstu;//pstu是一个标符识,pstu是一个结构体指针变量名 typedef struct Student * pstu;//pstu是一个标符识,pstu是一个结构体指针类型 int (*pfun)(int,int *);//pfun是一个标识符,pfun是一个函数指针变量名 typedef int (*pfun)(int,int *);// pfun <=> int (*)(int,int*) //pfun是一个标识符,pfun是一个函数指针类型, 是int (*)(int,int*)的别名
jni头文件
- 在了解了typedef 之后, 我们就可以看懂SDK里面的jni.h头文件了.
D:\SDK\ndk-bundle\platforms\android-19\arch-arm\usr\include
- jni.h头文件的作用
- 要实现java调用c 代码, 无非就是数据的传递, 数据要传递, 那么数据类型就一定要对应起来, 相同类型的数据可以直接传递, 不同类型则需要利用函数转换. 例如
这个函数指针指向的 函数的作用就是让c里面的char * 字符串转换成java里面的jstring对象. - 所以, jni头文件无非就是提供给我们在JNI编程时, c 代码数据传给java ,java 代码数据传给 c 的过程中, 如果数据类型不匹配, 就需要它里面的函数进行数据的转换.
- 要实现java调用c 代码, 无非就是数据的传递, 数据要传递, 那么数据类型就一定要对应起来, 相同类型的数据可以直接传递, 不同类型则需要利用函数转换. 例如
- 在了解了typedef 之后, 我们就可以看懂SDK里面的jni.h头文件了.
jni头文件里重要的结构体
jni头文件里有很多结构体指针类型
typedef struct JNINativeInterface* JNIEnv; JNIEnv <=> struct JNINativeInterface* JNIEnv * 是什么呢? JNIEnv * env; 是我们实际需要用到的参数. env : JNIEnv * <=> struct JNINativeInterface* * env本质上是一个二级结构体指针,若真正要访问struct JNINativeInterface*结构体中的成员,按照如下写法 (**env).FindClass(); 通常按照下面的写法: (*env)->FindClass();
JNI涉及概念
- 交叉编译: 一个平台(操作系统或处理器架构)为另一个平台编译代码.
区别本地编译 - 工具链: 一套编译工具,编译过程中顺序调用的一套工具.
- 函数库: 实现某一类功能函数的集合,其中的代码已经编译成了二进制机器指令(c中非系统函数库需要我们手动链接). 提供库至少需要提供两个文件,.h头文件声明库中的函数和.so/.a库文件包含函数的具体实现.
- 静态链接库 .a 静态库只用在静态链接过程中,将库的二进制机器指令拷贝一份.
- 动态链接库(共享库) .so 动态库先用在动态链接过程中,记录用到哪个库的哪个函数.在程序运行之前,需要先加载动态库.
JNI开发相关工具
- NDK: Native Development kit 本地开发工具集