文章目錄
C++模板篇
本章主要的內容:
- 友元函數、友元類
- 靜態數據成員、靜態成員函數
- 運算符重載
- 模板函數+模板類==》標準模板類
- 標準模板庫中向量Vector、鏈表list、映射map的使用
**注意:**關於友元,友元只是封裝的補充,會破壞封裝性,有定向暴露性(在實在沒有辦法的情況下使用友元)
1.友元函數-全局友元函數和友元成員函數
1.全局友元函數的定義和調用:
class Time {
// Time類的全局友元函數
friend void printTime(Time &t);
public:
Time(int hour, int min, int sec) {
m_iHour = hour;
m_iMinute = min;
m_iSecond = sec;
}
private:
m_iHour = hour;
m_iMinute = min;
m_iSecond = sec;
};
// Time類的全局友元函數
void printTime(Time &t){
cout << t.m_iHour << ":"
<< t.m_iMinute << ":"
<< t.m_iSecond << endl;
}
int main() {
Time t(6,34,25);
printTime(t);// 調用全局友元函數
return 0;
}
/*輸出:
6:34:25
*/
2.友元成員函數的定義和調用:
- Match類-Match.h:
//#include "Time.h"
// 先聲明有Time,代表之後會進行實現,但是不能使用include,會導致循環包含
class Time;
class Match {
public:
void printTime(Time &t);
};
2.Match類-Match.cpp:
#include <iostream>
#include "Match.h"
#include "Time.h"// [!]不明白此處爲何可以include,不會造成Match和Time的循環包含嗎?
using namespace std;
void Match::printTime(Time &t) {
cout << t.m_iHour << ":"
<< t.m_iMinute << ":"
<< t.m_iSecond << endl;
}
3.Time類-Time.h:
#include <iostream>
#include "Match.h"
using namespace std;
class Time {
// 在這裏聲明友元函數,在此並不受本類的訪問限定符限定,
// 故寫在public或private下並沒有區別
friend void Match::printTime(Time &t);
public:
Time(int hour,int min,int sec);
private:
int m_iHour;
int m_iMinute;
int m_iSecond;
};
4.Time類-Time.cpp:
#include "Time.h"
Time::Time(int hour, int min, int sec) {
m_iHour = hour;
m_iMinute = min;
m_iSecond = sec;
}
5.main.cpp:
#include <iostream>
#include "Time.c"
int main() {
Time t(6,34,25);
Match m;
m.printTime(t);// 調用友元函數
return 0;
}
/*輸出:
6:34:25
*/
2.友元類
友元類的寫法:
由於編譯器不同,友元類有兩種寫法:1、friend class 類名
; 2、friend 類名
。
友元類的聲明與使用舉例:
#include <iostream>
using namespace std;
// 事先加入Watch聲明,代表當前沒有實現,但是之後會進行實現
class Watch;
/**
* 定義Time類
* 數據成員:m_iHour, m_iMinute,m_iSecond
* 成員函數:構造函數
* 友元類:Watch
*/
class Time{
// 聲明Time類是Match類的友元類,即聲明Match有個友元類是Time
// 所以Match就可以訪問Time中的成員了
friend class Watch;
public:
Time(int hour, int min, int sec){
m_iHour = hour;
m_iMinute = min;
m_iSecond = sec;
}
public:
int m_iHour;
int m_iMinute;
int m_iSecond;
};
/**
* 定義Watch類
* 數據成員:m_tTime
* 成員函數:構造函數
* display用於顯示時間
*/
class Watch{
public:
Watch(Time &timeObj) : m_tTime(timeObj){
}
void display(){
cout << m_tTime.m_iHour << endl;
cout << m_tTime.m_iMinute << endl;
cout << m_tTime.m_iSecond << endl;
}
public:
Time m_tTime;
};
int main(){
Time t(6, 30, 20);
Watch w(t);
w.display();
return 0;
}
3.友元的總結
注意:
- 友元關係不可傳遞
- 友元關係得單向性(A是B的友元,不代表B是A的友元)
- 友元聲明的形式及數量不受限制
- 友元只是封裝的補充,會破壞封裝性,有定向暴露性(在實在沒有辦法的情況下使用友元)
- 友元的聲明不受訪問限定符影響,可以聲明在類中的任何位置。(即用
friend
修飾的聲明,僅限被friend
修飾的這一句話不受)
對於友元關係的記法:
- 友元函數:friend在哪個類出現,則此函數就是這個類的朋友,這個函數就可以對這個類的所有數據成員進行訪問了。(通過傳入的對象的引用來進行訪問)
- 友元類:friend在A類出現,則被
friend
修飾的B類就是這個類(A類)的朋友,所以B類就可以對A類中的所有數據成員進行訪問了(直接通過B類進行調用即可,無需使用引用)。即在A類中出現friend修飾的B,那麼B就是當前類的朋友,可以訪問當前類。
4.靜態變量與靜態函數
靜態成員依託於類,而不是對象,就算類沒有實例化對象,靜態成員也會存在於內存中。
假如有個類A,其中有靜態變量cout,在實例化a,b,c,d之前,cout這個靜態變量就已經存在於內存中 了。
靜態變量:
聲明:static int 變量名;
初始化(方法1):static int 變量名 = xxxx;
(在類中聲明+初始化)
初始化(方法2):int 類名::變量名 = xxxx;
(在類外初始化)
訪問(方法1):類名::變量名
訪問(方法2):對象名.變量名
或指針名->變量名
靜態函數:
初始化(方法1):static int 函數名(){xxxx;}
(在類中初始化)
訪問(方法1):類名::函數名()
訪問(方法2):對象名.函數名()
或指針名->函數名()
int 類名::變量名 = xxxx
注意事項:
- 靜態數據成員必須單獨初始化。(在對象實例化之前就有了,所以不能寫到類的構造函數中去)
- 靜態成員函數不能調用非靜態成員函數和費靜態數據成員。
- 靜態數據成員只有一份,且不依賴對象而存在。
對於上面注意事項的第2點做一下原理補充:
在類中的普通成員函數中調用普通成員變量,實際上爲了辨認出當前使用的變量屬於哪個對象,都會自動添加this指針;如果是靜態變量,因爲不依託於對象,而是依託於類,所以不會自動添加this指針,如下所示:
void FunctionName(){
variable1 = "01"; // 普通成員變量
variable2 = "02"; // 靜態成員變量
}
// 實際上會變成下面的這種形式
void FunctionName(ClassName *this){
this->variable1 = "01"; // 會自動添加this指針
variable2 = "02"; // 不會有this指針
}
在靜態成員函數中,因爲這個函數不依託於對象,所以也不會有代表當前對象的this指針,所以如果使用普通成員變量,會不知道到底是哪個對象的成員變量,所以如果這樣調用會報錯,如下所示:
static int FunctionName(){
// variable1是普通成員變量,variable2是靜態成員變量
variable1 = "01"; // 會報錯!
variable2 = "02"; //
}
但是普通的成員函數中使用靜態的數據成員,則是完全沒有問題。
靜態成員函數和const常量修飾符:
禁止在靜態函數後面加const,因爲const的本質是實際是對隱藏的this指針加const,因爲靜態函數根本不存在this指針,所以會報錯。
小總結:
-
定義靜態成員函數和靜態數據成員都需要static關鍵字。
-
公有靜態成員函數可以被類直接調用。
-
靜態成員函數
既可以訪問非靜態數據成員和靜態數據成員,也可以調用非靜態成員函數和靜態成員函數。靜態成員函數只能訪問靜態數據成員和調用靜態成員函數。 -
靜態數據成員不能在構造函數初始化,必須單獨初始化。
5.一元運算符的重載
**運算符重載:**給原有運算符賦予新的功能。
**運算符重載的本質:**函數重載。
關鍵字:operator
-(負號)的重載,有兩種重載的方法:
- 成員函數重載
- 友元函數重載
1.負號的重載-成員函數重載:
// 聲明
class Coordinate{
public:
Coordinate(int x,int y);
// 成員函數實現負號重載,
// 返回類型是Coordinate&,關鍵字是operator,
// 函數名是-,無參數
Coordinate& operator-();
private:
int m_iX;
int m_iY;
}
// 實現
Coordinate& Coordinate::operator-(){
// 兩種寫法都可以,this指針可加可不加
this->m_iX = -(this->m_iX);
this->m_iY = -(this->m_iY);
//m_iX = -m_iX;
//m_iY = -m_iY;
// 【爲什麼是*this?】
// 這裏我們的返回值是Coordinate對象的引用,這個引用該指向的是一個對象
// 而this指針指向這個對象,所以我們要對this指針取值,從而獲取到對象
// 也就是使用“*this”
return *this;
}
// 調用
int main(){
Coordinate coor1(3,5);
-coor1; // 相當於coor1.operator-();
return 0;
}
2.負號的重載-友元函數重載:
// 聲明
class Coordinate{
// 利用友元函數實現負號重載,
// 友元函數也需要一個像成員函數自帶的this一樣的參數,
// 故這裏表現爲Coordinate對象類型的參數,或引用
// 即“Coordinate c”或“Coordinate &c”
friend Coordinate& operator-(Coordinate& coor);
public:
Coordinate(int x,int y);
private:
int m_iX;
int m_iY;
};
// 實現
// 注意:友元函數不屬於Coordinate類,所以不能寫成Coordinate::XXXX
Coordinate& operator-(){
coor.m_iX = -coor.m_iX;
coor.m_iY = -coor.m_iY;
return *this;
}
// 調用
int main(){
Coordinate coor1(3,5);
-coor1; // 相當於coor1.operator-();coor1這個對象會通過形參被傳入
return 0;
}
++符號(前置)的重載:
前置++:先++,再使用
// 聲明
class Coordinate{
public:
Coordinate(int x,int y);
Coordinate& operator++(); // 前置++,返回當前對象的引用
private:
int m_iX;
int m_iY;
};
// 定義
Coordinate& Coordinate::operator++(){
m_iX++;
m_iY++;
return *this;
}
// 調用
int main(){
Coordinate coor1(3,5);
++coor1; // coor1.operator++();
return 0;
}
++符號(後置)的重載:
後置++:先使用,後++
// 聲明
class Coordinate{
public:
Coordinate(int x,int y);
// 成員函數實現++(後置)重載,後面的int是一個標誌,
// int標誌代表後置重載,沒有爲什麼
// 返回值也不再返回對象引用,而是返回一個對象
Coordinate operator++(int);
private:
int m_iX;
int m_iY;
};
// 實現
// 注意:這裏的int代表後置,是個標誌,不需要進行傳值,之後也不會使用
Coordinate operator++(int){
// 將當前對象保存在臨時對象中,這裏會調用拷貝構造函數,
// 作爲返回,因爲++後置是先使用,再++
Coordinate old(*this);
// 將當前對象中的屬性進行++,如果之後再次調用此對象,就是++之後的值
m_iX++;
m_iY++;
// 返回++之前的值
return old;
}
// 調用
int main(){
Coordinate coor1(3,5);
coor1++; // coor1.operator++(0);系統會默認傳入一個值到標誌中,一般是0
// 這樣驗證看得更清楚,會輸出++之前的m_iX值
cout << (coor1++).m_iX << ",";
cout << (coor1++).m_iY << endl;
return 0;
}
6.二元運算符的重載
+(加號)的重載,也有兩種重載的方法:
- 成員函數重載
- 友元函數重載
對於此例來說,採用友元函數重載能說明得更加清楚。
1.加號的重載-成員函數重載:
// 聲明
class Coordinate{
public:
Coordinate(int x,int y);
// 返回對象作爲結果,參數是一個對象引用常量,
// const是設計上的規範,我們在加的過程中並不希望修改加數本身的值
Coordinate operator+(const Coordinate &coor);
private:
int m_iX;
int m_iY;
}
// 實現
Coordinate operator+(const Coordinate &coor){
// 臨時對象,用來保存相加的結果
Coordinate temp;
temp.m_iX = this->m_iX + coor.m_iX;
temp.m_iY = this->m_iY + coor.m_iY;
// 返回結果(臨時對象)
return temp;
}
// 調用
int main(){
Coordinate coor1(3,5);
Coordinate coor2(4,7);
Coordinate coor3(0,0);
coor3 = coor1 + coor2; // coor1.operator+(coor2);coor2前面還有一個隱藏參數,就是this指針,指向當前對象
return 0;
}
2.加號的重載-友元函數重載:
// 聲明
class Coordinate{
// 加const是設計上的規範,我們在加的過程中並不希望修改加數本身的值
friend Coordinate operator+(const Coordinate &c1,const Coordinate &c2);
public:
Coordinate(int x,int y);
private:
int m_iX;
int m_iY;
}
// 實現
Coordinate operator+(const Coordinate &c1,const Coordinate &c2){
// 臨時對象,用來保存相加的結果
Coordinate temp;
temp.m_iX = c1.m_iX + c2.m_iX;
temp.m_iY = c1.m_iY + c2.m_iY;
return temp;
}
// 調用(和利用成員函數重載的調用一樣,但過程略有不同)
int main(){
Coordinate coor1(3,5);
Coordinate coor2(4,7);
Coordinate coor3(0,0);
coor3 = coor1 + coor2; // operator+(coor1,coor2);
return 0;
}
<<負號的重載:
這個用法也是規定的,和其他的略有不同。
**問題:**可以使用成員函數進行重載嗎?不行,應該使用友元函數進行重載。
因爲重載函數的第一個參數也必須是ostream&,所以不能用成員函數進行重載,因爲成員函數的第一個參數是this指針(隱藏的)。
// 聲明
class Coordinate{
// 利用友元函數進行重載
// 返回值必須是ostream&
// 第一個參數也必須是ostream&
// 之後是要輸出的對象
friend ostream& operator<<(ostream &out,const Coordinate &coor);
public:
Coordinate(int x,int y);
private:
int m_iX;
int m_iY;
}
// 實現
ostream& operator<<(ostream& out,const Coordinate& coor){
// 將原來用cout的地方換成out
out << coor.m_iX << "," << coor.m_iY;
// 返回out
return out;
}
// 使用
int main(){
Coordinate coor(3,5);
cout << coor; // operator<<(cout,coor);
return 0;
}
[ ]索引運算符的重載:
**問題:**可以使用友元函數進行重載嗎?不行,應該使用成員函數進行重載。
作爲索引運算符來說,他的第一個參數必須是this指針,因爲只有第一個參數是this指針,才能夠傳入索引,才能使這個索引所表達的是當前這個對象當中的成員。
但是友元重載,第一個參數可以是this指針,也可以是其他的值。所以不能使用友元函數進行重載。
// 聲明
class Coordinate{
public:
Coordinate(int x,int y);
int operator[](int index);
private:
int m_iX;
int m_iY;
}
// 實現
int Coordinate::operator[](int index){
if(0 == index){
return m_iX;
}
if(1 == index){
return m_iY;
}
// 還可以加其餘索引的處理...
}
// 使用
int main(){
Coordinate coor(3,5);
cout << coor[0]; // coor.operator[](0);
cout << coor[1]; // coor.operator[](1);
return 0;
}
小總結:
- 運算符重載可以使運算符具有新的功能。
- 運算符重載使用關鍵字operator
- 所有的運算符
都可以既使用友元函數重載和成員函數重載。有些運算符必須使用成員函數重載,有些則必須使用友元函數重載。 - 運算符重載需要區分前置++重載和後置++重載。
6.5.補充:運算符重載和友元函數的問題
頭文件:
`class` `Coordinate {` ` ``// 利用友元函數進行+號重載`` ``friend` `Coordinate operator+(``const` `Coordinate c1,``const` `Coordinate c2);` `public``:`` ``// 構造函數,帶初始化參數`` ``Coordinate(``int` `x,``int` `y);` ` ``int` `getX();`` ``int` `getY();` `private``:`` ``// 私有屬性`` ``int` `m_iX;`` ``int` `m_iY;``};`
cpp文件:
`#include "Coordinate.h"` `Coordinate operator+(Coordinate c1,Coordinate c2){` ` ``Coordinate temp(0,0);` ` ``temp.m_iX = c1.getX() + c2.getX();`` ``temp.m_iY = c1.getY() + c2.getY();` ` ``return` `temp;``}`
爲何這裏可以直接使用 temp.m_iX ?
答案:
對象直接訪問自己的私有成員這種用法絕對是錯的,不要這麼使用。可以藉助友元類/友元函數或類自己的成員函數進行訪問。
我們在Coordinate類中聲明瞭友元函數operator+(),友元函數在訪問權限上和類內函數比較相似,可以對這個類的所有數據成員進行訪問。
// 驗證友元函數中創建的對象是否可以訪問自己的私有成員。
// 結論:可以
#include <iostream>
using namespace std;
class hehe{
// hehe類的友元函數
friend void heheFriend(hehe &h);
public:
int getxx(){ return xx; }
int getyy(){ return yy; }
private:
int xx;
int yy;
};
void heheFriend(hehe &h){
// 友元函數可以訪問類的所有成員,包括私有
h.xx = 1;
h.yy = 2;
// 【友元函數中創建的對象h2可以訪問自己的私有成員】
hehe h2;
h2.xx = h.xx;
cout << "h2.xx = " << h2.xx << endl;
}
int main() {
hehe h1;
// 無法直接訪問私有成員
//h1.xx; // 錯誤提示:'xx' is a private member of 'hehe'
//h1.yy; // 錯誤提示:'yy' is a private member of 'hehe'
// 調用友元函數,進行函數成員賦值
heheFriend(h1);
cout << "h1.xx = " << h1.getxx() << endl;
return 0;
}
/*
* 輸出結果:
* h2.xx = 1
* h1.xx = 1
*/
7.函數模板
函數模板和類模板關鍵字:
template
, typename
和 class
,其中 typename
和 class
起到的作用是相同的,並且可以混用。
關鍵字的使用1:
// 函數模板
// 通過template聲明函數的模板
// 通過class來聲明一個參數,這個參數就能表明這是一種類型
template <class T>
T max(T a, T b){
return (a>b)?a:b;
}
// 模板函數(模板的使用),通過函數模板生產出來的函數
int ival = max(100,99); // 自動生成int數據類型
char cval = max<char>('A','B'); // 用<char>指定數據類型
關鍵字的使用2:
// 函數模板
template <class T>
void swap(T &a,T &b){
T tmp = a; a = b; b = tmp;
}
// 模板函數
int x = 20,y = 30;
swap<int>(x,y); // 用<int>指定數據類型
變量作爲模板參數:
// 函數模板
template <int size>
void display(){
cout << size << endl;
}
// 模板函數
display<10>();
多參數函數模板:
// 函數模板
// 後面的typename也不能省略
template <typename T,typename C>
void display(T a,C b){
cout << a << " " << b << endl;
}
// 模板函數
int a = 1024;string str = "hello world!";
display<int, string>(a,str);
函數模板定義中的混用:
// typename和class混用
template <typename T,class >
// 和變量混用
template <typename T,int size>
void display(T a){
for(int i = 0;i < size;i++){
cout << a << endl;
}
}
display<int,5>(15);
小總結:
- 函數模板的參數個數可以是一個也可以是多個。(模板的參數是指<>中的內容)
- 函數模板的參數個數
可以爲零個。不可以爲零個參數,參數個數如果爲0個,則沒有必要使用函數模板。 - 使用函數模板時,需要指定模板參數,此時的函數稱爲模板函數。
- 當需要定義多個功能相同,數據類型不同的函數時,可以使用函數模板來定義。
8.類模板
類模板的.h和.cpp文件:
在C++中,模板代碼不能分離編譯(不能寫成.h+.cpp這種形式,只能把所有的代碼都寫在.h文件中使用),不然部分編譯器無法通過編譯。在使用的時候,只需要把.h文件include
""進當前程序中。
類模板的定義與使用1:
template<class T>
class MyArray{
public:
// 1.類內定義
void display();
private:
T *m_pArr;
}
// 2.類外定義
// 類外定義類名後面需要添加<>
// 每定義一個成員函數,都要在這個成員函數前面添加template<......>
template<class T>
void MyArray<T>::display(){
......
}
// 使用
int main(){
MyArray<int> arr;
arr.display();
return 0;
}
類模板的定義與使用2:
寫的時候需要特別注意各種修飾符、限定符、返回值類型,很容易寫錯。
#include <iostream>
using namespace std;
/**
* 定義一個矩形類模板Rect
* 成員函數:calcArea()、calePerimeter()
* 數據成員:m_length、m_height
*/
template<typename T>
class Rect
{
public:
Rect(T length,T height);
T calcArea();
T calePerimeter();
public:
T m_length;
T m_height;
};
/**
* 類屬性賦值
*/
template<typename T>
Rect<T>::Rect(T length,T height)
{
m_length = length;
m_height = height;
}
/**
* 面積方法實現
*/
template<typename T>
T Rect<T>::calcArea()
{
return m_length * m_height;
}
/**
* 周長方法實現
*/
template<typename T>
T Rect<T>::calePerimeter()
{
return ( m_length + m_height) * 2;
}
// 使用
int main(void)
{
Rect<int> rect(3, 6);
cout << rect.calcArea() << endl; // 輸出:18
cout << rect.calePerimeter() << endl; // 輸出:18
return 0;
}
小總結:
- 定義一個類模板就相當於定義了一系列功能相同類型不同的類。
- 定義類模板需要使用關鍵字template。
- 定義類模板的參數可以使用typename和class,
但不能混用可以混用。 - 模板參數既可以是類型,也可以是變量。
9.標準模板庫STL(Standard Template Lib)
vector向量
**本質:**對數組的封裝
**特點:**讀取快速,讀取能在常數時間內完成,數據插入慢,如果插入數據,後面數據的位置都要移動。
初始化:
[外鏈圖片轉存失敗(img-8yBYgdSb-1563352671639)(./images/muke_C++/014.png)]
常用操作:
[外鏈圖片轉存失敗(img-Wy4kKhuO-1563352671640)(./images/muke_C++/015.png)]
向量的遍歷:
除了在for、while中使用下標進行遍歷,還可以使用迭代器。
// for遍歷
for(int i = 0;i < vec.size();i++){
cout << vec[i] << endl;
}
迭代器(iterator)及其使用方法:
int main(){
vector vec;
vec.push_back("hello");
// 定義、初始化迭代器iterator
vector<string>::iterator citer = vec.begin();
// 使用迭代器遍歷向量
for( ;citer != vec.end();citer++){
cout << *citer << endl;
}
return 0;
}
鏈表(list)
**特點:**數據插入速度快,查詢較慢。
操作和vector向量類似,也可以使用迭代器,但不能用下標循環遍歷。
映射(map)
鍵值對,有點類似數組。
// 創建一個映射
map<int,string> m;
// 創建映射關係
pair<int,string> p1(10,"shanghai");
pair<int,string> p2(20,"beijing");
// 將創建的映射關係插入映射中
m.insert(p1);
m.insert(p2);
// 調用映射
cout << m[10] << endl;
cout << m[20] << endl;
也可以使用字符串作爲索引:
map<string,string> m;
pair<string,string> p1("SH","shanghai");
pair<string,string> p2("BJ","beijing");
m.insert(p1);
m.insert(p2);
cout << m["SH"] << endl;
cout << m["BJ"] << endl;
映射的遍歷:
**注意:**打印是按索引(key)的順序打印的,如“A”,先於“B”打印。
// 使用迭代器進行遍歷
map<int,string>::iterator itor = m.begin();
for(;itor != m.end();itor++){
cout << itor->first << endl; // 輸出key
cout << itor->second << endl; // 輸出value
cout << endl;
}
小總結:
- vector是對數組的封裝,所以一旦對象被實例化,
其大小就不能改變了大小可以根據元素數量改變。 - list的特點是數據插入速度快。
- map需要與pair一起使用,用來存儲多個key-value對。
- 不同廠商的標準模板庫的實現細節可以不同,基本用法及原理相同。
練習:
// 使用vector存儲數字3,6,8,4,並遍歷。
// 使用map存儲S-Shang Hai B-Bei Jing G-Guang Zhou,並遍歷
#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(void)
{
// 使用vector存儲數字:3、4、8、4
vector<int> vec;
vec.push_back(3);
vec.push_back(4);
vec.push_back(8);
vec.push_back(4);
//循環打印數字
vector<int>::iterator citer = vec.begin();
for( ; citer != vec.end(); citer++){
cout << *citer << endl;
}
// 使用map來存儲字符串鍵值對
map<string, string> m;
pair<string,string> p1("S","Shang Hai");
pair<string,string> p2("B","Bei Jing");
pair<string,string> p3("G","Guang Zhou");
m.insert(p1);
m.insert(p2);
m.insert(p3);
// 打印map中數據
map<string,string>::iterator citer2 = m.begin();
for( ; citer2 != m.end(); citer2++){
cout << citer2->first << endl; // key
cout << citer2->second << endl; // value
}
return 0;
}
/* 輸出:
3
4
8
4
B
Bei Jing
G
Guang Zhou
S
Shang Hai
*/
**注意:**打印是按索引(key)的順序打印的,如“A”,先於“B”打印。
本篇爲視頻教程筆記,視頻如下: