C++ : 入門知識學習 下(引用、內聯函數、auto、新式for循環)

C++ : 入門知識學習 下(引用、內聯函數、auto、新式for循環)

引用(別名)

引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會給引用變量開闢內存空間,它和它引用的變量共用同一塊內存空間,值、地址相同

定義引用的表示方法與定義指針相似,只是用 & 代替了 *,引用(reference)是 C++ 對 C 語言的重要擴充。引用就是某一變量(目標)的一個別名,對引用的操作與對變量直接操作完全一樣
格式:類型 &引用變量名 = 已定義過的變量名 (如: int& ra = a ; ra是a的別名)

引用的特點

①引用在定義時必須初始化
②一個變量可取多個引用(別名)
③引用一旦引用一個實體,不能再引用其他實體(確定爲一個別名時,不能再爲其他別名)

基礎引用
void TestRef()
{
 int a = 10;
 int& ra = a;  //定義引用類型
 int& rra = ra;  //別名取別名
 printf("%p %p %p\n", &a, &ra, &rra); 
}
常引用 (只能縮小,不能放大)
void TestConstRef()
{
 const int a = 10;
 //int& ra = a; // 該語句編譯時會出錯,a爲常量;上面語句的權限是“只讀”,該條是“可讀可寫”,權限放大,使用錯誤
 const int& ra = a;
 
 
 int b = 10; 
 const int& rb = b; //使用正確;上面語句的權限是“可讀可寫”,該條是“只讀”,權限縮小
 
 
 int i = 10;
 const double& ri = i; //不同類型間的引用(引用的是ri 與 i 之間產生的臨時變量,臨時變量具有常性,不可更改,前面加了 const 也不再更改,語法上合理)
}
作用

1、做參數

void Swap(int& left, int& right)
{
 int temp = left;
 left = right;
 right = temp;
}
Swap ( a, b)
//left是 a的引用;right是 b的引用,left、right做形參,不開闢空間

2、做返回值

  • 傳值
    在這裏插入圖片描述
  • 傳引用
    在這裏插入圖片描述

注意:
如果函數返回時,離開函數作用域後,其棧上空間已經還給系統,則不能用棧上的空間作爲引用類型返回。
如果以引用類型返回,返回值的生命週期必須不受函數的限制(即比函數生命週期長)。
傳引用比傳值的效率高

引用和指針的區別
  • 在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間
  • 在底層實現上實際是有空間的,因爲引用是按照指針方式來實現的

引用和指針的不同點:

  1. 引用在定義時必須初始化,指針沒有要求
  2. 引用在初始化時引用一個實體後,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
  3. 沒有NULL引用,但有NULL指針
  4. 在sizeof中含義不同:引用結果爲引用類型的大小,但指針始終是地址空間所佔字節個數(32位平臺下佔4個字節)
  5. 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小
  6. 有多級指針,但是沒有多級引用
  7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
  8. 引用比指針使用起來相對更安全

內聯函數

函數是一個可以重複使用的代碼塊,CPU 會一條一條地挨着執行其中的代碼。CPU 在執行主調函數代碼時如果遇到了被調函數,主調函數就會暫停,CPU 轉而執行被調函數的代碼;被調函數執行完畢後再返回到主調函數,主調函數根據剛纔的狀態繼續往下執行。

一個 C/C++ 程序的執行過程可以認爲是多個函數之間的相互調用過程,它們形成了一個或簡單或複雜的調用鏈條,這個鏈條的起點是 main(),終點也是 main()。當 main() 調用完了所有的函數,它會返回一個值(例如return 0;)結束自己的生命,從而結束整個程序。

函數調用是有時間和空間開銷的。程序在執行一個函數之前需要做一些準備工作,要將實參、局部變量、返回地址以及若干寄存器都壓入棧中,然後才能執行函數體中的代碼;函數體中的代碼執行完畢後需要清理現場,將之前壓入棧中的數據都出棧,才能接着執行函數調用位置以後的代碼。

如果函數體代碼比較,需要較長的執行時間,那麼函數調用機制佔用的時間可忽略;如果函數只有一兩條語句,那麼大部分的時間都會花費在函數調用機制上,這種時間開銷就就不容忽視。

爲了消除函數調用的時空開銷,C++ 提供一種提高效率的方法,即在編譯時將函數調用處用函數體替換,類似於C語言中的宏展開。這種在函數調用處直接嵌入函數體的函數稱爲內聯函數(Inline Function),又稱內嵌函數或者內置函數。

以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,內聯函數提升程序運行的效率。

特性
  1. inline是一種以空間換時間的做法,省去調用函數額開銷。因此代碼很長或者有循環/遞歸的函數不適宜使用作爲內聯函數。
  2. inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義爲 inline 的函數體內有循環/遞歸等等,編譯器優化時會忽略掉內聯。
  3. inline 不建議聲明和定義分離,分離會導致鏈接錯誤。因爲inline被展開,就沒有函數地址了,鏈接就會找不到。

auto

C++98 auto

早在C++98標準中就存在了auto關鍵字,那時的auto用於聲明變量爲自動變量,自動變量意爲擁有自動的生命期,這是多餘的,因爲就算不使用auto聲明,變量依舊擁有自動的生命期:

int a =10 ;          //擁有自動生命期
auto int b = 20 ;    //擁有自動生命期
static int c = 30 ;  //延長了生命期
C++11 auto

C++98中的auto多餘且極少使用,C++11已經刪除了這一用法,取而代之的是全新的auto:變量的自動推導變量的類型
auto可以在聲明變量的時候根據變量初始值的類型自動爲此變量選擇匹配的類型,類似的關鍵字還有decltype。舉個例子:

int a = 10;
auto b = a;      //自動類型推斷,b 爲 int類型
cout << typeid(b).name() << endl;  //打印出 b 的類型 ,typeid運算符可以輸出變量的類型。

程序的運行結果輸出了

int

這種用法就類似於 C# 中的 var關鍵字。auto的自動類型推斷髮生在編譯期,所以使用 auto 不會造成程序運行時效率的降低。而是否會造成編譯期的時間消耗,我認爲是不會的,在未使用auto時,編譯器也需要得知右操作數的類型,再與左操作數的類型進行比較,檢查是否可以發生相應的轉化,是否需要進行隱式類型轉換。

auto的用法

上面舉的這個例子很簡單,在真正編程的時候也不建議這樣來使用auto,直接寫出變量的類型更加清晰易懂。

列舉auto關鍵字的正確用法 : 代替冗長複雜、變量使用範圍專一的變量聲明。

在沒有auto的時候,我們操作標準庫時經常需要這樣:

#include <string>
#include <vector>
int main()
{
    std::vector<std::string> vs;
    for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
    {
        //...
    }
}

像上面那樣看、寫代碼實在煩得很,使用auto能簡化代碼:

#include <string>
#include <vector>
int main()
{
    std::vector<std::string> vs;
    for (auto i = vs.begin(); i != vs.end(); i++)
    {
        //..
    }
}

for 循環中的 i 將在編譯時自動推導其類型,不用去定義長長的一串

定義模板函數時,用於聲明依賴模板參數的變量類型

template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
    auto v = x*y;
    std::cout << v;
}
注意事項
  • auto 變量必須在定義時初始化,這類似於const關鍵字
  • 定義在一個auto序列的變量必須始終推導成同一類型。例如:
auto a4 = 10, a5 = 20, a6 = 30;   //正確
auto b4 = 10, b5 = 20.0, b6 = 'a';   //錯誤,沒有推導爲同一類型
  • 如果初始化表達式是引用,則去除引用語義。
int a = 10;
int &b = a;

auto c = b;   //c的類型爲int而非int&(去除引用)
auto &d = b;  //此時c的類型才爲int&

c = 100;    //a =10;
d = 100;    //a =100;
  • 如果初始化表達式爲const或volatile(或者兩者兼有),則除去const/volatile語義
const int a1 = 10;
auto  b1= a1;    //b1的類型爲int而非const int(去除const)
const auto c1 = a1;   //此時c1的類型爲const int
b1 = 100;  //合法
c1 = 100;  //非法
  • 如果auto關鍵字帶上&號,則不去除const語意
const int a2 = 10;
auto &b2 = a2;   //因爲auto帶上&,故不去除const,b2類型爲const int
b2 = 10;    //非法

這是因爲如何去掉了const,則b2爲a2的非const引用,通過b2可以改變a2的值,則顯然是不合理的

  • 初始化表達式爲數組時,auto關鍵字推導類型爲指針
int a3[3] = { 1, 2, 3 };
auto b3 = a3;
cout << typeid(b3).name() << endl;

程序將輸出
int *

  • 若表達式爲數組且auto帶上&,則推導類型爲數組類型
int a7[3] = { 1, 2, 3 };
auto & b7 = a7;
cout << typeid(b7).name() << endl;

程序輸出

int [3]

  • 函數或者模板參數不能被聲明爲auto
void func(auto a)  //錯誤
{
//... 
}
  • 一定要注意auto並不是一個真正的類型,auto僅僅是一個佔位符,它並不是一個真正的類型,不能使用一些以類型爲操作數的操作符,如sizeof或者typeid
cout << sizeof(auto) << endl;//錯誤
cout << typeid(auto).name() << endl;//錯誤

新式for循環(基於範圍的for循環(C++11))

在C++98中如果要遍歷一個數組,可以按照以下方式進行:

void TestFor()
{
 int array[] = { 1, 2, 3, 4, 5 };
 for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
 array[i] *= 2;
 
 for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
 cout << *p << endl;
}

若循環是一個有範圍的集合,用詳細代碼說明循環的範圍是多餘的,有時候還容易犯錯誤。
因此C++11中引入了基於範圍的for循環。for循環後的括號由冒號“ :”分爲兩部分:第一部分是範圍內用於迭代的變量,第二部分則表示被迭代的範圍

void TestFor()
{
 int array[] = { 1, 2, 3, 4, 5 };
 for(auto& e : array)
 e *= 2;
 
 for(auto e : array)
 cout << e << " ";
 
 return 0;
}
範圍for的使用條件
  • for循環迭代的範圍必須是確定的

對於數組而言,就是數組中第一個元素和最後一個元素的範圍;對於類而言,應該提供begin和end的
方法,begin和end就是for循環迭代的範圍。
注意:以下代碼就有問題,因爲for的範圍不確定

void TestFor(int array[])
{
 for(auto& e : array)
 cout<< e <<endl;
 }
  • 迭代的對象要實現 ++ 和 == 的操作
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章