主頁 > 後端開發 > 現代C++學習指南-型別系統

現代C++學習指南-型別系統

2023-06-14 07:44:04 後端開發

在前一篇,我們提供了一個方向性的指南,但是學什么,怎么學卻沒有詳細展開,本篇將在前文的基礎上,著重介紹下怎樣學習C++的型別系統,

寫在前面

在進入型別系統之前,我們應該先達成一項共識——盡可能使用C++的現代語法,眾所周知,出于兼容性的考慮,C++中很多語法都是合法的,但是隨著新版本的推出,有些語法可能是不推薦或者是需要避免使用的,所以本篇也盡可能采用推薦的語法形式(基于C++11或以上版本),這也是現代C++標題的含義,

采用現代語法有兩點好處,其一,現代語法可以編譯出更快更健壯的代碼,編譯器也是隨著語言的發展而發展的,現代語法可以在一定程度上幫助編譯器做更好的優化,其二,現代語法通常更簡潔,更直觀,也更統一,有助于增強可讀性和可維護性,
明確了這點后,讓我們一起踏入現代C++的大門吧,

型別系統

程式是一種計算工具,根據輸入,和預定義的計算方法,產生計算結果,當程式運行起來后,這三者都需要在記憶體中表示成合適的值才能讓程式正常作業,負責解釋的這套工具就是型別系統,數字,字串,鍵盤滑鼠事件等都是資料,而且在記憶體中實際存在的形式也是一樣的,但是按我們人類的眼光來看的話,對它們的處理是不一樣的,數字能進行加減乘除等算術運算,但是對字串進行算術運算就沒有意義,而鍵盤滑鼠的值通常只是讀取,不進行計算的,正是由于這些差異,編程語言的第一個任務就是需要定義一套型別系統,告訴計算機怎樣處理記憶體中的資料,
為了讓編程語言盡可能簡單,編程語言一般把型別系統分為兩步實作,一部分是編譯器,另一部分是型別,編譯器那部分負責將開發者的代碼解釋成合適的形式,以便可以高效,準確在記憶體中表示,型別部分則定義一些編譯器能處理的型別,以便開發者可以找到合適的資料來完成輸入輸出的表示和計算方法的描述,這兩者相輔相成,相互成就,
型別作為型別系統的重要表現形式,在編程語言中的重要性也就不言而喻了,如果把寫程式看成是搭積木的話,那么程式的積木就是型別系統,型別系統是開發者能操作的最小單位,它限制了開發者的操作規則,但是提供了無限的可能,C++有著比積木更靈活的型別系統,

型別

型別是編程語言的最小單位,任何一句代碼都是一種記憶體使用形式,
而談到C++的型別也就不得不談到它的三種型別表現形式——普通型別,指標,參考,它們是三種不同的記憶體使用和解釋形式,也是C++的最基礎的形式,和大部分編程語言不同,C++對內置型別沒有做特權處理,只要開發者愿意,所有的型別都可以有一致的語法形式(通過運算子多載),所以下面關于型別的舉例適合所有的型別,
普通型別就是沒有修飾的型別,如int,long,double等,它們是按值傳遞的,也就是賦值和函式傳參是拷貝一份值,對拷貝后的值進行操作,不會再影響到老值,

int a=1; //老值,存在地址1
int b=a; //新值,存在地址2
b=2; //改變新值,改變地址2
//此時a還是1,b變成了2

那假如我們需要修改老值呢,有兩種途徑,一種是指標,另一種則是參考,
指標是C/C++里面的魔法,一切皆可指標,指標包含兩個方面,一方面它是指一塊記憶體,另一方面它可以指允許對這塊記憶體進行的操作,指標的值是一塊記憶體地址,操作指標,操作的是它指向的那塊地址,

int a=1; //老值,存在地址1
int* b=&a; //&代表取地址,從右往左讀,取a的地址——地址1,存在地址2
*b=2; //*是解參考,意思是把存在地址2(b)的值取出來,并把那個地址(地址1)的值改成2
//此時a,*b變成了2


參考則是指標的改進版,參考能避免無效參考,不過參考不能重設,比指標缺少一定的靈活性,

int a=1; //老值,存在地址1
int& b=a; //&出現在變數宣告的位置,代表該變數是參考變數,參考變數必須在宣告時初始化
b=2; //可以像普通變數一樣操作參考變數,同時,對它的操作也會反應到原始物件上
//此時a,b變成了2

變數定義

型別僅僅是一種語法定義,而要真正使用這種定義,我們需要用型別來定義變數,即變數定義,
C++變數定義是以下形式:

type name[{initial_value}]

這里的關鍵在于typetype是型別和限定符的組合,看下面的例子:

int a; //普通整型
int* b; //型別是int和*的組合,組成了整型指標
const int* c; //從右往左讀,*是指標,const int是常量整型,組成了指向常量整型的指標型別
int *const d; //也是從右往左讀,const是常量,后面是指標,說明這個指標是常量指標,指向最左邊的int,組成常量指標指向整型
int& e=a; //型別是int和&的組合,組成了整型參考
constexpr int f=a+e; //constexpr代表這個變數需要在編譯期求值,并且不再可變,

以上,基本就是變數定義的所有形式了,型別確定了變數的基本屬性,而限定符限定了變數的使用范圍,
定義變數也是按照這個步驟進行,首先確定我們需要什么型別的變數,其次再進一步確定是否需要對這個變數添加限定,很多時候是需要的,可以按以下步驟來確定添加什么樣的限定符:

  1. 是個大物件,可以考慮把變數宣告成參考型別,通常參考型別是比指標型別更優的選擇,
  2. 大物件可能需要被重置,可以考慮宣告為指標,
  3. 只想要個常量,添加constexpr
  4. 只想讀這個變數,添加const

變數初始化

變數定義往往伴隨著初始化,這對于區域變數來說很重要,因為區域變數的初值是不確定的,在沒有對變數進行有效初始化前就使用變數,會導致不可控的問題,所以嚴格來說,前面的變數定義是不完全正確的,
C++11推出了全新的,統一的初始化方式,即在變數名后面跟著大括號,大括號里包著初始化的值,這種方式可以用在任何變數上,稱之為統一初始化,如:

int a{9527}; //普通型別
string b={"abc"}; //另一種寫法,等價但是不推薦
Student c{"張三","20220226",18}; //大括號中是建構式引數

當然,除了用型別名來定義變數外,還可以將定義和初始化合二為一,變成下面這種最簡潔的形式:

auto a={1}; //推導為整型
auto b=string{"abc"}; 
auto c=Student{"張三","20220226",18}

這里auto是讓編譯器自己確定型別的意思,上面這種寫法是完全利用了C++的型別推導,這也是好多現代語言推薦的形式,不過需要注意的是,使用型別推導后,=就不能省略了,
有了初始化的變數后,我們就可以用它們完成各種計算任務了,C++為開發者實作了很多內置的計算支持,如數字的加減乘除運算,陣列的索引,指標的操作等,還提供了分支ifswitch,回圈whilefor等陳述句,為我們提供了更靈活的操作,

函式

變數是編程語言中的最小單位,隨著業務的復雜度增加,有些時候中間計算會分散業務的邏輯,增加復雜度,為了更好地組織代碼,型別系統增加了 函式來解決這個問題,
函式也是型別,是一種復合型別,它的型別由引數串列,回傳值組合而成,也就是說兩個函式,假如引數串列和回傳值一樣,那么它們從編譯器的角度來看是等價的,當然光有它們還不夠,不然怎么能出現兩個引數串列和回傳值一樣的函式呢,一個完整的函式還需要有個函式體和函式名,所以函式一般是下面這種形式:

//常規函式形式
[constexpr] 回傳值 函式名(引數串列)[noexcept]{
    函式體
    }

//回傳值后置形式
auto 函式名(引數串列)->回傳值

當一個函式沒有函式體的時候,我們通常稱之為函式宣告,加上函式體就是一個函式定義,

void f(int); //函式宣告
void fun(int value){  //函式定義,因為有大括號代表的函式體
    
}

以上就是函式的基本框架,接下來我們分別來看一看組成它的各部分,
先說最簡單的函式名,它其實是函式這種型別的一個變數,這個變數的值表示從記憶體地址的某個位置開始的一段代碼塊,前面也說過之所以能出現兩個引數串列和回傳值都相同的函式,但是編譯器能識別,其主要功勞就在函式名上,所以函式名也和變數名一樣,是一種識別符號,那假如反過來,函式名相同,但是引數串列或者回傳值不同呢,這種情況有個專有名詞——函式多載,基于函式是復合型別的認識,它們中只要其中一種不同就算多載,另外,在C++11,還有一種沒有名字的函式,稱為lambda運算式,lambda運算式是一種類似于直接量的函式值,就像13,'c'這種,是一種不提前定義函式,直接在呼叫處定義并使用的函式形式,
引數串列是前面型別定義的升級款,所有前面說的關于變數定義的都適用于它,三種形式的變數定義,多個變數,變數初始化等,不過,它們都有了新名詞,引數串列的變數稱為形式引數,初始化稱為默認引數,同樣形參在實際使用的時候需要初始化,不過初始化來自呼叫方,形式引數沒有默認值就需要在呼叫的時候提供引數,有默認值的可以省略,

int plus(int a,int b=1){ //b是一個默認引數
    return a+b;
}

int main(void){
    int c=plus(1); //沒有提供b的值,所以b初始化為1,結果是2
    int d=plus(2,2); //a,b都初始化為2,結果是4
    //int f=plus(1,2,3); //plus只有兩個形參,也就是兩個變數,沒法保存三個值,所以編譯錯誤
    return 0;
}

和引數串列一樣,回傳值也是一個變數,這個變數會通過return陳述句回傳給呼叫者,所以從記憶體操作來看,它是一個賦值操作,

std::string msg(){
    std::string input;
    std::cin>>input;
    return input;
}

int main(void){
    auto a=msg();
    std::string b=msg();//msg回傳的input復制到了b中
    return 0;
}

遺憾的是C++只支持單回傳值,也就是一個函式呼叫最多只能回傳一個值,假如有多個值就只能以形參形式回傳了,這種方式對于函式呼叫就不是很友好,所以C++提出了新的解決思路,

隨著業務的復雜度再次增加,函式形參個數可能會增加,或者可能需要回傳多個值,然后在多個不同的函式間傳遞,這樣會導致資料容易錯亂,并且增加使用者的學習成本,
為了解決這些問題,工程師們提出了面向物件——多個資料打包的技術,表現在語言層面上,就是用類把一組操作和完成這組操作需要的資料打包在一起,資料作為類的屬性,操作作為類的方法,使用者通過方法操作內部資料,資料不再需要使用者自己傳遞,管理,這對于開發者無疑是大大簡化了操作,我們稱之為面向物件編程,而在函式間傳遞資料的方式稱為面向程序編程,這兩種方式底層邏輯其實是一致的,該傳遞的引數和函式呼叫一樣都不少,但是面向物件的區別是這些繁瑣、容易出錯的作業交給編譯器來做,開發者只需要按照面向物件的規則做好設計作業就好了,剩下的交給編譯器,至此,我們的型別系統又向上提升了一級,類不僅是多個型別的聚合體,還是多個函式的聚合體,是比函式更高級的抽象,
可以看下面面向程序編程和面向物件編程的代碼對比

struct Computer{
    bool booted;
    friend std::ostream& operator<<(std::ostream& os,const Computer & c){
        os<<"Computing";
        return os;
    }
};

void boot(Computer& c){
    c.booted=true;
    std::cout<<"Booting...";
}

void compute(const Computer& c){
    if(c.booted){
       std::cout<<"Compute with "<<c;
    }
}

void shutdown(Computer& c){
    c.booted=false;
    std::cout<<"Shutdown...";
}

int main(void){
    auto c=Computer();
    boot(c);
    compute(c);
    shutdown(c);
    return 0;                                                                                                         
}

面向程序最主要的表現就是,開發者需要在函式間傳遞資料,并維護資料狀態,上面例子中的資料是c

struct Computer{
    bool booted;
    
    friend std::ostream& operator<<(std::ostream& os,const Computer & c){
        os<<"Computing";
        return os;
    }

    void boot(){
        booted=true;
        std::cout<<"Booting...";
    }

    void compute(){
        if(booted){
            std::cout<<"Compute with "<<this;
        }
    }

    void shutdown(){
        booted=false;
        std::cout<<"Shutdown...";
    }
};

int main(void){
    auto c=Computer();
    c.boot();
    c.compute();
    c.shutdown();
    return 0;
}

可以看出面向物件的代碼最主要的變化是,方法的引數變少了,但是可以在方法里面直接訪問到類定義的資料,另一個變化發生在呼叫端,呼叫端是用資料呼叫方法,而不是往方法里面傳遞資料,這也是面向物件的本質——以資料為中心,
當然,類的封裝功能只是類功能的一小部分,后面我們會涉及到更多的類知識,作為初學者,我們了解到這一步就能讀懂大部分代碼了,

總結

型別系統是一門語言的基本構成部分,它支撐著整個系統的高級功能,很多高級特性都是在型別系統的基礎上演化而來的,所以學習語言的型別系統有個從低到高,又從高到低的程序,從最基礎的型別開始,學習如何從低級型別構筑出高級型別,然后站在高級型別的高度上,審視高級型別是怎樣由低級型別構筑的,這一上一下,一高一低基本上就能把語言的大部分特性了解清楚了,
低級型別更偏向于讓編譯器更好地作業,高級型別偏向于讓開發者更好地作業,C++從普通型別,函式,類提供了各個層級的支持,讓開發者有更多自由的選擇,當然也就增加了開發者的學習難度,但是開發者并不是都需要所有選擇的,所以我覺得正確的學習應該是以專案規模為指導的,一些專案,完全用不到面向物件,就可以把精力放在打造好用的函式集上,而有的專案,面向物件是很好的選擇,就需要在類上花費時間,回到開頭的積木例子,選用什么積木完全看我們想搭什么模型,要是沒有合適的積木,我們可以自己創造,這就是C++的迷人之處,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555041.html

標籤:C++

上一篇:主席樹學習筆記

下一篇:返回列表

標籤雲
其他(160896) Python(38222) JavaScript(25493) Java(18225) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7248) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5874) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4591) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1964) Web開發(1951) C++(1935) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 現代C++學習指南-型別系統

    > 在前一篇,我們提供了一個方向性的指南,但是學什么,怎么學卻沒有詳細展開。本篇將在前文的基礎上,著重介紹下怎樣學習C++的型別系統。 ### 寫在前面 在進入型別系統之前,我們應該先達成一項共識——盡可能使用C++的現代語法。眾所周知,出于兼容性的考慮,C++中很多語法都是合法的。但是隨著新版本的 ......

    uj5u.com 2023-06-14 07:44:04 more
  • 主席樹學習筆記

    # 什么是主席樹 主席樹這個名字看上去很高級,其實不然,它還有另一個名字——可持久化線段樹。 ## 什么是可持久化 可持久化顧名思義就是它可以變得~~**持久**~~,就是我們對他不斷進行單點修改后,突然查詢它的某一個歷史版本,這就叫可持久化。 # 引入例題 [洛谷3919:可持久化陣列](http ......

    uj5u.com 2023-06-14 07:43:57 more
  • CoaXpress downlink資料決議方法

    ## 什么是downlink資料 downlink指的是相機傳輸到host采集卡的高速鏈路,其中包含了如下型別的資料: 1、Stream Data 2、Trigger Ack, Trigger; 3、Ack (reply data); 4、Event, Heartbeat ![](https://i ......

    uj5u.com 2023-06-13 07:48:48 more
  • Servlet p1 Servlet的實作

    學習課程: 【這可能是B站講的最好的Servlet教程,5小時打通Servlet全套教程丨2022最新版,輕松掌握servlet基礎+案例實操】 https://www.bilibili.com/video/BV1Kr4y1V7ZE/?share_source=copy_web&vd_source= ......

    uj5u.com 2023-06-13 07:48:35 more
  • C++面試八股文:了解位運算嗎?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第12面: > 面試官:了解位運算嗎? > > 二師兄:了解一些。(我很熟悉) > > 面試官:請列舉以下有哪些位運算? > > 二師兄:按位與(`&`)、按位或(`|`)、按位異或(`^`),按位取反(`~`)、左移(`>`)。 > > 面試官:好 ......

    uj5u.com 2023-06-13 07:48:29 more
  • Object原始碼閱讀

    # Object原始碼閱讀 > native:本地堆疊方法,使用C語言中實作的方法。 ```java package java.lang; public class Object { //注冊本地方法 private static native void registerNatives(); stati ......

    uj5u.com 2023-06-13 07:48:25 more
  • ArrayList 底層結構及原始碼分析

    ArrayList 實作了 List 介面。它可以存盤包括 null 的任何型別的物件,允許重復元素。ArrayList 在內部使用一個陣列來存盤元素,當元素數量超過陣列容量時,ArrayList 會自動重新分配更大的內部陣列,并且將現有元素復制到新陣列中。ArrayList 基本等同于 Vecto... ......

    uj5u.com 2023-06-13 07:48:21 more
  • 快取雪崩、快取擊穿、快取穿透原因及解決辦法

    快取雪崩是指在快取中的大量資料在同一個時刻全部過期,導致原本這些可以由快取中間件處理的高并發請求,一下子全部打到資料庫,導致資料庫服務器崩潰的一種現象。那么出現快取雪崩的原因可以有①:快取中間件宕機。②:快取中大部分key都設定了相同的時間,導致這些key在同一時間內全部失效。解決的方法: ①:可以 ......

    uj5u.com 2023-06-13 07:48:17 more
  • 翻車了,被讀者找出 BUG

    大家好呀,我是小樓。 本文是上篇文章[《使用增強版 singleflight 合并事件推送,效果炸裂!》](https://mp.weixin.qq.com/s/PFojA2DWJF7ry9Rdu8znyA)的續集,沒看過前文必須要先看完才能看本文,實在不想看,拉到文章末尾,給我點個贊再退出吧~Do ......

    uj5u.com 2023-06-13 07:48:00 more
  • 【python基礎】復雜資料型別-字典(遍歷)

    一個字典可能只包含幾個鍵值對,也可能包含數百萬個鍵值對,所以Python支持字典遍歷。字典可用于以各種方式存盤資訊,因此有多種遍歷字典的方式:可遍歷字典的所有鍵值對、鍵或值。 # 1.遍歷所有的鍵值對 其語法格式: ![image](https://img2023.cnblogs.com/blog/ ......

    uj5u.com 2023-06-13 07:47:39 more