一篇文章带你入门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 手动释放。

后续

本文参考了部分视频、书籍、博客的内容。这里就不列出来了。

相关文章
相关标签/搜索