引用
6.1 引用概念
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。
比如:李逵,在家稱為"鐵牛",江湖上人稱"黑旋風"
typedef 是給類型取別名
引用是給變量取別名
注意:引用類型必須和引用實體是同種類型的
類型& 引用變量名(對象名) = 引用實體;
int main()
{int a = 0;//定義一個變量a,賦值為0,int &b = a;//這里的&不是取地址符,而是引用,給變量取別名, &定義在一個類型和變量之間為引用//定義一個空間為a,又給這個空間a取別名為bint a = 0;int b = a;//這種寫法和上面是完全不同的,//這里是 定義一個變量為a,a的值是0,定義一個變量為b,用a的0初始化return 0;
}
如何證明是否公用一塊空間?
#include <iostream>
using namespace std;int main()
{int a = 0;int& b = a;cout << &b << endl;//取地址cout << &a << endl;//取地址a++;b++;return 0;
}
6.2 引用的特性
1. 引用在定義時必須初始化
int main()
{int a = 1;int& b;//引用在定義時必須初始化return 0;
}
2. 一個變量可以有多個引用(可以取無限個別名)
int main()
{int a = 1;int &b = a;int &c = a;int &d = c;//還可以給別名取別名a++;return 0;
}
3. 引用一旦引用一個實體,再不能引用其他實體
指針指向一個實體后,可以改變,引用不可以改變
int main()
{int a = 1;int& b = a;int& c = a;int& d = c;int x = 10;//引用一旦擁有一個實體,再不能引用其他實體b = x;b是x的別名呢?還是x賦值為b?return 0;
}
這里把x賦值給b
6.3 常引用
權限不能放大
int main()
{int a = 13;int& b = a;//權限不能放大const int c = 20;//int& d = c;const int& d = c;//權限可以縮小,從只讀的角度int e = 25;const int& f = e; return 0;
}
隱式類型轉換和強制類型轉換都是會產生臨時變量
所有的轉換都不是對原變量進行轉換和提升,而是之間產生臨時變量
并不會改變原變量類型,
這里本質上是權限的放大和縮小
類型轉換中間是會產生臨時變量的,臨時變量具有常性,相當于const修飾
int main()
{int ii = 1;doubble dd = ii;//int類型可以給double,會發生隱式類型轉換double& rdd = ii;//double類型的數據可以變成int的引用嗎?//不可以 這里不能引用的原因不是說類型不匹配,而是不能進行權限的放大const double& rdd = ii;//這里可以引用是因為權限的平移 中間產生的臨時變量也是相當于const修飾的變量//此時rdd引用的不是ii,而是int轉換成double之間產生的臨時變量,//這個臨時變量具有常性return 0;
}
const 可以把一個常量起一個別名
const int& x = 10;
應用場景:
如果使用引用傳參,函數內如果不改變n,那么盡量用const引用傳參
const引用傳參會有非常強的接受度
????????
6.4 引用的使用場景
1.做參數
a.輸出型參數
b.大對象傳參,提高效率
2.做返回值
a.輸出型返回對象,調用者可以修改返回對象.
b.減少拷貝提高效率,
不是什么時候都可以使用傳引用返回,要注意使用場景
出了作用域,返回對象還在可以用傳引用返回
出了作用域,返回對象銷毀.那么一定不能用引用返回,一定要用傳值返回
1. 做參數 --(引用的輸出型參數)
如果不用引用,傳入的是實參的一份臨時拷貝,形參的改變不會影響實參
如果想改變 必須使用指針 傳入地址
在C++里,這里x是a的別名,y是b的別名,x和y的交換就是a和b的交換
void Swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 3;int b = 5;cout <<"交換前:" << a << ' ' << b << endl;Swap(a, b);cout <<"交換后:" << a << ' ' << b << endl;return 0;
}
例如:有些書里會把數據結構里鏈表部分弄得很復雜
typedef struct SListNode
{.....
}SLTNode,*PSLTNode;
//這里的*PSLTNode 相當于typedef struct SListNode* PSLTNode 給這個結構體指針起了個別名 PSLTNode
void SListPushBack(PSLTNode& phead,int x)//這里的引用相當于 phead是list的別名,phead的改變會影響list
{.....
}
int main()
{SLTNode* list = NULL;SListPushBack(list,1);SListPushBack(list,1);SListPushBack(list,1);return 0;
}
大對象傳參 提高效率
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{A a;// 以值作為函數參數size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作為函數參數size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();// 分別計算兩個函數運行結束后的時間cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
2. 做返回值
傳值返回 都會生成一個返回對象拷貝作為函數調用的返回值
返回的是n嗎 是用n的拷貝來做表達式的返回值
為什么編譯器要這么做?
如果這里沒有static ,n返回時,出了該作用域 會銷毀 置為隨機值
傳引用返回:
引用返回的語法含義:返回返回對象的別名
不是什么時候都可以使用傳引用返回,要注意使用場景
返回n的別名 越界了為什么不報錯呢?
ret的結果是未定義的 如果棧幀結束時,系統清理棧幀置成隨機值,那么這里ret的結果就是隨機值
下面程序使用引用返回本質是不對的,結果是沒有保障的
int& Count()
{int n = 0;n++;// ...return n;
}
int main()
{int ret = Count();return 0;
}
如果引用ret ,那么相當于 ret也是n的別名,結果會不會有所不同
int& Count()
{int n = 0;n++;// ...return n;
}
int main()
{int& ret = Count();printf("%d\n", ret);printf("%d\n", ret);printf("%d\n", ret);printf("%d\n", ret);return 0;
}
總結:出了函數作用域,返回對象就銷毀了,那么一定不能用引用返回,一定要用傳值返回
什么時候可以用引用返回?
使用static
1. static修飾變量
???a. 函數中局部變量:
??????聲明周期延長:該變量不隨函數結束而結束
??????初始化:只在第一次調用該函數時進行初始化
??????記憶性:后序調用時,該變量使用前一次函數調用完成之后保存的值
??????存儲位置:不會存儲在棧上,放在數據段
int& Count()
{static int n = 0;n++;// ...return n;
}
int main()
{int& ret = Count();printf("%d\n", ret);printf("%d\n", ret);printf("%d\n", ret);printf("%d\n", ret);return 0;
}
const引用
為什么這里給c取別名不可以?
本質上是權相的放大 c自己是const類型 不可以修改
用d取別名 使得d是權限的放大 可以修改
#include <iostream>using namespace std;int main()
{int a = 10;//權限的的平移int& b = a;const int c = 20;//權限的放大int& d = c;int e = 30;//權限的縮小const int&f = e;//只讀的角度變成你的別名return 0;
}
權限不可以放大,但是權限可以縮小
int main()
{int ii = 1;double dd = ii;//發生隱式類型轉換double& rdd = ii;//double不可以變成int的引用//不可以使用的本質原因是 因為權限的放大,因為 int 類型要轉換成double類型//中間要先生成一個double類型的臨時變量,相當于 const double ii;//double& dd = const double ii; //權限的放大不可以const double& rdd = ii;//可以使用權限的平移return 0;
}
強制轉換并不是改變原變量類型 他也是產生一個臨時變量
如果使用引用傳參,函數內如果不改變n,那么盡量用const引用傳參
void func1(int& n)
{}
void func2(const int& n)
{}
int main()
{int a = 10;const int b =20;func1(a);func1(b);func1(30);func2(a);func2(b);func2(30);double c = 1.11;func2(c);return 0;
}
指針和引用的差別
指針和引用用途基本是相似的
但有
1.使用場景的差別
引用和指針的不同點:
1. 引用在定義時必須初始化,指針沒有要求
2. 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型
實體
3. 沒有NULL引用,但有NULL指針
4. 在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占
4個字節)
5. 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
6. 有多級指針,但是沒有多級引用
7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
8. 引用比指針使用起來相對更安全
2.語法特性以及底層原理的差別
從語法角度而言,引用沒有開辟空間,指針開了四個字節或者8個字節的空間
底層實現角度,那么引用底層是用指針實現的
個人水平不足 如果代碼中有錯誤,可以多多在評論區指出,一定會及時修改!
謝謝大家看到這里 覺得有收獲的話可以三連一下 一起加油!