C语言是静态、编译型语言,在编译时就会生成机器码,运行时直接执行,因此执行速度非常快。
Java是一种解释型语言,需要先将源代码翻译成字节码再由虚拟机执行。
1、编译到执行的消耗
C语言直接就生成了机器执行的机器码,而Java需要先编译成字节码这种中间代码,然后JVM在执行的时候还要再将字节码编译为机器码去真正的执行
2、C语言不需要垃圾回收机制
Java垃圾回收器本身也消耗了不少的资源,从而导致程序性能下降。
比如:Java程序运行时需要额外消耗时间和空间来检查和清理内存中未使用的对象。这个过程需要不断地扫描内存,并判断哪些对象可以被释放。由于能够处理的对象数量巨大,这个过程需要消耗大量的时间和空间。
而在手动释放内存的C语言中,程序员能够直接控制内存的分配和释放,避免了垃圾回收的额外开销,从而使程序运行更快。
另外,Java中,肯定会产生内存碎片,这些也导致内存利用率低
C语言和Java在性能方面有不同的优缺点。
因此:
C语言可以直接访问硬件资源,原因是C语言是静态、编译型语言,可以直接生成机器码执行,并且允许对内存地址进行直接操作,从而对硬件进行控制。下面是一个例子:
#include
#include
#include
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语言源代码文件,其中包含了C语言的实现代码,是程序的核心部分。它可以被编译器编译成可执行文件,直接运行在计算机上。
.h文件是指头文件(Header File),也是一种C语言源代码文件。其中包含了函数、变量和常量的声明,用于在多个源文件中共享实现。头文件通常被包含在.c文件的前面,可以在.c文件中使用其中定义的函数和变量。
因此,.c文件和.h文件一起构成了C语言程序的基本结构。
C语言中的宏指的是一种预处理指令,也称为宏定义(Macro Definition)。它可以将一组代码文本替换成另一组代码文本,以达到简化代码、提高代码重用性的目的。宏定义通常使用 #define 指令来完成。
宏和Java中的常量很相似,它们本质上都是表示在程序执行过程中不可修改的值。
但是宏可以用来表示常量、表达式、函数等多种形式的值,Java中的常量只能表示基本类型和字符串类型的值。
宏并没有类型的限制,因此有可能会出现一些类型错误的问题。Java中的常量具有类型限制,避免了出现类型错误的问题。
使用宏定义来定义常量:#define PI 3.1415926
这样就可以在代码中直接使用 PI 来代替 3.1415926,提高代码的可读性和可维护性。
宏定义虽然可以简化代码,但需要注意的是,宏定义只是简单的文本替换,因此容易产生不容易发现的错误,例如忘记将参数加上括号。此外,宏定义可能会造成代码的可读性降低,也可能导致代码体积变大。因此,在使用宏定义时需要谨慎考虑。
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。
在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。
C 语言为内存的分配和管理提供了几个函数。这些函数可以在
在 C 语言中,内存是通过指针变量来管理的。指针是一个变量,它存储了一个内存地址,这个内存地址可以指向任何数据类型的变量,包括整数、浮点数、字符和数组等。C 语言提供了一些函数和运算符,使得程序员可以对内存进行操作,包括分配、释放、移动和复制等。
案例:
#include
#include
#include
int main()
{
char name[100];
char *description;
strcpy(name, "Zara Ali");
/* 动态分配内存 */
description = (char *)malloc( 30 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student.");
}
/* 假设您想要存储更大的描述信息 */
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
/* 使用 free() 函数释放内存 */
free(description);
}
当上面的代码被编译和执行时,它会产生下列结果:
Name = Zara Ali
Description: Zara ali a DPS student.She is in class 10th
malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。
free() 函数:用于释放先前分配的内存。它接受一个指向要释放内存的指针作为参数,并将该内存标记为未使用状态。
calloc() 函数:用于动态分配内存,并将其初始化为零。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。
realloc() 函数:用于重新分配内存。它接受两个参数,即一个先前分配的指针和一个新的内存大小,然后尝试重新调整先前分配的内存块的大小。如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。
sizeof 运算符:用于获取数据类型或变量的大小(以字节为单位)。
指针运算符:用于获取指针所指向的内存地址或变量的值。
& 运算符:用于获取变量的内存地址。
* 运算符:用于获取指针所指向的变量的值。
-> 运算符:用于指针访问结构体成员,语法为 pointer->member,等价于 (*pointer).member。
memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。