三點睡六點起,閻王夸我好身體
不到三點我不睡,太平間里搶C位
目錄:
一、命名空間
1.命名空間的作用
2.命名空間定義
3.命名空間使用
二、C++的輸入輸出
1.輸入輸出說明介紹
2.std命名空間的使用慣例
三、缺省參數
1.缺省參數概念
2.缺省參數分類
四、函數重載
1.函數重載概念
五、引用
1.引用概念
2.引用特性
3.常引用
4.使用場景
5.傳值、傳引用、傳指針效率比較
6.引用和指針的區(qū)別
六、內聯函數
1.概念
2.特性
七、auto關鍵字(C++11)
1.概念
2.auto的使用規(guī)則
3.auto不能推導的場景
八、基于范圍的fou循環(huán)
1.范圍for的語法
2.范圍for的使用條件
九、指針空值——nullptr
1.C++98中的指針空值
2.nullptr的概念及使用
十、完結撒?
前言:
本片博客是以學習過C語言之后進入C++學習為前提,講解C++入門級知識,為C++之后的學習做鋪墊
–?–?–?–?–?–?–?–?–?–?–?–?–?–?–?-正文開始-?–?–?–?–?–?–?–?–?–?–?–?–?–?–?–
一、命名空間
1.命名空間的作用
在C/C++中,變量、函數和后面要學到的類都是大量存在的,這些變量、函數和類的名稱將都存在于全局作用域中,可能會導致很多沖突。使用命名空間的目的是對標識符的名稱進行本地化,以避免命名沖突或名字污染namespace關鍵字的出現就是針對這種問題的。
在編譯器運行時查找標識符遵循局部有先原則:從主函數開始從下往上,先查找局部域,再查找全局域(頭文件也包含在全部域當中)
舉例:
比如下面一段代碼:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C語言沒辦法解決類似這樣的命名沖突問題,所以C++提出了namespace來解決
int main()
{
printf("%d\n", rand);
return 0;
}
// 編譯后后報錯:error C2365: “rand”: 重定義;以前的定義是“函數”
眾所周知頭文件stdlib.h中包含有一個rand函數,而我們又在全局變量中定義一個rand變量,這就會引起命名沖突,而如果在C語言中我們不可能對stdlib.h中的rand函數進行重命名,所以我們只有對所起的變量名字進行重命名。
因為在以后工作中我們對一些函數或者變量得命名是要有一定意義的,不可以隨便命名,所以有時候即使發(fā)生了命名沖突我們也不想重命名。
而在C++中我們可以使用命名空間來解決這個問題,使得兩者都不必重新命名就可以進行區(qū)分
2.命名空間定義
定義命名空間,需要使用到namespace關鍵字,后面跟命名空間的名字,然后**接一對{}**即可,{}中即為命名空間的成員。
比如:
#include <stdio.h>
#include <stdlib.h>
namespace zdy//我的網名簡寫而已,一般開發(fā)中是用項目名字做命名空間名。
{
rand = 10;
}
int main()
{
printf("%d\n",rand);
return 0;
}
注意:一個命名空間就定義了一個新的作用域,命名空間中的所有內容都局限于該命名空間中
這樣上面程序主函數中rand編譯器最后編譯就會認為是頭文件stdlib.h中得rand函數,所以打印出來的值會很大。
命名空間中變量/函數/類型都是可以定義的,這里就不過多為大家進行舉例。
3.命名空間使用
命名空間中成員該如何使用呢?
拿上面例子來說我們要怎樣使用命名空間zdy中的rand變量呢?
這里我們來介紹一下 ::作用域限定符
#include <stdio.h>
#include <stdlib.h>//頭文件stdlib中也有rand是一個函數
namespace zdy
{
int rand = 10;
}
int main()
{
printf("%d\n",zdy::rand);
return 0;
}
可以看到在printf里面rand之前用了一個::作用域限定符,其作用就是限定所指向的作用域
上面代碼中zdy::rand表示限定在zdy作用域中指向rand變量。
當::作用域限定符前面為空時,默認還是代表全局域
這里我們再來介紹一個關鍵字 using
using的作用是可以將命名空間或是命名空間中的成員引入到全局域中
比如:
#include <stdio.h>
#include <stdlib.h>
using zdy::rand;//1
//using namespace zdy;//2
namespace zdy
{
int rand = 10;
}
int main()
{
printf("%d\n", rand);
return 0;
}
上面代碼中using1代表只展開了zdy命名空間中的rand變量
using2代表把命名空間zdy進行了展開,里面所有的成員都會引入到全局域當中(雖然這里我們只有rand一個變量??)
這時就又會出現命名沖突的問題
所以我們以下面代碼為例:
namespace bit
{
// 命名空間中可以定義變量/函數/類型
int a = 0;
int b = 1;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
// 編譯報錯:error C2065: “a”: 未聲明的標識符
printf("%d\n", a);
return 0;
}
那么根據上面代碼使用命名空間中的a變量的方式有三種:
1) 加命名空間名稱及作用域限定符
int main()
{
printf("%d\n", N::a);
return 0;
}
2) 使用using將命名空間中某個成員引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
3) 使用using namespace 命名空間名稱 引入
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
當然第3種方法這里對命名空間中的Add函數直接使用也是可以的。
二、C++的輸入輸出
1.輸入輸出說明介紹
輸出:
#include<iostream>
// std是C++標準庫的命名空間名,C++將標準庫的定義實現都放到這個命名空間中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
輸入:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
cin >> i;
cout << i<<endl;
return 0;
}
說明:
1) 使用cout標準輸出對象(控制臺)和cin標準輸入對象(鍵盤)時,必須包含< iostream >頭文件以及按命名空間使用方法使用std。
2) cout和cin是全局的流對象,endl是特殊的C++符號,表示換行輸出,他們都包含在包含< iostream >頭文件中。
3) <<是流插入運算符,>>是流提取運算符。
4) 使用C++輸入輸出更方便,不需要像printf/scanf輸入輸出時那樣,需要手動控制格式。C++的輸入輸出可以自動識別變量類型。
5) 實際上cout和cin分別是ostream和istream類型的對象,>>和<<也涉及運算符重載等知識,這些知識我們我們后續(xù)才會學習,所以我們這里只是簡單學習他們的使用。
所以cout,cin,endl等都是包含在頭文件iostream中的std命名空間當中,所以我們在使用它們的時候需要先將std命名空間全部進行展開 using namespace std,或者在工作中我們?yōu)榱吮苊馊空归_發(fā)生不利影響,通常是使用那個就將那個引用到全局變量中使用,比如:using std::cout。
注意:早期標準庫將所有功能在全局域中實現,聲明在.h后綴的頭文件中,使用時只需包含對應頭文件即可,后來將其實現在std命名空間下,為了和C頭文件區(qū)分,也為了正確使用命名空間,規(guī)定C++頭文件不帶.h;舊編譯器(vc 6.0)中還支持<iostream.h>格式,后續(xù)編譯器已不支持,因此推薦使用+std的方式。
關于cout和cin還有很多更復雜的用法,比如控制浮點數輸出精度,控制整形輸出進制格式等等。因為C++兼容C語言的用法,這些又用得不是很多,我們這里就不展開學習了。后續(xù)如果有需要我會再出博客進行講解。
2.std命名空間的使用慣例
std是C++標準庫的命名空間,如何展開std使用更合理呢?
1) 在日常練習中,建議直接using namespace std即可,這樣就很方便。
2) using namespace std展開,標準庫就全部暴露出來了,如果我們定義跟庫重名的類型/對象/函數,就存在沖突問題。該問題在日常練習中很少出現,但是項目開發(fā)中代碼較多、規(guī)模大,就很容易出現。所以建議在項目開發(fā)中使用,像std::cout這樣使用時指定命名空間 + using std::cout展開常用的庫對象/類型等方式。
三、缺省參數
1.缺省參數概念
缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參。
void Func(int a = 0)
{
cout<<a<<endl;
}
int main()
{
Func(); // 沒有傳參時,使用參數的默認值
Func(10); // 傳參時,使用指定的實參
return 0;
}
2.缺省參數分類
1) 全缺省參數
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
2) 半缺省參數
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意:
1. 半缺省參數必須從右往左依次來給出,不能間隔著給
2. 缺省參數不能在函數聲明和定義中同時出現
//a.h
void Func(int a = 10);
// a.cpp
void Func(int a = 20)
{}
// 注意:如果聲明與定義位置同時出現,恰巧兩個位置提供的值不同,那編譯器就無法確定到底該用那個缺省值。
3. 缺省值必須是常量或者全局變量
4. C語言不支持(編譯器不支持)
四、函數重載
自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載了。
比如:以前有一個笑話,國有兩個體育項目大家根本不用看,也不用擔心。一個是乒乓球,一個是男足。前者是“誰也贏不了!”,后者是“誰也贏不不了!”
1.函數重載概念
函數重載:是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,這些同名函數的形參列表(參數個數 或 類型 或 類型順序)不同,常用來處理實現功能類似數據類型不同的問題。
見下代碼:
#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;
}
五、引用
1.引用概念
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間。
比如:李逵,在家稱為"鐵牛",江湖上人稱"黑旋風",但都指同一個人。
類型& 引用變量名(對象名) = 引用實體;
void TestRef()
{
int a = 10;
int& ra = a;//<====定義引用類型
printf("%p\n", &a);
printf("%p\n", &ra);
}
調用上面函數運行可以看到打印出來的地址是一樣的。
注意:引用類型必須和引用實體是同種類型的
2.引用特性
1) 引用在定義時必須初始化
2) 一個變量可以有多個引用
3) 引用一旦引用一個實體,再不能引用其他實體
void TestRef()
{
int a = 10;
int b = 20;
// int& ra; // 該條語句編譯時會出錯
int& ra = a;
int& rra = a;
//int& rra = b; //運行報錯
printf("%p %p %p\n", &a, &ra, &rra);
}
3.常引用
我們通過下面幾個報錯例子進行學習
1)
const int a = 10;
//int& ra = a; // 該語句編譯時會出錯,a為常量
const int& ra = a;
這里ra不能引用變量a,因為變量a用const修飾,權限只能讀,而使用int& ra引用,ra權限是可讀可寫,權限被放大,不可以。
2)
// int& b = 10; // 該語句編譯時會出錯,b為常量
const int& b = 10;
這個原理和1)一樣,10為常量,只可讀,而被b引用后權限被放大,所以要用const進行修飾。
3)
double d = 12.34;
//int& rd = d; // 該語句編譯時會出錯,類型不同
const int& rd = d;
引用變量類型不同不可以進行引用,但是加上const表示引用變量名不可以被改變(只讀),那么會發(fā)生隱式類型轉換存儲到rd中。
4.使用場景
1)做參數
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
2)做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
大家思考一下下面代碼輸出結果是什么,為什么
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;
return 0;
}
講解看下圖:注意:如果函數返回時,出了函數作用域,如果返回對象還在(還沒還給系統(tǒng)),則可以使用引用返回,如果已經還給系統(tǒng)了,則必須使用傳值返回。
5.傳值、傳引用、傳指針效率比較
以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低。
所以傳值和指針與傳引用在作為傳參以及返回值類型上效率相差是很大的。
6.引用和指針的區(qū)別
在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間。
在底層實現上實際是有空間的,因為引用是按照指針方式來實現的
舉例
比如下面代碼:
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
我們來看下引用和指針的匯編代碼對比:
可以看到引用和指針在匯編代碼上是一摸一樣的。
引用和指針的不同點:
1)引用概念上定義一個變量的別名,指針存儲一個變量地址。
2)引用在定義時必須初始化,指針沒有要求
3)引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
4)沒有NULL引用,但有NULL指針
5)在sizeof中含義不同:引用結果為引用類型的大小,但指針始終是地址空間所占字節(jié)個數(32位平臺下占4個字節(jié))
6)引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
7)有多級指針,但是沒有多級引用
8)訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
9)引用比指針使用起來相對更安全
六、內聯函數
1.概念
以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數調用建立棧幀的開銷,內聯函數提升程序運行的效率。
下圖是沒有使用內聯函數,匯編代碼中的call表示調用了Add函數,沒有對Add函數進行展開
如果在上述函數前增加inline關鍵字將其改成內聯函數,在編譯期間編譯器會用函數體替換函數的調用。
查看方式:
1)在release模式下,查看編譯器生成的匯編代碼中是否存在call Add
2)在debug模式下,需要對編譯器進行設置,否則不會展開(因為debug模式下,編譯器默認不會對代碼進行優(yōu)化以便更好地調試,以下給出vs2022的設置方式)
右鍵點擊文件后點擊屬性
被inline修飾后的函數匯編代碼中將不會出現call指令,調用函數將直接進行展開
2.特性
1)inline是一種以空間換時間的做法,如果編譯器將函數當成內聯函數處理,在編譯階段,會用函數體替換函數調用,缺陷:可能會使目標文件變大,優(yōu)勢:少了調用開銷,提高程序運行效率。
2)inline對于編譯器而言只是一個建議,不同編譯器關于inline實現機制可能不同,一般建議:將函數規(guī)模較小(即函數不是很長,具體沒有準確的說法,取決于編譯器內部實現)、不是遞歸、且頻繁調用的函數采用inline修飾,否則編譯器會忽略inline特性。
下圖為《C++prime》第五版關于inline的建議:
3)inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了,鏈接就會找不到。
七、auto關鍵字(C++11)
1.概念
auto是用來修飾變量的
auto作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得。
也就是說使用auto來聲明變量不用表示其變量類型,變量的類型由編譯器推到而得,任何變量類型都可以用用auto進行聲明。
大家可以運行下面代碼進行驗證
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 無法通過編譯,使用auto定義變量時必須對其進行初始化
return 0;
}
typeid().name可以返回變量類型
注意:
使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化表達式來推導auto的實際類型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為變量實際的類型。
2.auto的使用規(guī)則
1)auto與指針和引用結合起來使用
用auto聲明指針類型時,用auto和auto*沒有任何區(qū)別,但用auto聲明引用類型時則必須加&
2)在同一行定義多個變量
當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 該行代碼會編譯失敗,因為c和d的初始化表達式類型不同
}
3.auto不能推導的場景
1)auto不能作為函數的參數
2)auto不能直接用來聲明數組
3)為了避免與C++98中的auto發(fā)生混淆,C++11只保留了auto作為類型指示符的用法
4)auto在實際中最常見的優(yōu)勢用法就是跟以后會講到的C++11提供的新式for循環(huán),還有l(wèi)ambda表達式等進行配合使用。
八、基于范圍的fou循環(huán)(C++11)
1.范圍for的語法
在C++98中如果要遍歷一個數組,可以按照以下方式進行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
對于一個有范圍的集合而言,由程序員來說明循環(huán)的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環(huán)。for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
上面代碼中第一個for循環(huán)將array數組中的所有元素按照循環(huán)以此賦給e,之后再將e乘2。
第二個for循環(huán)就是將array中的每個值按照循環(huán)以此賦給e并進行打印。
注意:與普通循環(huán)類似,可以用continue來結束本次循環(huán),也可以用break來跳出整個循環(huán)。
2.范圍for的使用條件
1)for循環(huán)迭代的范圍必須是確定的
對于數組而言,就是數組中第一個元素和最后一個元素的范圍;對于類而言,應該提供begin和end的方法,begin和end就是for循環(huán)迭代的范圍。
注意:以下代碼就有問題,因為for的范圍不確定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
2)迭代的對象要實現++和==的操作。(關于迭代器,以后再進行講解,現在提一下,沒辦法講清楚,現在大家了解一下就可以了)
九、指針空值——nullptr
1.C++98中的指針空值
在良好的C/C++編程習慣中,聲明一個變量時最好給該變量一個合適的初始值,否則可能會出現不可預料的錯誤,比如未初始化的指針。如果一個指針沒有合法的指向,我們基本都是按照如下方式對其進行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL實際是一個宏,在傳統(tǒng)的C頭文件(stddef.h)中,可以看到如下代碼:
可以看到,NULL可能被定義為字面常量0,或者被定義為無類型指針(void)的常量*。不論采取何種定義,在使用空值的指針時,都不可避免的會遇到一些麻煩,比如:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通過f(NULL)調用指針版本的f(int*)函數,但是由于NULL被定義成0,因此程序運行對于f函數的調用會出現匹配錯誤。
在C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void*)常量,但是編譯器默認情況下將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void *)0。
2.nullptr的概念及使用
所以為了解決以上問題,在C++11中就引入了nullptr來代替null:
1. 在使用nullptr表示指針空值時,不需要包含頭文件,因為nullptr是C++11作為新關鍵字引入的。
2. 在C++11中,sizeof(nullptr) 與 sizeof((void)0)所占的字節(jié)數相同。*
3. 為了提高代碼的健壯性,在后續(xù)表示指針空值時建議最好使用nullptr。
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
f(nullptr);
return 0;
}
運行上面程序就不會出現函數匹配錯誤。文章來源:http://www.zghlxwxcb.cn/news/detail-847133.html
十、完結撒?
如果以上內容對你有幫助不妨點贊支持一下,以后還會分享更多編程知識,我們一起進步。
最后我想講的是,據說點贊的都能找到漂亮女朋友?文章來源地址http://www.zghlxwxcb.cn/news/detail-847133.html
到了這里,關于計算機語言 之【C++】入門級知識講解(命名空間,C++輸入輸出,缺省參數,函數重載,引用,內斂函數,auto關鍵字,for循環(huán),指針空值nullptr)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!