一篇文章帶你入門NDK開發

NDK開發

1、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是一個指針,它指向引用,但引用不是實體,因此這是錯誤的
複製代碼

在分析上面代碼時,能夠從變量標識符開始從右往左看,最靠近標識符的是變量的本質類型,而再往左即爲對變量類型的進一步修飾。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->aandroid

  • ::

:: 是做用域符,是運算符中等級最高的,它分爲三種:ios

  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內存佈局

C 語言內存組成

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)結構體大小必須是全部成員大小的整數倍,也即全部成員大小的公倍數markdown

內存對齊

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指令 導入整個命名空間中的全部成員的名稱,包括那些可能根本用不到的名稱,若是其中有名稱與局部名稱發生衝突,則編譯器並不會發出任何警告信息,而只是用局部名去自動覆蓋命名空間中的同名成員。特別是命名空間的開放性,使得一個命名空間的成員,可能分散在多個地方,程序員難以準確知道,別人到底爲該命名空間添加了哪些名稱。

2、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"
        }
    }
}
複製代碼

3、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 內部結構以下:

JNIEnv 內部結構

  • JavaVM 的結構以下:

JavaVM 結構

3.2 數據類型

3.2.1 基礎數據類型
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
3.2.2 數組數據類型

數組簡稱:在前面添加 [

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
3.2.3 複雜數據類型

對象類型簡稱: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
3.2.4 函數簽名

(輸入參數...)返回值參數

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)

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 手動釋放。

後續

本文參考了部分視頻、書籍、博客的內容。這裏就不列出來了。

相關文章
相關標籤/搜索