【C++初階】第一站:C++入門基礎(中)

前言:
這篇文章是c++入門基礎的第一站的中篇,涉及的知識點
函數重載:函數重載的原理--名字修飾
引用:概念、特性、使用場景、常引用、傳值、傳引用效率比較的知識點

目錄

5. 函數重載 (續)

C++支持函數重載的原理--名字修飾(name Mangling)

為什么C++支持函數重載,而C語言不支持函數重載呢?

6. 引用

引用概念

關于引用的應用:

引用特性

🚩引用在定義時必須初始化

🚩一個變量可以有多個引用

🚩引用一旦引用一個實體,再不能引用其他實體

使用場景

傳值、傳引用效率比較

值和引用的作為返回值類型的性能比較

關于順序表的讀取與修改

c語言接口

Cpp的接口設計:

常引用


5. 函數重載 (續)

C++支持函數重載的原理--名字修飾(name Mangling)

編譯器是如何編譯的?

Test.cpp

預處理頭文件展開/宏替換/去掉注釋/條件編譯

Test.i

編譯檢查語法,生成匯編代碼(指令級代碼) -- 右擊鼠標打開反匯編

Test.s

匯編將匯編代碼生成二進制機器碼

Test.o

鏈接合并鏈接,生成可執行程序(a.out / xxx.exe)

在整個編譯的過程中涉及到的一個問題是什么呢?

????????在一個項目里面寫了一個stack.h(棧定義的各種接口)和stack.cpp ,這些各種接口不在Test.o內,而在stack.o內,那么怎么去這里找呢。那就涉及到名字去找地址,在鏈接的時候怎么用名字去找地址呢?

?C語言的特點呢 -- 直接用函數名去充當函數的名字

? ? ? ? 這樣的后果就是自己都區分不開來,所以C語言是不允許重名的。

那么C++是如何這塊的問題呢?如何把兩個同名的但參數類型順序不一樣的函數區分開來呢?

🎯 那就是函數名修飾規則解決這個問題

當函數只有聲明沒有定義的時候,就會出現以下的鏈接錯誤

? ? ? ? 🌼當函數只有定義的時候,沒有實現的時候,它就沒有一堆匯編指令,沒有指令就不能生成地址(就沒有建立函數棧幀的過程,寄存器沒有存地址)。所以在符號表里面拿這個名字去找的時候就找不到,以下是修飾以后的函數名,本質上它是用類型帶入這個名字里面去了(函數修飾規則)

而C語言是直接用函數名去找:

在linux環境底下去看:

以下兩張圖均是函數名修飾規則

? ? ? ? Linux底下:c++區分函數不同的依據是去找函數的地址(本質也就是第一句指令的地址),找到地址之后,將其函數名修飾成特殊函數名:

<_Z4funcid> 意思是:四個字節,func(int,double)

<_Z4funcdi> 意思是:四個字節,func(double,int)

🍔在符合表里面:

? ? ? ? 用一個獨特的符號去代表一個類型,跟據類型的個數不同、類型不同、類型的順序不同,修飾出了的名字就是不一樣的,所以根據這點,就可以在函數名相同的情況下區分不同的函數。

vs2019底下:

void __cdecl func(int,double)" (?func@@YAXHN@Z)

void __cdecl func(double,int)" (?func@@YAXNH@Z)

因為這兩個函數雖然函數名字相同,但是函數類型的順序不同,所以編譯器會根據情況修飾成特殊的函數名

問題:

函數名修飾規則帶入返回值,返回值不能能否構成重載? 不能。

為什么C++支持函數重載,而C語言不支持函數重載呢?

在C/C++中,一個程序要運行起來,需要經歷以下幾個階段:預處理、編譯、匯編、鏈接
1.實際項目通常是由多個頭文件和多個源文件構成,而通過C語言階段學習的編譯鏈接,我們
可以知道,【當前a.cpp中調用了b.cpp中定義的Add函數時】,編譯后鏈接前,a.o的目標
文件中沒有Add的函數地址,因為Add是在b.cpp中定義的,所以Add的地址在b.o中。那么
怎么辦呢?
2. 所以鏈接階段就是專門處理這種問題,鏈接器看到a.o調用Add,但是沒有Add的地址,就
會到b.o的符號表中找Add的地址,然后鏈接到一起。(老師要帶同學們回顧一下)
3. 那么鏈接時,面對Add函數,鏈接接器會使用哪個名字去找呢?這里每個編譯器都有自己的
函數名修飾規則。
4. 由于Windows下vs的修飾規則過于復雜,而Linux下g++的修飾規則簡單易懂,下面我們使
用了g++演示了這個修飾后的名字。
5. 通過下面我們可以看出gcc的函數修飾后名字不變。
而g++的函數修飾后變成:【_Z+函數長度+函數名+類型首字母】
  • 采用

結論:在linux下,采用gcc編譯完成后,函數名字的修飾沒有發生改變。
  • 采用C語言編譯器編譯后結果

結論:在linux下,采用g++編譯完成后,函數名字的修飾發生改變,編譯器將函數參
數類型信息添加到修改后的名字中。
  • Windows下名字修飾規則

對比Linux會發現,windows下vs編譯器對函數名字修飾規則相對復雜難懂,但道理都
是類似的,我們就不做細致的研究了。
6. 通過這里就理解了C語言沒辦法支持重載,因為同名函數沒辦法區分。而C++是通過函數修
飾規則來區分,只要參數不同,修飾出來的名字就不一樣,就支持了重載

7. 如果兩個函數函數名和參數是一樣的,返回值不同是不構成重載的,因為調用時編譯器沒辦法區分。

6. 引用

引用概念

????????引用不是新定義一個變量,而 是給已存在變量取了一個別名 ,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。
💤現實生活來說:比如:李逵,在家稱為"鐵牛",江湖上人稱"黑旋風"

????????

????????C++為了在拓展語法的過程中,為了防止創新符號太多不好記憶,直接沿用C語言的符號,讓其一個符號賦予了多重意思,&在這邊不是取地址的意思,而是引用操作符

當b++時,a也會同時++,當兩條語句都++時,那么這個值就變為2

代碼實現:

int main()
{int a = 0;int& b = a;//引用cout << &a << endl;cout << &b << endl;return 0;}
輸出:
注意: 引用類型必須和引用 實體同種類型

關于引用的應用:

1??簡單的應用,值的交換:
void swap(int& x1, int& x2){int tmp = x1;x1 = x2;x2 = tmp;}int main(){int size;int x = 0, y = 1;swap(x,y);printf("%d %d", x, y);}

執行:

2??二叉樹前序遍歷的應用
int TreeSize(struct TreeNode* root){//寫法一if (root == NULL)return 0;//寫法二return TreeSize(root->left)+ TreeSize(root->right)+ 1;}void _preorder(struct TreeNode* root, int* a, int& pi){if (root == NULL)return;//用指針的方式是為了不在不同棧幀內創建ia[pi] = root->val;pi++;_preorder(root->left, a, pi);_preorder(root->right, a, pi);}int* preorderTraversal(struct TreeNode* root, int& returnSize){int size = TreeSize(root);int* a = (int*)malloc(sizeof(int) * sizeof(int));int i = 0;_preorder(root,a,i);return a;}int main(){int size = 0;preorderTraversal(nullptr, size);}
執行:
3??關于單鏈表的鏈接
前后代碼對比:

引用特性

🚩引用在定義時必須初始化

🚩一個變量可以有多個引用

int main()
{int a = 0;int& b = a;int& c = a;int& d = b;//給別名取別名,實際上是同一塊空間。int x = 1;//賦值b = x;return 0;
}

代碼執行變化:

🚩引用一旦引用一個實體,再不能引用其他實體

int main()
{int a = 0;int& b = a;int x = 1;int&b = x;return 0;
}

代碼執行:

使用場景

傳引用返回和傳值返回:

以下的兩者返回的方式有什么區別呢?

? ? ? ? 答:這兩種情況的區別,在于傳值調用在函數銷毀時有寄存器,傳引用調用沒有寄存器保存值,因為是同一個空間,引用不同于傳值和傳址,既然直接傳的就是這個空間本身,因此既不用創建臨時拷貝,也不需要傳變量地址。而是直接變量進行賦值。

💥先來看傳值返回:

????????用了一個全局的寄存器eax把返回值保存起來,待Count函數棧幀銷毀后,回到主函數main,再將寄存器里面的值賦值給ret

💨傳引用返回

????????這個n的別名,出了作用域就銷毀(這意味著返回的值是對已被銷毀的變量的引用),還給操作系統了,在還給操作系統的時候,可能將這塊空間里面的值給清理了,變成隨機值了。由于Count函數的返回值是無效的(引用已被銷毀),所以打印ret的值將導致未定義的行為。它可能打印1,也可能打印隨機值,或者可能導致程序崩潰。

💢那如果對代碼再修改一下呢:

????????這段代碼是非法的。當函數?Count()?執行完畢后,局部變量?n?將被銷毀,引用?ret?將會成為懸空引用(dangling reference),它指向了已經歸還給操作系統的空間,該空間里面的值可能已經被初始化為隨機值。因此,將對n的引用返回給調用函數是無效的,導致未定義的行為。

?總結:

?傳引用的第一個示例還是第二個示例中,代碼都是非法的,并且會導致未定義的行為

????????如果函數返回時,離開函數作用域后,其棧上空間已經還給系統,因此不能用棧上的空間作為引用類型返回。如果以引用類型返回,返回值的生命周期必須不受函數的限制(即比函數生命周期長)。

????????只有當出了這個作用域,這個對象還在的情況下,才可以加引用比如便用static將變量設成靜態變量,或是全局變量,函數生命周期就不會影響到引用

代碼示例:

//傳引用調用
int& Count()
{int n = 0;n++;return n;
}
int main()
{int& ret = Count();//這里打印的結果可能是1,也可能是隨機值cout << ret << endl;cout << ret << endl;return 0;
}
//傳值調用
int Count()
{int n = 0;n++;return n;
}
int main()
{int ret = Count();cout << ret << endl;cout << ret << endl;return 0;
}

當變量前面有很大一塊空間被占用時,有可能不會被覆蓋:

? ? ? ? 寫一個相加兩個變量值的代碼:

代碼實現:

?#include<iostream>
#include<assert.h>
int& Add(int a,int b)
{int c = a + b;return c;
}int main()
{int& ret = Add(1, 2);Add(3, 4);cout << "Add(1,2) is :" << ret << endl;
}

解析:

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

傳值、傳引用效率比較

???? ? ?? ?以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效
率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低。
傳引用傳參(任何時候都可以用)
1、提高效率
2、輸出型參數(形參的修改,影響的實參)
????????
#include <time.h>
#include<iostream>struct A { int a[10000]; };
A a;//全局變量!!!
// 值返回
A TestFunc1() { return a; }//全局變量是可以使用傳引用返回的!!!// 引用返回
A& TestFunc2() { return a; }void TestReturnByRefOrValue()
{// 以值作為函數的返回值類型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()
{TestReturnByRefOrValue();return 0;
}

?值和引用的作為返回值類型的性能比較

?傳引用返回(出了函數作用域對象還在才可以用)-- static修飾的,全局變量,堆空間等等
?1、提高效率
?2、修改返回對象

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{// 以值作為函數的返回值類型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()
{TestReturnByRefOrValue();return 0;
}

關于順序表的讀取與修改

c語言接口
struct SeqList
{int a[10];int size;
};//C的接口設計 
//讀取第i個位置
int SLAT(struct SeqList* ps, int i)
{assert(i<ps->size);//防止越界//...return ps->a[i];
}
//修改第i個位置的值
void SLModify(struct SeqList* ps, int i, int x)
{assert(i< ps->size);// ...ps->a[i] = x;
}

????????可以看待以上代碼,C語言實現讀取和修改結構體成員--數組元素時,是非常繁瑣的,實現功能就要編寫一個功能函數,但是如果換成c++的引用,那就可以一個函數實現兩個功能

Cpp的接口設計:

代碼示例:

CPP接口設計
//讀 or 修改第i個位置的值
#include<iostream>
#include<assert.h>
int& SLAT(struct SeqList& ps, int i)
{assert(i < ps.size);return(ps.a[i]);}
int main()
{struct SeqList s;s.size = 3;SLAT(s, 0) = 10;SLAT(s, 1) = 20;SLAT(s, 2) = 30;cout << SLAT(s, 0) << endl;cout << SLAT(s, 1) << endl;cout << SLAT(s, 2) << endl;return 0;
}

特別注意:

?

常引用

在引用的過程中:
1.權限可以平移
2.權限可以縮小
3.權限不能放大!!!

int main()
{const int a = 0;//權限的放大int& b = a;//這個是不行的!!!//權限的平移const int& c = a;//權限的縮小
//形象地理解: int x = 0;//齊天大圣const int& y = x;//戴上緊箍咒的孫悟空return 0;
}

賦值:

int b = a;//可以的,因為這里是賦值拷貝,b修改不影響a

類型轉換:

? ? ? ? 在c/c++里面有個規定:表達式轉換的時候會產生一個臨時變量,具有常性

以及函數返回的時候也會產生一個臨時對象

? ? ? ? 本章未結束,盡快更新下一章完結此篇。

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

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

相關文章

ACE前攝器Proactor

轉載的&#xff0c;已經找不到原文地址了 Proactor是異步模式的網絡處理器&#xff0c;ACE中叫做“前攝器”。 先講幾個概念&#xff1a; 前攝器&#xff08;Proactor&#xff09;&#xff0d;異步的事件多路分離器、處理器&#xff0c;是核心處理類。啟動后由3個線程…

csv文件添加文件內容和讀取

append content to file import numpy as np acc_listnp.array([0.97,0.92,0.93,0.89]) # 注意這個地方添加文件不需要特別聲明是什么文件 file open("result.csv", "a") print("{:.2f}, {:.2f}".format(acc_list.mean(), acc_list.std()), f…

【JavaEE】Spring小練習——存儲和獲取對象

一、題目&#xff1a; 在 Spring 項目中&#xff0c;通過 main 方法獲取到 Controller 類&#xff0c;調用 Controller 里面通過注入的方式調用Service 類&#xff0c;Service 再通過注入的方式獲取到 Repository 類&#xff0c;Repository 類里面有一個方法構建?個 User 對象…

YOLO目標檢測——垃圾檢測數據集下載分享【含對應voc、coco和yolo三種格式標簽】

實際項目應用&#xff1a;智能化垃圾分類系統、垃圾回收和處理領域的優化管理等方面數據集說明&#xff1a;垃圾分類檢測數據集&#xff0c;真實場景的高質量圖片數據&#xff0c;數據場景豐富&#xff0c;含報紙、蛋殼、礦泉水瓶、電池、拉鏈頂罐、塑料餐盒、紙質藥盒、香蕉皮…

kubernetesr進階--Security Context之Security Context概述

提起 Security Context &#xff0c;估計大家都很陌生&#xff0c;那么現在讓我帶大家走進 Security Context的世界。 Security Context&#xff08;安全上下文&#xff09;用來限制容器對宿主節點的可訪問范圍&#xff0c;以避免容器非法操作宿主節點的系統級別的內容&#x…

SpringMVC(一)

1. SpringMVC簡介 1、什么是MVC MVC是一種軟件架構的思想&#xff0c;將軟件按照模型、視圖、控制器來劃分 M&#xff1a;Model&#xff0c;模型層&#xff0c;指工程中的JavaBean&#xff0c;作用是處理數據 JavaBean分為兩類&#xff1a; 一類稱為實體類Bean&#xff1a…

創新洞察|展望2030 – 企業數字化轉型的10大趨勢(阿里研究院)

企業是否一定要 數字化創新 轉型&#xff1f;究竟如何數字化轉型&#xff1f;難點和坑又是什么&#xff1f;阿里研究院副院長針對未來十年中國的數字化轉型提出十個方面需要關注的趨勢&#xff1a;1.大國優勢 2. 重構的消費者決策體系 3. 下一代數字原生企業 4. 所有企業都會成…

【python學習】中級篇-數據庫操作:SQLite

SQLite是一個輕量級的數據庫引擎&#xff0c;它可以嵌入到各種應用程序中。以下是SQLite的基本用法&#xff1a; 創建數據庫文件 import sqlite3# 連接到一個不存在的數據庫文件&#xff0c;如果文件不存在&#xff0c;將會自動創建一個新的數據庫文件 conn sqlite3.connect…

Vue樣式不生效 如何解決它

如果使用了scoped后,無法修改第三方UI組件庫組件的樣式&#xff0c;這里可以使用css深度作用選擇器&#xff0c;以作樣式修改。 在Vue項目中&#xff0c;經常需要使用如elementUI、vant、 iview等組件庫&#xff0c;都可能自定義一些樣式文件&#xff0c;但是有些樣式直接在組…

SQL LIKE 運算符:用法、示例和通配符解釋

SQL中的LIKE運算符用于在WHERE子句中搜索列中的指定模式。通常與LIKE運算符一起使用的有兩個通配符&#xff1a; 百分號 % 代表零個、一個或多個字符。下劃線 _ 代表一個單個字符。 以下是LIKE運算符的用法和示例&#xff1a; 示例 選擇所有以字母 “a” 開頭的客戶&#x…

Postman接口測試工具完整教程

前言 作為軟件開發過程中一個非常重要的環節&#xff0c;軟件測試越來越成為軟件開發商和用戶關注的焦點。完善的測試是軟件質量的保證&#xff0c;因此軟件測試就成了一項重要而艱巨的工作。要做好這項工作當然也絕非易事。 第一部分&#xff1a;基礎篇 postman:4.5.1 1.安…

【成功案例】7日ROI超65%!注冊率超85%!雷霆網絡 聯手 NetMarvel 實現效果翻倍增長!

雷霆網絡旗下多款角色扮演手游在國內長期霸占買量榜前列&#xff0c;而這股“買量大戶”的風依舊吹到了海外&#xff0c;其中《地下城堡3》依靠買量在境外業務收入上增長明顯&#xff0c;目前市場潛力巨大。 然而&#xff0c;面對競爭激烈的PRG游戲出海局面&#xff0c;打開市…

12.docker的網絡-host模式

1.docker的host網絡模式簡介 host模式下&#xff0c;容器將不會虛擬出自己的網卡、配置IP等&#xff0c;而是使用宿主機的IP和端口&#xff1b;也就說&#xff0c;宿主機的就是我的。 2. 以host網絡模式創建容器 2.1 創建容器 我們仍然以tomcat這個鏡像來說明一下。我們以h…

QSplitter分裂器

QSplitter QSplitter 是 Qt 框架提供的一個小部件&#xff08;widget&#xff09;&#xff0c;用于在用戶界面中創建可拖動的分割窗口&#xff0c;允許用戶調整子部件的大小和布局。它可以將父部件分割為多個可調整大小的子部件&#xff0c;使用戶能夠自定義界面的布局和大小。…

2024年跨境電商黃金賽道預測來了!跨境電商首選平臺和品類有哪些?

跨境電商作為外貿新常態&#xff0c;在2023年已逐漸進入穩定增長的發展階段&#xff0c;想必2024年跨境電商也會是一個向好的發展趨勢&#xff0c;2024年做跨境電商&#xff0c;找準適合自己的電商平臺和產品是成功的關鍵&#xff0c;今天東哥就對2024年的跨境電商黃金賽道做一…

Kotlin中 for in 是有序的嗎?forEach呢?

我們要遍歷一個數組、一個列表&#xff0c;經常會用到kotlin的 for in 語法&#xff0c;但是 for in 是不是有序的呢&#xff1f;forEach是不是有序的呢&#xff1f;這就需要看一下它們的本質了。 數組的 for in // 調用&#xff1a; val arr arrayOf(1, 2, 3) for (ele in …

安卓現代化開發系列——從生命周期到Lifecycle

由于安卓已經誕生快二十載&#xff0c;其最初的開發思想與現代的開發思想已經大相徑庭&#xff0c;特別是Jetpack庫誕生之后&#xff0c;項目中存在著新老思想混雜的情況&#xff0c;讓許多的新手老手都措手不及&#xff0c;項目大步向屎山邁進。為了解決這個問題&#xff0c;開…

P6 C++控制流語句(continue, break, return)

前言 今天我們講的是控制流語句&#xff0c;本期內容是上期課程的延續。 控制流語句一般與循環語句一起工作&#xff0c;它們讓我們可以更好的控制這些循環的實際運行。 我們有三個主要的控制流語句可以使用&#xff0c;continue 、break 和 return&#xff0c;它們有不同的…

Python 訂閱 image_transport 壓縮后的深度圖 compressedDepth

image_transport 是ros的一個圖像處理工具,可以很方便地進行圖像數據的壓縮,可惜它目前并不支持python 當你如下安裝了image_transport及其plugin后 sudo apt install ros-foxy-image-transport*運行 ros2 run image_transport list_transports可看到如下內容 Declared tr…

打印樓梯,同時在樓梯上方打印兩個笑臉。

#include<stdio.h> int main() { int i,j; printf("\1\1\n"); /*輸出兩個笑臉*/ for(i1;i<11;i) { for(j1;j<i;j) printf("%c%c",219,219); printf("\n"); } return 0; }