【C++深入淺出】初識C++中篇(引用、內聯函數)


目錄

一. 前言

二. 引用

2.1 引用的概念

2.2 引用的使用

2.3 引用的特性

2.4 常引用

2.5 引用的使用場景

2.6 傳值、傳引用效率比較

2.7 引用和指針的區別

?三. 內聯函數

3.1 內聯函數的概念

3.2 內聯函數的特性?


一. 前言

? ? ? ? 上期說道,C++是在C的基礎之上,容納進去了面向對象編程思想,并增加了許多有用的庫,以及編程范式等。我們介紹了部分C++為了補充C語言語法上的不足而新增的內容,如命名空間,缺省參數,函數重載等等,上期傳送門【C++深入淺出】初識C++(上篇)http://t.csdn.cn/UjbIo? ? ? ? 本期將繼續介紹C++剩下的一些有趣的功能,如引用內聯函數等等,這也是為了后面的類和對象打好基礎。?

? ? ? ? ? 話不多說,直接上菜!!!

二. 引用

2.1 引用的概念

????????引用并不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。

????????引用就相當于我們給別人起昵稱。例如你叫你女朋友小笨豬,那么對你而言,小笨豬就是你的女朋友,和叫名字是一個意思,既不是其他任何人,你也不會因此多一個女朋友

2.2 引用的使用

? ? ? ??類型& 引用變量名(對象名) = 引用實體

void Test()
{int a = 10;int& ra = a; //<====定義引用類型,此時ra就是變量a的別名,ra與a是同一塊內存空間printf("a的地址為%p\n", &a);printf("ra的地址為%p\n", &ra);
}int main()
{Test();return 0;
}

我們看到變量a和引用變量ra的地址是一樣的,說明它們共用同一塊內存空間。

?2.3 引用的特性

????????使用引用時需要注意以下幾點特性

? ? ? ? ?1、引用在定義時必須初始化

int main()
{int a = 10;int& b; //錯誤寫法,會報錯int& b=a; //正確寫法return 0;
}

? ? ? ? ?2、一個變量可以有多個引用

int main()
{int a = 10;//下面的b,c,d均是變量a的別名int& b = a;int& c = a;int& d = c;printf("%p %p %p %p\n", &a,&b,&c,&d);return 0;
}

?? ? ? ? ?3、引用一旦引用一個實體,就不能引用其他實體

int main()
{int a = 10;int& b = a; //b是a的別名int c = 20; //能不能將b改成c的別名呢?b = c; //不行,這條語句是將c的值賦給引用變量b,即修改變量a的值,并不是讓b引用cprintf("&a = %p &b = %p &c = %p\n", &a,&b,&c);printf("a = %d b = %d c = %d\n", a, b, c);return 0;
}

?? ? ? ? ?4、引用類型必須和引用實體是同種類型的

int main()
{int a = 10;double& b = a; //這種寫法會報錯return 0;
}

? ? ? ? 我們看到編譯器報錯說非常量限定,那如果我們加上const修飾呢?如下:

    const double& b = a;

? ? ? ? 我們驚訝地發現通過了編譯,說明上面不是因為int和double類型不一樣而報錯,那究竟是為什么呢?下面我們來分析分析

? ? ? ? ?實際上,由于引用實體和引用變量的類型不同,編譯器會自動進行隱式類型轉換。編譯器會生成一個double類型的臨時變量tmp,然后將a的內容以某種形式放到臨時變量tmp中,最后再讓b引用臨時變量tmp

int main()
{int a = 10;const double& b = a;//類似于下面的步驟int a = 10;double tmp = a; //將a的值轉換賦給tmpconst double& b = tmp; //b再引用tmpreturn 0;
}

? ? ? ? ?由于臨時變量具有常屬性,因此tmp的類型就是const double,用double類型的引用變量引用const double類型的變量,這無疑是一種權限的放大,是不被允許的。就好比別人大門緊縮不然你進,你偏偏另辟蹊徑從窗戶翻入,這無疑是犯法的,私闖民宅。這就是為什么編譯器會報出非常量限定的錯誤的原因,引用變量d需要加上const修飾,權限的平移是被允許的。

? ? ? ? 最后,本來臨時變量tmp在當前語句結束后就會被銷毀,但此時被b所引用,其生命周期就自動被延長了

? ? ? ? ?分析了這么多,下面我們用代碼來進行驗證一下:

int main()
{int a = 10;const double& b = a;printf("&a = %p , &b = %p\n", &a, &b); //求a,b空間的地址printf("修改前 a = %d , b = %.2lf\n", a, b);a = 20;//b = 30; //這句代碼會報錯,被const修飾的變量不可修改printf("修改后 a = %d , b = %.2lf\n", a, b);return 0;
}

我們發現a的地址和b的地址不同,這說明了b并不是變量a的引用,而是引用了新形成的臨時變量。并且,當我們對a進行修改時,b中的內容并沒有發生改變,這也印證了a和b不是同一塊內存空間。最后,當我們想要對b的內容進行修改時,編譯器會直接報錯,說明b所在的空間具有常屬性


2.4 常引用

? ? ? ? 被const關鍵字修飾的引用變量我們稱為常引用。我們無法通過常引用來修改引用實體的值,如下:

#include<iostream>
using namespace std;
int main()
{int a = 10;const int& b = a;//b++; //會報錯,b是常引用,無法修改a++; //a是普通變量,允許修改cout << "a = " << a <<' ' << "b = " << b;return 0;
}

? ? ? ? 前面我們提到了權限不能放大,也就是說:普通引用不能引用常屬性變量。但是,權限允許平移或者縮小,即常引用可以引用常屬性變量常引用可以引用普通變量。如下:

#include<iostream>
using namespace std;
int main()
{int a = 10;const int& b = a; //權限的縮小,const引用引用普通變量,編譯正常const int aa = 10;const int& bb = aa;//權限的縮小,const引用引用const變量,編譯正常int& cc = aa;//權限的放大,普通引用引用const變量,報錯return 0;
}

?2.5 引用的使用場景

? ? ? ? 引用的使用場景一般有兩個:做函數參數做函數返回值

? ? ? ? 1、引用作為函數參數

????????C語言中,如果我們調用函數時使用傳值調用,那么形參的改變是不會影響實參的,形參是實參的臨時拷貝。如果我們想在函數中對實參進行修改,那就必須使用傳址調用,通過地址對實參的值進行修改。

? ? ? ? 而在C++中,新增了引用的語法,我們可以使用引用作為函數的形參,此時形參就是實參的一個別名,并不會額外開辟空間,形參和實參共同內存空間,修改形參也就是對實參進行修改。具體實現方式如下

#include<iostream>
using namespace std;
void ModifyFun(int& x) //引用作為函數參數
{x = 100;
}
int main()
{int a = 10;cout << "調用前" << a << endl;ModifyFun(a);cout << "調用后" << a << endl;
}


#include<iostream>
using namespace std;
void Swap(int& x , int& y) //引用作為函數參數
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 10;int b = 20;cout << "交換前:" << "a = " << a << " b = " << b << endl;Swap(a, b);cout << "交換后:" << "a = " << a << " b = " << b << endl;
}

? ? ? ? 2、引用作為函數返回值

? ? ? ? 引用也可以作為函數的返回值,如下:

#include<iostream>
using namespace std;
int& Count()
{static int n = 0; //n是一個靜態變量,函數調用結束后不會銷毀cout << n << endl;;return n;
}
int main()
{int& k = Count();k++;Count();return 0;
}

在Count()函數通過引用返回n,此時main函數中的引用變量k就是n的別名,當我們在main函數中修改k,就相當于對靜態變量n做修改。

? ? ? ? 但是,如果以下情況使用引用返回會出現什么情況呢??

int& Add(int a, int b)
{int c = a + b; //c是局部變量,Add調用結束后被銷毀return c;
}
int main()
{int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :" << ret << endl;return 0;
}

?很驚訝的發現,最終ret變量的值不是3而是7,為什么呢?

?這就要來談談上述代碼出現的野引用問題了。

我們通過下圖來進行分析?

?總結:函數返回時,如果出了函數作用域,返回對象還在(還沒銷毀還給系統),則可以使用
引用返回;如果已經還給系統了,則必須使用傳值返回。

2.6 傳值、傳引用效率比較

? ? ? ? 在C/C++中,以值作為參數或者返回值類型,在傳參和返回期間,函數并不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時拷貝,因此用值作為參數或者返回值類型,需要額外進行拷貝,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低。

? ? ? ? 而如果以引用作為參數或者返回值類型,由于引用是作為變量的別名,并不會額外開辟空間形成拷貝。因此在傳參和返回期間,就相當于直接傳遞實參或將變量本身直接返回,效率大大提升。下面我們通過代碼來更直觀地看看二者的效率差距:

? ? ? ? 值和引用作為函數參數的效率差距

#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a)
{;
}
void TestFunc2(A& a)
{;
}
void TestRefAndValue()
{A a;// 以值作為函數參數size_t begin1 = clock(); //clock()函數返回程序運行到調用clock()函數所耗費的時間,單位是msfor (size_t i = 0; i < 100000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作為函數參數size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2(a);size_t end2 = clock();// 分別計算兩個函數運行結束后的時間cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{TestRefAndValue();return 0;
}


?? ? ? ? 值和引用作為返回值類型的效率差距

#include <time.h>
struct A 
{int a[10000]; 
}a;
// 值返回
A TestFunc1() 
{ return a; 
}
// 引用返回
A& TestFunc2() 
{ return a; 
}
void TestRefAndValue()
{// 以值作為函數的返回值類型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作為函數的返回值類型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 計算兩個函數運算完成之后的時間cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{TestRefAndValue();return 0;
}

通過上述代碼的比較,我們發現傳值和引用在作為傳參以及返回值類型上效率相差很大。傳引用的效率遠高于傳值。因此能使用引用就盡量使用引用,提高效率。

2.7 引用和指針的區別

????????在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。

? ? ? ? 但在底層實現上實際是有空間的,因為引用是按照指針方式來實現的。這點我們可以參照二者編譯后生成的匯編代碼證明

int main()
{//引用int a = 10;int& ra = a;//指針ra = 20;int* pa = &a;*pa = 20;return 0;
}

可見,引用和指針的匯編代碼是一模一樣的,最后都是通過變量a的地址來修改a。


不過,引用和指針還是有不同點的,如下:

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

三. 內聯函數

3.1 內聯函數的概念

????????以inline關鍵字修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,不會調用函數建立棧幀,因此內聯函數提升程序運行的效率。
? ? ? ? 我們可以通過匯編代碼來驗證加上inline的函數是否會被調用

? ? ? ? 沒加inline關鍵字:

int Add(int x, int y)
{return x + y;
}
int main()
{int ans = 0;ans = Add(1, 2);return 0;
}

?? ? ? ? ?加上inline關鍵字:

inline int Add(int x, int y)
{return x + y;
}
int main()
{int ans = 0;ans = Add(1, 2);return 0;
}

可以看到,內聯函數并不會生成對應的call指令,而是直接被替換到函數調用處,減少了調用函數建立棧幀的開銷。

?? ? ? ? ?注意事項:

? ? ? ? ??內聯函數的效果需要在release模式才會體現。因為在debug模式下編譯器默認不會對代碼進行優化,顧不會進行展開。當然我們也可以進行設置,方法如下(VS2022):

1、找到當前項目屬性設置頁:

?2、設置調試信息格式:

??3、設置內聯函數擴展:


3.2 內聯函數的特性?

主要有如下幾點特性:

  1. inline是一種以空間換時間的做法,如果編譯器將函數當成內聯函數處理,在編譯階段,會用函數體替換函數調用。缺陷:可能會使目標文件變大;優勢:少了調用建立棧幀開銷,提高程序運行效率。
  2. inline對于編譯器而言只是一個建議,不同編譯器關于inline實現機制可能不同,一般建
    議:將函數規模較小(即函數不是很長,具體沒有準確的說法,取決于編譯器內部實現)、不是遞歸、且頻繁調用的函數采用inline修飾,否則編譯器會忽略inline特性(編譯器也是很聰明的,可不要貪杯噢)。以下為《C++prime》第五版關于inline的描述:
  3. inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,符號表中就沒有函數地址了,鏈接就會找不到。
    // in.h
    #include <iostream>
    using namespace std;
    inline void f(int i);// in.cpp
    #include "in.h"
    void fun(int i)
    {cout << i << endl;
    }// main.cpp
    #include "in.h"
    int main()
    {fun(10);return 0;
    }

    報錯原因:由于in.h文件中只有函數的聲明沒有定義,顧在編譯階段main.cpp中的fun() 函數無法進行展開,只能在鏈接階段進行鏈接。但是由于in.cpp的fun()函數被聲明為內聯函數,fun()函數并不會進入符號表,最后就會導致鏈接時找不到函數地址,報錯。


以上,就是本期的全部內容啦🌸

制作不易,能否點個贊再走呢🙏

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/42845.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/42845.shtml
英文地址,請注明出處:http://en.pswp.cn/news/42845.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

onvif中imaging setting圖像畫質總結!

前言&#xff1a; 大家好&#xff0c;今天給大家來分享一篇關于圖像質量的內容&#xff0c;這個內容是我在做onvif中的imaging setting的時候&#xff0c;關注到里面有關于: brightness(亮度)color saturation(色彩飽和度)contrast(對比度)sharpness(銳度)white balance(白平衡…

C語言刷題指南(二)

&#x1f4d9;作者簡介&#xff1a; 清水加冰&#xff0c;目前大二在讀&#xff0c;正在學習C/C、Python、操作系統、數據庫等。 &#x1f4d8;相關專欄&#xff1a;C語言初階、C語言進階、C語言刷題訓練營、數據結構刷題訓練營、有感興趣的可以看一看。 歡迎點贊 &#x1f44d…

TDI(Time Delay Integration)

TDI&#xff08;Time Delay Integration&#xff09;是一種特殊的圖像采集技術&#xff0c;常用于線陣CCD&#xff08;Charge-Coupled Device&#xff09;相機。TDI技術可以在保持高分辨率的同時增強圖像的信噪比&#xff08;Signal-to-Noise Ratio, SNR&#xff09;&#xff0…

kubesphere 集成 sonar

文章目錄 安裝 helm通過 helm 安裝 sonar配置 SonarQube 服務器創建 SonarQube 管理員令牌SonarQube 配置添加到 ks-installer創建 Webhook 服務器將 SonarQube 服務器添加至 Jenkins將 sonarqubeURL 添加到 KubeSphere 控制臺重啟服務 為新項目創建 SonarQube Token 官方文檔&…

Threejs學習04——球緩沖幾何體環境光以及直線光源

實現隨機多個三角形隨機位置隨機顏色展示效果 這是一個非常簡單基礎的threejs的學習應用&#xff01;本節主要學習的是球面緩沖幾何體在環境光合直線光源下的效果&#xff0c;可以學習到環境光和直線光源的生成效果等功能&#xff01;主要使用的是球緩沖幾何體對象SphereGeome…

使用el-tree實現自定義樹結構樣式

實現效果: 直接上代碼: <template><div><div class"tops"><el-tree :default-expanded-keys"[1]" ref"myTree" :data"data" :props"defaultProps" node-click"handleNodeClick" highlight…

【uniapp】picker mode=“region“ 最簡單的省市區 三級聯動

省市區 picker template <picker mode"region" :value"date" class"u-w-440" change"bindTimeChange"><u--inputborder"bottom"class"u-fb u-f-s-28"placeholder"請選擇省市區"type"te…

第8章 對同步的硬件支持 摘錄

為了保證并行程序執行的正確性和高效性&#xff0c;構建一個共享存儲多處理器系統的硬件必須要解決緩存一致性、存儲一致性和同步原語的支持等問題。 被廣泛使用的同步原語包括鎖lock、柵欄barrier和點對點同步(signal和wait信號量)。舉例來說&#xff0c;鎖和柵欄被大量使用在…

ARM 作業1

一、思維導圖 二、 1. 2. .text 文本段 .globl _start 聲明_start:mov r0,#0mov r1,#0fun:cmp r1,#100bhi stopadd r0,r0,r1add r1,r1,#1b fun stop:b stop .end

C++函數模板和類模板

C另一種編程思想稱為泛型編程&#xff0c;主要利用的技術是模板 C提供兩種模板機制&#xff1a;函數模板和類模板 C提供了模板(template)編程的概念。所謂模板&#xff0c;實際上是建立一個通用函數或類&#xff0c; 其類內部的類型和函數的形參類型不具體指定&#xff0c; 用…

Axios使用CancelToken取消重復請求

處理重復請求&#xff1a;沒有響應完成的請求&#xff0c;再去請求一個相同的請求&#xff0c;會把之前的請求取消掉 新增一個cancelRequest.js文件 import axios from "axios" const cancelTokens {}export const addPending (config) > {const requestKey …

如何區分閏年與平年

首先要明白 地球繞太陽運行周期為365天5小時48分46秒&#xff08;合365.24219天&#xff09;&#xff0c;即一回歸年&#xff08;tropical year&#xff09;。公歷的平年只有365日&#xff0c;比回歸年短約0.2422 日&#xff0c;每四年累積約一天&#xff0c;把這一天加于2月末…

Docker安裝基礎使用練習

目錄 1、安裝Docker-CE 1&#xff09;簡單使用yum方式安裝 ! 2&#xff09;配置鏡像加速&#xff1a; 2、下載系統鏡像&#xff08;Ubuntu、 centos&#xff09; 1&#xff09;先查看我們所需的鏡像有哪些版本。使用search命令&#xff01; 2&#xff09;下載鏡像使用的是pul…

【爬蟲】P1 對目標網站的背景調研(robot.txt,advanced_search,builtwith,whois)

對目標網站的背景調研 檢查 robot.txt估算網站大小識別網站所用技術尋找網站的所有者 檢查 robot.txt 目的&#xff1a; 大多數的網站都會包含 robot.txt 文件。該文件用于指出使用爬蟲爬取網站時有哪些限制。而我們通過讀 robot.txt 文件&#xff0c;亦可以最小化爬蟲被封禁的…

vue中實現文字檢索時候將搜索內容標紅

實現結果 html&#xff1a; <div class"searchBox"><span class"bt">標&#8195&#8195題</span><div class"search"><div class"shuru"><!-- <span class"title">生產經營<…

[leetcode] 707 設計鏈表

707. 設L計鏈表 中等 902 相關企業 你可以選擇使用單鏈表或者雙鏈表&#xff0c;設計并實現自己的鏈表。 單鏈表中的節點應該具備兩個屬性&#xff1a;val 和 next 。val 是當前節點的值&#xff0c;next 是指向下一個節點的指針/引用。 如果是雙向鏈表&#xff0c;則還需…

如何批量修改圖片名為不同名稱

如何批量修改圖片名為不同名稱&#xff1f;當今社會&#xff0c;因為人們都養成了隨手拍照的習慣&#xff0c;所以擁有上千上萬張照片的相冊已經司空見慣不足為奇。然而&#xff0c;我們在保存這些照片時往往都會碰到一個大難題——電腦中的圖片名稱千奇百怪&#xff0c;讓整個…

C++并發多線程--std::async、std::packaged_task和std::promise的使用

目錄 1--std::async的使用 2--std::packaged_task的使用 3--std::promise的使用 1--std::async的使用 std::async用于啟動一個異步任務&#xff0c;并返回一個std::future對象&#xff1b;std::future對象里含有異步任務線程入口函數的結果&#xff1b; std::launch::deferr…

完美解決微信小程序使用復選框van-checkbox無法選中

由于小程序使用了vant-ui框架&#xff0c;導致checkbox點擊無法選中問題 <van-checkbox value"{{ checked }}" shape"square"><view class"check-content"><view class"checktext">我已閱讀并同意>《用戶協議》…

opencv-目標追蹤

import argparse import time import cv2 import numpy as np# 配置參數 ap argparse.ArgumentParser() ap.add_argument("-v", "--video", typestr,help"path to input video file") ap.add_argument("-t", "--tracker", …