c 學習記錄整理

2021-10-08 23:08:07 字數 4056 閱讀 2736

初學c++的乙個學習記錄,對c++的設計思路進行闡述。

在c++中,宣告、定義和初始化是三種不同的概念。

宣告乙個物件的含義是,把這個物件新增到符號表,告訴編譯器這個物件是有效的,但是,還沒有給這個物件分配具體的空間。

定義乙個物件的含義是,不但宣告了這個物件,還給這個物件分配了具體的記憶體空間。

初始化乙個物件的含義是,不但定義了這個物件,還呼叫了這個物件的初始化函式,給這個物件賦予了具體的值。

宣告、定義和初始化是三個遞進的過程。

只宣告不定義的情形主要有以下幾種情況:

用extern修飾的變數。用extern修飾的變數只是告訴編譯器有這個變數只是乙個引用,真正的定義在檔案外部。

可以在類中宣告乙個函式,在類外定義,這時宣告和定義是分離的。

類中的成員變數只能算作宣告,只有初始化乙個物件時才算初始化。(至於類的靜態成員變數,必須單獨在類外初始化。)

乙個類在沒完成定義時(大括號結束),只能算宣告了這個類。這時,可以宣告乙個指向該類的指標,但不能宣告乙個該類的例項化成員。

只定義不初始化的情景只發生在內建型別上,對於自定義的類的物件,定義的同時一定會初始化。

c++宣告過程中,講究呼叫和宣告的一致性。因此,如果按照下面的方式宣告:

int

(*a)

;

是為了表達:

(

*a)[

0]

的型別是int。

同時,考慮到const int a,是為了表達a是常量;int * const a是為了表達a是常量。

直接定義在檔案中的變數被稱為全域性變數,擁有全域性作用域。

定義在函式體或迴圈體中的變數被稱為區域性變數,擁有區域性作用域。

沒有名字,只能臨時存在的變數被稱為臨時變數,臨時變數的作用域只存在與表示式中,表示式結束自動銷毀。可以通過繫結延長臨時變數的作用域。(後面會展開分析)

c++中,乙個.cpp檔案對應乙個編譯單元,每個編譯單元單獨編譯,編譯完成後再鏈結到一起。每個編譯單元中,完成了定義的全域性變數和類的成員函式會放到符號表中,供其它編譯單元中宣告的同名符號使用。

一般來說,所有編譯單元的所有符號表中,宣告可以有任意次,但定義只能有一次。但是,用inline修飾的成員函式可以定義多次,在類中定義的函式預設有inline屬性。

值得注意的,由於類的靜態成員變數必須要用全域性變數的形式初始化一次,因此它也具有全域性作用域。

下面給出乙個非常好玩的例子:

// example 1

// source.cpp

#include

#include

#include

using

namespace std;

struct foo

;void

func()

;void

func_()

;};int foo::a =4;

intmain()

// foo.cpp

#include

using

namespace std;

struct foo

;void

func()

;void

func_()

;};int foo::b =

100;

void foo::

func()

void foo::

func_()

這個例子在visual studio2019上的執行結果是:

5100

請思考一下為什麼。

c++的記憶體分為5個部分:

全域性/靜態變數區;

常量區;

棧;堆;

自由儲存區;

全域性/靜態變數區、常量區中變數的位址在編譯期就已經確定,棧、堆、自由儲存區中變數的位址在執行期才確定。

全域性/靜態變數區中,儲存全域性變數和靜態變數,靜態變數。

常量區中,儲存常量。

棧中,儲存區域性變數和臨時變數。

堆中,儲存malloc分配的變數。

自由儲存區中,儲存new分配的變數。

關於常量:

有的常量會儲存在常量區,例如字串常量;有的常量直接在編譯期就完成了替換,例如整數常量。不要試圖對常量進行修改,這樣會導致未定義行為。

關於靜態變數:

用static修飾的類成員變數和成員函式具有全域性作用域,但靜態成員函式沒有this指標。

用static修飾的全域性變數被稱為靜態全域性變數,具有檔案作用域,只在編譯單元有效。

用static修飾的區域性變數被稱為靜態區域性變數,作用域不變。

靜態變數具有唯一性。

關於棧:

這個棧其實就是資料結構中的棧,先進後出,通過棧來完成函式呼叫。

關於堆和自由儲存區:

一般來說,編譯器的operator new往往通過呼叫malloc實現,這時自由儲存區也就是堆。但是,c++標準沒有強制要求一定要通過malloc實現,可以採用不同的方法實現operator new,這時自由儲存區和堆不同。

最後,乙個可執行程式還有乙個名為**段的部分,用來儲存各個函式的**。

可以直接定義乙個變數(常引用,右值引用也行)並繫結到臨時變數上,這時,臨時變數直接生成在這個變數分配的記憶體上,不會產生任何額外的開銷。繫結後臨時變數的作用域和繫結的變數一致。雖然部分型別還提供了移動建構函式,但是把臨時變數繫結到乙個變數上不會呼叫移動建構函式。另外,使用static_cast強制轉換會產生繫結。

通常的右值引用其實是乙個左值,引用繫結後和普通的左值沒有任何區別,僅僅表示這個變數是將亡值,可以通過破壞它來完成移動建構函式。只有用static_cast強制轉換(也就是move)才真正具有右值引用的型別。

// example 2

#include

#include

using

namespace std;

struct foo

~foo()

foo operator+(

const foo& other)};

intmain()

左值引用其實是通過指標實現的。

t&&根據引用摺疊規則實現了通用引用。

通過值傳遞函式引數會執行拷貝建構函式。

左值引用可以繫結左值和左值引用;右值引用可以繫結右值和move強制轉換的右值引用。

值和常引用可以繫結任何左值、右值、左值引用、右值引用。

使用通用引用時,由於會丟失右值屬性,可以通過forward實現完美**。

每乙個自定義類的物件都會自動初始化。先初始化基類,再依次初始化成員變數,初始化列表可以過載這個過程。我們既可以通過小括號,也可以通過大括號來呼叫初始化函式,一般沒有區別。但是,大括號還有初始化列表的含義。所以vector v和vector v(3)會產生不同的結果。另外,還可以通過大括號給出缺省的初始化函式,如example 1。

值得一提的是,初始化過程中,編譯器會自動優化operator=。

如果物件有虛函式,初始化過程中,先初始化基類,基類的建構函式完成後,再把基類的虛函式表指標換成子類的虛函式表指標。

對於多繼承的函式,擁有多個虛函式表,也對應的有多個虛函式指標。

如果有虛繼承,還會初始化乙個虛基類表。(gcc編譯器中,如果虛基類沒有成員變數,編譯器會無視virtual命令)

智慧型指標通過raii機制管理資源。

shared_ptr和unique_ptr都表示對物件所有權的占有。生成shared_ptr傳遞給其它函式時,相當於分享了物件的所有權(weak_ptr也是一樣,因為有lock方法)

多執行緒時最好不要傳this指標,如果要傳this指標必須保證this指標只繫結了一次智慧型指標。

c++中,一共有三種函式:

普通函式

成員函式

仿函式

相比普通函式,成員函式隱含了乙個this指標作為成員變數。一般把靜態成員函式也看作普通函式。

仿函式則通過構建乙個struct來儲存當前的環境變數,通過過載operator()來模擬函式。

c++中,lambda表示式其實是仿函式的語法糖。

三種函式的型別表達方式大不相同,但是可以通過function和bind進行轉換,給出統一的介面。

學習記錄 指標(未整理)

1.指標是什麼?指標就是記憶體的位址,指標變數就是能夠儲存記憶體位址的變數。一般資料型別變數名指的是這個資料所代表的值。2.如何定義指標?在最開始宣告乙個指標變數的時候,為了表明是乙個指標變數,要在變數名前加 間接引用符 在後續的使用中則不用加 了 下面兩種寫法是等價的 乙個變數 inta 第一種,...

C 學習記錄

由於之前寫c c 程式時,動不動就容易報錯,也就沒繼續用,一直覺得c 很難。最近在學習機器學習時,用到了乙個c 神經網路庫,並且在菜鳥教程中看到了c 的教程,於是學了一下,發現根本沒有想象中的那麼難,可能也是這個教程講的比較好吧。現在把一些學到的比較重要的點記錄一下吧!1.ifndef 突然覺得在標...

C 學習記錄

原創 主要是c 和c 不同的地方,會寫一下,流程控制什麼的都差不多,比較適合c 轉c 的人看 以前沒有系統的學過c 現在補一下,以前完全是為了看懂別人的 稍微看一下,也沒有太想在這方面下功夫,學一點是一點,目標是能修改別人的c 服務端程式。一.包含 包含原理上什麼檔案都能包含進來,但是一般只包含.c...