Android学习--JNI

news2024/11/15 16:45:10

文章目录

  • JNI(Java Native Interface)
  • NDK(Native Development Kit)
  • 一、创建一个JNI项目
    • 1.创建项目
    • 2.C++文件字段说明
      • 1. Extern “C”
      • 2. JNIEXPORTh和JNICALL
      • 3. JNI接口命名规则
      • 4. JNIEnv
      • 5. jclass和jobject
      • 6. 数据类型
      • 7.JNI函数签名信息
  • 二、JNI实现
    • 1.简单实现
    • 2.静态注册
    • 3.动态注册
  • 三、案例:利用JNI存储SDK相关的密钥信息
    • 1.静态注册方案
    • 2.动态注册方案
  • 四、原理解析
    • 1. 动态注册原理解析
    • 2. 静态注册原理解析
    • 3. Java调用Native的流程
  • 总结


JNI(Java Native Interface)

提供一种Java字节码调用C/C++的解决方案,JNI描述的是一种技术。
在这里插入图片描述

NDK(Native Development Kit)

Android NDK 是一组允许您将 C 或 C++(“原生代码”)嵌入到Android 应用中的工具,NDK描述的是工具集。 能够在 Android 应用中使用原生代码对于想执行以下一项或多项操作的开发者特别有用:

在平台之间移植其应用。
重复使用现有库,或者提供其自己的库供重复使用。
在某些情况下提高性能,特别是像游戏这种计算密集型应用。

一、创建一个JNI项目

1.创建项目

选择NativeC++ 创建项目

在这里插入图片描述
创建好的项目如下:
会有java目录和C++的cpp目录
在这里插入图片描述

2.C++文件字段说明

在native-lib.cpp文件中,有如下一些字段,详细记录说明下:

1. Extern “C”

	作用:避免编绎器按照C++的方式去编绎C函数
		1、C不支持函数的重载,编译之后函数名不变;
		2、C++支持函数的重载(这点与Java一致),编译之后函数名会改变;

2. JNIEXPORTh和JNICALL

JNIEXPORT :用来表示该函数是否可导出(即:方法的可见性)
JNICALL :用来表示函数的调用规范(如:__stdcall)

	1、宏 JNIEXPORT 代表的就是右侧的表达式: __attribute__((visibility ("default")))
	2、或者也可以说: JNIEXPORT 是右侧表达式的别名;
	3、宏可表达的内容很多,如:一个具体的数值、一个规则、一段逻辑代码等;

3. JNI接口命名规则

Java_<PackageName>_<ClassName>_<MethodName>
JNI Native函数有两种注册方式:
1、静态注册:按照JNI接口规范的命名规则注册;
2、动态注册:在.cpp的JNI_OnLoad方法里注册;

4. JNIEnv

JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境。通过JNIEnv可以调用到一系列JNI系统函数。
每个线程中都有一个 JNIEnv 指针。JNIEnv只在其所在线程有效, 它不能在线程之间进行传递。
通过JNIEnv*就可以对Java端的代码进行操作:

	1、创建Java对象
	2、调用Java对象的方法
	3、获取Java对象的属性等

在这里插入图片描述

5. jclass和jobject

jclass :定义native函数的Java类
jobject :定义native函数的Java类的实例

	如果native函数是static,参数则代表类class对象(jclass) 
	如果native函数非static,参数则代表类的实例对象( jobject)

6. 数据类型

  1. 基础类型
    在这里插入图片描述
  2. 引用数据类型
    除了Class、String、Throwable和基本数据类型的数组外,其余所有Java对象的数据类型在JNI中都用jobject表示。Java中的String也是引用类型,但是由于使用频率较高,所以在JNI中单独创建了一个jstring类型。
    在这里插入图片描述
    引用类型不能直接在 Native 层使用,需要根据 JNI 函数进行类型的转化后,才能使用;
    多维数组(含二维数组)都是引用类型,需要使用 jobjectArray 类型存取其值;
    例如,二维整型数组就是指向一位数组的数组,其声明使用方式如下:
//获得一维数组的类引用,即jintArray类型
jclass intArrayClass = env->FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray = env->NewObjectArray(length ,intArrayClass ,
NULL);

7.JNI函数签名信息

由于Java支持函数重载,因此仅仅根据函数名是没法找到对应的JNI函数。为了解决这个问题,JNI将参数类型和返回值类型作为函数的签名信息。

  1. JNI规范定义的函数签名信息格式:
    (参数1类型字符…)返回值类型字符
  2. 函数签名例子:
    在这里插入图片描述
  3. JNI常用的数据类型及对应字符:
    在这里插入图片描述

二、JNI实现

1.简单实现

创建JNI工程的时候,项目会默认自动创建一个简单的实现,如下:
在这里插入图片描述
在这里插入图片描述

页面显示:
在这里插入图片描述

2.静态注册

按照JNI规范书写函数名:Java_类路径_方法名(路径用下划线分隔)
优缺点:实现方式简单,灵活性差

  1. C++调用JAVA方法

java代码

	public native void  test1();
    public void callBack(int code){
        Toast.makeText(this, "native层回调  " + code, Toast.LENGTH_SHORT).show();
    }
    public void callBack(byte[] code){
        Toast.makeText(this, "native层回调  " + code, Toast.LENGTH_SHORT).show();
    }
    //
    public String callBack(String code,int code1){
        Toast.makeText(this, "native层回调  " + code +"  code1:"+code1, 			 Toast.LENGTH_SHORT).show();
        return null;
    }

C代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_test1(JNIEnv *env, jobject thiz) {
    
    //获取jclass对象
    jclass pJclass = env->GetObjectClass(thiz);
    //获取java 方法
    jmethodID idcode = env->GetMethodID(pJclass, "callBack", "(I)V");

    jmethodID id = env->GetMethodID(pJclass, "callBack", "([B)V");

    jmethodID pId = env->GetMethodID(pJclass, "callBack",
                                     "(Ljava/lang/String;I)Ljava/lang/String;");
    //反射调用方法
//    env->CallVoidMethod(thiz,idcode,200);
    jstring pJstring = env->NewStringUTF("200");
    env->CallObjectMethod(thiz,pId,pJstring,300);
}

目前执行调取callBack(String code,int code1)方法,执行结果如下:
在这里插入图片描述
2) C++修改JAVA成员变量的值
java代码:

String text = "我是JAVA";
public native void  test2();
    public void test2(View view) {
        test2();
        binding.sampleText.setText(text);
    }

C代码:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_test2(JNIEnv *env, jobject thiz) {

    jclass pJclass = env->GetObjectClass(thiz);
    //    方法签名 String 对象1    基本类型
    jfieldID textStringid = env->GetFieldID(pJclass, "text", "Ljava/lang/String;");
    //创建C字符串
    jstring pJstring = env->NewStringUTF("native 层修改 代码");
    //执行修改
    env->SetObjectField(thiz,textStringid,pJstring);
}

结果:

在这里插入图片描述

3.动态注册

JNI_Onload中指定Java Native函数与C实现函数的对应关系
优缺点:实现方式复杂,灵活性强

java写一个接口,C++动态注册接口方法。
java代码:

interface IAntiDebugCallback {
    void beInjectedDebug(String s);
}

MainActivity:
    public native void setAntiBiBCallback(IAntiDebugCallback callback);
    public void test7(View view) {
        setAntiBiBCallback(this);
    }
        @Override
    public void beInjectedDebug(String s) {
        binding.sampleText.setText(s);
    }

C代码:

void regist(JNIEnv *env, jobject thiz, jobject jCallback) {

    LOGD("--动态注册调用成功-->");
    jstring pJstring = env->NewStringUTF("动态注册调用成功");
    jclass pJclass = env->GetObjectClass(thiz);
    jmethodID id = env->GetMethodID(pJclass, "beInjectedDebug", "(Ljava/lang/String;)V");
    //执行函数
    env->CallVoidMethod(thiz,id,pJstring);
}

jint RegisterNatives(JNIEnv *env) {
    //动态反射MainActivity
    jclass activityClass = env->FindClass("com/example/jnidemo/MainActivity");
    if (activityClass == NULL) {
        return JNI_ERR;
    }
    //注册MainActivity中setAntiBiBCallback方法
    JNINativeMethod method_MainActivity [] = {
        "setAntiBiBCallback",
        "(Lcom/example/jnidemo/IAntiDebugCallback;)V",
        (void *)regist
    };

    //注册  参数1 页面实体类
    //参数2 注册的方法
    //参数3 注册方法的数量
    return env->RegisterNatives(activityClass,method_MainActivity, sizeof(method_MainActivity)/sizeof(method_MainActivity[0]));
}

/**
 * 动态注册
 * @param vm
 * @param reserved
 * @return
 */
JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM* vm,void* reserved){
//    手机app   手机开机了
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    
    jint result = RegisterNatives(env);
    //如果注册失败,打印日志
    if (result != JNI_OK) {
        LOGD("--动态注册失败-->");
        return -1;
    }

    return JNI_VERSION_1_6;

}

结果:
在这里插入图片描述

在这里插入图片描述

三、案例:利用JNI存储SDK相关的密钥信息

如果需要在本地存储一个密钥串,典型的方式有

  1. 直接写在java source code中
  2. 写在gradle脚本中,使用BuildConfig读取
  3. 写在gradle.properties中,再到gradle脚本中读取,后面同第二点
  4. 使用native方法,读取存放在C/C++中的字段

本质上来讲方式1,2,3没有什么区别。1为硬编码,2可以做到在不同的BuildType使用不同的密钥,3将配置写到脚本之外,方便管理查看。
然而,在项目编译之后,方式1,2,3都会把密钥直接替换到字节码文件中,对于反编译如此方便的Android来说,无疑是将密钥拱手让人。

因此,将密钥放在难以反编译的C/C++代码中,是一个解决的办法。

简单实现:
在类中声明native方法。

  public class A {
      public native String nativeMethod();
  }

C代码实现:

#include <jni.h>
#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
extern "C"{
#endif

jstring Java_[ClassAPackage]_A_nativeMethod(JNIEnv *env,jobject thiz) {
    // 返回密钥
    return (env)->NewStringUTF("你的密钥");

}

这是简单的代码,如果这么实现的话,别人想破解,找到相关的方法,执行就能拿到密钥,我们可以改进一下,android 应用只有自己有的,别人拿不到的,只有应用签名,我们在native代码里面,先验证一下应用的签名是否是我们的,如果是,才返回正确的密钥。
首先通过java相关的方法,拿到咱们当前项目的签名信息,然后配置到C++文件相关配置方法中。

    /**
     * 获取签名唯一字符串
     */
    public String getSignInfo() {
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(
                    getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signs = packageInfo.signatures;
            Signature sign = signs[0];
            Log.e("----->",sign.toCharsString());
            return sign.toCharsString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

然后编写相关代码,有静态和动态注册两种,本质是一样的,可以参考下。

1.静态注册方案

java代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        init();
    }
    /**
     * 初始化,获取应用签名信息,改成动态注册了,这个是静态注册
     * @return
     */
    public static native boolean init();

    /**
     * 获取配置的SDK key信息
     * @return
     */
    public static native String getKey();

    public void test5(View view) {
        binding.sampleText.setText(getKey());
    }

C代码:

const char *APP_PACKAGE_NAME = "com.example.jnidemo";
// 验证是否通过
static jboolean auth = JNI_FALSE;

/*
 * 获取全局 Application
 */
jobject getApplicationContext(JNIEnv *env) {
    jclass activityThread = env->FindClass("android/app/ActivityThread");
    jmethodID currentActivityThread = env->GetStaticMethodID(activityThread, "currentActivityThread", "()Landroid/app/ActivityThread;");
    jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread);
    jmethodID getApplication = env->GetMethodID(activityThread, "getApplication", "()Landroid/app/Application;");
    return env->CallObjectMethod(at, getApplication);
}

/**
 * 通过反射获取签名信息
 */
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_jnidemo_MainActivity_init(JNIEnv *env, jclass clazz) {

    jclass binderClass = env->FindClass("android/os/Binder");
    jclass contextClass = env->FindClass("android/content/Context");
    jclass signatureClass = env->FindClass("android/content/pm/Signature");
    jclass packageNameClass = env->FindClass("android/content/pm/PackageManager");
    jclass packageInfoClass = env->FindClass("android/content/pm/PackageInfo");

    jmethodID packageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jmethodID packageName = env->GetMethodID(contextClass, "getPackageName", "()Ljava/lang/String;");
    jmethodID toCharsString = env->GetMethodID(signatureClass, "toCharsString", "()Ljava/lang/String;");
    jmethodID packageInfo = env->GetMethodID(packageNameClass, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jmethodID nameForUid = env->GetMethodID(packageNameClass, "getNameForUid", "(I)Ljava/lang/String;");
    jmethodID callingUid = env->GetStaticMethodID(binderClass, "getCallingUid", "()I");

    jint uid = env->CallStaticIntMethod(binderClass, callingUid);

    // 获取全局 Application
    jobject context = getApplicationContext(env);

    jobject packageManagerObject = env->CallObjectMethod(context, packageManager);
    jstring packNameString = (jstring) env->CallObjectMethod(context, packageName);
    jobject packageInfoObject = env->CallObjectMethod(packageManagerObject, packageInfo, packNameString, 64);
    jfieldID signaturefieldID = env->GetFieldID(packageInfoClass, "signatures", "[Landroid/content/pm/Signature;");
    jobjectArray signatureArray = (jobjectArray) env->GetObjectField(packageInfoObject, signaturefieldID);
    jobject signatureObject = env->GetObjectArrayElement(signatureArray, 0);
    jstring runningPackageName = (jstring) env->CallObjectMethod(packageManagerObject, nameForUid, uid);

    if (runningPackageName) {// 正在运行应用的包名
        const char *charPackageName = env->GetStringUTFChars(runningPackageName, 0);
        if (strcmp(charPackageName, APP_PACKAGE_NAME) != 0) {
            return JNI_FALSE;
        }
        env->ReleaseStringUTFChars(runningPackageName, charPackageName);
    } else {
        return JNI_FALSE;
    }

    jstring signatureStr = (jstring) env->CallObjectMethod(signatureObject, toCharsString);
    const char *signature = env->GetStringUTFChars(
            (jstring) env->CallObjectMethod(signatureObject, toCharsString), NULL);

    env->DeleteLocalRef(binderClass);
    env->DeleteLocalRef(contextClass);
    env->DeleteLocalRef(signatureClass);
    env->DeleteLocalRef(packageNameClass);
    env->DeleteLocalRef(packageInfoClass);

    LOGE("current apk signature %s", signature);

    // 应用签名,通过 JNIDecryptKey.getSignature(getApplicationContext())
    //TODO  获取,注意开发版和发布版的区别,发布版需要使用正式签名打包后获取
    const char *SIGNATURE_KEY = "密钥信息,上面java代码获取到的,配置到这里";
    if (strcmp(signature, SIGNATURE_KEY) == 0) {
        LOGE("verification passed");
        env->ReleaseStringUTFChars(signatureStr, signature);
        auth = JNI_TRUE;
        return JNI_TRUE;
    } else {
        LOGE("verification failed");
        auth = JNI_FALSE;
        return JNI_FALSE;
    }
    return auth;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_getKey(JNIEnv *env, jclass clazz) {
    const char  * APP_KEY = "qweqw12312asdasdasda231231asdasdasd";
    if (auth) {
        return env->NewStringUTF(APP_KEY);
    } else {// 你没有权限,验证没有通过。
        return env->NewStringUTF("You don't have permission, the verification didn't pass.");
    }
}

结果:
在这里插入图片描述

2.动态注册方案

java代码:

    public native String nativeMethod_key(Context context);

    public void test6(View view) {
        binding.sampleText.setText(nativeMethod_key(this));
    }

C代码:

static jclass contextClass;
static jclass signatureClass;
static jclass packageNameClass;
static jclass packageInfoClass;
/*
    根据context对象,获取签名字符串
*/
const char* getSignString(JNIEnv *env,jobject contextObject) {
    jmethodID getPackageManagerId = (env)->GetMethodID(contextClass, "getPackageManager","()Landroid/content/pm/PackageManager;");
    jmethodID getPackageNameId = (env)->GetMethodID(contextClass, "getPackageName","()Ljava/lang/String;");
    jmethodID signToStringId = (env)->GetMethodID(signatureClass, "toCharsString","()Ljava/lang/String;");
    jmethodID getPackageInfoId = (env)->GetMethodID(packageNameClass, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    jobject packageManagerObject =  (env)->CallObjectMethod(contextObject, getPackageManagerId);
    jstring packNameString =  (jstring)(env)->CallObjectMethod(contextObject, getPackageNameId);
    jobject packageInfoObject = (env)->CallObjectMethod(packageManagerObject, getPackageInfoId,packNameString, 64);
    jfieldID signaturefieldID =(env)->GetFieldID(packageInfoClass,"signatures", "[Landroid/content/pm/Signature;");
    jobjectArray signatureArray = (jobjectArray)(env)->GetObjectField(packageInfoObject, signaturefieldID);
    jobject signatureObject =  (env)->GetObjectArrayElement(signatureArray,0);
    return (env)->GetStringUTFChars((jstring)(env)->CallObjectMethod(signatureObject, signToStringId),0);
}


extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnidemo_MainActivity_nativeMethod_1key(JNIEnv *env, jobject thiz,
                                                        jobject context) {
    const char *RELEASE_SIGN = "密钥信息,上面java代码获取到的,配置到这里";
    const char* signStrng =  getSignString(env,context);
    if(strcmp(signStrng,RELEASE_SIGN)==0)//签名一致  返回合法的 api key,否则返回错误
    {
        return (env)->NewStringUTF("dongtaizhucemiyao123123123123");
    }else
    {
        return (env)->NewStringUTF("error");
    }
}

/**
 * 动态注册
 * @param vm
 * @param reserved
 * @return
 */
JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM* vm,void* reserved){
//    手机app   手机开机了
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    //初始化函数
    contextClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/Context"));
    signatureClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/Signature"));
    packageNameClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/PackageManager"));
    packageInfoClass = (jclass)env->NewGlobalRef((env)->FindClass("android/content/pm/PackageInfo"));
    return JNI_VERSION_1_6;

}

结果:
在这里插入图片描述

四、原理解析

1. 动态注册原理解析

动态注册的主要流程
在这里插入图片描述
在这里插入图片描述
流程3:执行开发人员在JNI_OnLoad中写的注册方法: env->RegisterNatives()

流程4:Android中,Java、Java native函数,在虚拟机中对应的都是一个Method*对象;
            设置nativeFunc 指向函数 dvmCallJNIMethod(通常情况下)
            设置insns 指向native层的C函数指针 (我们写的C函数)

动态注册的详细流程
在这里插入图片描述
Method*属性映射源码
在这里插入图片描述

2. 静态注册原理解析

静态注册的主要流程
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. Java调用Native的流程

在这里插入图片描述


总结

本文中的项目代码:https://github.com/renbin1990/JNIDemo/tree/master

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/890822.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tomcat 一次请求的生命周期

在使用 Tomcat 的时候&#xff0c;我们只需要在 Servlet 实现类中写我们的业务逻辑代码即可&#xff0c;不需要管 Socket 连接、协议处理要怎么实现&#xff0c;因为这部分作为不经常变动的部分&#xff0c;被封装到了 Tomcat 中&#xff0c;程序员只需要引入 Tomcat 中即可&am…

DNNGP、DeepGS 和 DLGWAS模型构成对比

一、DNNGP DNNGP 是基于深度卷积神经网络&#xff0c;这个结构包括一个输入层&#xff0c;三个卷积层&#xff0c;一个批标准化层&#xff0c;两个dropout层&#xff0c;一个平坦化层&#xff0c;一个 dense层。 dropout层&#xff1a;在神经网络中,dropout层是一个非常有效的正…

拿捏--->打印爱心(小心机表白)

文章目录 题目描述算法思路代码示例思路一思路二 题目描述 利用java语言编写算法在控制台打印爱心算法 算法思路代码示例 思路一 打印心形主要分为上下两部分&#xff0c;如图&#xff1a; 下边主要是一个倒立三角形&#xff0c;容易打印。 上边可以分为左右两部分&#…

【数据结构】堆的初始化——如何初始化一个大根堆?

文章目录 源码是如何插入的&#xff1f;扩容向上调整实现大根堆代码&#xff1a; 源码是如何插入的&#xff1f; 扩容 在扩容的时候&#xff0c;如果容量小于64&#xff0c;那就2倍多2的扩容&#xff1b;如果大于64&#xff0c;那就1.5倍扩容。 还会进行溢出的判断&#xff0c…

NPDP含金量高吗?难考吗?

一&#xff0c;什么是NPDP认证&#xff1f; NPDP认证中文名为产品经理国际资格认证&#xff0c;New Product Development Professional (NPDP) &#xff0c;是由美国 产品开发与管理协会 (PDMA) 所发起&#xff0c; 是国际公认的唯一的新产品开发专业认证&#xff0c;集理论、方…

论文及代码详解——可变形卷积(DCNv2)

文章目录 论文详解Stacking More Deformable Conv LayersModulated Deformable ModulesR-CNN Feature Mimicking 代码详解 DCNv2 是在DCNv1的基础上的改进版。 理解DCNv2之前&#xff0c;建议先读 《论文及代码详解——可变形卷积&#xff08;DCNv1&#xff09;》 论文详解 DC…

数据结构单链表

单链表 1 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。 在我们开始讲链表之前&#xff0c;我们是写了顺序表&#xff0c;顺序表就是类似一个数组的东西&#xff0…

651页23万字智慧教育大数据信息化顶层设计及建设方案WORD

导读&#xff1a;原文《651页23万字智慧教育大数据信息化顶层设计及建设方案WORD》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 目录 一、 方案背景 1.1 以教育…

三、数据类型

1、数值类型 &#xff08;1&#xff09;分类&#xff1a; &#xff08;2&#xff09;注意&#xff1a; 存储某一类数据&#xff0c;如果只想要表示整数&#xff0c;则在其后加unsigned即可&#xff1b; 在表示小数时&#xff0c;需要指明该小数的总长度和小数部分的长度&…

PostgreSQL空值的判断

PostgreSQL空值的判断 空值判断非空判断总结 空值判断 -- 查询为空的 is null,sql简写isnull select * from employees where manager_id isnull;select * from employees where manager_id is null;非空判断 -- 查询不为空的 is not null;sql简写notnull select * from empl…

c++基本语法

c基础语法 由于长时间没有使用过c&#xff0c;因此c的语法已经忘得差不多了&#xff0c;这篇文章主要是快读地过一遍c的基本语法。之前也写过一篇从c过渡到c的文章&#xff0c;有兴趣地可以看看。 文章链接 1.c的安装 mingw64的安装配置环境变量 2.语法 #include<iostre…

leetcode 力扣刷题 两数/三数/四数之和 哈希表和双指针解题

两数/三数/四数之和 题目合集 哈希表求解1. 两数之和454. 四数相加Ⅱ 双指针求解15.三数之和18. 四数之和 这个博客是关于&#xff1a;找出数组中几个元素&#xff0c;使其之和等于题意给出的target 这一类题目的&#xff0c;但是各个题之间又有些差异&#xff0c;使得需要用不…

网络及其计算的重点知识及回顾

概述 1.1 计算机网络的分类 按照网络的作用范围&#xff1a;广域网&#xff08;WAN&#xff09;、城域网&#xff08;MAN&#xff09;、局域网&#xff08;LAN&#xff09;&#xff1b; 按照网络使用者&#xff1a;公用网络、专用网络。 1.2 计算机网络的层次结构 应用层&a…

谷歌云平台替代超级计算机?助力心脏病学研究,省心智能还省钱

哈佛大学的研究团队在使用谷歌云平台代替超级计算机进行心脏病研究方面取得了重大突破。通常情况下&#xff0c;进行模拟人体循环系统中血块和肿瘤细胞的疗法需要强大的计算能力才能提供准确的结果。然而&#xff0c;超级计算机的高昂成本和有限的可用性成为这项研究面临的巨大…

Qt+Pyhton实现麒麟V10系统下word文档读写功能

目录 前言1.C调用python1.1 安装Python开发环境1.2 修改Qt工程配置1.3 初始化Python环境1.4 C 调用Python 函数1.5 常用的Python接口 2.python虚拟环境2.1Python虚拟环境简介2.2 virtualenv 安装及使用2.3 在C程序中配置virtualenv 虚拟环境 3.python-docx库的应用4.总结 前言 …

【计算机网络篇】UDP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; UDP协议 1&#xff0c;UDP 简介 UDP&#xff08;User Datagram Protocol&#xff09;是一种无连…

集成DTM实现跨语言分布式事务V1.0

集成DTM实现跨语言分布式事务V1.0 简介 DTM是一款开源的分布式事务管理器&#xff0c;解决跨数据库、跨服务、跨语言栈更新数据的一致性问题。 通俗一点说&#xff0c;DTM提供跨服务事务能力&#xff0c;一组服务要么全部成功&#xff0c;要么全部回滚&#xff0c;避免只更新…

Qt编程基础 | 第六章-窗体 | 6.5、QTableWidget 使用过程注意的问题

一、QTableWidget 使用过程注意的问题 1、添加行时要先设置行数 往表格重添加行时&#xff0c;要先设置行数&#xff0c;不然添加的内容看不到&#xff0c;如下&#xff1a; void QCustomWidget::InitTableContent() {for (int row 0; row < m_rowData.size(); row ){// 注…

DAY4,ARM(用c语言点亮LED灯,封装库代码,软件编程控制硬件)

---gpio.h头文件--- #ifndef __LED_H__ #define __LED_H__//1RCC_MP_AHB4ENSETR寄存器封装 #define RCC_MP_AHB4ENSETR (*(volatile unsigned int*)0x50000a28)//2GPIO封装结构体 typedef struct {volatile unsigned int MODER;volatile unsigned int OTYPER;volatile unsigne…

Python系统学习1-9-类二之MVC框架

一、模型说明 View视图:处理界面逻辑,输入输出 Controller控制器:处理核心逻辑,存储计算 Model模型:将多个变量组合为一种类型 二、注意事项 类外通过自定义对象名调用 类中通过self调用 """疫情信息管理系统学习MVC的软件架构View Controller Model视…