寫在前面
本篇筆記作為C++的開篇筆記,主要是講解C++關鍵字(C++98)連帶一點點(C++11)的知識。掌握的C++新語法新特性,當然C++是兼容C的,我們學習C的那套在C++中也是受用。
ps:點我跳轉下集
文章目錄
- 寫在前面
- 一、命名空間域
- 1.1、命名空間域的定義與使用
- 1.2、命名空間域的細節
- 二、 C++的輸入/輸出
- 2.2、關于std命名空間的使用慣例
- 三、缺省參數
- 四、函數重載
- 4.1、深入了解C++的重載機制
- 五、C++98關鍵字
一、命名空間域
在C/C++中,變量、函數和后面要學到的類都是大量存在的,這些變量、函數和類的名稱將都存在于全局作用域中,可能會導致很多沖突。使用命名空間的目的是對標識符的名稱進行本地化,以避免命名沖突或名字污染,
namespace
關鍵字的出現就是針對這種問題的。
我們在全局中定義一個變量rand
,但是rand函數在stdllib.h
庫中已經定義,根據我們學習過的程序的程序編譯與鏈接筆記提到過,在連接中出現相同的變量符號表合并時會報錯。
為了避免這種情況C++推出了新的關鍵字namespace
,命名空間域。這樣在全局中,我們就保護了我們自己定義的rand
變量。
1.1、命名空間域的定義與使用
定義命名空間,需要使用到 namespace
關鍵字,后面跟命名空間的名字,然后接一對{}
即可,{}
中即為命名空間的成員。
這時候我們運行程序,發現打印結果并不是我們自己創建的全局變量rand
,如下圖
這是因為被命名空間域保護起來的變量外界不能直接訪問。
命名空間的使用有三種方式:
- 加命名空間名稱及作用域限定符( :: )
namespace Bucai {int rand = 10;int k = 20;
}
int main()
{printf("%d\n", Bucai::k);return 0;
}
- 使用using將命名空間中某個成員引入
namespace Bucai {int rand = 10;int k = 20;
}
using Bucai::k;
int main()
{printf("%d\n", k);return 0;
}
- 使用
using namespace
命名空間名稱 引入。
這個效果是暴露命名空間域的內容,讓外部可以直接訪問。但是這和直接在全局中定義變量的效果是不一樣的,因為在命名空間域中會有標識,在編譯連接中形成的符號表不會與在全局變量中定義的吻合。
#include <stdio.h>
#include <stdlib.h>namespace Bucai {int rand = 10;int k = 20;
}using namespace Bucai;int main()
{printf("%d\n", k);return 0;
}
但是需要注意的是,如果在上面代碼中,我們直接使用rand
會,程序會報錯,如下圖
報出錯誤是rand
不明確符號,不再是之前的重定義,所以我們使用using namespace
命名空間名稱引入,需要留意直接引入后的結果。
1.2、命名空間域的細節
命名空間域也是域,它與作用域的細節是相似的,代碼塊就是作用域的一種表現形式,我們使用代碼塊來理解命名空間域會更好,在代碼塊中,我們定義的變量等與外界是互不干涉的,而且在代碼塊中我們使用對應的變量采用的是就近原則,而且在代碼塊中可以嵌套代碼塊,在代碼塊外面訪問不了代碼塊的內容,因為在代碼塊中的內容出了代碼塊作用域就結束。
命名空間域我們可以理解為一個有名稱的代碼塊。必須定義在全局中的"代碼塊",在域中可以隨意的定義變量,這樣外界不會與域中的變量命有沖突,當我們想要使用域中變量時,可以通過域名+作用域限定符來完成引用。
命名空間域細節:
- 命名空間中可以定義變量/函數/自定義類型/類
namespace Bucai {int rand = 10;int k = 20;int Add(int left, int right){return left + right;}class MyName {public:int age = 18;};
}
- 命名空間可以嵌套
namespace Bucai {int k = 0;namespace bbbb {int age = 18;}
}
- 同一個工程中允許存在多個相同名稱的命名空間,編譯器最后會合成同一個命名空間中。
二、 C++的輸入/輸出
C++兼容C,自然是支持C的標準輸入輸出的,但是C的標準輸入輸出有點麻煩,每次都需要程序猿手動標識這個變量是上面類型,需要使用%
什么來進行輸出,很麻煩,所以C++推出了一個全新玩法。
#include<iostream>
// std是C++標準庫的命名空間名,C++將標準庫的定義實現都放到這個命名空間中
using namespace std;//在平時練習中我們可以直接展開std命名空間域
int main()
{cout << "Hello world" << endl;return 0;
}
說明:
- 使用cout標準輸出對象(控制臺)和cin標準輸入對象(鍵盤)時,必須包含
<iostream>
頭文件以及按命名空間使用方法使用std
。 cout
和cin
是全局的流對象,endl
是特殊的C++符號,表示換行輸出(即C中的\n
),他們都包含在包含<iostream>
頭文件中。<<
是流插入運算符,>>
是流提取運算符。- 使用C++輸入輸出更方便,不需要像
printf/scanf
輸入輸出時那樣,需要手動控制格式。C++ 的輸入輸出可以自動識別變量類型。 - 實際上
cout
和cin
分別是ostream
和istream
類型的對象,>>
和<<
也涉及運算符重載等知識。后面不才專門寫一篇筆記來講解IO流用法及原理。 - 在C++中為了兼容C語言,在C++ 使用流輸入輸出時需要檢查C語言的輸入輸出,從原理的角度說,C++ 的輸入輸出效率是比C語言的輸入輸出要低的。
注意: 早期標準庫將所有功能在全局域中實現,聲明在.h后綴的頭文件中,使用時只需包含對應頭文件即可,后來將其實現在std命名空間下,為了和C頭文件區分,也為了正確使用命名空間,規定C++頭文件不帶.h;舊編譯器(vc 6.0)中還支持<iostream.h>格式,后續編譯器已不支持,因此推薦使用<iostream>
+std
的方式來使用。
#include <iostream>
using namespace std;
int main()
{int a;double b;char c;// 可以自動識別變量的類型cin >> a;cin >> b >> c;cout << endl << a << endl;cout << b << " " << c<< " " << 12.888 << endl;return 0;
}
測試運行結果:
2.2、關于std命名空間的使用慣例
std是C++標準庫的命名空間
- 在日常練習中,建議直接
using namespace std
即可,這樣就很方便。 - 在項目開發中,
using namespace std
展開,標準庫就全部暴露出來了,但是項目開發中代碼較多、規模大,就很容易出現沖突問題。所以建議在項目開發中使用,像std::cout
這樣使用是指定命名空間 + using std::cout
展開常用的庫對象/類型等方式
三、缺省參數
缺省參數是聲明或定義函數時為函數的參數指定一個缺省值。在調用該函數時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參。
void Func(int a = 20)
{cout << a << endl;
}
int main()
{Func(); // 沒有傳參時,使用參數的默認值Func(10); // 傳參時,使用指定的實參return 0;
}
測試運行結果:
在上結果圖中,我們也可以看出在函數定義中,我們設計了一個形參,但是形參給了一個缺省值20
,在我們調用函數時,我們沒有給形參a
傳遞實參時,a
就使用缺省值,所以打印20
,在我們有傳遞實參時,形參就接收實參參數,缺省值就失效了,所以打印10
。
缺省參數類型:
- 全缺省參數
void Func(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}
- 半缺省參數
- 半缺省參數必須從右往左依次來給出,不能間隔著給
- 缺省參數不能在函數聲明和定義中同時出現,如果函數聲明和定義分開,缺省參數需要在聲明中指定。
- 缺省值必須是常量或者全局變量
- C語言不支持(編譯器不支持)
void Func(int a, int b = 10, int c = 20)//從右往左依次指定缺省值
{cout<<"a = "<<a<<endl;cout<<"b = "<<b<<endl;cout<<"c = "<<c<<endl;
}
傳參時編譯器讀取實參是從左往右讀取的,如果不是從右往左指定缺省值,那么在實參傳遞時,會出現程序猿意想之外的錯誤,所以編譯器會檢查缺省值的給定,若出現缺省值的指定不是從右往左,則報錯,如下圖。
在函數聲明與定義分開的工程中,如果我們把缺省值放在定義中會出現報錯:如下程序
//test.h
void Func(int a, int b, int c);//tect.c
#include "test.h"
void Func(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;
}//main.c
#include "test.h"
int main() {Func();return 0;
}
測試結果:
在程序編譯與鏈接筆記中,我們已經知道#include
引用的頭文件,最后都是拷貝頭文件內容到當前文件下的。
在test.h
頭文件中,我們只聲明了沒有缺省值的Func
函數,在編譯階段拷貝到工程中,就不是有缺省值的函數,所以報錯。
四、函數重載
自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載了。
比如:以前有一個笑話,國有兩個體育項目大家根本不用看,也不用擔心。一個是乒乓球,一個是男足。前者是“誰也贏不了!”,后者是“誰也贏不了!”。
誰也贏不了雖然是一樣的字,但是意思完全不一樣,這就形成了重載,同理,C++中也做出了相似的函數重載。
函數重載: 是函數的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數,要求這些同名函數的形參列表 (參數個數 或 類型 或 類型順序) 不同,常用來處理實現功能類似數據類型不同的問題。
我們設計一個交換函數,用來交換變量值:
void Swap(int* a, int* b) { int num = *a;*a = *b;*b = num;
}
void Swap(double *a, double *b) {double num = *a;*a = *b;*b = num;
}int main() {int a = 10, b = 20;Swap(&a, &b);cout << "a = " << a << " b = " << b << endl;double c = 10.12, d = 20.12;Swap(&c, &d);cout << "c = " << c << " d = " << d << endl;return 0;
}
運行結果:
這樣我們就完成了不同類型的變量交換,Swap
函數形成了重載,這時C++編譯器特有的屬性
構成重載的三大要素:
- 形參個數不同
- 形參類型順序不同
- 形參類型不同
注意:函數的返回值不同是不構成函數重載的!!
4.1、深入了解C++的重載機制
在深入了解之前,我們先認識一下C語言為什么不支持重載,但在此之前我們需要清楚C/C++的程序編譯與鏈接,因為重載機制的核心是發生在編譯階段完成的。
在C語言中,我們根據不才寫的程序編譯與鏈接筆記可以知道,在程序在經過編譯后我們的函數符號名是不會有改變的。我們以下程序為例:
#include <stdio.h>void Swap(int* a, int* b) { int num = *a;*a = *b;*b = num;
}int main(){int a = 10;int b = 20;Swap( &a,&b);printf("%d \n",a);return 0;
}
我們在Linux環境下查看上面C語言生成的符號表(如下圖)
在上圖中,可以清晰看出在C語言中,函數符號名有且只有一個,這樣就導致了C語言的編譯器不支持重載,而C++則推出了全新玩法:把編譯后的函數符號名更改為另一種形式函數符號名,讓其實現函數的重載。
在Windows環境下,函數命名太過復雜,不才這里使用 g++
編譯器。
我們以上面代碼為例:
#include <stdio.h>void Swap(int* a, int* b) { int num = *a;*a = *b;*b = num;
}int main(){int a = 10;int b = 20;Swap( &a,&b);printf("%d \n",a);return 0;
}
我們在Linux環境下,查看由g++
編譯器編譯后所形成的符號表,查看函數符號名的變化,如下圖。
此時C++中的函數,已經不再是單純的Swap
,而是在Swap
前后增加了新東西。那一前一后的東西需要查看對應編譯器的命名規則,如下。
C++的函數符號名命名規則:
- 每個編譯器都有自己的函數名修飾規則
- g++編譯器中的函數修飾后變成【_Z+函數長度+函數名+類型首字母】
- Windows下名字修飾規則
根據命名規則我們可以得出交換函數Swap
在C++形成_Z4SwapPiS_
代表著:
_Z
:固定開頭4
:代表著函數名字的長度,Swap
長度4個字符,所以是4
Swap
:代表了函數名Pi
:代表了int*
類型的首字母合體,P
代表是指針,i
代表是整形。
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;
}
void f(int a, double b, char c) {cout << "f(int a, double b, char c)" << endl;}
int main()
{f(10, 'a');f('a', 10);f(10, 2.5, 'a');return 0;
}
我們使用g++編譯器,生成符號表查看上面函數f
的符號名,如下圖
第一個函數f
:后面的類型是i
與c
,對應形參中的int
與char
;
第二個函數f
:后面的類型是c
與i
,對應形參中的char
與int
;
第三個函數f
:后面的類型是i
、d
與c
,對應形參中的int
、double
與char
;
通過這里就理解了C語言沒辦法支持重載,因為同名函數沒辦法區分。而C++是通過函數修飾規則來區分,只要參數不同,修飾出來的名字就不一樣,就支持了重載。
有此,我們可以總結出,函數的重載只會與形參列表 中的 參數個數 或 類型 或 類型順序相關,因為只有這些才控制著函數符號名。當函數的符號名相同時,編譯器在鏈接時候也是會區分不開,所以必須保證函數符號名唯一。
五、C++98關鍵字
C++總計63個關鍵字(其中包含C語言32個關鍵字),如下表格
asm | do | if | return | try | continue |
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
ps:點我跳轉下集
以上就是本章所有內容。若有勘誤請私信不才。萬分感激💖💖 如果對大家有用的話,就請多多為我點贊收藏吧~~~💖💖
ps:表情包來自網絡,侵刪🌹