目錄
一、引用
1.1? 引用的概念和定義
1.2? 引用的特性
1.3引用的使用
1.4? const引用
1.5? 指針和引用的關系
二、inline
三、nullptr
一、引用
1.1? 引用的概念和定義
引?不是新定義?個變量,?是給已存在變量取了?個別名,編譯器不會為引?變量開辟內存空間,它和它引?的變量共?同?塊內存空間。?如:水滸傳中的林沖,外號豹?頭;
類型& 引用別名 = 引用對象;
舉個栗子:
#include<iostream>
using namespace std;
int main()
{int a = 0;
// 引?:b和c是a的別名int& b = a;int& c = a;
// 也可以給別名b取別名,d相當于還是a的別名int& d = b;++d;
// 這?取地址我們看到是?樣的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}
?
注:C++中為了避免引?太多的運算符,會復?C語?的?些符號,?如前?的 <<和 >>,這?引?也和取地址使?了同?個符號&,?家注意使??法?度區分就可以
1.2? 引用的特性
- 引?在定義時必須初始化
- ?個變量可以有多個引?
- 引??旦引??個實體,再不能引?其他實體
#include<iostream>
using namespace std;
int main()
{int a = 10;
// 編譯報錯:“ra”: 必須初始化引?//int& ra;int& b = a;int c = 20;
// 這?并?讓b引?c,因為C++引?不能改變指向,
// 這?是?個賦值b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}
1.3引用的使用
- 引?在實踐中主要是于引?傳參和引?做返回值中減少拷?提?效率和改變引?對象時同時改變被引?對象。
- 引?傳參跟指針傳參功能是類似的,引?傳參相對更?便?些。
- 引?和指針在實踐中相輔相成,功能有重疊性,但是各有特點,互相不可替代。C++的引?跟其他語?的引?(如Java)是有很?的區別的,除了?法,最?的點,C++引?定義后不能改變指向,Java的引?可以改變指向。
?之前我們在另一個函數交換值需要傳地址, 有了引用就可以直接傳引用,此時rx就相當于x的別名, 而ry就相當于y的別名
void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}
int main()
{int x = 0, y = 1;cout << x <<" " << y << endl;Swap(x, y);cout << x << " " << y << endl;return 0;
}
雖然它的底層邏輯是指針,但在語法上是取別名,不要想底層,會將其搞復雜
看下面這個代碼,原C語言中的指針,現用引用替代
#include<iostream>
using namespace std;
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{rs.a = (STDataType*)malloc(n * sizeof(STDataType));rs.top = 0;rs.capacity = n;
}
// 棧頂
void STPush(ST& rs, STDataType x)
{
// 滿了, 擴容if (rs.top == rs.capacity){printf("擴容\n");int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;} rs.a = tmp;rs.capacity = newcapacity;} rs.a[rs.top] = x;rs.top++;
} int& STTop(ST& rs)
{assert(rs.top > 0);return rs.a[rs.top];
}
int main()
{
// 調?全局的ST st1;STInit(st1);STPush(st1, 1);STPush(st1, 2);cout << STTop(st1) << endl;STTop(st1) += 10;cout << STTop(st1) << endl;return 0;
}
還有這個是許多教材上喜歡寫的,以前C語言的時候是不是很懵啊?,?些主要?C代碼實現版本數據結構教材中,使?C++引?替代指針傳參,?的是簡化程序,避開復雜的指針,但是很多同學沒學過引?,導致?頭霧?
#include<iostream>
using namespace std;
typedef struct SeqList
{int a[10];int size;
}SLT;void SeqPushBack(SLT& sl, int x)
{}
typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, *PNode;// 指針變量也可以取別名,這?LTNode*& phead就是給指針變量取別名// 這樣就不需要??級指針了,相對??簡化了程序//void ListPushBack(LTNode** phead, int x)//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));newnode->val = x;newnode->next = NULL;if (phead == NULL){phead = newnode;} else{//...}
}
int main()
{PNode plist = NULL;ListPushBack(plist, 1);return 0;
}
1.4? const引用
? 可以引??個const對象,但是必須?const引?。const引?也可以引?普通對象,因為對象的訪問權限在引?過程中可以縮?,但是不能放?。
? 不需要注意的是類似 int& rb = a*3; double d = 12.34; int& rd = d; 這樣?些場景下a*3的和結果保存在?個臨時對象中, int& rd = d 也是類似,在類型轉換中會產?臨時對象存儲中間值,也就是時,rb和rd引?的都是臨時對象,?C++規定臨時對象具有常性,所以這?就觸發了權限放?,必須要?常引?才可以。
? 所謂臨時對象就是編譯器需要?個空間暫存表達式的求值結果時臨時創建的?個未命名的對象,C++中把這個未命名對象叫做臨時對象
舉個栗子:?
int main()
{const int a = 10;// 編譯報錯:error C2440: “初始化”: ?法從“const int”轉換為“int &”// 這?的引?是對a訪問權限的放?//int& ra = a; //報錯const int& ra = a;// 這樣才可以// 編譯報錯:error C3892: “ra”: 不能給常量賦值//ra++;// 這?的引?是對b訪問權限的縮?int b = 20;const int& rb = b;// 編譯報錯:error C3892: “rb”: 不能給常量賦值//rb++;return 0;
}
#include<iostream>
using namespace std;
int main()
{int a = 10;const int& ra = 30;// 編譯報錯: “初始化”: ?法從“int”轉換為“int &”// int& rb = a * 3; //errorconst int& rb = a * 3;//rightdouble d = 12.34;// 編譯報錯:“初始化”: ?法從“double”轉換為“int &”// int& rd = d; //errorconst int& rd = d; //rightreturn 0;
}
權限放大只存在于指針和引用中,下面這兩種不要搞混了?
?
1.5? 指針和引用的關系
C++中指針和引?就像兩個性格迥異的親兄弟,指針是哥哥,引?是弟弟,在實踐中他們相輔相成,功能有重疊性,但是各有??的特點,互相不可替代。
? 語法概念上引?是?個變量的取別名不開空間,指針是存儲?個變量地址,要開空間。
? 引?在定義時必須初始化,指針建議初始化,但是語法上不是必須的。
? 引?在初始化時引??個對象后,就不能再引?其他對象;?指針可以在不斷地改變指向對象。
? 引?可以直接訪問指向對象,指針需要解引?才是訪問指向對象。
? sizeof中含義不同,引?結果為引?類型的??,但指針始終是地址空間所占字節個數(32位平臺下占4個字節,64位下是8byte)
? 指針很容易出現空指針和野指針的問題,引?很少出現,引?使?起來相對更安全?些。
int main()
{int a = 0;int* p = &a;*p = 1;int& ra = a;ra = 2;return 0;
}
這串代碼通過vs中的反匯編可以看出,指針和引用在底層是一樣的,這里大家做一個了解就行了
二、inline
? ?inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調?的地?展開內聯函數,這樣調?內聯函數就需要建?棧幀了,就可以提?效率。
? inline對于編譯器??只是?個建議,也就是說,你加了inline編譯器也可以選擇在調?的地?不展開,不同編譯器關于inline什么情況展開各不相同,因為C++標準沒有規定這個。inline適?于頻繁調?的短?函數,對于遞歸函數,代碼相對多?些的函數,加上inline也會被編譯器忽略。
? C語?實現宏函數也會在預處理時替換展開,但是宏函數實現很復雜很容易出錯的,且不?便調試,C++設計了inline?的就是替代C的宏函數。
? inline不建議聲明和定義分離到兩個?件,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址,鏈接時會出現報錯。
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{int ret = x + y;//ret += 1;//ret += 1;//ret += 1;//...return ret;
}
int main()
{// 可以通過匯編觀察程序是否展開// 有call Add語句就是沒有展開,沒有就是展開了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}
?
這里說一下 vs 編譯器debug版本下?默認是不展開inline的,這樣?便調試,debug版本想展開需要設置?下以下兩個地?
三、nullptr
NULL實際是?個宏,在傳統的C頭?件(stddef.h)中,可以看到如下代碼:
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
? C++中NULL可能被定義為字?常量0,或者C中被定義為?類型指針(void*)的常量。不論采取何種定義,在使?空值的指針時,都不可避免的會遇到?些?煩,本想通過f(NULL)調?指針版本的
f(int*)函數,但是由于NULL被定義成0,調?了f(int x),因此與程序的初衷相悖。f((void*)NULL);
調?會報錯
? C++11中引?nullptr,nullptr是?個特殊的關鍵字,nullptr是?種特殊類型的字?量,它可以轉換
成任意其他類型的指針類型。使?nullptr定義空指針可以避免類型轉換的問題,因為nullptr只能被
隱式地轉換為指針類型,?不能被轉換為整數類型
#include<iostream>
using namespace std;
void f(int x)
{cout << "f(int x)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}
int main()
{f(0);
// 本想通過f(NULL)調?指針版本的f(int*)函數,
//但是由于NULL被定義成0,調?了f(intx),因此與程序的初衷相悖。f(NULL);f((int*)NULL);
// 編譯報錯:error C2665: “f”: 2 個重載中沒有?個可以轉換所有參數類型
// f((void*)NULL);f(nullptr);return 0;
}
本篇到這里就結束了,如有問題歡迎在評論區指正。感謝!下篇見!