-
请问C++线上测评时的data和output目录在哪啊,我是不是只要提交CodeCraft-2022文件夹就行了,但是一直显示运行失败,我猜测是源码里csv数据读取路径错了,不知道是哪个路径啊
-
【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
RT
-
如题,如果第三方库以源码的形式进行编译提交,是否会被认定为作弊。 C++/
-
【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
*本文转载自InfoQ-专访崔慧敏教授:令开发者“为难”的异构编程,以及生态发展建议* 作者:王一鹏(InfoQ 主编) > 本文专访嘉宾:崔慧敏,中科院计算所研究员。 > > 崔慧敏研究员关注异构环境下的编程和编译方向, > > 具体的研究领域包括:异构编程模型,异构编译优化,数据中心编程与编译技术等。 关注 InfoQ “2021 年度技术盘点与展望”的朋友会发现,在编程语言部分,异构编程以及对多核算力的充分利用,已经成为编程语言领域众多研究和开发人员所关注的重点。 我们可以将异构编程受瞩目的原因归结为随着“[云边端一体化](https://xie.infoq.cn/article/213d1a5bafe4913186d441e78)”的发展,CPU、GPU 混合应用,构成了当下主要的算力基础设施。但从另一方面讲,与硬件的高度协同,本来也是编程语言的设计初衷。用中科院计算所研究员崔慧敏教授的话说,就是: “编程语言设计,一直存在两个目标:一个就是让编程越来越容易;另外一个目标就是,在新的硬件架构出现后,编程语言要能够充分利用硬件特质,发挥其性能。” 但无论是 CPU 、GPU 还是 FPGA,其演进时间都不算短,为何在当下,我们对其仍然知之甚少呢?一方面是生态原因,因为整个开发者群体呈倒金字塔分布,大部分顶层的开发者不需要关注太多的底层细节,因而异构编程生态圈较小;另一方面则出在异构编程自己身上——它实在是太难了。 ## 令开发者“为难”的异构编程 在早期,编程语言会通过引入新特性来支持多核算力,更充分地利用硬件性能。比如 C/C++引入了 OpenMP , 用于共享内存并行系统的多处理器程序设计,通过多线程的形式,榨干多核算力。 随着 GPU 更多地被部署在云计算、数据科学、AI 等领域,支持异构编程的框架开始出现。现在应用最广的是英伟达基于自家硬件推出的 CUDA 框架。 CUDA 无疑是开创性的,率先让 GPU 脱离了“显卡”的范畴,作为一枚强大的芯片,可以被程序员直接使用硬件访问接口进行编程。后来的 oneAPI、C++ AMP、OpenCL 都是不断围绕[异构编程](https://www.infoq.cn/article/FShKRWXoUJ7b8C801Z9i)的概念做新的研发工具迭代。 但这样的发展态势,也给异构编程带来了麻烦:由于硬件本身仍是多元化的状态,大部分厂商更偏向围绕自己的硬件进行开发,没有也不愿分散太多资源用于支持其他厂商的硬件编程框架,导致异构编程的方案始终不够收敛。 到今天为止,异构编程的性能调优,始终是个相当大的挑战,它需要程序员不仅对软件研发有足够深入的了解,同时对各类型不同的硬件也有深刻认知。 崔慧敏教授以自己团队的日常工作为例,详细描述了异构编程的工作难度: “团队那时在做一个关于超算的大型应用,其中一项工作是对泊松求解器进行研发和性能调优。按计划,我们首先要对这个应用进行拆分(很多 C 语言代码的模块化做得并不好),然后逐个步骤评估计算量,再将计算量最大的部分加载到加速核上执行。 当时我们发现其中两个步骤计算量最大,于是用 OpenCL 做了初步的实现,但测试完后,却发现性能非常差。我们分析了下原因,才发现所有的存储都在外部,而片上的高速存储没有用好。为了充分利用片上存储,我们调研、使用了循环分块、数据双缓冲等等机制,但应用之后,发现性能还是不够好。 没办法,只能继续分析。很快我们发现,虽然存储这里没问题了,但取数据的带宽没有充分利用起来。而超算芯片自有一套非常复杂的取数机制,为了做好这一块的性能调优,我们花费了大量的时间。 但调整了取数带宽,并没有完全解决问题,我们又发现,不规则的数据访问,导致分块也变得不合理……调整了分块,意味着向量化的策略也要重新调整…… 最终,当我们把性能调整到一个可接受的水平,已经用掉了 10 个人月的时间(项目组共两人,每人占用 5-6 个月时间)。” ## 编译器的黄金时代,已经来了 尽管许多开发者在做程序跨硬件迁移时,其工作并不像崔教授基于超算平台做性能调优这样复杂,性能要求也不会这么高,但其难度仍是公认较高的。这背后的原因要追究到 CPU、GPU 乃至 FPGA 的硬件设计差异上。 CPU 有比较丰富的多元化指令,存在片上的多级缓存。要基于 CPU 进行程序调优,首先要想办法将程序划分为颗粒度更细的并行任务,并在负载均衡的情况下,分配到各个核上去执行,让每一组线程的流水线尽可能排满,让共享内存的访问锁尽可能的少。 而相比 CPU ,基于 GPU 的研发,需要将程序划分的比 CPU 并发性更高,颗粒度更细。划分完成后,还要考虑数据在存储上的摆放。同时,因为 GPU 擅长处理比较规整的计算,也需要我们更加理解这种较为规整的计算模型,甚至涉及部分数据结构的重新设计。同时,GPU 有很多浮点运算部件,需要找到足够多的浮点运算工作来充分利用其特性。 FPGA 与以上编程模式都非常不同。无论 CPU 还是 GPU ,本质上还是完成从软件到硬件的映射,但 FPGA 的关键在于精心设计生成线路,以充分利用硬件性能。早期的 FPGA 完全基于底层硬件描述语言编写,因此使研发工作变得非常困难。 因为硬件的巨大差异,导致异构编程通常只能选择中庸方案,在 CPU 上达到了极致性能,几乎一定会导致在 GPU、FPGA 上性能较差。 映射在开发者身上,就造成了迁移工作的困难。所以,另一类方案是基于支持异构编程的新型编程语言来做,C/C++ OpenMP 只是早期形态,更为典型的案例出现在国内 —— 华为在 2021 年推出的毕昇 C++,并发布了北冥多样计算性计算架构。据华为官方介绍,毕昇 C++是支持多样算力的通用编程语言,以 C++为基础,支持 SYCL 异构编程标准,以一种语言即可对鲲鹏、昇腾及业界主流算力进行编程。为了更好地配合毕昇 C++ ,华为同时发布了一个加速库,全称叫做“北冥融合加速库 2.0”为领域关键功能加速提供基于 API 的编程调用,进一步屏蔽硬件细节。 而北冥多样计算性计算架构主要面向多样性算力集群的软件开发需求,融合开发语言、编译器、调度器、开发框架、计算套件、开发工具链在内的整套多样性算力开发解决方案与生态。毕昇 C++、北冥融合加速库 2.0 都是计算架构的关键组件,相当于面向开发者的整体解决方案集成。 对毕昇 C++ 及[北冥多样计算性计算架构](https://www.infoq.cn/article/5P6ZH8hpluGICooLvPSW)而言,其采用的 SYCL 异构编程标准是业界一大创新和亮点。SYCL 异构编程标准是一种免版税,跨平台的抽象层,它允许使用标准 ISO C ++ 编写异构处理器的代码,并将应用程序的主机代码和内核代码包含在同一源文件中,也是业内最近关注度比较高的一套标准。许多科研人员正在基于 SYCL 进行性能方面的探索,但目前还未有来自学术界的确定性的答案。 而不管是毕昇 C++,还是北冥融合加速库, 其诞生一方面是为了缓解异构编程高度复杂的研发现状,一方面也反映了属于编译器的“黄金时代”正在到来,围绕编译器,在编程语言层面解决问题,正在成为异构编程的核心解决方案。 2017 年的两位图灵奖得主 John L. Hennessy 和 David A. Patterson 在年初的一篇报告中展望道:未来的十年将是计算机体系架构领域的“新的黄金十年”。同时,也有专家提到,编译器的“黄金十年”也到来了。 崔慧敏教授提到:“过去硬件发展得非常快,软件发展得自然也快,我们将那个时代形容为‘免费的午餐’。但在当下,‘免费的午餐’已经没有了。” David A. Patterson 在报告中提到,通过语言的改善和编译器的优化,应用程序可以实现超过六万倍的性能提升。这就要求我们对编译器有非常深入的优化,以匹配当下的硬件环境。 当然,对编程语言及编译器的优化,不仅是个技术问题,也是个产品设计问题,它将永远在易用性和性能间摇摆,以取得最佳平衡。C++ AMP 曾因为易用性广受产业界、学术界欢迎,但也因为对底层操作的过度屏蔽,导致性能存在瓶颈,最终退出舞台。 同样的挑战,放在国内,对于毕昇 C++ 来说,也必将存在,而解决问题的主要途径很可能要归结在行业生态上。 ## 生态决定未来发展 崔慧敏教授说:“早期基于 CPU 的编译器,大家对基础设施做了很好的抽象,与平台无关的部分考虑开源,其他部分考虑闭源。但现在大家更倾向自己做自己的,目前还没有对基础设施进行抽象。” 尤其是在当下硬件行业呈碎片化发展,除英特尔、英伟达的芯片外,鲲鹏、昇腾、倚天、Graviton……各类芯片百花齐放,若没有生态支撑,所谓的异构编程基础设施,很容易变成只属于某几类芯片的基础设施。开源也是个值得重点考虑的实现方式,RISC-V 的组织模式就起到了很好的示范作用。 这样从生态的视角来看,SYCL 作为开放的异构编程标准,就更有利于同样软件生态的建设,避免异构开发套件过度碎片化。 在国内,华为在毕昇 C++、北冥融合加速库方面的工作,对于整个产业而言是个很好的开始,代表着中国企业,开始在基础软件领域,全方位地向前迈进。但对比国外老牌厂商,能否实现弯道超车,则要仰仗于生态合力。 另外,崔慧敏教授认为,产业界和学术界的协同也值得引起重视。产业界有很好的工程化能力,有充足的需求和完善的平台;学术界则有很好的技术探索能力,一名学生天马行空的构想,也有可能成为未来产业界创新的解决方案。二者的充分结合,可以更好地推动行业发展。 在高校学生群体内的普及和教育,则是打造良性生态的另一个重要举措。而在学生群体里的成功推广,也是 CUDA 早期获得成功的重要原因。 在国内,华为也组建了“智能基座”产教融合协同育人基地,作为鲲鹏昇腾与高校师生的学习交流中心,其相关在线课程也包括高性能计算等内容。而同时面向高校学生的智能计算认证和鲲鹏应用开发者认证,早在 2019 年就已推出,开创了国内产学融合的新路径。 总体而言,产学研界的高度融合,再辅以一定的开源策略,或将决定异构编程生态的发展高度;而异构编程生态的发展,也将决定未来 IT 基础设施的服务能力。在国内已有企业率先迈开脚步的情况下,未来无疑充满想象。 ------ 
-
1.1 介绍PYCXX是一组C++工具,可以更轻松地编写Python3扩展。主要的优点是大大增加了程序不出现引用计数错误和不必不断坚持来自Python C API的错误返回的可能性。关于PYCXX的更多信息请访问PYCXX官网。语言:Python。一句话描述:一组C++工具,可以更轻松地编写Python3扩展。开源协议:自定义开源协议。建议的版本建议使用的版本为“PYCXX 6.2.8”。1.2 环境要求硬件要求硬件要求如表所示。硬件要求项目说明CPU鲲鹏920处理器。 软件要求软件要求如表所示。软件要求项目版本下载地址PYCXX6.2.8https://sourceforge.net/projects/cxx/files/CXX/PyCXX%20V6.2.8/pycxx-6.2.8.tar.gz 操作系统要求操作系统要求如表所示。操作系统要求项目版本下载地址openEuler20.03https://www.openeuler.org/zh/download/ 1.3 移植规划数据本章节给出PYCXX软件在移植过程中涉及到的相关软件安装规划路径的用途及详细说明。移植规划数据序号软件安装规划路径用途说明1/path/to/KPGCCGCC for openEuler的安装规划路径。参考《用户指南(GCC for openEuler)》中“GCC for openEuler安装使用”章节,将GCC for openEuler安装到规划路径。2/path/to/PYCXXPYCXX的安装规划路径。这里的安装规划路径只是一个举例说明,建议部署在共享路径中。现网需要根据实际情况调整,后续章节凡是遇到安装路径的命令,都以现网实际规划的安装路径为准进行替换,不再单独说明。 1.4 配置编译环境前提条件使用SFTP工具将各安装包上传至服务器对应目录下。配置流程配置流程序号配置项说明1基础环境搭建参考《HPC解决方案 基础环境搭建指导书》中“集群场景环境搭建”章节。2GCC for openEuler编译器环境搭建参考《用户指南(GCC for openEuler)》中“安装GCC for openEuler”章节。 1.5 获取源码操作步骤步骤 1 下载PYCXX安装包。下载地址: https://sourceforge.net/projects/cxx/files/CXX/PyCXX%20V6.2.8/pycxx-6.2.8.tar.gz。步骤 2 使用SFTP工具将PYCXX安装包上传至服务器“/path/to/PYCXX”目录。----结束1.6 编译和安装操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令PYCXX安装目录。cd /path/to/PYCXX步骤 3 执行以下命令解压PYCXX安装包。tar -zxvf pycxx-6.2.8.tar.gz步骤 4 执行以下命令进入解压后的目录。cd pycxx-6.2.8步骤 5 执行以下命令进行编译安装。python setup.py installpython setup_makefile.py linux linux.mak----结束1.7 运行和验证操作步骤步骤 1 使用PuTTY工具,以root用户登录服务器。步骤 2 执行以下命令进入PYCXX安装目录。cd /path/to/PYCXX步骤 3 执行以下命令验证是否安装成功。make -f linux.mak clean test是,表示安装成功。否,请执行1.4 配置编译环境~1.6 编译和安装重新安装或联系华为技术支持。----结束
-
# 基于KML_SPBLAS的JAVA语言适配 ### 项目介绍 本项目的主要任务是为鲲鹏处理器的高性能稀疏线性代数库函数提供Java平台适配,包括向量与矩阵,矩阵与矩阵等共32个函数。基本原理是利用Java语言的JNI调用C语言的高性能库函数接口。 下面详细介绍我们团队在此次项目中的经验总结。 ### JNI简述 **JNI**是Java Native Interface的缩写,为Java程序提供与本地程序交互的能力。使用JNI技术,能够使得Java程序充分利用本地代码的优势,如高性能,不必重复造轮子等,在生产中,有着诸多实用价值。  ### JNI工作流程 JNI的典型使用场景是:Java程序调用C,C++代码编译而成的动态库文件。动态库文件在Windows下是.dll文件,在Linux下为.so文件。其主要工作流程如图所示:  1. 编写.java源代码,其中用native关键字标明需要本地实现的函数。 2. 使用命令 `javac -h 输出位置> 源代码路径>` 编译出.h头文件。 3. 编写.cpp源代码实现.h文件中声明的native函数。 4. 编译.h头文件和.cpp源文件生成.dll动态库,并移动到.java源代码中指定的位置。 5. 使用命令 `javac 源代码路径>` 将.java源代码编译成.class字节码。 6. 使用命令 `java 类名>` 执行类名.class文件,调用.dll库文件得到输出。 ### Hello, world! 这里实现一个简单的Windows版本的demo,带读者来熟悉上述流程。 1. 编写Hello.java源代码 ```java public class Hello { // native 关键字声明native函数 native void sayHelloToC(); // 加载 java2c.dll库文件 static { System.loadLibrary("java2c");} public static void main(String []args){ Hello hello = new Hello(); hello.sayHelloToC(); } } ``` 2. 在Hello.java所在目录,打开cmd命令行,编译出Hello.h头文件。(也一并编译出了Hello.class文件) ```sh javac -encoding utf-8 -h . Hello.java ``` utf-8防止中文注释乱码,. 表示输出到当前路径下。 3. 为了简化较长的.dll编译命令,此处我们借助 Dev-C++ 开发工具来完成C++代码的编写和编译。 1. 打开Dev-C++,新建dll项目,将自动生成dll.h和dllmain.cpp两个文件。 2. 将dll.h的内容替换为Hello.h。 ```c++ /* DO NOT EDIT THIS FILE - it is machine generated */ #include /* Header for class Hello */ #ifndef _Included_Hello #define _Included_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: Hello * Method: sayHelloToC * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif ``` 3. 清空dllmain.cpp中的内容,修改为如下 ```c++ #include"dll.h" #include // 实现dll.h中声明的函数 JNIEXPORT void JNICALL Java_Hello_sayHelloToC (JNIEnv * env, jobject obj){ printf("Hello, C! I am Java."); } ``` 4. 编译生成.dll库文件。 dll.h的第一行,引入了jni.h头文件。在java安装目录下的include文件夹,找到**jni.h**文件和**jni_md.h**。将它们复制到dev-c++项目的 工具->编译选项->目录->C++包含文件 中的任一目录下。  点击编译,生成hello_c++.dll文件(hello_c++为项目名),将其重命名为java2c.dll(与1中的java源代码保持一致),移动到Hello.java所在的文件夹。 5. 编译Hello.java生成Hello.class字节码,其实在第2步已经顺带完成。 ```sh javac -encoding utf-8 Hello.java ``` 6. 在Hello.class目录下,打开cmd命令行,执行字节码程序。 ```sh java Hello ```  以上实现一个简单的demo,完成Java程序调用C++编写的动态库的目标。通过JNI技术还可以实现Java程序和C/C++程序间的数据传递,下面将一一陈述。 ### 基本数据 | Java类型 | JNI类型 | 描述 | | :------- | :------- | :--------------- | | boolean | jboolean | unsigned 8 bits | | byte | jbyte | signed 8 bits | | char | jchar | unsigned 16 bits | | short | jshort | signed 16 bits | | int | jint | signed 32 bits | | long | jlong | signed 64 bits | | float | jfloat | 32 bits | | double | jdouble | 64 bits | | void | void | void | JNI的基本数据类型与Java类型的对照表如上,下面演示传递基本类型的数据。 Hello.java中添加: ```java native double average(int x, int y); ``` Hello.h中新增: ```c++ /* * Class: Hello * Method: average * Signature: (II)D */ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv *, jobject, jint, jint); ``` java编译程序自动添加了三行注释,Class表示类名,Method表示方法名,Signature是java的函数签名,告诉我们函数的参数类型和返回类型。参数是两个Interger,返回是Double。 函数体代码中,函数返回值为jdouble与java中的double对映,函数名为程序自动生成的**Java _类名 _方法名**形式,JNIEnv参数为jni环境,jobject为native函数所在的java对象,这两个是jni自带的。后面两个jint参数对应原本java方法的int参数。 在dllmain.cpp中实现average的逻辑: ```c++ JNIEXPORT jdouble JNICALL Java_Hello_average (JNIEnv * env, jobject obj, jint x, jint y){ return jdouble(x + y) / 2; } ``` 执行输出: ```java System.out.println(hello.average(1, 2)); // 1.5 ``` ### 字符串 JNI的字符串处理函数丰富全面,此处演示传递字符串的简单例子,并以此引出JNI处理复杂数据类型时的一般流程。更多API文件见参考链接的官方文档。 Hello.java中添加: ```java native String sendMessage(String msg); ``` Hello.h中生成: ```c++ /* * Class: Hello * Method: sendMessage * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv *, jobject, jstring); ``` dllmain.cpp中实现: ```c++ JNIEXPORT jstring JNICALL Java_Hello_sendMessage (JNIEnv * env, jobject obj, jstring msg){ // 1.convert jstring to cstring const char* cMsg = env->GetStringUTFChars(msg, NULL); if(NULL == cMsg) return NULL; // 2.use cstring printf("Java: %s\n", cMsg); // 3.release resources env->ReleaseStringUTFChars(msg, cMsg); // 4.return const char* cMsg2 = "Good afternoon! Miss.Java."; return env->NewStringUTF(cMsg2); } ``` 注意:JNI中需要手动显式地释放资源,否则会造成内存泄漏。 执行输出: ```java String msg = "Good morning! Mr.C++."; System.out.println("C++: " + hello.sendMessage(msg)); // Java: Good morning! Mr.C++. // C++: Good afternoon! Miss.Java. ``` ### 对象 JNI同样可以传递对象,也就是说,JNI让C/C++代码能够访问Java类中的成员变量和方法。 从这里开始,我们使用Maven来管理我们的代码。 新建maven项目,其目录结构如下:  Hello.java的代码如下 ```java package org.example; public class Hello { static { System.loadLibrary("java2c"); } private int num = 2021; private void printNum(){ System.out.println("In Java, num is " + num); } // 演示访问成员变量和方法 native void jniMethod(); public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } } ``` 此处引入了java包,且Hello中导入了Color类,所以如果直接在Hello.java同级文件夹下调用**javac**命令,会报找不到符号的错误,应在所有源文件的根目录,即maven项目的java文件夹下,调用**javac**命令。这样编译器能将包名和路径对应起来。 进入src\main\java路径,运行: ```sh javac -encoding utf-8 -h . ./org/example/Hello.java ``` 如果源文件还引入了其他位置的包,如test文件夹下的源文件引入了main文件夹下的包,可使用`javac -cp 依赖路径>`告知java编译器依赖包的位置。更多javac选项,可使用`javac -help`自行查看。 Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: jniMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv *, jobject); ``` dllmain.h中实现定义的函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod (JNIEnv* env, jobject obj){ jclass cls = env->GetObjectClass(obj); // fieldID is certain for a class jfieldID numField = env->GetFieldID(cls, "num", "I"); // get number jint num = env->GetIntField(obj, numField); printf("In C++, num is %d\n", num); // change number env->SetIntField(obj, numField, 2035); // call method jmethodID printNumField = env->GetMethodID(cls, "printNum", "()V"); env->CallVoidMethod(obj, printNumField); return; } ``` 执行编译,在maven项目中编译执行的路径不再是源文件所在的路径。为简单起见,我们将生成的dll文件放到系统环境变量中,以便java虚拟机能够找到。执行输出得到: ```c++ public static void main(String []args){ Hello hello = new Hello(); hello.jniMethod(); } // In Java, num is 2035 // In C++, num is 2021 ``` 通过JNI,获取对象的成员变量或调用对象的主要过程如下: 1. `GetObjectClass`通过jobject获得jclass,或者`FindClass`通过类名直接获得jclass 2. 通过jclass获得jfieldID获得jmethodID。 3. 使用jfieldID获取成员变量的值或使用jmethodID调用对象的方法。 dllmain.cpp中的"I"、"()V"是java的签名字符串,可以在终端通过`javap`命令获取。 ### 数组 jni也可以传递基本类型的数组和对象数组,此处通过向量加法来演示此功能。 Hello.java中新增: ```java native void vectorAdd(int[] a, int[] b, int[] c); public static void main(String []args){ Hello hello = new Hello(); int []a = {1, 2, 3}; int []b = {4, 5, 6}; int []c = new int[3]; hello.vectorAdd(a, b, c); for(int i:c){ System.out.print(i + " "); } ``` `javac -h`重新编译后,Hello.h中生成: ```c++ /* * Class: org_example_Hello * Method: vectorAdd * Signature: ([I[I[I)V */ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *, jobject, jintArray, jintArray, jintArray); ``` 在dllmain.h中实现上述函数: ```c++ JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd (JNIEnv *env, jobject obj, jintArray a, jintArray b, jintArray c){ // 1.convert JNI jintArray to C jint[] jint *cA = env->GetIntArrayElements(a, NULL); jint *cB = env->GetIntArrayElements(b, NULL); jint *cC = env->GetIntArrayElements(c, NULL); jint len = env->GetArrayLength(a); // 2.use array for(jint i=0; i+ cB[i]; } // 3.release resources env->ReleaseIntArrayElements(a, cA, JNI_ABORT); env->ReleaseIntArrayElements(b, cB, JNI_ABORT); env->ReleaseIntArrayElements(c, cC, 0); return; } ``` 函数依旧分为:转化数据、使用数据、释放资源三步。 Release函数第三个参数为mode,有三个备选值如下: | mode | 行为 | | :--------- | :-------------------------------------------------------- | | 0 | copy back the content and free the elems buffer | | JNI_COMMIT | copy back the content but do not free the elems buffer | | JNI_ABORT | free the buffer without copying back the possible changes | 编译后执行得输出如下: ``` 5 7 9 ``` 对象数组与基本类型数组的方法相似,但是没有**Get``ArrayElements**对数组元素进行批量转化,究其原因,Java对象不能直接转化为C/C++的对象。 使用如下两个函数,可以操作基本数据类型数组的直接指针,极大的加快程序运行的效率。但同时,我们要保证在调用ReleasePrimitiveArrayCritical函数之前,不能进行任何可能导致线程阻塞的操作。 void * **GetPrimitiveArrayCritical**(JNIEnv *env, jarray array, jboolean *isCopy); void **ReleasePrimitiveArrayCritical**(JNIEnv *env, jarray array, void *carray, jint mode); ### 工程经验 #### c语言宏定义 观察`javac -h`生成的函数声明,我们会发现都是如下结构: ``` Java_包名_方法名 JNIEXPORT void JNICALL Java_org_example_Hello_jniMethod(...) JNIEXPORT void JNICALL Java_org_example_Hello_vectorAdd(...) ``` 当程序越来越复杂,我们调整Java的包结构,每一个地方都要修改。可以使用C++的宏替换来减少这种冗余如下: ```c++ #define FULL_FUNC_NAME(SHORT_NAME) Java_org_example_Hello_##SHORT_NAME JNIEXPORT void JNICALL FULL_FUNC_NAME(jniMethod)(...) JNIEXPORT void JNICALL FULL_FUNC_NAME(vectorAdd)(...) ``` #### 将dll动态库打入jar包 首先我们要明白Java加载动态库的方式: 1. **System.load** System.load加载绝对路径下的库文件,如: ```java System.load("D:\\workplace\\java2c.dll"); ``` 2. **System.loadLibrary** System.loadLibrary加载相对路径下的库文件,参数为库文件名,不包含库文件的扩展名,如: ```java System.loadLibrary("java2c"); ``` 这里java2c.dll必须在**java.library.path**这一jvm变量指向的路径中。 可以通过如下方法来获得该变量的值: ```java System.getProperty("java.library.path"); ``` 默认情况下,在Windows平台下,该值包含如下位置: 1)和jre相关的一些目录 2)程序当前目录 3)Windows目录 4)系统目录(system32) 5)系统环境变量path指定目录 上述两个函数都是以路径作为参数的,但是jar包中的文件没有路径,只能以文件流的形式获取。为了能将动态库文件打入jar包,并且能够顺利调用,我们的策略是将jar包中的动态库写入系统临时目录下,再调用System.load载入,参考代码如下: ```java static { // copy .so from jar to syetem tmp dir String libName = "java2c.dll"; String nativeTempDir = System.getProperty("java.io.tmpdir"); File extractedLibFile = new File(nativeTempDir + File.separator + libName); InputStream in = null; BufferedInputStream reader = null; FileOutputStream writer = null; if(!extractedLibFile.exists()){ try{ in = Level2.class.getClassLoader().getResourceAsStream(libName); reader = new BufferedInputStream(in); writer = new FileOutputStream(extractedLibFile); byte[] buffer = new byte[1024]; while(reader.read(buffer) > 0){ writer.write(buffer); } }catch(IOException e){ e.printStackTrace(); }finally { if (in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (writer != null){ try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } System.load(extractedLibFile.toString()); } ``` ### 参考链接 [JNIdemo图文](https://blog.csdn.net/WeiHao0240/article/details/99568579) [JNI完全指南文档](https://www.zybuluo.com/cxm-2016/note/563686) [JNI 官方文档](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html)
-
C++有很多新的特征,这些新特征并不一定实用,甚至很烦人。但是,C++的开发效率确实比C要高,所以我仍然采用断章取义的方式来介绍C++的知识。不管是C还是C++,实用就好。
gongyudehuaweiyun
发表于2021-12-05 13:43:11
2021-12-05 13:43:11
最后回复
hw26536804
2021-12-06 00:12:28
3672 2 -
有业务需要在QT的开发环境中引入OBS服务,有没有相关的集成文档或指南提供,方便加快集成过程,谢谢
-
OpenCV用C++语言编写,它的主要接口也是C++语言,但是依然保留了大量的C语言接口。该库也有大量的Python、Java and MATLAB/OCTAVE(版本2.5)的接口。这些语言的API接口函数可以通过在线文档获得。如今也提供对于C#、Ch、Ruby的支持。所有新的开发和算法都是用C++接口。一个使用CUDA的GPU接口也于2010年9月开始实现。OpenCV可以在Windows,Android,Maemo,FreeBSD,OpenBSD,iOS,Linux 和Mac OS等平台上运行。使用者可以在 SourceForge 获得官方版本,或者从 SVN 获得开发版本。OpenCV也是用CMake。在Windows上编译OpenCV中与摄像输入有关部分时,需要DirectShow SDK中的一些基类。该SDK可以从预先编译的Microsoft Platform SDK(or DirectX SDK 8.0 to 9.0c / DirectX Media SDK prior to 6.0)的子目录Samples\Multimedia\DirectShow\BaseClasses获得。于2010年12月06日,OpenCV 2.2.0 正式版发布
-
非static数据成员存在于类类型的每个对象中。static数据成员独立于该类的任意对象而存在。每个static数据成员是与类关联的对象,并不与该类的对象相关联。声明:在声明之前加上关键字static使用:使用 作用域运算符 :: 直接访问静态成员 r = Account::rate()可以使用类的对象或引用访问:r = ac1.rate()使用指针访问:r = ac2->rate()定义:在类的外部定义静态成员时,不能重复static关键字,该关键字只出现在类内部的声明语句。在类外部定义时不用加static。初始化:通常不在类的内部初始化,而是在定义时进行初始化,如:double Account::interestRate = initRate();如果一定要在类内定义,则要求必须是字面值常量类型的constexpr。练习
-
【功能模块】hyper-tuner 系统分析中c/c++分析【操作步骤&问题现象】1、对编译后的可执行文件进行c/c++分析,任务失败,失败原因:exit status 1,采集日志已上传2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
一、C++概念C++是一种面向对象的计算机程序设计语言,由美国AT&T贝尔实验室的本贾尼·斯特劳斯特卢普博士在20世纪80年代初期发明并实现,最初它被称作“C with Classes”(包含类的C语言)。C++它是一种静态数据类型检查的、支持多重编程范式的通用程序设计语言,支持过程化程序设计、数据抽象、面向对象程序设计、泛型程序设计等多种程序设计风格。C++是C语言的继承,进一步扩充和完善了C语言,成为一种面向对象的程序设计语言二、C++关键字C++中总共63个关键字,包括了C语言中32个关键字三、C++命名空间在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。1.命名空间的定义定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。2.命名空间的使用C++为了防止命名冲突,把自己库里面的东西都定义在一个std的命名空间中要使用标准库里面的东西,有三种方式指定命名空间–麻烦,每个地方都要指定,但也是最规范的方式代码如下:int c = 100;namespace N{ int a = 10; int b = 20; int Add(int left, int right) { return left + right; } int Sub(int left, int right) { return left - right; }}把std整个展开,相当于库里面的东西全部到全局域里面去了,使用起来方便但是可能会有与自己命名空间定义的冲突,规范工程中不推荐这种,日常练习可以用这种。代码如下:using namespace std;对部分常用的库里面的东西展开->针对1和2的折中方案,项目中也经常使用代码如下:using std::cout;using std::endl;int main(){ printf("%d\n", N::a); printf("%d\n", N::b); printf("%d\n", N::Add(1, 2)); printf("%d\n", N::Sub(1, 2)); int c = 10; printf("%d\n", c); //局部变量优先,所以c为10 printf("%d\n", ::c); //指定访问左边域,空白表示全局域}四、C++输入&&输出使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%costream 类型全局对象,istream 类型全局对象 ,endl全局的换行符号代码如下:struct Person{ char name[10]; int age;};int main(){ std::cout << "bit education "; std::cout << "bit education" << std::endl; //cout与cin对比C语言printf\scanf 来说可以自动识别类型(函数重载+运算符重载) int a = 10; int* p = &a; printf("%d,%p\n", a, p); std::cout << a << "," << p << std::endl; std::cin >> a; printf("%d\n", a); char str[100]; std::cin >> str; //cin不用&,因为引用 std::cout << str << std::endl; struct Person P = { "uzi", 23 }; //格式化输出printf比cout好 printf("name:%s age:%d\n", P.name, P.age); std::cout << "name:" << P.name<<" age:"<< P.age << "\n";}五、C++缺省参数1.缺省参数的概念1.缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参代码如下:void TestFunc(int a = 0){ cout << a << endl;}int main(){ TestFunc(); // 没有传参时,使用参数的默认值 TestFunc(10); // 传参时,使用指定的实参}2.缺省参数的分类半缺省参数代码如下:void testFunc3(int a, int b = 10, int c = 20){ cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl;}全缺省参数代码如下:void testFunc2(int a = 10, int b = 20, int c = 30){ cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl;}正常参数代码如下:void testFunc1(int a = 0){ std::cout << a << std::endl;}int main(){ testFunc1(10); testFunc2(); testFunc3(1); return 0;}注意:半缺省参数必须从右往左依次来给出,不能间隔着给缺省参数不能在函数声明和定义中同时出现缺省值必须是常量或者全局变量C语言不支持(编译器不支持)六、C++函数重载1.函数重载概念函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。2.函数重载实现代码如下:int Add(int left, int right){ return left + right;}double Add(double left, double right){ return left + right;}int main(){ cout << Add(10, 20) << endl; cout << Add(10.5, 20.0) << endl; //fun(); return 0;}注意(特别重要): 缺省参数缺省参数符合重载的定义,但如果调用的时候编译器不识别函数重载调用哪个函数,所以分情况讨论。代码如下:void fun(int a, int b, int c = 10){}void fun(int a, int b){}3.函数命名规则–>C++支持重载,C不支持为什么C++支持函数重载,而C语言不支持函数重载呢?在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程 序员个人的程序库,将其需要的函数也链接到程序中。其中编译和链接也分为几个步骤:其中分为更细的话:在C++调用Add函数在C下调用Add函数通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。4.extern "C"的作用有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译,所以这个函数不能进行重载。代码如下:extern "C" int Add(int left, int right);int main(){ Add(1, 2); return 0;}七、C++引用1.引用的概念引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间代码如下:int main(){ int x = 10; int &y = x; y = 20; std::cout << "y=" << y << std::endl; int &z = y; z = 30; std::cout << "z=" << z << std::endl;}2.引用的特性引用在定义时必须初始化一个变量可以有多个引用引用一旦引用一个实体,再不能引用其他实体3.常引用代码如下:void TestConstRef(){ //常引用是创建一个临时变量,引用名是临时变量的引用 const int a = 10; //int& ra = a; // 该语句编译时会出错,a为常量,而且a为不可以修改 const int& ra = a; // int& b = 10; // 该语句编译时会出错,b为常量 const int& b = 10; double d = 12.34; //int& rd = d; // 该语句编译时会出错,类型不同 const int& rd = d;}rc是临时空间的别名代码如下:int c=10;double d=1.11;const double& rc=c;4.引用的使用场景1.做参数代码如下:void Swap2(int& a, int& b) //通过引用来交换{ int tmp = a; a = b; b = tmp;}void Swap1(int* a, int *b) //通过指针来交换{ int tmp = *a; *a = *b; *b = tmp;}2.做返回值代码如下:int& Add(int a, int b){ int c = a + b; return c;}int main(){ int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :" << ret << endl; return 0;}如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回5.传值、传引用效率比较以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。6.引用和指针的区别在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间,在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。八、C++内联函数1.内联函数概念以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。2.内联函数特性代码如下:#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>int Add2(int left, int right){ return left + right;}inline int Add1(int left, int right){ return left + right;}int main(){ int ret1, ret2; ret1 = Add1(1, 2); ret2 = Add1(1, 2); std::cout << ret1 << std::endl; std::cout << ret2 << std::endl; return 0;}inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。九、C++auto关键字1.auto关键字概念在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。2.auto关键字的使用代码如下:int main(){ int x = 10; auto a = &x; // int* auto* b = &x; // int* int& y = x; // y的类型是什么?int auto c = y; // int auto& d = x; // d的类型是int, 但是这里指定了d是x的引用 // 打印变量的类型 cout << typeid(x).name() << endl; cout << typeid(y).name() << endl; cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(d).name() << endl; return 0;}auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&在同一行定义多个变量当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。3.auto关键字不能使用场景auto不能作为函数的参数代码如下:// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导void TestAuto(auto a){}auto不能直接用来声明数组代码如下:void TestAuto(){ int a[] = {1,2,3}; auto b[] = {4,5,6};}为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。十、基于范围的for循环(C++11)1.范围for的语法在C++98中如果要遍历一个数组,可以按照以下方式进行:代码如下:int main(){ int array[] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) { cout << array[i] << " "; } cout << endl;} return 0;}对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。代码如下:int main(){ // 范围for C++11新语法遍历,更简单,数组都可以 // 自动遍历,依次取出array中的元素,赋值给e,直到结束 for (auto& e : array) { e *= 2; } for (auto ee : array) { cout << ee << " "; } cout << endl;}2.范围for的使用条件for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在大家了解一下就可以了)十一、指针空值nullptr(C++11)1.程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。2.在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下3.将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。总结以上就是今天要讲的内容,本文仅仅简单介绍了C++入门的简单知识,虽然这么知识范围很大,但也为我们以后学习C++有更好的了解,我们务必掌握。另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。
-
1、请你说一下什么是面向对象?Java是面向对象的编程语言,不同于C语言是面向过程的。对于面向对象和面向过程的区别,举一个简单的例子说明一下(我们以洗衣机洗衣服为例):面向过程:面向过程的编程方式,程序会将要完成的某一个任务拆解成一系列的小步骤 (函数),如:① 打开洗衣机:method01()② 放入要洗的衣服:method02()③ 放入洗衣服:method03()④ 清洗:method04()⑤ 烘干:method05()面向对象:面向对象的编程方式,程序会将要完成的洗衣机洗衣服的任务拆分成如下两个对象:人(Person):Person在洗衣机洗衣服这个程序任务中有三个作用,分别是打开洗衣机、放入要洗的衣服、放入洗衣粉。洗衣机(Machine):Machine在洗衣机洗衣服这个程序任务中有两个作用,分别是清洗、烘干。从上面这个例子能看出,面向过程的编程方式比较直接且高效,而面向对象的编程方式更易复用、扩展和维护!2、请你简述一下面向对象的三个基本特征?继承:承是Java中面向对象最显著的一个特征,继承是从已有的类中派生出新的类,新的类可以吸收已有的属性、行为,并扩展新的能力。Java中不支持多继承,但是接口可以支持多实现。封装:将同一类事物的特征和功能包装在一起,只对外暴露需要调用的接口。封装也称为信息的隐藏,在Java中接口是体现封装最常用的方法,在接口中我们没有任何功能的实现(具体实现都交给实现类),只是定义了一系列抽象的方法声明用于外部调用。多态:封装和继承都是为多态来服务的,多态是指同一个行为具有多个不同的表现形式。在Java中方法的重载和重写是实现多态的2种方式。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载。方法重载体现了编译时的多态性。重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,重载对返回类型没有特殊的要求。方法重写体现了运行时的多态性。多态的三要素:继承 、重写、父类指向子类引用!3、为什么说 Java 是一种半解释半编译的程序设计语言呢?什么是编译形语言,什么又是解释形语言?编译型语言:把做好的源程序全部编译成二进制代码的可运行程序。然后,就可以直接运行这个程序。执行速度快,效率高,依靠编译器,跨平台性稍差。解释型语言:把已经做好的源程序,翻译一句,执行一句,直到结束。执行速度慢,效率低,依靠编译器,但是跨平台性稍好。那么为什么说Java 是编译型语言呢?第一个观点认为 Java 是编译型语言,因为Java程序想要运行,那么第一步就是要使用Javac进行编译(将Java源文件编译成.class二进制文件)。没有经过编译的.java文件,是没办法运行的!那么为什么又说Java 是解释型语言呢?那么第二个观点则是认为Java是解释型语言,Java经过编译,Javac 将.java源文件编译成.class二进制文件之后,仍然需要借助 JVM 的解释执行。综合上面两个观点来看,Java似乎既有编译型语言的特点,又有解释型语言的特点,也没有看到哪本权威的书籍上认定Java就是哪一种类型的语言。4、请你说一下Java中的8大基本类型是那些?如图所示:8种基本数据类型和取值范围:基本类型 大小(位/bit) 字节数(byte) 最小值 最大值 默认值 包装器类型boolean - - false true false Booleanchar 16 bits 2 bytes Unicode 0 Unicode 2^16-1 空 Characterbyte 8 bits 1 byte -2^7 2^7-1 0 Byteshort 16 bits 2 bytes -2~15 2^15-1 0 Shortint 32 bits 4 bytes -2^31 2^31-1 0 Integerlong 64 bits 8 bytes -2^63 2^63-1 0 Longfloat 32 bits 4 bytes 0.0 Floaddouble 64 bits 8 bytes 0.0 Double注意:对于boolean值,在Java规范中并没有给出其储存大小,在《Java虚拟机规范》中给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。除了void之外,其他8种基本数据类型被称为八大基本数据类型。图中从左向右的转换都是隐式转换,无需再代码中进行强制转换。从右向左均要进行强制类型转换,才能通过编译。强制转换会丢失精度。5、请你讲讲抽象类和接口有什么区别?(一) 继承方面:抽象类只能单继承;而接口可以多实现;(二) 成员属性方面:抽象类中可以有普通属性,也可以有常量;接口中的成员变量全部默认是常量,使用public static final修饰,这个可以省略不写;(三) 代码块方面:抽象类可以含初始化块;接口不能含初始化块;(四) 构造函数方面:抽象类可以有构函数,但是这里的构造函数不是用来创建对象的,而且用来被实现类调用进行初始化操作的;接口不能有构造函数;(五) 方法方面:接口在JDK1.8之后可以定义抽象方法(无方法体)、default 修饰的默认方法(有方法体)、static修饰的静态方法(有方法体),JDK1.8以前是只能有抽象方法。public interface Test { static void test() { } default void test2(){ } void test3();// 默认是abstract修饰 }抽象类中除了静态方法和抽象方法外还可以有普通方法。二者相同之处接口与抽象类都不能被实例化,需要被其他进行实现或继承。接口与抽象类里面都能包含抽象方法,实现接口或继承抽象类的子类都必须实现这些抽象方法。6、请判断当一个对象被当作参数传递给一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?是值传递。java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变,但对象的引用是永远不会改变的。java中只有值传递,基本类型传递的是值的副本,引用类型传递的是引用的副本7、请你说一下JVM/JRE/JDK的区别?直接看一张图就可以理解他们的区别了:JVM = Java虚拟机JRE = JVM + 基础类库JDK = JVM + 基础类库 + 编译工具8、请你说一下方法重载和方法重写的区别?重载:方法重载发生在同一个类中,重载的方法之间方法名必须相同,参数列表不同(参数的类型、参数的个数),方法的返回值和访问修饰符可以不同,发生在编译时期(方法重载实现了编译时多态)。重写:方法重写发生在子父类中,子类重写父类的方法,方法名称必须相同,参数列表也必须相同,方法的返回值小于等于父类方法的返回值,访问修饰符方位大于等于父类方法(如果父类方法修饰符为private,则子类就无法重写了)。9、请你说一下List接口和Set接口的区别?List:有序、可重复集合。按照对象插入的顺寻保存数据,允许多个Null元素对象,可以使用iterator迭代器遍历,也可以使用get(int index)方法获取指定下标元素。Set:无序、不可重复集合只允许有一个Null元素对象,取元素时,只能使用iterator迭代器逐一遍历。10、为什么重写了equals()方法还需要重写hashCode()方法?equals()只是判断对象属性是否相同,hashCode()要判断二者地址是否相同。java中如果要判断两个对象是否相等,需要同时满足地址 + 属性都相同!如果两个对象相同(即:用 equals() 比较返回true),那么它们的 hashCode 值一定要相同;如果两个对象的 hashCode 相同,它们并不一定相同;举例子:只重写 equals() 方法,不重写 hashcode() 方法:public class Student { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Student other = (Student) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } // 省略 get,set方法...}执行下面的程序看看效果:public class hashTest { @Test public void test() { Student stu1 = new Student("Jimmy",24); Student stu2 = new Student("Jimmy",24); System.out.println("两位同学是同一个人吗?"+stu1.equals(stu2)); System.out.println("stu1.hashCode() = "+stu1.hashCode()); System.out.println("stu1.hashCode() = "+stu2.hashCode()); }}如果重写了 equals() 而未重写 hashcode() 方法,可能就会出现两个没有关系的对象 equals 相同(因为equals都是根据对象的特征进行重写的),但 hashcode 不相同的情况。因为此时 Student 类的 hashcode() 方法就是 Object 默认的 hashcode()方 法,由于默认的 hashcode()方法是根据对象的内存地址经哈希算法得来的,所以 stu1 != stu2,故两者的 hashcode 值不一定相等。根据 hashcode 的规则,两个对象相等其 hash 值一定要相等,矛盾就这样产生了。上面我们已经解释了为什么要使用 hashcode 算法,所以即使字面量相等,但是产生两个不同的 hashCode 值显然不是我们想要的结果。如果我们在重写 equals() 时,也重写了 hashCode() 方法:public class Student { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Student other = (Student) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } // 省略 get,set方法...}再来看执行结果:两位同学是同一个人吗?truestu1.hashCode() = 71578563stu1.hashCode() = 71578563从 Student 类重写后的 hashcode() 方法中可以看出,重写后返回的新的 hash 值与 Student 的两个属性是有关,这样就确保了对象和对象地址之间的关联性。
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签