对比java复习C语言
零、jni 中间件,配适器
JNI provides a clean interface between Java code and native code.
JNI在Java代码和本机代码之间提供了一个干净的接口。
- 了解
- 为什么用
- 怎么用
招聘信息
了解 知道什么是
熟悉 为什么用,是用来干什么的
掌握 知道怎么用
精通 在某一方面知道更多一些
一、java和C
jni使java的方法和C语言的函数库相互调用,
让java和C语言通讯
-
java代码一次编译,多次运行
-
垃圾回收机制
虚拟机相对于在本机抽取出一个cpu内存电脑 -
C语言没有虚拟机,不存在java上面的虚拟机步骤。
会直接与操作系统交互。执行效率比较快 -
c语言可以进行驱动的开发,驱动就是和硬件打交道。
-
C语言是手动回收内存。
-
面向过程
C语言调用java的案例
文件目录
Demo
HelloWorld.c
HelloWorld.java
HelloWorld.c
//import
#include <stdio.h>
#include <stdlib.h>
//public static void main(String[] args)
main(){
//System.out.println();
printf("Hello World From C.\n");
//瞬间开启了一个控制台,瞬间没了
// 注意:要关闭控制台才能再编译
// system("mspaint"); //打开画图
//system("calc"); //打开计算器
//shudown -shu 关机 shutdown -a 取消关机
system("java HelloWorld");//调用java代码
system("pause");//让控制台暂停 调用系统的指令
}
HelloWorld.java
class HelloWorld{
public static void main(String[] args){
System.out.println("Hello World Form Java.");
}
}
//控制台 ->cmd E: ->进入E盘
//复制本文件路径,不要有中文
//cd 粘贴上面路径 ->进入本文件目录
//怎么处理警告:编码 GBK 的不可映射字符
//输入javac -encoding utf-8 文件名.java 就可以解决了。
//编译 javac HelloWorld.java
//运行 java HelloWorld
控制台
Hello World From C.
Hello World Form Java.
请按任意键继续. . .
目标:能看懂C代码,会读会调用
二、C语言的基本数据类型
/*
-java
byte short char int float double long boolean
1 2 2 4 4 8 8 1
String 不是基本类型,只是代表一个类,是一个引用类型。
boolean: https://blog.csdn.net/qq_35181209/article/details/77016508
-C language
char short int float double long void signed unsigned
1 2 4 4 8 4
*/
#include <stdio.h>
#include <stdlib.h>
main(){
//测试每种数据类型占几个字节
printf("char 类型在c语言中占%d\n",sizeof(char));
printf("int 类型在c语言中占%d\n",sizeof(int));
printf("float 类型在c语言中占%d\n",sizeof(float));
printf("double 类型在c语言中占%d\n",sizeof( double));
printf("long 类型在c语言中占%d\n",sizeof(long));
printf("short 类型在c语言中占%d\n",sizeof(short));
/*
char 类型在c语言中占1
int 类型在c语言中占4
float 类型在c语言中占4
double 类型在c语言中占8
long 类型在c语言中占4
short 类型在c语言中占2
请按任意键继续. . .
*/
// java int float double short类型 可以直接用c语言中 代替
// c语言中long java int
// java long 用 long long
// signed, 有符号
// unsigned, 无符号
system("pause");
return 0;
}
三、基本输入输出函数
/*
C语言输出需要占位符
%d - int
%ld – long int
%c - char
%f - float
%u – 无符号数
%hd – 短整型 short half d
%lf – double long float
%x – 十六进制输出 int 或者long int 或者short int
%o - 八进制输出
%s – 字符串 String 但是c没有
*/
#include <stdio.h>
#include <stdlib.h>
main(){
int i=255;//ff
float f=3.14;
double d=3.1415926535;
char c=\'f\';
short s= 123;
// 占位符和后面数据类型要一一对应
printf("int = %d\n",i);
printf("float = %f\n",f); // 小数 默认都保留6位 整数部分 小数点位置 小数部分
printf("double = %lf\n",d);
printf("char = %c\n",c);
printf("short = %d\n",s); //自动转换
printf("int 的16进制表示形式为 %#x\n",i); // %#x 代表 输出16进制 前面加0x112
// 只能把中括号放在后面
char str[] ={\'h\',\'l\',\'l\',\'o\',\'\0\'};// \0代表的结束
printf("字符串%s\n", str); //输出字符数组
system("pause");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
main(){
printf("请输入一个整数");
// 输入一个数 存放到一个int类型中
int i;// 定义变量接受
// scanf()
scanf("%d",&i);// &i 拿到i的内存地址
printf("i的值为%d\n",i);
// 想输入一段字母 存到字符数组中
printf("请输入一串字符");
char c[]={\' \',\' \',\' \',\' \',\' \'};
scanf("%s",c);// 读取输入的字符串 存放到字符数组中 //为什么不加&? 数组名就是地址
// println(c);//
printf("输入的字符串为%s",c);
}
四、指针(***)
指针就是代表内存地址
地址就是系统给内存分配的编号
野指针 随机安排内存地址(不给指针赋值而直接调用,会产生野指针 )
参考资料:产生野指针及其危害
指针和指针变量不同
指针变量是储存地址的一个变量,不同的指针变量对应不同的数据类型,指针变量也是变量,本身也有它自己的内存地址。
int i=3;// 定义了一个int类型的变量i i的值 是3
//&i;// 得到i的地址 //把地址存起来
// 指针变量
int* p=&i;// 定义了一个int类型的指针变量 p p的值为 i的地址
1、指针可以修改变量的值
*号的三种含义
- 乘法
- 如果
*在类型后 代表是该类型的指针变量类型int* p=&i; -
*如果再指针变量前 代表的是指针变量储存的內存地址对应的值*p
#include <stdio.h>
#include <stdlib.h>
main(){
int i=3;
int j=4;
int* p=&i;
//*p// 代表的是 p变量的值指向内存地址对应的值
printf("i的值为%d\n",i);
printf("p的值为%#x\n",p);
// 修改i的值 会不会改变 p的值呢 ?不会
// i=5;
// printf("修改i后i的值为%d\n",i);
// printf("修改i后p的值为%#x\n",p);
// 修改p的值 会不会改变 i的值呢 ?不会
// p=&j;
// printf("修改p的值后i的值为%d\n",i);
// printf("修改p的值后p的值为%#x\n",p);
// 修改 *p的值 i的值会不会变 ?会
*p=5;
printf("i的值为%d\n",i);
// 修改 i的值 *p的值会不会变 ?会
i =10;
printf("*p的值为%d\n",*p);
system("pause");// 让控制台暂停 调用系统的指令
}
案例:交换两个数的值
其实Java的参数传递完全等同于赋值运算符的操作
参考资料:Java 到底是值传递还是引用传递?
c语言形参值的任何变化不会影响到实参的值
参考资料:值传递和地址传递,C语言函数传参方式详解
#include <stdio.h>
#include <stdlib.h>
main(){
int a = 3;
int b = 4;
swap1(a,b);
printf("a的值为%d,b的值为%d\n",a,b);
swap2(&a,&b);
printf("a的值为%d,b的值为%d\n",a,b);
/*
a的值为3,b的值为4
a的值为4,b的值为3
*/
}
//值传递
swap1(int a,int b){
int temp;
temp = a;
a = b;
b = temp;
}
//引用传递,指针传递
swap2(int* a, int* b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
2、多级指针
想要自动它们的值,*数与级数相同即可。
#include <stdio.h>
#include <stdlib.h>
main()
{
int i=3;
int* p=&i;
int** q=&p;
int*** x=&q;
int**** y=&x;
printf("i的值为%d\n",i);
printf("p的值为%#x\n",p);
printf("q的值为%#x\n",q);
printf("x的值为%#x\n",x);
printf("y的值为%#x\n",y);
printf("****y对应的值%d\n",****y);
//printf("****y对应的值%d",i);
printf("***x对应的值%d\n",***x);
system("pause");
}
i的值为3
p的值为0x62fe14
q的值为0x62fe08
x的值为0x62fe00
y的值为0x62fdf8
****y对应的值3
***x对应的值3
请按任意键继续. . .
3、指针和数组的关系
- 数组中的元素在一块连续的内存地址中
- 数组变量的内存地址对应的是数组中第一个元素的内存地址
- 不同类型的指针默认的偏移量是不一样的,因为它们对应的数据类型的字节不同。
比如int*偏移4个字节 ,char*偏移2个字节
指针偏移
运用:遍历数组*(arr+i)
注意:*的优先级比+高,*(a+1)中的括号是不可省略的,*a+1表示先取第一个元素的值,再加1。
#include <stdio.h>
#include <stdlib.h>
printArr(int arr[],int len){
int i=0;
for(;i<len;i++){
printf("数组中第%d元素为%d\n",i,arr[i]);
printf("数组中第%d元素为%d\n",i,*(arr+i));
}
}
main(){
// 从键盘录入一个数组 然后打印到控制台上
// scanf()
int len;
printf("请输入数组的长度\n");
scanf("%d",&len);
int arr[len];
// c99标准下 不能再for循环中 定义变量
int i=0;
for(;i<len;i++){
printf("请输入数组中的第%d元素\n",i);
scanf("%d",&arr[i]);
}
// 打印数组
printArr(arr,len);
system("pause");// 让控制台暂停 调用系统的指令
}
4、指针长度
指针的长度不是固定的,取决你的编译器
如果是32位的编译器,指针类型默认是4个字节
如果是64位的编译器,指针类型默认是8个字节
指针只需要知道 它的地址和偏移量
#include <stdio.h>
#include <stdlib.h>
main(){
int i=123;
short s=321;
float f=3.14;
double d=3.1415;
int* p=&i;
float* q=&f;
short* t=&s;
double* r=&d;
// sizeof();
printf("int* 的长度为%d\n",sizeof(p));
printf("float* 的长度为%d\n",sizeof(q));
printf("short* 的长度为%d\n",sizeof(t));
printf("double* 的长度为%d\n",sizeof(r));
system("pause");// 让控制台暂停 调用系统的指令
}
int* 的长度为4
float* 的长度为4
short* 的长度为4
double* 的长度为4
请按任意键继续. . .
int* 的长度为8
float* 的长度为8
short* 的长度为8
double* 的长度为8
请按任意键继续. . .
注:
sizeof 判断数据类型长度符的关键字
sizeof()
java没有sizeof()
java有 instanceOf
Java关键字(一)——instanceof
5、指针的运算
加减法
指针的运算在连续的内存空间内才有意义
6、函数的指针
定义:函数返回值类型 (* 指针变量名) (函数参数列表);
指向函数的指针变量同我们之前讲的指向变量的指针变量的定义方式是不同的
操作步骤:
- 声明函数
int add(int x ,int y){……} - 定义
int (*pf)(int x ,int y); //int的位置是返回值类型 - 赋值
pf = add;//add是函数名 - 指针调用函数
int i = (*pf)(4,5);
案例:
int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/
int y =2,int i = (*p)(y) /*通过函数指针调用Func函数*/
# include <stdio.h>
int Max(int, int); //函数声明
int main(void)
{
int(*p)(int, int); //定义一个函数指针
int a, b, c;
p = Max; //把函数Max赋给指针变量p, 使p指向Max函数
printf("please enter a and b:");
scanf("%d%d", &a, &b);
c = (*p)(a, b); //通过函数指针调用Max函数
printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
return 0;
}
int Max(int x, int y) //定义Max函数
{
int z;
if (x > y)
{
z = x;
}
else
{
z = y;
}
return z;
}
7、指针表示字符串( char* c 可以表示字符串 )
字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以 \0 作为串的结束。
可以指针偏移,就像操作数组那样子
// import
#include <stdio.h>
#include <stdlib.h>
// public static void main(String[] args)
main(){
char c1[]="HelloWorld";
char* c2 ="HelloWorld";// 代表的字符串的地址 <-> char c[];
printf("%s\n",c1);
printf("%s\n",c2);
system("pause");// 让控制台暂停 调用系统的指令
}
*a只是指向一个字符
#include <stdio.h>
#include <stdlib.h>
int main(void){
char *a= "bcd" ;
printf("输出字符:%c \n", *a); /*输出字符,使用"%c"*/
printf("输出字符:%c \n", *(a+1) ); /*输出字符,使用"%c"*/
printf("输出字符串:%s \n", a); /*输出字符串,使用"%s";而且a之前不能有星号"*" */
system("pause");
}
参考资料: C语言指针的使用、字符串和指针详解(详细、易懂)
五、内存分配
1、静态内存
- 静态内存可以被系统自动回收
如何实现?想让主函数知道子函数的变量的内存地址
#include <stdio.h>
#include <stdlib.h>
//C语言函数想要改变值需要传引用,传递引用
f(int** p){
int i=3;
*p=&i;
printf("子函数的i的的地址是%#x\n",&i);
// 内存自动回收
}
main(){
int* p;// 定义一个int类型的指针接受内存地址
f(&p);
printf("子函数的i的的地址是%#x\n",p);
printf("i的值是%d\n",*p);
system("pause");// 让控制台暂停 调用系统的指令
}
子函数的i的的地址是0x62fddc
子函数的i的的地址是0x62fddc
i的值是0
请按任意键继续. . .
为什么i的值是0?- 系统内存自动回收
2、动态内存
需要手动回收
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
//引入头文件 <malloc.h>
main(){
int* p = (int* )malloc(sizeof(int));
//int* p 定义了一个int类型的 *p
//(int*)malloc(sizeof(int)) 给指针分配了一块内存地址
*p =3;
printf("%d\n",*p);
free(p);//手动回收内存
printf("%d\n",*p);
system("pause");// 让控制台暂停 调用系统的指令
}
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef int* point;// 给类型起一个别名
//传递引用
f(int** p){
point j;
j =(int*)malloc(sizeof(int));
*j=3;
*p=j;
printf("子函数的i的的地址是%#x\n",*p);
// 内存自动回收
//free(j); // 动态分配内存 需要手动的回收
}
main(){
// 想让主函数 知道子函数的变量的内存地址
int* p;// 定义一个int类型的指针接受内存地址
f(&p);
printf("子函数的i的的地址是%#x\n",p);
printf("*p的值是%d\n",*p); //3
system("pause");// 让控制台暂停 调用系统的指令
}
子函数的i的的地址是0x7d13e0
子函数的i的的地址是0x7d13e0
*p的值是3
请按任意键继续. . .
C语言内存四区
- .data 常量池
- .code 代码区 存放的都是代码 比如
main() - .stack 所有静态分配的内存都放在栈内存中,连续分配
- .heap 动态分配的内存都放在堆内存中, 不连续分配内存。
free()手动释放内存。硬盘删除文件, 格式化硬盘30次才能彻底删除文件
参考资料: c语言中的堆、栈和内存映射
六、结构体,联合体
java中用类来封装对象
class Person{
//成员变量(属性),成员方法(行为)
int age;
String name;
char sex;
public Person(){
}
public void setAge(){
}
public void getAge(){
}
}
c语言用结构体
#include <stdlib.h>
#include <malloc.h>
struct Person{
int age;// 4 *2
char sex; // 1
};
main(){
struct Person p={5,\'m\'};
// struct person={5,\'m\'};
// printf("结构体中的元素分别是%d,%c",person.age,person.sex);
// 打印结构体的长度
printf("结构体的长度是多少啊%d",sizeof(p)); //8
system("pause");
}
参考资料:c语言结构体
#include <stdio.h>
#include <stdlib.h>
main(){
struct date { double year;int month;char day; }today; // 结构体 13个字节
// 超过 4 字节 按四个字节以上 不到4个字节 按4个字节做
union { double i; int k; char ii; } mix; // 联合体
printf("date:%d\n",sizeof(struct date)); //12
printf("mix:%d\n",sizeof(mix)); // 9 4 // 联合体只能给一部分元素复制 联合体的长度 相当于里面占最大内存长度的元素的长度
system("pause");// 让控制台暂停 调用系统的指令
}
进程相关函数
程序的运行过程
程序是如何运行起来的?
-
在内存中开辟(划出)一块内存空间
-
将硬盘上可执行文件中的代码(机器指令)拷贝到划出的内存空间中
-
pc指令指向第一条指令,cpu取指运行
当有操作系统时,以上过程肯定都是通过调用相应的API来实现的。
在Linux下,os提供两个非常关键的API,一个是fork,一个是exec.
fork: 开辟出一块内存空间
exec: 将程序代码(机器指令)拷贝到开辟的内存空间中,并让pc指向第一条指令,
cpu开始执行,进程就运行起来了。运行起来的进程会与其他进程切换着并发运行
fork
如何让父子进程做不同的事情?
wait()会暂时停止目前进程的执行, 直到有信号来到或子进程结束
在linux进程中通常使用fork函数来创建父子进程,虽然fork函数采用的是写实拷贝技术,但是当创建的子进程并不想继续与父进程相关的操作时那些拷贝的内容就纯粹属于浪费,那么一个子进程怎样变成一个全新的进程,此时exec函数族的函数就派上用场了。
exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
exec函数族共有6个函数,其中的5个是库函数,只有execve是系统调用。下边来介绍函数名字中的’l’, ‘p’, ‘e’, ‘v’的含义。
‘l’表示传递的参数是以列表形式出现即分开的,需要一个一个的传递,比如:execl(“/bin/ps”, “ps”, “ajx”, NULL), 其中第一个参数代表传递的一个要执行的程序的路径,第二个参数必须和第一个参数的文件保持一致,后边的 ajx,NULL都是参数,而且最后一个参数必须是NULL。
......
exec函数族可以通过不同的参数组合来运行一个新进程,只不过不同组合传递参数的方法和意义不同而已,只要掌握明白l, p, v, e的意义就可以搞定了。