主頁 > 移動端開發 > 騰訊T4大佬閑暇無事整理好的NDK開發,帶你入門NDK開發

騰訊T4大佬閑暇無事整理好的NDK開發,帶你入門NDK開發

2021-09-24 11:18:41 移動端開發

在這里插入圖片描述

一、C++ 基礎知識

1.1 函式

  • 函式是一組一起執行一個任務的陳述句,每個 C 程式都至少有一個函式,即主函式 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是一個指標,它指向參考,但參考不是物體,所以這是錯誤的
復制代碼

在分析上面代碼時,可以從變數識別符號開始從右往左看,最靠近識別符號的是變數的本質型別,而再往左即為對變數型別的進一步修飾,

例如:int * & a 識別符號a的左邊緊鄰的是 &,證明 a 是一個參考變數,而再往左是 * ,可見 a 是一個指標的參考,再往左是 int,可見 a 是一個指向int型別的指標的參考,

  • .->
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 不是指標,是結構體名,

一般情況下用 “.” 只需要宣告一個結構體,格式是:結構體型別名+結構體名,然后用 結構體名加“.”加成員名 就可以參考成員了,因為自動分配了結構體的記憶體,如同 int a; 一樣, 用 “->” ,則要宣告一個結構體指標,還要手動開辟一個該結構體的記憶體(上面的代碼則是建了一個結構體實體,自動分配了記憶體,下面的例子則會講到手動動態開辟記憶體),然后把回傳的地址賦給宣告的結構體指標,才能用“->” 正確參考,否則記憶體中只分配了指標的記憶體,沒有分配結構體的記憶體,導致想要的結構體實際上是不存在,這時候用 “->” 參考自然出錯了,因為沒有結構體,自然沒有結構體的域了, 此外,(*p).a 等價于 p->a

  • ::

:: 是作用域符,是運算子中等級最高的,它分為三種:

  1. 全域作用域符,用法 ::name
  2. 類作用域符,用法 class::name
  3. 命名空間作用域符,用法 namespace::name

他們都是左關聯,他們的作用都是為了更明確的呼叫你想要的變數:

  1. 如在程式中的某一處你想呼叫全域變數 a,那么就寫成 ::a;(也可以是全域函式)
  2. 如果想呼叫 class A 中的成員變數 a,那么就寫成 A::a
  3. 另外一個如果想呼叫 namespace std 中的 cout 成員,你就寫成 std::cout(相當于 using namespace std;cout)意思是在這里我想用 cout 物件是命名空間 std 中的 cout(即就是標準庫里邊的cout);
  1. 表示“域運算子”:宣告了一個類 A,類 A 里宣告了一個成員函式 void f(),但沒有在類的宣告里給出 f 的定義,那么在類外定義f時, 就要寫成 void A::f(),表示這個 f() 函式是類 A 的成員函式,
  2. 直接用在全域函式前,表示是全域函式:在 VC 里,你可以在呼叫 API 函式里,在 API 函式名前加 ::
  3. 表示參考成員函式及變數,作用域成員運算子:System::Math::Sqrt() 相當于 System.Math.Sqrt()

1.2 linux記憶體布局

[圖片上傳失敗...(image-365f2d-1632398115969)]

1.3 指標陣列

  • 陣列: 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][]

1.4 結構體

struct Person
{
	char c;
	int i;
    char ch;
};

int main()
{
	struct Person person;
	person.c = 8;
	person.i = 9;
}
復制代碼

存盤變數時地址要求對齊,編譯器在編譯程式時會遵循兩個原則:

(1)結構體變數中成員的偏移量必須是成員大小的整數倍 (2)結構體大小必須是所有成員大小的整數倍,也即所有成員大小的公倍數

[圖片上傳失敗...(image-2ef9b7-1632398115969)]

1.5 共用體

  • 共用體是一種特殊的資料型別,允許你在相同的記憶體位置存盤不同的資料型別,
  • 你可以定義一個帶有多成員的共用體,但是任何時候只能有一個成員帶有值,
  • 共用體提供了一種使用相同的記憶體位置的有效方式,
  • 共用體占用的記憶體應足夠存盤共用體中最大的成員,
union Data
{
	int i;
	float f;
	char str[20];
}data;

int main()
{
	union Data data;
	data.i = 11;
}
復制代碼

1.6 typedef

  • 定義一種型別的別名,而不只是簡單的宏替換,可以用作同時宣告指標型的多個物件:
char *pa, *pb;//傳統寫法
復制代碼
typedef char* PCHAR; // 使用typedef 寫法  一般用大寫
PCHAR pa, pb; // 可行,同時宣告了兩個指向字符變數的指標
復制代碼
  • 用在舊的C的代碼中,幫助 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];
復制代碼

1.7 類的構造和決議、友元函式

1.7.1 C++ 中頭檔案(.h)和源檔案(.cpp)
  • .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;
}
復制代碼
1.7.2 建構式和解構式
  • 類的建構式是類的一種特殊的成員函式,它會在每次創建類的新物件時執行,建構式的名稱與類的名稱是完全相同的,并且不會回傳任何型別,也不會回傳 void,建構式可用于為某些成員變數設定初始值,
  • 類的解構式是類的一種特殊的成員函式,它會在每次洗掉所創建的物件時執行,解構式的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作為前綴,它不會回傳任何值,也不能帶有任何引數,解構式有助于在跳出程式(比如關閉檔案、釋放記憶體等)前釋放資源,
1.7.3 友元函式、友元類
  • 友元函式是一種定義在類外部的普通函式,它不屬于任何類,但它需要在類體內進行說明,為了與該類的成員函式加以區別,在說明時前面加以關鍵字 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); 
}
復制代碼

1.8 單例物件、運算子多載

  • 我們可以重定義或多載大部分 C++ 內置的運算子,這樣就能使用自定義型別的運算子,多載的運算子是帶有特殊名稱的函式,函式名是由關鍵字 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

1.9 繼承多型、虛函式

1.9.1 繼承
  • 一個類可以派生自多個類,這意味著,它可以從多個基類繼承資料和函式,定義一個派生類,我們使用一個類派生串列來指定基類,類派生串列以一個或多個基類命名:class derived-class: access-specifier base-class
  • 其中,訪問修飾符 access-specifierpublicprotectedprivate 其中的一個,base-class 是之前定義過的某個類的名稱,如果未使用訪問修飾符 access-specifier,則默認為 private
  • 派生類可以訪問基類中所有的非私有成員,因此基類成員如果不想被派生類的成員函式訪問,則應在基類中宣告為 private
  • 一個派生類繼承了所有的基類方法,但下列情況除外:

基類的建構式、解構式和拷貝建構式,

基類的多載運算子, 基類的友元函式,

  • 當一個類派生自基類,該基類可以被繼承為 publicprotectedprivate 幾種型別,繼承型別是通過上面講解的訪問修飾符 access-specifier 來指定的,

  • 我們幾乎不使用 protectedprivate 繼承,通常使用 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

1.9.2 虛函式

定義一個函式為虛函式,不代表函式為不被實作的函式,

定義他為虛函式是為了允許用基類的指標來呼叫子類的這個函式, 定義一個函式為純虛函式,才代表函式沒有被實作, 定義純虛函式是為了實作一個介面,起到一個規范的作用,規范繼承這個類的程式員必須實作這個函式,

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;
}
復制代碼
  • 一個類函式的呼叫并不是在編譯時刻被確定的,而是在運行時刻被確定的,由于撰寫代碼的時候并不能確定被呼叫的是基類的函式還是哪個派生類的函式,所以被稱為“虛”函式,
  • 純虛函式是在基類中宣告的虛函式,它在基類中沒有定義,但要求任何派生類都要定義自己的實作方法,在基類中實作純虛函式的方法是在函式原型后加 “=0” :virtual void funtion()=0
  • 將函式定義為純虛函式,則編譯器要求在派生類中必須予以重寫以實作多型性,宣告了純虛函式的類是一個抽象類,所以,用戶不能創建類的實體,只能創建它的派生類的實體,

1.10 類模板、函式模板

  • 模板是泛型編程的基礎,泛型編程即以一種獨立于任何特定型別的方式撰寫代碼,
  • 模板是創建泛型類或函式的藍圖或公式,

模板函式定義的一般形式如下所示:

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

1.11 容器

  • 序列式容器(Sequence containers),此為可序群集,其中每個元素均有固定位置—取決于插入時機和地點,和元素值無關,如果你以追加方式對一個群集插入六個元素,它們的排列次序將和插入次序一致,STL提供了三個序列式容器:向量(vector)、雙端佇列(deque)、串列(list),此外你也可以把 string 和 array 當做一種序列式容器,
  • 關聯式容器(Associative containers),此為已序群集,元素位置取決于特定的排序準則以及元素值,和插入次序無關,如果你將六個元素置入這樣的群集中,它們的位置取決于元素值,和插入次序無關,STL提供了四個關聯式容器:集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap),
  • 容器配接器:根據上面七種基本容器類別實作而成,stack,元素采取 LIFO(后進先出)的管理策略、queue,元素采取 FIFO(先進先出)的管理策略,也就是說,它是個普通的緩沖區(buffer)、priority_queue,元素可以擁有不同的優先權,所謂優先權,乃是基于程式員提供的排序準則(預設使用 operators)而定義,Priority queue 的效果相當于這樣一個 buffer:“下一元素永遠是queue中優先級最高的元素”,如果同時有多個元素具備最髙優先權,則其次序無明確定義,

特點

  1. vector 頭部與中間插入和洗掉效率較低,在尾部插入和洗掉效率高,支持隨機訪問,
  2. deque 是在頭部和尾部插入和洗掉效率較高,支持隨機訪問,但效率沒有 vector 高,
  3. list 在任意位置的插入和洗掉效率都較高,但不支持隨機訪問,
  4. set 由紅黑樹實作,其內部元素依據其值自動排序,每個元素值只能出現一次,不允許重復,且插入和洗掉效率比用其他序列容器高,
  5. map 可以自動建立 Key - value 的對應,key 和 value 可以是任意你需要的型別,根據 key 快速查找記錄,

選擇

  1. 如果需要高效的隨機存取,不在乎插入和洗掉的效率,使用 vector
  2. 如果需要大量的插入和洗掉元素,不關心隨機存取的效率,使用 list
  3. 如果需要隨機存取,并且關心兩端資料的插入和洗掉效率,使用 deque
  4. 如果打算存盤資料字典,并且要求方便地根據 key 找到 value,一對一的情況使用 map,一對多的情況使用 multimap
  5. 如果打算查找一個元素是否存在于某集合中,唯一存在的情況使用 set,不唯一存在的情況使用 multiset

時間復雜度

  1. vector 在頭部和中間位置插入和洗掉的時間復雜度為 O(N),在尾部插入和洗掉的時間復雜度為 O(1),查找的時間復雜度為 O(1);
  2. deque 在中間位置插入和洗掉的時間復雜度為 O(N),在頭部和尾部插入和洗掉的時間復雜度為 O(1),查找的時間復雜度為 O(1);
  3. list 在任意位置插入和洗掉的時間復雜度都為 O(1),查找的時間復雜度為 O(N);
  4. setmap 都是通過紅黑樹實作,因此插入、洗掉和查找操作的時間復雜度都是 O(log N),

1.12 命名空間

1.12.1 namespace
  • 命名空間是一種描述邏輯分組的機制,可以將按某些標準在邏輯上屬于同一個集團的宣告放在同一個命名空間中,用于區分不同庫中相同名稱的函式、類、變數,
namespace namespace_name {
   // 代碼宣告
}
復制代碼
  • 無名命名空間,你可以在當前編譯單元中(無名命名空間之外),直接使用無名命名空間中的成員名稱,但是在當前編譯單元之外,它又是不可見的,它可以使代碼保持區域性,從而保護代碼不被他人非法使用,
namespace {
   // 代碼宣告
}
復制代碼
  • 不能在命名空間的定義中宣告(另一個嵌套的)子命名空間,只能在命名空間的定義中定義子命名空間,
  • 不能直接使用 命名空間名::成員名 …… 定義方式,為命名空間添加新成員,而必須先在命名空間的定義中添加新成員的宣告,
  • 命名空間是開放的,即可以隨時把新的成員名稱加入到已有的命名空間之中去,方法是,多次宣告和定義同一命名空間,每次添加自己的新成員和名稱,
1.12.2 using
  • 可以使用 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編譯指令(組合關鍵字 using namespace)外,還可以使用 using宣告 來簡化對命名空間中的名稱的使用:using 命名空間名::[命名空間名::……]成員名; ,注意,關鍵字 using 后面并沒有跟關鍵字 namespace,而且最后必須為命名空間的成員名(而在 using 編譯指令的最后,必須為命名空間名),

using指令 使用后,可以一勞永逸,對整個命名空間的所有成員都有效,非常方便,而 using宣告,則必須對命名空間的不同成員名稱,一個一個地去宣告,但是,一般來說,使用 using宣告 會更安全,因為,using宣告 只匯入指定的名稱,如果該名稱與區域名稱發生沖突,編譯器會報錯,using指令 匯入整個命名空間中的所有成員的名稱,包括那些可能根本用不到的名稱,如果其中有名稱與區域名稱發生沖突,則編譯器并不會發出任何警告資訊,而只是用區域名去自動覆寫命名空間中的同名成員,特別是命名空間的開放性,使得一個命名空間的成員,可能分散在多個地方,程式員難以準確知道,別人到底為該命名空間添加了哪些名稱,

二、java 呼叫 C/C++

  • 加載 .so 庫;
//MainActivity.java
static {
        System.loadLibrary("native-lib");
}
復制代碼
  • 撰寫 java 函式;
//MainActivity.java
public native String stringFromJNI();
復制代碼
  • 撰寫 C/C++ 函式;
//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());
}
復制代碼
  • ndk/cmake 配置(下面只列出cmake配置);
# 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"
        }
    }
}
復制代碼

三、JNI 基礎

3.1 JNIEnv 、JavaVM

  • JavaVM 是 Java 虛擬機在 JNI 層的代表, JNI 全域只有一個;
  • 從上面的代碼中我們可以發現,雖然 Java 函式不帶引數,但是 native 函式卻帶了兩個引數,第一個引數 JNIEnv 是指向可用 JNI 函式表的介面指標,第二個引數 jobject 是 Java 函式所在類的實體的 Java 物件參考;
  • JNIEnvJavaVM 在執行緒中的代表, 每個執行緒都有一個, JNI 中可能有很多個 JNIEnv,同時 JNIEnv 具有執行緒相關性,也就是 B 執行緒無法使用 A 執行緒的 JNIEnv
  • JNIEnv 型別實際上代表了 Java 環境,通過這個 JNIEnv* 指標,就可以對 Java 端的代碼進行操作:

呼叫 Java 函式; 操作 Java 物件;

  • JNIEnv 的本質是一個與執行緒相關的代表 JNI 環境的結構體,里面存放了大量的 JNI 函式指標;
  • JNIEnv 內部結構如下:

[圖片上傳失敗...(image-d1b2a4-1632398115966)]

  • JavaVM 的結構如下:

[圖片上傳失敗...(image-5ffe41-1632398115966)]

3.2 資料型別

3.2.1 基礎資料型別
Signature格式JavaNativeDescription
Bbytejbytesigned 8 bits
Ccharjcharunsigned 16 bits
Ddoublejdouble64 bits
Ffloatjfloat32 bits
Iintjintsigned 32 bits
Sshortjshortsigned 16 bits
Jlongjlongsigned 64 bits
Zbooleanjbooleanunsigned 8 bits
VvoidvoidN/A
3.2.2 陣列資料型別

陣列簡稱:在前面添加 [

Signature格式JavaNative
[Bbyte[]jbyteArray
[Cchar[]jcharArray
[Ddouble[]jdoubleArray
[Ffloat[]jfloatArray
[Iint[]jintArray
[Sshort[]jshortArray
[Jlong[]jlongArray
[Zboolean[]jbooleanArray
3.2.3 復雜資料型別

物件型別簡稱:L+classname +;

Signature格式JavaNative
Ljava/lang/String;Stringjstring
L+classname +;所有物件jobject
[L+classname +;Object[]jobjectArray
Ljava.lang.Class;Classjclass
Ljava.lang.Throwable;Throwablejthrowable
3.2.4 函式簽名

(輸入引數...)回傳值引數

Signature格式Java函式
()Vvoid func()
(I)Ffloat func(int i)
([I)Jlong func(int[] i)
(Ljava/lang/Class;)Ddouble func(Class c)
([ILjava/lang/String;)Zboolean func(int[] i,String s)
(I)Ljava/lang/String;String func(int i)

3.3 JNI 操作 JAVA 物件、類

  • 獲取你需要訪問的 Java 物件的類:
jclass thisclazz = env->GetObjectClass(thiz);//使用GetObjectClass方法獲取thiz對應的jclass, 
jclass thisclazz = env->FindClass("com/xxx/xxx/abc");//直接搜索類名
復制代碼
  • 獲取 MethodID:
/**
 * 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 常用介面檔案,有需要可以在這里查詢,

3.4 JNI 參考

3.4.1 區域參考
  • 通過 NewLocalRef 和各種JNI介面創建(FindClassNewObjectGetObjectClassNewCharArray等),
  • 會阻止 GC 回收所參考的物件,不在本地函式中跨函式使用,不能跨線前使用,
  • 函式回傳后區域參考所參考的物件會被 JVM 自動釋放,或手動釋放,
  • 手動釋放的方式:GetXXX 就必須呼叫 ReleaseXXX,呼叫完 GetStringUTFChars 之后,呼叫 ReleaseStringUTFChars 釋放;對于手動創建的 jclassjobject 等物件使用 DeleteLocalRef 方法進行釋放,
3.4.2 全域參考
  • 呼叫 NewGlobalRef 基于區域參考創建,
  • 會阻 GC 回收所參考的物件,可以跨方法、跨執行緒使用,
  • JVM 不會自動釋放,必須手動釋放,
  • 全域參考在顯式釋放之前保持有效,必須通過 DeleteGlobalRef 來手動洗掉全域參考呼叫,
3.4.3 弱全域參考
  • 呼叫 NewWeakGlobalRef 基于區域參考或全域參考創建,
  • 不會阻止 GC 回收所參考的物件,可以跨方法、跨執行緒使用,
  • 參考不會自動釋放,在 JVM 認為應該回收它的時候(比如記憶體緊張的時候)進行回收而被釋放,或呼叫 DeleteWeakGlobalRef 手動釋放,

后續

本文參考了部分視頻、書籍、博客的內容,這里就不列出來了,想要了解更多NDK開發專案與學習資料,點擊下方二維碼,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/302538.html

標籤:其他

上一篇:【已OC】位元組_Android_社招_一二三面

下一篇:Unity WebView 插件??(十一)特定模塊 安卓網頁視圖—AndroidWebView

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more