一. 結構體
結構體是⼀種自定義的類型,使用這種自定義類型可以描述一些複雜對象。 前面我們學習的都是單一的數據類型,比如: char 、 short 、 int 、 double 類型,但是我們現實生活中總是有寫複雜對象,比如:人、書等,這些複雜對象,僅僅使用單一的數據類型是不能描述的。比如:描述一本書,書有作者、出版社、定價等信息;描述一個人,人有名字、性別、年齡、 身高、體重等信息。那怎麼描述這些複雜對象呢? C++中引入了結構體和類來解決這些問題。在C++中結構體和類是相似的,這裏首先介紹結構體。
1. 結構體類型聲明和變量定義
結構體類型聲明的關鍵字是 struct ,結構體類型聲明的基本語法如下:
struct tag
{
成員變量列表;
//成員變量可以有1個,也可以有多個
成員函數列表;
//成員函數可以有,也可以沒有
}
結構體變量列表;
//在聲明結構體類型的同時,創建結構體變量,可以有多個,
//中間使用逗號隔開
像這樣
#include <iostream>
#include <string>
using namespace std;
struct Stu //--學生類型
{
string name;
int age;
int chinese;
int math;
int total;
} ;
這就是我們聲明的結構體類型
struct Stu s1; ---這是結構體變量的創建
當我們聲明結構體後就可以使用其來創建變量
#include <iostream>
#include <string>
using namespace std;
struct Stu //--學生類型
{
string name;
int age;
int chinese;
int math;
int total;
}s4,s5,s6;//這裏的s4,s5,s6,就是在結構體聲明的同時,
//順便創建的三個變量 ,是全局變量
struct Stu s7;
//全局變量
int main()
{
struct Stu s1;
struct Stu s2;
Stu s3; //struct (在創建變量時)可省略
Stu arr[6];//結構體類型的數組
return 0;
}
總結為
1. 在創建變量的時候,結構體類型中的 struct 就可以省略了。
2. 在創建結構體變量的時候,結構體變量的名字不能和結構體類型的名字相同。
3. 聲明的結構體類型是不佔用內存空間,當使用結構體類型創建了變量後,才向內存申請了空間的。
(變量創建的本質是:在內存中申請空間,如int a = 10 , 是向內存申請了4個字節空間,用來存放整數10,int是不佔用空間的,所以類型不佔用空間,只有創建變量時才會向內存申請空間)
4. 結構體變量可以是全局變量,也可以是局部變量。
2. 結構體變量的特點
結構體的初始化
結構體的初始化和數組類似,使用 {} ,將初始化的內容按照順序放在 {} 中就可以。
#include <iostream>
#include <string>
using namespace std;
struct Stu //--學生類型
{
string name;
int chinese;
int math;
int total;
};
int main()
{
struct Stu s1 = {"zhangsan",85,95,0};
//調試時,確實能看到我們創建的變量被存入了類型中
return 0;
}
這便是初始化
結構體整體操作
結構體變量中雖然包含多個成員,而且成員可能是不同類型的。 但是一個結構體變量可以看做一個整體,是可以直接進行賦值操作的。
#include <iostream>
#include <string>
using namespace std;
struct Stu //--學生類型
{
string name;
int chinese;
int math;
int total;
};
int main()
{
struct Stu s1 = {"zhangsan",85,95,0};
Stu s2;
s2 = s1;
//進行調試監視s2時,能發先第一遍s2的值是隨機的
//第二遍則為s1的值
return 0;
}
結構體成員的訪問
結構體成員訪問的基本形式是:
結構體變量.成員名
因為每個結構體變量中都有屬於自己的成員,所有必須使用 . 這種結構體成員訪問操作符。 通過這種方式找到成員後,可以直接給結構體變量的成員中輸入值,或者直接存儲和使用。
#include <iostream>
#include <string>
using namespace std;
struct Stu //--學生類型
{
string name;
int chinese;
int math;
int total;
};
int main()
{
Stu s1;
s1.name = "lisi";
s1.chinese = 90;
cin >> s1.math;
//輸入92
s1.total = s1.chinese + s1.math;
cout << s1.total << endl;
//輸出182
//使用調試-監視可以看到數值的變換
return 0;
}
結構體嵌套
當然如果結構體中嵌套了其他結構體成員,這裏初始化的時候,也可以員中的成員可以連續使用 . 操作符。
例如:
#include <iostream>
#include <string>
using namespace std;
struct Score
{
int chinese;
int math;
int english;
};
struct Stu
{
string name;
Score sc;//三門成績
int total;//總
int avg;//平均
};
int main()
{
Stu s1 = { "wangwu", {80,90,60}, 0, 0 };
//結構體裏包含嵌套
s1.total = s1.sc.chinese + s1.sc.english + s1.sc.math;
s1.avg = s1.total / 3;
cout << s1.total << " " << s1.avg << endl;
//輸出 230 76
return 0;
}
這便是結構體的嵌套
3. 結構體成員函數
以上我們講的是結構體的基本知識,C語言中的結構體就是這樣的,但是C++中的結構體和C語言結構體的有一個比較大的差異就是:
C++中的結構體中除了有成員變量之外,還可以包含成員函數。
1. C++的結構體會有⼀些默認的成員函數,比如:構造函數、析構函數等,是編譯器默認生成的,如果覺得不合適,也是可以自己顯示的定義這些函數,這些函數都是自動被調用,不需要手動調用。
2. 除了默認的成員函數之外,我們可以自己定義一些成員函數,這些成員函數可以有,也可以沒有, 完全根據的實際的需要來添加就可以。
3. 這些成員函數可以直接訪問成員變量
4. 成員函數的調用也使用 . 操作符
代碼舉例
#include <iostream>
#include <string>
using namespace std;
struct Stu
{
//成員變量
string name;
int chinese;
int math;
int total;
//成員函數
//初始化結構體的成員變量
void init_stu()
{
name = "小明";
chinese = 100;
math = 100;
total = chinese + math;
}
};
int main()
{
struct Stu s1;
s1.init_stu();
return 0;
}
調試時就能觀察到該函數完成了結構體的初始化和調用
#include <iostream>
#include <string>
using namespace std;
struct Stu
{
//成員變量
string name;
int chinese;
int math;
int total;
//成員函數
//初始化結構體的成員變量
void init_stu()
{
name = "小明";
chinese = 100;
math = 100;
total = chinese + math;
}
};
int main()
{
struct Stu s1;
s1.init_stu();
cout << "名字:" << s1.name << endl;
cout << "語文:" << s1.chinese << endl;
cout << "數學:" << s1.math << endl;
cout << "總分:" << s1.total << endl;
//輸出
//名字:小明
//語文:100
//數學:100
//總分:200
return 0;
}
#include <iostream>
#include <string>
using namespace std;
struct Stu
{
//成員變量
string name;
int chinese;
int math;
int total;
//成員函數
//初始化結構體的成員變量
void init_stu()
{
name = "小明";
chinese = 100;
math = 100;
total = chinese + math;
}
//打印的信息
void print_stu() //--成員函數
{
cout << "名字:" << name << endl;
cout << "語文:" << chinese << endl;
cout << "數學:" << math << endl;
cout << "總分:" << total << endl;
}
};
int main()
{
struct Stu s1;
s1.init_stu();
s1.print_stu();
//輸出
//名字:小明
//語文:100
//數學:100
//總分:200
return 0;
}
上面的void都是成員函數
構造函數和析構函數
構造函數:
構造函數是結構中默認的成員函數之一,構造函數的主要的任務是初始化結構體變量。寫了構造函 數,就不需要再寫其他成員函數來初始化結構體(類)的成員,而且構造函數是在結構變量創建的時候, 編譯器⾃動被調用的。
構造函數的特徵如下:
• 函數名與結構體(類)名相同。
• 無返回值。
• 構造函數可以重載。
• 若未顯式定義構造函數,系統會自動生成默認的構造函數。
析構函數:
析構函數是用來完成結構體變量中資源的清理工作,也是結構體中默認的成員函數之一。
析構函數在結構體變量銷燬的時候,被自動調用的。
析構函數的特徵如下:
• 析構函數名是在結構體(類)名前加上字符
• 無參數無返回值類型。
• 一個類只能有一個析構函數。若未顯式定義析構函數,系統會自動生成默認的析構函數。
注意:析構函數不能重載。
//代碼-演示構造和析構函數
//代碼-演示構造和析構函數
#include <iostream>
using namespace std;
struct Stu
{
//成員變量
string name;
int chinese;
int math;
int total;
//構造函數
Stu()
{
cout << "調用構造函數" << endl;
name = "小明";
chinese = 99;
math = 95;
total = math + chinese;
}
//析構函數
~Stu()
{
////資源釋放的工作
cout << "調用析構函數:~Stu()" << endl;
//對於當前這個類的對象沒有啥資源清理
chinese = 0;
}
void print_stu() //打印
{
cout << "名字:" << name << endl;
cout << "語文:" << chinese << endl;
cout << "數學:" << math << endl;
cout << "總分:" << total << endl;
}
};
int main()
{
struct Stu s;
s.print_stu();
return 0;
}
//輸出
//調用構造函數
//名字:小明
//語文:99
//數學:95
//總分:194
//調用析構函數:~Stu()
構造函數和析構函數是編譯器主動調用的
這裏給大家簡單的舉例演示了一下結構體的成員函數,其實成員函數和自定義函數是一樣的,是可以根據實際的需要來完成編寫的。 當然在競賽的題目中關於結構體其實很少使用成員函數,使用最多的應該是針對結構變量的運算符的重載。
4. 運算符的重載
在上一個代碼的例子中,在打印結構體成員信息的時候,我們是通過 的。 但是我們在C++中打印數據的時候,習慣了直接使用 s.print_stu() 的方式完成 cout 來直接打印數據的。
比如:
int n = 100;
float f = 3.14f;
cout << n << endl;
cout << f << endl;
那針對 struct Stu 類型的變量能不能直接使用 cout 來打印呢?
比如這樣:
#include <iostream>
using namespace std;
struct Stu
{
//成員變量cout 來打印呢??如這樣:
string name;
int chinese;
int math;
int total;
};
int main()
{
struct Stu s = {"張三", 90, 80, 170};
cout << s << endl;
return 0;
}
報錯了,報錯沒有與這些操作數匹配的輸出運算符,直接使用cout是不可以的
那這樣需要對它進行重載
#include <iostream>
using namespace std;
struct Stu
{
//成員變量
string name;
int chinese;
int math;
int total;
};
//重載運算符
//重載的是<<(輸出運算符),
//讓<< 支持struct stu 類型數據的打印
ostream& operator<<(ostream& os, const Stu & s)
{
os << "名字: " << s.name << endl;
os << "語文: " << s.chinese << endl;
os << "數學: " << s.math << endl;
os << "總分: " << s.total << endl;
return os;
}
int main()
{
struct Stu s = {"張三", 90, 80, 170};
cout << s << endl;
return 0;
}
//輸出
//名字: 張三
//語文: 90
//數學: 80
//總分: 170
5. 結構體排序-sort
説到排序,我們之前講過冒泡排序,我們也可以寫一個冒泡排序函數來排序一組結構體數據,但是這裏給大家介紹一個C++的STL中的庫函數 sort ,可以直接用來排序數據,在算法競賽和日常開發中使用非常頻繁。 只要涉及到數據的排序,又沒有明確要求自己實現排序算法的時候,就可以直接使用sort函數。
sort 函數介紹
函數原型如下:
template <class RandomAccessIterator>
void sort (RandomAccessIterator first, RandomAccessIterator last);
//void sort(開始位置,結束位置);
//first:指向要排序範圍的第一個元素的迭代器或者指針。
//last:指向要排序範圍的最後一個元素之後位置的迭代器或者指針。
在默認情況下sort函數,按升序對給定範圍[first, last ] 的元素進行排序。
sort函數需要包含的頭文件 <algorithm>
還有另一種版本
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
//void sort(開始位置,結束位置,自定義排序函數);
//first:指向要排序範圍的第一個元素的迭代器或者指針。
//last:指向要排序範圍的最後一個元素之後位置的迭代器或者指針。
//comp:是一個比較函數或者函數對象
//這裏開始位置和結束位置,可以是指針,也可以是迭代器
//自定義排序函數以是函數,也可以是仿函數
排序內置類型數據
對數組進行排序
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//起始位置和結束位置傳的是地址
//數組名就是首元素的地址
//arr + sz 跳過sz個元素,就是sz+1個元素的地址
sort(arr, arr + size);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
//輸出
//1 2 2 4 4 5 5 6 7 8 9
return 0;
}
默認排序結果是升序的。
對字符串中的字符進行排序
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
string s("defxxxabccba");
sort(s.begin(), s.end());
cout << s << endl;
//輸出
//aabbccdefxxx
return 0;
}
這裏是對字符串中字符的順序進行排序,是按照字符的ASCII值進行排序的
默認排序的結果依然是升序。
自定義排序
sort 在默認的情況下是按照升序排序,如何按照降序排序呢?如果是結構體類型數據進行排序呢? 怎麼辦? 使用自定義排序方式
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare
comp);
sort 的第三個參數是個一可選的自定義比較函數(或函數對象),用於指定排序的規則。如果不提 供這個參數, std::sort 默認會使用小於運算符( < )來比較元素,並按照升序排序。 這個比較函數,接受兩個參數,並返回一個布爾值。如果第一個參數應該排在第二個參數之前,則返回true;否則返回 false。
comp 表示可以自定義一個排序方法,使用方法如下:
創建比較函數
#include <iostream>
#include <algorithm>
using namespace std;
//自定義一個比較函數,這個比較函數能夠比較被排序數據的2元素大小
//函數返回bool類型的值
bool compare(int x,int y)
{
return x > y; //降序,,--- < ---升序
}
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//將函數名作為第三個參數傳入sort函數中
sort(arr, arr + size, compare);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
//輸出 9 8 7 6 5 5 4 4 2 2 1
return 0;
}
就是構造一個比較函數 compare,然後函數名傳入參數,實現調用
結構體中重載()運算符-仿函數
#include <iostream>
#include<algorithm>
using namespace std;
//仿函數方式- 仿函數也叫函數對象
struct Cmp
{
bool operator()(int x, int y)
{
return x > y;
//排降序
}
}cmp;
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//將結構體對象作為第三個參數傳入sort函數中
sort(arr, arr + size, cmp);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
//輸出
// 9 8 7 6 5 5 4 4 2 2 1
return 0;
}
關於仿函數、操作符重載,要深入學習C++,競賽中涉及的較少、也比較淺。
排序結構體數據
兩個結構體數據也是不能直接比較⼤大小的,在使用 sort 函數排序的時候,也是需要提供自定義的比較方法。比如:
使用名字
#include <iostream>
#include <algorithm>
using namespace std;
struct S
{
string name;
int age;
};
//提交比較函數
bool cmp_s_by_name(const struct S& s1, const struct S& s2)
{
return s1.name > s2.name;
//按名字降序
}
int main()
{
struct S arr[] = {{"zhangsan",20},{"lisi",25},{"wangwu",18}};
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,arr + sz,cmp_s_by_name );
for(int i = 0;i < sz; i++)
{
cout << arr[i].name << ":" <<arr[i].age << endl;
}
return 0;
}
//輸出
// zhangsan:20
// wangwu:18
// lisi:25
使用年齡
#include <iostream>
#include <algorithm>
using namespace std;
struct S
{
string name;
int age;
};
//提交比較函數
bool cmp_s_by_age(const struct S& s1, const struct S& s2)
{
return s1.age > s2.age;
//按年齡降序
}
int main()
{
struct S arr[] = {{"zhangsan",20},{"lisi",25},{"wangwu",18}};
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,arr + sz,cmp_s_by_age );
for(int i = 0;i < sz; i++)
{
cout << arr[i].name << ":" <<arr[i].age << endl;
}
return 0;
}
//輸出
// lisi:25
// zhangsan:20
// wangwu:18
使用仿函數的方式
#include <iostream>
#include <algorithm>
using namespace std;
struct S
{
string name;
int age;
};
//按照仿函數的方式來名字降序
struct Cmp
{
bool operator()(struct S s1, struct S s2)
{
return s1.name > s2.name;
//按名字降序
}
}cmp;
int main()
{
struct S arr[] = {{"zhangsan",20},{"lisi",25},{"wangwu",18}};
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,arr + sz,cmp );
for(int i = 0;i < sz; i++)
{
cout << arr[i].name << ":" <<arr[i].age << endl;
}
return 0;
}
//輸出
// zhangsan:20
// wangwu:18
// lisi:25
按年齡只需name改為age,升序<,降序>
二. (拓展)類
C++中為了更好的實現面向對象,更喜歡使用的理解為 class 和 class (類)來替換 struct (結構體)。你可以簡單粗暴 struct 是⼀回事,但是其實本上還是有區別的。接下來我們學習一下類。
1. 類的定義
類的關鍵字是 class ,類中主要也是由兩部分組成,分別為成員變量和成員函數。
形式如下:
class tag
{
public:
成員變量列表;
成員函數列表;
};
// 一定要注意後面的分號
class 是用來定義類類型的關鍵字,在類中可以定義成員函數和成員變量。
public 是類成員權限訪問限定符,標誌類中的成員可以公開訪問及調用,訪問限定符在後續內容中 會介紹。
問:為什麼結構體中沒有 public 呢?
原因是:結構體的成員變量和成員函數,默認就是公開的,而函數默認是私有的( private )。
• 包含成員變量的類,例如描述一個學生:
class Stu
{
public:
int math;
//數學成績
string name;
//名字
int chinese; //語文成績
int total;
//總成績
};
包含成員變量和成員函數的類,如:
class Stu
{
public:
void init_stu()
{
name= "小明";
chinese = 0;
math = 0;
total = 0;
}
string name; //名字
int math; //數學成績
int chinese; //語文成績
int total; //總成績
};
在類中,成員變量和成員函數都是沒有數量限制的,即可以有多個成員變量或成員函數
2. 類的使用
2.1. 創建對象
int main()
{
Stu s1;
Stu s2;
return 0;
}
2.2. 調用類對象成員
像結構體⼀樣,通過操作符(.)即可對類對象的成員進行訪問
#include <iostream>
using namespace std;
int main()
{
//創建類對象
Stu s1;
//調用成員變量
s1.name = 張三";
s1.chinese = 90;
s1.math = 98;
s1.total = s1.chinese + s1.math;
Stu s2;
//調用成員函數
s2.init_stu();
return 0;
}
3. 訪問權限控制
訪問限定符是C++語言中的關鍵字,用於指定類成員的訪問權限。 訪問限定符主要有三個:
public :成員被聲明為 public 後,可以被該類的任何方法訪問,包括在類的外部。
protected :成員被聲明為 protected 後,可以被該類訪問。(暫不介紹)
private :成員被聲明為 private 後,只能被該類的成員函數訪問。
#include <iostream>
using namespace std;
class Stu
{
public:
//將成員函數設置為公有屬性
void init_stu()
{
name= "小明";
chinese = 0;
math = 0;
total = 0;
}
private: //將成員變量設置為私有屬性
string name; //名字
int chinese; //語?成績
int math; //數學成績
int total; //總成績
};
int main()
{
Stu stu;
stu.init_stu(); //訪問公有成員函數
cout << stu.name << endl; //訪問私有成員變量,編譯器報錯
return 0;
}
通過運行結果可見,類外可直接訪問設置為公有的成員,而不可以直接訪問私有的成員。
⼩提⽰:
習慣上,外部可訪問的成員函數通常設置為公有屬性( public ),而為了提高成員變量的訪問安全性,通常將成員變量設置為私有屬性( private ),即只有類內部可以訪問。
4. 結構體和類的區別
C++中 struct 和 class 的區別是什麼?
C++兼容了C語言,所以C++中 類是一樣的。區別是 struct 既可以當成結構體使用,還可以用來定義類,和 struct 定義的類默認訪問權限是 private 。
結論:
類的定義既可以使用 public , class 定義 class 定義的類默認訪問權限 struct ,也可以使用 class C++更常用 class 關鍵詞來定義類,因此在選用 class 的時候需要加上訪問限定符 public 才可以在類外調用類的成員函數和成員變量。
C++中關於類和對象的知識,遠不止這些,類和對象的相關知識,在算法競賽很少使用,但是在真正的軟件開發過程中非常重要,C++的類和對象是C++面向對象思想重要組成體現,下來 一定要深入學習和挖掘。