总结:C语言与Java

时间:2021-07-30 01:25:12

一、机器码与字节码的区别

1、介绍

  • 机器码:是特定计算机硬件平台上汇编代码经过汇编器汇编成的二进制指令码。
  • 字节码:是一种中间代码,是通过Java编译器将Java源代码编译成的一种与特定计算机平台无关的二进制指令码。字节码通常是在Java虚拟机上运行的。

2、C与Java的编译

C语言是静态、编译型语言,在编译时就会生成机器码,运行时直接执行,因此执行速度非常快。

Java是一种解释型语言,需要先将源代码翻译成字节码再由虚拟机执行。

  • 在Java程序运行时,Java虚拟机需要将字节码转换成机器码以实现程序的执行。这个过程分为两个步骤,首先是通过即时编译技术(JIT)将热点代码即时编译成本地机器码,然后再由本地机器码执行程序的操作。因此,虽然字节码不是直接执行的机器指令,但它最终也是要转换成机器码来执行程序的。

二、C语言为什么比Java的性能好?

1、编译到执行的消耗

C语言直接就生成了机器执行的机器码,而Java需要先编译成字节码这种中间代码,然后JVM在执行的时候还要再将字节码编译为机器码去真正的执行

2、C语言不需要垃圾回收机制

Java垃圾回收器本身也消耗了不少的资源,从而导致程序性能下降。

比如:Java程序运行时需要额外消耗时间和空间来检查和清理内存中未使用的对象。这个过程需要不断地扫描内存,并判断哪些对象可以被释放。由于能够处理的对象数量巨大,这个过程需要消耗大量的时间和空间。

而在手动释放内存的C语言中,程序员能够直接控制内存的分配和释放,避免了垃圾回收的额外开销,从而使程序运行更快。

另外,Java中,肯定会产生内存碎片,这些也导致内存利用率低

三、Java与C各自的优势

C语言和Java在性能方面有不同的优缺点。

  • C语言是静态、编译型语言,在编译时就会生成机器码,运行时直接执行,因此执行速度非常快。C语言还允许直接访问硬件资源,这使得其在系统编程和嵌入式开发领域非常受欢迎。
    • C语言没有自动垃圾回收机制,需要程序员手动管理内存。C语言中的变量和数据都存储在内存中,程序需要显式地分配和释放内存,否则就会造成内存泄漏等问题。
      • 这个好处是,由于不需要垃圾收集器,因此性能高
      • 缺点是,对程序员要求比较高,时刻关注内存回收相关代码
  • 相比之下,Java是一种解释型语言,需要先将源代码翻译成字节码再由虚拟机执行。
    • 虽然Java虚拟机的JIT编译器可以根据不同的环境和数据对代码进行一定的优化,但是其执行速度相较于C语言而言更慢一些。Java还有垃圾回收机制,这在一定程度上会影响程序的运行速度,特别是当程序的内存消耗较大时。
    • 但是,Java也有其优势。Java的面向对象编程模式、内存自动管理、强类型检查等特性使得其具有更高的安全性、可维护性和移植性,能够以更高效的方式进行大规模软件开发。

因此:

  • C语言在性能方面有明显优势,适用于对速度要求较高、需要直接操作机器硬件的场景;
  • 而Java则适用于需要更高安全性、可维护性和移植性的大规模软件开发。

四、C语言直接访问硬件资源案例

C语言可以直接访问硬件资源,原因是C语言是静态、编译型语言,可以直接生成机器码执行,并且允许对内存地址进行直接操作,从而对硬件进行控制。下面是一个例子:

#include <stdio.h>
#include <conio.h>
#include <bios.h>

int main(void) {
    int ch;
    int status = _bios_keybrd(_KEYBRD_READ);
    
    printf("status = %04X\n", status);
    
    while ((ch = getch()) != ESC) {
        printf("key code: %02X\n", ch);
    }
    
    return 0;
}

这个程序利用了C语言的特性,使用bios.h头文件中的_bios_keybrd函数读取键盘状态,然后根据读取的结果进行相应的操作。这种直接访问硬件资源的方式在系统编程和嵌入式开发中非常有用。

相比之下,Java不支持直接访问硬件资源。这是因为Java语言的主要设计目标之一是保证程序的安全性,避免因为直接访问硬件资源而导致的安全问题。Java虚拟机(JVM)为了实现跨平台的兼容性,实现了一层抽象,对系统资源进行统一的封装和管理,来保证程序的可移植性与安全性。因此,Java程序无法直接操作硬件资源,需要通过特定的API来间接操作硬件资源,例如Java Native Interface(JNI)等。

虽然Java无法像C语言那样直接操作硬件资源,但是Java仍然可以通过JNI调用C语言函数,从而实现硬件操作的需求。不过需要注意的是,这样做可能会降低Java程序的可移植性和安全性,并且需要手动管理内存等问题。因此,需要权衡利弊,选择适合的方法来操作硬件资源。

以下继续提供一些C访问硬件的例子:

/* 案例:访问摄像头设备 */
int main()
{
    int fd;
    fd = open("/dev/video0", O_RDWR);   //以读写方式打开摄像头设备
    ioctl(fd, VIDIOC_QUERYCAP, &cap);   //查询设备属性
    ...
    close(fd);   //关闭摄像头设备
    return 0;
}

这段代码通过调用 Linux 系统提供的摄像头操作接口(open、ioctl、close)实现对摄像头设备的访问,并查询设备属性。
/* 案例:访问麦克风 */
int main()
{
    int fd;
    fd = open("/dev/dsp", O_RDONLY);   //以只读方式打开麦克风设备
    ioctl(fd, SOUND_MIXER_READ_VOLUME, &vol);  //读取音量
    ...
    close(fd);   //关闭麦克风设备
    return 0;
}

这段代码通过调用 Linux 系统提供的麦克风操作接口(open、ioctl、close)实现对麦克风设备的访问,并读取音量。
/* 案例:访问打印机 */
int main()
{
    FILE *fp;
    fp = fopen("/dev/usb/lp0", "w");   //以写方式打开打印机设备
    fprintf(fp, "Hello, printer!\n");   //向打印机写入数据
    fclose(fp);   //关闭打印机设备
    return 0;
}

这段代码通过 Linux 文件系统提供的文件操作接口(fopen、fprintf、fclose)实现对打印机设备的访问,并向打印机写入数据。

/* 案例:访问键盘 */
int main()
{
    int fd;
    struct input_event event;
    fd = open("/dev/input/event0", O_RDONLY);   //以只读方式打开键盘设备
    while(1)
    {
        read(fd, &event, sizeof(event));   //读取键盘事件
        if (event.type == EV_KEY && event.value == 1)   //判断是否为按键事件
        {
            printf("Key pressed: %d\n", event.code);   //输出按键编号
        }
    }
    close(fd);   //关闭键盘设备
    return 0;
}

这段代码通过调用 Linux 系统提供的输入设备操作接口(open、read、close)实现对键盘设备的访问,并读取键盘事件,并判断是否为按键事件,最后输出按键编号。
/* 案例:访问鼠标 */
int main()
{
    int fd;
    struct input_event event;
    fd = open("/dev/input/mice", O_RDONLY);   //以只读方式打开鼠标设备
    while(1)
    {
        read(fd, &event, sizeof(event));   //读取鼠标事件
        if (event.type == EV_REL && event.code == REL_X)   //判断是否为横向移动事件
        {
            printf("X moved: %d\n", event.value);   //输出横向移动距离
        }
    }
    close(fd);   //关闭鼠标设备
    return 0;
}

过调用 Linux 系统提供的输入设备操作接口(open、read、close)实现对鼠标设备的访问,并读取鼠标事件,并判断是否为横向移动事件,最后输出横向移动距离。
/* 案例:访问定时器 */
int main()
{
    int fd;
    struct itimerval timer;
    fd = open("/dev/rtc0", O_RDONLY);   //以只读方式打开定时器设备
    timer.it_value.tv_sec = 1;   //设置定时时间为1秒
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = 1;   //设置定时周期为1秒
    timer.it_interval.tv_usec = 0;
    setitimer(ITIMER_REAL, &timer, NULL);   //设置定时器
    while(1)
    {
        ...   //执行定时任务
    }
    close(fd);   //关闭定时器设备
    return 0;
}
这段代码通过调用 Linux 系统提供的定时器操作接口(open、setitimer、close)实现对定时器设备的访问,并设置定时时间和周期,最后执行定时任务。

五、C语言中.c文件和.h文件

.c文件是指C语言源代码文件,其中包含了C语言的实现代码,是程序的核心部分。它可以被编译器编译成可执行文件,直接运行在计算机上。

.h文件是指头文件(Header File),也是一种C语言源代码文件。其中包含了函数、变量和常量的声明,用于在多个源文件*享实现。头文件通常被包含在.c文件的前面,可以在.c文件中使用其中定义的函数和变量。

因此,.c文件和.h文件一起构成了C语言程序的基本结构。

六、C语言中宏

C语言中的宏指的是一种预处理指令,也称为宏定义(Macro Definition)。它可以将一组代码文本替换成另一组代码文本,以达到简化代码、提高代码重用性的目的。宏定义通常使用 #define 指令来完成。

宏和Java中的常量很相似,它们本质上都是表示在程序执行过程中不可修改的值

但是宏可以用来表示常量、表达式、函数等多种形式的值,Java中的常量只能表示基本类型和字符串类型的值。

宏并没有类型的限制,因此有可能会出现一些类型错误的问题。Java中的常量具有类型限制,避免了出现类型错误的问题。

使用宏定义来定义常量:#define PI 3.1415926
这样就可以在代码中直接使用 PI 来代替 3.1415926,提高代码的可读性和可维护性。

宏定义虽然可以简化代码,但需要注意的是,宏定义只是简单的文本替换,因此容易产生不容易发现的错误,例如忘记将参数加上括号。此外,宏定义可能会造成代码的可读性降低,也可能导致代码体积变大。因此,在使用宏定义时需要谨慎考虑。