【C++詳解】C++入門(二)引用、內聯函數、nullptr宏

文章目錄

  • 一、引用
    • 引用的概念和定義
    • 引用的功能
    • 引用的特性
    • const引用
      • const用法回顧
      • 權限的放大縮小
      • const引用的功能
    • 指針和引用的關系
  • 二、內聯函數
  • 三、nullptr
  • 補充
    • 結構體指針變量類型重定義

一、引用

引用的概念和定義

C++祖師爺為了優化在部分場景中使用指針會出現的效率較低和比較復雜的情況,引入了一個新概念——引用。
引用不是新定義一個變量,而是為已經存在的變量取別名,編譯器不會為引用變量開辟空間,它和它引用的變量共用一塊空間,通俗來說就是為一個變量取別名,雖然叫法不同,變量的不同的別名還是指代這個變量本身,也就是你的正式名字和你的外號都是你本身,引用用法如下:
類型& 引用別名 = 引用的對象;

#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;//這里我們可以看到abcd的地址都是一樣的cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

在這里插入圖片描述

下面代碼不是讓d變成x的別名,只是賦值修改d指向的這塊空間的值。

在這里插入圖片描述

引用的功能

一、

其實引用在功能上和指針是有部分重疊的,至于怎么個重疊法呢,跟隨小編的腳步一起來看看吧。
在C語言階段我們想要實現一個交換變量值的函數時是需要傳變量的地址的,這樣形參的改變才會影響實參,現在我們了解了引用后其實就可以把它用上了,如果我們傳引用調用函數,那么函數的形參名就是實參的別名,形參發生了什么變化實參也會跟著變。
補充:變量引用的生命周期一定和變量本身一樣或者比變量本身小,比如下面x是a的引用,x出了swap這個函數后就銷毀了,但是a還存在。

void swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}void swap(int& x, int& y)
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 10;int b = 20;swap(&a, &b);cout << a << " " << b << endl;swap(a, b);cout << a << " " << b << endl;return 0;
}

在這里插入圖片描述

這里我們可以總結出引用的第一個功能:
引用做函數形參,修改形參影響實參

二、

在調用函數傳參的時候當參數很大時,比如下面這個結構體變量A,如果傳參數本身會把這個參數拷貝過去(因為形參是實參的臨時拷貝),這樣空間開銷就太大了,所以我們通常會傳它的指針,指針最大也就8個字節。我們想想,引用在這里依舊可以實現同樣的功能,前面介紹到引用是不會為變量開辟空間的,所以這里也可以采用傳引用的方式。

struct A
{int a[100];int b;
};void Func(struct A* a)
{ }void Func(struct A& aa)
{ }int main()
{struct A a;Func(&a);Func(a);
}

引用做函數形參,減少拷貝,提高效率

三、

引用不僅可以作為函數形參,還可作為函數返回值返回,在介紹引用做函數返回值有哪些功能之前,還請允許小編科普一些有關函數返回值的知識點。

在這里插入圖片描述

上面小編實現了一個簡單的傳值返回,其中變量ret在出了Func這個函數作用域后就銷毀了,所以我們可以確定x接受的返回值一定不會是ret本身。這里返回的其實是ret的一份臨時拷貝變量,并且語法規定了這個臨時變量是具有常性的,這里可以簡單理解成它被const修飾過,并且語法規定就算返回值出了作用域還存在它返回的也是臨時變量,因為編譯器不會去識別返回值出作用域是否銷毀。

有了上面的鋪墊過后接下來小編就要介紹今天的主角了。

typedef struct SeqList
{int* arr;int size;int capacity;
}SL;void SLInit(SL& sl, int n = 4)
{sl.arr = (int*)malloc(n * sizeof(SL));sl.size = 0;sl.capacity = n;
}void SLPushBack(SL& sl, int x)
{sl.arr[sl.size] = x;sl.size++;
}//該函數表示返回順序表第i個位置的數據
int& SLAt(SL& sl, int i)
{//斷言檢查i是否越界assert(i < sl.size);return sl.arr[i];
}int main()
{SL s;SLInit(s);SLPushBack(s, 1);SLPushBack(s, 2);SLPushBack(s, 3);SLPushBack(s, 4);for (int i = 0; i < s.size; i++){SLAt(s, i) += 1;}for (int i = 0; i < s.size; i++){cout << SLAt(s, i) << endl;}return 0;
}

上面是小編實現的一個簡單順序表,我們可以看到順序表尾插了四個數字1234,SLAt函數實現的是返回順序表第i個位置的數據,如果我們想要在主函數中修改該函數的返回值該怎么做呢?讀者朋友應該可以發現SLAt的返回類型是int&,這不就是才介紹的引用嗎?沒錯,這就是傳引用返回。

在這里插入圖片描述

上圖我們可以直觀看到傳值返回和傳引用返回的區別,傳值返回產生了臨時變量,所以修改臨時變量不會改變返回值,直觀來講就是順序表的值不會發生改變(并且這種操作會編譯報錯,因為臨時變量具有常性,具有常性的變量無法被修改),但是傳引用返回就不一樣了,它返回的是返回值的別名,所以不會產生臨時變量,編譯就不會報錯,我們可以簡單理解別名就是它本身,所以修改返回值的別名就是修改它自己,所以我們可以看到順序表的值確實被修改了。

注意:傳引用返回只適用于返回對象出了函數作用域還存在的情況,例如上面這個例子返回值對象是在堆上申請的。

在這里插入圖片描述

這里我們可以類比引用的前兩個功能,總結出另外兩個功能:
引用做函數返回類型,修改返回對象
引用做函數返回類型,減少拷貝,提高效率

引用的特性

  • 引用在定義時必須初始化,變量和指針都可以先定義后賦值。
	//變量可以先初始化再賦值int x;x = 10;//引用不能先初始化再賦值int& r;r = x;//錯誤用法
  • 一個變量可以有多個引用
	//變量x可以有多個引用int x = 10;int& a = x;int& b = x;int& c = x;
  • 引用一但引用一個實體,就不能引用其他實體。
    在這里插入圖片描述

就是平時所說的引用不能改變指向。(可以形象理解引用是非常忠貞的,一個別名只能對應一個對象)
所以引用是不能完全替代指針的,有些情況比如數據結構中的鏈式結構或者樹形結構我們有修改指向的需求,所以這類情況只能使用指針,指針可以修改指向。

const引用

const用法回顧

在介紹const引用之前,我們先回顧一下C語言階段我們掌握的有關const的用法:

  • const修飾變量
  • const修飾的變量無法直接修改,但是可以通過它的指針繞過他,使用它的地址修改,雖然這樣做打破了語法的規則。
	//const修飾變量const int n = 10;n = 20;     //無法實現int* pn = &n;*pn = 20;   //可以實現,但是破壞了語法規則
  • const修飾指針變量

1、const在*左邊,修飾指針指向的對象,保證指針指向的對象本身無法通過指針修改,但是指針變量本身可變。

	//const在*左邊,修飾指針指向的內容,const int* p = &n;*p = m;  //無法實現p = &m;  //可以實現

2、const在*右邊,修飾指針變量本身,保證指針變量本身無法被修改,但是指針指向的對象可以通過指針改變。

	//const在*左邊,修飾指針變量本身int* const p = &n;*p = m;  //可以實現p = &m;  //無法實現

權限的放大縮小

	const int a = 10;//權限不能放大int& r1 = a;//權限可以平移const int& r2 = a;int b = 20;//權限可以縮小const int& r3 = b;

上面我們定義了一個const修飾的int類型變量a,它具有常性無法被修改。下面我們用沒被const修飾的r1做a的別名,編譯器會報錯,因為a本身不能被修改,r1為a的別名卻沒有任何限制,可以隨意修改,這里就會涉及到權限的放大。但是權限是可以平移和縮小的,下面兩種引用編譯器都不會報錯。
注意:指針和引用才會涉及權限的放大和縮小,因為指針和引用對象的改變會影響原對象。

const引用的功能

	int a = 10;double d = 1.1;//以下兩種情況都是引用的臨時對象const int& r1 = d;       //1const int& r2 = a * 10;  //2

我們先看第一個引用,double類型的d被const修飾的int類型的r1引用,這里會發生隱式類型轉換(C語言規定相關類型可以進行隱式轉換),這里C++語法還規定類型轉換會產生臨時對象,這里r1引用的其實是那個臨時對象,臨時對象是是編譯器需要一個空間來暫存表達式的求值結果或者隱式轉化時產生的中間對象時臨時創建的一個未命名對象,它的特點是具有常性,不能修改。
所以這里r1需要加const修飾,防止權限的放大。

在這里插入圖片描述

有了上面臨時對象的概念后第二個引用為什么要加const就很好理解了,直接上圖。

在這里插入圖片描述

總結:C++引入const引用的目的就是方便傳參,后續函數形參部分若用const引用來接受,即可適配大部分傳參的情況。

指針和引用的關系

在C++中,引用和指針功能有重疊性,它們各自有各自的特點,在實踐中相輔相成,互相不可替代,所以C++從某種程度上會比C語言更便捷,能實現更多功能。
1、指針存儲一個變量地址需要開空間,引用是為一個變量取別名不用開空間。
2、引用在定義時必須初始化,指針可以先定義再初始化。
3、指針可以更改指向的對象,引用一旦引用一個實體后就不能再引用其他對象。
4、引用可以直接訪問指向對象,而指針必須解引用后訪問指向對象。
5、sizeof中含義不同,引用結果為引用類型大小,而指針始終是地址空間所占字節數(32位為4字節,64位為8字節)。
6、指針容易出現空指針和野指針的問題,引用很少出現,所以引用更安全一些。
7、在指令匯編角度,引用也是用指針實現的,但是一般我們都以語法層面為準。

二、內聯函數

1、我們在C語言階段學習的宏函數會在預處理時替換展開,但是宏函數是很容易出錯的,比如要注意運算符優先級,并且不能調試,但是函數會自動處理運算符優先級所以C++設計內聯函數就是為了替代C語言的宏函數。
2、內聯函數的關鍵字是inline,編譯時C++編譯器會在調用的地方展開內聯函數,這樣調用內聯函數就不會創建棧幀了,提高了時間效率(創建棧幀消耗時間),這里我給大家演示一下普通函數和內聯函數在匯編指令層面的區別:

inline int Add(int x, int y)
{int ret = x + y;return ret;
}
int main()
{int ret = Add(1, 2);cout << ret << endl;return 0;
}

不是內聯:
在這里插入圖片描述
是內聯:
在這里插入圖片描述

3、既然內聯函數有這么多優點,那么我們就可以無腦用它了嗎?肯定不行,因為內聯函數調用過多會造成程序變大,會占用很多空間,比如內聯函數轉化為指令有50行,調用10000次就是500000行,我們不內聯的話函數就一直待在一塊公共空間,每一次調用的時候會call指令跳轉到函數所在位置,那么就是程序指令一共就是10000+50行。世界上沒有什么是完美的,內聯函數可以簡單理解成是用空間換時間
4、inline對于編譯器而言只是一個建議,也就是說就算你加了inline編譯器也可以在調用的地方不展開,比如說遞歸函數或者代碼相對多一點的函數加了inline也會被編譯器忽略,所以短小并且需要頻繁調用的函數適合用內聯

5、內聯函數不能聲明和定義分離,因為內聯函數會直接展開,所以在編譯階段不會進符號表,那么在另一個文件調用內聯函數時,因為文件里只有內聯函數的聲明,(聲明只有無效地址,只有在函數定義才會分配確切的地址)所以在編譯階段找不到函數的地址,那么編譯器只有寄希望于鏈接的時候合并符號表時得到函數的地址,但是內聯函數不會進符號表,所以編譯器就會報鏈接錯誤。
所以內聯函數的定義和聲明只能在一起,簡單理解就是內聯函數只能在有它的定義的文件里展開,若內聯函數定義在頭文件里,其他文件包了這個頭文件,調用這個內聯函數就可以展開。

6、普通函數必須聲明和定義分離,如果普通函數定義在頭文件里,那么如果有兩個文件都包含了這個頭文件,最后兩個文件編譯后生成的目標文件都有這個函數的確切地址,后面鏈接這兩個目標文件時兩個文件的符號表都有這個函數的確切地址,就會鏈接報錯。

與前面知識的聯系:前面我們知道有static關鍵字,它修飾全局變量和函數后使得它們之只能在當前源文件里使用,之前我們的解釋是static讓它的外部鏈接屬性變成了內部鏈接屬性,其實本質就是讓他不進符號表,和inline類似。

三、nullptr

NULL實際上是一個宏,在傳統C頭文件中,可以看到如下代碼:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

C++中NULL被定義為字面常量0,C中被定義為無類型指針(void*)的常量,不論采用何種定義,在使用空值的指針時,都不可避免會遇到一些麻煩,比如下面:

在這里插入圖片描述

本來想通過f(NULL)調用指針版本的Func(int*)函數,但是由于NULL被定義成0,調用了f(int x),因此與程序初衷相悖。
所以C++11中引?nullptr,nullptr是?個特殊的關鍵字,nullptr是?種特殊類型的字面量,它可以轉換成任意其他類型的指針類型。使?nullptr定義空指針可以避免類型轉換的問題,因為為nullptr只能被隱式地轉換為指針類型,而不能被轉換為整數類型。

補充

結構體指針變量類型重定義

下面代碼上面的寫法等價于下面

typedef struct ListNode
{int val;struct ListNode* next;
}ListNode, *pnode;typedef struct ListNode ListNode;
typedef struct ListNode* *pnode;

以上就是小編分享的全部內容了,如果覺得不錯還請留下免費的贊和收藏
如果有建議歡迎通過評論區或私信留言,感謝您的大力支持。
一鍵三連好運連連哦~~

在這里插入圖片描述

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

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

相關文章

畢業設計-基于深度學習的實時網絡入侵檢測系統

項目技術說明 深度學習實時網絡入侵檢測系統是一種利用深度學習技術對網絡流量進行實時分析&#xff0c;以識別和阻止潛在網絡攻擊的安全解決方案。相比傳統基于規則的入侵檢測系統(IDS)&#xff0c;這種系統能夠通過學習網絡流量的正常模式和異常模式&#xff0c;更有效地檢測…

中藥企業數字化轉型:從傳統制造到智能制藥的跨越

在當今數字化浪潮下&#xff0c;中藥企業正積極擁抱變革&#xff0c;努力實現從傳統制造向智能制藥的跨越&#xff0c;以適應市場競爭和滿足人們對中藥質量與效率的更高要求。 在原料管理環節&#xff0c;企業通過采用物聯網技術&#xff0c;對中藥材種植、采集過程進行全程監…

Vue 2 的響應式 API 和 Vue 3 的組合式 API 的詳細對比,從核心機制、使用方式、代碼示例及優缺點展開

以下是 Vue 2 的響應式 API 和 Vue 3 的組合式 API 的詳細對比&#xff0c;從核心機制、使用方式、代碼示例及優缺點展開&#xff1a; 1. Vue 2 的響應式 API 核心機制 基于 Object.defineProperty&#xff1a; 通過劫持對象的 getter 和 setter 實現數據變化追蹤。限制&…

“八股訓練營”學習總結

在參加為期 40 天的八股訓練營的這段時間里&#xff0c;我收獲滿滿&#xff0c;不僅在知識技能上得到了提升&#xff0c;更在學習習慣和自我認知方面有了很大的進步。 在知識層面&#xff0c;訓練營涵蓋了網絡、數據庫、緩存以及python測試開發等多方面的知識點。 網絡方面&a…

Python對比兩張CAD圖并標記差異的解決方案

以下是使用Python對比兩張CAD圖并標記差異的解決方案&#xff0c;結合圖像處理和CAD結構分析&#xff1a; 一、環境準備與庫選擇 圖像處理庫&#xff1a;使用OpenCV進行圖像差異檢測、顏色空間轉換和輪廓分析。CAD解析庫&#xff1a;若為DXF格式&#xff0c;使用ezdxf解析實體…

記錄學習記錄學習《手動學習深度學習》這本書的筆記(九)

馬不停蹄地來到了第十二章&#xff1a;計算性能…… 感覺應該是講并行計算方面的&#xff0c;比如GPU、CPU、CUDA那些。 第十二章&#xff1a;計算性能 12.1 編譯器和解釋器 這里先提出了命令式編程和符號式編程的概念。 命令式編程VS符號式編程 目前為止&#xff0c;本書…

模板引擎語法-過濾器

模板引擎語法-過濾器 文章目錄 模板引擎語法-過濾器[toc]1.default過濾器2.default_if_none過濾器3.length過濾器4.addslashes過濾器5.capfirst過濾器6.cut過濾器7.date過濾器8.dictsort過濾器 1.default過濾器 default過濾器用于設置默認值。default過濾器對于變量的作用&…

make學習三:書寫規則

系列文章目錄 Make學習一&#xff1a;make初探 Make學習二&#xff1a;makefile組成要素 文章目錄 系列文章目錄前言默認目標規則語法order-only prerequisites文件名中的通配符偽目標 Phony Targets沒有 Prerequisites 和 recipe內建特殊目標名一個目標多條規則或多個目標共…

網絡安全技能大賽B模塊賽題解析Server12環境

已知靶機存在?站系統&#xff0c;使?Nmap?具掃描靶機端?&#xff0c;并將?站服務的端?號作為Flag &#xff08;形式&#xff1a;Flag字符串&#xff09;值提交 使用nmap掃描目標靶機網站服務的端口號為8089 Falg&#xff1a;8089 訪問?站/admin/pinglun.asp??&#…

1、Linux操作系統下,ubuntu22.04版本切換中英文界面

切換中英文界面的方法很多&#xff0c;我也是按照一個能用的方法弄過來并且記錄&#xff0c; 1.如果剛開始使用Ubuntu環境&#xff0c;桌面的語言環境為英文&#xff0c;需要安裝中文簡體的字體包 打開桌面終端&#xff0c;輸入 sudo apt install language-pack-zh-hans lan…

SmolVLM2: The Smollest Video Model Ever(六)

繼續微調 微調視頻的代碼如下&#xff1a; # 此Python文件用于對SmolVLM2進行視頻字幕任務的微調 # 導入所需的庫 import os os.environ["CUDA_VISIBLE_DEVICES"] "1" import torch from peft import LoraConfig, prepare_model_for_kbit_training, get…

Spring Boot安裝指南

&#x1f516; Spring Boot安裝指南 &#x1f331; Spring Boot支持兩種使用方式&#xff1a; 1?? 可作為常規Java開發工具使用 2?? 可作為命令行工具安裝 ?? 安裝前提&#xff1a; &#x1f4cc; 系統需安裝 Java SDK 17 或更高版本 &#x1f50d; 建議先運行檢查命令…

數據結構(七)---鏈式棧

#### 鏈式棧實現 ##### linkstack.h #ifndef _LINKSTACK_H #define _LINKSTACK_H // 引入相關的庫文件 #include <stdio.h> #include <stdlib.h> #include <string.h> // 定義元素類型的別名 typedef int DATA; //定義鏈式棧節點 typedef struct node { …

【Spring Boot】Maven中引入 springboot 相關依賴的方式

文章目錄 Maven中引入 springboot 相關依賴的方式1. 不使用版本管理&#xff08;不推薦&#xff09;2、使用版本管理&#xff08;推薦&#xff09;2.1 繼承 spring-boot-starter-parent2.2 使用 spring-boot-dependencies 自定義父工程2.3引入 spring-framework-bom Maven中引…

DataStreamAPI實踐原理——快速上手

引入 通過編程模型&#xff0c;我們知道Flink的編程模型提供了多層級的抽象&#xff0c;越上層的API&#xff0c;其描述性和可閱讀性越強&#xff0c;越下層API&#xff0c;其靈活度高、表達力越強&#xff0c;多數時候上層API能做到的事情&#xff0c;下層API也能做到&#x…

WPF 圖片文本按鈕 自定義按鈕

效果 上面圖片,下面文本 樣式 <!-- 圖片文本按鈕樣式 --> <Style x:Key="ImageTextButtonStyle" TargetType="Button"><Setter Property="Background" Value="Transparent"/><Setter Property="BorderTh…

驅動開發硬核特訓 · Day 22(上篇): 電源管理體系完整梳理:I2C、Regulator、PMIC與Power-Domain框架

&#x1f4d8; 一、電源子系統總覽 在現代Linux內核中&#xff0c;電源管理不僅是系統穩定性的保障&#xff0c;也是實現高效能與低功耗運行的核心機制。 系統中涉及電源管理的關鍵子系統包括&#xff1a; I2C子系統&#xff1a;硬件通信基礎Regulator子系統&#xff1a;電源…

設計模式全解析:23種經典設計模式及其應用

創建型模式 1. 單例模式&#xff08;Singleton Pattern&#xff09; 核心思想&#xff1a;確保一個類只有一個實例&#xff0c;并提供一個全局訪問點。適用場景&#xff1a;需要共享資源的場景&#xff0c;如配置管理、日志記錄等。 public class Singleton {// 靜態變量保存…

力扣熱題100題解(c++)—矩陣

73.矩陣置零 給定一個 m x n 的矩陣&#xff0c;如果一個元素為 0 &#xff0c;則將其所在行和列的所有元素都設為 0 。請使用 原地 算法。 int m matrix.size(); // 行數int n matrix[0].size(); // 列數bool firstRowZero false; // 標記第一行是否包含 0bool f…

本地部署DeepSeek-R1(Dify升級最新版本、新增插件功能、過濾推理思考過程)

下載最新版本Dify Dify1.0版本之前不支持插件功能&#xff0c;先升級DIfy 下載最新版本&#xff0c;目前1.0.1 Git地址&#xff1a;https://github.com/langgenius/dify/releases/tag/1.0.1 我這里下載到老版本同一個目錄并解壓 拷貝老數據 需先停用老版本Dify PS D:\D…