一. 結構體

結構體是⼀種自定義的類型,使用這種自定義類型可以描述一些複雜對象。 前面我們學習的都是單一的數據類型,比如: 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++面向對象思想重要組成體現,下來 一定要深入學習和挖掘。