【C++】C++入門—(中)

前言:上一篇文章我們介紹了C++入門的一些基礎的語法,將了命名空間,缺省參數等。這篇文章我們就來介紹剩余的語法。

在這里插入圖片描述

文章目錄

  • 一,函數重載
  • 二,引用
    • 2.1引用的概念和定義
    • 2.2引用的特性
    • 2.3引用的引用場景
      • 2.3.1做函數形參,修改形參影響實參
      • 2.3.2 做函數形參時減少拷貝,提高效率
      • 2.3.3 引用做返回值類型,修改返回對象,減少拷貝提高效率
    • 2.4引用返回的坑
    • 3.1 const引用
    • 4.1指針和引用的關系

一,函數重載

函數重載:函數重載(Function Overloading)允許在同一個作用域內定義多個同名函數,但這些函數的參數列表必須不同(參數類型、數量或順序)。編譯器根據調用時傳遞的實參類型和數量選擇最匹配的函數版本。紫

舉個例子:

void func1(int x,int y)
{printf("%d %d",x,y);
}
void func2(double x,double y)
{printf("%lf %lf",x,y);
}
int main()
{int a = 10;int b = 20;double c = 10.0;double d = 20.0;//同樣在調用加法函數時調用的是不同的func函數//int類型的都調用func1func1(a,b);//double類型的就調用func2func2(c,d);return 0;
}

在C語言的學習過程中,功能相同的函數由于它們的參數類型不同,我們就要再創建一個函數并用數字區別開來,從而保證編譯器能區分不同函數。本質上就是函數名不能相同。這種行為一方面增加了代碼的冗余度,另一方面很麻煩如果有很多不同的類型那么我們要根據不同類型創建函數從效率上來說是很低的。

C++的祖師爺本賈尼博士覺得這個問題很麻煩,每次調用都要指定func1,func2。說能不能搞一個func函數,且可以創建多個func函數函數內部的區別就是參數不同,這樣每次調用只用寫func就好了,具體比較什么類型的參數由func函數自己去匹配。
于是祖師爺就創造出了函數重載。

函數重載有幾種情況
情況一:參數類型不同

#include<iostream>
using namespace std;
int Add(int x,int y)
{cout<<"int Add(int x,int y)"<<endl;return x+y;
}
double Add(double x,double y)
{cout<<"double Add(double x,double y)"<<endl;return x+y;
}
int main()
{int a = 10;int b = 20;double c = 10.0;double d = 20.0;//Add進行了函數重載 同名函數參數與返回類型不同Add(a, b);Add(c, d);return 0;
}

情況二:參數順序不同

void func(int a,char b)
{cout<<"func(int a,char b)"<<endl;
}
void func(char a,int b)
{cout<<"func(char a,int b)"<<endl;
}

情況三:參數的個數不同

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

情況四:返回類型不同

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

注意,僅僅是返回值不同,參數相同的同名函數是不構成函數重載的。因為在調用函數的時候也分不清到底是調用有返回值的那個還是沒有返回值的那個。

情況五,在情況三的基礎上加上缺省值

void func()
{cout<<"func()"<<endl;
}
void func(int a=10)
{cout<<"func(int a)"<<endl;
}
int main()
{//傳指定參數就直接調用第二個func函數func(20);//不串參數這時編譯器就搞不清楚了 到底是第一個還是第二個func();return 0;
}

注意,這里如果不傳參調用func函數就會存在歧義,因為不傳參的話無參的func函數能調用,有缺省值的func函數也能調用,編譯不清楚要調用哪個所以會報錯。

特別注意:

  1. 僅僅是返回值不同,參數類型相同的同名函數不構成重載!
  2. 函數重載只發生在相同域或作用域里面,不同域的同名函數不叫函數重載!

二,引用

引用是C++中一個非常重要且使用的語法,且貫穿著整個C++的學習包括后面的STL部分都會使用到引用,所以學好引用是非常有必要的。

2.1引用的概念和定義

  • 引用是C++中一種特殊的變量類型,本質是已存在變量的別名。它與指針類似,但更安全且語法更簡潔。引用必須在聲明時初始化,且無法重新綁定到其他變量。
  • 在引用時使用的是C語言中的& 取地址運算符,使用方法是類型& 引用的別名=引用的對象 舉個例子:
#include<iostream>
using namespace std;
int main()
{int a = 10;//給a取一個別名叫bint& b = a;//也可以給b取一個別名叫c 此時c也相當于是a的別名int& c = b;cout << "a=" << a << endl;++b;cout << "++b=" << a << endl;++c;cout << "++c=" << a << endl;cout<<"&a="<<&a<<endl;cout<<"&b="<<&b<<endl;cout<<"&c="<<&c<<endl;return 0;
}

在這里插入圖片描述

通過運行結果可以看到對b和c進行加加改變的是a的值,并且它們的地址還是同一個。由此我們可以得出一個結論:引用沒有開新空間,引用就是給變量起了一個新的別名,對別名的操作就是對被引用對象的操作。

2.2引用的特性

  1. 引用在定義時必須初始化:告訴那個引用它是誰的別名,這就是對引用的初始化。
#include <iostream>
using namespace std;
int main()
{int p=2;
//這樣做是不行的 沒初始化會報錯 
//int& rp;//至少要告訴rp引用的是誰 rp找不到引用對象就報錯int a = 1;
//對ra初始化 告訴ra它引用的是a
int& ra = a;
return 0;
}
  1. 一個變量可以有多個引用:就像人西游記中的孫悟空一樣,有齊天大圣,弼馬溫,猴哥等別名,變量也一樣可以有多個。
  2. 引用一旦引用了一個實體就不可能再引用其他的實體。就像一個狗子一般它一旦認定了一個主人就不會再認其他的主人了。
#include <iostream>
using namespace std;
int main()
{int a=10;int& b=a;int& c=a;int& d=a;//其中bcd都是a的別名cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;int p=100;d=p;//這里要注意p是賦值給d 而不是d是p的別名//因為d已經是a的別名了已經有一個實體了 它就不可能是另外一個變量的別名了return 0;
}

既然引用這么重要這么有用。那我們來看看引用的引用場景。

2.3引用的引用場景

2.3.1做函數形參,修改形參影響實參

回憶一下過去在C語言中,我們通過形參來影響實參是通過指針間接來控制的,比如:

#include<stdio.h>
void swap(int*x,int*y)
{int tmp=*x;*x=*y;*y=tmp;
}
int main()
{int a=10;int b=20;printf("交換前:\n");printf("a=%d\n",a);printf("b=%d\n",b);swap(&a,&b);printf("交換后:\n");printf("a=%d\n",a);printf("b=%d\n",b);return 0;
}

在C語言階段,我們通過形參影響實參的方式只有通過指針交換指針指向的內容從而實現實參的交換,那C++有了引用之后就可以使用引用來平替指針比如:

#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;swap(a, b);cout << "a=" << a << endl;cout << "b=" << b << endl;return 0;
}

因為引用就是別名,形參中的x和y就是實參對應的別名,操作它們就相當于操作實參,所以形參的改變可以影響實參。

2.3.2 做函數形參時減少拷貝,提高效率

這個很好理解,舉個例子:

int add(int x,int y)
{return x+y;
}int main()
{int a=10;int b=20;int c=add(a,b);
}

上面這段代碼是我們在C語言段會寫的代碼,且在C語言階段我們知道形參是實參的臨時拷貝,形參會自己開空間所以形參與實參不一樣。但是C++不一樣:

int add(int&x,int&y)
{return x+y;
}
int main()
{int a=10;int b=20;int c=add(a,b);
}

在這里插入圖片描述

2.3.3 引用做返回值類型,修改返回對象,減少拷貝提高效率

首先回顧一下我們在C語言階段學過的返回值類型有哪些?無非就是有返回值和沒有返回值。有返回值一般返回的是內置類型像int char double和struct定義的結構體
比如:

int func()
{int ret=10;//......return ret;
}
int main()
{int x=func();//嘗試修改一下返回值func()=20;//10=20//被const修飾的tmp具有常量的屬性不能再被修改//const int tmp = 10;//tmp = 20;return 0;
}

通過上面的代碼我們來看看有返回值的傳值返回本質是什么?當我們嘗試去修改返回值的時候,這個時候編譯器就會報錯:
在這里插入圖片描述
那什么情況下會報這種左操作數為左值的錯誤呢?將上面那一小段將20賦值給10以及給const變量賦值的代碼去編譯就會出現這種錯誤,因此我們可以推測出:傳值返回傳回來的是一個臨時變量(因為ret出了函數棧幀會銷毀 ),該變量不能被修改的原因可能是被const修飾了,具有了常量的屬性不能再被修改。

了解完了傳值返回,接著我們再來看看C++支持的傳引用返回:

#include<iostream>
using namespace std;
typedef struct SList
{int* arr;int size;int capacity;
}SL;void SLInit(SL& sl,int n=4)
{int* tmp = (int*)malloc(sizeof(int) * n);sl.arr = tmp;sl.size = 0;sl.capacity = 4;
}void SLPush(SL& sl,int x)
{if (sl.size == sl.capacity){sl.arr = (int*)realloc(sl.arr, 2 * sl.capacity);}sl.arr[sl.size] = x;sl.size++;
}int& SLAt(SL& sl, int i)
{assert(i < sl.size);return sl.arr[i];
}int main()
{SL sl1;SLInit(sl1);SLPush(sl1, 1);SLPush(sl1, 2);SLPush(sl1, 3);//傳引用返回可以對返回值進行修改SLAt(sl1, 0) += 2;return 0;

先來分析一下SLAt(sl1,0)+=2 這段代碼:

在這里插入圖片描述

同樣在上面的分析中,我們看到傳值返回是要經過拷貝成臨時變量才返回的,而引用返回本質就是給變量取了一個別名不需要拷貝不會浪費空間極大的提高了效率。試想:如果返回的是一個占用內存很大的結構體,使用傳值返回或者做函數形參,拷貝的時候要花費多少空間?所以從節省空間這方面來說引用還是很香的!

2.4引用返回的坑

引用返回既然這么香,那么以后是否就只使用引用就好了?當然不是,引用和指針一樣是一把雙刃劍用的好可以快捷高效,用不好會造成一定后果。
舉個例子:

int& func()
{int ret = 0;//...return ret;
}int& fun()
{int xxx = 1000;//...return xxx;
}
int main()
{//傳值返回/*int x = func();cout << x << endl;*/int& x = func();cout << x << endl;fun();cout << x << endl;//野引用對越界的空間進行訪問 在vs編譯器下可能查不到//因為vs編譯器對越界讀的行為是抽查行為int arr[10];//越界讀不一定報錯cout<<arr[12]<<endl;//越界寫可能會報錯 也有可能不會報錯 vs僅在數組末尾一兩個位置上設置了檢查arr[20]=1;return 0;
}

在這里插入圖片描述

兩次打印x,我們發現x的值竟然被fun函數中的xxx給修改了,這時為什么呢?
首先,func函數是傳引用返回給了x那么x就是ret的別名沒跑。第二次打印的x的值與xxx一樣為1000,這很難不讓人聯想到x可能是xxx的別名,為什么會造成這種情況呢?原因是ret是在函數棧幀中創建的,棧幀銷毀后ret的空間被回收,被回收的空間被起了一個別名叫x,好巧不巧的是當fun函數執行的時候xxx分配到的空間就是原來ret的空間,此時這個空間還有一個別名叫x,所以我們打印x就是xxx的值本質上xxxx就是同一塊空間。

在這里插入圖片描述

這里還有一點要注意就是,上面SLAt的引用返回能夠使用是因為sl1中的數組是在堆上開辟的空間,函數棧幀的銷毀不影響它的銷毀;只有被手動釋放才會銷毀所以引用返回是沒有問題的!

3.1 const引用

提到const在C語言階段就會想到被限制的問題。例如:被const修飾過的指針其指向的內容或其本身就會受到限制,那么在C++中我們就習慣稱之為權限問題:

int main()
{//引用int x = 10;int& a = x;int y = 20;//權限可以縮小const int& a1 = x;//a1雖然是x的別名 但是加上const修飾后 a1就不能改了a1 = y;//權限相同const int z = 10;const int& c = z;c = x; //同樣c雖然是z的別名 但是c被const修飾定死了 不能被修改 這主要是在后面函數傳參的時候用到//權限不能被放大const int r = 10;int& b = r;//這是對權限的放大 這樣做是不行的 因為r本身已經被限制 再給r取別名的時侯也因該對別名進行修飾 const int y=10;int z=y;//權限有沒有放大?//沒有 就是普通的賦值 
}

通過上面代碼要注意:可以引??個const對象,但是必須?const引用(相同權限下)。const引用也可以引用普通對象,因為對象的訪問權限在引用過程中可以縮小,但是不能放大。

void func(const int& x)
{//這里形參要用const修飾 因為實參已經被const修飾了 不能存在權限的放大}int main()
{int x = 1;const int y = 2;double d = 1.1;const int& r1 = x;const int& r2 = y;const int& r3 = 10;const int& r4 = x * 10;//編譯器會將x*10給存在一個臨時變量中 再將這個臨時變量賦給r4 所以r4的改變不會影響x//int& r5=d; 這樣是不行的,因為d會將臨時變量給r5 臨時變量具有常性 如果要引用臨時變量要怎么辦? 加上const修飾限制權限const int& r5 = d;//這里要注意 d給r5是一個普通的賦值 func(x);func(y);func(10);func(x*10);//這里的實參是 x*10后的值func(d);return 0;
}

在這里插入圖片描述

為什么要加const控制權限呢?比如上面的函數傳參,如果不用const修飾那么臨時變量都是傳不過去的,因為臨時變量具有常性不能被修改如果不加const修飾就引用就是權限放大了,所以這些都是為了以后傳參更加方便。 注意:權限問題只有在指針和引用的時候才會出現!!!

4.1指針和引用的關系

首先要明確一點:C++中出現的引用就是為了優化指針在C語言上的不足;換句話說引用就是優化指針的復雜度,但卻不能替代彼此。比如在鏈式結構和屬性結構中只有指針才能改變指向引用不能所以必須使用指針。

那么引用與指針的關系到底是什么呢?

在將引用返回的坑那里的時候,代碼編譯有兩個警告:
在這里插入圖片描述
這里就有人疑問了?明明是引用返回怎么警告說是返回局部變量的地址?

我們打開反匯編代碼:
在這里插入圖片描述
我們看到指針與引用在匯編代碼層面是極其的相似,所以不難推出引用它就是使用指針來實現的,就像上面的兩個警告一樣引用返回的是局部變量的地址,按照上面的分析也說得過去。

以上就是本章的全部內容啦!
最后感謝能夠看到這里的讀者,如果我的文章能夠幫到你那我甚是榮幸,文章有任何問題都歡迎指出!制作不易還望給一個免費的三連,你們的支持就是我最大的動力!
在這里插入圖片描述

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

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

相關文章

嵌入式Linux驅動開發:i.MX6ULL按鍵中斷驅動(非阻塞IO)

嵌入式Linux驅動開發&#xff1a;i.MX6ULL按鍵中斷驅動&#xff08;非阻塞IO&#xff09; 概述 本文檔詳細介紹了在i.MX6ULL開發板上實現按鍵中斷驅動的完整過程。該驅動程序實現了非阻塞IO操作&#xff0c;允許用戶空間應用程序通過poll系統調用高效地監控按鍵狀態變化&…

從 @Schedule 到 XXL-JOB:分布式定時任務的演進與實踐

從Schedule到XXL-JOB&#xff1a;分布式定時任務的演進與實踐 在分布式系統中&#xff0c;定時任務是常見需求&#xff08;如數據備份、報表生成、緩存刷新等&#xff09;。Spring框架的Schedule注解雖簡單易用&#xff0c;但在集群環境下存在明顯局限&#xff1b;而XXL-JOB作為…

阿里云營業執照OCR接口的PHP實現與技術解析:從簽名機制到企業級應用

一、阿里云營業執照OCR接口的核心技術架構 阿里云OCR服務基于深度學習模型和大規模數據訓練,針對中國營業執照的版式特征(如統一社會信用代碼位置、企業名稱排版、經營范圍換行規則等)進行了專項優化,識別準確率可達98%以上。其接口調用遵循RESTful API設計規范,采用HMAC…

AI人工智能大模型應用如何落地

AI人工智能大模型應用落地需要經過以下步驟&#xff1a; 明確應用場景和目標&#xff1a;首先需要明確AI大模型在哪個領域、解決什么問題。例如&#xff0c;在智能客服領域&#xff0c;AI大模型可以用于提高客戶服務的效率和質量&#xff1b;在醫學領域&#xff0c;AI大模型可以…

手寫Muduo網絡庫核心代碼2--Poller、EPollPoller詳細講解

Poller抽象層代碼Muduo 網絡庫中的 Poller 抽象層是其事件驅動模型的核心組件之一&#xff0c;負責統一封裝不同 I/O 復用機制&#xff08;如 epoll、poll&#xff09;&#xff0c;實現事件監聽與分發。Poller 抽象層的作用統一 I/O 復用接口Poller 作為抽象基類&#xff0c;定…

基于MCP架構的OpenWeather API服務端設計與實現

隨著微服務和模塊化架構的發展&#xff0c;越來越多的系統傾向于采用可插拔、高內聚的設計模式。MCP(Modular, Collaborative,Pluggable)架構正是這樣一種強調模塊化、協作性和擴展性的設計思想。它允許開發者以“組件”方式組合功能&#xff0c;提升系統的靈活性與可維護性。 …

從“疊加”到“重疊”:Overlay 與 Overlap 雙引擎驅動技術性能優化

在技術領域&#xff0c;“Overlay”和“Overlap”常因拼寫相似被混淆&#xff0c;但二者實則代表兩種截然不同的優化邏輯&#xff1a;Overlay 是“主動構建分層結構”&#xff0c;通過資源復用與隔離提升效率&#xff1b;Overlap 是“讓耗時環節時間交叉”&#xff0c;通過并行…

【Vue2 ?】 Vue2 入門之旅(六):指令與過濾器

前一篇我們學習了組件化開發。本篇將介紹 指令與過濾器&#xff0c;這是 Vue 模板語法的重要擴展&#xff0c;讓頁面渲染更加靈活。 目錄 常見內置指令自定義指令過濾器小結 常見內置指令 Vue 提供了豐富的內置指令&#xff0c;常見的有&#xff1a; <div id"app&qu…

【隨筆】【Debian】【ArchLinux】基于Debian和ArchLinux的ISO鏡像和虛擬機VM的系統鏡像獲取安裝

一、Debian Debian -- Debian 全球鏡像站 阿里巴巴開源鏡像站-OPSX鏡像站-阿里云開發者社區 debian-cd-current-amd64-iso-cd安裝包下載_開源鏡像站-阿里云 清華源&#xff1a; 清華大學開源軟件鏡像站 | Tsinghua Open Source Mirror USTC Open Source Software Mirror 二、…

如何用 Kotlin 在 Android 手機開發一個文字游戲,并加入付費機制?

Kotlin 開發 Android 文字游戲基礎框架使用 Android Studio 創建項目&#xff0c;選擇 Kotlin 作為主要語言。基礎游戲邏輯可通過狀態機和文本解析實現&#xff1a;class GameEngine {private var currentScene: Scene loadStartingScene()fun processCommand(input: String):…

安卓開發---BaseAdapter(定制ListView的界面)

概念&#xff1a;BaseAdapter 是 Android 中最基礎的適配器類&#xff0c;它是所有其他適配器&#xff08;如 ArrayAdapter、SimpleAdapter&#xff09;的父類。方法簽名&#xff1a;public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { // 獲取數據…

Conda配置完全指南:Windows系統Anaconda/Miniconda的安裝、配置、基礎使用、清理緩存空間和Pycharm/VSCode配置指南

本文同步發布在個人博客&#xff1a; Conda配置完全指南Conda 是一個開源的跨平臺包管理與環境管理工具&#xff0c;廣泛應用于數據科學、機器學習及 Python 開發領域。它不僅能幫助用戶快速安裝、更新和卸載第三方庫&#xff0c;還能創建相互隔離的虛擬環境&#xff0c;解決不…

什么是賬號矩陣?如何避免賬號IP關聯風險

賬號矩陣是指在同一平臺或多個平臺上&#xff0c;圍繞同一品牌、業務或個人 IP 構建的多個相互關聯、協同運作的賬號體系。這些賬號通過差異化的內容定位和運營策略形成互補&#xff0c;共同實現流量聚合、品牌曝光或業務拓展的目標。協同效應&#xff1a;賬號間通過內容互推、…

解析ELK(filebeat+logstash+elasticsearch+kibana)日志系統原理以及k8s集群日志采集過程

ELK日志系統解析 ELK 日志系統&#xff08;現常稱為 Elastic Stack&#xff0c;由 Filebeat、Logstash、Elasticsearch、Kibana 組成&#xff09;是一套用于 日志收集、清洗、存儲、檢索和可視化 的開源解決方案。 它的核心價值是將分散在多臺服務器 / 應用中的日志 “匯聚成池…

python 內置函數 sort() 復雜度分析筆記

在做 280. 擺動排序 時&#xff0c;有一版 python 題解&#xff0c;里面直接用了sort() &#xff0c;又用了一個簡單的 for 循環&#xff0c;但整體時間復雜度為 O(n?log(n)) &#xff0c;那么問題就出自這個 sort() &#xff0c;所以在這分析一下 sort() 的復雜度。Python 的…

【光照】Unity中的[經驗模型]

【從UnityURP開始探索游戲渲染】專欄-直達 圖形學第一定律&#xff1a;“看起來對就對” URP光照模型發展史 ?2018年?&#xff1a;URP首次發布&#xff08;原LWRP&#xff09;&#xff0c;繼承傳統前向渲染的Blinn-Phong簡化版?2019年?&#xff1a;URP 7.x引入Basic Shade…

uniapp小程序使用自定義底部tabbar,并根據用戶類型動態切換tabbar數據

1.注意點 在pages.json中配置tabbar如下字段&#xff1a;custom&#xff1a;true &#xff0c;會自動隱藏原生tabbar&#xff0c;使用自定義的tabbar2.如何自定義呢 可以使用第三方組件庫的tabbar組件&#xff0c;然后二次封裝下內部封裝邏輯&#xff1a; 1.點擊切換邏輯 2.根據…

Redis 哨兵 (基于 Docker)

目錄 1. 基本概念 2. 安裝部署 (基于 Docker) 2.1 使用 docker 獲取 redis 鏡像 2.2 編排 主從節點 2.3 編排 redis-sentinel 節點 3. 重新選舉 4. 選舉原理 5. 總結 1. 基本概念 名詞 邏輯結構物理結構主節點Reids 主服務一個獨立的 redis-server 進程從節點Redis 從…

Python學習-day4

Python 語言的運算符: 算術運算符比較&#xff08;關系&#xff09;運算符賦值運算符邏輯運算符位運算符成員運算符身份運算符運算符優先級 算術運算符 定義變量a 21&#xff0c;變量b 10。運算符描述實例加 - 兩個對象相加a b 輸出結果 31-減 - 得到負數或是一個數減去另一…

Vite 插件 @vitejs/plugin-legacy 深度解析:舊瀏覽器兼容指南

&#x1f4d6; 簡介 vitejs/plugin-legacy 是 Vite 官方提供的兼容性插件&#xff0c;專門用于為現代瀏覽器構建的應用程序提供對舊版瀏覽器的支持。該插件通過自動生成兼容性代碼和 polyfill&#xff0c;確保您的應用能夠在 IE 11 等舊版瀏覽器中正常運行。 核心價值 向后兼…