欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入解析C++中的auto自動類型推導(dǎo)

 更新時(shí)間:2024年04月11日 16:28:36   作者:iShare_愛分享  
C++標(biāo)準(zhǔn)委員會在C++11標(biāo)準(zhǔn)中改變了auto關(guān)鍵字的語義,使它變成一個類型占位符,允許在定義變量時(shí)不必明確寫出確切的類型,讓編譯器在編譯期間根據(jù)初始值自動推導(dǎo)出它的類型,下面我們就來看看auto自動類型推導(dǎo)的推導(dǎo)規(guī)則吧

關(guān)鍵字auto在C++98中的語義是定義一個自動生命周期的變量,但因?yàn)槎x的變量默認(rèn)就是自動變量,因此這個關(guān)鍵字幾乎沒有人使用。于是C++標(biāo)準(zhǔn)委員會在C++11標(biāo)準(zhǔn)中改變了auto關(guān)鍵字的語義,使它變成一個類型占位符,允許在定義變量時(shí)不必明確寫出確切的類型,讓編譯器在編譯期間根據(jù)初始值自動推導(dǎo)出它的類型。這篇文章我們來解析auto自動類型推導(dǎo)的推導(dǎo)規(guī)則,以及使用auto有哪些優(yōu)點(diǎn),還有羅列出自C++11重新定義了auto的含義以后,在之后發(fā)布的C++14、C++17、C++20標(biāo)準(zhǔn)對auto的更新、增強(qiáng)的功能,以及auto有哪些使用限制。

推導(dǎo)規(guī)則

我們將以下面的形式來討論:

auto var = expr;

這時(shí)auto代表了變量var的類型,除此形式之外還可以再加上一些類型修飾詞,如:

const auto var = expr;
// 或者
const auto& var = expr;

這時(shí)變量var的類型是const auto或者const auto&,const也可以換成volatile修飾詞,這兩個稱為CV修飾詞,引用&也可以換成指針,如const auto,這時(shí)明確指出定義的是指針類型。

根據(jù)上面定義的形式,根據(jù)“=”左邊auto的修飾情況分為三種情形:

規(guī)則一:只有auto的情況,既非引用也非指針,表示按值初始化

如下的定義:

auto i = 1;	// i為int
auto d = 1.0;	// d為double

變量i將被推導(dǎo)為int類型,變量d將被推導(dǎo)為double類型,這時(shí)是根據(jù)“=”右邊的表達(dá)式的值來推導(dǎo)出auto的類型,并將它們的值復(fù)制到左邊的變量i和d中,因?yàn)槭菍⒂疫卐xpr表達(dá)式的值復(fù)制到左邊變量中,所以右邊表達(dá)式的CV(const和volatile)屬性將會被忽略掉,如下的代碼:

const int ci = 1;
auto i = ci;		// i為int

盡管ci是有const修飾的常量,但是變量i的類型是int類型,而非const int,因?yàn)榇藭r(shí)i拷貝了ci的值,i和ci是兩個不相關(guān)的變量,分別有不同的存儲空間,變量ci不可修改的屬性不代表變量i也不可修改。

當(dāng)使用auto在同一條語句中定義多個變量時(shí),變量的初始值的類型必須要統(tǒng)一,否則將無法推導(dǎo)出類型而導(dǎo)致編譯錯誤:

auto i = 1, j = 2;	// i和j都為int
auto i = 1, j = 2.0;	// 編譯錯誤,i為int,j為double

規(guī)則二:形式如auto&或auto*,表示定義引用或者指針

當(dāng)定義變量時(shí)使用如auto&或auto*的類型修飾,表示定義的是一個引用類型或者指針類型,這時(shí)右邊的expr的CV屬性將不能被忽略,如下的定義:

int x = 1;
const int cx = x;
const int& rx = x;
auto& i = x;	// (1) i為int&
auto& ci = cx;	// (2) ci為const int&
auto* pi = ℞	// (3) pi為const int*

(1)語句中auto被推導(dǎo)為int,因此i的類型為int&。(2)語句中auto被推導(dǎo)為const int,ci的類型為const int &,因?yàn)閏i是對cx的引用,而cx是一個const修飾的常量,因此對它的引用也必須是常量引用。(3)語句中的auto被推導(dǎo)為const int,pi的類型為const int*,rx的const屬性將得到保留。

除了下面即將要講到的第三種情況外,auto都不會推導(dǎo)出結(jié)果是引用的類型,如果要定義為引用類型,就要像上面那樣明確地寫出來,但是auto可以推導(dǎo)出來是指針類型,也就是說就算沒有明確寫出auto*,如果expr的類型是指針類型的話,auto則會被推導(dǎo)為指針類型,這時(shí)expr的const屬性也會得到保留,如下的例子:

int i = 1;
auto pi = &i;	// pi為int*
const char word[] = "Hello world!";
auto str = word;	// str為const char*

pi被推導(dǎo)出來的類型為int,而str被推導(dǎo)出來的類型為const char。

規(guī)則三:形式如auto&&,表示萬能引用

當(dāng)以auto&&的形式出現(xiàn)時(shí),它表示的是萬能引用而非右值引用,這時(shí)將視expr的類型分為兩種情況,如果expr是個左值,那么它推導(dǎo)出來的結(jié)果是一個左值引用,這也是auto被推導(dǎo)為引用類型的唯一情形。而如果expr是個右值,那么將依據(jù)上面的第一種情形的規(guī)則。如下的例子:

int x = 1;
const int cx = x;
auto&& ref1 = x;	// (1) ref1為int&
auto&& ref2 = cx;	// (2) ref2為const int&
auto&& ref3 = 2;	// (3) ref3為int&&

(1)語句中x的類型是int且是左值,所以ref1的類型被推導(dǎo)為int&。(2)語句中的cx類型是const int且是左值,因此ref2的類型被推導(dǎo)為const int&。(3)語句中右側(cè)的2是一個右值且類型為int,所以ref3的類型被推導(dǎo)為int&&。

上面根據(jù)“=”左側(cè)的auto的形式歸納討論了三種情形下的推導(dǎo)規(guī)則,接下來根據(jù)“=”右側(cè)的expr的不同情況來討論推導(dǎo)規(guī)則:

expr是一個引用

如果expr是一個引用,那么它的引用屬性將被忽略,因?yàn)槲覀兪褂玫氖撬玫膶ο?,而非這個引用本身,然后再根據(jù)上面的三種推導(dǎo)規(guī)則來推導(dǎo),如下的定義:

int x = 1;
int &rx = x;
const int &crx = x;
auto i = rx;	// (1) i為int
auto j = crx;	// (2) j為int
auto& ri = crx;	// (3) ri為const int&

(1)語句中rx雖然是個引用,但是這里是使用它引用的對象的值,所以根據(jù)上面的第一條規(guī)則,這里i被推導(dǎo)為int類型。(2)語句中的crx是個常量引用,它和(1)語句的情況一樣,這里只是復(fù)制它所引用的對象的值,它的const屬性跟變量j沒有關(guān)系,所以變量j的類型為int。(3)語句里的ri的類型修飾是auto&,所以應(yīng)用上面的第二條規(guī)則,它是一個引用類型,而且crx的const屬性將得到保留,因此ri的類型推導(dǎo)為const int&。

expr是初始化列表

當(dāng)expr是一個初始化列表時(shí),分為兩種情況而定:

auto var = {};	// (1)
// 或者
auto var{};	// (2)

當(dāng)使用第一種方式時(shí),var將被推導(dǎo)為initializer_list類型,這時(shí)無論花括號內(nèi)是單個元素還是多個元素,都是推導(dǎo)為initializer_list類型,而且如果是多個元素,每個元素的類型都必須要相同,否則將編譯錯誤,如下例子:

auto x1 = {1, 2, 3, 4};		// x1為initializer_list<int>
auto x2 = {1, 2, 3, 4.0};	// 編譯錯誤

x1的類型為initializer_list,這里將經(jīng)過兩次類型推導(dǎo),第一次是將x1推導(dǎo)為initializer_list類型,第二次利用花括號內(nèi)的元素推導(dǎo)出元素的類型T為int類型。x2的定義將會引起編譯錯誤,因?yàn)閤2雖然推導(dǎo)為initializer_list類型,但是在推導(dǎo)T的類型時(shí),里面的元素的類型不統(tǒng)一,導(dǎo)致無法推導(dǎo)出T的類型,引起編譯錯誤。

當(dāng)使用第二種方式時(shí),var的類型被推導(dǎo)為花括號內(nèi)元素的類型,花括號內(nèi)必須為單元素,如下:

auto x1{1};	// x1為int
auto x2{1.0};	// x2為double

x1的類型推導(dǎo)為int,x2的類型推導(dǎo)為double。這種形式下花括號內(nèi)必須為單元素,如果有多個元素將會編譯錯誤,如:

auto x3{1, 2};	// 編譯錯誤

這個將導(dǎo)致編譯錯誤:error: initializer for variable 'x3' with type 'auto' contains multiple expressions。

expr是數(shù)組或者函數(shù)

數(shù)組在某些情況會退化成一個指向數(shù)組首元素的指針,但其實(shí)數(shù)組類型和指針類型并不相同,如下的定義:

const char name[] = "My Name";
const char* str = name;

數(shù)組name的類型是const char[8],而str的類型為const char*,在某些語義下它們可以互換,如在第一種規(guī)則下,expr是數(shù)組時(shí),數(shù)組將退化為指針類型,如下:

const char name[] = "My Name";
auto str = name;	// str為const char*

str被推導(dǎo)為const char*類型,盡管name的類型為const char[8]。

但如果定義變量的形式是引用的話,根據(jù)上面的第二種規(guī)則,它將被推導(dǎo)為數(shù)組原本的類型:

const char name[] = "My Name";
auto& str = name;	// str為const char (&)[8]

這時(shí)auto被推導(dǎo)為const char [8],str是一個指向數(shù)組的引用,類型為const char (&)[8]。

當(dāng)expr是函數(shù)時(shí),它的規(guī)則和數(shù)組的情況類似,按值初始化時(shí)將退化為函數(shù)指針,如為引用時(shí)將為函數(shù)的引用,如下例子:

void func(int, double) {}
auto f1 = func;		// f1為void (*)(int, double)
auto& f2 = func;	// f2為void (&)(int, double)

f1的類型推導(dǎo)出來為void (*)(int, double),f2的類型推導(dǎo)出來為void (&)(int, double)。

expr是條件表達(dá)式語句

當(dāng)expr是一個條件表達(dá)式語句時(shí),條件表達(dá)式根據(jù)條件可能返回不同類型的值,這時(shí)編譯器將會使用更大范圍的類型來作為推導(dǎo)結(jié)果的類型,如:

auto i =  condition ? 1 : 2.0;	// i為double

無論condition的結(jié)果是true還是false,i的類型都將被推導(dǎo)為double類型。

使用auto的好處

強(qiáng)制初始化的作用

當(dāng)你定義一個變量時(shí),可以這樣寫:

int i;

這樣寫編譯是能夠通過的,但是卻有安全隱患,比如在局部代碼中定義了這個變量,然后又接著使用它了,可能面臨未初始化的風(fēng)險(xiǎn)。但如果你這樣寫:

auto i;

這樣是編譯不通過的,因?yàn)樽兞縤缺少初始值,你必須給i指定初始值,如下:

auto i = 0;

必須給變量i初始值才能編譯通過,這就避免了使用未初始化變量的風(fēng)險(xiǎn)。

定義小范圍內(nèi)的局部變量時(shí)

在小范圍的局部代碼中定義一個臨時(shí)變量,對理解整體代碼不會造成困擾的,比如:

for (auto i = 1; i < size(); ++i) {}

或者是基于范圍的for循環(huán)的代碼,只是想要遍歷容器中的元素,對于元素的類型不關(guān)心,如:

std::vector<int> v = {};
for (const auto& i : v) {}

減少冗余代碼

當(dāng)變量的類型非常長時(shí),明確寫出它的類型會使代碼變得又臃腫又難懂,而實(shí)際上我們并不關(guān)心它的具體類型,如:

std::map<std::string, int> m;
for (std::map<std::string, int>::iterator it = m.begin(); it != m.end(); ++it) {}

上面的代碼非常長,造成閱讀代碼的不便,對增加理解代碼的邏輯也沒有什么好處,實(shí)際上我們并不關(guān)心it的實(shí)際類型,這時(shí)使用auto就使代碼變得簡潔:

for (auto it = m.begin(); it != m.end(); ++it) {}

再比如下面的例子:

std::unordered_multimap<int, int> m;
std::pair<std::unordered_multimap<int, int>::iterator,
		  std::unordered_multimap<int ,int>::iterator>
	range = m.equal_range(k);

對于上面的代碼簡直難懂,第一遍看還看不出來想代表的意思是什么,如果改為auto來寫,則一目了然,一看就知道是在定義一個變量:

auto range = m.equal_range(k);

無法寫出的類型

如果說上面的代碼雖然難懂和難寫,畢竟還可以寫出來,但有時(shí)在某些情況下卻無法寫出來,比如用一個變量來存儲lambda表達(dá)式時(shí),我們無法寫出lambda表達(dá)式的類型是什么,這時(shí)可以使用auto來自動推導(dǎo):

auto compare = [](int p1, int p2) { return p1 < p2; }

避免對類型硬編碼

除了上面提到的可以減少代碼的冗余之外,使用auto也可以避免對類型的硬編碼,也就是說不寫死變量的類型,讓編譯器自動推導(dǎo),如果我們要修改代碼,就不用去修改相應(yīng)的類型,比如我們將一種容器的類型改為另一種容器,迭代器的類型不需要修改,如:

std::map<std::string, int> m = { ... };
auto it = m.begin();
// 修改為無序容器時(shí)
std::unordered_map<std::string, int> m = { ... };
auto it = m.begin();

C++標(biāo)準(zhǔn)庫里的容器大部分的接口都是相同的,泛型算法也能應(yīng)用于大部分的容器,所以對于容器的具體類型并不是很重要,當(dāng)根據(jù)業(yè)務(wù)的需要更換不同的容器時(shí),使用auto可以很方便的修改代碼。

跨平臺可移植性

假如你的代碼中定義了一個vector,然后想要獲取vector的元素的大小,這時(shí)你調(diào)用了成員函數(shù)size來獲取,此時(shí)應(yīng)該定義一個什么類型的變量來承接它的返回值?vector的成員函數(shù)size的原型如下:

size_type size() const noexcept;

size_type是vector內(nèi)定義的類型,標(biāo)準(zhǔn)庫對它的解釋是“an unsigned integral type that can represent any non-negative value of difference_type”,于是你認(rèn)為用unsigned類型就可以了,于是寫下如下代碼:

std::vector<int> v;
unsigned sz = v.size();

這樣寫可能會導(dǎo)致安全隱患,比如在32位的系統(tǒng)上,unsigned的大小是4個字節(jié),size_type的大小也是4個字節(jié),但是在64位的系統(tǒng)上,unsigned的大小是4個字節(jié),而size_type的大小卻是8個字節(jié)。這意味著原本在32位系統(tǒng)上運(yùn)行良好的代碼可能在64位的系統(tǒng)上運(yùn)行異常,如果這里用auto來定義變量,則可以避免這種問題。

避免寫錯類型

還有一種似是而非的問題,就是你的代碼看起來沒有問題,編譯也沒有問題,運(yùn)行也正常,但是效率可能不如預(yù)期的高,比如有以下的代碼:

std::unordered_map<std::string, int> m = { ... };
for (const std::pair<std::string, int> &p : m) {}

這段代碼看起來完全沒有問題,編譯也沒有任何警告,但是卻暗藏隱患。原因是std::unordered_map容器的鍵值的類型是const的,所以std::pair的類型不是std::pair<std::string, int>而是std::pair<const std::string, int>。但是上面的代碼中定義p的類型是前者,這會導(dǎo)致編譯器想盡辦法來將m中的元素(類型為std::pair<const std::string, int>)轉(zhuǎn)換成std::pair<std::string, int>類型,因此編譯器會拷貝m中的所有元素到臨時(shí)對象,然后再讓p引用到這些臨時(shí)對象,每迭代一次,臨時(shí)對象就被析構(gòu)一次,這就導(dǎo)致了無故拷貝了那么多次對象和析構(gòu)臨時(shí)對象,效率上當(dāng)然會大打折扣。如果你用auto來替代上面的定義,則完全可以避免這樣的問題發(fā)生,如:

for (const auto&amp; p : m) {}

新標(biāo)準(zhǔn)新增功能

自動推導(dǎo)函數(shù)的返回值類型(C++14)

C++14標(biāo)準(zhǔn)支持了使用auto來推導(dǎo)函數(shù)的返回值類型,這樣就不必明確寫出函數(shù)返回值的類型,如下的代碼:

template&lt;typename T1, typename T2&gt;
auto add(T1 a, T2 b) {
    return a + b;
}

int main() {
    auto i = add(1, 2);
}

不用管傳入給add函數(shù)的參數(shù)的類型是什么,編譯器會自動推導(dǎo)出返回值的類型。

使用auto聲明lambda的形參(C++14)

C++14標(biāo)準(zhǔn)還支持了可以使用auto來聲明lambda表達(dá)式的形參,但普通函數(shù)的形參使用auto來聲明需要C++20標(biāo)準(zhǔn)才支持,下面會提到。如下面的例子:

auto sum = [](auto p1, auto p2) { return p1 + p2; };

這樣定義的lambda式有點(diǎn)像是模板,調(diào)用sum時(shí)會根據(jù)傳入的參數(shù)推導(dǎo)出類型,你可以傳入int類型參數(shù)也可以傳入double類型參數(shù),甚至也可以傳入自定義類型,如果自定義類型支持加法運(yùn)算的話。

非類型模板形參的占位符(C++17)

C++17標(biāo)準(zhǔn)再次拓展了auto的功能,使得能夠作為非類型模板形參的占位符,如下的例子:

template&lt;auto N&gt;
void func() {
    std::cout &lt;&lt; N &lt;&lt; std::endl;
}

func&lt;1&gt;();	// N為int類型
func&lt;'c'&gt;();	// N為chat類型

但是要保證推導(dǎo)出來的類型是能夠作為模板形參的,比如推導(dǎo)出來是double類型,但模板參數(shù)不能接受是double類型時(shí),則會導(dǎo)致編譯不通過。

結(jié)構(gòu)化綁定功能(C++17)

C++17標(biāo)準(zhǔn)中auto還支持了結(jié)構(gòu)化綁定的功能,這個功能有點(diǎn)類似tuple類型的tie函數(shù),它可以分解結(jié)構(gòu)化類型的數(shù)據(jù),把多個變量綁定到結(jié)構(gòu)化對象內(nèi)部的對象上,在沒有支持這個功能之前,要分解tuple里的數(shù)據(jù)需要這樣寫:

tuple x{1, "hello"s, 5.0};
itn a;
std::string b;
double c;
std::tie(a, b, c) = x;	// a=1, b="hello", c=5.0

在C++17之后可以使用auto來這樣寫:

tuple x{1, "hello"s, 5.0};
auto [a, b, c] = x;	// 作用如上
std::cout &lt;&lt; "a=" &lt;&lt; a &lt;&lt; ", b=" &lt;&lt; b &lt;&lt; ", c=" &lt;&lt; c &lt;&lt; std::endl;

auto的推導(dǎo)功能從以前對單個變量進(jìn)行類型推導(dǎo)擴(kuò)展到可以對一組變量的推導(dǎo),這樣可以讓我們省略了需要先聲明變量再處理結(jié)構(gòu)化對象的麻煩,特別是在for循環(huán)中遍歷容器時(shí),如下:

std::map&lt;std::string, int&gt; m;
for (auto&amp; [k, v] : m) {
    std::cout &lt;&lt; k &lt;&lt; " =&gt; " &lt;&lt; v &lt;&lt; std::endl;
}

使用auto聲明函數(shù)的形參(C++20)

之前提到無法在普通函數(shù)中使用auto來聲明形參,這個功能在C++20中也得到了支持。你終于可以寫下這樣的代碼了:

auto add (auto p1, auto p2) { return p1 + p2; };
auto i = add(1, 2);
auto d = add(5.0, 6.0);
auto s = add("hello"s, "world"s);	// 必須要寫上s,表示是string類型,默認(rèn)是const char*,
                                	// char*類型是不支持加法的

這個看起來是不是和模板很像?但是寫法要比模板要簡單,通過查看生成的匯編代碼,看到編譯器的處理方式跟模板的處理方式是一樣的,也就是說上面的三個函數(shù)調(diào)用分別產(chǎn)生出了三個函數(shù)實(shí)例:

auto add&lt;int, int&gt;(int, int);
auto add&lt;double, double&gt;(double, double);
auto add&lt;std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt; &gt;(std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;, std::__cxx11::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt; &gt;);

使用auto的限制

上面詳細(xì)列出了使用auto的好處和使用場景,但在有些地方使用auto還存在限制,下面也一并羅列出來。

類內(nèi)初始化成員時(shí)不能使用auto

在C++11標(biāo)準(zhǔn)中已經(jīng)支持了在類內(nèi)初始化數(shù)據(jù)成員,也就是說在定義類時(shí),可以直接在類內(nèi)聲明數(shù)據(jù)成員的地方直接寫上它們的初始值,但是在這個情況下不能使用auto來聲明非靜態(tài)數(shù)據(jù)成員,比如:

class Object {
	auto a = 1;	// 編譯錯誤。
};

上面的代碼會出現(xiàn)編譯錯誤:error: 'auto' not allowed in non-static class member。雖然不能支持聲明非靜態(tài)數(shù)據(jù)成員,但卻可以支持聲明靜態(tài)數(shù)據(jù)成員,在C++17標(biāo)準(zhǔn)之前,使用auto聲明靜態(tài)數(shù)據(jù)成員需要加上const修飾詞,這就給使用上造成了不便,因此在C++17標(biāo)準(zhǔn)中取消了這個限制:

class Object {
	static inline auto a = 1;	// 需要寫上inline修飾詞
};

函數(shù)無法返回initializer_list類型

雖然在C++14中支持了自動推導(dǎo)函數(shù)的返回值類型,但卻不支持返回的類型是initializer_list類型,因此下面的代碼將編譯不通過:

auto createList() {
    return {1, 2, 3};
}

編譯錯誤信息:error: cannot deduce return type from initializer list。

lambda式參數(shù)無法使用initializer_list類型

同樣地,在lambda式使用auto來聲明形參時(shí),也不能給它傳遞initializer_list類型的參數(shù),如下代碼:

std::vector&lt;int&gt; v;
auto resetV = [&amp;v](const auto&amp; newV) { v = newV; };
resetV({1, 2, 3});

上面的代碼會編譯錯誤,無法使用參數(shù){1, 2, 3}來推導(dǎo)出newV的類型。

到此這篇關(guān)于深入解析C++中的auto自動類型推導(dǎo)的文章就介紹到這了,更多相關(guān)C++ auto自動類型推導(dǎo)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論