0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

C++基礎知識

jf_96884364 ? 來源:jf_96884364 ? 作者:jf_96884364 ? 2023-01-12 11:00 ? 次閱讀

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主要有如下幾種用法:

  1. 用于類層次結(jié)構中基類派生類之間指針或引用的轉(zhuǎn)換。

    進行向上轉(zhuǎn)換是安全的;
    進行向下轉(zhuǎn)換時,由于沒有動態(tài)類型檢查,所以是不安全的。因為 基類不包含派生類的成員變量,無法對派生類的成員變量賦值。

  2. 用于基本數(shù)據(jù)類型之間的轉(zhuǎn)換,如int、float、char之間的互相轉(zhuǎn)換

  3. 把空指針轉(zhuǎn)換成 目標類型的空指針

  4. 把任何類型的表達式轉(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ū)別

  1. 指針有自己的一塊空間,而引用只是一個別名;
  2. 使用 sizeof 看一個指針的大小是 4,而引用則是被引用對象的大小;
  3. 指針可以被初始化為 NULL,而引用必須被初始化且必須是一個已有對象的引用;
  4. 作為參數(shù)傳遞時,指針需要被解引用才可以對對象進行操作,而對引用的修改都會改變引用所指向的對象;
  5. 可以有 const 指針,但是沒有 const 引用;
  6. 指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能 被改變;
  7. 指針可以有多級指針(**p),而引用至于一級;
  8. 指針和引用使用++運算符的意義不一樣;
  9. 如果返回動態(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 類析構順序

  1. 派生類本身的析構函數(shù)
  2. 對象成員析構函數(shù)
  3. 基類析構函數(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ū)六部分。

  1. 棧(stack) :程序 自動分配 ,使用??臻g存儲函數(shù)的返回地址、參數(shù)、局部變量、返回值。
  2. 堆(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ū)。
  1. 靜態(tài)/全局區(qū) :在 C++ 里面沒有區(qū)分bss和data。
  • bss段 :存儲未初始化的全局變量和靜態(tài)變量(局部+全局),以及所有被初始化為0的全局變量和靜態(tài)變量,Block Started by Symbol。
  • data段 :存儲程序中已初始化的全局變量和靜態(tài)變量。
  1. 代碼區(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ū)域

  1. 局部常量 ,存放在 棧區(qū) ;
  2. static/全局常量 ,存放在 靜態(tài)/全局存儲區(qū) ;
  3. 字面值常量 ,其值一望而知,存放在 常量區(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::operatorint)
{
    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;
}

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • C++
    C++
    +關注

    關注

    22

    文章

    2108

    瀏覽量

    73650
  • static
    +關注

    關注

    0

    文章

    33

    瀏覽量

    10370
  • 關鍵字
    +關注

    關注

    0

    文章

    37

    瀏覽量

    6896
收藏 人收藏

    評論

    相關推薦

    【微信精選】C++的精髓類和繼承就該這么理解!

    C++基礎知識說明,如果你學過C語言,那么基礎知識是一樣的,比循環(huán)、運算符、指針等…。可以說C++是在C
    發(fā)表于 08-15 10:00

    【微信精選】C++的精髓類和繼承就該這么理解!

    C++基礎知識說明,如果你學過C語言,那么基礎知識是一樣的,比循環(huán)、運算符、指針等…??梢哉fC++是在C
    發(fā)表于 08-15 09:41

    分享一個開源的ESP32 物聯(lián)網(wǎng)小工具

    固件所需的 C++ 基礎知識。這是該項目的視頻演示:我已經(jīng)使用KiCad為這個項目設計了 PCB 。我已將我的 IoT 小工具連接到我的一個燈架上,所以我沒有使用 Daniel 的桌面底座。代碼Github 上的小工具固件和 PlatformIO 項目
    發(fā)表于 06-17 10:03

    C++C/C++程序設計教程_C/C++概述

    C++基礎知識,簡要介紹了C++的一些簡單知識,概念,函數(shù)
    發(fā)表于 12-25 10:15 ?0次下載

    C++語言基礎知識講解

    C++語言基礎知識講解,喜歡的朋友可以下載來學習。
    發(fā)表于 01-14 15:30 ?21次下載

    Visual C++教程之C++基礎知識介紹

    本文檔的主要內(nèi)容詳細介紹的是Visual C++教程之C++基礎知識介紹主要內(nèi)容包括了:1 類和對象,2 類的成員及特性,3 繼承和派生類
    發(fā)表于 02-15 15:59 ?9次下載
    Visual <b class='flag-5'>C++</b>教程之<b class='flag-5'>C++</b>的<b class='flag-5'>基礎知識</b>介紹

    C專家編程PDF電子書免費下載

    內(nèi)容提要:《C專家編程》展示了最優(yōu)秀的C程序員所使用的編碼技巧,并專門開辟了一章對C++基礎知識進行了介紹。書中C的歷史、語言特性、聲明、
    發(fā)表于 05-14 08:00 ?15次下載
    <b class='flag-5'>C</b>專家編程PDF電子書免費下載

    C++程序設計的基礎知識初步了解C++的資料免費下載

    本文檔的主要內(nèi)容詳細介紹的是C++程序設計的基礎知識初步了解C++的資料免費下載包括了:1 認識C++,2 C++的現(xiàn)狀和發(fā)展,3
    發(fā)表于 06-10 08:00 ?25次下載
    <b class='flag-5'>C++</b>程序設計的<b class='flag-5'>基礎知識</b>初步了解<b class='flag-5'>C++</b>的資料免費下載

    C++核心編程基礎知識大全免費下載

    C++核心編程基礎知識大全免費下載
    發(fā)表于 12-03 18:06 ?2次下載

    C/C++基礎知識匯總

    這是一篇五萬字的C/C++知識點總結(jié),包括答案。
    的頭像 發(fā)表于 06-12 15:10 ?2398次閱讀

    C++基礎知識之面向?qū)ο笃?

    這兩期講完基本上面試遇到的相關問題就過了一半了,后續(xù)將STL和內(nèi)存相關的補充完整,C++這塊的基本上就全部結(jié)束了,以后可能再也不會像現(xiàn)在這樣在這個方向投入過多時間,且行且珍惜啊,還是跟以前一樣,所有的總結(jié)都會有PDF版,如有需要自取。廢話不多說,發(fā)完這期,繼續(xù)整理STL去了。
    的頭像 發(fā)表于 03-02 09:49 ?387次閱讀

    C++基礎知識之面向?qū)ο笃?

    這兩期講完基本上面試遇到的相關問題就過了一半了,后續(xù)將STL和內(nèi)存相關的補充完整,C++這塊的基本上就全部結(jié)束了,以后可能再也不會像現(xiàn)在這樣在這個方向投入過多時間,且行且珍惜啊,還是跟以前一樣,所有的總結(jié)都會有PDF版,如有需要自取。廢話不多說,發(fā)完這期,繼續(xù)整理STL去了。
    的頭像 發(fā)表于 03-02 09:53 ?407次閱讀

    C++基礎知識之函數(shù)1

    函數(shù)是 C++ 中的一個重要概念,它可以讓我們將一段代碼封裝起來,然后在需要的時候調(diào)用它。C++ 中的函數(shù)有以下幾個特點: * 函數(shù)可以有參數(shù)和返回值。 * 函數(shù)可以被其他函數(shù)調(diào)用。 * 函數(shù)可以被重載,即可以定義多個同名的函數(shù),只要它們的參數(shù)列表不同即可。
    的頭像 發(fā)表于 04-03 10:34 ?577次閱讀

    C++基礎知識之函數(shù)2

    C++中,我們可以使用inline關鍵字來定義內(nèi)聯(lián)函數(shù)。內(nèi)聯(lián)函數(shù)是一種特殊的函數(shù),它在編譯時會被直接嵌入到調(diào)用它的代碼中,從而避免了函數(shù)調(diào)用的開銷,提高了程序的執(zhí)行效率。內(nèi)聯(lián)函數(shù)的定義通常比較
    的頭像 發(fā)表于 04-03 10:34 ?535次閱讀

    C++語言基礎知識

    電子發(fā)燒友網(wǎng)站提供《C++語言基礎知識.pdf》資料免費下載
    發(fā)表于 07-19 10:58 ?7次下載