C++初階(2)C++入門基礎1

C++是在C的基礎之上,容納進去了面向對象編程思想,并增加了許多有用的庫,以及編程范式 等。熟悉C語言之后,對C++學習有一定的幫助。

本章節主要目標:

  1. 補充C語言語法的不足,以及C++是如何對C語言設計不合理的地方進行優化的。
    比如:作用域方面、IO方面、函數方面、指針方面、宏方面等。
  2. 為后續類和對象學習打基礎。

C++初階課程的核心就是類和對象,然后使用類和對象+模版,實現基礎的一些數據結構。

在學類和對象之前,需要先學C++入門,C++的祖師爺覺得C語言很多地方設計得不好,于是開發了許多C++的小語法,去改進C語言的不足。

0. C++關鍵字(C98)

C++總計?63?個關鍵字,C語言32個關鍵字。

ps:下面我們只是粗略看一下C++有哪些關鍵字,不對關鍵字進行具體的講解。后面我們學到以后再細講。

1. 命名空間

1.1 namespace的價值

在C/C++中,變量函數和后面要學到的都是大量存在的。

這些變量、函數和類的名稱若都存在于全局作用域中的話,則很可能會導致很多沖突。

使用命名空間的目的

  • 對標識符的名稱進行本地化,以避免命名沖突或名字污染。

?namespace 關鍵字的出現就是針對這種問題的。

c語言項目類似下面程序這樣的命名沖突是普遍存在的問題,C++引入namespace就是為了更好的解決這樣的問題。(C的缺陷 / 不足)

#include <stdio.h?
#include <stdlib.h>    //不包含這個頭文件,則不會報錯
int rand = 10;
int main()
{// 編譯報錯:error C2365: “rand”: 重定義;以前的定義是“函數”printf("%d\n", rand);return 0;
}//頭文件展開,里面的rand()函數和全局的rand變量——>命名沖突

即C語言中,變量不能和函數重名。

命名沖突——C的缺陷之一:存在于編碼者與庫、(一個大項目的)編碼者之間。


C語言是解決不了這個問題的,兩個都想叫rand,最終只能有一個在全局域取名為rand。

解決C語言第一個缺陷——命名沖突:同名xx不知道使用哪一個,的第一個C++語法namespace。


7.2 namespace的語法規則

(1)namespace定義

? 定義命名空間,需要使用到namespace關鍵字,后面跟命名空間的名字,然后接一對{ }即可。——和結構體不一樣的是,后面不需要加分號

{}中即為命名空間的成員,可以把日常定義的變量、函數、類型封裝到命名空間里面。

  • 命名空間中可以定義變量/函數/類型
    命名空間中是各種標識符的定義:變量名、函數名、類型名……

命名空間中的變量、函數、類型不會和全局沖突,只有指定才會找到。

(2)域

? namespace本質是定義出一個域(命名空間域),這個域跟全局域各自獨立,不同的域可以定義同名變量,所以下面的rand不再沖突了。

C++中域有函數局部域、全局域、命名空間域、類域

#include <stdio.h>
#include <stdlib.h>
// 1. 正常的命名空間定義
// bit是命名空間的名字,?般開發中是用項?名字做命名空間名。
// 示例用的是bit,自己練習可以考慮用自己名字縮寫,如張三:zs
namespace bit
{// 命名空間中可以定義變量/函數/類型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}int main()
{// 這里默認是訪問的是全局的rand函數指針printf("%p\n", rand);// 這里指定bit命名空間中的randprintf("%d\n", bit::rand);return 0;
}

代碼編譯時,遇到標識符——變量(名)、函數(名)、類型(名),編譯器需要去找它的出處(定義),找不到會報錯“未聲明的標識符”。


編譯默認查找順序——

  1. 當前局部域(自留地)
  2. 全局域找——頭文件包含的東西也在全局域(村子野地)

類比做菜摘蔥,優先去自留地摘,沒有再去村子野地摘。


命名空間相當于在全局域,劃分出一些獨立的域(命名空間域)
——將一些村子野地,劃歸成自家的自留地。

編譯時不會到其他命名空間中去找(隔壁張大爺自留地)。


這樣的話,加了命名空間,同時不包含 <stdlib.h>頭文件,就會報錯:errorC2065:未聲明的標識符。


而指定查找域,則只會去這一個域里面找。

類比做菜摘蔥,指定去張大爺地摘。


(3)域的作用

不同域可以定義同名的變量/函數/類型——域可以做到名字的隔離

{ }括起來的都是域,全局域可以不用{ }括起來。

? 域影響的是編譯時語法查找一個變量/函數/類型出處(聲明或定義)的邏輯,所以有了域隔離,名字沖突就解決了。

  • 局部域和全局域除了會影響編譯查找邏輯,還會影響變量的生命周期
  • 命名空間域和類域不影響變量生命周期。
    (命名空間域都是修飾全局的,只是把名字給隔離起來了)

域的作用是影響編譯時的查找規則——先到局部域,再到全局域&展開的命名空間域

局部優先原則: 默認優先訪問局部變量。

C語言,在局部,訪問同名全局變量的方法。

C++,在局部,訪問同名全局變量的方法——作用域限定符(作用域解析運算符)

(4)嵌套定義

? namespace只能定義在全局,當然它還可以嵌套定義。
? 變量rand還是全局變量,只是封裝到了bit命名空間中。

命名空間內部可以定義變量、函數、類型,還可以定義其他的命名空間。

//2. 命名空間可以嵌套
namespace bit
{// 鵬哥namespace pg{int rand = 1;int Add(int left, int right){return left + right;}}// 杭哥namespace hg{int rand = 2;int Add(int left, int right){return (left + right)*10;}}
}int main()
{printf("%d\n", bit::pg::rand);printf("%d\n", bit::hg::rand);printf("%d\n", bit::pg::Add(1, 2));printf("%d\n", bit::hg::Add(1, 2));return 0;
}

嵌套的命名空間,在使用的時候也要使用多個域作用限定符來訪問。

(5)多個同名namespace

? 項目工程中多文件中定義的同名namespace會認為是一個namespace,不會沖突。

// 多?件中可以定義同名namespace,他們會默認合并到?起,就像同?個namespace?樣
// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>namespace bit
{typedef int STDataType;typedef struct Stack{STDataType* a;int top;int capacity;}ST;void STInit(ST* ps, int n);void STDestroy(ST* ps);void STPush(ST* ps, STDataType x);void STPop(ST* ps);STDataType STTop(ST* ps);int STSize(ST* ps);bool STEmpty(ST* ps);
}// Stack.cpp
#include"Stack.h"
namespace bit
{void STInit(ST* ps, int n){assert(ps);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;}// 棧頂void STPush(ST* ps, STDataType x){assert(ps);// 滿了, 擴容if (ps->top == ps->capacity){printf("擴容\n");int newcapacity = ps->capacity == 0 ? 4 : ps->capacity*2;STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;}//...
}// Queue.h
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>namespace bit
{typedef int QDataType;typedef struct QueueNode{int val;struct QueueNode* next;}QNode;typedef struct Queue{QNode* phead;QNode* ptail;int size;}Queue;void QueueInit(Queue* pq);void QueueDestroy(Queue* pq);// 入隊列void QueuePush(Queue* pq, QDataType x);// 出隊列void QueuePop(Queue* pq);QDataType QueueFront(Queue* pq);QDataType QueueBack(Queue* pq);bool QueueEmpty(Queue* pq);int QueueSize(Queue* pq);
}// Queue.cpp
#include"Queue.h"
namespace bit
{void QueueInit(Queue* pq){assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;}// ...
}// test.cpp
#include"Queue.h"
#include"Stack.h"// 全局定義了一份單獨的Stack
typedef struct Stack
{int a[10];int top;
}ST;void STInit(ST* ps){}
void STPush(ST* ps, int x){}int main()
{// 調用全局的ST st1;STInit(&st1);STPush(&st1, 1);STPush(&st1, 2);printf("%d\n", sizeof(st1));// 調用bit namespace的bit::ST st2;printf("%d\n", sizeof(st2));bit::STInit(&st2);bit::STPush(&st2, 1);bit::STPush(&st2, 2);return 0;
}

情景1:假設上述代碼中,棧的隊列的初始化函數,都希望叫作Init(不希望用名字來區分),那么就需要將棧和隊列的代碼,放入不同的命名空間中。

情景2:害怕自己的棧的STInit()和別人的沖突,就不能直接#include "Stack.h",這樣會直接暴露在全局域,需要先在頭文件中,把代碼都放到命名空間中,才能#include "Stack.h"

一個一般的項目有幾十上百個頭文件,定義多少個命名空間?
(不分命名空間的話因為變量、函數、類型……的名字就得拉扯很久理不清)

一般分組合作,一個組內幾個頭文件共用一個命名空間。

不同文件可以定義同名的命名空間,同名的命名空間可合并

合并后(同一個域)有同名會報錯——命名沖突——1改名,2嵌套

而同一個文件也沒必要搞多個命名空間,一個足矣,即都放到一起——因為多個文件都可以共用一個命名空間。

(6)標準庫——標準命名空間

? C++標準庫都放在一個叫 std?(standard的縮寫)的命名空間中。

7.3 命名空間使用(3種方法)

編譯查找一個變量的聲明/定義時,默認只會在局部或者全局查找,不會到命名空間里面去查找。所以下面程序會編譯報錯。

#include<stdio.h>
namespace bit
{int a = 0;int b = 1;
}int main()
{// 編譯報錯:error C2065: “a”: 未聲明的標識符printf("%d\n", a);return 0;
}

其他的例證:

原因:有兩個rand,這里printf里的rand默認取庫里面的rand,即函數rand(),代表一個函數指針,d%改成p%就可以打印出這個函數指針。


所以我們要使用命名空間中定義的變量/函數,有以下三種方式:

? 指定命名空間訪問,項目中推薦這種方式。

? using將命名空間中某個成員展開,項目中經常訪問的不存在沖突的成員推薦這種方式。

? 展開命名空間中全部成員,項目不推薦,沖突風險很大,日常小練習程序為了方便推薦使用。
(只有一個命名空間,或只有少量命名空間且相互之間不沖突時,可以完全展開)

總結。

  • 指定域。
  • 展開命名空間。(不指定域也不會報錯,會到展開的命名空間中去查找)
    • 展開某個成員。
    • 完全展開。
#include<stdio.h>
#include<stdlib.h>namespace bit
{int rand = 0;int x = 0int y = 0
}
// 指定命名空間訪問——最安全
int main()
{printf("%d\n", rand);        //默認全局——庫函數rand()printf("%d\n", bit::rand);return 0;
}// using將命名空間中某個成員展開——某個頻繁使用的成員
using bit::x;
int main()
{printf("%d\n", bit::y);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);        //x經常使用,y偶爾使用return 0;
}// 展開命名空間中全部成員——最危險
using namespce bit;
int main()
{printf("%d\n", rand);printf("%d\n", x);printf("%d\n", y);return 0;
}

展開命名空間后不指定域也不會報錯的前提——和其他展開的命名空間域 or 全局域不沖突。

故雖然麻煩一點,盡量還是別展開。


展開頭文件:把頭文件的內容在預處理階段拷貝過來。

展開命名空間:命名空間是一個域,域的展開是開放訪問權限,域的作用是影響編譯時查找——先到局部域,沒有再到全局域,最后如果有展開的命名空間,就會到展開的命名空間內查找

編譯默認查找
a、當前局部域?? ??? ??? ??? ?????????: 自留地
b、全局域找?? ??? ??? ??? ? ? ?????????: 村子野地
b、到展開的命名空間中查找 ? : 相當于張大爺在自己的自留地加了聲明,誰需要就來摘

沒加這個聲明時,默認不會到命名空間中去找。

  • 注意展開不是放到全局域,展開后仍然是兩個域,只是展開的命名空間域、全局域在查找標識符的出處的時候,具有等價的優先級——局部域之后。

展開的命名空間域、全局域有同名函數不會報錯——不調用就不會報錯。

一旦調用就會產生調用歧義——調用哪個都可以,全局域、命名空間域都能去找。
(不指定的情況下,全局域、展開的命名空間域都會同等優先級地搜索)

但是指定域的方式去調用也不會出錯

::func();        //指定全局域里面去找//或者bit::func()      //指定命名空間域里面去找

有了命名空間,就能很好地解決命名沖突的問題。

類型的“域限定符”放在類型名前面——Node前面。(標識符前面)

編譯器的一個很智能的點:

應用:


總結——命名空間的價值

命名空間就是把某塊空間圈起來, 圈起來之后影響了查找規則,以此解決了命名沖突。
(沖突的本質:不知道用哪一個)


2. C++輸入&輸出

c++搞了一套新的輸入輸出流——IO流 ?/ ?iostream。

新的頭文件“iostream” ? ?—— ? ?相當于是stdio.h的進化版。

也可以繼續包含stdio頭文件使用 printf/scanf,但是c++更喜歡使用cout/cin——因為其在命名空間std內,產生隔離更安全。

這里的cout中的c不是c++的c,而是console(控制臺)的 c。

windows下的控制臺,相當于linux下的終端。


? <iostream> 是 Input Output Stream 的縮寫,是標準的輸入、輸出流庫,定義了標準的輸入、輸

出對象。


為什么頭文件iosream沒有.h——特別老的c++標準帶.h(還沒有命名空間),如老編譯器vc6.0就可以用。

后來出了命名空間,就包到新的不帶.h的頭文件里了。

頭文件.h只是一個標識,現在c++標準庫的頭文件幾乎都不帶.h。

C++對C兼容的時候,對C的頭文件都封裝了一個不帶.h的版本。

不帶.h的版本,就是用命名空間封裝過中的版本。(<stdlib.h> ? ?== ? ?<cstdlib>)

注意:早期標準庫將所有功能在全局域中實現,聲明在.h后綴的頭文件中,使用時只需包含對應
頭文件即可,后來將其實現在std命名空間下,為了和C頭文件區分,也為了正確使用命名空間,
規定C++頭文件不帶.h;舊編譯器(vc 6.0)中還支持<iostream.h>格式,后續編譯器已不支持,因
推薦使用<iostream>+std的方式。

? std::cin 是 istream 類的對象,它主要面向窄字符(narrow characters (of type char))的標準輸

入流。

? std::cout 是 ostream 類的對象,它主要面向窄字符的標準輸出流。

? std::endl 是一個函數,流插入輸出時,相當于插入一個換行字符加刷新緩沖區。

??<< 是流插入運算符, >> 是流提取運算符。
(C語言還用這兩個運算符做位運算左移/右移)

cout和cin是全局的流對象,endl是特殊的C++符號,表示換行輸出。
他們都包含在包含
< iostream >頭文件中。

? 使用C++輸入輸出更方便,不需要像printf/scanf輸入輸出時那樣,不需要手動指定格式,C++的輸入輸出可以自動識別變量類型(本質是通過函數重載實現的,這個以后會講到).

  • 其實最重要的是 C++的流能更好的支持自定義類型對象的輸入輸出

? IO流涉及類和對象,運算符重載、繼承等很多面向對象的知識,這些知識我們還沒有講解,所以這里我們只能簡單認識一下C++ IO流的用法,后面我們會有專門的一個章節來細節IO流庫。

? cout/cin/endl 等都屬于C++標準庫,C++標準庫都放在一個叫std(standard)的命名空間中。

編譯器查找的時候,默認先去局部,再去全局,全局域包含了頭文件,頭文件展開后按理來說應該有了,但是C++的標準庫里面做了一件事——為了防止標準庫里面的東西和程序員自己定義的東西沖突了,所以標準庫里面的代碼被封裝進了一個命名空間——std。

直接使用cout,并不會到命名空間std中去查找。

所以要通過命名空間的使用方式去用他們——3種方式

① 指定命名空間域

② 完全展開

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << " " << c << endl;std::cout << a << " " << b << " " << c << std::endl;scanf("%d%lf", &a, &b);printf("%d %lf\n", a, b);// 可以自動識別變量的類型cin >> a;cin >> b >> c;                    //可以連續提取——不需要取地址cout << a << endl;cout << b << " " << c << endl;    //可以連續插入cout << b << " " << c << '\n';    //換行有兩種方式://'\n'//endlreturn 0;
}

③ 指定展開

cin

  • 作用:從console里面把輸入的數據拿出來放到這個對象(C習慣叫變量,c++習慣叫對象)里面。
  • 優點:不用指定格式;也不需要取地址。
  • cin不常用就不用指定展開。

? 一般日常練習中我們可以using namespace std,實際項目開發中不建議using namespace std。

? 這里我們沒有包含<stdio.h>,也可以使用printf和scanf,在包含<iostream>間接包含了。vs系列 編譯器是這樣的,其他編譯器可能會報錯。

cout、cin優點

  • 可以“自動識別類型”(printf需要指定類型:%d、%s......),即可以隨便插入,不用考慮占位符
  • 并且可以連續地輸出cout<<i<<j;
  • 并且可以在中間自由插入。
    • cout<<i<<"abcd"<<j;(插入字符串)
    • cout<<i<<" "<<j;(插入空格隔開)

printf、scanf優點

  • 更高效(99%的場景都不需要考慮這個)——因為C++要兼容C語言,會有一定的效率的影響
#include<iostream>
using namespace std;
int main()
{// 在io需求?較?的地?,如部分?量輸?的競賽題中,加上以下3?代碼// 可以提?C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}

? cin、cout也有相關的精度控制函數,默認是有多少輸出多少。

c++.ostream


cout需要控制精度、寬度時非常麻煩,涉及一系列函數——>能兼容printf就用printf。

但是VS下的iostream是包含了printf、scanf,Linux下就不一定了,可能會報錯,就需要多包一個頭文件。


競賽的2個tips

① C++IO效率

#include<iostream>
using namespace std;
int main()
{// 在io需求?較?的地?,如部分?量輸?的競賽題中,加上以下3?代碼// 可以提?C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}

② 萬能頭文件

萬能頭文件<bits/stdc++.h>:把c++常見的基本都包進來了——但是VS不支持。

但是一展開就會導致程序變大很多,日常、項目都不建議使用,競賽可用。

展開頭文件有極大的銷耗。

3. 缺省參數

? 缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數時,如果沒有指定實參,則采用該形參的缺省值,否則使用指定的實參。

(有些地方把缺省參數也叫默認參數)

缺省參數分為:全缺省參數、半缺省參數。

? 全缺省就是全部形參給缺省值,半缺省就是部分形參給缺省值。

? C++規定半缺省參數必須從右往左依次連續缺省,不能間隔跳躍給缺省值。

(從左往右缺省帶有歧義)

從右往左給缺省值,函數調用就不存在歧義——調用函數的實參按形參列表從左往右依次給到形參

? 帶缺省參數的函數調用,C++規定必須從左到右依次給實參,不能跳躍給實參。

? 函數聲明和定義分離時,缺省參數不能在函數聲明和定義中同時出現,規定只能由函數聲明給缺省值。——如果聲明與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確定到底該用那個缺省值。

? 缺省值必須是常量或者全局變量。

? C語言不支持(編譯器不支持)。

#include <iostream>
#include <assert.h>
using namespace std;void Func(int a = 0)
{    cout << a << endl;
}int main()
{Func();     // 沒有傳參時,使用參數的默認值Func(10);   // 傳參時,使用指定的實參return 0;
}

缺省參數的優勢:

可以不傳參數,函數調用按默認值運行——可傳,可不傳,提高了程序的靈活度。

#include <iostream>
using namespace std;// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;    cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}// 半缺省——從右往左缺省 && 不能間隔著給
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}int main()
{//帶缺省參數的函數,函數調用就有多種方式了Func1();Func1(1);Func1(1,2);      //規定是按順序傳,傳一個參數就是給a,傳兩個參數就是給a,b——所以不允許間隔著給缺省值 //Func1(1, ,3)   //不能跳躍著傳Func1(1,2,3);     Func2(100);Func2(100, 200);Func2(100, 200, 300);return 0;
}

缺省參數的用途——C的棧的初始化(開0個空間)是實現得不太好的。

在首次插入時開4個空間,后續擴容2倍。

// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
void STInit(ST* ps, int n = 4);// Stack.cpp
#include"Stack.h"
// 缺省參數不能聲明和定義同時給
void STInit(ST* ps, int n)
{assert(ps && n > 0);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;
}// test.cpp
#include"Stack.h"
int main()
{ST s1;STInit(&s1);// 確定知道要插?1000個數據,初始化時?把開好,避免擴容ST s2;STInit(&s2, 1000);return 0;
}

定義一個棧——>對棧初始化——>插入1000個數據 這個時候會導致一個問題——>這個程序會有大量的擴容——>而且擴容到后面,異地擴容消耗很大(如果沒有足夠的空間:開新的空間——拷貝數據——釋放舊的空間),而且越往后消耗越大(拷貝空間、拷貝時間)

C語言傳統的解決方案:

(1)定義一個宏N,一開始就先開一堆空間。

這樣寫死的方式都是不太好的,因為當初始數據較小時就會造成比較大的空間浪費。

(2)增加一個參數n,靈活一點,能夠幫助我們去控制,即要初始化多少你直接給我
(最好不要使用宏——>不好用)

(3)在C++,如果一開始不知道要初始化多大的空間,就可以給參數一個官方指導值(缺省值)

有了缺省參數,就能在初始化時不去指定具體開辟多大的空間,不指定就默認申請4字節的空間。

——體現半缺省的價值:知道要開多大就傳多大,不知道就開默認值。

4. 函數重載

4.1 函數重載的概念

函數重載:是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這 些同名函數的形參列表(參數個數、類型、類型順序)不同,常用來處理實現功能類似、數據類型不同的問題。

函數重載的要點

  • 同一作用域:同名函數——>形參不同(個數不同、類型不同)。

這樣C++函數調用就表現出了多態行為,使用更靈活。


C語言是不支持同一作用域中出現同名函數的。

error C2084:“函數“int?Add(int,int)”已有主體

#include<iostream>
using namespace std;// 1、參數類型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}// 2、參數個數不同
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}// 3、參數類型順序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}

c++可根據形參匹配同名函數
(同一作用域內的同名函數——都在同一命名空間、或都在全局)
(不同域本就允許同名函數)

之前是在不同作用域,即使有“不去訪問未展開的命名空間”的規定,也可以根據參數去調用命名空間內的函數。

原理都是參數匹配。


4.1.1 不構成函數重載的情況

(1)返回值不同
void fxx()
{//……
}int fxx()
{return 0;
}

返回值不同是不構成重載的,函數重載根本就不看返回值,有返回值沒有返回值都沒關系,只看參數列表。

因為看返回值的不同,無法區分函數調用,調用時會根據參數去匹配函數。

換一個角度看,返回值在調用的時候并不是必須的,返回值可以不接收,但是參數必須傳遞。

上述代碼結果:報錯。

(2)缺省值不同
void f(int a = 10)
{//……
}void f(int a = 20)
{//……
}

上述代碼結果:報錯。

(3)不同的(命名空間)域
//情景1:兩個同名函數——不構成重載,但是可以同時存在
namespace bit1
{void func(int a){//……}
}namespace bit2
{void func(int b){//……}
}

情景2:若是都叫bit1——不構成重載,不能同時存在。

還是在同一命名空間(會合并),那同一作用域函數要同名存在,必須滿足重載規則。


4.1.2 函數重載的調用歧義

// 下?兩個函數構成重載
// f()但是調用時,會報錯,存在歧義,編譯器不知道調用誰
void f1()
{cout << "f()" << endl;
}void f1(int a = 10)
{cout << "f(int a)" << endl;
}int main()
{f1();return 0;
}

不調用就不會報錯

全缺省和無參的同名函數,構成函數重載,但是調用f()會存在歧義

調用全缺省的函數時給參數,也不會發生調用歧義。

4.1.3 非函數重載的調用歧義

調用歧義:兩個swag(int*,int*)都可以調——swag(&a,&b)不知道調哪一個 。? ??

但是swag(&c,&d)知道——只有全局域有其定義swag(double*,double*)

展開命名空間,兩個swap還是在各自的域中,不構成函數重載。
(不構成函數重載但是都可以存在,不調用就不會出錯,這里的錯誤在于調用歧義)

命名空間都展開——不構成重載關系——還是在各自的作用域,同一個域內才有重載的概念。

重載:同一個菜地。
展開:菜地旁插了塊牌子。

4.1.4 隱式類型轉換

//只有一個函數的時候——才存在隱式類型轉換
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}//void f(char b, int a)
//{
//	cout << "f(char b, int a)" << endl;
//}int main()
{int a = 0, b = 1;double c = 0.1, d = 1.1;f(1, 'a');f('a', 1);                //只有一個f(int,char),調用f(char,int)會有轉換return 0;
}//存在多個函數同名:只有匹配的問題,沒有轉換的問題——不讓轉
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}int main()
{int a = 0, b = 1;double c = 0.1, d = 1.1;f(1, 'a');f('a', 1);//兩個同名函數,這個調用就存在歧義f('a', 'a')//這個調用是帶有條件的,條件就是隱式類型轉換//調用第一個f(int,char)就是一參char——>int//調用第二個f(char,int)就是二參char——>int//這里的調用就存在歧義,不知道調哪一個——調用必須得沒有歧義return 0;
}

4.2 函數重載的原理

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

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

——函數名修飾規則

注意點:只要函數調用去匹配函數聲明的時候,在參數上是匹配的,那么這個調用就是合法的,即函數調用的合法性與函數定義無關,只與函數聲明有關。

聲明和定義分離時,就會有缺地址的問題——地址在外部符號表。

只有聲明,沒有定義(沒有地址),call指令就只檢查:

使用符不符合規則—參數匹不匹配—語法正不正確

語法不正確:報出語法錯誤。

語法正確:在鏈接的時候拿函數名去符號表里面找地址。

直接拿函數名去找嗎???

前面講這么多就是為了說明:

  • 在鏈接時有這么一個查找的過程,為什么C不支持,而c++支持?
  • 因為在鏈接的時候要用函數名去找地址,如果聲明和定義分離,即包.h一起編譯的匯編代碼缺地址,而調用一個函數,要用函數名去找,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+函數長度+函數名+類型首字母】。

采用C語言編譯器編譯后結果

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

采用C++編譯器編譯后結果

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

Windows下名字修飾規則

對比Linux會發現,windows下vs編譯器對函數名字修飾規則相對復雜難懂,但道理都是類似的,我們就不做細致的研究了。

【擴展學習:C/C++函數調用約定和名字修飾規則--有興趣好奇的同學可以看看,里面有對vs下函數名修飾規則講解】

C/C++的調用約定

6. 通過這里就理解了C語言沒辦法支持重載,因為同名函數沒辦法區分。而C++是通過函數修

飾規則來區分,只要參數不同,修飾出來的名字就不一樣,就支持了重載

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

法區分。

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

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

相關文章

ANSI終端色彩控制知識散播(II):封裝的層次(Python)——不同的邏輯“一樣”的預期

基礎高階各有色&#xff0c;本原純真動乾坤。 筆記模板由python腳本于2025-08-22 18:05:28創建&#xff0c;本篇筆記適合喜歡終端色彩ansi編碼和python的coder翻閱。 學習的細節是歡悅的歷程 博客的核心價值&#xff1a;在于輸出思考與經驗&#xff0c;而不僅僅是知識的簡單復述…

前端無感刷新 Token 的 Axios 封裝方案

在現代前端應用中&#xff0c;基于 Token 的身份驗證已成為主流方案。然而&#xff0c;Token 過期問題常常困擾開發者 —— 如何在不打斷用戶操作的情況下自動刷新 Token&#xff0c;實現 "無感刷新" 體驗&#xff1f;本文將詳細介紹基于 Axios 的解決方案。什么是無…

【數據結構】線性表——鏈表

這里寫自定義目錄標題線性表鏈表&#xff08;鏈式存儲&#xff09;單鏈表的定義單鏈表初始化不帶頭結點的單鏈表初始化帶頭結點的單鏈表初始化單鏈表的插入按位序插入帶頭結點不帶頭結點指定結點的后插操作指定結點的前插操作單鏈表的刪除按位序刪除&#xff08;帶頭結點&#…

容器安全實踐(三):信任、約定與“安全基線”鏡像庫

容器安全實踐&#xff08;一&#xff09;&#xff1a;概念篇 - 從“想當然”到“真相” 容器安全實踐&#xff08;二&#xff09;&#xff1a;實踐篇 - 從 Dockerfile 到 Pod 的權限深耕 在系列的前兩篇文章中&#xff0c;我們探討了容器安全的底層原理&#xff0c;并詳細闡述…

百度面試題:賽馬問題

題目現在有25匹馬和一個賽馬場&#xff0c;賽馬場有5條跑道&#xff08;即一次只能比較5匹馬&#xff09;&#xff0c;并且沒有秒表等計時工具&#xff0c;因此每次賽馬只能知道這5匹馬的相對時間而非絕對時間。問&#xff1a;如何篩選出跑的最快的3匹馬&#xff1f;需要比賽幾…

centos下安裝Nginx(搭建高可用集群)

CentOS-7下安裝Nginx的詳細過程_centos7安裝nginx-CSDN博客 centos換yum軟件管理包鏡像 CentOS 7.* 更換國內鏡像源完整指南_centos7更換國內源-CSDN博客 VMware虛擬機上CentOS配置nginx后,本機無法訪問 執行命令&#xff1a;/sbin/iptables -I INPUT -p tcp --dport 80 -j…

實時視頻技術選型深度解析:RTSP、RTMP 與 WebRTC 的邊界

引言&#xff1a;WebRTC 的“光環”與現實落差 在實時音視頻領域&#xff0c;WebRTC 常常被貼上“終極解決方案”的標簽&#xff1a;瀏覽器原生支持、無需插件、點對點傳輸、毫秒級延遲&#xff0c;這些特性讓它在媒體和開發者群體中擁有了近乎神話般的地位。許多人甚至認為&a…

基于深度學習的阿爾茨海默癥MRI圖像分類系統

基于深度學習的阿爾茨海默癥MRI圖像分類系統 項目概述 阿爾茨海默癥是一種進行性神經退行性疾病&#xff0c;早期診斷對于患者的治療和生活質量至關重要。本項目利用深度學習技術&#xff0c;基于MRI腦部掃描圖像&#xff0c;構建了一個高精度的阿爾茨海默癥分類系統&#xff0…

54 C++ 現代C++編程藝術3-移動構造函數

C 現代C編程藝術3-移動構造函數 文章目錄C 現代C編程藝術3-移動構造函數場景1&#xff1a;動態數組資源轉移 #include <iostream> #include <vector> class DynamicArray { int* data; size_t size; public: // 移動構造函數&#xff08;關鍵實現&#xf…

Sping Boot + RabbitMQ :如何在Spring Boot中整合RabbitMQ實現消息可靠投遞?

Spring Boot整合RabbitMQ實現消息可靠投遞全解析 在分布式系統中&#xff0c;消息中間件是解耦、異步、流量削峰的核心組件。RabbitMQ作為高可靠、易擴展的AMQP協議實現&#xff0c;被廣泛應用于企業級場景。但消息傳遞過程中可能因網絡波動、服務宕機等問題導致消息丟失&#…

STAR-CCM+|K-epsilon湍流模型溯源

【1】引言 三維CFD仿真經典軟件很多&#xff0c;我接觸過的有Ansys和STAR-CCM兩種。因為一些機緣&#xff0c;我使用STAR-CCM更多&#xff0c;今天就來回顧一下STAR-CCM中K-epsilon湍流模型的基本定義。 【2】學習地址介紹 點擊鏈接User Guide可以到達網頁版本的STAR-CCM 24…

osgEarth 圖像融合正片疊底

* 需求&#xff1a;* 高程渲染圖 RGB.tif、 山體陰影圖 AMP.tif** 高程渲染圖 rgb波段分別 乘以 山體陰影圖r波段&#xff0c; 然后除以255(AI說 讀取的紋理就已經歸一化到了 0~1 范圍&#xff0c;不用除以 255)。本人遙感知識匱乏。問了AI,以上 需求在許多商業軟件上已實現。…

Java接口響應速度優化

在 Java 開發中&#xff0c;接口響應速度直接影響用戶體驗和系統吞吐量。優化接口性能需要從代碼、數據庫、緩存、架構等多個維度綜合考量&#xff0c;以下是具體方案及詳細解析&#xff1a;一、代碼層面優化代碼是接口性能的基礎&#xff0c;低效的代碼會直接導致響應緩慢。1.…

A Large Scale Synthetic Graph Dataset Generation Framework的學習筆記

文章的簡介 作者提出了一個可擴展的合成圖生成框架&#xff0c;能夠從真實圖中學習結構和特征分布&#xff0c;并生成任意規模的圖數據集&#xff0c;支持&#xff1a; 節點和邊的結構生成節點和邊的特征生成特征與結構的對齊&#xff08;Aligner&#xff09; 它區別于GraphWor…

Android12 Framework讀寫prop屬性selinux報錯解決

文章目錄問題描述解決過程相關文章問題描述 Android讀prop值時&#xff0c;就算是system應用&#xff0c; 也需要selinux權限&#xff0c;否則會報錯。 java代碼如下 SystemProperties.get("ro.input.resampling", "")selinux報錯如下 2025-06-28 17:57:…

【圖文版】AIOT 小智 AI 聊天機器人 ESP32 項目源碼圖解

前言 小智 AI 聊天機器人是最近一個很火的開源項目&#xff0c;它借助LLM大模型以及TTS等AI的能力&#xff0c;通過自然語言來與其對話實現交互。它可以回答任何問題、播放音樂、背誦古詩&#xff0c;頗有未來AI機器人的雛形。 因為最近工作上的需要對其進行了研究&#xff0c;…

250821-RHEL9.4上Docker及Docker-Compose的離線安裝

在 離線環境下 在 RHEL (Red Hat Enterprise Linux) 系統上安裝 Docker 和 Docker Compose&#xff0c;需要提前在有網絡的環境中下載相關 RPM 包及依賴&#xff0c;然后在目標機器上進行安裝。以下是比較完整的步驟&#xff1a; 1. Docker及Docker-Compose離線安裝 在 RHEL 9.…

react相關知識

1.類組件和函數組件&#xff08;1&#xff09;類組件import React, { Component } from react;class UserProfile extends Component {constructor(props) {super(props);this.state {userData: null,isLoading: true,};this.timerId null;}componentDidMount() {// 模擬 API…

算法第五十五天:圖論part05(第十一章)

并查集理論基礎并查集主要有兩個功能&#xff1a;將兩個元素添加到一個集合中。判斷兩個元素在不在同一個集合class UnionFind:def __init__(self, n):"""初始化并查集"""self.n nself.father list(range(n)) # 每個節點自己是根self.rank […

雨霧天氣漏檢率驟降80%!陌訊多模態車牌識別方案實戰解析

一、行業痛點&#xff1a;車牌識別的天氣敏感性據《智慧交通系統檢測白皮書》統計&#xff0c;雨霧環境下傳統車牌識別漏檢率高達42.7%&#xff08;2024年數據&#xff09;。主要存在三大技術瓶頸&#xff1a;1.??水膜干擾??&#xff1a;擋風玻璃水漬導致車牌區域紋理模糊2…