C++11:系統類型增強

C++11:系統類型增強

    • 強枚舉類型
      • 作用域限定
      • 隱式類型轉換
      • 指定類型
      • 前置聲明
    • 類型別名 using
      • 模板別名
      • 復雜指針別名
    • auto
      • 限制性 auto
      • 注意事項
    • nullptr
    • decltype


強枚舉類型

在C++98的枚舉設計中,存在很多缺陷,為此C++11推出了強枚舉來代替舊版的枚舉,提供更加安全可靠的枚舉類型。

強枚舉的語法如下:

enum class e : type
{val1 = 1,val2 = 2
}

此處定義了一個名為e的強枚舉,只需要在enum后面加一個calss關鍵字即可,此處的: type用于指定底層類型,后面講解,可以省略。


作用域限定

舊版本的 enum 會直接把枚舉內部的值放到外層作用域,如果多個枚舉有一樣的值,或者在相同作用域定義了與枚舉同名的變量,那么就會導致命名沖突:

enum e1
{a = 1
};enum e2
{a = 2
};

以上代碼中,e1::ae2::a發生了沖突,全局作用域存在兩個叫做a的變量。

在強枚舉中,每個枚舉自成一個作用域,不會污染外部變量,相互之間也不會沖突

enum class e1
{a = 1
};enum class e2
{a = 2
};

以上代碼就不會報錯了,因為e1e2都是強類型枚舉,各自作用域獨立,枚舉也不會放到全局作用域。

由于枚舉值不在全局作用域了,那么也就不能直接訪問a了,必須通過域限定符來訪問:e1::ae2::a


隱式類型轉換

舊版本的 enum 可以隨意的隱式轉換成其他類型,這是從C語言繼承下來的極其不安全的特性,它可以轉化為任意整形家族,甚至charboolfloat

enum type_old
{a = 128
};int main()
{int x = a;signed char c = a;long long ll = a;float f = a;return 0;
}

以上代碼是合法的(在 vs2022g++13.3編譯均通過),這非常不安全,例如此處的 a = 128,它其實是超出了 signed char 的范圍的,但是他不僅沒有報錯,而且還隱式的發生了截斷,此時如果再std::cout << (int)c,你會得到-128這個值。

強枚舉有非常嚴格的類型限定,他不能隱式轉化為強枚舉之外的類型。

enum class type_new
{a = 1
};int main()
{// err: 不允許隱式轉化int x = type_new::a;type_new e1 = 1;// success: 顯示類型轉換int y = static_cast<int>(type_new::a);type_new e2 = (type_new)1;// success: C風格顯示類型轉換int z = (int)type_new::a;type_new e3 = static_cast<type_new>(1);return 0;
}

使用強枚舉后,既不允許從其他類型隱式轉為強枚舉(哪怕這個值在枚舉中存在),也不允許強枚舉隱式轉為其他類型。如果需要轉換,那么必須用static_cast或者C語言風格的顯式類型轉換


指定類型

在舊版enum中,其類型往往是不確定的,這可能隨著編譯器不同而變化,一般為int。就算你傳入一個long long類型,最后也會被轉回int

這是因為 在C++標準中規定:編譯器指定的枚舉底層類型,只要可以存儲所有的枚舉值即可

例如你的枚舉值是1 2 3,那么就有可能用short這樣的來存儲,如果再大一點就可能是int。不過就算C++標準這么規定,其實大部分主流編譯器都固定使用int,不論你數據范圍是多少,例如MSVCgcc

enum type_old
{a = INT_MAX + 1ll // 此處 1ll 表示 long long 字面量,后綴ll不可省略
};int main()
{std::cout << a << std::endl;return 0;
}

以上代碼中,type_old::a這個枚舉接受了一個大于int最大值的long long字面量,程序正常運行,最后輸出結果為:-2147483648也就是int的最小值,這是因為發生了從long long -> int的隱式截斷,你無法在 C++98 的枚舉中存儲大于int范圍的值。相應的,當你存儲的數據范圍比較小,比如枚舉值都是0 ~ 127,你也不能用一個字節來存,必須用int,(或者你可以祈禱某種編譯器檢測到了你的數據范圍比較小,給你改用小范圍的類型來存儲)。

在強枚舉中,可以指定枚舉值的底層類型:

enum class type_new : long long
{a = INT_MAX + 1ll
};int main()
{std::cout << static_cast<long long>(type_new::a) << std::endl;return 0;
}

代碼中: long long指定了枚舉的底層使用long long存儲,最后程序輸出:2147483648,也就是INT_MAX + 1

要注意的是,如果你在C++11環境運行以下代碼,也是合法的:

enum type_new : long long // 此處把 class 刪掉了,是普通枚舉
{a = INT_MAX + 1ll
};

因為C++11在更新強枚舉的同時,對舊版枚舉也做了優化,允許普通枚舉也指定底層類型!但是普通枚舉作用域,隱式轉化等特性依然保留。

前置聲明

舊版枚舉是不允許前置聲明的,例如以下代碼會報錯:

enum type_old;void func(type_old e)
{
}enum type_old
{a = 128
};int main()
{func(a);return 0;
}

以上代碼在C++98環境運行會報錯,剛才說過,枚舉底層使用什么類型是不確定的,在不同編譯器可能不同,這就導致func函數的第一個參數type_old聲明后,無法確定其內存大小,從而編譯失敗

在C++11中,其實強枚舉直接這么寫也會報錯,例如把上面的聲明改成:enum class type_new這樣的強枚舉,還是會報錯。

根本原因是無法確定枚舉的底層變量,從而無法得知大小。因為C++標準明確說了編譯器可以自己來指定底層變量,在枚舉值還沒有定義之前,編譯器根本就無法推斷用什么類型來存,也就不知道這個枚舉的底層類型。

此時上一個特性就派上用場了,用戶可以自己顯式指定枚舉底層類型,那編譯器不就明確了枚舉的大小了么

而剛才又說過,C++11對普通枚舉和強枚舉都支持指定底層類型,那么代碼就可以這樣改寫:

enum type_old : int;
enum type_new : int;void func(type_old e1, type_new e2)
{
}enum type_old : int
{a = 128
};enum type_new : int
{a = 128
};

現在不論強枚舉還是普通枚舉,都可以提前聲明了,因為通過: int明確指定了底層使用int存儲,那么func的兩個參數就知道自己要給參數預留多少空間,此時前置聲明就有用了。


類型別名 using

在 C++11 之前,typedef 是定義類型別名的唯一方式,但它存在語法局限,尤其是在模板編程中不夠靈活。

在C++11之前,using主要用于展開命名空間,或者聲明其它命名空間內部的變量。

C++11 給 using 添加了類型別名的功能,不僅替代了 typedef,還提供了更強大的功能,特別是在模板別名和復雜類型表達式簡化方面。

語法如下:

using new_name = old_name;

模板別名

如果想給一系列模板類取別名,例如希望簡化list的迭代器類型std::list<T>::iterator變成list_it<T>,使用typedef是無法做到的,而using就可以配合模板使用:

template<typename T>
using list_it = std::list<T>::iterator;int main()
{list_it<int> p; // successreturn 0;
}

這是typedef無法做到的,也是using最大的優勢。

復雜指針別名

在使用typedef給函數指針或者數組指針取別名的時候,語法會很復雜,而且可讀性很差,例如:

// 把 void(*)(int, int) 類型的函數指針取別名為 func_ptr
typedef void(*func_ptr)(int, int);// 把 int(*)[] 類型的數組指針取別名為 arr_ptr
typedef int(*arr_ptr)[];

這是因為在typedef中,要求新名稱必須寫在*后面,這樣編譯器才知道這個新的名稱是一個指針,這就導致可讀性很差,例如一個帶有回調函數的函數指針取別名:

typedef void(*func_ptr)(void(*)(void), int);

此處func_ptr的類型是void(*)(void(*)(void), int),如果C語言基礎差一些,這段代碼要琢磨一點時間。

using中,無需把新名稱寫到*后面,就是固定寫在=左邊,右邊就是原始類型,例如:

// 把 void(*)(int, int) 類型的函數指針取別名為 func_ptr
using func_ptr = void(*)(int, int);// 把 int(*)[] 類型的數組指針取別名為 arr_ptr
typedef arr_ptr = int(*)[];

這樣語義就明確很多了,程序員一下就看出來新名稱是什么,原始類型是什么。


auto

在C++中,auto關鍵字可以用來自動推斷變量的類型,它在編譯時會根據初始化表達式的類型來確定變量的類型。

使用auto的主要好處是可以簡化代碼并提高可讀性。它可以減少手動指定變量類型的工作,并且可以防止類型錯誤。相比于顯式指定變量類型,使用auto可以讓代碼更加靈活和易于維護。

  1. 自動推斷基本類型變量的類型
auto age = 25; // 推斷age為int類型
auto salary = 5000.50; // 推斷salary為double類型

auto也可以自動推斷指針的類型,比如這樣:

int x = 10;
auto y = &x;

此時y的類型自動判別為int*

實際上不建議這么做,C++中最好還是明確每個變量的類型,對于這種簡單的類型還是不要用auto的好。

  1. 自動推斷非常長的類型
std::vector<int> numbers = {1, 2, 3, 4, 5};for (auto it = numbers.begin(); it != numbers.end(); ++it) 
{std::cout << *it << " ";
}

有的時候獲得變量的類型會需要很長的代碼,使用auto可以縮短變量類型的長度,這是C++推薦的做法,當然用戶心里還是要清楚auto最后接收到了什么類型,只是懶得寫出來而已。

  1. 接受不確定的類型
auto add = [](int a, int b){ return a + b; };

lambda表達式中,返回值類型是不確定的,必須用auto接收。這是因為lambda設計出來,就是只用一次就不再用的匿名函數,那么用戶就無需知道這個表達式的類型,因為拿到類型就可以再去定義相同的函數了,那就不要用lambda,直接寫一個函數/仿函數就行了。因此C++中lambda的類型是隨機生成的,必須用auto才能接收。


限制性 auto

除去基本的類型推斷,auto可以限制接收到的類型必須是指針或引用。

看到一段代碼:

int x = 10;auto* a1 = x;
auto* a2 = &x;
auto a3 = &x;

auto* a1 = x;中,x的類型是int,那么auto本應將其值判別為int,但是由于auto**限制了,此時auto必須得到一個指針,所以編譯器會報錯;而auto* a2 = &x;得到的就是指針,此時代碼不會報錯,可以正常識別為int*

在本質上auto* a2 = &x;auto a3 = &x;的結果是沒有區別的,只是auto*要求得到的必須是一個指針類型,而auto不限制其類型。

同理auto&也可以限定類型必須是一個引用,否則會報錯。


注意事項

  1. auto不能作為函數的參數
  2. auto不能用于聲明數組

比如以下代碼:

int arr1[] = {1, 3, 5, 7, 9};
auto arr2[] = {1, 3, 5, 7, 9};

此時第二條代碼就會報錯,因為其用auto類型定義了一個數組。

  1. 在同一行定義多個變量時,如果將auto作為其類型,必須一整行都是同一個類型的變量。

比如以下代碼:

int x = 1, y = 2;
auto a = 3, b = 4;
auto c = 5, d = 6.0;

以上代碼中,auto a = 3, b = 4;是合法的,因為一行內都是int類型。

但是auto c = 5, d = 6.0;是非法的,因為同一行內有不同類型,會報錯。


nullptr

在C++11后,推出了新的空指針nullptr,明明已經有NULL了,為啥還需要nullptr?

NULL在C語言中,表示的是((void*)0),也就是被強制轉為void*類型的0。但是在C++中,NULL就是整數0

比如可以用剛才學的typeid驗證一下:

cout << typeid(NULL).name() << endl;

輸出結果為:int,這下就石錘了NULL在C++中就是int

這會導致不少問題,比如這樣:

void func(int x)
{cout << "參數為整型" << endl;
}void func(void* x)
{cout << "參數為指針" << endl;
}int main()
{func(NULL);return 0;
}

以上代碼中,func函數有兩個重載,一個是參數為指針,一個是參數為整型。我現在就是想傳一個空指針去調用指針版本的func。但是最后還是會調用int類型的。

nullptr不一樣,nullptr不僅不是整型,而且其也不是void*。C++給了nullptr一個專屬類型nullptr_t。這個類型有一個非常非常大的優勢,該類型只能轉化為其它指針類型,不能轉化為指針以外的類型

比如以下代碼:

int x1 = NULL;//正確
int x2 = nullptr;//錯誤

因為NULL本質是0,其可以轉化為很多非指針類型,比如intdoublechar。但是nullptrnullptr_t,它只能轉化為其他指針。上述代碼中,我們把nullptr轉化為一個int,此時編譯器會直接報錯,絕對禁止這個行為。

但是這樣是可以的:

void* p1 = nullptr;
int* p2 = nullptr;
char* p3 = nullptr;
double* p4 = nullptr;

可以看到,nullptr保證了指針類型的穩定,空指針不會被傳遞到指針以外的類型。因此nullptr在各方面都有足夠的優勢,以更加安全的形式給用戶提供空指針。


decltype

在C++11以前,有一個關鍵字typeid,其可以識別一個類型,并且可以通過name成員函數來輸出類型名。

比如這樣:

int i = 0;
int* pi = &i;cout << typeid(i).name() << endl;
cout << typeid(pi).name() << endl;

輸出結果為:

int
int * __ptr64

也就是說,我們可以通過typeid來檢測甚至輸出變量類型。

decltype也是用于識別類型的,但是decltypetypeid應用方向不同。

decltype可以檢測一個變量的類型,并且拿這個類型去聲明新的類型

比如這樣:

int i = 0;
decltype(i) x = 5;

decltype(i)檢測出i的類型為int,于是decltype(i)整體就變成int,從而定義出一個新的變量x

autodecltype 的區別在于,decltype聲明變量可以無需初始化。

int a;
decltype(a) b; // success
auto c = a;    // success
auto d;        // error

decltype還可以作為模板參數

例如把lambda傳給std::priority_queue作為比較條件:

auto comp = [](const std::string& a, const std::string& b) {return a.size() >= b.size();};std::priority_queue<std::string, std::deque<std::string>, decltype(comp)> q(comp);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84831.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84831.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84831.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux 內核warn_on/Bug_on

1,warn_on() warn_on() 是 Linux 內核中用于報告潛在問題或警告的宏。與 bug_on() 不同&#xff0c;bug_on() 通常用于報告嚴重錯誤&#xff0c;其觸發往往會導致內核Oops或panic&#xff0c;而 warn_on() 則用于報告不太嚴重的、可能只是潛在問題或預期外情況的情況。它的觸…

SQL輸出20個9

在SQL Server中要輸出20個連續的9&#xff0c;可以使用以下幾種方法&#xff1a; 使用REPLICATE函數重復生成字符&#xff1a; SELECT REPLICATE(9, 20) AS Result 2. 使用UNION ALL聯合查詢生成多行&#xff1a; SELECT 9 AS Number FROM (VALUES (1),(1),(1),(1),(1),(1),…

懶人云電腦方案:飛牛NAS遠程喚醒 + 節點小寶一鍵喚醒、遠程控制Windows!

后臺高頻問題解答&#xff1a; “博主&#xff0c;飛牛NAS能定時開關機了&#xff0c;能不能讓它順便把家里Windows電腦也遠程喚醒控制&#xff1f;最好點一下就能連&#xff0c;不用記IP端口那種&#xff01;” 安排&#xff01;今天這套方案完美實現&#xff1a; ? 飛牛NAS…

Linux特殊符號

1 管道符| 管道符號 | 用于將一個命令的輸出作為另一個命令的輸入。這種機制允許將多個命令組合在一起&#xff0c;形成一個數據處理鏈&#xff0c;每個命令處理前一個命令的輸出&#xff0c;從而實現復雜的數據處理任務。示例 # 查詢/var/log目錄下所有的log文件,并進行分頁…

初識Docker:容器化技術的入門指南

初識Docker&#xff1a;容器化技術的入門指南 一、Docker是什么&#xff1a;容器化技術的核心概念二、Docker的核心優勢2.1 環境一致性2.2 高效部署與快速迭代2.3 資源利用率高 三、Docker的安裝與基本使用3.1 安裝Docker3.2 Docker基本概念3.3 第一個Docker容器體驗 四、Docke…

商務風企業公司推廣培訓計劃PPT模版分享

商務風企業公司推廣培訓計劃PPT模版分享&#xff1a;商務培訓推廣計劃PPT模版https://pan.quark.cn/s/063282eaf739 第1套PPT模版&#xff0c;綠橙配色&#xff0c;幾何圖形拼接背景&#xff0c;有中英文標題和占位文本。 第2套PPT模版是黑金高端商務風格&#xff0c;有匯報人…

深入理解Nginx:詳盡配置手冊

Nginx是一款高性能的HTTP和反向代理服務器&#xff0c;廣泛應用于負載均衡、緩存和Web服務器等場景。隨著互聯網應用的快速發展&#xff0c;掌握Nginx的配置和優化技巧顯得尤為重要。在本篇文章中&#xff0c;我們將深入探討Nginx的配置&#xff0c;幫助你更好地理解和使用這款…

每日leetcode

1572. 矩陣對角線元素的和 - 力扣&#xff08;LeetCode&#xff09; 題目 給你一個正方形矩陣 mat&#xff0c;請你返回矩陣對角線元素的和。 請你返回在矩陣主對角線上的元素和副對角線上且不在主對角線上元素的和。 示例 1&#xff1a; 輸入&#xff1a;mat [[1,2,3], …

Server 9 ,在 VMware 虛擬機上安裝 Windows 系統完整指南

目錄 前言 一、準備工作 1.1 準備安裝文件 1.2 安裝VMware軟件 1.3 創建新的虛擬機 1.4 開啟虛擬機 二、注意事項 2.1 調整硬件設置 2.2 啟動順序配置 2.3 固件類型選擇 2.4 安全啟動配置 三、安裝優化 3.1 安裝VMware Tools 3.2 系統更新與激活 四、更多操作 ?…

最終章:終焉之塔 · 前端之道

第一章&#xff1a;HTML基石現實的骨架 第二章&#xff1a;CSS秘典 色彩與布局的力量 第三章&#xff1a;JavaScript引擎 行為之火 第四章&#xff1a;DOM迷宮 掌控頁面之心 第五章&#xff1a;異步幻境 時間與數據的秘密 第六章&#xff1a;事件風暴 用戶的意志 第七章&a…

詳解 .net9 內置 Lock 對象,更加現代化和靈活可控的鎖對象

.NET 9 引入了全新的 System.Threading.Lock 類型&#xff0c;作為更現代、類型安全且具備遞歸支持的同步原語。與傳統的基于 Monitor.Enter/lock(obj) 的方式不同&#xff0c;Lock 是一個具體的類&#xff0c;提供了更靈活的 API 和結構化編程模型。 Lock 類 Lock 是一個具體…

python幾行命令實現快速打包apk

1. ??環境準備? sudo apt update sudo apt install -y python3-pip git zip unzip openjdk-17-jdk sudo apt-get install -y autoconf automake libtool pip install kivy buildozer cython2. ??項目配置? 在項目目錄中初始化Buildozer&#xff1a; buildozer init這會…

實時數倉和離線數倉的區別是什么?企業如何選擇合適的數倉架構?

實時數倉和離線數倉的區別是什么&#xff1f;企業如何選擇合適的數倉架構&#xff1f; 時數倉和離線數倉都是數據倉庫的不同類型&#xff0c;用于存儲和管理企業的數據&#xff0c;但它們在數據處理和使用的時間、速度以及用途方面有明顯的區別。 在介紹實時數倉之前&#xf…

Docker Desktop for Windows 系統設置說明文檔

1. 文檔概述 本文檔旨在詳細說明 Docker Desktop for Windows 應用程序中“設置 (Settings)”界面下的所有可配置選項及其子選項。對于每個配置項&#xff0c;我們將提供其功能描述、推薦配置&#xff08;如適用&#xff09;以及相關注意事項&#xff0c;幫助用戶更好地理解和…

精準監測,健康無憂--XC3576H工控主板賦能亞健康檢測儀

在快節奏的現代生活中&#xff0c;亞健康問題逐漸成為困擾人們健康的隱形殺手。疲勞、失眠、免疫力下降等問題頻發&#xff0c;卻往往因難以察覺而延誤調理。智能亞健康檢測儀通過高科技手段&#xff0c;幫助用戶實時了解身體狀況&#xff0c;提前預警潛在健康風險。 其核心功能…

SBT開源構建工具

SBT 的多元定義與核心解釋 SBT&#xff08;Simple Build Tool&#xff09;是專為 Scala 和 Java 項目設計的開源構建工具&#xff0c;基于 Scala 語言開發&#xff0c;提供依賴管理、編譯、測試、打包等全流程支持。其核心特點包括&#xff1a; 核心功能與特性&#xff1a; …

npm run build后將打包文件夾生成zip壓縮包

安裝依賴 npm install archiver --save-dev準備compress.js文件 const fs require(fs); const archiver require(archiver);const sourceDir ./dist; //替換為你的文件夾路徑 const outputZip ./dist.zip;console.log(開始壓縮); const output fs.createWriteStream(ou…

力扣 215 .數組中的第K個最大元素

文章目錄 題目介紹題解 題目介紹 題解 法一&#xff1a;基于快速排序的選擇方法 以中間元素pivot為基準進行排序后&#xff0c;右指針 r 的位置就是最終全部排序好后pivot的位置&#xff0c;然后去左邊或右邊遞歸尋找第k個位置&#xff08;答案&#xff09;的元素。 代碼如下…

CentOS 7.0重置root密碼

文章目錄 版本&#xff1a;CentOS 7.0內核版本&#xff1a;CentOS Linux, with Linux 3.10.0-123.el7.x86_64 服務器重啟后&#xff0c;等待進入上述頁面&#xff0c;按??鍵&#xff0c;中斷正常啟動。在此頁面按E&#xff0c;進入編輯模式 繼續按?&#xff0c;找到linux16…

Linux之高效文本編輯利器 —— vim

目錄 一、vim的基本概念 二、Vim 的三種基本模式 1. 命令模式&#xff08;Command Mode&#xff09; 2. 插入模式&#xff08;Insert Mode&#xff09; 3. 底行模式&#xff08;Last Line Mode&#xff09; 模式切換方法 IDE例子&#xff1a; 三、vim的基本操作 進入vim…