【C++】入門基礎知識(1.5w字詳解)

本篇博客給大家帶來的是一些C++基礎知識!包含函數棧幀的詳解!

🐟🐟文章專欄:C++

🚀🚀若有問題評論區下討論,我會及時回答

??歡迎大家點贊、收藏、分享!

今日思想:微事不通,粗事不能者,必勞;大事不得,小事不為者,必貧——劉向!

一、C++簡介

? ? ? ? C++是在C語言的基礎上完善C語言的不足(表達能力、可維護性、可擴展性)而發展而來的一門語言,它是由Bjarne Stroustrup(本賈尼 斯特勞斯特盧普)命名并完善。

? ? ? ? C++和C語言的區別:

????????C語言:面向過程。C++:面向對象和泛型編程。

? ? ? ? C++:由于C++是在C語言的基礎上完善而來的一門語言,它比C語言更加便捷,很多人學習了這門語言之后都不想寫C語言了

二、C++的重要性

? ? ? ? 論述一門編程語言的重要性我們可以看它在各種語言的排名、能干些什么(僅個人觀點)。

? ? ? ? 1、2025年5月TIOBE編程語言排行榜:

? ? ? ? 從上圖我們可以看出在近幾年C++的排名是在前5名的,這表明在各行各業C++起著至關重要的作用。

? ? ? ? 2、C++作用

? ? ? ? 1)大型系統軟件開發。瀏覽器、操作系統、編譯器等的開發。

? ? ? ? 2)音視頻處理。FFmpeg、WebRTC等開發最主要的技術棧就是C++。

? ? ? ? 3)PC客戶端開發。開發Windows上的桌面軟件。

? ? ? ? 4)服務端開發。游戲服務、流媒體服務、量化高頻交易服務等的開發。

? ? ? ? 5)游戲引擎開發。開發游戲的。

? ? ? ? 6)嵌入式開發。把具有技術能力的主控板嵌入到機器裝置或者電子裝置的內部,通過軟件來控制這些裝置。如:智能手環、攝像頭、掃地機器人等。

? ? ? ? 7)機器學習引擎。底層用C++來實現,上層用python封裝起來。

? ? ? ? 8)測試開發測試。根據產品來設計測試用例,然后手動的方式進行測試。

三、C++推薦書籍

? ? ? ? 1)C++Primer:經典主講C++語言語法的書籍。

? ? ? ? 2)STL源碼剖析:從底層實現的角度結合STL源碼來剖析STL的實現。

? ? ? ? 3 ) Effctive C++:主講55個高效使用C++的條款。

四、C++第一個程序

? ? ? ? 我們之前一開始學C語言第一次編寫的代碼:

//C語言版
int main()
{printf("hello world\n");return 0;
}

? ? ? ? 由于C++是在C語言的基礎上完善的,它兼容C,那么我們可以在后綴為.cpp文件上實現C語言的代碼。

? ? ? ? 那么我們怎么用C++來實現呢??

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
using namespace std;//這里看不懂,往后學再回來看
//C++版int main()
{cout << "hello world" << endl;return 0;
}

? ? ? ? 他們的區別是什么??

? ? ? ? C++不用手寫數據類型,而C語言要。

例如:

//C語言版
int main()
{char arr[] = "hello wolde";printf("%s\n",arr);return 0;
}
//C ++版
int main()
{char arr[] = "hello wolde";cout << arr << endl;return 0;
}

五、命名空間(namespace)

????????1、namespace的使用價值

? ? ? ? 在工作或者學習中我們自己定義的變量、類、函的名稱有可能和庫里面、其他人定義的一樣,為了防止這樣的情況出現,我們使用namespace就能能很好解決這樣的問題。

????????2、namespace的定義

? ? ? ? ???1)namespace的書寫方式:namespace + 名字。

示例:

namespace LA
{}int main()
{return 0;
}

? ? ? ? 注意:namespace只能寫在全局域。他和結構體不一樣。

? ? ? ? 2)命名空間里面可以寫些什么?定義變量/函數/類型。

例如:


namespace LA
{int a = 0;int add(int x, int y){return x + y;}struct node{struct node* next;int data;};}

? ? ? ? 3)在C++中有四個域:局部域、全局域、類域、命名空間域。不同域可以定義同名變量。

除了命名空間域和類域不影響變量生命周期,其他會影響生命周期。

例如:

namespace LA
{int a = 0;//命名空間域int add(int x, int y){return x + y;}struct node{struct node* next;int data;};}
//C語言版int a = 8;//全局域
int main()
{int a = 1;//局部域char arr[] = "hello wolde";printf("%s\n",arr);return 0;
}

? ? ? ? 注意:如果訪問同名變量,先訪問局部再訪問全局(就近原則)。如果要訪問命名空間域的要使用域作用限定符( ::),::a 訪問全局域的a,LA::a訪問命名空間域的a。編譯器默認的查找規則:先局部再全局。

? ? ? ? 4)在工作中,如果自己定義類型或者變量與其他人一樣,各自可以把自己寫的變量和類型放到的自己的命名空間域里面,這樣就解決了命名沖突的問題。

? ? ? ? 5)命名空間域可以嵌套使用。一般只嵌套兩層,多個嵌套的命名空間域會認為是一個命名空間域。

例如:


namespace LA
{int a = 0;//命名空間域namespace LB{int a = 10;}int add(int x, int y){return x + y;}struct node{struct node* next;int data;};}
//C語言版int a = 8;//全局域
int main()
{int a = 1;//局部域//char arr[] = "hello wolde";printf("%d\n",LA::LB::a);return 0;
}

? ? ? ? 6)C++標準庫放在std(standard)命名空間里面。

? ? ? ? 3、命名空間的使用

? ? ? ? ? ? 訪問命名空間的三種方法:指定命名空間訪問(::)、展開命名空間中全部成員、using將命名空間中某個成員展開。

? ? ? ? ? ? 域作用限定符之前講過了,我們看看展開命名空間中全部成員:

? ? ? ? ? ? 1)展開命名空間中全部成員即:影響編譯器的查找規則,此時他們從命名空間域變成了全局域了,不用使用域作用限定符就能訪問。

示例:

namespace LA
{int a = 0;//命名空間域int b = 1;namespace LB{int a = 10;}int add(int x, int y){return x + y;}struct node{struct node* next;int data;};}
//C語言版
using namespace LA;
//int a = 8;//全局域
//using LA::b;int main()
{//int a = 1;//局部域//char arr[] = "hello wolde";printf("%d\n",a);

注意:這樣的沖突風險極大,項目不推薦。

? ? ? ?2) 如果經常使用命名空間中的某個成員可以指定的把他展開:

例如:

namespace LA
{int a = 0;//命名空間域int b = 1;namespace LB{int a = 10;}int add(int x, int y){return x + y;}struct node{struct node* next;int data;};}
//C語言版
//using namespace LA;
//int a = 8;//全局域
using LA::b;int main()
{//int a = 1;//局部域//char arr[] = "hello wolde";printf("%d\n",b);return 0;
}

? ? ? ? 因為我們經常使用C++中的cout、cin、endl(換行),所以我們一開始就展開標準庫。

例如:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
using namespace std;

? ? ? ? 如果不展開std,我們使用cout、cin等要指定std命名空間域:

#include<stdio.h>
#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
//using namespace std;int main()
{std::cout << "hello world" << std::endl;return 0;
}

六、C++輸入和輸出

? ? ? ? 我們一開始就<iostream>文件,iostream是Input Output Stream的縮小,是標準的輸入輸出庫,定義了標準的輸入和輸出對象。

? ? ? ? 1)std::cin是istream類的對象,它主要面向窄字符的標準輸入流。

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

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

? ? ? ? 4)<<是流插入運算符,>>是流提取運算符。在C語言里面他們分別為位運算左移和右移。

? ? ? ? 5)使用C++的輸入輸出它不用像C語言那樣指定類型,更加方便,其實它的本質就是運算符重載。

#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
//using namespace std;int main()
{std::cout << "hello world" << '\n'<<"abc"<<std::endl;int a;std::cin >> a ;//不能使用換行return 0;
}

? ? ? ? 6)我們包含<iosream>其實就包含了<stdio.h>,我們包含<stdio.h>也可以用printf和scanf函數(VS是這樣,其他不知道)。

?七、函數重載

? ? ? ? C語言是不支持同名函數在同一個作用域的,而C++支持,不過C++對于同名函數的書寫是有要求的,具體如下:

? ? ? ? 1)同名函數的形參不同。

#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
using namespace std;int Add(int x)
{cout << "Add(int x,int y)"<<endl;return 1;
}int Add(char x, int y)
{cout << "Add(int x,int y)" << endl;return 4;
}int main()
{Add(1, 'a');Add('d', 1);return 0;

? ? ? ? 2)同名函數的類型不同。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
using namespace std;double Add(double x, double y)
{cout << "double" << endl;return x + y;
}int Add(int x, int y)
{cout << "int" << endl;return x + y;
}
int main()
{int a = 1;int b = 2;double a1 = 3.1;double b2 = 3.14;Add(a, b);Add(a1, b2);return 0;
}

? ? ? ? 3)同名函數的參數個數不同。

using namespace std;void fun()
{cout << "void fun()" << "\n" << endl;
}void fun(int a)
{cout << "void fun(int a)" << "\n" << endl;
}int main()
{fun();int f = 1;fun(f);return 0;
}

? ? ? ? 4)參數類型的順序不同(本質是類型不同)。

#include<iostream>//初學階段一旦我們使用流插入(cin)和流提取(cout)就要包含這個文件
using namespace std;int Add(int x, char y)
{cout << "Add(int x,int y)"<<endl;return 1;
}int Add(char x, int y)
{cout << "Add(int x,int y)" << endl;return 4;
}int main()
{Add(1, 'a');Add('d', 1);return 0;
}

注意:返回值不同不能構成函數重載。

如:

int fun()
{return 2;
}
int fun()
{return 3;
}int main()
{/*int z = Add();cout << z << endl;*/fun();return 0;
}

八、缺省參數(默認參數)

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

?例如:

#include<iostream>
using namespace std;void fun(int i=1)
{cout << i << endl;
}int main()
{fun();return 0;
}

?打印結果:

#include<iostream>
using namespace std;void fun(int i=1)
{cout << i << endl;
}int main()
{//fun();fun(2);return 0;
}

打印結果:

1、全缺省

? ? ? ? 全缺省是全部形參都給缺省值。

例如:

//全缺省
void fun(int a = 1,int b = 2,int c = 3)
{cout << a << endl;cout << b << endl;cout << c << endl;}int main()
{fun();//如果一個不傳實參,就用缺省值來打印fun(10);//傳一個則a=10,b,c就用缺省值來打印fun(10.20);//傳兩個實參,則a=10,b=20,c就用缺省值來打印fun(10, 20, 30);//傳三個實參,就用這三個值來打印return 0;
}

?打印結果:

注意:不能跳躍給實參。

如:?

#include<iostream>
using namespace std;//全缺省
void fun(int a = 1,int b = 2,int c = 3)
{cout << a << endl;cout << b << endl;cout << c << endl;cout <<'\n' << endl;}int main()
{//fun();//如果一個不傳實參,就用缺省值來打印//fun(10);//傳一個則a=10,b,c就用缺省值來打印//fun(10,20);//傳兩個實參,則a=10,b=20,c就用缺省值來打印//fun(10, 20, 30);//傳三個實參,就用這三個值來打印fun(, , 30);return 0;
}

?2、半缺省

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

? ? ? ? 注意:給缺省值只能從右往左傳。

如:

//半缺省
void fun(int a,int b,int c = 3)
{cout << a << endl;cout << b << endl;cout << c << endl;cout <<'\n' << endl;}int main()
{//fun();//如果一個不傳實參,就用缺省值來打印//fun(10);//傳一個則a=10,b,c就用缺省值來打印fun(10,20);//傳兩個實參,則a=10,b=20,c就用缺省值來打印fun(10, 20, 30);//傳三個實參,就用這三個值來打印return 0;
}

注意:不能跳躍著給缺省值。

如:

#include<iostream>
using namespace std;//半缺省
void fun(int a=1,int b,int c = 3)
{cout << a << endl;cout << b << endl;cout << c << endl;cout <<'\n' << endl;}int main()
{//fun();//如果一個不傳實參,就用缺省值來打印//fun(10);//傳一個則a=10,b,c就用缺省值來打印fun(10,20);//傳兩個實參,則a=10,b=20,c就用缺省值來打印fun(10, 20, 30);//傳三個實參,就用這三個值來打印return 0;
}

注意:傳實參的時候形參有幾個沒有給缺省值就至少要傳幾個實參。

如:

#include<iostream>
using namespace std;//半缺省
void fun(int a,int b,int c = 3)
{cout << a << endl;cout << b << endl;cout << c << endl;cout <<'\n' << endl;}int main()
{//fun();//如果一個不傳實參,就用缺省值來打印//fun(10);//傳一個則a=10,b,c就用缺省值來打印fun(10,20);//傳兩個實參,則a=10,b=20,c就用缺省值來打印fun(10, 20, 30);//傳三個實參,就用這三個值來打印return 0;
}

3、聲明和定義不能同時給缺省值

正確代碼:

//聲明
#pragma onceint Add(int x = 10, int y = 20);
//定義
int	Add(int x, int y)
{return x + y;
}int main()
{int z = Add();cout << z << endl;//打印結果為30return 0;
}

錯誤代碼:

//聲明
#pragma onceint Add(int x = 10, int y = 20);
//定義
int	Add(int x=1, int y=2)
{return x + y;
}int main()
{int z = Add();cout << z << endl;return 0;
}

注意:不能在定義那里給缺省值,而定義那里給,因為我們包含的是頭文件,別人使用是聲明的那個。

注意:如果一個函數有缺省值,另外一個沒有,則他們構成函數重載,但是他們在調用的使用有些問題。

如:

void fun()
{cout << "void fun "<<endl;
}
void fun(int a=1)
{cout << "void fun(int a=1)" << endl;
}int main()
{/*int z = Add();cout << z << endl;*/fun(10);//沒有問題fun();//有問題,不知道調用哪個return 0;
}

九、引用

? ? ? ? 引用就是給一個變量取別名,例如:我叫橘頌,別人叫我小橘子,那么小橘子就是我,小橘子的改變等于我的改變。引用的符號為:&,引用的底層是指針,引用不能代替指針(例如:鏈表)。

代碼示例:

int main()
{int a;int& b = a;b = 10;//b=10,那么a也等于10;cout << a << endl;cout << b << endl;return 0;
}

打印結果:

注意:他們的地址也是一樣的,如:

int main()
{int a;int& b = a;b = 10;//b=10,那么a也等于10;cout << &a << endl;cout << &b << endl;return 0;
}

?打印結果:

1、引用的特性

? ? ? ? 1、1引用定義時必須初始化

int main()
{//錯誤代碼int a = 1;int& b;//編譯報錯,必須確定引用的對象是什么b = a;//正確代碼int a = 1;int& b = a;return 0;
}

? ? ? ? 1、2一個變量可以有多個引用

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

? ? ? ? 1、3引用一旦引用一個實體就不能再引用其他實體

int main()
{int a = 1;int b = 2;int& c = a;c = b;//這里不是引用而是賦值return 0;
}

2、引用的使用

? ? ? ? 2.1引用傳參

? ? ? ? 原來我們傳地址過去,通過解引用來改變他們,選擇同取別名來直接改變,增強了代碼的可讀性。

void swap(int& x, int& y)//相當于int& x=a,int& y=b
{int tmp = x;x = y;y = tmp;
}int main()
{int a = 10;int b = 20;swap(a, b);//不能swap(10,20),常量不能做變量的別名,如果真的要這樣傳可以swap(const int& x,const int& y)讓x具有常屬性cout << a << endl;cout << b << endl;return 0;
}

? 打印結果:

typedef struct test
{int a;
}test;int Add(test& x)//x就是a
{x.a = 2;return x.a + 2;
}
int main()
{test a;Add(a);//不用傳地址過去return 0;
}

2、2引用在引用傳參和引用做返回值中可以提高效率和減少拷貝并且改變引用對象的同時也改變被引用對象

? ? ? ? 我們以前在使用C語言傳值調用的時候會進行臨時對象的拷貝而引用不用這樣提高了代碼執行的效率,當然我們使用傳址傳參的時候會開空間,而引用不用這樣也提高了代碼的效率。

int tmp;
int& Add(int x, int y)
{tmp = x + y;//如果定義成int tmp=x+y,把第一行代碼刪除,當函數棧幀銷毀的時候tmp就成了野引用return tmp;//返回值必須為左值(變量),不能是常量
}
int main()
{tmp += Add(2, 3);//tmp=5+5;cout << tmp << endl;return 0;
}

注意:如果我們返回是常量,這時候會拷貝臨時對象作為返回值,而臨時對象具有常屬性,不能讓引用作為返回值。左值:能取地址的就是左值,左值能被修改。右值和左值相反。

示例:

int Add(int x, int y)
{return x+y;//傳值返回的時候會生成它的拷貝即臨時對象,臨時對象具有常性,相當于被const修飾了
}
int main()
{Add(2, 3)+=2;//相當于5=5+2,這樣是不對的,一般都是變量在左邊:a+=2;a=a+2;return 0;
}

課外知識:

int main()
{int arr[10];//越界讀,編譯器通常不報錯cout << arr[10] << endl;cout << arr[11] << endl;cout << arr[12] << endl;return 0;
}
int main()
{int arr[10];//越界讀,編譯器通常不報錯//cout << arr[10] << endl;//cout << arr[11] << endl;//cout << arr[12] << endl;//越界寫通常會報錯,但是數組檢查一般是抽查,如果多次越界寫偶爾不會報錯arr[10] = 10;return 0;
}

十、const引用

補充知識:

int main()
{const int a = 2;//表示a指向內容不能被修改,例如:a++int const b = 2;//也表示b指向的內容不能改變,和上一句代碼一樣int const b=2和const int b=2,一樣//在const修飾指針的時候不一樣int c = 2;int d = 3;const int* f = &c;//const在*號的左邊,表示指向的內容不能別修改如:*f=20;但是本身可以改變:d=*f;int* const f = &c;//本身不能被修改如:f=NULL;但是指向的內容可以改變如:*f=20return 0;
}

1、權限的放大和縮小

int main()
{const int a = 1;int& b = a;//權限的放大,本來a是不能改變它指向的內容,你b是a的別名之后有權力改變,倒反天罡啊!!const int& b = a;//正確代碼return 0;
}
int main()
{//const int a = 1;//int& b = a;//權限的放大,本來a是不能改變它指向的內容,你b是a的別名之后有權力改變,倒反天罡啊!!//const int& b = a;//正確代碼int b = 2;const int& a = b;//權限的縮小,但是a不能a++return 0;
}

注意:權限可以縮小不能放大。

2、const使用的場景

1):

void swap(int& x, int& y)
{//
}
int main()
{const int a = 10;const int b = 30;swap(a, b);//權限的放大return 0;
}

正確代碼:

void swap(const int& x,const int& y)
{//
}
int main()
{const int a = 10;const int b = 30;swap(a, b);return 0;
}

2):?

void swap(int& x,int& y)
{//
}
int main()
{const int a = 10;const int b = 30;swap(10, 10);//編譯錯誤return 0;
}

正確代碼:

void swap(const int& x,const int& y)
{//
}
int main()
{const int a = 10;const int b = 30;swap(10, 10);//int& x = 1;//變量不能做常量的別名const int& x = 1;//const修飾變量x讓它具有常屬性,而且不能改變它的內容即:x++return 0;
}

3):

int main()
{double a = 3.14;int b = a;//進行隱式類型轉換,過程是a創建一個臨時對象給b,把a的整形(3)給它//int& c = a;//錯誤代碼,因為臨時對象具有常屬性,引用的是它的臨時對象,相當于權限放大。const int& c = a;//正確代碼return 0;
}

注意:臨時對象一般存儲在寄存器,而寄存器一般是4個字節或者8個字節,存儲不下之后放到內存的某塊區域。臨時對象的產生的場景:函數傳值返回,表達式運算(a+b),類型轉換等。

4)權限的放大和縮小只涉及到引用和指針(大白話:除了引用和指針有權限的放大和縮小,其他都沒有),不涉及到變量。

int main()
{const int a = 3;int b = a;//a的改變不影響b,b的改變也不影響a,這個代碼實現的過程是把a的值拷貝給c,他們是兩個不同的空間return 0;
}

十一、指針和引用的關系

? ? ? ? 1)指針和引用互相不能替代,在語法層面上:引用是不開空間,指針要開空間(存儲地址),但是底層就不一定咯,就像老婆餅沒有老婆,引用的底層也是要開空間的,我們可以通過它匯編代碼來看一下:

int main()
{int a = 2;int& b = a;int* c = &a;*c = 3;return 0;
}

匯編代碼:

int main()
{
001118C0  push        ebp  
001118C1  mov         ebp,esp  
001118C3  sub         esp,0E8h  
001118C9  push        ebx  
001118CA  push        esi  
001118CB  push        edi  
001118CC  lea         edi,[ebp-28h]  
001118CF  mov         ecx,0Ah  
001118D4  mov         eax,0CCCCCCCCh  
001118D9  rep stos    dword ptr es:[edi]  
001118DB  mov         eax,dword ptr [__security_cookie (011A040h)]  
001118E0  xor         eax,ebp  
001118E2  mov         dword ptr [ebp-4],eax  
001118E5  mov         ecx,offset _889F381A_C++_test@cpp (011C066h)  
001118EA  call        @__CheckForDebuggerJustMyCode@4 (0111343h)  int a = 2;
001118EF  mov         dword ptr [a],2  int& b = a;
001118F6  lea         eax,[a]  //和下面指針代碼執行指令一模一樣,都要開空間存儲地址
001118F9  mov         dword ptr [b],eax  int* c = &a;
001118FC  lea         eax,[a]  
001118FF  mov         dword ptr [c],eax  *c = 3;
00111902  mov         eax,dword ptr [c]  
00111905  mov         dword ptr [eax],3  return 0;
0011190B  xor         eax,eax  
}

總結:引用的底層是指針。

? ? ? ? 2)引用和指針一樣,有野指針也有野引用。引用在定義的時候必須初始化,而指針可以不初始化(建議初始化防止成為野指針)

? ? ? ? 3)引用初始化引用一個對象之后就不能引用其他對象而指針可以。

? ? ? ? 4)sizeof計算引用的大小是引用類型的大小,而指針始終是地址空間所占字節個數(32位平臺下占4個字節,64位下是8字節)

示例:


//64位下
int main()
{int a = 2;int& b = a;cout << sizeof(b) << '\n' << endl;int* c = &a;*c = 3;cout << sizeof(c) << '\n' << endl;return 0;
}

打印結果:

? ? ? ? 5)指針很容易出現空指針和野指針的問題,而引用很少出現,能用引用就不用指針。

十二、inline(內聯)

補充知識:

//正確的宏函數的書寫
#define ADD(a,b) ((a)+(b))
//1)為什么不能加分號?
//2)為什么要加外面的括號?
//3)為什么要加里面的括號?int main()
{//1)ADD(a,b)((a)+(b));//int a = 2;//int b = 3;//if(ADD(a,b))//if((((2)+(3));)錯誤//{//	////}//2)ADD(a,b) (a)+(b)//int a = 2;//int b = 3;//cout << ADD(2, 3) * 5 << endl;//本來計算結果是25,但是(2)+(3)*5,結果是17,錯誤//3)ADD(a,b) a+b//cout << ADD(2&1, 3&1) << endl;//當出現比加號的優先級低的就會出現問題:本來預想:(2&1)+(3&1)結果:2&(1+3)&1return 0;
}

1)我們在C語言部分的時候經常定義宏函數,從上面的知識補充可知宏函數很復雜而且容易出錯,還不能調試,這些都是宏函數的缺點。宏函數的優點:預處理的時候直接替換不用建立函數棧幀,效率提高。這里我們C++設計了inline目的就是為了解決宏函數的缺點來代替宏函數。

2)函數可以解決宏函數的缺點但是會建立函數棧幀,效率降低。我們可以把函數放到內聯里面,這樣編譯C++時候調用的時候展開內聯函數,不用建立函數棧幀,提高效率。

小知識:內聯代替宏函數,const和enum代替宏常量。

3)inline對于編譯器來說只是一個建議,既然是個建議編譯器可以在調用的時候展開inline也不展開。不同編譯器關于inline什么情況展開各不相同,因為C++標準沒有規定。一般來說放到內聯的函數代碼較少的,會展開,反之會被編譯器忽略。

注意:關于函數棧幀的創立、銷毀和匯編代碼指令的理解我們要補充一下相關知識:

代碼示例:

inline int Add(int x, int y)
{
005A1910  push        ebp  
005A1911  mov         ebp,esp  
005A1913  sub         esp,0CCh  
005A1919  push        ebx  
005A191A  push        esi  
005A191B  push        edi  
005A191C  lea         edi,[ebp-0Ch]  
005A191F  mov         ecx,3  
005A1924  mov         eax,0CCCCCCCCh  
005A1929  rep stos    dword ptr es:[edi]  
005A192B  mov         ecx,offset _ACF1371A_test3@cpp (05AC15Ch)  
005A1930  call        @__CheckForDebuggerJustMyCode@4 (05A133Eh)  int ret = x + y;
005A1935  mov         eax,dword ptr [x]  
005A1938  add         eax,dword ptr [y]  
005A193B  mov         dword ptr [ret],eax  return ret;
005A193E  mov         eax,dword ptr [ret]  
}
005A1941  pop         edi  
005A1942  pop         esi  
005A1943  pop         ebx  
005A1944  add         esp,0CCh  
005A194A  cmp         ebp,esp  
005A194C  call        __RTC_CheckEsp (05A125Dh)  
005A1951  mov         esp,ebp  
005A1953  pop         ebp  
005A1954  ret  
int main()
{
005A1970  push        ebp  
005A1971  mov         ebp,esp  
005A1973  sub         esp,0CCh  
005A1979  push        ebx  
005A197A  push        esi  
005A197B  push        edi  
005A197C  lea         edi,[ebp-0Ch]  
005A197F  mov         ecx,3  
005A1984  mov         eax,0CCCCCCCCh  
005A1989  rep stos    dword ptr es:[edi]  
005A198B  mov         ecx,offset _ACF1371A_test3@cpp (05AC15Ch)  
005A1990  call        @__CheckForDebuggerJustMyCode@4 (05A133Eh)  int ret = Add(2, 3);
005A1995  push        3  
005A1997  push        2  
005A1999  call        Add (05A11D6h)  
005A199E  add         esp,8  
005A19A1  mov         dword ptr [ret],eax  cout << ret << endl;
005A19A4  mov         esi,esp  
005A19A6  push        offset std::endl<char,std::char_traits<char> > (05A103Ch)  
005A19AB  mov         edi,esp  
005A19AD  mov         eax,dword ptr [ret]  
005A19B0  push        eax  
005A19B1  mov         ecx,dword ptr [__imp_std::cout (05AB0A8h)]  
005A19B7  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (05AB09Ch)]  
005A19BD  cmp         edi,esp  
005A19BF  call        __RTC_CheckEsp (05A125Dh)  
005A19C4  mov         ecx,eax  
005A19C6  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (05AB0A0h)]  
005A19CC  cmp         esi,esp  
005A19CE  call        __RTC_CheckEsp (05A125Dh)  return 0;
005A19D3  xor         eax,eax  
}

圖片解疑:

好了我們接著解讀內聯是否展開的問題,有了前面函數棧幀的創建和銷毀的知識鋪墊我們可以得出一個結論:

通過匯編代碼觀察內聯是否展開,如果匯編代碼出現call Add就是沒有展開,反之就是展開了。

代碼示例:

	int ret = Add(2, 3);
005A1995  push        3  
005A1997  push        2  
005A1999  call        Add (05A11D6h)  //沒有展開,根據Add函數的地址跳到Add函數執行代碼
005A199E  add         esp,8  
005A19A1  mov         dword ptr [ret],eax  

注意:VS的call Add后面跟的不是Add函數的地址,它做了一些優化。如:

現在Add后面跟的才是Add函數的地址。

總結欸:通過上面的知識我們可得:inline在一定程度上會讓編譯后的可執行程序變大。可執行程序變大,用戶體驗降低。如果inline展開受程序員來控制(本質就是對程序員的不放心),那么可執行程序會變大,所以inline受編譯器來控制是否展開。

示例:函數編譯號之后一共10行指令,調用10000次,inline展開合計:10000*10行,不展開:10000+10行(原因:通過調用函數來棧幀來完成)?

4)VS編譯器debug版本下默認是不展開inline的,這樣方便調試。debug版本如果想展開需要設置一下:

?這時候就展開了,代碼示例:

	int ret = Add(2, 3);
00301560  mov         eax,2  
00301565  add         eax,3  
00301568  mov         dword ptr [ebp-8],eax  
0030156B  mov         ecx,dword ptr [ebp-8]  
0030156E  mov         dword ptr [ret],ecx  

5)inline不建議聲明和定義分離,分離會導致鏈接錯誤,因為inline被展開,就沒有函數地址,鏈接就會出現報錯。

//test2.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"test2.h"
using namespace std;inline int Add(int x, int y)
{int ret = x + y;return ret;
}
//test2.h
#pragma onceinline int Add(int x, int y);
//test3.cpp
#define _CRT_SECURE_NO_WARNINGS 1
using namespace std;
#include<iostream>#include"test2.h"
int main()
{int ret = Add(2, 3);cout << ret << endl;return 0;
}

結果:

解讀:鏈接錯誤,找不到Add函數。一般來說我們只要定義和聲明分離,然后在使用Add函數的時候我們在前面聲明一下,給編譯器說一聲這個函數是有的,然后在鏈接過程中在其他文件的符號表找到Add函數的地址填充使用Add函數的時候給他一個地址,這樣就算是完整的鏈接了(聲明的時候是沒有Add函數的地址的,定義才有)。那么inline使用定義和聲明分離,在inline定義的那個文件上,鏈接過程中是不會放到符號表的,如果加static也是不會放到符號表的。

正確書寫inline的定義:不要定義和聲明分離,把它直接放到頭文件就行。

代碼示例:

//test2.h
#pragma onceinline int Add(int x, int y)
{int ret = x + y;return ret;
}

十三、nullptr

? ? ? ? 在C語言中我們知道NULL是個宏,被定義為((void*)0),空指針不是一個無效的地址,它是內存地址的最開始的第一個字節的編號(地址是指向一個一個的字節),系統認為它沒有人用會自動的把它空出來進行初始化,如果訪問就會報錯。在C++中被定義為0。

? ? ? ? NULL的缺陷:

void fun(int x)
{cout << "void fun(int x)" << endl;
}
void fun(int* ptr)
{cout << "void fun(int* ptr)" << endl;
}
int main()
{fun(0);//調用第一個函數fun(NULL);//在C++中NULL在預處理的時候被替換成0,所以調用第一個函數fun((int*)NULL);//如果想調用第二個函數就要對NUULL進行強轉,太麻煩了。所以在C++創建了nullptrfun(nullptr);return 0;
}

運行結果:

? ? ? ? 注意:nullptr是一個特殊的關鍵字,它可以自動轉換成任何類型的指針類型,但是不會轉換整型,為什么?如果能轉換成整型要NULL干嘛!,就是說在C++中NULL表示0,而nullptr表示指針。?

完!!

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

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

相關文章

二.MySQL庫的操作

一.創建數據庫create database 名稱; 字符集和校驗規則 一、字符集&#xff08;Character Set&#xff09; 表示數據庫中可以使用哪些字符。 例如&#xff1a;utf8 可以存儲包括中文在內的多種語言字符&#xff0c;gbk 更適合中文字符環境。 功能舉例控制支持哪些語言字符utf…

【Linux 學習計劃】-- 命令行參數 | 環境變量

目錄 命令行參數 環境變量 環境變量的本質是什么&#xff1f; 相關配置文件 修改環境變量的相關操作 代碼獲取env —— environ 內建命令 結語 命令行參數 試想一下&#xff0c;我們的main函數&#xff0c;也是一個函數&#xff0c;那么我們的main函數有沒有參數呢&am…

具有離散序列建模的統一多模態大語言模型【AnyGPT】

第1章 Instruction 在人工智能領域、多模態只語言模型的發展正迎來新的篇章。傳統的大型語言模型(LLM)在理解和生成人類語言方面展現出了卓越的能力&#xff0c;但這些能力通常局限于 文本處理。然而&#xff0c;現實世界是一個本質上多模態的環境&#xff0c;生物體通過視覺、…

git查看commit屬于那個tag

1. 快速確認commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支與master關系 # git show --all 0.5.67_0006 --stat 以縮…

day10機器學習的全流程

浙大疏錦行 1.讀取數據 import pandas as pd import pandas as pd #用于數據處理和分析&#xff0c;可處理表格數據。 import numpy as np #用于數值計算&#xff0c;提供了高效的數組操作。 import matplotlib.pyplot as plt #用于繪制各種類型的圖表# 設置中文字體…

基于對比學習的推薦系統開發方案,使用Python在PyCharm中實現

以下是一個基于對比學習的推薦系統開發方案,使用Python在PyCharm中實現。本文將詳細闡述技術原理、系統設計和完整代碼實現。 基于對比學習的推薦系統開發方案 一、技術背景與原理 1.1 對比學習核心思想 對比學習(Contrastive Learning)通過最大化正樣本相似度、最小化負…

2025山東CCPC題解

文章目錄 L - StellaD - Distributed SystemI - Square PuzzleE - Greatest Common DivisorG - Assembly Line L - Stella 題目來源&#xff1a;L - Stella 解題思路 簽到題&#xff0c;因為給出的字母不是按順序&#xff0c;可以存起來賦其值&#xff0c;然后在比較。 代碼…

某航參數逆向及設備指紋分析

文章目錄 1. 寫在前面2. 接口分析3. 加密分析4. 算法還原5. 設備指紋風控分析與繞過 【&#x1f3e0;作者主頁】&#xff1a;吳秋霖 【&#x1f4bc;作者介紹】&#xff1a;擅長爬蟲與JS加密逆向分析&#xff01;Python領域優質創作者、CSDN博客專家、阿里云博客專家、華為云享…

Python訓練營---Day41

DAY 41 簡單CNN 知識回顧 數據增強卷積神經網絡定義的寫法batch歸一化&#xff1a;調整一個批次的分布&#xff0c;常用與圖像數據特征圖&#xff1a;只有卷積操作輸出的才叫特征圖調度器&#xff1a;直接修改基礎學習率 卷積操作常見流程如下&#xff1a; 1. 輸入 → 卷積層 …

【Netty系列】Reactor 模式 2

目錄 流程圖說明 關鍵流程 以下是 Reactor 模式流程圖&#xff0c;結合 Netty 的主從多線程模型&#xff0c;幫助你直觀理解事件驅動和線程分工&#xff1a; 流程圖說明 Clients&#xff08;客戶端&#xff09; 多個客戶端&#xff08;Client 1~N&#xff09;向服務端發起連…

前端開發中 <> 符號解析問題全解:React、Vue 與 UniApp 場景分析與解決方案

前端開發中 <> 符號解析問題全解&#xff1a;React、Vue 與 UniApp 場景分析與解決方案 在前端開發中&#xff0c;<> 符號在 JSX/TSX 環境中常被錯誤解析為標簽而非比較運算符或泛型&#xff0c;導致語法錯誤和邏輯異常。本文全面解析該問題在不同框架中的表現及解…

【Web應用】 Java + Vue 前后端開發中的Cookie、Token 和 Swagger介紹

文章目錄 前言一、Cookie二、Token三、Swagger總結 前言 在現代的 web 開發中&#xff0c;前后端分離的架構越來越受到歡迎&#xff0c;Java 和 Vue 是這一架構中常用的技術棧。在這個過程中&#xff0c;Cookie、Token 和 Swagger 是三個非常重要的概念。本文將對這三個詞進行…

投稿Cover Letter怎么寫

Cover Letter控制在一頁比較好&#xff0c;簡短有力地推薦你的文章。 Dear Editors: Small objects detection in remote sensing field remains several challenges, including complex backgrounds, limited pixel representation, and dense object distribution, which c…

創建型設計模式之Prototype(原型)

創建型設計模式之Prototype&#xff08;原型&#xff09; 摘要&#xff1a; Prototype&#xff08;原型&#xff09;設計模式通過復制現有對象來創建新對象&#xff0c;避免重復初始化操作。該模式包含Prototype接口聲明克隆方法、ConcretePrototype實現具體克隆邏輯&#xff…

spark在執行中如何選擇shuffle策略

目錄 1. SortShuffleManager與HashShuffleManager的選擇2. Shuffle策略的自動選擇機制3. 關鍵配置參數4. 版本差異(3.0+新特性)5. 異常處理與調優6. 高級Shuffle服務(CSS)1. SortShuffleManager與HashShuffleManager的選擇 SortShuffleManager:默認使用,適用于大規模數據…

AUTOSAR圖解==>AUTOSAR_EXP_AIADASAndVMC

AUTOSAR高級駕駛輔助系統與車輛運動控制接口詳解 基于AUTOSAR R22-11標準的ADAS與VMC接口規范解析 目錄 1. 引言2. 術語和概念說明 2.1 坐標系統2.2 定義 2.2.1 乘用車重心2.2.2 極坐標系統2.2.3 車輛加速度/推進力方向2.2.4 傾斜方向2.2.5 方向盤角度2.2.6 道路變量2.2.7 曲率…

26考研——文件管理_文件目錄(4)

408答疑 文章目錄 二、文件目錄1、目錄的作用與結構1.1、目錄的基本概念1.2、目錄的組織形式1.2.1、單級目錄結構1.2.2、兩級目錄結構1.2.3、多級&#xff08;樹形&#xff09;目錄結構1.2.4、無環圖目錄結構 1.3、目錄的實現方式1.3.1、線性列表1.3.2、哈希表 2、文件共享與鏈…

Maven 項目中集成數據庫文檔生成工具

在 Maven 項目中&#xff0c;可以通過集成 數據庫文檔生成工具&#xff08;如 screw-maven-plugin、mybatis-generator 或 liquibase&#xff09;來自動生成數據庫文檔。以下是使用 screw-maven-plugin&#xff08;推薦&#xff09;的完整配置步驟&#xff1a; 1. 添加插件配置…

WebSocket指數避讓與重連機制

1. 引言 在現代Web應用中&#xff0c;WebSocket技術已成為實現實時通信的重要手段。與傳統的HTTP請求-響應模式不同&#xff0c;WebSocket建立持久連接&#xff0c;使服務器能夠主動向客戶端推送數據&#xff0c;極大地提升了Web應用的實時性和交互體驗。然而&#xff0c;在實…

本地部署AI工作流

&#x1f9f0; 主流 RAG / 工作流工具對比表&#xff08;含是否免費、本地部署支持與資源需求&#xff09; 工具名類型是否支持 RAG可視化目標用戶是否免費支持本地部署本地部署一般配置Dify企業級問答系統平臺??非技術 & 企業用戶? 免費版 商業版? 支持2C4G 起&…