【JNI】C,CPP概念及使用方法

. ->运算符

在C和C++两种语言中,.->运算符都存在,并且它们的用法和含义是相同的。

  • .运算符:在C和C++中,当你有一个结构体或类的实例(也就是一个对象)时,你可以使用.运算符来访问其成员。
  • ->运算符:在C和C++中,当你有一个指向结构体或类的指针时,你可以使用->运算符来访问其成员。

JNIEnv *env; c中你需要写(*env)->member,而在C++中,你可以简单地写env->member 是因为JNIEnv在c和cpp是不一样的,

对于(env) c中是JNINativeInterface,cpp是_JNIEnv。并不是由于. ->运算符在c和cpp中的功能不同

方法签名

JNIEnv *env, jobject thiz参数签名时要省略 例如:

cpp 复制代码
jint native_op(JNIEnv *env, jobject thiz,int p) {  
return p;  
}

对应的签名是:(I)I

函数的类型是什么

还是上面那个例子:

cpp 复制代码
jint native_op(JNIEnv *env, jobject thiz,int p) {  
return p;  
}

如果只写一个native_op他的类型应该怎么写?

c 复制代码
int addFunction(int a, int b) {  
return a + b;  
}  
  
void test(){  
int (*funcPtr)(int, int) = addFunction;  
int p = funcPtr(1,2);  
funcPtr(p,3);  
}

对于addFunction,他的类型是int (*funcPtr)(int, int),变量名字叫funcPtr

所以例子中的函数类型是:jint (*funcPtr)(JNIEnv *, jobject, int)

当然在c++中除了上面那样写,还可以使用auto关键字,c语言中的auto没有这个特性

cpp 复制代码
void test() {  
    auto funcPtr = addFunction;  
    int p = funcPtr(1, 2);  
    funcPtr(p, 3);  
}

c++特有的类型自动推断

在C++中,auto关键字用于自动类型推断,让编译器根据初始化表达式来推断变量的类型。这在处理复杂类型或者想要避免类型名称冗长的情况下非常有用。以下是一个简单的例子:

cpp 复制代码
auto i = 5; // i is int
auto d = 3.14; // d is double
auto s = "hello"; // s is const char*

在这个例子中,编译器会根据等号右边的值来推断ids的类型。这样可以使代码更简洁,更易于阅读和维护。

注: 在C语言中,auto关键字主要用于声明一个变量为自动存储类型,这意味着这个变量的生命周期仅限于它的声明块(通常是一个函数)。一旦控制流离开这个块,变量就会被销毁。

然而,实际上auto关键字在C语言中的使用非常少,因为如果没有明确指定存储类型,局部变量默认就是自动存储类型。也就是说,以下两个声明是等价的:

cpp 复制代码
int x; // 默认为自动存储类型
auto int y; // 显式声明为自动存储类型

因此,auto关键字在C语言中的应用场景非常有限,通常不需要显式使用。在现代C++中,auto关键字的含义已经改变,用于自动类型推断,这是一个完全不同的概念。

c++中的lambda

除了和c语言一样去定义函数

还可以使用lambda

cpp 复制代码
int main() { 
    // 定义一个lambda表达式,接受两个int参数,返回它们的和
    auto add = [](int a, int b) { 
            return a + b; 
    }; 
    // 使用lambda表达式 
    int result = add(3, 4); 
    // result is 7 
    std::cout << result << std::endl; 
    return 0; 
}

c++ 面向对象

在C++中,对象是类的实例。首先,你需要定义一个类,然后你可以创建该类的对象。以下是一个简单的例子:

cpp 复制代码
#include <iostream>

// 定义一个类
class MyClass { 
public: 
    int myNumber; 
    std::string myString; 
private: 
    int privateNumber; 
public: 
    void print() { 
        std::cout << "Number: " << myNumber << ", String: " << myString << std::endl; 
    } 
};

int main() {
    // 创建一个MyClass的对象
    MyClass obj;

    // 设置对象的属性
    obj.myNumber = 5;
    obj.myString = "Hello";

    // 调用对象的成员函数
    obj.print();

    return 0;
}

在这个例子中,MyClass是一个类,它有两个成员变量(myNumbermyString)和一个成员函数(print)。然后,我们在main函数中创建了一个MyClass的对象obj,设置了它的属性,并调用了它的成员函数。

注: 在C++中,创建对象的方式有两种:一种是在栈上创建,另一种是在堆上创建。

在上述例子中,我们在栈上创建了一个对象。这种情况下,不需要使用new关键字。对象在声明时就已经创建,并且在离开作用域时会自动销毁。

cpp 复制代码
MyClass obj; // 在栈上创建对象

如果你想在堆上创建对象,那么就需要使用new关键字。这种情况下,对象在使用new创建后存在,直到你使用delete显式销毁它。

cpp 复制代码
MyClass* obj = new MyClass(); // 在堆上创建对象
// 使用对象...
delete obj; // 销毁对象

在堆上创建对象可以让对象的生命周期超过创建它的作用域,但需要手动管理内存,否则可能会导致内存泄漏。

构造方法

在C++中,构造函数是一种特殊的成员函数,它在创建类的对象时自动调用。构造函数的名称与类的名称相同,没有返回类型。以下是一些构造函数的写法:

  1. 默认构造函数:没有参数或所有参数都有默认值。
cpp 复制代码
class MyClass {
public:
    MyClass() {
        // 默认构造函数
    }
};
  1. 参数化构造函数:带有参数的构造函数。
cpp 复制代码
class MyClass {
public:
    int x;
    MyClass(int val) : x(val) {
        // 参数化构造函数
    }
};
  1. 拷贝构造函数:用于初始化一个对象为另一个对象的副本。
cpp 复制代码
class MyClass {
public:
    int x;
    MyClass(const MyClass &obj) : x(obj.x) {
        // 拷贝构造函数
    }
};
  1. 移动构造函数:用于初始化一个对象为另一个对象的"移动"副本。
cpp 复制代码
class MyClass {
public:
    int *x;
    MyClass(MyClass &&obj) : x(obj.x) {
        obj.x = nullptr; // 移动构造函数
    }
};
  1. 委托构造函数:一个构造函数调用同类的其他构造函数。
cpp 复制代码
class MyClass {
public:
    int x, y;
    MyClass(int val) : x(val), y(val) {}
    MyClass() : MyClass(0) {} // 委托构造函数
};

在C++中,x(val)y(val)是成员初始化列表的一部分,用于初始化类的成员变量。在这个例子中,x(val)y(val)表示将val的值分别赋给xy

成员初始化列表位于构造函数参数列表和函数体之间,由冒号开始,每个初始化器由一个成员名和括号内的初始值组成,初始化器之间用逗号分隔。

这种初始化方式比在构造函数体内部进行赋值更有效,因为它可以直接初始化成员,而不是先创建然后再赋值。对于某些类型的成员(如const或引用成员),必须在成员初始化列表中进行初始化,因为它们不能在构造函数体内部赋值。

如果你既要初始化成员变量又要调用基类的构造函数,你可以在派生类的构造函数中使用成员初始化列表。成员初始化列表可以同时完成这两个任务。以下是一个示例:

cpp 复制代码
#include <iostream>

class Base {
public:
    Base(int x) {
        std::cout << "Base constructor called with value: " << x << std::endl;
    }
};

class Derived : public Base {
public:
    int y;
    Derived(int x, int y_value) : Base(x), y(y_value) { // 调用基类的构造函数并初始化成员变量
        std::cout << "Derived constructor called with value: " << y << std::endl;
    }
};

int main() {
    Derived obj(10, 20);
}

在这个例子中,Derived类的构造函数通过成员初始化列表调用了Base类的构造函数,并初始化了成员变量y。当创建Derived类的对象时,将首先调用Base类的构造函数,然后初始化y,最后执行Derived类的构造函数体。

派生

C++中,派生是通过继承来创建新类的过程。新类(派生类)继承了一个或多个现有类(基类)的特性。以下是一个简单的例子:

cpp 复制代码
// 基类
class BaseClass {
public:
    int baseNumber;

    void basePrint() {
        std::cout << "Base Number: " << baseNumber << std::endl;
    }
};

// 派生类
class DerivedClass : public BaseClass {//还有private、protected
public:
    int derivedNumber;

    void derivedPrint() {
        std::cout << "Derived Number: " << derivedNumber << std::endl;
    }
};

int main() {
    DerivedClass obj;

    // 访问基类的成员
    obj.baseNumber = 10;
    obj.basePrint();

    // 访问派生类的成员
    obj.derivedNumber = 20;
    obj.derivedPrint();

    return 0;
}

在这个例子中,DerivedClass是通过公有继承从BaseClass派生的。这意味着BaseClass的所有公有和保护成员都成为了DerivedClass的成员,并且保持了它们在BaseClass中的访问级别。因此,我们可以在DerivedClass的对象中访问baseNumberbasePrint如果选择私有继承,那么 BaseClass对外是不可见的,只可以在DerivedClass类中调用。

C++ 模板 也就是java和kotlin里面的泛型

在C++中,模板是一种特性,允许你编写通用的代码,可以处理多种数据类型。模板可以应用于函数(称为函数模板)和类(称为类模板)。

以下是一个函数模板的例子:

cpp 复制代码
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    std::cout << max<int>(3, 7) << std::endl; // 输出7
    std::cout << max<double>(3.14, 2.72) << std::endl; // 输出3.14
    return 0;
}

这里的max<int>(3, 7)等价于max(3, 7),c++可以自动推断出。

在这个例子中,max函数是一个模板函数,可以接受任何类型的参数,只要该类型支持>运算符。

以下是一个类模板的例子:

cpp 复制代码
template <typename T>
class MyPair {
public:
    T first, second;

    MyPair(T a, T b) {
        first = a;
        second = b;
    }

    T getMax() {
        return (first > second) ? first : second;
    }
};

int main() {
    MyPair<int> intPair(3, 7);
    std::cout << intPair.getMax() << std::endl; // 输出7

    MyPair<double> doublePair(3.14, 2.72);
    std::cout << doublePair.getMax() << std::endl; // 输出3.14

    return 0;
}

在这个例子中,MyPair类是一个模板类,可以接受任何类型的参数,只要该类型支持>运算符。

c++ 异常处理

在C++中,异常处理主要涉及到以下几个关键字:trycatchthrow。以下是一些基本的使用方式:

  1. 抛出异常:使用throw关键字抛出异常。
cpp 复制代码
throw "An error occurred";
  1. 捕获异常:使用try/catch块捕获并处理异常。
cpp 复制代码
try {
    // 可能抛出异常的代码
    throw "An error occurred";
}
catch (const char* msg) {
    std::cerr << msg << std::endl;
}
  1. 捕获所有异常:使用catch(...)捕获所有类型的异常。
cpp 复制代码
try {
    // 可能抛出异常的代码
    throw "An error occurred";
}
catch (...) {
    std::cerr << "An unknown error occurred" << std::endl;
}
  1. 标准库异常:C++标准库提供了一系列的异常类,如std::exceptionstd::runtime_errorstd::out_of_range等。
cpp 复制代码
try {
    // 可能抛出异常的代码
    throw std::runtime_error("A runtime error occurred");
}
catch (const std::exception& e) {
    std::cerr << e.what() << std::endl;
}

请注意,异常处理应该用于处理那些无法预防或无法通过其他方式处理的错误情况。过度使用异常处理可能会导致代码难以理解和维护。

c++ 可变参数

在C++中,...被称为省略号或者可变参数。它有两个主要的用途:

  1. 在函数参数列表中表示该函数可以接受任意数量和类型的参数。这在C++的旧版本中常见,但在现代C++中,我们更倾向于使用模板和容器来处理可变数量的参数。
cpp 复制代码
#include <cstdarg>
#include <iostream>

void printNumbers(int count, ...) {
    va_list list;
    va_start(list, count);

    for (int i = 0; i < count; ++i) {
        std::cout << va_arg(list, int) << '\n';
    }

    va_end(list);
}

int main() {
    printNumbers(3, 10, 20, 30);
}
  1. catch语句中,...表示捕获所有类型的异常。
cpp 复制代码
try {
    // 可能抛出异常的代码
    throw "An error occurred";
}
catch (...) {
    std::cerr << "An unknown error occurred" << std::endl;
}

在C++11及其后续版本中,...还用于模板编程中的参数包,用于表示任意数量和类型的模板参数。

例如: 在C++11及其后续版本中,...被用于模板编程中的参数包,表示任意数量和类型的模板参数。这是一种强大的功能,允许你创建可以处理任意类型和数量参数的模板函数或模板类。

以下是一个简单的例子,展示了如何使用参数包创建一个可以接受任意数量参数的函数模板:

cpp 复制代码
#include <iostream>

// 基本情况,没有参数
void print() {}

// 递归模板函数
template<typename T, typename... Args>
void print(T head, Args... tail) {
    std::cout << head << " ";
    print(tail...); // 递归调用,参数包中的下一个参数成为新的head
}

int main() {
    print(1, 2.0, "three", '4');//省略泛型的声明<>
}

在这个例子中,print函数模板可以接受任意数量和类型的参数。T表示第一个参数的类型,Args...表示剩余参数的类型。在函数体中,print(tail...)递归地调用print函数,每次调用都将参数包中的下一个参数作为head

va_start

这里的va_start是C和C++中处理可变参数的宏。它被用于初始化va_list类型的对象,这个对象用于访问函数参数列表中的可变参数。

在使用va_start宏之前,你需要定义一个va_list类型的变量,这个变量将被初始化为指向第一个可变参数的指针。va_start接受两个参数:第一个是刚刚提到的va_list变量,第二个是可变参数列表前的最后一个非可变参数。

以下是一个例子:

cpp 复制代码
#include <cstdarg>
#include <iostream>

void printNumbers(int count, ...) {
    va_list list;
    va_start(list, count); // 初始化va_list

    for (int i = 0; i < count; ++i) {
        int num = va_arg(list, int); // 获取下一个参数的值
        std::cout << num << '\n';
    }

    va_end(list); // 清理为va_list分配的内存
}

int main() {
    printNumbers(3, 10, 20, 30);
}

在这个例子中,va_start(list, count)list初始化为指向第一个可变参数的指针。然后,va_arg宏被用于获取可变参数的值。最后,va_end宏被用于清理为va_list分配的内存。

c++ 智能指针

在C++中,智能指针是一种对象,它可以像常规指针一样处理,但当它不再需要时,它会自动删除它所指向的对象。这种自动删除可以防止内存泄漏,使内存管理变得更加简单。

C++标准库提供了几种类型的智能指针,包括std::unique_ptrstd::shared_ptrstd::weak_ptr

  • std::unique_ptr是一种独占所有权的智能指针,它不允许多个指针指向同一个对象。
  • std::shared_ptr允许多个指针共享同一个对象,当最后一个shared_ptr不再指向对象时,对象会被删除。
  • std::weak_ptr是一种不控制对象生命周期的智能指针,它被设计为与std::shared_ptr一起使用,防止智能指针的循环引用。

使用智能指针可以帮助你更有效地管理内存,避免常规指针可能导致的内存泄漏和悬挂指针问题。

当然可以。以下是一个使用std::unique_ptr的例子:

cpp 复制代码
#include <memory>

struct MyClass {
    int value;
    MyClass(int v) : value(v) {}
};

int main() {
    std::unique_ptr<MyClass> ptr(new MyClass(10));
    std::cout << ptr->value << std::endl;  // 输出:10
    return 0;
}

在这个例子中,当ptr离开其作用域(即main函数结束)时,它会自动删除它所指向的MyClass对象,释放内存。

以下是一个使用std::shared_ptr的例子:

cpp 复制代码
#include <memory>

struct MyClass {
    int value;
    MyClass(int v) : value(v) {}
};

int main() {
    std::shared_ptr<MyClass> ptr1(new MyClass(10));
    {
    std::shared_ptr<MyClass> ptr2 = ptr1;
    std::cout << ptr1->value << std::endl;  // 输出:10
    std::cout << ptr2->value << std::endl;  // 输出:10
    }  // ptr2离开作用域,但ptr1仍然指向对象
    std::cout << ptr1->value << std::endl;  // 输出:10
    return 0;
}

在这个例子中,ptr1ptr2共享同一个对象。当ptr2离开其作用域时,对象不会被删除,因为ptr1仍然指向它。只有当ptr1也离开其作用域时,对象才会被删除。

验证std::unique_ptr指向的指针只能存在一个引用,编译期间就会报错

c++ 并发

以下是一些C++并发编程的例子:

  1. 线程(Threads)
cpp 复制代码
#include <iostream>
#include <thread>

void hello() {
    std::cout << "Hello, World!" << std::endl;
}

int main() {
    std::thread t(hello);
    t.join();
    return 0;
}
  1. 互斥量(Mutexes)
cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;

void print_block(int n, char c) {
    mtx.lock();
    for (int i=0; i<n; ++i) { std::cout << c; }
    std::cout << '\n';
    mtx.unlock();
}

int main() {
    std::thread th1(print_block,50,'*');
    std::thread th2(print_block,50,'$');

    th1.join();
    th2.join();

    return 0;
}
  1. 条件变量(Condition Variables)
cpp 复制代码
#include <iostream>
#include <thread>
#include <condition_variable>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;

void waits() {
    std::unique_lock<std::mutex> lk(cv_m);
    cv.wait(lk, []{return i == 1;});
    std::cout << "Finished waiting. i == 1\n";
}

void signals() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    i = 1;
    cv.notify_one();
}

int main() {
    std::thread t1(waits), t2(signals);
    t1.join(); 
    t2.join();
    return 0;
}

std::this_thread是C++标准库中的一个命名空间,它提供了一些函数,这些函数对于操作当前线程非常有用。这些函数包括:

  1. std::this_thread::get_id():返回当前线程的线程ID。
  2. std::this_thread::sleep_for():使当前线程休眠指定的时间段。
  3. std::this_thread::sleep_until():使当前线程休眠直到指定的时间点。
  4. std::this_thread::yield():建议操作系统切换到其他线程。这是一种对操作系统的提示,表示当前线程没有紧急的任务要执行,可以切换到其他线程。

例如,以下代码使当前线程休眠1秒:

cpp 复制代码
#include <iostream>
#include <thread>
#include <chrono>

int main() {
    std::cout << "Sleeping for 1 second...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Awake!\n";
    return 0;
}

这些函数可以帮助你更好地控制和管理线程的行为。

命名空间

命名空间(Namespace)是C++中的一个特性,用于组织代码并防止命名冲突。命名空间可以包含变量、函数、类等。

以下是一个简单的命名空间示例:

cpp 复制代码
namespace MyNamespace {
    int myVar = 10;
    void myFunction() {
        // 函数实现
    }
}

你可以使用::运算符来访问命名空间中的元素,如MyNamespace::myVar

命名空间的使用方式和应用场景包括:

  1. 防止命名冲突:当两个库都定义了同名的函数或类时,可以通过命名空间来区分它们。
  2. 代码组织:可以使用命名空间来将相关的函数、类和变量组织在一起。
  3. 使用using声明 :可以使用using声明来引入命名空间中的特定元素,或者使用using namespace来引入整个命名空间。

例如:

cpp 复制代码
using MyNamespace::myVar;  // 引入特定元素
using namespace MyNamespace;  // 引入整个命名空间

使用using namespace后就不需要带MyNamespace::前缀了

请注意,过度使用using namespace可能会导致命名冲突,因此在编写大型项目时应谨慎使用。

相关推荐
哎呦没20 分钟前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
编程、小哥哥1 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程2 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇2 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
网络研究院2 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
杨哥带你写代码2 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
凉亭下2 小时前
android navigation 用法详细使用
android
郭二哈3 小时前
C++——模板进阶、继承
java·服务器·c++
A尘埃3 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23073 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端