文章目錄
前言
一、引用
1.引用的概念和定義
2.引用的特性
3.引用的使用
4.const引用
5.指針和引用的關系
二、內聯函數
三、nullptr
總結
前言
這篇續上篇的內容新手入門指南(上),繼續帶大家學習新知識。如果你感興趣歡迎訂購本專欄。
一、引用
1.引用的概念和定義
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。如:在水滸傳里,林沖,外號豹子頭,宋江叫"鐵牛",他們都有外號,而外號的作用跟引用一樣,相當于給一個變量取別名。
using namespace std;
int main()
{int a = 3;int& b = a;//這里&符號表示引用取別名int& c = a;int& d = b;cout << &a << endl;//012FF88C //這里&符號表示取地址cout << &b << endl;//012FF88Ccout << &c << endl;//012FF88Ccout << &d << endl;//012FF88C//每個變量地址是一樣的return 0;
}
注:C++中為了避免引入太多的運算符,會復用C語言的一些符號,這里引用也和取地址使用了同一個符號&,大家注意區分使用。
2.引用的特性
1.引用在定義時必須初始化引用需要初始值設定值,不然在編譯時不知道引用指向哪個變量導致報錯
using namespace std;
int main()
{
//錯誤寫法:int a=10;int& b;
//正確寫法:int a = 10;int& b = a;return 0;
}
2.一個變量可以有多個引用
一個變量可以被多個引用指向,每個引用表示這個變量的不同別名
using namespace std;
int main()
{int a = 3;int& b = a;int& c = a;int& d = b;return 0;
}
3.引用一旦引用一個實體,再不能引用其他實體
一旦引用初始化為某個變量后,就不能指向其他變量
using namespace std;
int main()
{int x = 4, y = 5;int& z = x;z=y;//這里是把變量y賦值給引用z指向的變量x,不是把引用c又指向變量yreturn 0;
}
3.引用的使用
引用主要有兩種用法是 引用傳參 和 引用做返回值 ;優點: 減少拷貝提高效率和改變引用對象時同時改變被引用對象 。
引用做參數的時候,可以避免傳值時的拷貝開銷,提高效率。
//引用傳參
using namespace std;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;
}
引用做返回值的時候,在取棧頂時可以直接用引用做返回值,這樣出函數作用域對象還可以用。提高效率和可以修改返回對象
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)
{//assert(rs);// 滿了, 擴容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)
{return rs.a[rs.top - 1];
}
4.const引用
在使用引用會出現這種情況,int& rb = a*3; double d = 12.34; int& rd = d;
a*3 和12.34 保存在?個臨時對象中, int& rd = d 發生類型轉換?,在類型轉換中會產?臨時對象存儲中間值,rb和rd引用的都是臨時對象。C++規定臨時對象具有常性,不能修改。因此這里權限被放大,需用常引用才可以。 所以如果引用對象是需要放在臨時變量就有常性的,就需要使用常引用,權限可以縮小、平移但不能放大。
using namespace std;
int main()
{//權限不能放大:const可以把變量變常量,不能常量變變量const int a = 10;//int& ra = a;//權限可以平移://const int& ra = a;//權限可以縮小://因為const起常量的作用,所以rb雖然和b是同一個地址,但是rb不能改大只能往比b小的改int b = 20;const int& rb = b;//對于指針也是一樣//權限不能放大const int* pa = &a;//int* ppa = pa;//權限可以縮小int* pb = &b;const int* ppb = pb;return 0;
}
5.指針和引用的關系
- 引用是一個變量的取別名不開空間,指針是存儲一個變量地址,要開空間
- 引用在定義時必須初始化,指針建議初始化,但不是必須的。
- 引用在初始化時引用一個對象后,就不能再引用其他對象;而指針可以在任何時候可以改變指向對象。
- 引用可以直接訪問指向對象,指針需要解引用才是訪問指向對象。
- sizeof中含義不同,引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占4個字節,64位下是8個字節)
- 引用不存在空引用,比指針更安全,指針很容易出現空指針和野指針的問題。
二、內聯函數
在C語言中,我們了解宏的定義和用法,在實現宏時會發現宏有很多弊端,比如在宏寫一個加法函數 #define ADD(a, b) ((a) + (b))? 時,因為宏容易出錯在語法上會出“為什么里層要加括號” 、“為什么外層要加括號”以及“為什么不能加分號”等問題。 因此C語言實現宏函數也會在預處理時替換展開,但是宏函數實現很復雜很容易出錯的,且不方便調試;所以設計 inline內聯函數 來代替C的宏函數。用inline修飾的函數叫做內聯函數。 編譯時C++編譯器會在調用的地方展開內聯函數,這樣調用內聯 函數就需要建立棧幀了,就可以提高效率。
注:vs編譯器 debug版本下面默認是不展開inline的,這樣方便調試,debug版本想展開需要設置一下以下兩個地方。
從匯編層上看內聯函數是否展開,在VS編譯器上是默認不展開的,但設置一下是會展開的。
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{int ret = x + y;ret += 1;ret += 1;return ret;
}
int main()
{// 可以通過匯編觀察程序是否展開// 有call Add語句就是沒有展開,沒有就是展開了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}
沒設置之前不展開:
設置之后展開:
inline對于編譯器而言只是一個建議,也就是說,你加了inline編譯器也可以選擇在調用的地方不展開,不同編譯器關于inline什么情況展開各不相同,因為C++標準沒有規定這個。 inline適用于頻繁調用的短小函數 ,對于遞歸函數,代碼相對多一些的函數,加上inline也會被編譯器忽略。
inline不建議聲明和定義分離到兩個文件,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址,鏈接時會出現報錯。?因為分離在兩個文件,會導致預處理替換;編譯時會出現語法錯誤;調用時,聲明和定義在兩個文件中,在找聲明時,通過鏈接找到符號表,這時找不到定義,就會出現鏈接錯誤例子如下:
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{f(10);return 0;
}
會發生鏈接錯誤,編譯報錯:無法解析的外部符號 "void __cdecl f(int)" (?f@@YAXH@Z)
因為函數的聲明和定義分離,在鏈接時出錯從而編譯報錯。
三、nullptr
C++中NULL可能被定義為字面常量0,或者C中被定義為無類型指針(void*)的常量。不論采取何種定義,在使用空值的指針時,都不可避免的會遇到一些麻煩,本想通過f(NULL)調用指針版本的 f(int*)函數,但是由于NULL被定義成0,調用了f(int x),因此與程序的初衷相悖。f((void*)NULL); 調用會報錯。
#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(int
x),因此與程序的初衷相悖。f(NULL);f((int*)NULL);// 編譯報錯:error C2665: “f”: 2 個重載中沒有?個可以轉換所有參數類型// f((void*)NULL);f(nullptr);return 0;
}
運行結果:
因此,C++11中引入nullptr,nullptr是一個特殊的關鍵字,nullptr是一種特殊類型的字面量,它可以轉換成任意其他類型的指針類型。使用nullptr定義空指針可以避免類型轉換的問題,因為nullptr只能被隱式地轉換為指針類型,而不能被轉換為整數類型。
總結
非常感謝大家閱讀完這篇博客。希望這篇文章能夠為您帶來一些有價值的信息和啟示。如果您發現有問題或者有建議,歡迎在評論區留言,我們一起交流學習。