一荣恐、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í)最高的,它分為三種:
- 全局作用域符熟菲,用法
::name
- 類作用域符月弛,用法
class::name
- 命名空間作用域符肴盏,用法
namespace::name
他們都是左關(guān)聯(lián),他們的作用都是為了更明確的調(diào)用你想要的變量:
- 如在程序中的某一處你想調(diào)用全局變量
a
帽衙,那么就寫(xiě)成::a
菜皂;(也可以是全局函數(shù)) - 如果想調(diào)用
class A
中的成員變量a
,那么就寫(xiě)成A::a
厉萝; - 另外一個(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
)谴垫;
- 表示“域操作符”:聲明了一個(gè)類
A
章母,類A
里聲明了一個(gè)成員函數(shù)void f()
,但沒(méi)有在類的聲明里給出f
的定義翩剪,那么在類外定義f時(shí)乳怎, 就要寫(xiě)成void A::f()
,表示這個(gè)f()
函數(shù)是類A
的成員函數(shù)前弯。- 直接用在全局函數(shù)前蚪缀,表示是全局函數(shù):在 VC 里,你可以在調(diào)用 API 函數(shù)里恕出,在 API 函數(shù)名前加
::
- 表示引用成員函數(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-specifier
是public
岸售、protected
或private
其中的一個(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
苍在、protected
或private
幾種類型绝页。繼承類型是通過(guò)上面講解的訪問(wèn)修飾符access-specifier
來(lái)指定的。我們幾乎不使用
protected
或private
繼承寂恬,通常使用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):
vector
頭部與中間插入和刪除效率較低,在尾部插入和刪除效率高避凝,支持隨機(jī)訪問(wèn)舞萄。deque
是在頭部和尾部插入和刪除效率較高,支持隨機(jī)訪問(wèn)管削,但效率沒(méi)有vector
高倒脓。list
在任意位置的插入和刪除效率都較高,但不支持隨機(jī)訪問(wèn)含思。set
由紅黑樹(shù)實(shí)現(xiàn)崎弃,其內(nèi)部元素依據(jù)其值自動(dòng)排序甘晤,每個(gè)元素值只能出現(xiàn)一次,不允許重復(fù)饲做,且插入和刪除效率比用其他序列容器高线婚。map
可以自動(dòng)建立 Key - value 的對(duì)應(yīng),key 和 value 可以是任意你需要的類型盆均,根據(jù) key 快速查找記錄塞弊。
選擇:
- 如果需要高效的隨機(jī)存取,不在乎插入和刪除的效率泪姨,使用
vector
游沿。- 如果需要大量的插入和刪除元素,不關(guān)心隨機(jī)存取的效率肮砾,使用
list
诀黍。- 如果需要隨機(jī)存取,并且關(guān)心兩端數(shù)據(jù)的插入和刪除效率仗处,使用
deque
眯勾。- 如果打算存儲(chǔ)數(shù)據(jù)字典,并且要求方便地根據(jù) key 找到 value婆誓,一對(duì)一的情況使用
map
吃环,一對(duì)多的情況使用multimap
。- 如果打算查找一個(gè)元素是否存在于某集合中洋幻,唯一存在的情況使用
set
模叙,不唯一存在的情況使用multiset
。
時(shí)間復(fù)雜度:
vector
在頭部和中間位置插入和刪除的時(shí)間復(fù)雜度為 O(N)鞋屈,在尾部插入和刪除的時(shí)間復(fù)雜度為 O(1)范咨,查找的時(shí)間復(fù)雜度為 O(1);deque
在中間位置插入和刪除的時(shí)間復(fù)雜度為 O(N)厂庇,在頭部和尾部插入和刪除的時(shí)間復(fù)雜度為 O(1)渠啊,查找的時(shí)間復(fù)雜度為 O(1);list
在任意位置插入和刪除的時(shí)間復(fù)雜度都為 O(1)权旷,查找的時(shí)間復(fù)雜度為 O(N)替蛉;set
和map
都是通過(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ì)象引用跷究; -
JNIEnv
是JavaVM
在線程中的代表, 每個(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
杨赤、GetObjectClass
和NewCharArray
等)。 - 會(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