當(dāng)前位置:首頁 > 公眾號(hào)精選 > CPP開發(fā)者
[導(dǎo)讀]我們說的ModernC,一般指的是C11及以后的標(biāo)準(zhǔn),從C11開始,ModernC引入了大量的實(shí)用的特性,主要是兩大方面,學(xué)習(xí)的時(shí)候也可以從這兩大方面學(xué)習(xí):增強(qiáng)或者改善的語法特性;新增的或者改善的STL庫(kù)。我們來看幾個(gè)具體的案例:案例1:統(tǒng)一的類成員初始化語法與std::init...

我們說的 Modern C ,一般指的是 C 11 及以后的標(biāo)準(zhǔn),從 C 11 開始,Modern C 引入了大量的實(shí)用的特性,主要是兩大方面,學(xué)習(xí)的時(shí)候也可以從這兩大方面學(xué)習(xí):

  1. 增強(qiáng)或者改善的語法特性;

  2. 新增的或者改善的 STL 庫(kù)。

我們來看幾個(gè)具體的案例:

案例 1:統(tǒng)一的類成員初始化語法與 std::initializer_list

在 C 98/03 中,假設(shè)我們要初始化一個(gè)類數(shù)組類型的成員(例如常用的清零操作),我們需要這么寫:

class?A
?{
?public:
?????A()
?????{
?????????//初始化arr
?????????arr[0]?=?0;
?????????arr[1]?=?0;
?????????arr[2]?=?0;
?????????arr[3]?=?0;
?????}
?
?public:
?????int?arr[4];
?};

假設(shè)數(shù)組 arr 較長(zhǎng),我們可以使用循環(huán)或者借助 memset 函數(shù)去初始化,代碼如下:

??class?A
?{
?public:
?????A()
?????{
?????????//使用循環(huán)初始化arr
?????????for?(int?i?=?0;?i??????????????arr[i]?=?0;
?????}
?
?public:
?????int?arr[4];
?};
?
?class?A
?{
?public:
?????A()
?????{
?????????//使用memset初始化arr
?????????memset(arr,?0,?sizeof(arr));
?????}
?
?public:
?????int?arr[4];
?};
但是,我們知道,在 C 98/08 中我們可以直接通過賦值操作來初始化一個(gè)數(shù)組的:

?int?arr[4]?=?{?0?};
但是對(duì)于作為類的成員變量的數(shù)組元素,C 98/03 是不允許我們這么做的。到 C 11 中全部放開并統(tǒng)一了,在 C 11 中我們也可以使用這樣的語法是初始化數(shù)組:

?class?A
?{
?public:
?????//在?C 11中可以使用大括號(hào)語法初始化數(shù)組類型的成員變量
?????A()?:?arr{0}
?????{
?????}
?
?public:
?????int?arr[4];
?};
如果你有興趣,我們可以更進(jìn)一步:在 C 98/03 標(biāo)準(zhǔn)中,對(duì)類的成員必須使用 static const 修飾,而且類型必須是整型 (包括 bool、 char、 int、 long 等),這樣才能使用這種初始化語法:

?//C 98/03?在類定義處初始化成員變量
?class?A
?{
?public:
?????//T?的類型必須是整型,且必須使用?static?const?修飾
?????static?const?T?t?=?某個(gè)整型值;
?};
在 C 11 標(biāo)準(zhǔn)中就沒有這種限制了,我們可以使用花括號(hào)(即{})對(duì)任意類型的變 量進(jìn)行初始化,而且不用是 static 類型:

?//C ?11?在類定義處初始化成員變量
?class?A
?{
?public:
?????//有沒有一種Java初始化類成員變量的即視感^?_?^
?????bool?ma{true};
?????int?mb{2019};
?????std::string?mc{"helloworld"};
?};
當(dāng)然,在實(shí)際開發(fā)中,建議還是將這些成員變量的初始化統(tǒng)一寫到構(gòu)造函數(shù)的初始化列表中,方便閱讀和維護(hù)代碼。

案例 2:注解標(biāo)簽

C 14 引入了?[[deprecated]]?標(biāo)簽來表示一個(gè)函數(shù)或者類型等已被棄用,在使用這些被棄用的函數(shù)或者類型并編譯時(shí), 編譯器會(huì)給出相應(yīng)的警告, 有的編譯器直接生成編譯錯(cuò)誤:

?[[deprecated]]?void?funcX();
這個(gè)標(biāo)簽在實(shí)際開發(fā)中非常有用,尤其在設(shè)計(jì)一些庫(kù)代碼時(shí),如果庫(kù)作者希望某個(gè)函數(shù)或者類型不想再被用戶使用,則可以使用該標(biāo)注標(biāo)記。當(dāng)然,我們也可以使用如下語法給出編譯時(shí)的具體警告或者出錯(cuò)信息:

?[[deprecated("use?funY?instead")]]?void?funcX();
有如下代碼:

?#include?
?[[deprecated("use?funcY?instead")]]?void?funcX()
?{
?????//實(shí)現(xiàn)省略
?}
?
?int?main()
?{
?????funcX();
?????return?0;
?}
若在 main 函數(shù)中調(diào)用被標(biāo)記為 deprecated 的函數(shù) funcX,則在 gcc/g 7.3 中編譯時(shí)會(huì)得到如下警告信息:

?[root@myaliyun?testmybook]#?g ?-g?-o?test_attributes?test_attributes.cpp
?test_attributes.cpp:?In?function?‘int?main()’:
?test_attributes.cpp:10:11:?warning:?‘void?funcX()’?is?deprecated:?use?funcY?instead
?[-Wdeprecated-declarations]
?funcX();
?^
?test_attributes.cpp:3:42:?note:?declared?here
?[[deprecated("use?funcY?instead")]]?void?funcX()
Java 開發(fā)者對(duì)這個(gè)標(biāo)注應(yīng)該再熟悉不過了。在 Java 中使用@Deprecated 標(biāo)注可以達(dá)到同樣的效果,這大概是 C 標(biāo)準(zhǔn)委員“拖欠”廣大 C 開發(fā)者太久的一個(gè)特性吧。

C 17 提供了三個(gè)實(shí)用注解:?[[fallthrough]]、?[[nodiscard]]?和?[[maybe_unused]],這里 逐一介紹它們的用法。[[fallthrough]]?用于 switch-case 語句中,在某個(gè) case 分支執(zhí)行完畢后如果沒有 break 語句,則編譯器可能會(huì)給出一條警告。但有時(shí)這可能是開發(fā)者有意為之的。為了讓編譯器明確知道開發(fā)者的意圖,可以在需要某個(gè) case 分支被“貫穿”的地方(上一個(gè) case 沒有break 語句)顯式設(shè)置 [[fallthrough]] 標(biāo)記。代碼示例如下:

?switch?(type)
?{
?case?1:
?????func1();
?????//這個(gè)位置缺少?break?語句,且沒有?fallthrough?標(biāo)注,
?????//可能是一個(gè)邏輯錯(cuò)誤,在編譯時(shí)編譯器可能會(huì)給出警告,以提醒修改
?
?case?2:
?????func2();
?????//這里也缺少?break?語句,但是使用了?fallthrough?標(biāo)注,
?????//說明是開發(fā)者有意為之的,編譯器不會(huì)給出任何警告
?????[[fallthrough]];
?
?case?3:
?????func3();
?}
注意:在 gcc/g 中, [[fallthrough]] 后面的分號(hào)不是必需的,在 Visual Studio 中必須加上分號(hào),否則無法編譯通過。

熟悉 Golang 的讀者,可能對(duì) fallthrough 這一語法特性非常熟悉, Golang 中在 switch-case 后加上 fallthrough,是一個(gè)常用的告訴編譯器意圖的語法規(guī)則。代碼示例如下:

?//以下是?Golang?語法
?s?:=?"abcd"
?switch?s[3]?{
?????case?'a':
?????????fmt.Println("The?integer?was?<=?4")
?????????fallthrough
?
?????case?'b':
?????????fmt.Println("The?integer?was?<=?5")
?????????fallthrough
?
?????case?'c':
?????????fmt.Println("The?integer?was?<=?6")
?
?????default:
?????????fmt.Println("default?case")
?}
[[nodiscard]]?一般用于修飾函數(shù),告訴函數(shù)調(diào)用者必須關(guān)注該函數(shù)的返回值(即不能丟棄該函數(shù)的返回值)。如果函數(shù)調(diào)用者未將該函數(shù)的返回值賦值給一個(gè)變量,則編譯器會(huì)給出一個(gè)警告。例如,假設(shè)有一個(gè)網(wǎng)絡(luò)連接函數(shù) connect,我們通過返回值明確說明了連接是否建立成功,則為了防止調(diào)用者在使用時(shí)直接將該值丟棄,我們可以將該函數(shù)使用?[[nodiscard]]?標(biāo)記:

?[[nodiscard]]?int?connect(const?char*?address,?short?port)
?{
?????//實(shí)現(xiàn)省略
?}
?
?int?main()
?{
?????//忽略了connect函數(shù)的返回值,編譯器會(huì)給出一個(gè)警告
?????connect("127.0.0.1",?8888);
?????return?0;
?}

在 C 20 中,對(duì)于諸如 operator new()、 std::allocate()等庫(kù)函數(shù)均使用了 [[nodiscard]] 進(jìn)行標(biāo)記,以強(qiáng)調(diào)必須使用這些函數(shù)的返回值。再來看另外一個(gè)標(biāo)記。在通常情況下,編譯器會(huì)對(duì)程序代碼中未使用的函數(shù)或變量給出警告,另一些編譯器干脆不允許通過編譯。在 C 17 之前,程序員為了消除這些未使用的變量帶來的編譯警告或者錯(cuò)誤,要么修改編譯器的警告選項(xiàng)設(shè)置,要么定義一個(gè)類似于 UNREFERENCED_PARAMETER 的宏來顯式調(diào)用這些未使用的變量一次,以消除編譯警告或錯(cuò)誤:

?#define?UNREFERENCED_PARAMETER(x)?x
?
?int?APIENTRY?wWinMain(HINSTANCE?hInstance,?HINSTANCE?hPrevInstance,?LPWSTR?lpCmdLine,?int?nCmdShow)
?{
?????//C 17之前為了消除編譯器對(duì)未使用的變量hPrevInstance、lpCmdLine給出的警告,我們可以這么做
?????UNREFERENCED_PARAMETER(hPrevInstance);
?????UNREFERENCED_PARAMETER(lpCmdLine);
?????//無關(guān)代碼省略
?}

以上代碼節(jié)選自一個(gè)標(biāo)準(zhǔn) Win32 程序的結(jié)構(gòu),其中的函數(shù)參數(shù) hPrevInstance 和 lpCmdLine 一般不會(huì)被用到,編譯器會(huì)給出警告。為了消除這類警告,這里定義了一個(gè)宏 UNREFERENCED_PARAMETER 并進(jìn)行調(diào)用,造成這兩個(gè)參數(shù)被使用的假象。C 17 有了 [[maybe_unused]] 注解之后,我們就再也不需要這類宏來“欺騙”編譯器了。以上代碼使用該注解后可以修改如下:

?int?APIENTRY?wWinMain(HINSTANCE?hInstance,
???????????????????????[[maybe_unused]]?HINSTANCE?hPrevInstance,
???????????????????????[[maybe_unused]]?LPWSTR?lpCmdLine,
???????????????????????int?nCmdShow)
?{
?????//無關(guān)代碼省略
?}

案例 3:final、 override 關(guān)鍵字和 =default、 =delete 語法

3.1 final 關(guān)鍵字

在 C 11 之前,我們沒有特別好的方法阻止一個(gè)類被其他類繼承,到了 C 11 有了 final 關(guān)鍵字我們就可以做到了。final 關(guān)鍵字修飾一個(gè)類,這個(gè)類將不允許被繼承,這在其他語言(如 Java)中早就實(shí)現(xiàn)了。在 C 11 中, final 關(guān)鍵字要寫在類名的后面,這在其他語言中是寫在 class 關(guān)鍵字前面的。示例如下:

?class?A?final
?{
?};
?
?class?B?:?A
?{
?};
由于類 A 被聲明成 final, B 繼承 A, 所以編譯器會(huì)報(bào)如下錯(cuò)誤提示類 A 不能被繼承:

?error?C3246:?'B'?:?cannot?inherit?from?'A'?as?it?has?been?declared?as?'final'

3.2 override 關(guān)鍵字

C 98/03 語法規(guī)定,在父類中加了 virtual 關(guān)鍵字的方法可以被子類重寫,子類重寫該方法時(shí)可以加或不加 virtual 關(guān)鍵字,例如下面這樣:

?class?A
?{
?protected:
?????virtual?void?func(int?a,?int?b)
?????{
?????}
?};
?
?class?B?:?A
?{
?protected:
?????virtual?void?func(int?a,?int?b)
?????{
?????}
?};
?
?class?C?:?B
?{
?protected:
?????void?func(int?a,?int?b)
?????{
?????}
?};
這種寬松的規(guī)定可能會(huì)帶來以下兩個(gè)問題。

  • 當(dāng)我們閱讀代碼時(shí),無論子類重寫的方法是否添加了 virtual 關(guān)鍵字,我們都無法 直觀地確定該方法是否是重寫的父類方法。

  • 如果我們?cè)谧宇愔胁恍⌒膶戝e(cuò)了需要重寫的方法的函數(shù)簽名(可能是參數(shù)類型、 個(gè)數(shù)或返回值類型),這個(gè)方法就會(huì)變成一個(gè)獨(dú)立的方法,這可能會(huì)違背我們重寫 父類某個(gè)方法的初衷,而編譯器在編譯時(shí)并不會(huì)檢查到這個(gè)錯(cuò)誤。

為了解決以上兩個(gè)問題, C 11 引進(jìn)了 override 關(guān)鍵字,其實(shí) override 關(guān)鍵字并不是新語法,在 Java 等其他編程語言中早就支持。類方法被 override 關(guān)鍵字修飾,表明該方法重寫了父類的同名方法,加了該關(guān)鍵字后,編譯器會(huì)在編譯階段做相應(yīng)的檢查,如果其父類不存在相同簽名格式的類方法,編譯器就會(huì)給出相應(yīng)的錯(cuò)誤提示。情形一,父類不存在,子類標(biāo)記了 override 的方法:

?class?A
?{
?};
?
?class?B?:?A
?{
?protected:
?????void?func(int?k,?int?d)?override
?????{
?????}
?};
由于在父類 A 中沒有 func 方法,所以編譯器會(huì)提示錯(cuò)誤:

?error?C3668:?'B::func'?:?method?with?override?specifier?'override'?did?not?override
?any?base?class?methods
情形二,父類存在,子類標(biāo)記了 override 的方法,但函數(shù)簽名不一致:

?class?A
?{
?protected:
?????virtual?int?func(int?k,?int?d)
?????{
?????????return?0;
?????}
?};
?
?class?B?:?A
?{
?protected:
?????virtual?void?func(int?k,?int?d)?override
?????{
?????}
?};
上述代碼編譯器會(huì)報(bào)同樣的錯(cuò)誤。正確的代碼如下:

?class?A
?{
?protected:
?????virtual?void?func(int?k,?int?d)
?????{
?????}
?};
?
?class?B?:?A
?{
?protected:
?????virtual?void?func(int?k,?int?d)?override
?????{
?????}
?};

3.3 default 語法

如果一個(gè) C 類沒有顯式給出構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、 operator= 這幾類函數(shù)的實(shí)現(xiàn),則在需要它們時(shí),編譯器會(huì)自動(dòng)生成;或者,在給出這些函數(shù)的聲明時(shí),如果沒有給出其實(shí)現(xiàn),則編譯器在鏈接時(shí)會(huì)報(bào)錯(cuò)。如果使用=default 標(biāo)記這類函數(shù),則編譯器會(huì)給出默認(rèn)的實(shí)現(xiàn)。來看一個(gè)例子:

?class?A
?{
?};
?
?int?main()
?{
?????A?a;
?????return?0;
?}
這樣的代碼是可以編譯通過的,因?yàn)榫幾g器默認(rèn)生成 A 的一個(gè)無參構(gòu)造函數(shù),假設(shè)我們現(xiàn)在向 A 提供一個(gè)有參構(gòu)造函數(shù):

?class?A
?{
?public:
?????A(int?i)
?????{
?????}
?};
?
?int?main()
?{
?????A?a;
?????return?0;
?}
這時(shí),編譯器就不會(huì)自動(dòng)生成默認(rèn)的無參構(gòu)造函數(shù)了,這段代碼會(huì)編譯出錯(cuò),提示 A 沒有合適的無參構(gòu)造函數(shù):

?error?C2512:?'A'?:?no?appropriate?default?constructor?available
我們這時(shí)可以手動(dòng)為 A 加上無參構(gòu)造函數(shù), 也可以使用=default 語法強(qiáng)行讓編譯器自己生成:

class?A
?{
?public:
?????A()?=?default;
?????A(int?i)
?????{
?????}
?};
?
?int?main()
?{
?????A?a;
?????return?0;
?}
=default 最大的作用可能是在開發(fā)中簡(jiǎn)化了構(gòu)造函數(shù)中沒有實(shí)際初始化代碼的寫法,尤其是聲明和實(shí)現(xiàn)分別屬于.h 和.cpp 文件。例如,對(duì)于類 A,其頭文件為 a.h,其實(shí)現(xiàn)文件為 a.cpp,則正常情況下我們需要在 a.cpp 文件中寫其構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)(可能沒有實(shí)際的構(gòu)造和析構(gòu)邏輯):

//a.h
?class?A
?{
?public:
?????A();
?????~A();
?};
?
?//a.cpp
?#include?"a.h"
?
?A::A()
?{
?}
?
?A::~A()
?{
?}
可以發(fā)現(xiàn),即使在 A 的構(gòu)造函數(shù)和析構(gòu)函數(shù)中什么邏輯也沒有,我們還是不得不在 a.cpp 中寫上構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)。有了=default 關(guān)鍵字,我們就可以在 a.h 中直接寫成:

??//a.h
?class?A
?{
?public:
?????A()?=?default;
?????~A()?=?default;
?};
?
?//a.cpp
?#include?"a.h"
?//在?cpp?文件中就不用再寫?A?的構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)了

3.4 =delete 語法

既然有強(qiáng)制讓編譯器生成構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、 operator=的語法,那么也應(yīng)該有禁止編譯器生成這些函數(shù)的語法,沒錯(cuò),就是 =delete。在 C 98/03 規(guī)范中, 如果我們想讓一個(gè)類不能被拷貝(即不能調(diào)用其拷貝構(gòu)造函數(shù)),則可以將其拷貝構(gòu)造函數(shù)和 operator=函數(shù)定義成 private 的:

class?A
?{
?public:
?????A()?=?default;
?????~A()?=?default;
?
?private:
?????A(const?A
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉