1.namespace產生原因
在C語言中,變量,函數,以至于類都是大量存在的,因此會產生大量的名稱存在于全局作用域中,可能產生很多沖突,至此c++的祖師爺為避免命名沖突和名字的污染,造出來了關鍵字namespace來解決這種問題
1.2查找規則
c/c++規定用任何變量類型的函數都要向上訪問,在向上的過程中去找到他的出處在編譯時候查找沒有在去全局中找。
例如;
在c語言中rand是包含在頭文件#include<stdlib,h>中的函數,因此編譯器無法判斷是函數還是變量。但事實上用c++可以完美解決。
2.namespace的特點
2.1用namespace來定義命名空間
后加名字加{成員},可定義變量/函數/類型。
1.在命名空間中定義的變量。
2.定義函數。
3.定義結構體類型。
2.2本質是定義一個全局域各自獨立
如2.1中的a在不同的域中可以定義同名且不沖突。
2.3 c++中的域
函數局部域,全局域,命名空間域,類域;域影響的是編譯時語法查找?個變量/函數/ 類型出處(聲明或定義)的邏輯,所有有了域隔離,名字沖突就解決了。局部域和全局域除了會影響 編譯查找邏輯,還會影響變量的?命周期,命名空間域和類域不影響變量?命周期。
?局部域
局部域是指在函數、代碼塊(如if
、for
、while
語句塊)內部定義的變量所處的作用域。在局部域中定義的變量,其生命周期從定義的位置開始,到所在的代碼塊結束時終止。當程序執行離開該代碼塊時,局部變量所占用的內存會被自動釋放。
#include <iostream>void testLocalScope() {// 局部變量 a,生命周期從這里開始int a = 10;std::cout << "Local variable a: " << a << std::endl;// 代碼塊結束,局部變量 a 的生命周期結束
}int main() {testLocalScope();// 這里無法訪問局部變量 areturn 0;
}
?全局域
全局域是指在所有函數、類和命名空間之外定義的變量所處的作用域。全局變量的生命周期從程序開始執行時就已經分配內存,直到程序結束時才會釋放內存。
#include <iostream>// 全局變量 b,生命周期從程序開始
int b = 20;void testGlobalScope() {std::cout << "Global variable b: " << b << std::endl;
}int main() {testGlobalScope();// 全局變量 b 仍然可以訪問std::cout << "Global variable b in main: " << b << std::endl;// 程序結束,全局變量 b 的生命周期結束return 0;
}
??命名空間域
命名空間域主要用于解決命名沖突的問題,它只是對標識符進行邏輯上的分組,并不影響變量的生命周期。在命名空間中定義的變量,其生命周期取決于它是全局變量還是局部變量。
#include <iostream>
// 全局命名空間
namespace GlobalNS {int globalVar = 10;void globalFunction() {std::cout << "This is a function in the global namespace. globalVar = " << globalVar << std::endl;}
}
void testLocalNamespace() {// 局部命名空間namespace LocalNS {int localVar = 20;void localFunction() {std::cout << "This is a function in the local namespace. localVar = " << localVar << std::endl;}}// 使用局部命名空間中的變量和函數std::cout << "Value of localVar in LocalNS: " << LocalNS::localVar << std::endl;LocalNS::localFunction();// 也可以使用全局命名空間中的變量和函數std::cout << "Value of globalVar in GlobalNS: " << GlobalNS::globalVar << std::endl;GlobalNS::globalFunction();
}int main() {// 可以直接在 main 函數中使用全局命名空間std::cout << "Value of globalVar in GlobalNS (from main): " << GlobalNS::globalVar << std::endl;GlobalNS::globalFunction();// 調用包含局部命名空間的函數testLocalNamespace();// 嘗試在 main 函數中使用局部命名空間(這是錯誤的,因為局部命名空間超出了作用域)// 下面這行代碼會導致編譯錯誤// std::cout << LocalNS::localVar << std::endl;return 0;
}
域的名字在自己內部只能用一次,同一個域名字不能重復,但不同的域可以
2.4?namespace只能定義在全局,當然他還可以嵌套定義
?
圖1;嵌套后要區別調用
圖二注:當庫很大或項目大還可以套中套來解決命名沖突?
?2.5項??程中多?件
項??程中多?件中定義的同名namespace會認為是?個namespace,不會沖突。
多?件中可以定義同名namespace,他們會默認合并到?起,就像同?個namespace?樣
定個文件一個命名空間都會封在一起?
例如:當同時定義棧與隊列?會產生多個文件
3.命名空間使用
?編譯查找?個變量的聲明/定義時,默認只會在局部或者全局查找,不會到命名空間??去查找。所以 下?程序會編譯報錯。所以我們要使?命名空間中定義的變量/函數,有三種?式:
3.1指定命名空間訪問
不易出錯,推薦使用
3.2?展開命名空間中全部成員(展開頭文件與命名空間的區別)
展開命名空間中全部成員--》變成全局變量因此可能造成命名沖突
展開頭文件是將頭文件在預處理階段將頭文件的代碼拷貝過來
不推薦,沖突風險大,但是在小練習中為方便推薦使用。
3.3折中using將命名空間中某個成員展開
?當a用的不多但是b用的多是將b解開變成全局;
項?中經常訪問的不存在沖突的成員推薦這種?式
?前提了解
<<是流插入運算符,>>是流提取運算符。(C語?還?這兩個運算符做位運算左移/右移)
cout/cin/endl等都屬于C++標準庫,C++標準庫都放在?個叫std(standard)的命名空間中
C++輸?&&輸出&&換行?
1包含文件#include<iostream>?
是Input Output Stream 的縮寫,是標準的輸?、輸出流庫,定義了標準的輸?、輸出對象。
2輸出?
std::cout是iostream類的對象,它主要?向窄字符的標準輸出流。
#include <iostream>
int main()
{int a = 0;double b = 0.1;char c = 'x';
//自動識別變量類型std::cout << a << std::endl;std::cout << b << " " << c << std::endl;return 0;
}
好處:1.連續輸出并且與printf可以一起使用2.可自動識別所輸出的類型
壞處:此用法控制輸出的小數繁瑣相比用printf更好用些
3輸入
std::cin是istream類的對象,它主要?向窄字符的標準輸?流。
#include <iostream>
int main()
{int a = 0;double b = 0.1;char c = 'x';std::cin >> a;std::cin >> b >> c;std::cout << "輸出"<<std::endl;std::cout << a << std::endl;std::cout << b << " " << c << std::endl;return 0;
}
注:cout/cin/endl等都屬于C++標準庫,C++標準庫都放在?個叫std(standard)的命名空間中,所以要 通過命名空間的使??式去?他們。
4.換行
std::endl是?個函數,流插?輸出時,相當于插??個換?字符加刷新緩沖區。
相比與在C語言中學到的“/n”在c++中std::endl在不同平臺上都可以運行
上述圖片有其應用
缺省函數
1.缺省參數說明
將聲明或定義函數時的參數指定一個值(該值即為缺省值)
注:當實參沒有指定數值就采用缺省值否則采用實參。
#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)//在形參后面加個值
{cout << a << endl;
}
int main()
{Func(); // 沒有傳參時,使?參數的默認值 Func(10); // 傳參時,使?指定的實參 return 0;
}
2.缺省參數的分類
全缺省就是全部形參給缺省值
?
?半缺省就是部分形參給缺省值
?
注 :C++規定半缺省參數必須從右往左 依次連續缺省,不能間隔跳躍給缺省值。
?
?
3.應用
例如,在棧構建空間時會動態開辟通常以二的倍速增加一定就會虧損,因此控制空間大小顯得尤為重要。
// 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++函數重載
?C++?持在同?作?域中出現同名函數
但是分為參數類型不同,參數個數不同,參數順序不同
1.參數類型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return 0;
}
double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return 0;
}
int main()
{Add(10, 20);Add(10.1, 20.2);return 0;
}
2、參數個數不同 ?
#include <iostream>
using namespace std;
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl;
}
int main()
{f();f(10);return 0;
3、參數類型順序不同(類型不同)
#include <iostream>
using namespace std;
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()
{f(10, 'a');f('a', 10);return 0;
}
特殊的?
1.返回值不同
不能作為重載條件,因為調?時也?法區分
void fxx()
{
}
int fxx()
{return 0;
}
2.一個參數問題
f()但是調?時,會報錯,存在歧義,編譯器不知道調?誰
void f1()
{cout << "f()" << endl;
}
void f1(int a = 10)
{cout << "f(int a)" << endl;
}
引用
?一,概念
是對已知的變量取別名,并與它引用的對象共用同一塊空間。
類型& 引?別名 = 引?對象;
int a = 0; // 引?:b和c是a的別名 int& b = a;
就像在《西游記》中孫悟空(變量)可以叫孫大圣,悟空,猴哥。這里指的全是他一個人(空間)。
?但是,不得不🤮一下祖師爺當初設計的與C語言相同符號&,讓后續辨析&費了很大勁。
?二,引用的特征
?🔪?引?在定義時必須初始化
int main() { int a = 10; int& ra; return 0; }
?🔪🔪?個變量可以有多個引?
int main() { int a = 0; int& b = a; int& c = a; cout << &a << endl; cout << &b << endl; cout << &c << endl; return 0; }
typedef unsigned int uint;
這?取地址我們看到是?樣的
?🔪🔪🔪?引??旦引??個實體,再不能引?其他實體
int main() {int a = 0;int d = 1;int& b = a;int& b = d;return 0; }
三 ,引用的使用
🥤引用傳參->減少拷貝提高效率
圖一中形參是實參的拷貝有4*1000=4000個字節
🌸圖二與圖三都是引用傳參的方法
辨析圖二為傳指針占4/8個字節,而圖三是別名不另開空間
🥤引用作返回值->改變引用對象的同時改變被引用對象
在我們在實現棧中有top取棧頂元素
???? ? ?? 中間商? ?? ? ?? ? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ??? ?? ??? 無中間商??? ?? ??
取棧頂元素中為什么會? 吶
因為c/c++規定返回值存在臨時寄存器(中間商)中,所以被引用對象改變時已經銷毀了產生野指針(進棧創建出棧就銷毀)通俗->(進函數創建出函數就銷毀)-
那傳引用為什么?那
返回的棧是在堆中建立的所以沒有了野指針且返回的是別名無臨時變量(中間商)
引用傳參
在函數調用時,若采用值傳遞,會把實參的值復制一份給形參,這在處理大型對象(如大型結構體、類對象)時,會帶來較大的性能開銷。而引用傳參則是將實參的引用傳遞給形參,不會進行對象的拷貝,從而提高效率。此外,由于引用是對象的別名,對引用形參的修改會直接影響到實參
#include <iostream>
#include <string>// 引用傳參
void modifyString(std::string& str) {str += " - Modified";
}// 值傳參
void modifyStringByValue(std::string str) {str += " - Modified by value";
}int main() {std::string original = "Hello";std::cout << "Before modification: " << original << std::endl;// 調用引用傳參的函數modifyString(original);std::cout << "After modification by reference: " << original << std::endl;std::string another = "World";std::cout << "\nBefore value modification: " << another << std::endl;// 調用值傳參的函數modifyStringByValue(another);std::cout << "After value modification: " << another << std::endl;return 0;
}
代碼解釋:
modifyString
?函數使用引用傳參,直接對傳入的字符串對象進行修改,調用該函數后,original
?字符串會被改變。
modifyStringByValue
?函數使用值傳參,會創建傳入字符串的一個副本,對副本的修改不會影響到原始字符串。
引用做返回值
1. 返回全局變量的引用
#include <iostream>// 全局變量
int globalValue = 10;// 返回全局變量的引用
int& getGlobalValue() {return globalValue;
}int main() {// 獲取全局變量的引用int& ref = getGlobalValue();std::cout << "Original value: " << ref << std::endl;// 通過引用修改全局變量的值ref = 20;std::cout << "Modified value: " << globalValue << std::endl;return 0;
}
globalValue
?是一個全局變量,其生命周期貫穿整個程序。
getGlobalValue
?函數返回?globalValue
?的引用,在?main
?函數中通過引用?ref
?可以直接訪問和修改?globalValue
?的值。
2. 返回類成員變量的引用
#include <iostream>class MyClass {
private:int memberValue;
public:MyClass(int value) : memberValue(value) {}// 返回類成員變量的引用int& getMemberValue() {return memberValue;}
};int main() {MyClass obj(30);// 獲取類成員變量的引用int& ref = obj.getMemberValue();std::cout << "Original member value: " << ref << std::endl;// 通過引用修改類成員變量的值ref = 40;std::cout << "Modified member value: " << obj.getMemberValue() << std::endl;return 0;
}
MyClass
?類包含一個私有成員變量?memberValue
。
getMemberValue
?函數返回?memberValue
?的引用,在?main
?函數中通過引用?ref
?可以直接訪問和修改?memberValue
?的值。
?返回數組元素的引用
#include <iostream>// 函數返回數組元素的引用
int& getArrayElement(int arr[], int index) {return arr[index];
}int main() {int myArray[5] = {1, 2, 3, 4, 5};// 獲取數組元素的引用int& ref = getArrayElement(myArray, 2);std::cout << "Original array element: " << ref << std::endl;// 通過引用修改數組元素的值ref = 10;std::cout << "Modified array element: " << myArray[2] << std::endl;return 0;
}
代碼解釋:
getArrayElement
?函數接受一個數組和一個索引作為參數,返回數組中指定索引位置元素的引用。在?
main
?函數中通過引用?ref
?可以直接訪問和修改數組元素的值。
注意事項
-
避免返回局部變量的引用:局部變量在函數結束時會被銷毀,返回其引用會導致未定義行為。例如:
// 錯誤示例:返回局部變量的引用 int& getLocalValue() {int localVar = 5;return localVar; // 錯誤:返回局部變量的引用 }
- 使用?
const
?引用:如果不希望通過返回的引用修改對象的值,可以返回?const
?引用。例如
const int& getReadOnlyValue(const int& value) {return value;
}
const引?
可以引??個const對象,但是必須?const引?。const引?也可以引?普通對象,因為對象的訪 問權限在引?過程中可以縮?,但是不能放?。
所謂臨時對象就是編譯器需要?個空間暫存表達式的求值結果時臨時創建的?個未命名的對象, C++中把這個未命名對象叫做臨時對象。
?
在類型轉換中會產?臨時對 象存儲中間值,也就是時,b和c引?的都是臨時對象,?C++規定臨時對象具有常性,所以這? 就觸發了權限放?,必須要?常引?才可以。
解析例子
?
指針和引?的關系
C++中指針和引?就像兩個性格迥異的親兄弟,指針是哥哥,引?是弟弟,在實踐中他們相輔相成,功能有重疊性,但是各有??的特點,互相不可替代。
? 語法概念上引?是?個變量的取別名不開空間,指針是存儲?個變量地址,要開空間。
? 引?在定義時必須初始化,指針建議初始化,但是語法上不是必須的。
? 引?在初始化時引??個對象后,就不能再引?其他對象;?指針可以在不斷地改變指向對象。 ? 引?可以直接訪問指向對象,指針需要解引?才是訪問指向對象。
? sizeof中含義不同,引?結果為引?類型的??,但指針始終是地址空間所占字節個數(32位平臺下 占4個字節,64位下是8byte)
? 指針很容易出現空指針和野指針的問題,引?很少出現,引?使?起來相對更安全?些。 ?
拓展?
💩(辨析)#define與typedef與引用的不同
#define N 10;
定義宏(#define)的常亮,N替換成宏用符號代替常量 好處:是預處理階段將N換成10.
typedef unsinged int uint;
typedef :給類型取別名
🪡引用的底層邏輯?
??
這是對引用和指針的匯編代碼
發現劃橫線的地方相同---->>>引用的底層是指針?
nullptr
?了解nullptr之前我們要知道
- ?c++為什么不用NULL反而用nullptr。
- ?nullptr它與NULL的區別是什么?
NULL?
是?個宏
🍦🍦🍦在c++中被定義為從常量0,而C語言中被定義為無類型指針(void*)常量
會產生兩種問題
#include<iostream> using namespace std; void f(int x) { cout << "f(int x)" << endl; } void f(int* ptr) { cout << "f(int* ptr)" << endl; } int main() { f(0); f((int*)NULL); // f((void*)NULL); f(nullptr); return 0; }
?
?🍞問題一
f(NULL);
本想通過f(NULL)調?指針版本的f(int*)函數,但是由于NULL被定義成0,調?了f(int x),因此與程序的初衷相悖
🍞🍞?問題二
f((int*)NULL); f((char*)NULL);
我們在調用空值指針返回空🈳的時候要強轉不同類型。
有沒有能直接調用的呢。
nullptr
🌸🌸ta?個特殊的關鍵字(值為零)
🌸ta可以轉換成任意其他類型的指針類型<--相當于->f((類型*)NULL);
#include<iostream> using namespace std; void f(char* x) {cout << "f(char* x)" << endl; } void f(int* ptr) {cout << "f(int* ptr)" << endl; } int main() {f(nullptr);return 0; }
?
一定要想好寫的是什么在調用。
如果不確定就強制類型轉換
f((int* )nullptr);f((char*)nullptr);
內聯函數的概念
?以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,內聯函數的使用可以提升程序的運行效率。
?我們可以通過觀察調用普通函數和內聯函數的匯編代碼來進一步查看其優勢:
int Add(int a, int b)
{return a + b;
}
int main()
{int ret = Add(1, 2);return 0;
}
下圖左是以上代碼的匯編代碼,下圖右是函數Add加上inline后的匯編代碼:
?
從匯編代碼中可以看出,內聯函數調用時并沒有調用函數這個過程的匯編指令。?
內聯函數的特性
?1、inline是一種以空間換時間的做法,省了去調用函數的額外開銷。由于內聯函數會在調用的位置展開,所以代碼很長或者有遞歸的函數不適宜作為內聯函數。頻繁調用的小函數建議定義成內聯函數。
?2、inline對于編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函數體內有遞歸等,編譯器優化時會忽略掉內聯。
?3、inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了鏈接就會找不到。
?auto?
auto的使用細則
一、auto與指針和引用結合起來使用
?用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時必須加&。
#include <iostream>
using namespace std;
int main()
{int a = 10;auto b = &a; //自動推導出b的類型為int*auto* c = &a; //自動推導出c的類型為int*auto& d = a; //自動推導出d的類型為int//打印變量b,c,d的類型cout << typeid(b).name() << endl;//打印結果為int*cout << typeid(c).name() << endl;//打印結果為int*cout << typeid(d).name() << endl;//打印結果為intreturn 0;
}
注意:用auto聲明引用時必須加&,否則創建的只是與實體類型相同的普通變量。
二、在同一行定義多個變量
?當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
int main()
{auto a = 1, b = 2; //正確auto c = 3, d = 4.0; //編譯器報錯:“auto”必須始終推導為同一類型return 0;
}
auto不能推導的場景
一、auto不能作為函數的參數
?以下代碼編譯失敗,auto不能作為形參類型,因為編譯器無法對x的實際類型進行推導。
int main()
簡化代碼,替代寫起來長的類型
?
auto自動判斷類型或別名
?
2.也可用于返回值與函數返回類型(不建議多用)
for流
概念
?
?