日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

【C++系列】引用与临时变量

發布時間:2024/3/12 c/c++ 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【C++系列】引用与临时变量 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

        • ??引用的概念
        • ??引用的作用
          • ??1. 作為函數參數,更加簡潔,能減少內存的消耗。
          • ??編譯器是如何檢查越界訪問的
          • ??2. 引用作為返回值

前言


  • 🔖 分享每一次的學習,期待你我都有收獲。
  • 🎇 歡迎🔎關注👍點贊??收藏?評論,共同進步!
  • 🌊 “要足夠優秀,才能接住上天給的驚喜和機會”
  • 💬 博主水平有限,如若有誤,請多指正,萬分感謝!

??引用的概念

引用不是新定義一個變量,
而是給已存在變量取了一個別名。

編譯器不會為引用變量開辟內存空間,
它和它引用的變量共用同一塊內存空間。

說人話就是,引用就是取小名,平常說的二狗子啥的都是小名。

就比如這個人,他的伙伴喜歡親切地叫他綠藻頭,當然也可以直接叫他索隆,但無論是叫他綠藻頭還是索隆,叫的都是這個人,兩種稱呼,一個身體。引用也是這樣的,在語法上,雖然稱呼不同,但是它們都是指同一塊內存空間

我們來觀察一下這一段代碼

#include<iostream> using namespace std;int main() {int a = 10;int& b = a;return 0; }

可以看到,我們定義的b與a具有相同的值,且他們的地址也是相同的,再次印證了b只是a的別名,b就是a

引用特性:

  • 引用在定義時必須初始化
  • 一個變量可以有多個引用
  • 引用一旦引用一個實體,再不能引用其他實體
  • 常引用

    int main() {const int a = 10;int& x = a; // ① 錯誤 正確寫法:const int& x = a;int b = 10;const int& y = b; // ② 正確int c = 0;double z = c //不同類型之間的計算——>隱式轉換,正確int c = 0;double& z = c; // ③ 錯誤int d = 0;const double& s = d;// ④ 正確return 0; }

    ① a為const修飾的常變量,x為a的別名卻沒有用const加以修飾,也就是說,a不能改變自己的值,但是用它的別名卻能改變它的值,屬于權限的放大,因此該語句無法通過編譯。

    ②b為變量,可讀可寫的權限,它的別名只使用了讀的權限,屬于權限的縮小,這是允許的,就好比權利與義務的關系,有些權利我們雖然有,但是我們可以選擇放棄,前提是我們擁有這種權利。 ①則體現了沒有的權利不能隨意擴大。

    ③與④是一組對比

    我們先來看這樣一段代碼

    int main() {int a = 10;double b = 0;double c = 0;b = a; //int ->double 隱式轉換c = (double)a; //強制類型轉換return 0; }

    不同類型之間的計算會發生隱式轉換,一個變量可以進行強制類型轉換,但無論是這兩種中的哪一種,都是借由臨時變量進行的,不會改變變量本身。

    臨時變量具有常性,其值無法被修改。

    同理

    int c = 0;double& z = c; // ③ 錯誤

    這里也是先將c的值交給臨時變量存儲,再給z,也就是說z不是c的別名,而是臨時變量的別名,而臨時變量無法被修改,因此z必須用const修飾。

    int c = 0;const double& z = c; // 正確

    我們不妨再看一下c的地址和z的地址。

    再次印證了轉換的過程產生了臨時變量,z是臨時變量的別名,而不是c的別名,因此z的地址是臨時變量的地址。

    ??引用的作用

    ??1. 作為函數參數,更加簡潔,能減少內存的消耗。

    在c語言中,我們學習了指針作為函數參數,其實引用效果就類似于指針。

    C語言版

    #include<stdio.h>void Swap(int* a, int* b) {int tmp = *a;*a = *b;*b = tmp; }int main() {int a = 10;int b = 20;Swap(&a, &b);return 0; }

    C++版

    void Swap(int&a,int &b) {int tmp = a;a = b;b = tmp; }int main() {int a = 10;int& b = a;return 0; }
    • 函數傳值和傳引用的效率比較

    我們知道,引用只是一塊內存空間的別名,

    因此函數傳引用,它并不會耗費額外的內存空間。

    而實參傳值時,形參只是實參的一份臨時拷貝,它會在內存中開辟一塊臨時空間,然后拷貝實參的內容。

    并且實參所占空間越大,則開辟的臨時空間就越大,對性能的消耗也就越大。

    #include<iostream> #include<time.h> using namespace std;struct Node {int a[10000]; };void Func1(Node a) //傳值 {; }void Func2(Node& a) //傳引用 {}void TestEffic() {Node a; int begin1 = clock();for (int i = 0; i < 100000; i++){Func1(a); //傳值}int end1 = clock();int begin2 = clock();for (int i = 0; i < 100000; i++){Func2(a); //傳引用}int end2 = clock();cout << "Func1(Node) = " << end1 - begin1 << endl;cout << "Func2(Node&) = " << end2 - begin2 << endl;}int main() {TestEffic();return 0; }

    從數字上就可以清晰看到他們運行所花費的時間的差距了。

    這就是由于每次引用并不需要再開空間對A進行拷貝,而傳值時每次都要建立臨時空間對A進行拷貝。

    需要開的數組越大,則他們的差距會越大。


    在講第二個作用前,要先講一件事——

    ??編譯器是如何檢查越界訪問的

    編譯器對越界訪問的檢查是——抽查。

    我們看這么一段代碼:

    int main() {int a[6] = { 1,2,3,4};a[4] = 10;return 0; }

    這段代碼很明顯越界訪問了,編譯器也確實檢查到了,于是程序就崩了。但是如果我們再往更后面一點的內存訪問又會發生什么呢?

    int main() {int a[4] = {1,2,3,4}; //數組下標范圍 0~3//a[4] = 10;a[6] = 16; //這次往更后面訪問內存塊,訪問a[6]printf("a[6] = %d\n", a[6]);return 0; }

    我們看到,程序非但沒有崩,我們不但越界將內存空間中的值更改,甚至還將它打印出來了,編譯器也只是給了我們一個警告,并沒有報錯。因此,編譯器對于內存的越界訪問檢查,確實是采取抽查的方式。

    編譯器通常只會在合法內存塊后面,一兩個內存塊的位置設立檢查,因為正常情況下,這里是最容易發生越界訪問的地方,如果在每個內存空間都設立檢查,那樣對性能的消耗太大,不現實。

    就好比交警檢查酒駕,不可能一天24小時設立檢查點,那樣對于社會的運轉也有很大的消耗和不便,不科學。

    因此通常會在半夜,凌晨,或是利用酒駕人的心理,在小路上設立檢查點,針對這些最容易查到酒駕的情況,設立檢查點。


    我們繼續講引用的第二個作用。

    ??2. 引用作為返回值

    如果內存空間在函數結束時不會被銷毀,才可以作為返回值引用返回這塊內存空間本身

    我們看這么一段代碼

    int Add(int a, int b) {int c = a + b;return c; }int main() {int a = 1;int b = 2;int& ret = Add(a, b); //如果返回的是c,那我們直接引用別名接收ccout << "ret = " << ret << endl;return 0; }

    可能有些同學會存在這樣的誤區,當我們調用Add函數后,返回的是c這個變量。

    如果這樣的結論成立,那么這段代碼就應該是能通過編譯的,但其實它不能,編譯器給我們報了錯誤

    如果我們借助前面介紹的常引用,稍微把代碼改一下

    //int& ret = Add(a, b);const int& ret = Add(a,b);

    編譯就通過了,這說明返回的不是c本身,而是一個臨時變量。

    實際在內存中是這么個過程:

    有這么兩塊棧幀。

    當我們調用完函數時,main函數上開辟了一塊臨時空間,用來接收c返回來的值,然后c內存空間被銷毀。

    被銷毀后,c的內存空間會被賦上隨機值,如果這時候系統還沒來得及給c一個隨機值,那c就還保留著原來的值。

    看看這些代碼的運行結果是什么

    int& Add(int a, int b) //注意:這里我們的返回值用了引用,因此返回的是c的內存空間 {int c = a + b;return c; //返回c本身 }int main() {int a = 1;int b = 2;int& ret = Add(a, b); // c的別名。cout << "ret = " << ret << endl;return 0; }

    雖然c已經被回收,但其中的值還未被賦上隨機值


    int& Add(int a, int b) //注意:這里我們的返回值用了引用,因此返回的是c的內存空間 {int c = a + b;return c; //返回c本身 }int main() {int a = 1;int b = 2;int& ret = Add(a, b); // c的別名。Add(6, 8);cout << "ret = " << ret << endl;return 0; }

    再次調用Add函數,c的內存空間中所保留的值被改變,且出函數后c的內存空間還未被賦上隨機值。

    這里要解釋一下,為什么第二次調用函數,還是用原來c的那塊內存空間,而不是去用其他的內存空間。

    因為銷毀后,沒有進行其他需要創建棧幀執行代碼的行為,所以第二次調Add函數的時候,調用的還是同一塊棧幀,所以改變的還是原來c那塊空間的內容。


    int& Add(int a, int b) //注意:這里我們的返回值用了引用,因此返回的是c的內存空間 {int c = a + b;return c; //返回c本身 }int main() {int a = 1;int b = 2;int& ret = Add(a, b); // c的別名。Add(6, 8);cout << "hello world" << endl; //至此,原來調用Add的那塊棧幀被修改cout << "ret = " << ret << endl;return 0; }


    上述幾種情況都涉及到了越界訪問,但都可以運行,這是因為越界訪問其實是不容易被檢查出來的,因為是抽查。

    實際上,在上述幾種情況中,c所在的棧幀在函數調用完成時就已經被回收了,不應該引用返回,再次印證,符合引用返回的條件是函數調用完成時,返回對象還未還給系統,才可以引用返回

    因此,如果用static修飾變量c——c被轉移到靜態區,Add函數調用完后,c的內存空間還在,我們在調用其它函數,開辟棧幀時,才不會改變c的數據,滿足引用返回的使用條件,這時候才可以使用引用返回。

    總結

    以上是生活随笔為你收集整理的【C++系列】引用与临时变量的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。