1 static關鍵字
加了 static 關鍵字的全局變量只能在本文件中使用。
static 定義的靜態(tài)局部變量分配在數(shù)據(jù)段上,普通的局部變量分配在棧上,會因為函數(shù)棧幀的釋放而被釋放掉。
1.1 全局靜態(tài)變量
在全局變量前加上關鍵字 static,全局變量就定義成一個 全局靜態(tài)變量 。
內(nèi)存中的位置: 靜態(tài)存儲區(qū) ,在整個程序運行期間一直存在。
初始化:未經(jīng)初始化的全局靜態(tài)變量會被 自動初始化為 0 (自動對象的值是任意的,除非他被顯式初始化);
作用域:全局靜態(tài)變量在聲明 僅在本文件可見 ,他的文件之外是不可見的,準確地說是從定義之處開始,到文件結(jié)尾。
1.2 局部靜態(tài)變量
在局部變量之前加上關鍵字 static,局部變量就成為一個 局部靜態(tài)變量 。
內(nèi)存中的位置: 靜態(tài)存儲區(qū) ,在整個程序運行期間一直存在。
初始化:未經(jīng)初始化的全局靜態(tài)變量會被自動初始化為 0(自動對象的值是任意的,除非他被顯式初始化);
作用域:作用域仍為局部作用域,當定義它的函數(shù)或者語句塊結(jié)束的時候,作用域結(jié)束。但是當局部靜態(tài)變量離開作用域后,并沒有銷毀,而是 仍然駐留在內(nèi)存當中 ,只不過我們不能再對它進行訪問,直到該函數(shù)再次被調(diào)用,并且值不變;
1.3 靜態(tài)函數(shù)
在函數(shù)返回類型前加 static,函數(shù)就定義為 靜態(tài)函數(shù) 。函數(shù)的定義和聲明在默認情況下都是 extern 的,但靜態(tài)函數(shù) 僅在本文件可見 ,不能被其他文件所用。
函數(shù)的實現(xiàn)使用 static 修飾,那么這個函數(shù)只可在本 cpp 內(nèi)使用,不會同其他 cpp 中的同名函數(shù)引起沖突;
warning:在 頭文件中聲明非static 的全局函數(shù) ,在 cpp 內(nèi)聲明static 的全局函數(shù) ,如果你要在多個 cpp 中復用該函數(shù),就把它的聲明提到頭文件里去,否則 cpp 內(nèi)部聲明需加上 static 修飾;
1.4 類的靜態(tài)成員
對一個類中成員變量和成員函數(shù)來說,加了 static 關鍵字,則此變量/函數(shù)就沒有 this指針了,必須通過 類名訪問 。
在類中,靜態(tài)成員可以實現(xiàn)多個對象之間的數(shù)據(jù)共享,并且使用靜態(tài)數(shù)據(jù)成員還不會破壞隱藏的原則,即保證了安全性。因此, 靜態(tài)成員是類的所有對象***享的成員 ,而不是某個對象的成員。對多個對象來說,靜態(tài)數(shù)據(jù)成員 只存儲一處 ,供所有對象共用。
1.5 類的靜態(tài)函數(shù)
靜態(tài)成員函數(shù)和靜態(tài)數(shù)據(jù)成員一樣,它們都屬于 類的靜態(tài)成員 ,它們都不是對象成員。因此,對靜態(tài)成員的引用不需要用對象名。在 靜態(tài)成員函數(shù)的實現(xiàn)中不能直接引用類中說明的非靜態(tài)成員 ,可以引用類中說明的靜態(tài)成員(這點非常重要)。如果靜態(tài)成員函數(shù)中要引用非靜態(tài)成員時,可通過對象來引用。從中可看出,調(diào)用靜態(tài)成員函數(shù)使用如下格式:<類名>::<靜態(tài)成員函數(shù)名>(<參數(shù)表>);
2 C++和C的區(qū)別
2.1 設計思想上
C++是面向?qū)ο?/strong>的語言,而 C 是面向過程的結(jié)構化編程語言
2.2 語法上
C++具有 重載 、 繼承 、多態(tài)三種特性;
C++相比 C,增加多許多類型安全的功能,比如強制類型轉(zhuǎn)換;
C++支持 范式編程 ,比如模板類、函數(shù)模板等。
3 C++中四種cast轉(zhuǎn)換
C++中四種類型轉(zhuǎn)換是:static_cast, dynamic_cast, const_cast, reinterpret_cast
3.1 const_cast
用于將 const 變量轉(zhuǎn)為 非 const 。它也是四個強制類型轉(zhuǎn)換運算符中唯一能夠去除 const 屬性的運算符。對于未定義 const 版本的成員函數(shù),我們通常需要使用 const_cast 來去除 const引用對象的 const,完成函數(shù)調(diào)用。另外一種使用方式,結(jié)合 static_cast,可以在非 const 版本的成員函數(shù)內(nèi)添加 const,調(diào)用完 const 版本的成員函數(shù)后,再使用 const_cast 去除 const限定。
3.2 static_cast
static_cast< new_type >(expression)
// new_type 為目標數(shù)據(jù)類型,expression 為原始數(shù)據(jù)類型變量或者表達式。
基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如int、float、char之間的互相轉(zhuǎn)換;用于各種 隱式轉(zhuǎn)換 ,比如非 const 轉(zhuǎn) const,void*轉(zhuǎn)指針等,但 沒有運行時類型檢查來保證轉(zhuǎn)換的安全性 。
隱式類型轉(zhuǎn)換 :首先,對于內(nèi)置類型,低精度的變量給高精度變量賦值會發(fā)生隱式類型轉(zhuǎn)換,其次,對于只存在單個參數(shù)的構造函數(shù)的對象構造來說,函數(shù)調(diào)用可以直接使用該參數(shù)傳入,編譯器會自動調(diào)用其構造函數(shù)生成 臨時對象 。
static_cast主要有如下幾種用法:
-
用于類層次結(jié)構中基類和派生類之間指針或引用的轉(zhuǎn)換。
進行向上轉(zhuǎn)換是安全的;
進行向下轉(zhuǎn)換時,由于沒有動態(tài)類型檢查,所以是不安全的。因為 基類不包含派生類的成員變量,無法對派生類的成員變量賦值。 -
用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如int、float、char之間的互相轉(zhuǎn)換
-
把空指針轉(zhuǎn)換成 目標類型的空指針 。
-
把任何類型的表達式轉(zhuǎn) 換成void類型 。
注意:static_cast不能去掉expression的const、volatile、或者__unaligned屬性。
char a = 'a'; int b = static_cast<char>(a); //將char型數(shù)據(jù)轉(zhuǎn)換成int型數(shù)據(jù)
const int g = 20;
int *h = static_cast<int*>(&g); //編譯錯誤,static_cast不能去掉g的const屬性
class Base
{};
class Derived : public Base
{}
Base* pB = new Base();
if(Derived* pD = static_cast(pB))
{} //下行轉(zhuǎn)換是不安全的(堅決抵制這種方法)
Derived* pD = new Derived();
if(Base* pB = static_cast (pD))
{} //上行轉(zhuǎn)換是安全的
3.3 dynamic_cast
dynamic_cast< new_type >(expression)
// new_type 為目標數(shù)據(jù)類型,expression 為原始數(shù)據(jù)類型變量或者表達式。
dynamic_cast< type* >(e) //type必須是一個類類型且必須是一個有效的指針
dynamic_cast< type& >(e) //type必須是一個類類型且必須是一個左值
dynamic_cast< type&& >(e) //type必須是一個類類型且必須是一個右值
用于 動態(tài)類型轉(zhuǎn)換 。只能用于 含有虛函數(shù)的類 ,用于類層次間的向上和向下轉(zhuǎn)化、類之間的 交叉轉(zhuǎn)換 (cross cast)。只能轉(zhuǎn)指針或 引用 。
在類層次間向上轉(zhuǎn)換時,dynamic_cast和static_cast的效果是一樣的;在進行向下轉(zhuǎn)換時,dynamic_cast具有類型檢查的功能,它通過判斷在執(zhí)行到該語句的時候,變量類型和要轉(zhuǎn)換的類型是否相同來判斷是否能夠進行向下轉(zhuǎn)換,如果是非法的對于轉(zhuǎn)換目標是指針類型返回 NULL,對于引用拋std::bad_cast異常比static_cast更安全。
3.4 reinterpret_cast
幾乎什么都可以轉(zhuǎn),比如將 int 轉(zhuǎn)指針,執(zhí)行的是逐個比特復制的操作。容易出問題,盡量少用。
3.5 為何不用C的強制轉(zhuǎn)換
C 的強制轉(zhuǎn)換表面上看起來功能強大什么都能轉(zhuǎn),但是轉(zhuǎn)化不夠明確,不能進行錯誤檢查,容易出錯。
4 C/C++中指針和引用的區(qū)別
4.1 指針
指針利用地址,它的值直接指向存在電腦存儲器中另一個地方的值。由于通過地址能找到所需的變量單元,可以說,地址指向該變量單元。因此,將地址形象化的稱為“指針”。意思是通過它能找到以它為地址的內(nèi)存單元。
4.2 引用
引用就是某一變量的一個 別名 ,對引用的操作與對變量直接操作完全一樣。引用的聲明方法:類型標識符 &引用名=目標變量名;引用引入了對象的一個同義詞。定義引用的表示方法與定義指針相似,只是用&代替了*
4.3 區(qū)別
- 指針有自己的一塊空間,而引用只是一個別名;
- 使用 sizeof 看一個指針的大小是 4,而引用則是被引用對象的大小;
- 指針可以被初始化為 NULL,而引用必須被初始化且必須是一個已有對象的引用;
- 作為參數(shù)傳遞時,指針需要被解引用才可以對對象進行操作,而對引用的修改都會改變引用所指向的對象;
- 可以有 const 指針,但是沒有 const 引用;
- 指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能 被改變;
- 指針可以有多級指針(**p),而引用至于一級;
- 指針和引用使用++運算符的意義不一樣;
- 如果返回動態(tài)內(nèi)存分配的對象或者內(nèi)存,必須使用指針,引用可能引起內(nèi)存泄露。
5 C++智能指針
C++里面的四個智能指針: auto_ptr , shared_ptr , weak_ptr , unique_ptr 其中后三個是c++11 支持,并且第一個已經(jīng)被 11 棄用。
為什么要使用智能指針:
智能指針的作用是 管理一個指針 ,因為存在以下這種情況:申請的空間在函數(shù)結(jié)束時 忘記釋放 ,造成 內(nèi)存泄漏 。使用智能指針可以很大程度上的避免這個問題,因為智能指針就是一個 類 ,當超出了類的作用域是,類會 自動調(diào)用析構函數(shù) ,析構函數(shù)會自動釋放資源。所以智能指針的作用原理就是在函數(shù)結(jié)束時自動釋放內(nèi)存空間,不需要手動釋放內(nèi)存空間。
對 shared_ptr 進行初始化時不能將一個普通指針直接賦值給智能指針,因為一個是指針,一個是類??梢酝ㄟ^ make_shared 函數(shù)或者通過構造函數(shù)傳入普通指針。并可以通過 get 函數(shù)獲得普通指針。
5.1 auto_ptr
c++98 的方案,cpp11 已經(jīng)拋棄。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr p2;
p2 = p1; //auto_ptr 不會報錯.
此時不會報錯, p2 剝奪了 p1 的所有權 ,但是當程序運行時訪問 p1 將會報錯。所以 auto_ptr存在潛在的內(nèi)存***問題。
5.2 unique_ptr
替換 auto_ptr。unique_ptr 實現(xiàn)獨占式擁有或嚴格擁有概念,保證同一時間內(nèi)只有一個智能指針可以指向該對象。它對于避免資源泄露(例如:以 new 創(chuàng)建對象后因為發(fā)生異常而忘記調(diào)用 delete)特別有用。還是上面那個例子:
unique_ptr<string> p3 (new string ("auto")); //#4
unique_ptr<string> p4; //#5
p4 = p3; //此時會報錯!!
編譯器認為 p4=p3 非法,避免了 p3 不再指向有效數(shù)據(jù)的問題。因此,unique_ptr 比 auto_ptr更安全。另外unique_ptr 還有更聰明的地方:當程序試圖將一個 unique_ptr 賦值給另一個時,如果源 unique_ptr 是個臨時右值,編譯器允許這么做;如果源 unique_ptr 將存在一段時間,編譯器將禁止這么做,比如:
unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed
其中#1 留下懸掛的 unique_ptr(pu1),這可能導致危害。而#2 不會留下懸掛的 unique_ptr,因為它調(diào)用unique_ptr 的構造函數(shù),該構造函數(shù)創(chuàng)建的臨時對象在其所有權讓給 pu3 后就會被銷毀。這種隨情況而已的行為表明,unique_ptr 優(yōu)于允許兩種賦值的 auto_ptr 。
注:如果確實想執(zhí)行類似與#1 的操作,要安全的重用這種指針,可給它賦新值。C++有一個標準庫函數(shù) std::move(),讓你能夠?qū)⒁粋€ unique_ptr 賦給另一個。例如:
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
5.3 shared_ptr
shared_ptr 實現(xiàn)共享式擁有概念。多個智能指針可以指向相同對象,該對象和其相關資源會在“最后一個引用被銷毀”時候釋放。從名字 share 就可以看出了資源可以被多個指針共享,它使用計數(shù)機制來表明資源被幾個指針共享。可以通過成員函數(shù) use_count()來查看資源的所有者個數(shù)。除了可以通過 new 來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr 來構造。當我們調(diào)用 release()時,當前指針會釋放資源所有權,計數(shù)減一。當計數(shù)等于 0 時,資源會被釋放。
shared_ptr 是為了解決 auto_ptr 在對象所有權上的局限性(auto_ptr 是獨占的), 在使用引用計數(shù)的機制上提供了可以共享所有權的智能指針。
成員函數(shù):
- use_count 返回引用計數(shù)的個數(shù)
- unique 返回是否是獨占所有權( use_count 為 1)
- swap 交換兩個 shared_ptr 對象(即交換所擁有的對象)
- reset 放棄內(nèi)部對象的所有權或擁有對象的變更, 會引起原有對象的引用計數(shù)的減少
- get 返回內(nèi)部對象(指針), 由于已經(jīng)重載了()方法, 因此和直接使用對象是一樣的。如:
shared_ptr<int> sp(new int(1)); // sp 與 sp.get()是等價的
5.4 weak_ptr
weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象。weak_ptr 設計的目的是為協(xié)助shared_ptr 而引入的一種智能指針,它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造,它的構造和析構不會引起引用記數(shù)的增加或減少。
weak_ptr 是用來 解決 shared_ptr 相互引用時的死鎖問題 ,如果說兩個 shared_ptr 相互引用,那么這兩個指針的引用計數(shù)永遠不可能下降為 0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數(shù),和 shared_ptr 之間可以相互轉(zhuǎn)化,shared_ptr 可以直接賦值給它,它可以通過調(diào)用 lock 函數(shù)來獲得 shared_ptr。
class B;
class A
{
public:
shared_ptr pb_;
~A()
{
cout<<"A delete\\n";
}
};
class B
{
public:
shared_ptr pa_;
~B()
{
cout<<"B delete\\n";
}
};
void fun()
{
shared_ptr pb(new B());
shared_ptr pa(new A());
pb->pa_ = pa;
pa->pb_ = pb;
cout<endl;
cout<endl;
}
int main()
{
fun();
return 0;
}
可以看到 fun 函數(shù)中 pa,pb 之間互相引用,兩個資源的引用計數(shù)為 2,當要跳出函數(shù)時,智能指針 pa,pb 析構時兩個資源引用計數(shù)會減一,但是兩者引用計數(shù)還是為 1,導致跳出函數(shù)時資源沒有被釋放(A B 的析構函數(shù)沒有被調(diào)用),如果把其中一個改為 weak_ptr 就可以了,我們把類 A 里面的 shared_ptr pb
改為 weak_ptr pb
運行結(jié)果如下,這樣的話,資源 B 的引用開始就只有 1,當 pb 析構時,B 的計數(shù)變?yōu)?0,B 得到釋放,B 釋放的同時也會使 A 的計數(shù)減一,同時 pa 析構時使 A 的計數(shù)減一,那么 A 的計數(shù)為 0,A 得到釋放。
注意的是我們不能通過 weak_ptr 直接訪問對象的方法,比如 B 對象中有一個方法 print(),我們不能這樣訪問,pa->pb->print();
英文 pb_是一個 weak_ptr,應該先把它轉(zhuǎn)化為shared_ptr,如:
shared_ptr p = pa->pb_.lock();
p->print();
5.5 內(nèi)存泄露
當兩個對象相互使用一個 shared_ptr 成員變量指向?qū)Ψ?,會造?循環(huán)引用 ,使引用計數(shù)失效,從而導致內(nèi)存泄漏。
#include
#include
using namespace std;
class B;
class A
{
public: // 為了省去一些步驟這里 數(shù)據(jù)成員也聲明為public
shared_ptr pb;
~A()
{
cout << "kill A\\n";
}
};
class B
{
public:
shared_ptr pa;
~B()
{
cout <<"kill B\\n";
}
};
int main(int argc, char** argv)
{
shared_ptr sa(new A());
shared_ptr sb(new B());
if(sa && sb)
{
sa->pb=sb;
sb->pa=sa;
}
cout<<"sa use count:"<use_count()<return 0;
}
注意此時sa,sb都沒有釋放,產(chǎn)生了內(nèi)存泄露問題。即A內(nèi)部有指向B,B內(nèi)部有指向A,這樣對于A,B必定是在A析構后B才析構,對于B,A必定是在B析構后才析構A,這就是循環(huán)引用問題,違反常規(guī),導致內(nèi)存泄露。
解決辦法
使用弱引用的智能指針weak_ptr打破這種循環(huán)引用。為了解決循環(huán)引用導致的內(nèi)存泄漏,引入了weak_ptr 弱指針,weak_ptr 的構造函數(shù)不會修改引用計數(shù)的值,從而不會對對象的內(nèi)存進行管理,其類似一個普通指針,但不指向引用計數(shù)的共享內(nèi)存,但是其可以檢測到所管理的對象是否已經(jīng)被釋放,從而避免非法訪問。
5.6 shared_ptr的實現(xiàn)
template <typename T>
class SmartPtr
{
private:
T *ptr; //底層真實的指針
int *use_count;//保存當前對象被多少指針引用計數(shù)
public:
SmartPtr(T *p); //SmartPtrp(new int(2));
SmartPtr(const SmartPtr&orig);//SmartPtrq(p);
SmartPtr&operator=(const SmartPtr &rhs);//q=p
~SmartPtr();
T operator*(); //為了能把智能指針當成普通指針操作定義解引用操作
T*operator->(); //定義取成員操作
T* operator+(int i);//定義指針加一個常數(shù)
int operator-(SmartPtr&t1, SmartPtr&t2);//定義兩個指針相減
void getcount()
{
return *use_count
}
};
template <typename T>
int SmartPtr::operator-(SmartPtr &t1, SmartPtr &t2)
{
return t1.ptr - t2.ptr;
}
template <typename T>
SmartPtr::SmartPtr(T *p)
{
ptr = p;
try
{
use_count = new int(1);
}
catch (...)
{
delete ptr; //申請失敗釋放真實指針和引用計數(shù)的內(nèi)存
ptr = nullptr;
delete use_count;
use_count = nullptr;
}
}
template <typename T>
SmartPtr::SmartPtr(const SmartPtr &orig) //復制構造函數(shù)
{
use_count = orig.use_count;//引用計數(shù)保存在一塊內(nèi)存,所有的 SmarPtr 對象的引用計數(shù)
都指向這里
this->ptr = orig.ptr;
++(*use_count); //當前對象的引用計數(shù)加 1
}
template <typename T>
SmartPtr& SmartPtr::operator=(const SmartPtr &rhs)
{
//重載=運算符,例如 SmartPtrp,q; p=q;這個語句中,首先給 q 指向的對象的引用計數(shù)加1,因為 p 重新指向了 q 所指的對象,所以 p 需要先給原來的對象的引用計數(shù)減 1,如果減一后為 0,先釋放掉 p 原來指向的內(nèi)存,然后講 q 指向的對象的引用計數(shù)加 1 后賦值給 p
++*(rhs.use_count);
if ((--*(use_count)) == 0)
{
delete ptr;
ptr = nullptr;
delete use_count;
use_count = nullptr;
}
ptr = rhs.ptr;
*use_count = *(rhs.use_count);
return *this;
}
template <typename T>
SmartPtr::~SmartPtr()
{
getcount();
if (--(*use_count) == 0) //SmartPtr 的對象會在其生命周期結(jié)束的時候調(diào)用其析構函數(shù),在析構函數(shù)中檢測當前對象的引用計數(shù)是不是只有正在結(jié)束生命周期的這個 SmartPtr 引用,如果是,就釋放掉,如果不是,就還有其他的 SmartPtr 引用當前對象,就等待其他的 SmartPtr對象在其生命周期結(jié)束的時候調(diào)用析構函數(shù)釋放掉
{
getcount();
delete ptr;
ptr = nullptr;
delete use_count;
use_count = nullptr;
}
}
template <typename T>
T SmartPtr::operator*()
{
return *ptr;
}
template <typename T>
T* SmartPtr::operator->()
{
return ptr;
}
template <typename T>
T* SmartPtr::operator+(int i)
{
T *temp = ptr + i;
return temp;
}
6 數(shù)組和指針
指針 | 數(shù)組 |
---|---|
保存數(shù)據(jù)的地址 | 保存數(shù)據(jù) |
指針的內(nèi)容為為地址,從該地址訪問數(shù)據(jù) | 直接訪問數(shù)據(jù) |
通常用于動態(tài)的數(shù)據(jù)結(jié)構 | 通常用于固定數(shù)目且數(shù)據(jù)類型相同的元素 |
通過 Malloc 分配內(nèi)存,free 釋放內(nèi)存 | 隱式的分配和刪除 |
通常指向匿名數(shù)據(jù),操作匿名函數(shù) | 自身即為數(shù)據(jù)名 |
7 野指針
野指針就是指向一個已刪除的對象或者未申請訪問受限內(nèi)存區(qū)域的指針
8 函數(shù)指針
8.1 定義
函數(shù)指針是指向函數(shù)的指針變量。
函數(shù)指針本身首先是一個指針變量,該指針變量指向一個具體的函數(shù)。這正如用指針變量可指向整型變量、字符型、數(shù)組一樣,這里是指向函數(shù)。
C 在編譯時,每一個函數(shù)都有一個入口地址,該入口地址就是函數(shù)指針所指向的地址。有了指向函數(shù)的指針變量后,可用該指針變量調(diào)用函數(shù),就如同用指針變量可引用其他類型變量一樣,在這些概念上是大體一致的。
8.2 用途:
調(diào)用函數(shù)和做函數(shù)的參數(shù),比如回調(diào)函數(shù)。
8.3 示例:
char * fun(char * p) {…} // 函數(shù) fun
char * (*pf)(char * p); // 函數(shù)指針 pf
pf = fun; // 函數(shù)指針 pf 指向函數(shù) fun
pf(p); // 通過函數(shù)指針 pf 調(diào)用函數(shù) fun
9 fork函數(shù)
Fork:創(chuàng)建一個和當前進程映像一樣的進程可以通過 fork( )系統(tǒng)調(diào)用:
#include
#include
pid_t fork(void);
成功調(diào)用 fork( )會 創(chuàng)建一個新的進程 ,它幾乎與調(diào)用 fork( )的進程一模一樣,這兩個進程都會繼續(xù)運行。在子進程中,成功的 fork( )調(diào)用會返回 0。在父進程中 fork( )返回子進程的 pid。如果出現(xiàn)錯誤,fork( )返回一個負值。
最常見的 fork( )用法是創(chuàng)建一個新的進程,然后使用 **exec( )**載入二進制映像,替換當前進程的映像。這種情況下,派生(fork)了新的進程,而這個子進程會執(zhí)行一個新的二進制可執(zhí)行文件的映像。這種“派生加執(zhí)行”的方式是很常見的。
在早期的 Unix 系統(tǒng)中,創(chuàng)建進程比較原始。當調(diào)用 fork 時,內(nèi)核會把所有的內(nèi)部數(shù)據(jù)結(jié)構復制一份,復制進程的頁表項,然后把父進程的地址空間中的內(nèi)容逐頁的復制到子進程的地址空間中。但從內(nèi)核角度來說,逐頁的復制方式是十分耗時的?,F(xiàn)代的 Unix 系統(tǒng)采取了更多的優(yōu)化,例如 Linux,采用了寫時復制的方法,而不是對父進程空間進程整體復制。
10 析構函數(shù)
析構函數(shù)與構造函數(shù)對應,當對象結(jié)束其生命周期,如對象所在的函數(shù)已調(diào)用完畢時,系統(tǒng)會自動執(zhí)行析構函數(shù)。
析構函數(shù)名也應與類名相同,只是在函數(shù)名前面加一個位取反符~,例如~stud( )
,以區(qū)別于構造函數(shù)。它 不能帶任何參數(shù),也沒有返回值 (包括 void 類型)。只能有一個析構函數(shù), 不能重載 。
如果用戶沒有編寫析構函數(shù),編譯系統(tǒng)會自動生成一個缺省的析構函數(shù)(即使自定義了析構函數(shù),編譯器也總是會為我們合成一個析構函數(shù),并且如果自定義了析構函數(shù),編譯器在執(zhí)行時會先調(diào)用自定義的析構函數(shù)再調(diào)用合成的析構函數(shù)),它也不進行任何操作。所以許多簡單的類中沒有用顯式的析構函數(shù)。
如果一個類中有指針,且在使用的過程中動態(tài)的申請了內(nèi)存,那么最好顯示構造析構函數(shù)在銷毀類之前,釋放掉申請的內(nèi)存空間,避免內(nèi)存泄漏。
10.1 類析構順序
- 派生類本身的析構函數(shù)
- 對象成員析構函數(shù)
- 基類析構函數(shù)
因為析構函數(shù)沒有參數(shù),所以包含成員對象的類的析構函數(shù)形式上并無特殊之處。但在撤銷該類對象的時候,會首先調(diào)用自己的析構函數(shù),再調(diào)用成員對象的析構函數(shù),調(diào)用次序與初始化時的次序相反。
11 虛函數(shù)和多態(tài)
多態(tài)的實現(xiàn)主要分為靜態(tài)多態(tài)和 動態(tài)多態(tài) , 靜態(tài)多態(tài)主要是重載 ,在 編譯的時候就已經(jīng)確定 ;動態(tài)多態(tài)是用虛函數(shù)機制實現(xiàn)的,在 運行期間動態(tài)綁定 。例如:一個父類類型的指針指向一個子類對象時候,使用父類的指針去調(diào)用子類中重寫了的父類中的虛函數(shù)的時候,會調(diào)用子類重寫過后的函數(shù),在父類中聲明為加了 virtual 關鍵字的函數(shù),在子類中重寫時候不需要加 virtual也是虛函數(shù)。
虛函數(shù)的實現(xiàn):在有虛函數(shù)的類中,類的最開始部分是一個虛函數(shù)表的 指針 ,這個指針指向一個 虛函數(shù)表 ,表中放了虛函數(shù)的地址,實際的虛函數(shù)在代碼段(.text)中。當子類繼承了父類的時候也會繼承其虛函數(shù)表,當子類重寫父類中虛函數(shù)時候,會將其繼承到的 虛函數(shù)表中的地址替換為重新寫的函數(shù)地址 。使用了虛函數(shù),會增加訪問內(nèi)存開銷,降低效率。
12 析構函數(shù)與虛函數(shù)
析構函數(shù)必須是虛函數(shù),因為將可能會被繼承的父類的析構函數(shù)設置為虛函數(shù),可以保證當我們 new 一個子類,然后使用基類指針指向該子類對象, 釋放基類指針時可以釋放掉子類的空間 ,防止內(nèi)存泄漏。
C++默認的析構函數(shù)不是虛函數(shù)是因為虛函數(shù)需要額外的虛函數(shù)表和虛表指針,占用額外的內(nèi)存。而對于不會被繼承的類來說,其析構函數(shù)如果是虛函數(shù),就會浪費內(nèi)存。因此 C++默認的析構函數(shù)不是虛函數(shù),而是只有當需要當作父類時, 設置虛函數(shù) 。
13 靜態(tài)函數(shù)和虛函數(shù)
靜態(tài)函數(shù)在編譯的時候就已經(jīng)確定運行時機,虛函數(shù)在運行的時候動態(tài)綁定。虛函數(shù)因為用了虛函數(shù)表機制,調(diào)用的時候會增加一次內(nèi)存開銷。
13.1 靜態(tài)函數(shù)
用static修飾的函數(shù),限定在本源碼文件中使用,不能被本源碼文件以外的代碼文件調(diào)用。 普通的函數(shù),默認是extern的,也就是說,可以被其它代碼文件調(diào)用該函數(shù)。
13.2 虛函數(shù)表
當一個類中包含被virtual 關鍵字修飾的成員函數(shù)時,該成員函數(shù)就成為了一個 虛函數(shù) 。頭一個含有虛函數(shù)的類所實例化出來的對象都擁有同一個 虛函數(shù)表 ,在對象中含有一個* 虛函數(shù)指針 _vptr ,該指針指向該類的虛函數(shù)表,虛函數(shù)表保存的是類中虛函數(shù)的地址(一個類可能有多個虛函數(shù))。
13.3 虛函數(shù)作用
當一個子類繼承了一個含有虛函數(shù)的基類,并重寫了該基類中的一個虛函數(shù),我們就說這兩個類構成多態(tài)。子類繼承基類的同時,基類的虛函數(shù)表也被子類繼承,不同的是被 子類重寫的虛函數(shù)將會替代原來虛函數(shù)表中對應的基類的虛函數(shù)的地址 。從而基類與子類調(diào)用同名的虛函數(shù)時,所調(diào)用的就不是同一個函數(shù),從而體現(xiàn)了多態(tài)和虛函數(shù)表的作用。
13.4 靜態(tài)函數(shù)與虛函數(shù)的區(qū)別
我們知道類的靜態(tài)函數(shù)是沒有this指針的,調(diào)用它時不需要創(chuàng)建對象,通過:類名 ::函數(shù)名(參數(shù))的形式直接調(diào)用。靜態(tài)函數(shù)只有唯一的一份,因此它的 地址是固定不變的 , 所以編譯的時候但凡遇到調(diào)用該靜態(tài)函數(shù)的時候就知道調(diào)用的是哪一個函數(shù),因此說靜態(tài)函數(shù)在編譯的時候就已經(jīng)確定運行時機。 而虛函數(shù)則不然,看下面的代碼:
class A
{
public:
virtual void fun()
{
cout<<"i am A <I am B" <fun();
return 0;
}
類A與類B構成多態(tài),創(chuàng)建了 A類指針pb指向 B類對象,當程序編譯的時候只對語法等進行檢測,該語句沒有什么問題,但是編譯器此時無法確定調(diào)用的是哪一個 fun() 函數(shù),因為類A類B中都含有fun函數(shù),因此只能是在程序運行的時候通過 pb指針查看對象的虛函數(shù)表(訪問虛函數(shù)表就是所謂的訪問內(nèi)存)才能確定該函數(shù)的地址,即確定調(diào)用的是哪一個函數(shù)。這就解釋了所說的“ 虛函數(shù)在運行的時候動態(tài)綁定。虛函數(shù)因為用了虛函數(shù)表機制,調(diào)用的時候會增加一次內(nèi)存開銷。 ”
14 重載和覆蓋
14.1 重載
兩個函數(shù)名相同,但是參數(shù)列表不同(個數(shù),類型),返回值類型沒有要求,在同一作用域中。
14.2 重寫
子類繼承了父類,父類中的函數(shù)是虛函數(shù),在子類中重新定義了這個虛函數(shù),這種情況是重寫,是一種同名覆蓋。
15 在main函數(shù)前先運行的函數(shù)
1.test0 :__attribute((constructor))
是gcc擴展,標記這個函數(shù)應當在main函數(shù)之前執(zhí)行。同樣有一個__attribute((destructor))
,標記函數(shù)應當在程序結(jié)束之前(main結(jié)束之后,或者調(diào)用了exit后)執(zhí)行。
2.test1 :全局static變量的初始化在程序初始階段,先于main函數(shù)的執(zhí)行。
#include
using namespace std;
__attribute((constructor)) void test0()
{
printf("before main 0\\n");
}
int test1()
{
cout << "before main 1" << endl;
return 54;
}
static int i = test1();
int main(int argc, char** argv)
{
cout << "main function." <<endl;
return 0;
}
在leetcode里經(jīng)常見到static,在main之前關閉cin與stdin的同步來“加快”速度的黑科技。
static int _ = []{
cin.sync_with_stdio(false);
return 0;
}();
16 內(nèi)存管理
在 C++ 中,虛擬內(nèi)存分為代碼段、數(shù)據(jù)段、BSS 段、堆區(qū)、文件映射區(qū)以及棧區(qū)六部分。
- 棧(stack) :程序 自動分配 ,使用??臻g存儲函數(shù)的返回地址、參數(shù)、局部變量、返回值。
- 堆(heap) :
- 堆 :調(diào)用
malloc
在堆區(qū)動態(tài)分配內(nèi)存,調(diào)用free
來手動釋放。堆是操作系統(tǒng)所維護的一塊特殊內(nèi)存,它提供了動態(tài)分配的功能。 - 自由存儲區(qū) :由
new
分配內(nèi)存,用來delete
手動釋放。和堆類似,通過new
來申請的內(nèi)存區(qū)域可稱為自由存儲區(qū)。
- 靜態(tài)/全局區(qū) :在 C++ 里面沒有區(qū)分bss和data。
- bss段 :存儲未初始化的全局變量和靜態(tài)變量(局部+全局),以及所有被初始化為0的全局變量和靜態(tài)變量,Block Started by Symbol。
- data段 :存儲程序中已初始化的全局變量和靜態(tài)變量。
- 代碼區(qū) (code segment 或 text segment):
- 代碼段 :存放函數(shù)體的二進制代碼,text段。
- 常量區(qū) :只讀數(shù)據(jù),比如字符串常量,程序結(jié)束時由系統(tǒng)釋放。 rodata段 ,read only。
init段:程序初始化入口代碼,在main() 之前運行。
17 常量const
常量是固定值,在程序執(zhí)行期間不會改變。常量可以是任何的基本數(shù)據(jù)類型,可分為int、float、char、string和bool。常量定義必須初始化。
17.1 存儲區(qū)域
- 局部常量 ,存放在 棧區(qū) ;
- static/全局常量 ,存放在 靜態(tài)/全局存儲區(qū) ;
- 字面值常量 ,其值一望而知,存放在 常量區(qū) 。
17.2 const修飾成員函數(shù)
const 修飾的成員函數(shù)表明函數(shù)調(diào)用 不會對對象做出任何更改 ,事實上,如果確認不會對對象做更改,就應該為函數(shù)加上 const 限定,這樣無論 const 對象還是普通對象都可以調(diào)用該函數(shù)
若同時定義了兩個函數(shù),一個帶 const,一個不帶,這相當于函數(shù)的 重載 。
18 代碼解析
18.1 strcpy和strlen
strcpy 是字符串拷貝函數(shù),原型:
char *strcpy(char* dest, const char *src);
從 src 逐字節(jié)拷貝到 dest,直到遇到'\\0'結(jié)束,因為沒有指定長度,可能會導致拷貝越界,造成緩沖區(qū)溢出漏洞,安全版本是 strncpy 函數(shù)。
strlen 函數(shù)是計算字符串長度的函數(shù),返回從開始到'\\0'之間的字符個數(shù)。
18.2 ++i和i++
++i 實現(xiàn):
int& int::operator++()
{
*this +=1;
return *this;
}
i++ 實現(xiàn):
const int int::operator(int)
{
int oldValue = *this;
++(*this);
return oldValue;
}
18.3 代碼的區(qū)別
(1)字符串 123 保存在 常量區(qū) ,const 本來是修飾 arr 指向的值,不能通過 arr 去修改,但是字符串“123”在常量區(qū),本來就不能改變,所以加不加 const 效果都一樣:
const char * arr = "123";
(2)字符串 123 保存在常量區(qū),這個 和arr 指針指向的是同一個位置,同樣不能通過 brr 去修改"123"的值:
char * brr = "123";
(3)這里 123 本來是在棧上的,但是編譯器可能會做某些優(yōu)化,將其放到常量區(qū):
const char crr[] = "123";
(4)字符串 123 保存在 棧區(qū) ,可以通過 drr 去修改:
char drr[] = "123";
19 編程題
19.1 點是在三角形內(nèi)
給定三角形ABC和一點P(x,y,z),判斷點P是否在ABC內(nèi)。
根據(jù)面積法,如果P在三角形ABC內(nèi),那么三角形ABP的面積+三角形BCP的面積+三角形ACP的面積應該等于三角形ABC的面積。
S=(x_1y_2+x_2y_3+x_3y_1-x_1y_3-x_2y_1-x_3y_2)/2
代碼如下:
#include
#include
using namespace std;
#define ABS_FLOAT_0 0.0001
struct point_float
{
float x;
float y;
};
float GetTriangleSquar(const point_float pt0, const point_float pt1, const point_float pt2) // 計算三角形面積
{
point_float AB, BC;
AB.x = pt1.x - pt0.x;
AB.y = pt1.y - pt0.y;
BC.x = pt2.x - pt1.x;
BC.y = pt2.y - pt1.y;
return fabs((AB.x * BC.y - AB.y * BC.x)) / 2.0f;
}
bool IsInTriangle(const point_float A, const point_float B, const point_float C, const point_float D) // 判斷給定一點是否在三角形內(nèi)或邊上
{
float SABC, SADB, SBDC, SADC;
SABC = GetTriangleSquar(A, B, C);
SADB = GetTriangleSquar(A, D, B);
SBDC = GetTriangleSquar(B, D, C);
SADC = GetTriangleSquar(A, D, C);
float SumSuqar = SADB + SBDC + SADC;
if ((-ABS_FLOAT_0 < (SABC - SumSuqar)) && ((SABC - SumSuqar) < ABS_FLOAT_0))
return true;
else
return false;
}
19.2 判斷一個數(shù)是二的倍數(shù)
判斷一個數(shù)是不是二的倍數(shù),即判斷該數(shù)二進制末位是不是 0:
a % 2 == 0
a & 0x0001 == 0 // 兩種辦法都可
19.3 一個數(shù)中有幾個1
可以直接逐位除十取余判斷:
int fun(long x)
{
int _count = 0;
while(x)
{
if(x % 10 == 1)
++_count;
x /= 10;
}
return _count;
}
int main()
{
cout << fun(123321) << endl;
return 0;
}
審核編輯:湯梓紅
-
C++
+關注
關注
22文章
2108瀏覽量
73650 -
static
+關注
關注
0文章
33瀏覽量
10370 -
關鍵字
+關注
關注
0文章
37瀏覽量
6896
發(fā)布評論請先 登錄
相關推薦
評論