一.結構體
1.概念:
結構體(struct)是一種用戶自定義復合數據類型,其中可以包含不同類型的不同成員
2.結構體的應用場景:
我們在使用多個變量描述一個對象時,雖然也可以做到,但是難免顯得雜亂和繁瑣,比如我們在描述一個人時,經常會使用例如姓名、年齡、性別等等屬性來描述他,那么在這種情況下,我們使用結構體就會顯得一切都有序起來
3.結構體的聲明定義和使用的基本語法:
#include"iostream"
using namespace std;int main() {// 聲明結構體/*struct 結構體類型 {成員1類型 成員1名稱;...成員N類型 成員N名稱; }; */// 結構體聲明案例:// 在此案例中我們聲明了一個Student結構體,也就是一個學生對象,其中包含name,age,gender三個屬性struct Student {string name;int age;string gender;}; // 在結構體變量的聲明中,可以在前面帶上struct關鍵字(也可以省略不寫)// 但是還是建議寫上,可以清晰的知道變量是自定義結構體類型的Student stu1; // 創建一個叫做stu1的結構體變量struct Student stu2; // 創建一個叫做stu2的結構體變量// 為結構體變量賦值stu1 = {"zhansgan", 18, "男"};stu2 = {"lisi", 19, "女"};// 若是想要輸出結構體變量,不能使用cout << stu1 << endl;的形式// 因為結構體變量是一個整體的包裝,無法直接cout輸出// 那么如果想要輸出的話,就需要訪問它的每一個成員進行輸出,訪問語法:結構體變量.成員名稱cout << stu1.name << endl;cout << stu1.age << endl;cout << stu1.gender << endl;cout << stu2.name << endl;cout << stu2.age << endl;cout << stu2.gender << endl;//我們還可以在初始化一個結構體變量時就對其中的數值進行定義:struct Student stu3 = {"wangwu", 20, "男"};cout << stu3.name << endl;cout << stu3.age << endl;cout << stu3.gender << endl;return 0;
}
4.結構體成員默認值:
在我們設計一個結構體的時候,可以根據需要向成員設置默認值
如下:
#include"iostream"
using namespace std;int main() {// 定義一個Student結構體struct Student {string name;int age = 18; // 默認年齡為18string gender = "男"; // 默認性別為男};// 創建兩個結構體變量,一個使用結構體成員默認值,一個不使用struct Student stu1 = {"zhangsan"};struct Student stu2 = {"lisi", 20, "女"};cout << stu1.name << endl;cout << stu1.age << endl;cout << stu1.gender << endl;cout << endl;cout << stu2.name << endl;cout << stu2.age << endl;cout << stu2.gender << endl;return 0;
}
5.結構體數組
作為用戶自定義數據類型,結構體是支持數組模式的
#include"iostream"
using namespace std;int main() {/*// 聲明數組對象[struct] 結構體類型 數組名[數組長度];// 賦值數組的每一個元素數組名[0] = {a, b, ...};數組名[1] = {c, d, ...};...//聲明和賦值同步的快捷寫法[struct] 結構體類型 數組名[數組長度] = {{第一個數組中的數值}, {第二個數組中的數值}, ..., {第n個數組中的數值}};*/struct Student {string name;int age;string gender;};struct Student stu[3]; // 結構體數組對象的聲明// 對創建的結構體數組對象進行賦值stu[0] = {"zhangsan", 18, "男"};stu[1] = {"lisi", 19, "女"};stu[2] = {"wangwu", 20, "男"};// 使用for循環遍歷結構體數組對象中的成員for (int i = 0; i < 3; i++) {cout << stu[i].name << endl;cout << stu[i].age <<endl;cout << stu[i].gender << endl;cout << endl;}// 在對結構體數組聲明的同時對其進行賦值:struct Student student[3] = {{"chen", 21, "男"},{"zhang", 22, "女"},{"li", 23, "男"}};for (int i = 0; i < 3; i++) {cout << student[i].name << endl;cout << student[i].age <<endl;cout << student[i].gender << endl;cout << endl;}return 0;
}
6.結構體指針
作為一種數據類型,結構體同樣支持使用指針
(1).引入已經存在的結構體地址
(2).通過new操作符申請指針空間
注意:在我們使用指針變量訪問結構體成員時需要更換操作符號為:->
#include"iostream"
using namespace std;int main() {struct Student {string name;int age;string gender;};// 先創建一個結構體對象struct Student stu = {"zhangsan", 18, "男"};// 引入已經存在的結構體地址struct Student *p1 = &stu; // 注意這里的取地址符,也就是&,指針中存儲的是地址,必須加上這個符號獲取該變量的地址// 通過結構體指針訪問結構體的成員,其中我們需要使用的符號是:->cout << p1->name << endl;cout << p1->age << endl;cout << p1->gender << endl;// 通過new操作符申請指針空間struct Student *p2 = new Student {"lisi", 19, "女"};cout << p2->name << endl;cout << p2->age << endl;cout << p2->gender << endl;return 0;
}
7.結構體指針數組
在結構體中可以使用指針數組,其主要用于動態內存分配,方便管理大量結構體占用的內存
(1).引入已經存在的結構體數組地址
(2).通過new操作符申請指針數組空間
注意:在我們使用指針變量訪問結構體指針數組的成員時需要更換操作符號為一開始的:.
#include"iostream"
using namespace std;int main() {struct Student {string name;// 設置了默認值,省事兒int age = 18;string gender = "男";};// 引入已經存在的結構體數組地址// 創建一個結構體數組struct Student stu1[3] = {{"zhangsan"}, {"lisi"}, {"wangwu"}};// 數組的存儲方式本來就是地址,所以不需要使用取地址符號取出該變量對應的地址struct Student *p1 = stu1;// 若要取出結構體指針數組中的成員不再是使用->符號,而是回到.for (int i = 0; i < 3; i++) {cout << p1[i].name << endl;cout << p1[i].age << endl;cout << p1[i].gender << endl;}// 通過new操作符自行申請結構體數組的空間(通過delete操作符回收)struct Student *p2 = new Student[3] {{"chen"}, {"li"}, {"wang"}};for (int i = 0; i < 3; i++) {cout << p2[i].name << endl;cout << p2[i].age << endl;cout << p2[i].gender << endl;}// 在使用結束之后使用delete操作符釋放申請的空間,可以減輕內存壓力delete[] p2;return 0;
}
二.函數
1.函數的概念:
函數是一個提前封裝好的,可以重復使用的,完成特定功能的獨立代碼單元
2.使用函數的好處:
(1).提前封裝:
提前準備好了代碼邏輯
(2).可重復使用:
可以多次調用
(3).完成特定功能:
只用來完成我們所希望它完成的部分特定功能
3.函數的使用場景:
我們可以將針對特定功能的、有重復使用需求的代碼提前封裝到函數內,以便我們在需要的時候隨時調用
4.函數的結構:
函數返回值類型 函數名(傳入參數1, 傳入參數2, ..., 傳入參數n) { // 共同構成了函數聲明函數體;return 返回值;}
5.基礎函數語法
注意:return語句執行之后函數就會立刻結束;函數不可以被定義在main函數內部
#include"iostream"
using namespace std;// 編寫一個函數返回兩個傳入參數中的較大值
int max(int a, int b) {int max = a > b ? a : b;return max;
}
// 注意在return語句執行之后函數就會立刻結束,以及函數不可以被定義在main函數的內部int main() {// 調用函數int a = 10, b = 20;cout << max(a, b) << endl;return 0;
}
6.無返回值函數與void類型
函數的返回值并非是必須提供的,也就是說可以聲明一個不提供返回值的函數
聲明一個不提供返回值的函數時需要做到以下幾點:
①:聲明函數返回值類型為void(void表示“空”,用于聲明無返回值函數)
②:不需要寫return語句
③:調用者無法得到返回值
#include"iostream"
using namespace std;// 編寫一個無返回值函數
void love(string name) {cout << "I love you, my lovely lover——" << name << endl;
}int main() {// 調用函數love("unmasked lover");love("my beauty");return 0;
}
7.空參函數
除了返回值以外,函數的傳入參數也是可選的,也就是聲明一個不需要傳入參數的函數(括號是不能省略的嗷)
#include"iostream"
using namespace std;// 編寫一個無返回值,同時無參的函數
void love() {cout << "I'll betray my instinct to fall in love with you, until the end of my life" << endl;
}int main() {// 調用函數while(1) {love(); // 無限循環,記得手動暫停嗷(別在沒設置停止條件的時候這么寫,我寫著玩的)}return 0;
}
8.函數嵌套調用
函數作為一個獨立的代碼單元,可以在函數內調用其他函數,這種嵌套調用關系沒有任何限制,可以根據需要無限嵌套
#include"iostream"
using namespace std;void yesturday() {cout << "I just want to love you" << endl;
}void today() {cout << "for three days," << endl;
}void tomorrow() {cout << "yesturday, today, and tomorrow." << endl;
}// 寫出本函數調用上方的三個函數,需要注意的是我們需要先定義再調用,也就是說如果我們把函數定義在了調用處的下方就會報錯
void love() {yesturday();today();tomorrow();
}int main() {// 調用函數while(1) {love(); // 無限循環,記得手動暫停嗷(別在沒設置停止條件的時候這么寫,我寫著玩的)}return 0;
}
9.參數的值傳遞和地址傳遞
在這之前學習的函數的形參聲明其實都是在使用“值傳遞”的參數傳入方式,但是像這樣的值傳遞方式會出現問題,例如:
#include"iostream"
using namespace std;// 這是一個交換兩數之值的函數,但是值傳遞方式根本無法達到我們想要的效果
void exchange(int a, int b) {int temple = a;a = b;b = temple;
}int main() {int a = 10, b = 20;exchange(a, b);cout << a << " " << b << endl;// 你會發現像這樣值傳遞的函數根本無法做到交換兩個變量的值,這是因為交換的只是傳遞過去的形參的值,對兩個變量中存儲的數值而言并沒有影響return 0;
}
那么為了不出現這樣的問題,我們就需要進行地址(指針)的傳遞,將函數體的效果直接作用于內存上
#include"iostream"
using namespace std;// 地址傳遞方式下的值交換函數
void exchange(int *a, int *b) {int temple = *a;*a = *b;*b = temple;
}int main() {int a = 10, b = 20;exchange(&a, &b);// 地址傳遞方式之下就能夠完成我們所想要的值交換效果了cout << a << " " << b << endl;return 0;
}
10.函數傳入數組
由于數組對象本身只是第一個元素的地址,所以數組傳參不區分值傳遞還是地址傳遞,從本質上來說都是地址傳遞(在傳入數組時一般建議附帶數組長度的傳入,因為C++不會檢查數組的內存邊界)
#include"iostream"
using namespace std;// 傳入函數后的數組由于已經變成了指針的形式,所以大小固定為8
void function(int arr[]) {cout << sizeof(arr) << endl;
}int main() {// 但是在main函數中計算的數組大小為數組長度*數據類型的大小int arr[3];// 所以在這里計算出的結果就是3*4(int類型數據的大小)cout << sizeof(arr) << endl;function(arr);return 0;
}
也因為傳入函數后的數組有著這樣的特點,所以我們很難在函數中計算出該數組的長度,所以一般都會在傳入數組后順帶傳入該數組的長度
#include"iostream"
using namespace std;// 在main函數中計算出數組的長度傳入函數中,我們在函數里面很難獲取數組長度
void function(int arr[], int length) {for (int i = 0; i < length; i++) {cout << arr[i] << endl;}
}int main() {int arr[10];for (int i = 0; i < 10; i++) {arr[i] = i;}int length = sizeof(arr) / sizeof(arr[0]);function(arr, length);return 0;
}
11.函數的引用傳參
函數的形參還有引用傳參這一形式
引用指的是變量的一個別名,它是某個已存在變量的另一個名字
注意:引用不是指針,指針可以修改指向,但是引用不行
引用的特點:
①:引用在創建后不可更改(更改它的指向到其他內存區域)
②:因為引用的不可更改特性,所以引用必須初始化
③:因為引用的必須初始化特性,所以引用不可為空(不可被修改)
引用的語法:
主要使用&表明引用
數據類型& 引用名 = 被引用變量;
舉例:
#include"iostream"
using namespace std;int main() {int a = 10;int& b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}
雖然不可以改變引用的指向,但是可以改變引用指向中的內容,例如:
#include"iostream"
using namespace std;int main() {int a = 10;int& b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;a = 20;cout << "a = " << a << endl;cout << "b = " << b << endl;a = 30;cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}
12.引用傳參
除了值傳遞和地址傳遞以外,函數也可以使用引用傳遞的形式傳參,同時引用傳參也是最為常見的傳參方式
各種函數傳參方式的對比:
①:值傳遞雖然會復制值,但是無法對實參本身產生影響
②:地址傳遞雖然會復制地址,可以對實參本身產生影響,但是指針的寫法麻煩
③:引用傳遞可以像普通變量那樣操作,但是對實參本身可以產生影響
#include"iostream"
using namespace std;// 定義形參,使用&表示傳遞引用的對象
void exchange(int &a, int &b) {int temple = a;a = b;b = temple;
}int main() {int a = 10, b = 20;exchange(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;return 0;
}
方式/特征 | 形參定義 | 傳遞內容 | 對實參的影響 | 是否直接內存操作 |
---|---|---|---|---|
值傳遞 | 普通變量(int) | 賦值變量值(數據傳遞) | 無 | 否 |
地址傳遞 | 指針變量(int*) | 賦值指針值(地址傳遞) | 有 | 是 |
引用傳遞 | 引用變量(int &) | 復制引用對象 | 有 | 否 |
13.返回指針的函數以及局部變量的生命周期
函數的返回值可以是一個指針(內存地址),語法為:
返回值類型 * 函數名(傳入的形參) {函數體;return 指針;
}
注意:
①:在聲明中,提供*,表示返回值是指針
②:return所返回的返回值一定要是一個指針變量
但是可能出現語法正確,但無法正常返回結果的情況,例如:
#include"iostream"
using namespace std;// 定義了一個加法函數
int* add(int a, int b) {int sum = a + b;return ∑
}
// 雖然像這樣的代碼是符合語法的,但是無法正常返回結果
// 因為sum變量是一個局部變量,它的作用域僅在函數內,函數一旦執行完畢,sum變量就會被銷毀,根本傳不出去
// 這就是C++的靜態內存管理的弊端,如果需要進行規避,那么就要用到動態內存管理(new和delete)int main() {int a = 10, b = 20;int* sum = add(a, b);cout << sum << endl;return 0;
}
局部變量:
①:在函數內部創建的變量
②:其作用范圍僅在函數內部,函數一旦執行完畢就會將此變量銷毀并且將其占用的內存空間釋放
上述代碼想要正確表達出效果的解決方式:
#include"iostream"
using namespace std;int* add(int a, int b) {// 動態分配一塊空間給sum,這樣sum才能正確地傳遞出去int* sum = new int;*sum = a + b;return sum;
}int main() {int a = 10, b = 20;int* sum = add(a, b);cout << *sum << endl;// 使用完畢之后記得delete刪除動態分配出去的內存,減輕內存壓力delete sum;return 0;
}
14.static關鍵字
(1).static是C++中的一個關鍵字,并且在眾多的編程語言中都有static的應用
(2).static表示靜態(將內容存入靜態內存區域),可以修飾變量和函數
(3).當static修飾變量時,比如函數中的局部變量,可以將其的生命周期延長到整個程序的運行周期
函數內局部變量的特點: | 函數內static修飾的變量的特點: |
---|---|
函數運行結束后銷毀 | 程序運行結束后銷毀 |
static關鍵字的語法:
在變量類型之前加入static關鍵字即可
例如:
#include"iostream"
using namespace std;// 使用static關鍵字將sum變量由局部變量轉換為全局變量之后本函數依然能正常生效
int* add(int a, int b) {static int sum = a + b;return ∑
}
// 在本例中,sum變量將會獲得:
// ①:僅被初始化一次(在函數第一次調用時)
// ②:將持續存在(轉換為全局變量,不會因函數運行結束而銷毀)int main() {int a = 10, b = 20;int* sum = add(a, b);cout << *sum << endl;return 0;
}
15.函數返回數組
由于數組對象本身是第一個元素的地址,所以返回數組的本質就是返回指針
語法要求按照返回指針聲明返回值,例如:
變量類型* 函數名() {...;return 數組名;
}
其中需要注意的是:返回的數組不可以是局部變量(因為其生命周期僅限函數內),可以返回全局數組或者static修飾的數組
錯誤案例:
int* function() {int arr[] = {1, 2, 3};return arr;
}
// 這樣的方式就根本傳遞不出數組,因為該數組是在函數內部創建的,屬于局部變量,一旦函數運行完畢就銷毀
正確示范:
int* function() {static int arr[] = {1, 2, 3};return arr;
}
int* function() {int* arr = new int[3]{1, 2, 3};return arr;
}
int arr[3] = {1, 2, 3};
int* function() {return arr;
}
16.函數返回數組(改進)
前言:
一般不建議使用函數返回數組的方式
注意:
①:函數接受數組指針傳入
②:調用函數前需要自行創建數組
③:傳入數組到函數中進行操作
#include"iostream"
using namespace std;// 定義一個函數為數組中每個元素都+1,因為實際操作的是地址,所以能夠正常生效
void plus_one(int* arr, int length) {for (int i = 0; i < length; i++) {arr[i] += 1;}
}int main() {int arr[3] = {0, 1, 2};plus_one(arr, 3);for (int i = 0; i < 3; i++) {cout << arr[i] << endl;}return 0;
}
17.函數默認值
在定義函數的時候我們可以指定函數的默認值,也就是說,調用函數不傳入實參,則使用函數默認值進行代替
注意:
每個提供默認值的參數,其右側參數也必須提供默認值(當不傳遞參數時,使用提供的默認值)
使用默認值的函數的案例:
#include"iostream"
using namespace std;void love(const string& name = "beauty") {cout << "I love you, my " << name << endl;
}int main() {love();love("little girl");return 0;
}
錯誤寫法:
void add(int x = 3, int y, int z)
void add(int x, int y = 6, int z)
正確寫法:
void add(int x = 3, int y = 6, int z = 9)
void add(int x, int y = 6, int z = 9)
void add(int x, int y, int z = 9)