【C++】入門基礎【下】

目錄

  • 一、缺省參數
  • 二、函數重載
    • 1. 函數類型不同
    • 2. 參數個數不同
    • 3、函數類型順序不同
  • 三、引用
    • 1、引用的概念和定義
    • 2、引用的功能
      • 2.1 功能1: 做函數形參,修改形參影響實參
      • 2.2 功能2: 做函數形參,減少拷貝,提高效率
      • 2.3 功能3:引用做返回值類型,修改返回對象
      • 2.4 功能4: 引用做返回值類型,減少拷貝,提高效率
    • 3、引用的特性
    • 4、`const`引用
    • 5、指針和引用的關系
    • 6、`inline`
    • 7、`nullptr`

【C++】入門基礎【上】<–請點擊
在這里插入圖片描述
個人主頁<—請點擊
C++專欄<—請點擊

一、缺省參數

  • 缺省參數是聲明或定義函數時為函數的參數指定?個缺省值。在調用該函數時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參,缺省參數分為全缺省半缺省參數。(有些地方把缺省參數也叫默認參數)。

下面我們通過代碼來認識一下缺省參數。

void func(int a = 10)
{cout << a << endl;
}

我們知道,在C語言中函數定義中變量是不能給值的,但在C++中卻可以,這時候,給定的這個值就是缺省值,那這個參數就是缺省參數

那了解了缺省參數是什么,那么它在使用的時候又該怎么用能?請看代碼:

func();
func(5);

此時我們要調用我們定義的函數,并且一個不給實參,一個給實參,讓我們一起看一下代碼的運行情況。
在這里插入圖片描述
可以看到,沒給實參的,函數默認使用了缺省值,而給定實參的,函數使用的是給定的實參而在C語言中,如果我們不給實參但函數又需要實參時,此時程序就會報錯,所以C++的缺省參數優化了這一問題。

  • 全缺省就是全部形參給缺省值,半缺省就是部分形參給缺省值C++規定半缺省參數必須從右往左依次連續缺省不能間隔跳躍給缺省值

如果你這樣定義代碼:

void func1(int a = 100, int b)
{cout << a<< " "<< b << endl;
}

會報錯:
在這里插入圖片描述
因為半缺省參數必須從右往左依次連續缺省

  • 帶缺省參數的函數調用,C++規定必須從左到右依次給實參,不能跳躍給實參
    在這里插入圖片描述
    像上圖打算只給第二個傳實參,跳過第一個時就會報錯。

  • 函數聲明和定義分離時缺省參數不能在函數聲明定義同時出現規定必須函數聲明缺省值

void func2(int a = 100);
void func2(int a = 100)
{cout << a << endl;
}

在這里插入圖片描述
應改為:

void func2(int a = 100);
void func2(int a)
{cout << a << endl;
}

二、函數重載

我們知道C語言中不支持在同一作用域中出現同名函數的,它會報錯,但在C++中是被允許的。C++支持在同?作用域中出現同名函數但是要求這些同名函數的形參不同,可以是參數個數不同或者類型不同如果全部相同那在C++中也是不被允許的

1. 函數類型不同

void func(int x, int y)
{cout << x + y << endl;
}void func(double x, double y)
{cout << x + y << endl;
}

測試結果:
在這里插入圖片描述
從上面的測試結果,可以看出函數的調用成功而且正確

2. 參數個數不同

void f()
{cout << "hello world!" << endl;
}void f(int a)
{cout << a << endl;
}

測試結果:
在這里插入圖片描述
注意:這里的第二個函數的參數不能帶缺省值,如果帶缺省值的話,當我們調用f()時,第一個函數和第二個函數都滿足,此時程序會報出下面的錯誤:
在這里插入圖片描述
這樣編譯器就會不知道調用誰,所以我們寫重載函數的時候一定要注意區分它們,不能讓它們存在歧義。

3、函數類型順序不同

void fd(int a, double b)
{cout << "fd(int a, double b)" << endl;
}void fd(double b, int a)
{cout << "fd(double b, int a)" << endl;
}

測試結果
在這里插入圖片描述

三、引用

1、引用的概念和定義

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

引用的用法類型& 引?別名 = 引?對象;

C++中為了避免引入太多的運算符,會復用C語言的?些符號,引用取地址使用了同?個符號&,大家注意使用方法角度區分就可以。

#include<iostream>
using namespace std;int main()
{int a = 10;//b和c是a的別名int& b = a;int& c = a;cout << a << endl;b++;cout << a << endl;c++;cout << a << endl;return 0;
}

結果:
在這里插入圖片描述

#include<iostream>
using namespace std;int main()
{int a = 10;//b和c是a的別名int& b = a;int& c = a;//d也是a的別名int& d = b;cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

在這里插入圖片描述
它們的地址都是一模一樣的。

2、引用的功能

2.1 功能1: 做函數形參,修改形參影響實參

我們在C語言階段實現交換函數的時候是用指針來實現的,現在引用也可以起到這樣的作用。

#include<iostream>
using namespace std;void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}int main()
{int x = 10;int y = 20;Swap(x, y);cout << "x=" << x << endl;cout << "y=" << y << endl;return 0;
}

交換結果:
在這里插入圖片描述

2.2 功能2: 做函數形參,減少拷貝,提高效率

不使用引用時,值傳遞會觸發原對象的拷貝,如果對象比較大(如包含復雜成員,動態內存或嵌套結構),拷貝操作的時間和空間開銷顯著。

而當使用引用時,引用就是原對象的別名,傳遞時不會產生拷貝,直接操作原始對象,省去了拷貝構造的開銷,尤其對大型對象效果顯著。

2.3 功能3:引用做返回值類型,修改返回對象

#include <iostream>
using namespace std;int& func(int* a, int n)
{return a[n];
}void print(int* a,int n)
{for (int i = 0;i < n;i++){cout << a[i]<<" ";}cout << endl;
}int main()
{int a[11];for (int i = 0;i < 11;i++){a[i] = i + 1;}print(a, 11);func(a, 2)+=10;print(a, 11);
}

從上面代碼中可以看出,func函數的返回值類型是引用充當的,這樣的好處是可以更改返回對象。

運行結果:
在這里插入圖片描述
從運行結果可以看出數組中下標為2的位置被更改了。

2.4 功能4: 引用做返回值類型,減少拷貝,提高效率

我們知道當函數調用結束時函數棧幀會被銷毀,那么其中定義的局部變量的生命周期也就結束了自然也會被銷毀,當函數的返回值是函數中定義的局部變量時,編譯器會將返回值拷貝下來,然后儲存在臨時變量中作為返回值

假設有如下函數:

int func()
{int set = 10;return set;
}

在這里插入圖片描述
那既然傳引用返回可以更改返回對象,那傳值返回可以嗎?下面我們來試一下。我們還是使用傳引用返回的代碼,但是將傳引用返回改為傳值返回。

發現程序會報出以下錯誤:
在這里插入圖片描述
這是因為函數返回的是值,而在函數返回值之前同樣會將返回的值拷貝下來,儲存在臨時變量中進行返回,而臨時變量它具有常性,是不可修改的,所以才會報出以上錯誤。

下圖是它們三者之間的區別
在這里插入圖片描述
所以說引用做返回值類型,減少了拷貝,提高了效率。

產生臨時變量的情況:

出現類型轉換的時候也會產生臨時變量,像double類型d=1.5,轉化為int類型的x這種情況,會產生一個臨時變量存儲轉換結果,然后再將臨時變量賦值給x

產生臨時變量的情況有以下幾種:類型轉換、值傳遞、表達式求值等等。
其中值傳遞就是我們上圖展示的情況,表達式求值例如:

int a = 1;
int b = 9;
int c = a + b * 10;

計算b*10時生成臨時int,再與a相加生成臨時int,最后賦值給c

不安全的引用寫法:

int& func()
{int set = 10;return set;
}

原因:set是局部變量,func結束后,set就銷毀了,返回它的別名本質也是一種類似野指針的行為。

3、引用的特性

  • 引用在定義時必須初始化;
  • ?個變量可以有多個引用;
  • 引用?旦引用?個實體,再不能引用其他實體。

引用無法改變指向所以在鏈式結構中無法替代指針,這樣的場景下必須使用指針。

4、const引用

可以引用?個const對象,但是必須用const引用const引用也可以引用普通對象,因為對象的訪問權限在引用過程中可以縮小,而不能放大

權限放大的錯誤樣例

const int x = 10;
int& y = x;

在這里插入圖片描述
最初定義的x的本意就是x不可更改,但卻使用int&引用x這就導致了沖突,它的權限被放大了。

正確的使用:

const int x = 10;
const int& y = x;

注意以下這種情況中沒有權限的放大

const int x = 10;
const int& y = x;
int z = y;

有人在學習完成引用后他們會認為上面這段代碼涉及到了權限的放大,但上面代碼的意圖是定義一個變量z,并將y值賦值給z,只是一個簡單的賦值操作,注意不要混淆了。

權限的縮小是被允許的

就像老師對你說,下課不準出校門,但你連教室門都不出這種情況一樣。

int x = 10;
const int& y = x;

5、指針和引用的關系

  • 語法概念上引用是?個變量的取別名,不開空間,指針是存儲?個變量地址,要開空間。
  • 引用在定義時必須初始化,指針建議初始化,但是語法上不是必須的。
  • 引用在初始化時引用?個對象后,就不能再引用其他對象;而指針可以在不斷地改變指向對象。
  • 引用可以直接訪問指向對象,指針需要解引用才能訪問指向對象。
  • sizeof中含義不同,引用結果為引用類型的大小,但指針始終是地址空間所占字節個數(32位平臺下占4個字節,64位下是8個字節)
  • 指針很容易出現空指針和野指針的問題,引用很少出現,引用使用起來相對更安全?些。

指令匯編角度引用是使用指針實現的。

int x = 10;
int& y = x;
int* py = &x;

轉到指令匯編
在這里插入圖片描述
引用的下面兩行是引用語句的匯編代碼,而指針下面兩行是指針語句的匯編代碼。我們從上圖可以看出兩者一模一樣,所以進一步印證了引用是使用指針實現的。

6、inline

inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用的地方展開內聯函數,這樣調用內聯函數就不需要建立函數棧幀了,就可以提高效率

C語言實現的宏函數也會在預處理時替換展開**,但是宏函數實現很復雜且很容易出錯,還不能調試,C++設計inline目的就是替代C語言的宏函數。**

正常的函數:

int add(int x, int y)
{int sum=x + y;return sum;
}

執行語句:int ret = add(2, 3);時,它的反匯編代碼是這樣的:
在這里插入圖片描述
圖片中有一個call指令,這個指令是調用add函數,說明函數沒有在預處理時展開

inline修飾的函數:

inline int add(int x, int y)
{int sum=x + y;return sum;
}

執行語句:int ret = add(2, 3);時,它的反匯編代碼是這樣的:
在這里插入圖片描述
從上圖可以看出它沒有調用函數,也就是沒有創建函數棧幀,而是在預處理階段就展開了,像C語言的宏函數一樣。這樣就可以提高效率。

inline對于編譯器而言只是?個建議,也就是說,你加了inline,編譯器也可以選擇在調用的地方不展開,不同編譯器關于inline什么情況展開各不相同,因為C++標準沒有規定這個。inline適用于頻繁調用的短小函數,對于遞歸函數,代碼相對多?些的函數,加上inline也會被編譯器忽略。

inline不建議聲明定義分離到兩個文件,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址,鏈接時會出現報錯。我們知道編譯器生成可執行程序會經過預處理、編譯、匯編、鏈接等過程,而在鏈接過程代碼中的普通函數的地址需要到XXX.o符號表中去尋找,因為他們不會展開所以它們的函數地址會進入符號表中,當你正確使用inline修飾函數,即聲明和定義不分離時,inline修飾的函數調用的地方已經正常展開了,而當你聲明和定義分離時,由于inline修飾的函數的地址它本身不會進入XXX.o符號表,又因為你沒有inline修飾的函數定義,此時函數調用的地方就沒有正常展開編譯器尋找地址的時候又找不到,此時就會報鏈接錯誤

聲明和定義分離的錯誤情況:
F.h

#include <iostream>
using namespace std;inline int add(int x, int y);

F.cpp

#include "F.h"inline int add(int x, int y)
{int sum = x + y;return sum;
}

test.cpp

#include"F.h"int main()
{int ret = add(2, 3);return 0;
}

鏈接錯誤
在這里插入圖片描述
當你聲明和定義不分離,再將F.cpp中的定義刪去,(因為此時F.h中已經有了,如果你這里不刪除的話它依舊會報錯,因為出現了兩個一摸一樣的函數主體),即:

F.h

#include <iostream>
using namespace std;inline int add(int x, int y)
{int sum = x + y;return sum;
}

這樣就可以正常展開了。

拓展inlinestatic所修飾的函數都具有內部鏈接屬性,不會進入XXX.o符號表中,所以不會造成C2084類型錯誤:
在這里插入圖片描述

7、nullptr

NULL實際是?個宏,在傳統的C頭文件stddef.h中,可以看到如下代碼:

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

上面這段代碼是條件編譯指令,感興趣的小伙伴點擊–>【C語言】編譯和鏈接、預處理詳解

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

#include <iostream>
using namespace std;void f(int n)
{cout << "f(int n)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);f(NULL);return 0;
}

這段代碼的執行結果是:
在這里插入圖片描述本想通過f(NULL)調?指針版本的f(int*)函數,但是由于NULL定義成0,調用了f(int x),因此與程序的初衷相悖f((void*)NULL);調用會報錯
在這里插入圖片描述
為了解決這個問題C++11中引入了nullptrnullptr是?個特殊的關鍵字nullptr是?種特殊類型的字面量,它可以轉換成任意其他類型的指針類型。使用nullptr定義空指針可以避免類型轉換的問題,因為nullptr只能被隱式地轉換為指針類型,而不能被轉換為整數類型。

#include <iostream>
using namespace std;void f(int n)
{cout << "f(int n)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);f(NULL);f(nullptr);return 0;
}

在這里插入圖片描述
這樣就解決了這個問題。所以在C++初始化指針為空,會用nullptr這個關鍵字初始化。

總結:
以上就是本期博客分享的全部內容啦!如果覺得文章還不錯的話可以三連支持一下,你的支持就是我前進最大的動力!
技術的探索永無止境! 道阻且長,行則將至!后續我會給大家帶來更多優質博客內容,歡迎關注我的CSDN賬號,我們一同成長!
(~ ̄▽ ̄)~

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

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

相關文章

git比較不同分支的不同提交文件差異

背景&#xff1a;只想比較某2個分支的某2次提交的差異&#xff0c;不需要帶上父提交。 以commitA為基準&#xff0c;用commitB去比較差異 直接上代碼&#xff1a; commitAxxxx1 commitBxxxx2 outputFile"output.txt"# 獲取與第一個父提交的文件列表 filesA$(git di…

Linux內核之struct pt_regs結構

前沿 項目開發最近進行系統hook功能實現相關業務&#xff0c;主要在centos7和8系列環境開發下關功能。調研了相關知識點&#xff0c;發現在系統7和8上內核版本差別比較大&#xff0c;7-3.10.x系列版本&#xff0c;8-4.18.x系列版本。依據兩個系統的內核情況根對應的內核符號表進…

《從混亂到有序:ArkUI項目文件結構改造指南》

在ArkUI開發的廣袤天地里&#xff0c;構建一個清晰、有序的文件結構&#xff0c;是打造優質應用的關鍵。一個合理的文件結構&#xff0c;就像為開發者精心繪制的地圖&#xff0c;在項目的各個階段&#xff0c;都能提供明確的指引&#xff0c;讓開發過程順暢無阻。今天&#xff…

C#基于Sunnyui框架和MVC模式實現用戶登錄管理

C#基于Sunnyui框架和MVC模式實現用戶登錄管理 1 Controller1.1 UserManagementController.cs&#xff08;控制器入口&#xff09; 2 Model2.1 UserRepository.cs&#xff08;用戶管理模型&#xff09;2.2 User.cs&#xff08;用戶結構體&#xff09;2.3 SQLiteHelper.cs&#x…

自然語言處理(NLP)技術的實例

自然語言處理&#xff08;NLP&#xff09;技術在各個領域都有廣泛的應用&#xff0c;以下是幾個例子&#xff1a; 語音識別&#xff1a;通過NLP技術&#xff0c;計算機可以識別和理解語音指令&#xff0c;例如智能助手如Siri和Alexa就是通過語音識別技術實現與用戶的交互。 機…

Spring Boot實戰(三十六)編寫單元測試

目錄 一、什么是單元測試&#xff1f;二、Spring Boot 中的單元測試依賴三、舉例 Spring Boot 中不同層次的單元測試3.1 Service層3.2 Controller 層3.3 Repository層 四、Spring Boot 中 Mock、Spy 對象的使用4.1 使用Mock對象的背景4.2 什么是Mock對象&#xff0c;有哪些好處…

aws服務(四)文件存儲服務S3 介紹使用代碼集成

一、介紹 1、簡介 Amazon S3 是 Amazon Web Services 提供的一種對象存儲服務(Object Storage),用于在云中存儲和檢索任意數量的數據。它以高可用性、高擴展性和高持久性著稱,非常適合用來存儲網站資源、數據備份、日志文件、大數據、機器學習輸入輸出等。 2、主要特性 …

應用信息1.13.0發布

增加工具箱 增加啟動器功能 增加布局查看器 增加手動安裝和卸載應用 增加APK文件解析 增加應用多選功能 增加查看應用預裝版本 增加應用信息和ADB命令導出 修復其它問題... 百度下載&#xff1a;百度網盤 請輸入提取碼 提取碼&#xff1a;1234

【Vue3 實戰】插槽封裝與懶加載

一、為什么需要插槽&#xff1f;從一個面板組件說起 在電商首頁開發中&#xff0c;經常遇到這樣的場景&#xff1a; 「新鮮好物」「人氣推薦」同樣類型模塊都需要相同的標題欄&#xff0c;但內容區布局不同 這時候&#xff0c;插槽&#xff08;Slot&#xff09;就像一個「內容…

虛無隧穿產生宇宙(true nothing tunneling) 是誰提出的

是 亞歷克斯.維連金 英文名&#xff08;alex vilenkin 或者 Alexander Vilenkin)提出來的。 “虛無隧穿產生宇宙”&#xff08;true nothing tunneling&#xff09;這一概念并非一個標準的物理學術語&#xff0c;它更像是對某些現代宇宙學理論的描述&#xff0c;尤其是涉及宇宙…

postgis:添加索引時提示“對訪問方法 gist 數據類型 geometry 沒有默認的操作符表“

問題 在對gis表的geom字段創建空間索引時&#xff0c;出現“對訪問方法 "gist" 數據類型 geometry 沒有默認的操作符表”的提示報錯。 解決方案 按系列步驟進行排查并解決。 1.先確認已安裝postgis -- 查看postgis版本 SELECT postgis_full_version() 若安裝了則…

圖論---Prim堆優化(稀疏圖)

題目通常會提示數據范圍&#xff1a; 若 V ≤ 500&#xff0c;兩種方法均可&#xff08;樸素Prim更穩&#xff09;。 若 V ≤ 1e5&#xff0c;必須用優先隊列Prim vector 存圖。 #include <iostream> #include <vector> #include <queue> #include <…

代碼隨想錄算法訓練營第一天:數組part1

今日學習的文章鏈接和視頻鏈接 ● 自己看到題目的第一想法 ● 看完代碼隨想錄之后的想法 ● 自己實現過程中遇到哪些困難 ● 今日收獲&#xff0c;記錄一下自己的學習時長 狀態 思路理解完成 30% 代碼debug完成 60% 代碼模板總結并抽象出來 100% 題目 704 二分查找 題目鏈接…

企業為何要求禁用缺省口令?安全風險及應對措施分析

在當今數字化時代&#xff0c;企業網絡安全面臨著前所未有的挑戰。缺省口令的使用是網絡安全中的一個重要隱患&#xff0c;許多企業在制定網絡安全紅線時&#xff0c;明確要求禁用缺省口令。本文將探討這一要求的原因及其對企業安全的重要性。 引言&#xff1a;一個真實的入侵場…

PostgreSQL 中的權限視圖

PostgreSQL 中的權限視圖 PostgreSQL 提供了多個系統視圖來查詢權限信息&#xff0c;雖然不像 Oracle 的 DBA_SYS_PRIVS 那樣集中在一個視圖中&#xff0c;但可以通過組合以下視圖獲取完整的系統權限信息。 一 主要權限相關視圖 Oracle 視圖PostgreSQL 對應視圖描述DBA_SYS_…

【防火墻 pfsense】1簡介

&#xff08;1&#xff09; pfSense 有以下可能的用途&#xff1a; 邊界防火墻 路由器 交換機 無線路由器 / 無線接入點 &#xff08;2&#xff09;邊界防火墻 ->要充當邊界防火墻&#xff0c;pfSense 系統至少需要兩個接口&#xff1a;一個廣域網&#xff08;WAN&#xff0…

數據庫+Docker+SSH三合一!深度評測HexHub的全棧開發體驗

作為一名技術博主&#xff0c;我最近一直被各種開發工具切換搞得焦頭爛額。數據庫要用Navicat&#xff0c;服務器管理得開Termius&#xff0c;Docker操作還得切到命令行&#xff0c;每天光在不同工具間切換就浪費了大量時間。直到團隊里的一位架構師向我推薦了HexHub這個一體化…

第十天 Shader編程:編寫簡單表面著色器 Addressable資源管理系統 DOTS(面向數據技術棧)入門

前言 作為Unity初學者&#xff0c;在實現復雜場景時經常會遇到性能瓶頸。本文將帶你通過四個關鍵技術的實戰學習&#xff0c;掌握現代Unity開發的核心優化方案&#xff1a; Shader編程 - 編寫表面著色器控制物體渲染Addressable系統 - 實現高效資源管理DOTS技術棧 - 解鎖百萬…

項目自動化測試

一.設計測試用例(細致全面) 二.先引入所需要的pom.xml依賴 1.selenium依賴 2.webdrivermanager依賴 3.commons-io依賴 編寫測試用例–按照頁面對用例進行劃分,每個頁面是Java文件,頁面下的所有用例統一管理 三.common包(放入公用包) 類1utils 可以調用driver對象,訪問url …

ap無法上線問題定位(交換機發包沒有剝掉pvid tag)

一中學&#xff0c;新開的40臺appoe交換機核心交換機旁掛ac出口路由的組網&#xff0c;反饋ap無法上線&#xff0c;讓協助解決。 組網如下&#xff1a; 排查過程&#xff1a; 檢查ac的配置&#xff0c;沒有發現問題 發現配置沒有問題&#xff0c;vlan1000配置子接口&#xff…