main()
,全部簡單的程序均可以定義其餘額外的函數。.h
頭文件 。int *func(int x, int y)
。int (*funcp)(int x)
。int i;
int *a = &i; //這裏a是一個指針,它指向變量i
int &b = i; //這裏b是一個引用,它是變量i的引用(別名)
int * &c = a; //這裏c是一個引用,它是指針a的引用
int & *d; //這裏d是一個指針,它指向引用,但引用不是實體,因此這是錯誤的
複製代碼
在分析上面代碼時,能夠從變量標識符開始從右往左看,最靠近標識符的是變量的本質類型,而再往左即爲對變量類型的進一步修飾。html
例如:int * & a
標識符a的左邊緊鄰的是 &,證實 a 是一個引用變量,而再往左是 * ,可見 a 是一個指針的引用,再往左是 int,可見 a 是一個指向int類型的指針的引用。java
.
和 ->
struct Data
{
int a,b,c;
}; /*定義結構體類型*/
struct Data * p; /* 定義結構體指針 */
struct Data A = {1,2,3}; / * 聲明結構體變量A,A即結構體名 */
int x; /* 聲明一個變量x */
p = &A ; /* 地址賦值,讓p指向A */
x = p->a; /* 取出p所指向的結構體中包含的數據項a賦值給x */
/* 此時因爲p指向A,於是 p->a == A.a,也就是1 */
複製代碼
由於此處
p
是一個指針,因此不能使用.號訪問內部成員(即不能p.a
),而要使用->
。可是A.a
是能夠的,由於A
不是指針,是結構體名。linux
通常狀況下用 「.」
只須要聲明一個結構體。格式是:結構體類型名+結構體名
。而後用 結構體名加「.」加成員名
就能夠引用成員了。由於自動分配告終構體的內存。如同 int a;
同樣。 用 「->」
,則要聲明一個結構體指針,還要手動開闢一個該結構體的內存(上面的代碼則是建了一個結構體實例,自動分配了內存,下面的例子則會講到手動動態開闢內存),而後把返回的地址賦給聲明的結構體指針,才能用「->」
正確引用。不然內存中只分配了指針的內存,沒有分配結構體的內存,致使想要的結構體其實是不存在。這時候用 「->」
引用天然出錯了,由於沒有結構體,天然沒有結構體的域了。 此外,(*p).a
等價於 p->a
。android
::
::
是做用域符,是運算符中等級最高的,它分爲三種:ios
::name
class::name
namespace::name
他們都是左關聯,他們的做用都是爲了更明確的調用你想要的變量:程序員
a
,那麼就寫成 ::a
;(也能夠是全局函數)class A
中的成員變量 a
,那麼就寫成 A::a
;namespace std
中的 cout
成員,你就寫成 std::cout
(至關於 using namespace std;cout
)意思是在這裏我想用 cout
對象是命名空間 std
中的 cout
(即就是標準庫裏邊的cout
);
- 表示「域操做符」:聲明瞭一個類
A
,類A
裏聲明瞭一個成員函數void f()
,但沒有在類的聲明裏給出f
的定義,那麼在類外定義f時, 就要寫成void A::f()
,表示這個f()
函數是類A
的成員函數。- 直接用在全局函數前,表示是全局函數:在 VC 裏,你能夠在調用 API 函數裏,在 API 函數名前加
::
- 表示引用成員函數及變量,做用域成員運算符:
System::Math::Sqrt()
至關於System.Math.Sqrt()
;
int arr[] = {1,2,3};
int* p = arr
,指針 p 指向數組 arr 的首地址;*p = 6;
將 arr 數組的第一個元素賦值爲 6;*(p+1) = 10;
將 arr 數組第二個元素賦值爲 10;int* p[3];
for(int i = 0; i<3; i++){
p[i] = &arr[i];
}
複製代碼
int (*p)[n]
優先級高,首先說明 p 是一個指針,指向一個整型的一維數組,這個一維數組的長度是 n,也能夠說是 p 的步長。執行 p+1 時,p 要跨過 n 個整型數據的長度。編程
int a[3][4]
; int (*p)[4];
//該語句是定義一個數組指針,指向含 4 個元素的一維數組 p = a;
//將該二維數組的首地址賦給 p,也就是 a[0] 或 &a[0][0] p++;
//該語句執行後,也就是 p = p+1;
p 跨過行 a[0][] 指向了行 a[1][]數組
struct Person
{
char c;
int i;
char ch;
};
int main()
{
struct Person person;
person.c = 8;
person.i = 9;
}
複製代碼
存儲變量時地址要求對齊,編譯器在編譯程序時會遵循兩個原則:安全
(1)結構體變量中成員的偏移量必須是成員大小的整數倍 (2)結構體大小必須是全部成員大小的整數倍,也即全部成員大小的公倍數markdown
union Data
{
int i;
float f;
char str[20];
}data;
int main()
{
union Data data;
data.i = 11;
}
複製代碼
char *pa, *pb;//傳統寫法
複製代碼
typedef char* PCHAR; // 使用typedef 寫法 通常用大寫
PCHAR pa, pb; // 可行,同時聲明瞭兩個指向字符變量的指針
複製代碼
struct
。之前的代碼中,聲明 struct
新對象時,必需要帶上 struct
,即形式爲: struct 結構名 對象名
:struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
複製代碼
//使用 typedef
typedef struct tagPOINT
{
int x;
int y;
}POINT;
POINT p1; // 這樣就比原來的方式少寫了一個struct,比較省事,尤爲在大量使用的時候
複製代碼
typedef
來定義與平臺無關的類型:#if __ANDROID__
typedef double SUM;
#else
typedef float SUM ;
#endif
int test() {
SUM a;
return 0;
}
複製代碼
//原聲明:
int *(*a[5])(int, char*);
//變量名爲a,直接用一個新別名pFun替換a就能夠了:
typedef int *(*pFun)(int, char*);
//原聲明的最簡化版:
pFun a[5];
複製代碼
.h
這裏通常寫類的聲明(包括類裏面的成員和方法的聲明)、函數原型、#define常數等,但通常來講不寫出具體的實現。寫頭文件時,爲了防止重複編譯,咱們在開頭和結尾處必須按照以下樣式加上預編譯語句:#ifndef CIRCLE_H
#define CIRCLE_H
class Circle
{
private:
double r;
public:
Circle();//構造函數
Circle(double R);//構造函數
double Area();
};
#endif
複製代碼
至於
CIRCLE_H
這個名字其實是無所謂的,你叫什麼都行,只要符合規範都行。原則上來講,很是建議把它寫成這種形式,由於比較容易和頭文件的名字對應。
.cpp
源文件主要寫實現頭文件中已經聲明的那些函數的具體代碼。須要注意的是,開頭必須 #include
一下實現的頭文件,以及要用到的頭文件。#include "Circle.h"
Circle::Circle()
{
this->r=5.0;
}
Circle::Circle(double R)
{
this->r=R;
}
double Circle:: Area()
{
return 3.14*r*r;
}
複製代碼
main.cpp
來測試咱們寫的 Circle 類#include <iostream>
#include "Circle.h"
using namespace std;
int main()
{
Circle c(3);
cout<<"Area="<<c.Area()<<endl;
return 1;
}
複製代碼
friend
。friend class 類名
(friend和class是關鍵字,類名必須是程序中的一個已定義過的類)。class INTEGER
{
private:
int num;
public:
friend void Print(const INTEGER& obj);//聲明友元函數
};
void Print(const INTEGER& obj) //不使用friend和類::
{
//函數體
}
void main()
{
INTEGER obj;
Print(obj);//直接調用
}
複製代碼
#include <iostream>
using namespace std;
class girl
{
private:
char *name;
int age;
friend class boy; //聲明類boy是類girl的友元
public:
girl(char *n,int age):name(n),age(age){};
};
class boy
{
private:
char *name;
int age;
public:
boy(char *n,int age):name(n),age(age){};
void disp(girl &);
};
void boy::disp(girl &x) // 該函數必須在girl類定義的後面定義,不然girl類中的私有變量仍是未知的
{
cout<<"boy's name is:"<<name<<",age:"<<age<<endl;
cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl;
//藉助友元,在boy的成員函數disp中,藉助girl的對象,直接訪問girl的私有變量
//正常狀況下,只容許在girl的成員函數中訪問girl的私有變量
}
void main()
{
boy b("aaa",8);
girl g("bbb",99);
b.disp(g);
}
複製代碼
operator
和其後要重載的運算符符號構成的。與其餘函數同樣,重載運算符有一個返回類型和一個參數列表。#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重載 + 運算符,用於把兩個 Box 對象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
// 程序的主函數
int main( )
{
Box Box1; // 聲明 Box1,類型爲 Box
Box Box2; // 聲明 Box2,類型爲 Box
Box Box3; // 聲明 Box3,類型爲 Box
double volume = 0.0; // 把體積存儲在該變量中
// Box1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的體積
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的體積
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把兩個對象相加,獲得 Box3
Box3 = Box1 + Box2;
// Box3 的體積
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
複製代碼
打印結果:
Volume of Box1 : 210 Volume of Box2 : 1560 Volume of Box3 : 5400
class derived-class: access-specifier base-class
;access-specifier
是 public
、protected
或 private
其中的一個,base-class
是以前定義過的某個類的名稱。若是未使用訪問修飾符 access-specifier
,則默認爲 private
。private
。基類的構造函數、析構函數和拷貝構造函數。
基類的重載運算符。 基類的友元函數。
當一個類派生自基類,該基類能夠被繼承爲 public
、protected
或 private
幾種類型。繼承類型是經過上面講解的訪問修飾符 access-specifier
來指定的。
咱們幾乎不使用 protected
或 private
繼承,一般使用 public
繼承。當使用不一樣類型的繼承時,遵循如下幾個規則:
公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,可是能夠經過調用基類的公有和保護成員來訪問。
保護繼承(protected): 當一個類派生自保護基類時,基類的公有和保護成員將成爲派生類的保護成員。 私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員將成爲派生類的私有成員。
#include <iostream>
using namespace std;
// 基類
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生類
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 輸出對象的面積
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
複製代碼
打印結果:
Total area: 35
定義一個函數爲虛函數,不表明函數爲不被實現的函數。
定義他爲虛函數是爲了容許用基類的指針來調用子類的這個函數。 定義一個函數爲純虛函數,才表明函數沒有被實現。 定義純虛函數是爲了實現一個接口,起到一個規範的做用,規範繼承這個類的程序員必須實現這個函數。
class A
{
public:
virtual void foo()
{
cout<<"A::foo() is called"<<endl;
}
};
class B:public A
{
public:
void foo()
{
cout<<"B::foo() is called"<<endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在這裏,a雖然是指向A的指針,可是被調用的函數(foo)倒是B的!
return 0;
}
複製代碼
virtual void funtion()=0
模板函數定義的通常形式以下所示:
template <typename type> ret-type func-name(parameter list)
{
// 函數的主體
}
複製代碼
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
複製代碼
打印結果:
Max(i, j): 39 Max(f1, f2): 20.7 Max(s1, s2): World
類模板,泛型類聲明的通常形式以下所示:
template <class type> class class-name {
.
.
.
}
複製代碼
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入棧
void pop(); // 出棧
T top() const; // 返回棧頂元素
bool empty() const{ // 若是爲空則返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加傳入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 刪除最後一個元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最後一個元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 類型的棧
Stack<string> stringStack; // string 類型的棧
// 操做 int 類型的棧
intStack.push(7);
cout << intStack.top() <<endl;
// 操做 string 類型的棧
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
複製代碼
打印結果:
7 hello Exception: Stack<>::pop(): empty stack
特色:
vector
頭部與中間插入和刪除效率較低,在尾部插入和刪除效率高,支持隨機訪問。deque
是在頭部和尾部插入和刪除效率較高,支持隨機訪問,但效率沒有 vector
高。list
在任意位置的插入和刪除效率都較高,但不支持隨機訪問。set
由紅黑樹實現,其內部元素依據其值自動排序,每一個元素值只能出現一次,不容許重複,且插入和刪除效率比用其餘序列容器高。map
能夠自動創建 Key - value 的對應,key 和 value 能夠是任意你須要的類型,根據 key 快速查找記錄。選擇:
vector
。list
。deque
。map
,一對多的狀況使用 multimap
。set
,不惟一存在的狀況使用 multiset
。時間複雜度:
vector
在頭部和中間位置插入和刪除的時間複雜度爲 O(N),在尾部插入和刪除的時間複雜度爲 O(1),查找的時間複雜度爲 O(1);deque
在中間位置插入和刪除的時間複雜度爲 O(N),在頭部和尾部插入和刪除的時間複雜度爲 O(1),查找的時間複雜度爲 O(1);list
在任意位置插入和刪除的時間複雜度都爲 O(1),查找的時間複雜度爲 O(N);set
和 map
都是經過紅黑樹實現,所以插入、刪除和查找操做的時間複雜度都是 O(log N)。namespace namespace_name {
// 代碼聲明
}
複製代碼
namespace {
// 代碼聲明
}
複製代碼
命名空間名::成員名 ……
定義方式,爲命名空間添加新成員,而必須先在命名空間的定義中添加新成員的聲明。using namespace
指令,這樣在使用命名空間時就能夠不用在前面加上命名空間的名稱。這個指令會告訴編譯器,後續的代碼將使用指定的命名空間中的名稱。#include <iostream>
using namespace std;
// 第一個命名空間
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二個命名空間
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space;
int main ()
{
// 調用第一個命名空間中的函數
func();
return 0;
}
複製代碼
using namespace
)外,還可使用 using聲明
來簡化對命名空間中的名稱的使用:using 命名空間名::[命名空間名::……]成員名;
。注意,關鍵字 using
後面並無跟關鍵字 namespace
,並且最後必須爲命名空間的成員名(而在 using
編譯指令的最後,必須爲命名空間名)。
using指令
使用後,能夠一勞永逸,對整個命名空間的全部成員都有效,很是方便。而using聲明
,則必須對命名空間的不一樣成員名稱,一個一個地去聲明。可是,通常來講,使用using聲明
會更安全。由於,using聲明
只導入指定的名稱,若是該名稱與局部名稱發生衝突,編譯器會報錯。而using指令
導入整個命名空間中的全部成員的名稱,包括那些可能根本用不到的名稱,若是其中有名稱與局部名稱發生衝突,則編譯器並不會發出任何警告信息,而只是用局部名去自動覆蓋命名空間中的同名成員。特別是命名空間的開放性,使得一個命名空間的成員,可能分散在多個地方,程序員難以準確知道,別人到底爲該命名空間添加了哪些名稱。
.so
庫;//MainActivity.java
static {
System.loadLibrary("native-lib");
}
複製代碼
//MainActivity.java
public native String stringFromJNI();
複製代碼
//native-lib.cpp
#include <jni.h>
#include <string>
//函數名的構成:Java 加上包名、方法名並用下劃線鏈接(Java_packageName_methodName)
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_cppdemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
複製代碼
# CMakeLists.txt
# 設置構建本地庫所需的最小版本的cbuild。
cmake_minimum_required(VERSION 3.4.1)
# 建立並命名一個庫,將其設置爲靜態
# 或者共享,並提供其源代碼的相對路徑。
# 您能夠定義多個庫,而cbuild爲您構建它們。
# Gradle自動將共享庫與你的APK打包。
add_library( native-lib #設置庫的名稱。即SO文件的名稱,生產的so文件爲「libnative-lib.so」, 在加載的時候「System.loadLibrary("native-lib");」
SHARED # 將庫設置爲共享庫。
native-lib.cpp # 提供一個源文件的相對路徑
helloJni.cpp # 提供同一個SO文件中的另外一個源文件的相對路徑
)
# 搜索指定的預構建庫,並將該路徑存儲爲一個變量。由於cbuild默認包含了搜索路徑中的系統庫,因此您只須要指定您想要添加的公共NDK庫的名稱。cbuild在完成構建以前驗證這個庫是否存在。
find_library(log-lib # 設置path變量的名稱。
log # 指定NDK庫的名稱 你想讓CMake來定位。
)
#指定庫的庫應該連接到你的目標庫。您能夠連接多個庫,好比在這個構建腳本中定義的庫、預構建的第三方庫或系統庫。
target_link_libraries( native-lib # 指定目標庫中。與 add_library的庫名稱必定要相同
${log-lib} # 將目標庫連接到日誌庫包含在NDK。
)
#若是須要生產多個SO文件的話,寫法以下
add_library( natave-lib # 設置庫的名稱。另外一個so文件的名稱
SHARED # 將庫設置爲共享庫。
nataveJni.cpp # 提供一個源文件的相對路徑
)
target_link_libraries( natave-lib #指定目標庫中。與 add_library的庫名稱必定要相同
${log-lib} # 將目標庫連接到日誌庫包含在NDK。
)
複製代碼
// build.gradle(:app)
android {
compileSdkVersion 29
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.cppdemo"
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
複製代碼
JavaVM
是 Java 虛擬機在 JNI 層的表明, JNI 全局只有一個;JNIEnv
是指向可用 JNI 函數表的接口指針,第二個參數 jobject
是 Java 函數所在類的實例的 Java 對象引用;JNIEnv
是 JavaVM
在線程中的表明, 每一個線程都有一個, JNI 中可能有不少個 JNIEnv
,同時 JNIEnv
具備線程相關性,也就是 B 線程沒法使用 A 線程的 JNIEnv
;JNIEnv
類型實際上表明瞭 Java 環境,經過這個 JNIEnv*
指針,就能夠對 Java 端的代碼進行操做:調用 Java 函數; 操做 Java 對象;
JNIEnv
的本質是一個與線程相關的表明 JNI 環境的結構體,裏面存放了大量的 JNI 函數指針;JNIEnv
內部結構以下:JavaVM
的結構以下:Signature格式 | Java | Native | Description |
---|---|---|---|
B | byte | jbyte | signed 8 bits |
C | char | jchar | unsigned 16 bits |
D | double | jdouble | 64 bits |
F | float | jfloat | 32 bits |
I | int | jint | signed 32 bits |
S | short | jshort | signed 16 bits |
J | long | jlong | signed 64 bits |
Z | boolean | jboolean | unsigned 8 bits |
V | void | void | N/A |
數組簡稱:在前面添加 [
Signature格式 | Java | Native |
---|---|---|
[B | byte[] | jbyteArray |
[C | char[] | jcharArray |
[D | double[] | jdoubleArray |
[F | float[] | jfloatArray |
[I | int[] | jintArray |
[S | short[] | jshortArray |
[J | long[] | jlongArray |
[Z | boolean[] | jbooleanArray |
對象類型簡稱:L+classname +;
Signature格式 | Java | Native |
---|---|---|
Ljava/lang/String; | String | jstring |
L+classname +; | 全部對象 | jobject |
[L+classname +; | Object[] | jobjectArray |
Ljava.lang.Class; | Class | jclass |
Ljava.lang.Throwable; | Throwable | jthrowable |
(輸入參數...)返回值參數
Signature格式 | Java函數 |
---|---|
()V | void func() |
(I)F | float func(int i) |
([I)J | long func(int[] i) |
(Ljava/lang/Class;)D | double func(Class c) |
([ILjava/lang/String;)Z | boolean func(int[] i,String s) |
(I)Ljava/lang/String; | String func(int i) |
jclass thisclazz = env->GetObjectClass(thiz);//使用GetObjectClass方法獲取thiz對應的jclass。
jclass thisclazz = env->FindClass("com/xxx/xxx/abc");//直接搜索類名
複製代碼
/**
* thisclazz -->上一步獲取的 jclass
* "onCallback"-->要調用的方法名
* "(I)Ljava/lang/String;"-->方法的 Signature, 簽名參照前面的第 3.2 小節表格。
*/
jmethodID mid_callback = env->GetMethodID(thisclazz , "onCallback", "(Ljava/lang/String;)I");
jmethodID mid_callback = env->GetStaticMethodID(thisclazz , "onCallback", "(Ljava/lang/String;)I");//獲取靜態方法的ID
複製代碼
jint result = env->CallIntMethod(thisclazz , mid_callback , jstrParams);
jint result = env->CallStaticIntMethod(thisclazz , mid_callback , jstrParams);//調用靜態方法
複製代碼
貼一下JNI 經常使用接口文檔,有須要能夠在這裏查詢。
NewLocalRef
和各類JNI接口建立(FindClass
、NewObject
、GetObjectClass
和NewCharArray
等)。GetStringUTFChars
以後,調用 ReleaseStringUTFChars
釋放;對於手動建立的 jclass
,jobject
等對象使用 DeleteLocalRef
方法進行釋放。NewGlobalRef
基於局部引用建立。DeleteGlobalRef
來手動刪除全局引用調用。NewWeakGlobalRef
基於局部引用或全局引用建立。DeleteWeakGlobalRef
手動釋放。本文參考了部分視頻、書籍、博客的內容。這裏就不列出來了。