前言:
類型一直是C++中最重要的部分,相比于其他高級語言,C++的類型會復雜許多,往往一個類型匹配錯誤就會導致程序報錯。
一、初始化與賦值
定義:初始化與賦值語句是程序中最基本的語句,功能是將某個值與一個對象關聯起來;
- 值:字面量、對象(變量或常量)所表示的值等
- 標識符(對象):變量、常量、引用
初始化的基本操作:
- 1、在內存中開辟空間、保存相應的數值;
- 2、在編譯器中構造符號表、將標識符與相關內存空間關聯起來;
二、 類型概述
下面通過幾點概要說明:
1、類型是編譯期概念,可執行程序中不存在類型的概念;
2、C++是強類型語言;
強類型語言定義: 一旦一個變量被定義類型,如果不經過強制轉換,那么它永遠就是該數據類型;
弱類型語言定義: 某一變量被定義類型,該變量可根據環境變化自動進行轉換,不需要強轉;
3、引入類型是為了更好描述程序,防止誤用;
4、類型描述的信息:
存儲所需要的大小: (sizeof,標準沒有嚴格限制,根據硬件不同字節數也不同)
取值空間: (可用std::numeric_limits來判斷,超過范圍可能產生溢出)
#include<iostream> #include<limits> int main() { int x = 10; std::cout << std::numeric_limits<int>::min() << std::endl; //-2147483648 std::cout << std::numeric_limits<int>::max() << std::endl; //2147483647 std::cout << std::numeric_limits<unsigned int>::min() << std::endl; //0 std::cout << std::numeric_limits<unsigned int>::max() << std::endl; //4294967295 }
由上面程序運行結果可知,無符號int類型占4個字節,也就是32個比特位,所以最大范圍為232,在不同的硬件下可能不同;
- 對齊信息(一般存放在內存中按類型的對齊信息的整數倍存儲,比如int的對齊信息為4個字節,那存儲的空間首地址為4的倍數,在結構體中,因為存在對齊信息,char也會按4個字節保存)
- 類型可執行的操作
三、類型分類
類型可以劃分為基本類型和復雜類型;
基本(內建)類型:C++語言中支持的類型,包含以下幾種:
1、數值類型
字符類型:char、wchar_t、char16_t、char32_t,通常為1個字節,表示256個值,也就是ASCII編碼的字符;
整數類型:帶符號整數類型(short、int、long、long long),無符號整數類型(unsigned+帶符號整數類型)
浮點類型:float、double、long double
注意:在C++11中引入了固定尺寸的整數類型,如int32_t等,之前在針對開發板的程序中有見過該類型,主要是便于硬件的可移植性:
2、void類型
復雜類型:由基本類型組合、變種所產生的類型,可能是標準庫引入,或自定義類型;
四、字面值及其類型
字面值:在程序中直接表示為一個具體數值或字符串的值;
每個字面值都有其類型,例子如下:
- 整數字面值(int):20(十進制)、024(八進制)、0x14(十六進制)
- 浮點數(double):1.3、1e8
- 字符字面值(char): ‘c'、'\n'
- 字符串字面值(char[4]): “cpp”,注意這里字符串后會默認加/0,所以是四個字符長度
- 布爾字面值(bool): True、False
像如果想要定義float類型的數,可以加入后綴如1.3f;
C++提供了用戶創建自定義后綴的函數:
#include<iostream> // 后綴可自行定義,我這里用_bang int operator "" _bang(long double x) { return (int)x * 2; } int main() { int x = 7.14_bang; std::cout << x << std::endl; }
上面代碼將7.14的浮點類型轉換成整型并增大一倍,可自行定義后綴試一下;
五、變量及其類型
變量:對應一段存儲空間,可以改變其中內容;
聲明與定義的區別:不能重定義已經初始化的變量,需要加入extern用來聲明;
初始化:全局變量會默認初始化為0,局部變量會缺省初始化(隨機數值);
六、復合類型
1、指針:一種間接類型;
如上圖為一個指針p指向一段內存,p保存的為val的地址,我們通過打印尺寸可知,指針p為8個字節;
特點:
- 可以"指向"不同的對象;
- 具有相同的尺寸;
- 指針與bool的隱式轉換:非空指針可以轉換為true、空指針可以轉換為false;
注意:兩個符號:*(解引用符)、&(取地址符);
解引用符在不同環境下含義不同,看如下代碼:
int x = 10; int* p = &x; // 表示p為一個int指針類型 *p; // 表示解引用,獲取指針指向地址的值
關于nullptr:
- 一個特殊的對象(類型為nullptr_t),表示空指針;
- 類似于C中的NULL,但更加安全;
void 指針*:沒有記錄對象的尺寸,可以表示任意類型指針,一般作為形參或返回值;
指針對比對象:指針復制成本低,引用成本高;
總結:指針在程序中的作用,最重要的就是作為參數傳入,由于數據類型可能很大,傳入指針大小固定為8個字節,并且指針值為地址可復制,復制成本低,并且可在函數中改變變量的值;
2、引用
取地址符&也有兩個含義:
int x = 10; &x; // 取地址符 int& ret = x; // 定義ret為一個引用類型
特點:
- 是對象的別名,不能綁定字面值(指針也不能指向字面值);
- 構造時綁定對象,在其生命周期內不能綁定其他對象(賦值操作會改變對象內容);
- 不存在空引用,但可能存在非法引用,總體比指針安全;
- 屬于編譯期概念,在底層還是通過指針實現;
七、常量類型
- 使用關鍵字const聲明常量對象;
- 是編譯期概念,由編譯器保證,作用為防止非法操作、優化程序邏輯;
常量指針(頂層常量):
int* const p = &x;
常量指針表示指針為常量,指針不能更改指向;
底層常量:
const int* p = &x;
底層常量表示指針指向的地址的內容不能發生改變,指針指向可改變;
常量引用:
用const int&定義一個常量引用;
主要用于函數形參(對于較復雜的數據類型);
可以綁定字面值;
常量表達式:
constexpr int x = 1; // x的類型仍為const int
聲明的是編譯期的常量,編譯器可以對其進行優化;
八、類型別名
類型別名:引入特殊的含義或便于使用,例如size_t;
引入類型別名的兩種方式:
1、typedef int Mytype;
2、using Mytype = int;(C++11后)
第二種方式更好;
- 應將指針類型別名視為一個整體,引入常量const表示指針為常量的類型;
- 不能通過類型別名構造引用的引用;
九、類型自動推導
定義:通過初始化表達式定義對象類型,編譯器會自動推導得到;(C++11開始)
推導得到的類型還是強類型,并不是弱類型;
自動推導的幾種形式:
1、auto:最常用的形式,會產生類型退化(由于左值右值的類型區別);
2、const auto、constexpr auto:推導出的是常量、常量表達式類型;
3、auto&:推導出引用類型,避免類型退化;
4、decltype(exp) :返回exp表達式的類型(左值加引用);
5、decltype(val) :返回val的類型;
6、decltype(auto) :簡化decltype的使用,C++14開始支持;
補充:類型退化表示一個變量作為左值和右值時類型不同,例如數組作為右值為指針;
十、域與對象聲明周期
域(scope):表示程序中的一部分,其中的名稱有唯一含義,有全局域、塊域等;
域可以嵌套,嵌套域中定義的名稱可以隱藏外部域中定義的名稱;
對象的生命周期起始于被初始的時刻,終止于被銷毀的時刻;
全局對象的生命周期是整個程序運行期間,局部對象終止在所在域執行完成;
思考
1、思考下下面關于指針的兩行代碼的含義
int x = 1; int* p = &x; int y = 0; *p = y; // 第一行 p = &y; // 第二行
這兩行表明了指針的一個特定,可改變性,每一行的含義如下:
第一行:將指針p指向的內存地址的值改變為y;
第二行:不改變x的值,而是將指針p的指向改成y;
2、經過指針的思考后,我們看看關于引用的思考
int x = 1; int& f = x; int y = 0; f = y; // 思考一下這一行的作用,是改變了引用f的綁定嗎?
上面這行代碼并不改變f的綁定,而是改變了f的值,同時引用對象x的值也發生改變;
3、經過了指針和引用的思考
下面思考下兩者在底層有什么關聯:
int x; int* p = &x; *p = 1; int& f = x; f = 1;
分析下上面兩行代碼,他們底層實現會相同嗎?
這是兩者的匯編代碼實現,可以發現是完全相同的,引用底層也是通過指針實現的;
4、思考以下代碼中&x是什么數據類型?
int x = 1; const int* p = &x;
如果我們只考慮&x的話,這是一個int*的類型,但由于第二行代碼執行拷貝構造,隱式地將&x轉換為左值所需要的 const int *類型;
5、思考下面函數傳參的區別?
void fun(int x){} void fun(const int& x){}
從本質上來說,上面兩種傳參實現的作用是一致的,第一個進行拷貝構造傳遞,所以在函數內部無法改變外部x變量的值,而下面的傳參傳入引用可以在函數內部改變外部x的值,加入const強制成變量;第二種其實是畫蛇添足地做法,但常量引用對于復雜的數據類型來說,是能夠節省很多空間的,比如自定義的結構體;
6、下面常量表示底層常量還是頂層常量?
using mytype = int*; int x = 1; const mytype p = &x;
這里我們容易誤導,還會認為這是一個底層常量,但由于別名的定義,這里其實是一個頂層常量,我們可以將mytype看作一個整體,那么指針的指向不可發生改變;
7、下面auto&自動推導出的y是什么類型?
const int x = 1; auto& y = x;
相信大部分人會認為x會類型退化,從而y為int&類型,實際上這里類型不會退化,所以y為const int&類型;
8、下面來看看decltype自動推導的類型是什么?
int x = 1; decltype(x); // 1 decltype((x)); // 2
decltype在傳入參數為左值時加入引用,那么第一行為一個變量,所以為int類型,第二行為表達式,所以加入引用為int&類型;
總結:
本篇講解的類型知識點很雜,并且涵蓋很多小的知識點,很多細節部分在實際工程中不一定會接觸到,當然在工程中也會遇到很多自己不理解的類型轉換,需要多通過debug模式來查看類型;
本篇知識點較多,可以選擇自己想了解的部分進行查看,后續會繼續推出更深層次的內容;
到此這篇關于C++ 中的類型詳細的文章就介紹到這了,更多相關C++ 類型內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/weixin_40620310/article/details/121285344