7年老Android開(kāi)發(fā):一篇文章帶你入門(mén)NDK開(kāi)發(fā)

一荣恐、C++ 基礎(chǔ)知識(shí)

1.1 函數(shù)

  • 函數(shù)是一組一起執(zhí)行一個(gè)任務(wù)的語(yǔ)句。每個(gè) C 程序都至少有一個(gè)函數(shù)混萝,即主函數(shù) main() 嘹吨,所有簡(jiǎn)單的程序都可以定義其他額外的函數(shù)逾冬。
  • .h 頭文件 。
  • 指針函數(shù):指帶指針的函數(shù)躺苦,即本質(zhì)是一個(gè)函數(shù)。函數(shù)返回類型是某一類型的指針产还。int *func(int x, int y)匹厘。
  • 函數(shù)指針:指向函數(shù)的指針變量,即本質(zhì)是一個(gè)指針變量脐区。int (*funcp)(int x)愈诚。
int i;
int *a = &i;        //這里a是一個(gè)指針,它指向變量i
int &b = i;         //這里b是一個(gè)引用牛隅,它是變量i的引用(別名)
int * &c = a;       //這里c是一個(gè)引用炕柔,它是指針a的引用
int & *d;           //這里d是一個(gè)指針,它指向引用媒佣,但引用不是實(shí)體匕累,所以這是錯(cuò)誤的
復(fù)制代碼

在分析上面代碼時(shí),可以從變量標(biāo)識(shí)符開(kāi)始從右往左看默伍,最靠近標(biāo)識(shí)符的是變量的本質(zhì)類型欢嘿,而再往左即為對(duì)變量類型的進(jìn)一步修飾衰琐。 例如:int * & a 標(biāo)識(shí)符a的左邊緊鄰的是 &,證明 a 是一個(gè)引用變量炼蹦,而再往左是 * 羡宙,可見(jiàn) a 是一個(gè)指針的引用,再往左是 int掐隐,可見(jiàn) a 是一個(gè)指向int類型的指針的引用狗热。

  • .->
struct Data
{
int a,b,c;
}; /*定義結(jié)構(gòu)體類型*/
struct Data * p;                 /*  定義結(jié)構(gòu)體指針   */
struct Data A = {1,2,3};         / *  聲明結(jié)構(gòu)體變量A,A即結(jié)構(gòu)體名   */
int x;                               /*  聲明一個(gè)變量x  */
p = &A ;                           /*   地址賦值虑省,讓p指向A    */
x = p->a;        /* 取出p所指向的結(jié)構(gòu)體中包含的數(shù)據(jù)項(xiàng)a賦值給x   */
/* 此時(shí)由于p指向A匿刮,因而 p->a == A.a,也就是1 */
復(fù)制代碼

因?yàn)榇颂?p 是一個(gè)指針,所以不能使用.號(hào)訪問(wèn)內(nèi)部成員(即不能 p.a)慷妙,而要使用 ->僻焚。但是 A.a 是可以的,因?yàn)?A 不是指針膝擂,是結(jié)構(gòu)體名虑啤。 一般情況下用 “.” 只需要聲明一個(gè)結(jié)構(gòu)體。格式是:結(jié)構(gòu)體類型名+結(jié)構(gòu)體名架馋。然后用 結(jié)構(gòu)體名加“.”加成員名 就可以引用成員了狞山。因?yàn)樽詣?dòng)分配了結(jié)構(gòu)體的內(nèi)存。如同 int a; 一樣叉寂。 用 “->” 萍启,則要聲明一個(gè)結(jié)構(gòu)體指針,還要手動(dòng)開(kāi)辟一個(gè)該結(jié)構(gòu)體的內(nèi)存(上面的代碼則是建了一個(gè)結(jié)構(gòu)體實(shí)例屏鳍,自動(dòng)分配了內(nèi)存勘纯,下面的例子則會(huì)講到手動(dòng)動(dòng)態(tài)開(kāi)辟內(nèi)存),然后把返回的地址賦給聲明的結(jié)構(gòu)體指針钓瞭,才能用“->” 正確引用驳遵。否則內(nèi)存中只分配了指針的內(nèi)存,沒(méi)有分配結(jié)構(gòu)體的內(nèi)存山涡,導(dǎo)致想要的結(jié)構(gòu)體實(shí)際上是不存在堤结。這時(shí)候用 “->” 引用自然出錯(cuò)了,因?yàn)闆](méi)有結(jié)構(gòu)體鸭丛,自然沒(méi)有結(jié)構(gòu)體的域了竞穷。 此外,(*p).a 等價(jià)于 p->a鳞溉。

  • ::

:: 是作用域符瘾带,是運(yùn)算符中等級(jí)最高的,它分為三種:

  1. 全局作用域符熟菲,用法 ::name
  2. 類作用域符月弛,用法 class::name
  3. 命名空間作用域符肴盏,用法 namespace::name

他們都是左關(guān)聯(lián),他們的作用都是為了更明確的調(diào)用你想要的變量:

  1. 如在程序中的某一處你想調(diào)用全局變量 a帽衙,那么就寫(xiě)成 ::a菜皂;(也可以是全局函數(shù))
  2. 如果想調(diào)用 class A 中的成員變量 a,那么就寫(xiě)成 A::a厉萝;
  3. 另外一個(gè)如果想調(diào)用 namespace std 中的 cout 成員恍飘,你就寫(xiě)成 std::cout(相當(dāng)于 using namespace std;cout)意思是在這里我想用 cout 對(duì)象是命名空間 std 中的 cout(即就是標(biāo)準(zhǔn)庫(kù)里邊的cout)谴垫;
  1. 表示“域操作符”:聲明了一個(gè)類 A章母,類 A 里聲明了一個(gè)成員函數(shù) void f(),但沒(méi)有在類的聲明里給出 f 的定義翩剪,那么在類外定義f時(shí)乳怎, 就要寫(xiě)成 void A::f(),表示這個(gè) f() 函數(shù)是類 A 的成員函數(shù)前弯。
  2. 直接用在全局函數(shù)前蚪缀,表示是全局函數(shù):在 VC 里,你可以在調(diào)用 API 函數(shù)里恕出,在 API 函數(shù)名前加 ::
  3. 表示引用成員函數(shù)及變量询枚,作用域成員運(yùn)算符:System::Math::Sqrt() 相當(dāng)于 System.Math.Sqrt()

1.2 linux內(nèi)存布局

1.3 指針數(shù)組

  • 數(shù)組: int arr[] = {1,2,3};
  • 指針: int* p = arr浙巫,指針 p 指向數(shù)組 arr 的首地址金蜀;*p = 6; 將 arr 數(shù)組的第一個(gè)元素賦值為 6;*(p+1) = 10; 將 arr 數(shù)組第二個(gè)元素賦值為 10的畴;
  • 指針數(shù)組:數(shù)組里面每一個(gè)元素都是指針
int* p[3]渊抄;
for(int i = 0; i<3; i++){
  p[i] = &arr[i];
}
復(fù)制代碼
  • 數(shù)組指針:也稱為行指針;int (*p)[n]

優(yōu)先級(jí)高丧裁,首先說(shuō)明 p 是一個(gè)指針抒线,指向一個(gè)整型的一維數(shù)組,這個(gè)一維數(shù)組的長(zhǎng)度是 n渣慕,也可以說(shuō)是 p 的步長(zhǎng)。執(zhí)行 p+1 時(shí)抱慌,p 要跨過(guò) n 個(gè)整型數(shù)據(jù)的長(zhǎng)度逊桦。 int a[3][4]; int (*p)[4]; //該語(yǔ)句是定義一個(gè)數(shù)組指針,指向含 4 個(gè)元素的一維數(shù)組 p = a; //將該二維數(shù)組的首地址賦給 p抑进,也就是 a[0] 或 &a[0][0] p++; //該語(yǔ)句執(zhí)行后强经,也就是 p = p+1; p 跨過(guò)行 a[0][] 指向了行 a[1][]

1.4 結(jié)構(gòu)體

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

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

存儲(chǔ)變量時(shí)地址要求對(duì)齊,編譯器在編譯程序時(shí)會(huì)遵循兩個(gè)原則: (1)結(jié)構(gòu)體變量中成員的偏移量必須是成員大小的整數(shù)倍 (2)結(jié)構(gòu)體大小必須是所有成員大小的整數(shù)倍寺渗,也即所有成員大小的公倍數(shù)

1.5 共用體

  • 共用體是一種特殊的數(shù)據(jù)類型匿情,允許你在相同的內(nèi)存位置存儲(chǔ)不同的數(shù)據(jù)類型兰迫。
  • 你可以定義一個(gè)帶有多成員的共用體,但是任何時(shí)候只能有一個(gè)成員帶有值炬称。
  • 共用體提供了一種使用相同的內(nèi)存位置的有效方式汁果。
  • 共用體占用的內(nèi)存應(yīng)足夠存儲(chǔ)共用體中最大的成員。
union Data
{
    int i;
    float f;
    char str[20];
}data;

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

1.6 typedef

  • 定義一種類型的別名玲躯,而不只是簡(jiǎn)單的宏替換据德。可以用作同時(shí)聲明指針型的多個(gè)對(duì)象:
char *pa, *pb;//傳統(tǒng)寫(xiě)法
復(fù)制代碼
typedef char* PCHAR; // 使用typedef 寫(xiě)法  一般用大寫(xiě)
PCHAR pa, pb; // 可行跷车,同時(shí)聲明了兩個(gè)指向字符變量的指針
復(fù)制代碼
  • 用在舊的C的代碼中棘利,幫助 struct。以前的代碼中朽缴,聲明 struct 新對(duì)象時(shí)善玫,必須要帶上 struct,即形式為: struct 結(jié)構(gòu)名 對(duì)象名
struct tagPOINT1
{
int x;
int y;
};
struct tagPOINT1 p1;
復(fù)制代碼
//使用 typedef
typedef struct tagPOINT
{
int x;
int y;
}POINT;

POINT p1; // 這樣就比原來(lái)的方式少寫(xiě)了一個(gè)struct密强,比較省事茅郎,尤其在大量使用的時(shí)候
復(fù)制代碼
  • typedef 來(lái)定義與平臺(tái)無(wú)關(guān)的類型:
#if __ANDROID__
typedef double SUM;
#else
typedef float SUM ;
#endif

int test() {
    SUM a;
    return 0;
}
復(fù)制代碼
  • 為復(fù)雜的聲明定義一個(gè)新的簡(jiǎn)單的別名:
 //原聲明:
int *(*a[5])(int, char*);
//變量名為a,直接用一個(gè)新別名pFun替換a就可以了:
typedef int *(*pFun)(int, char*);
//原聲明的最簡(jiǎn)化版:
pFun a[5];
復(fù)制代碼

1.7 類的構(gòu)造和解析誓斥、友元函數(shù)

1.7.1 C++ 中頭文件(.h)和源文件(.cpp)
  • .h 這里一般寫(xiě)類的聲明(包括類里面的成員和方法的聲明)只洒、函數(shù)原型、#define常數(shù)等劳坑,但一般來(lái)說(shuō)不寫(xiě)出具體的實(shí)現(xiàn)毕谴。寫(xiě)頭文件時(shí),為了防止重復(fù)編譯距芬,我們?cè)陂_(kāi)頭和結(jié)尾處必須按照如下樣式加上預(yù)編譯語(yǔ)句:
#ifndef CIRCLE_H
#define CIRCLE_H

class Circle
{
private:
    double r;
public:
    Circle();//構(gòu)造函數(shù)
    Circle(double R);//構(gòu)造函數(shù)
    double Area();
};

#endif
復(fù)制代碼

至于 CIRCLE_H 這個(gè)名字實(shí)際上是無(wú)所謂的涝开,你叫什么都行,只要符合規(guī)范都行框仔。原則上來(lái)說(shuō)舀武,非常建議把它寫(xiě)成這種形式,因?yàn)楸容^容易和頭文件的名字對(duì)應(yīng)离斩。

  • .cpp 源文件主要寫(xiě)實(shí)現(xiàn)頭文件中已經(jīng)聲明的那些函數(shù)的具體代碼银舱。需要注意的是,開(kāi)頭必須 #include 一下實(shí)現(xiàn)的頭文件跛梗,以及要用到的頭文件寻馏。
#include "Circle.h"

Circle::Circle()
{
    this->r=5.0;
}
Circle::Circle(double R)
{
    this->r=R;
}
double Circle:: Area()
{
    return 3.14*r*r;
}
復(fù)制代碼
  • 最后,我們建一個(gè) main.cpp 來(lái)測(cè)試我們寫(xiě)的 Circle 類
#include <iostream>
#include "Circle.h"
using namespace std;

int main()
{
    Circle c(3);
    cout<<"Area="<<c.Area()<<endl;
    return 1;
}
復(fù)制代碼
1.7.2 構(gòu)造函數(shù)和析構(gòu)函數(shù)
  • 類的構(gòu)造函數(shù)是類的一種特殊的成員函數(shù)核偿,它會(huì)在每次創(chuàng)建類的新對(duì)象時(shí)執(zhí)行诚欠。構(gòu)造函數(shù)的名稱與類的名稱是完全相同的,并且不會(huì)返回任何類型,也不會(huì)返回 void轰绵。構(gòu)造函數(shù)可用于為某些成員變量設(shè)置初始值粉寞。
  • 類的析構(gòu)函數(shù)是類的一種特殊的成員函數(shù),它會(huì)在每次刪除所創(chuàng)建的對(duì)象時(shí)執(zhí)行左腔。析構(gòu)函數(shù)的名稱與類的名稱是完全相同的唧垦,只是在前面加了個(gè)波浪號(hào)(~)作為前綴,它不會(huì)返回任何值翔悠,也不能帶有任何參數(shù)业崖。析構(gòu)函數(shù)有助于在跳出程序(比如關(guān)閉文件、釋放內(nèi)存等)前釋放資源蓄愁。
1.7.3 友元函數(shù)双炕、友元類
  • 友元函數(shù)是一種定義在類外部的普通函數(shù),它不屬于任何類撮抓,但它需要在類體內(nèi)進(jìn)行說(shuō)明妇斤,為了與該類的成員函數(shù)加以區(qū)別,在說(shuō)明時(shí)前面加以關(guān)鍵字 friend丹拯。
  • 友元函數(shù)不是成員函數(shù)站超,但是它可以訪問(wèn)類中的私有成員。
  • 一個(gè)函數(shù)可以是多個(gè)類的友元函數(shù)乖酬,只需要在各個(gè)類中分別聲明死相。
  • 友元的作用在于提高程序的運(yùn)行效率(即減少了類型檢查和安全性檢查等都需要的時(shí)間開(kāi)銷),但是咬像,它破壞了類的封裝性和隱藏性算撮,使得非成員函數(shù)可以訪問(wèn)類的私有成員。
  • 友元類的所有成員函數(shù)都是另一個(gè)類的友元函數(shù)县昂,都可以訪問(wèn)另一個(gè)類中的隱藏信息(包括私有成員和保護(hù)成員)肮柜。
  • 當(dāng)希望一個(gè)類可以存取另一個(gè)類的私有成員時(shí),可以將該類聲明為另一類的友元類倒彰。定義友元類的語(yǔ)句格式如下:friend class 類名 (friend和class是關(guān)鍵字审洞,類名必須是程序中的一個(gè)已定義過(guò)的類)。
class INTEGER
{  
private:
    int num;
public:
    friend void Print(const INTEGER& obj);//聲明友元函數(shù)
};
void Print(const INTEGER& obj)     //不使用friend和類::
{
    //函數(shù)體
}
void main()
{
    INTEGER obj;
    Print(obj);//直接調(diào)用
}
復(fù)制代碼
#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)       //  該函數(shù)必須在girl類定義的后面定義待讳,否則girl類中的私有變量還是未知的    
{ 
    cout<<"boy's name is:"<<name<<",age:"<<age<<endl;
    cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl; 
    //借助友元芒澜,在boy的成員函數(shù)disp中,借助girl的對(duì)象创淡,直接訪問(wèn)girl的私有變量
    //正常情況下痴晦,只允許在girl的成員函數(shù)中訪問(wèn)girl的私有變量
}

void main()  
{   
    boy b("aaa",8);  
    girl g("bbb",99);  
    b.disp(g); 
}
復(fù)制代碼

1.8 單例對(duì)象、操作符重載

  • 我們可以重定義或重載大部分 C++ 內(nèi)置的運(yùn)算符辩昆。這樣就能使用自定義類型的運(yùn)算符。重載的運(yùn)算符是帶有特殊名稱的函數(shù)旨袒,函數(shù)名是由關(guān)鍵字 operator 和其后要重載的運(yùn)算符符號(hào)構(gòu)成的汁针。與其他函數(shù)一樣术辐,重載運(yùn)算符有一個(gè)返回類型和一個(gè)參數(shù)列表。
#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;
      }
      // 重載 + 運(yùn)算符施无,用于把兩個(gè) Box 對(duì)象相加
      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;      // 長(zhǎng)度
      double breadth;     // 寬度
      double height;      // 高度
};
// 程序的主函數(shù)
int main( )
{
   Box Box1;                // 聲明 Box1辉词,類型為 Box
   Box Box2;                // 聲明 Box2,類型為 Box
   Box Box3;                // 聲明 Box3猾骡,類型為 Box
   double volume = 0.0;     // 把體積存儲(chǔ)在該變量中

   // 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;

   // 把兩個(gè)對(duì)象相加瑞躺,得到 Box3
   Box3 = Box1 + Box2;

   // Box3 的體積
   volume = Box3.getVolume();
   cout << "Volume of Box3 : " << volume <<endl;

   return 0;
}
復(fù)制代碼

打印結(jié)果: Volume of Box1 : 210 Volume of Box2 : 1560 Volume of Box3 : 5400

1.9 繼承多態(tài)、虛函數(shù)

1.9.1 繼承
  • 一個(gè)類可以派生自多個(gè)類兴想,這意味著幢哨,它可以從多個(gè)基類繼承數(shù)據(jù)和函數(shù)。定義一個(gè)派生類嫂便,我們使用一個(gè)類派生列表來(lái)指定基類捞镰。類派生列表以一個(gè)或多個(gè)基類命名:class derived-class: access-specifier base-class
  • 其中毙替,訪問(wèn)修飾符 access-specifierpublic岸售、protectedprivate 其中的一個(gè),base-class 是之前定義過(guò)的某個(gè)類的名稱厂画。如果未使用訪問(wèn)修飾符 access-specifier凸丸,則默認(rèn)為 private
  • 派生類可以訪問(wèn)基類中所有的非私有成員袱院。因此基類成員如果不想被派生類的成員函數(shù)訪問(wèn)屎慢,則應(yīng)在基類中聲明為 private
  • 一個(gè)派生類繼承了所有的基類方法坑填,但下列情況除外:

基類的構(gòu)造函數(shù)抛人、析構(gòu)函數(shù)和拷貝構(gòu)造函數(shù)。 基類的重載運(yùn)算符脐瑰。 基類的友元函數(shù)妖枚。

  • 當(dāng)一個(gè)類派生自基類,該基類可以被繼承為 public苍在、protectedprivate 幾種類型绝页。繼承類型是通過(guò)上面講解的訪問(wèn)修飾符 access-specifier 來(lái)指定的。

  • 我們幾乎不使用 protectedprivate 繼承寂恬,通常使用 public 繼承续誉。當(dāng)使用不同類型的繼承時(shí),遵循以下幾個(gè)規(guī)則:

公有繼承(public):當(dāng)一個(gè)類派生自公有基類時(shí)初肉,基類的公有成員也是派生類的公有成員酷鸦,基類的保護(hù)成員也是派生類的保護(hù)成員,基類的私有成員不能直接被派生類訪問(wèn),但是可以通過(guò)調(diào)用基類的公有和保護(hù)成員來(lái)訪問(wèn)臼隔。 保護(hù)繼承(protected): 當(dāng)一個(gè)類派生自保護(hù)基類時(shí)嘹裂,基類的公有和保護(hù)成員將成為派生類的保護(hù)成員。 私有繼承(private):當(dāng)一個(gè)類派生自私有基類時(shí)摔握,基類的公有和保護(hù)成員將成為派生類的私有成員寄狼。

#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);

   // 輸出對(duì)象的面積
   cout << "Total area: " << Rect.getArea() << endl;

   return 0;
}
復(fù)制代碼

打印結(jié)果: Total area: 35

1.9.2 虛函數(shù)

定義一個(gè)函數(shù)為虛函數(shù),不代表函數(shù)為不被實(shí)現(xiàn)的函數(shù)氨淌。 定義他為虛函數(shù)是為了允許用基類的指針來(lái)調(diào)用子類的這個(gè)函數(shù)泊愧。 定義一個(gè)函數(shù)為純虛函數(shù),才代表函數(shù)沒(méi)有被實(shí)現(xiàn)盛正。 定義純虛函數(shù)是為了實(shí)現(xiàn)一個(gè)接口删咱,起到一個(gè)規(guī)范的作用,規(guī)范繼承這個(gè)類的程序員必須實(shí)現(xiàn)這個(gè)函數(shù)蛮艰。

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的指針,但是被調(diào)用的函數(shù)(foo)卻是B的!
    return 0;
}
復(fù)制代碼
  • 一個(gè)類函數(shù)的調(diào)用并不是在編譯時(shí)刻被確定的壤蚜,而是在運(yùn)行時(shí)刻被確定的即寡。由于編寫(xiě)代碼的時(shí)候并不能確定被調(diào)用的是基類的函數(shù)還是哪個(gè)派生類的函數(shù),所以被稱為“虛”函數(shù)袜刷。
  • 純虛函數(shù)是在基類中聲明的虛函數(shù)聪富,它在基類中沒(méi)有定義,但要求任何派生類都要定義自己的實(shí)現(xiàn)方法著蟹。在基類中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加 "=0" :virtual void funtion()=0
  • 將函數(shù)定義為純虛函數(shù)麸锉,則編譯器要求在派生類中必須予以重寫(xiě)以實(shí)現(xiàn)多態(tài)性违寿。聲明了純虛函數(shù)的類是一個(gè)抽象類食听。所以邻寿,用戶不能創(chuàng)建類的實(shí)例,只能創(chuàng)建它的派生類的實(shí)例涮雷。

1.10 類模板阵面、函數(shù)模板

  • 模板是泛型編程的基礎(chǔ),泛型編程即以一種獨(dú)立于任何特定類型的方式編寫(xiě)代碼洪鸭。
  • 模板是創(chuàng)建泛型類或函數(shù)的藍(lán)圖或公式样刷。

模板函數(shù)定義的一般形式如下所示:

template <typename type> ret-type func-name(parameter list)
{
   // 函數(shù)的主體
}
復(fù)制代碼
#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;
}
復(fù)制代碼

打印結(jié)果: Max(i, j): 39 Max(f1, f2): 20.7 Max(s1, s2): World

類模板,泛型類聲明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}
復(fù)制代碼
#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"); 
    }
    // 刪除最后一個(gè)元素
    elems.pop_back();         
} 

template <class T>
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
    // 返回最后一個(gè)元素的副本 
    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;
    } 
}
復(fù)制代碼

打印結(jié)果: 7 hello Exception: Stack<>::pop(): empty stack

1.11 容器

  • 序列式容器(Sequence containers)置鼻,此為可序群集,其中每個(gè)元素均有固定位置—取決于插入時(shí)機(jī)和地點(diǎn)蜓竹,和元素值無(wú)關(guān)箕母。如果你以追加方式對(duì)一個(gè)群集插入六個(gè)元素储藐,它們的排列次序?qū)⒑筒迦氪涡蛞恢隆TL提供了三個(gè)序列式容器:向量(vector)嘶是、雙端隊(duì)列(deque)邑茄、列表(list),此外你也可以把 string 和 array 當(dāng)做一種序列式容器俊啼。
  • 關(guān)聯(lián)式容器(Associative containers),此為已序群集左医,元素位置取決于特定的排序準(zhǔn)則以及元素值授帕,和插入次序無(wú)關(guān)。如果你將六個(gè)元素置入這樣的群集中浮梢,它們的位置取決于元素值跛十,和插入次序無(wú)關(guān)。STL提供了四個(gè)關(guān)聯(lián)式容器:集合(set)秕硝、多重集合(multiset)芥映、映射(map)和多重映射(multimap)。
  • 容器配接器:根據(jù)上面七種基本容器類別實(shí)現(xiàn)而成远豺。stack奈偏,元素采取 LIFO(后進(jìn)先出)的管理策略、queue躯护,元素采取 FIFO(先進(jìn)先出)的管理策略惊来。也就是說(shuō),它是個(gè)普通的緩沖區(qū)(buffer)棺滞、priority_queue裁蚁,元素可以擁有不同的優(yōu)先權(quán)。所謂優(yōu)先權(quán)继准,乃是基于程序員提供的排序準(zhǔn)則(缺省使用 operators)而定義枉证。Priority queue 的效果相當(dāng)于這樣一個(gè) buffer:“下一元素永遠(yuǎn)是queue中優(yōu)先級(jí)最高的元素”。如果同時(shí)有多個(gè)元素具備最髙優(yōu)先權(quán)移必,則其次序無(wú)明確定義室谚。

特點(diǎn)

  1. vector 頭部與中間插入和刪除效率較低,在尾部插入和刪除效率高避凝,支持隨機(jī)訪問(wèn)舞萄。
  2. deque 是在頭部和尾部插入和刪除效率較高,支持隨機(jī)訪問(wèn)管削,但效率沒(méi)有 vector 高倒脓。
  3. list 在任意位置的插入和刪除效率都較高,但不支持隨機(jī)訪問(wèn)含思。
  4. set 由紅黑樹(shù)實(shí)現(xiàn)崎弃,其內(nèi)部元素依據(jù)其值自動(dòng)排序甘晤,每個(gè)元素值只能出現(xiàn)一次,不允許重復(fù)饲做,且插入和刪除效率比用其他序列容器高线婚。
  5. map 可以自動(dòng)建立 Key - value 的對(duì)應(yīng),key 和 value 可以是任意你需要的類型盆均,根據(jù) key 快速查找記錄塞弊。

選擇

  1. 如果需要高效的隨機(jī)存取,不在乎插入和刪除的效率泪姨,使用 vector游沿。
  2. 如果需要大量的插入和刪除元素,不關(guān)心隨機(jī)存取的效率肮砾,使用 list诀黍。
  3. 如果需要隨機(jī)存取,并且關(guān)心兩端數(shù)據(jù)的插入和刪除效率仗处,使用 deque眯勾。
  4. 如果打算存儲(chǔ)數(shù)據(jù)字典,并且要求方便地根據(jù) key 找到 value婆誓,一對(duì)一的情況使用 map吃环,一對(duì)多的情況使用 multimap
  5. 如果打算查找一個(gè)元素是否存在于某集合中洋幻,唯一存在的情況使用 set模叙,不唯一存在的情況使用 multiset

時(shí)間復(fù)雜度

  1. vector 在頭部和中間位置插入和刪除的時(shí)間復(fù)雜度為 O(N)鞋屈,在尾部插入和刪除的時(shí)間復(fù)雜度為 O(1)范咨,查找的時(shí)間復(fù)雜度為 O(1);
  2. deque 在中間位置插入和刪除的時(shí)間復(fù)雜度為 O(N)厂庇,在頭部和尾部插入和刪除的時(shí)間復(fù)雜度為 O(1)渠啊,查找的時(shí)間復(fù)雜度為 O(1);
  3. list 在任意位置插入和刪除的時(shí)間復(fù)雜度都為 O(1)权旷,查找的時(shí)間復(fù)雜度為 O(N)替蛉;
  4. setmap 都是通過(guò)紅黑樹(shù)實(shí)現(xiàn),因此插入拄氯、刪除和查找操作的時(shí)間復(fù)雜度都是 O(log N)躲查。

1.12 命名空間

1.12.1 namespace
  • 命名空間是一種描述邏輯分組的機(jī)制,可以將按某些標(biāo)準(zhǔn)在邏輯上屬于同一個(gè)集團(tuán)的聲明放在同一個(gè)命名空間中译柏。用于區(qū)分不同庫(kù)中相同名稱的函數(shù)镣煮、類、變量鄙麦。
namespace namespace_name {
   // 代碼聲明
}
復(fù)制代碼
  • 無(wú)名命名空間典唇。你可以在當(dāng)前編譯單元中(無(wú)名命名空間之外)镊折,直接使用無(wú)名命名空間中的成員名稱,但是在當(dāng)前編譯單元之外介衔,它又是不可見(jiàn)的恨胚。它可以使代碼保持局部性,從而保護(hù)代碼不被他人非法使用炎咖。
namespace {
   // 代碼聲明
}
復(fù)制代碼
  • 不能在命名空間的定義中聲明(另一個(gè)嵌套的)子命名空間赃泡,只能在命名空間的定義中定義子命名空間。
  • 不能直接使用 命名空間名::成員名 …… 定義方式乘盼,為命名空間添加新成員急迂,而必須先在命名空間的定義中添加新成員的聲明。
  • 命名空間是開(kāi)放的蹦肴,即可以隨時(shí)把新的成員名稱加入到已有的命名空間之中去。方法是猴娩,多次聲明和定義同一命名空間阴幌,每次添加自己的新成員和名稱。
1.12.2 using
  • 可以使用 using namespace 指令卷中,這樣在使用命名空間時(shí)就可以不用在前面加上命名空間的名稱矛双。這個(gè)指令會(huì)告訴編譯器,后續(xù)的代碼將使用指定的命名空間中的名稱蟆豫。
#include <iostream>
using namespace std;

// 第一個(gè)命名空間
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二個(gè)命名空間
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
using namespace first_space;
int main ()
{

   // 調(diào)用第一個(gè)命名空間中的函數(shù)
   func();

   return 0;
}
復(fù)制代碼
  • 除了可以使用 using編譯指令(組合關(guān)鍵字 using namespace)外议忽,還可以使用 using聲明 來(lái)簡(jiǎn)化對(duì)命名空間中的名稱的使用:using 命名空間名::[命名空間名::……]成員名; 。注意十减,關(guān)鍵字 using 后面并沒(méi)有跟關(guān)鍵字 namespace栈幸,而且最后必須為命名空間的成員名(而在 using 編譯指令的最后,必須為命名空間名)帮辟。

using指令 使用后速址,可以一勞永逸,對(duì)整個(gè)命名空間的所有成員都有效由驹,非常方便芍锚。而 using聲明,則必須對(duì)命名空間的不同成員名稱蔓榄,一個(gè)一個(gè)地去聲明并炮。但是,一般來(lái)說(shuō)甥郑,使用 using聲明 會(huì)更安全逃魄。因?yàn)椋?code>using聲明 只導(dǎo)入指定的名稱,如果該名稱與局部名稱發(fā)生沖突澜搅,編譯器會(huì)報(bào)錯(cuò)嗅钻。using指令 導(dǎo)入整個(gè)命名空間中的所有成員的名稱皂冰,包括那些可能根本用不到的名稱,如果其中有名稱與局部名稱發(fā)生沖突养篓,則編譯器并不會(huì)發(fā)出任何警告信息秃流,而只是用局部名去自動(dòng)覆蓋命名空間中的同名成員。特別是命名空間的開(kāi)放性柳弄,使得一個(gè)命名空間的成員舶胀,可能分散在多個(gè)地方,程序員難以準(zhǔn)確知道碧注,別人到底為該命名空間添加了哪些名稱嚣伐。

二、java 調(diào)用 C/C++

  • 加載 .so 庫(kù)萍丐;
//MainActivity.java
static {
        System.loadLibrary("native-lib");
}
復(fù)制代碼
  • 編寫(xiě) java 函數(shù)轩端;
//MainActivity.java
public native String stringFromJNI();
復(fù)制代碼
  • 編寫(xiě) C/C++ 函數(shù);
//native-lib.cpp
#include <jni.h>
#include <string>
//函數(shù)名的構(gòu)成: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());
}
復(fù)制代碼
  • ndk/cmake 配置(下面只列出cmake配置)基茵;
# CMakeLists.txt

# 設(shè)置構(gòu)建本地庫(kù)所需的最小版本的cbuild。
cmake_minimum_required(VERSION 3.4.1)
# 創(chuàng)建并命名一個(gè)庫(kù)壳影,將其設(shè)置為靜態(tài)
# 或者共享拱层,并提供其源代碼的相對(duì)路徑。
# 您可以定義多個(gè)庫(kù)宴咧,而cbuild為您構(gòu)建它們根灯。
# Gradle自動(dòng)將共享庫(kù)與你的APK打包。
add_library( native-lib       #設(shè)置庫(kù)的名稱掺栅。即SO文件的名稱烙肺,生產(chǎn)的so文件為“l(fā)ibnative-lib.so”, 在加載的時(shí)候“System.loadLibrary("native-lib");”
             SHARED            # 將庫(kù)設(shè)置為共享庫(kù)。
             native-lib.cpp    # 提供一個(gè)源文件的相對(duì)路徑
             helloJni.cpp      # 提供同一個(gè)SO文件中的另一個(gè)源文件的相對(duì)路徑
           )
# 搜索指定的預(yù)構(gòu)建庫(kù)氧卧,并將該路徑存儲(chǔ)為一個(gè)變量茬高。因?yàn)閏build默認(rèn)包含了搜索路徑中的系統(tǒng)庫(kù),所以您只需要指定您想要添加的公共NDK庫(kù)的名稱假抄。cbuild在完成構(gòu)建之前驗(yàn)證這個(gè)庫(kù)是否存在怎栽。
find_library(log-lib   # 設(shè)置path變量的名稱。
             log       #  指定NDK庫(kù)的名稱 你想讓CMake來(lái)定位宿饱。
             )
#指定庫(kù)的庫(kù)應(yīng)該鏈接到你的目標(biāo)庫(kù)熏瞄。您可以鏈接多個(gè)庫(kù),比如在這個(gè)構(gòu)建腳本中定義的庫(kù)谬以、預(yù)構(gòu)建的第三方庫(kù)或系統(tǒng)庫(kù)强饮。
target_link_libraries( native-lib    # 指定目標(biāo)庫(kù)中。與 add_library的庫(kù)名稱一定要相同
                       ${log-lib}    # 將目標(biāo)庫(kù)鏈接到日志庫(kù)包含在NDK为黎。
                       )
#如果需要生產(chǎn)多個(gè)SO文件的話邮丰,寫(xiě)法如下
add_library( natave-lib       # 設(shè)置庫(kù)的名稱行您。另一個(gè)so文件的名稱
             SHARED           # 將庫(kù)設(shè)置為共享庫(kù)。
             nataveJni.cpp    # 提供一個(gè)源文件的相對(duì)路徑
            )
target_link_libraries( natave-lib     #指定目標(biāo)庫(kù)中剪廉。與 add_library的庫(kù)名稱一定要相同
                       ${log-lib}     # 將目標(biāo)庫(kù)鏈接到日志庫(kù)包含在NDK娃循。
                        )     
復(fù)制代碼
// 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"
        }
    }
}
復(fù)制代碼

三、JNI 基礎(chǔ)

3.1 JNIEnv 斗蒋、JavaVM

  • JavaVM 是 Java 虛擬機(jī)在 JNI 層的代表, JNI 全局只有一個(gè)捌斧;
  • 從上面的代碼中我們可以發(fā)現(xiàn),雖然 Java 函數(shù)不帶參數(shù)泉沾,但是 native 函數(shù)卻帶了兩個(gè)參數(shù)捞蚂,第一個(gè)參數(shù) JNIEnv 是指向可用 JNI 函數(shù)表的接口指針,第二個(gè)參數(shù) jobject 是 Java 函數(shù)所在類的實(shí)例的 Java 對(duì)象引用跷究;
  • JNIEnvJavaVM 在線程中的代表, 每個(gè)線程都有一個(gè)姓迅, JNI 中可能有很多個(gè) JNIEnv,同時(shí) JNIEnv 具有線程相關(guān)性俊马,也就是 B 線程無(wú)法使用 A 線程的 JNIEnv丁存;
  • JNIEnv 類型實(shí)際上代表了 Java 環(huán)境,通過(guò)這個(gè) JNIEnv* 指針潭袱,就可以對(duì) Java 端的代碼進(jìn)行操作:

調(diào)用 Java 函數(shù); 操作 Java 對(duì)象锋恬;

  • JNIEnv 的本質(zhì)是一個(gè)與線程相關(guān)的代表 JNI 環(huán)境的結(jié)構(gòu)體屯换,里面存放了大量的 JNI 函數(shù)指針;
  • JNIEnv 內(nèi)部結(jié)構(gòu)如下:
  • JavaVM 的結(jié)構(gòu)如下:

3.2 數(shù)據(jù)類型

3.2.1 基礎(chǔ)數(shù)據(jù)類型
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 數(shù)組數(shù)據(jù)類型

數(shù)組簡(jiǎn)稱:在前面添加 [

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 復(fù)雜數(shù)據(jù)類型

對(duì)象類型簡(jiǎn)稱:L+classname +;

Signature格式 Java Native
Ljava/lang/String; String jstring
L+classname +; 所有對(duì)象 jobject
[L+classname +; Object[] jobjectArray
Ljava.lang.Class; Class jclass
Ljava.lang.Throwable; Throwable jthrowable
3.2.4 函數(shù)簽名

(輸入?yún)?shù)...)返回值參數(shù)

Signature格式 Java函數(shù)
()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 對(duì)象与学、類

  • 獲取你需要訪問(wèn)的 Java 對(duì)象的類:
jclass thisclazz = env->GetObjectClass(thiz);//使用GetObjectClass方法獲取thiz對(duì)應(yīng)的jclass彤悔。 
jclass thisclazz = env->FindClass("com/xxx/xxx/abc");//直接搜索類名
復(fù)制代碼
  • 獲取 MethodID:
/**
 * thisclazz -->上一步獲取的 jclass
 * "onCallback"-->要調(diào)用的方法名
 * "(I)Ljava/lang/String;"-->方法的 Signature, 簽名參照前面的第 3.2 小節(jié)表格索守。
 */
jmethodID mid_callback = env->GetMethodID(thisclazz , "onCallback", "(Ljava/lang/String;)I");
jmethodID mid_callback = env->GetStaticMethodID(thisclazz , "onCallback", "(Ljava/lang/String;)I");//獲取靜態(tài)方法的ID
復(fù)制代碼
  • 調(diào)用方法:
jint result = env->CallIntMethod(thisclazz , mid_callback , jstrParams);
jint result = env->CallStaticIntMethod(thisclazz , mid_callback , jstrParams);//調(diào)用靜態(tài)方法
復(fù)制代碼

貼一下JNI 常用接口文檔晕窑,有需要可以在這里查詢。

3.4 JNI 引用

3.4.1 局部引用
  • 通過(guò) NewLocalRef 和各種JNI接口創(chuàng)建(FindClass卵佛、NewObject杨赤、GetObjectClassNewCharArray等)。
  • 會(huì)阻止 GC 回收所引用的對(duì)象截汪,不在本地函數(shù)中跨函數(shù)使用疾牲,不能跨線前使用。
  • 函數(shù)返回后局部引用所引用的對(duì)象會(huì)被 JVM 自動(dòng)釋放衙解,或手動(dòng)釋放阳柔。
  • 手動(dòng)釋放的方式:GetXXX 就必須調(diào)用 ReleaseXXX,調(diào)用完 GetStringUTFChars 之后蚓峦,調(diào)用 ReleaseStringUTFChars 釋放舌剂;對(duì)于手動(dòng)創(chuàng)建的 jclass济锄,jobject 等對(duì)象使用 DeleteLocalRef 方法進(jìn)行釋放。
3.4.2 全局引用
  • 調(diào)用 NewGlobalRef 基于局部引用創(chuàng)建霍转。
  • 會(huì)阻 GC 回收所引用的對(duì)象荐绝。可以跨方法谴忧、跨線程使用很泊。
  • JVM 不會(huì)自動(dòng)釋放,必須手動(dòng)釋放沾谓。
  • 全局引用在顯式釋放之前保持有效委造,必須通過(guò) DeleteGlobalRef 來(lái)手動(dòng)刪除全局引用調(diào)用。
3.4.3 弱全局引用
  • 調(diào)用 NewWeakGlobalRef 基于局部引用或全局引用創(chuàng)建均驶。
  • 不會(huì)阻止 GC 回收所引用的對(duì)象昏兆,可以跨方法、跨線程使用妇穴。
  • 引用不會(huì)自動(dòng)釋放爬虱,在 JVM 認(rèn)為應(yīng)該回收它的時(shí)候(比如內(nèi)存緊張的時(shí)候)進(jìn)行回收而被釋放√谒或調(diào)用 DeleteWeakGlobalRef 手動(dòng)釋放跑筝。

作者:滌生_Woo
鏈接:https://juejin.im/post/6870678713361661966

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瞒滴,隨后出現(xiàn)的幾起案子曲梗,更是在濱河造成了極大的恐慌,老刑警劉巖妓忍,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虏两,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡世剖,警方通過(guò)查閱死者的電腦和手機(jī)定罢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)旁瘫,“玉大人祖凫,你說(shuō)我怎么就攤上這事〕甑剩” “怎么了蝙场?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)粱年。 經(jīng)常有香客問(wèn)我售滤,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任完箩,我火速辦了婚禮赐俗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弊知。我一直安慰自己阻逮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布秩彤。 她就那樣靜靜地躺著叔扼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漫雷。 梳的紋絲不亂的頭發(fā)上瓜富,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音降盹,去河邊找鬼与柑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蓄坏,可吹牛的內(nèi)容都是我干的价捧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涡戳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼结蟋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起渔彰,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嵌屎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后胳岂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體编整,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舔稀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年乳丰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片内贮。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡产园,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夜郁,到底是詐尸還是另有隱情什燕,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布竞端,位于F島的核電站屎即,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜技俐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一乘陪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雕擂,春花似錦啡邑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仇穗,卻和暖如春流部,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仪缸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工贵涵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恰画。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓宾茂,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拴还。 傳聞我的和親對(duì)象是個(gè)殘疾皇子跨晴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355