目錄
1.結構體的基本應用
2.結構體成員的默認值
3.結構體數組
4.結構體指針
->操作符
5.結構體指針數組
1.引入已存在的結構體數組地址
2.通過new操作符申請指針數組空間
6.函數的概念
7.函數的基礎語法
8.無返回值函數和void類型
9.空參函數
10.函數的嵌套調用
11.函數的嵌套調用練習題講解
12.參數的值傳遞和地址傳遞
13.函數綜合案例——黑馬ATM
14.函數傳入數組
15.函數傳入數組練習題講解——數組排序函數
16.引用的基本概念
區別引用和指針
引用語法:
17.引用傳參
18.函數返回指針及局部變量
如何規避?——動態內存管理
19.static關鍵字
使用static關鍵字還是動態內存分配?
20.函數返回數組
21.函數返回數組的改進
22.函數的默認值
23.【了解】手動編譯的流程和include指令
在clion集成開發環境中演示g++手動編譯流程:
1.新建一個文件夾“手動編譯演示”
2.切換成命令行模式
3.輸入預處理指令:
4.輸入編譯指令:
5.輸入匯編指令:
6.輸入鏈接指令:
簡易版手動編譯:
24.【了解】多文件函數編程
1.結構體的基本應用
結構體struct是一種用戶自定義的復合數據類型,可以包含不同類型的成員。例如:
struct Studet
{string name;int age;string gender;
}
結構體的聲明定義和使用的基本語法:
struct 結構體類型
{成員1類型 成員1名稱;···成員n類型 成員n名稱;
};
結構體定義和訪問成員練習:
#include <iostream>
using namespace std;int main() {struct Student // 自己創建的新數據類型{string name; // 成員1 表示姓名int age; // 成員2 表示年齡string gender; // 成員3 表示性別};// 使用該類型創建變量struct Student stu; // 結構體變量的聲明可以省略struct關鍵字,建議寫上這樣便于區分自定義結構體與其他類型// 結構體賦值stu = {"周杰", 11, "man"};// 輸出結構體// 錯誤寫法 cout << stu; 因為結構體變量是一個整體的包裝,無法直接cout輸出// 需要訪問每一個成員進行輸出cout << stu.name << endl;cout << stu.age << endl;cout << stu.gender << endl;// 聲明和賦值同步寫法struct Student stu2 = {"lin", 20, "woman"};cout << stu2.name << endl;cout << stu2.age << endl;cout << stu2.gender << endl;return 0;
}
2.結構體成員的默認值
設計結構體時可以給成員設置一個默認值,例如:
struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};struct Student s1 = {"周末輪"};// 不想使用默認值可以自己賦值struct Student s2 = {"林軍杰", "083082", 3};
代碼演示本節內容:
#include <iostream>
using namespace std;int main() {struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};struct Student s1 = {"周末輪"};// 不想使用默認值可以自己賦值struct Student s2 = {"林軍杰", "083082", 3};cout << "學生1姓名" << s1.name << endl;cout << "學生2姓名" << s2.name << endl;cout << "學生2專業代碼" << s2.major_code << endl;cout << "學生2宿舍號" << s2.dormitory_num << endl;return 0;
}
3.結構體數組
結構體支持數組模式。結構體數組的語法和普通的數組基本一樣,只不過需要把結構體類型代入進去:
// 聲明數組對象
[struct] 結構體類型 數組名[數組長度];
// 賦予數組的每一個元素
數組名[0]={,,,,,};
數組名[1]={,,,,,};
···// 聲明和賦值同步的快捷寫法
[struct] 結構體類型 數組名 [數組長度] = {{},{},···,{}};
本節內容的代碼演示:
#include <iostream>
using namespace std;int main() {struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};// 創建結構體數組Student arr[3]; //結構體數組對象聲明arr[0] = {"張三", "083032", 1};arr[1] = {"李四", "083032", 2};arr[2] = {"王五", "083032", 3};for (int i = 0; i < 3; i++) {cout << "當前下標:" << i << "姓名是:" << arr[i].name << endl; // 通過.訪問成員cout << "當前下標:" << i << "專業代碼是:" << arr[i].major_code << endl;cout << "當前下標:" << i << "宿舍號是:" << arr[i].dormitory_num << endl;}// 數組的聲明和賦值同步寫法Student students[2] = {{"張三", "083032", 1},{"李四", "234567", 0}};for (int i = 0; i < 2; i++) {cout << "結構體數組2的下標:" << i << "姓名是:" << students[i].name << endl;cout << "結構體數組2的下標:" << i << "專業代碼是:" << students[i].major_code << endl;cout << "結構體數組2的下標:" << i << "宿舍號是:" << students[i].dormitory_num << endl;}return 0;
}
4.結構體指針
結構體不僅支持數組,同樣支持指針。以下面的代碼為例,結構體類型的指針p指向結構體對象的內存地址。
struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};struct Student stu = {"張三", "003321", 5};
struct Student *p = &stu;
由于指針指向的地址是C++管理的,也就是靜態內存管理,所以指針無法被回收。如果想要回收空間,可以使用動態內存管理,通過new操作符申請一個指針/結構體空間:
struct Student *p = new Student{"張三", "446712", 7};
->操作符
使用指針變量訪問結構體成員需要更換操作符為:->
cout << p->name << endl;
cout << p->major_code << endl;
cout << p->dormitory_num << endl;
代碼演示本節知識點:
#include <iostream>
using namespace std;int main() {struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};// 先創建一個標準的結構體對象(靜態內存管理struct Student stu = {"jay", "443321", 2};// 創建結構體的指針,指向結構體對象的地址struct Student * p = &stu;// 通過結構體指針訪問結構體的成員cout << "結構體中成員的name: " << p->name << endl;cout << "結構體中成員的major_code: " << p->major_code << endl;cout << "結構體中成員的dormitory_num: " << p->dormitory_num << endl;// 這種寫法的指針無法回收內存空間// new操作符申請結構體指針struct Student *p2 = new Student{"jay", "443321", 2};cout << "結構體中成員的name: " << p2->name << endl;cout << "結構體中成員的major_code: " << p2->major_code << endl;cout << "結構體中成員的dormitory_num: " << p2->dormitory_num << endl;// 釋放delete p2;return 0;
}
5.結構體指針數組
結構體可以使用數組指針,主要用于動態內存分配,方便管理大量結構體占用的內存。
結構體指針數組分為2種使用方式:
1.引入已存在的結構體數組地址
struct Student arr[] = {{"張三"},{"李四"},{"王五","009988",2}};// 指向已存在數組地址struct Student *p = arr;// 數組的第一個元素是結構體對象(非指針)使用,訪問成員cout << p[0].name << endl;cout << p[1].name << endl;cout << p[2].name << endl;
這種方法相較第二種的使用更少,因為無法動態分配內存。
2.通過new操作符申請指針數組空間
struct Student *p2 = new Student[3] {{"張三"},{"李四"},{"王五"}};
代碼演示本節內容:
#include <iostream>
using namespace std;int main() {struct Student {string name;string major_code = "083032"; // 默認專業代碼int dormitory_num = 1; // 默認分配1號宿舍};struct Student arr[] = {{"張三"},{"李四"},{"王五","009988",2}};// 指向已存在數組地址struct Student *p = arr;// 數組的第一個元素是結構體對象(非指針)使用,訪問成員// 訪問結構體對象仍然用.cout << p[0].name << endl; // 指針和數組一樣可以使用下標,代表p指針所在的位置,0相當于沒有進行任何運算,以此類推cout << p[1].name << endl;cout << p[2].name << endl;// 通過new操作符申請指針數組空間,定義指針接收struct Student *p2 = new Student[3] {{"張三"},{"李四"},{"王五"}};cout << "數組二第一個元素中記錄的name是:" << p2[0].name << endl;cout << "數組二第二個元素中記錄的name是:" << p2[1].name << endl;cout << "數組二第三個元素中記錄的name是:" << p2[2].name << endl;// 釋放內存delete[] p2;return 0;
}
6.函數的概念
函數是一個提前封裝好的、可重復使用的、完成特定功能的獨立代碼單元。
函數的5個組成要素:返回值類型(int、bool等);函數名;參數聲明;函數體;返回值。
7.函數的基礎語法
返回值類型 函數名(參數1類型 參數1名稱, 參數2類型 參數2名稱···){函數體;
···return 返回值;
}
返回值類型聲明函數運行完成后提供的結果類型,與返回值相關。
返回值提供聲明的結果給調用者。
函數名稱即為函數名。
參數列表向函數提供待處理的數據。
函數體就是實現函數功能的代碼。
函數執行流程:
- 調用者通過函數名調用函數,通過參數列表傳輸數據
- 函數用返回值給調用者提供結果
本節內容代碼演示:
#include <iostream>
using namespace std;/** 需求:編寫一個函數,接收2個int數字傳入,返回最大的那一個**/// 自定義函數要寫在main函數外
int get_max(int a, int b) {}int main() {return 0;
}
8.無返回值函數和void類型
函數的返回值并非是必須提供的,即可以聲明函數不提供返回值。
void say_hello(string name){cout << name << "你好,我是黑馬程序員" << endl;
}
當函數不提供返回值時,需要:
- 聲明函數返回值類型為void(void表示空,用于聲明無返回值函數)
- 不需要寫return語句
- 調用者無法得到返回值
本節內容代碼演示:
#include <iostream>
using namespace std;void say_hello(string name){cout << name << "你好,我是黑馬程序員" << endl;
}int main() {say_hello("張三");say_hello("李四");return 0;
}
9.空參函數
函數的傳入參數和返回值一樣也是可選的,即聲明不需要參數(形參)的傳入(注意:()必須寫)。
void i_like_you()
{cout << "小美我喜歡你! << endl;
}
本節內容代碼演示:
#include <iostream>
using namespace std;void i_like_you() {for (int i = 0; i < 5; i ++){cout << "小美我喜歡你" << endl;
}
}int main() {i_like_you();return 0;
}
10.函數的嵌套調用
函數作為一個獨立的代碼單元,可以在函數內調用其他函數。這種嵌套調用關系沒有任何限制,可以根據需要無限嵌套。以下面的函數為例,在函數fuc_a中又調用了func_b和func_c函數:
int func_a(){code;int num = func_b();int result = num + func_c();code;return result;
}
本節內容代碼演示:
#include <iostream>
using namespace std;/** 喜歡小美,正在追求中,每天3種追求方案/;* 1.送早餐、送花、說喜歡* 2.送花、說喜歡、邀請一起看電影* 3. 邀請一起看電影、送花、說喜歡** 用函數的思想模擬這些動作。*/
void send_food() {cout << "小美,我給你買了早餐!" << endl;
}void send_flower() {cout << "小美,我給你買了玫瑰花,你真好看!" << endl;
}void say_love() {cout << "小美,我很喜歡你" << endl;
}void watch_ovie() {cout << "小美,我們一起看電影吧" << endl;
}
void i_like_you(int num) {switch (num) {case 1:send_food();send_flower();say_love();break;case 2:send_flower();say_love();watch_ovie();break;case 3:watch_ovie();send_flower();say_love();break;default:cout << "今天不追求小美了,去打球去" << endl;}
}int main() {cout << "今天天氣不錯,執行方案3追求小美" << endl;i_like_you(3);cout << "第二天天氣也不錯,執行方案2" << endl;i_like_you(2);return 0;}
11.函數的嵌套調用練習題講解
#include <iostream>
using namespace std;// 函數1接收2個int值傳入,返回最小值
int get_min(int a, int b) {return a < b ? a : b;
}// 函數2接收2個int值傳入,返回最大值
int get_max(int a, int b) {return a > b ? a : b;
}// 函數3接收2個int值傳入,返回一個結構體
// 結構體有2個成員,成員1最小值,成員2最大值
struct MinAndMax {int min;int max;
};
struct MinAndMax get_min_and_max(int a, int b) {// 函數3調用另外2個函數并將其包裝為結構體int min = get_min(a, b);int max = get_max(a, b);struct MinAndMax v = {min, max};return v;
}int main() {struct MinAndMax v = get_min_and_max(3, 4);cout << "結構體最小值:" << v.min << endl;cout << "結構體最大值:" << v.max << endl;return 0;}
12.參數的值傳遞和地址傳遞
之前學習的函數形參聲明使用“值傳遞”的參數傳入方式。例如下面代碼的輸出結果是x = 1
y = 2:
void switch_num(int a, int b) {int temp;temp = a;a = b;b = temp;
}int main() {int x = 1, y = 2;switch_num(x, y);cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;}
函數通過值傳遞接收參數時,系統會在內存棧中為該函數開辟一個獨立的棧幀,并將傳入的實參復制為局部變量。函數體中操作的只是這份副本,和主函數的變量沒有地址聯系。函數結束后棧幀銷毀,因此不會對原變量產生任何修改效果。
另一種不傳遞值而改用傳遞指針/地址的寫法如下,此時結果真正完成了交換:
#include <iostream>
using namespace std;void switch_num(int *a, int *b) {int temp;temp = *a;*a = *b;*b = temp;
}int main() {int x = 1, y = 2;switch_num(&x, &y);cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;}
本節內容代碼演示:
#include <iostream>
using namespace std;void switch_num(int a, int b) {int temp;temp = a;a = b;b = temp;
}void switch_num_pointer(int *a, int *b) {int temp;temp = *a;*a = *b;*b = temp;
}int main() {int a = 1, b = 2;switch_num(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;int x = 1, y = 2;switch_num_pointer(&x, &y);cout << "x = " << x << endl;cout << "y = " << y << endl;return 0;}
13.函數綜合案例——黑馬ATM
主菜單效果
查詢余額效果
存取款效果
需求:
- 需要記錄銀行卡余額(默認5000000)并記錄其變化
- 定義一個變量:name,用來記錄客戶姓名(啟動程序時輸入)
- 定義如下函數:
- 余額查詢函數
- 存款函數
- 取款函數
- 主菜單函數
- 要求:
- 程序啟動后要輸入客戶姓名
- 查詢余額、存款、取款后都會返回主菜單
- 存款、取款后,都應顯示一下當前余額
- 客戶選擇退出或輸入錯誤,程序會退出,否則一直運行
代碼:
#include <iostream>
using namespace std;/*
* 實現余額查詢函數、存款函數、取款函數、主菜單函數,共4個函數
*/// 1.查詢函數
void query_balance(const string * const name, int * const money) { // 限制money指針的指向,但是不限制值的修改// 當前案例參數可以使用值傳遞,但是性能略低,因為涉及到值的復制// 同時四個函數都需要使用到這個變量,所以使用引用傳遞去取同一塊內存區域更合理cout << "------------查詢余額------------" << endl;cout << *name << ",您好,您的余額剩余:" << *money << "元" << endl;
}// 2.存款函數
void deposit_money(const string * const name, int * const money, int num) {// 存款數額不需要使用引用傳遞,該變量為臨時變量,不需要傳遞cout << "------------存款------------" << endl;cout << *name << ",您好,您存入" << num << "元成功" << endl;// 余額發生變更*money += num;cout << *name << ",您好,您的余額剩余:" << *money << "元" << endl;
}// 3.取款函數
void withdraw_money(const string * const name, int * const money, int num) {cout << "------------取款------------" << endl;cout << *name << ",您好,您取出" << num << "元成功" << endl;// 余額發生變更*money -= num;cout << *name << ",您好,您的余額剩余:" << *money << "元" << endl;
}// 4.主菜單函數
int menu(const string * const name) { // name只查詢不修改,所以使用const修飾限定cout << "------------主菜單------------" << endl;cout << "您好,歡迎來到黑馬ATM,請選擇操作:" << endl;cout << "1.查詢余額" << endl;cout << "2.存款" << endl;cout << "3.取款" << endl;cout << "4.退出" << endl;int num;cout << "請輸入您的選擇:";cin >> num;return num;
}int main() {// 要求輸入用戶姓名string name;cout << "請輸入您的用戶名:";cin >> name;int * money = new int;* money = 500000; // 余額默認500000元// 保證執行完所有操作都返回主菜單,退出除外bool is_run = true;while (is_run) {// 顯示主菜單int select_num = menu(&name);// 根據選擇執行相應操作switch (select_num) {case 1:query_balance(&name, money);break;case 2:int num_for_deposit;cout << "請輸入存款金額:";cin >> num_for_deposit;deposit_money(&name, money, num_for_deposit);break;case 3:int num_for_withdraw;cout << "請輸入取款金額:";cin >> num_for_withdraw;withdraw_money(&name, money, num_for_withdraw);break;default:cout << "您選擇了退出,程序退出" << endl;is_run = false;}}return 0;
}
運行結果:
請輸入您的用戶名:may
------------主菜單------------
您好,歡迎來到黑馬ATM,請選擇操作:
1.查詢余額
2.存款
3.取款
4.退出
請輸入您的選擇:1
------------查詢余額------------
may,您好,您的余額剩余:500000元
------------主菜單------------
您好,歡迎來到黑馬ATM,請選擇操作:
1.查詢余額
2.存款
3.取款
4.退出
請輸入您的選擇:2
請輸入存款金額:300000
------------存款------------
may,您好,您存入300000元成功
may,您好,您的余額剩余:800000元
------------主菜單------------
您好,歡迎來到黑馬ATM,請選擇操作:
1.查詢余額
2.存款
3.取款
4.退出
請輸入您的選擇:4
您選擇了退出,程序退出
14.函數傳入數組
由于數組對象本身只是第一個元素的地址,所以數組傳參不區分傳值還是傳地址,其本質都是傳遞指針(地址) 。也就是說下面三種代碼雖然寫法不一樣,但是在本質上是一樣的。
void func1(int arr[]) {}void func2(int arr[10]) {}void func3(int * arr) {}
因此,如果在傳參中需要傳入數組,通常會附帶第二個參數,也就是數組的長度,因為c+不會檢查數組的內存邊界。本節內容代碼演示:
#include <iostream>
using namespace std;void func1(int arr[]) {cout << "函數內統計的數組總大小: " << sizeof(arr) << endl;}void func2(int arr[10]) {}
// 正常寫法
void func(int arr[], int length) {for (int i = 0; i < length; i++) {cout << arr[i] << endl;}}
void func3(int * arr) {}int main() {int arr[] = {1,2,3,4,5};cout << "在main函數內統計的數組總大小: " << sizeof(arr) << endl;func1(arr); // 輸出總是8,原因是sizeof(arr)計算的是指針的大小// 因此傳遞數組就相當于傳遞指針,此時無法用sizeof計算數組大小func(arr,5); // 輸出為10,因為sizeof(arr)計算的是數組的大小
}
15.函數傳入數組練習題講解——數組排序函數
思路:使用內外兩層循環,設置2個額外變量,記錄當前的最小值及其下標,用于每一次內層循環完畢后將最小值移動到當前外層循環的起始坐標。結束條件:外層循環至倒數第二個元素。代碼實現:
#include <iostream>
using namespace std;void sort_array(int *arr, int length) {int min,min_index;for (int i = 0; i < length - 1; i++) {for (int j = i; j < length; j++) {// 第一個元素直接放入min和記錄min_indexif (i == j) {min = arr[i];min_index = i;}// 非本次內循環第一個元素就要比較大小if (arr[j] < min) {min = arr[j];min_index = j;}}// 本次內循環完成后要進行一次轉換,將最小值與當前內循環起始位置元素交換if (min_index != i) {int temp = arr[i];arr[i] = min;arr[min_index] = temp;}}
}int main() {int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};sort_array(arr, 10);for (int i = 0; i < 10; i++) {cout << arr[i] << " ";}}
16.引用的基本概念
函數的參數傳遞除了值傳遞和地址傳遞外,還有引用傳遞。引用是變量的別名,對變量進行操作和對引用進行操作是一樣的。
區別引用和指針
引用并不是指針,引用不可修改、必須初始化、不能為空,而指針恰好相反。
對變量A創建一個引用,相當于給它起了一個別名B,B的內存區域就鎖定了。同時,因為不可修改,所以在創建時必須初始化。因為必須初始化,所以引用不可為空。
引用語法:
數據類型& 引用名 = 被引用變量;int a = 10;
int& b = a;double d1 = 11.11;
double& d2 = d1;
本節內容代碼演示:
#include <iostream>
using namespace std;int main() {int a = 20;int& b = a;cout << "a = " << a << endl;cout << "b = " << b << endl;b = 30;cout << "a = " << a << endl;cout << "b = " << b << endl;double c = 20.2;double& d = c;cout << "c = " << c << endl;cout << "d = " << d << endl;d = 30.1;cout << "c = " << c << endl;cout << "d = " << d << endl;return 0;}
17.引用傳參
引用傳參是最常見的傳參方式,下面是已經學過的三種傳參方式的總結:
- 值傳遞:會復制值,無法對實參本身產生影響。
- 地址傳遞:會復制地址,對實參本身可以產生影響,指針寫法較麻煩。
- 引用傳遞:既可以像普通變量一樣操作內容,又可以對參數本身產生影響。
引用傳遞寫法(接收引用對象):
void switch_num(int& a, int& b) {int temp = a;a = b;b = temp;
}
本節內容代碼演示(與指針傳參對比):
#include <iostream>
using namespace std;void switch_num1(int& a, int& b) {int temp = a;a = b;b = temp;
}
void switch_num2(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}
int main() {int a = 10, b = 20;switch_num1(a, b);cout << a << " " << b << endl;switch_num2(&a, &b);cout << a << " " << b << endl;return 0;}
18.函數返回指針及局部變量
函數的返回值可以是指針(內存地址),語法如下:
返回值類型 * 函數名(形參列表){函數體;return 指針;
}
實際代碼舉例:
int *add(int a, int b)
{int sum;sum = a + b;return ∑
}
注意,當前代碼的格式(語法)正確,但是無法正常使用,原因是sum在函數內聲明,屬于局部變量,作用范圍僅在函數內部,因此函數執行完成后就會銷毀sum的內存區域。
如何規避?——動態內存管理
代碼演示:
1.錯誤寫法(靜態分配內存),add函數執行完后就被回收了,因此返回result指針的地址是懸垂指針,不能安全使用。
#include <iostream>
using namespace std;// 返回指針的函數,就在函數返回值聲明和函數名之間加上*即可
int *add(int a, int b)
{int sum;sum = a + b;return ∑ // 直接return sum的話,本質返回的是一個int類型的變量,所以要加上取地址符&
}int main() {int *result = add(1, 2);cout << *result << endl;return 0;}
?執行結果:
進程已結束,退出代碼為 -1073741819 (0xC0000005)
2.正確在函數內返回指針的寫法,函數內動態分配內存+main函數調用函數執行完畢后手動銷毀:
#include <iostream>
using namespace std;// 返回指針的函數,就在函數返回值聲明和函數名之間加上*即可
int *add(int a, int b)
{int *sum = new int;*sum = a + b;return sum; // 直接return sum的話,本質返回的是一個int類型的變量,所以要加上取地址符&
}int main() {int *result = add(1, 2);cout << *result << endl;delete result;return 0;}
19.static關鍵字
static表示靜態(將內容存入靜態內存區域,后續學習),可以修飾變量和函數。
當static修飾變量,比如函數內的局部變量,可以延長其生命周期到整個程序運行周期。
語法(在變量類型前加入static關鍵字即可):
int *add(int a, int b)
{static int *sum = new int;*sum = a + b;return sum;
}
此時sum變量僅被初始化一次(在函數第一次調用時),并且持續存在(不因函數運行結束而銷毀),直至程序結束。
本節內容代碼演示:
#include <iostream>
using namespace std;int *add(int a, int b)
{static int sum ;sum = a + b;return ∑
}int main() {int *result = add(1, 2);cout << *result << endl;return 0;
}
static還可以修飾函數等,其余的作用會在后面講到。
使用static關鍵字還是動態內存分配?
分情況。如果變量是一個儲存幾萬個數據的數組,為了不占用內存建議手動分配內存并釋放;如果數據很小且內存充足可以使用static關鍵字。
20.函數返回數組
我們已經學過函數返回指針,由于數組對象本身是第一個數組元素的地址,所以返回數組本質上就是返回指針。
函數返回數組的語法要求按照返回指針的方式聲明返回值:
int * func()
{···return arr;
}
要注意,返回的數組不可是局部變量(生命周期僅限函數),可以返回全局數組或static修飾的數組。常見的三種返回方式:
int * func()
{static int arr[] = {1, 2, 3};return arr;
}int * func()
{int * arr = new int[3]{1, 2, 3};return arr;
}int arr[3] = {1, 2, 3};
int * func()
{···return arr;
}
本節內容代碼演示:
#include <iostream>
using namespace std;int * func1()
{static int arr[] = {1, 2, 3};return arr;
}int * func2()
{int * arr = new int[3]{1, 2, 3};return arr;
}// 可以用但不推薦,原因是該數組函數只使用一次但是設置成了全局變量,把局部邏輯依賴寫死到全局變量上
int arr[3] = {1, 2, 3};
int * func3()
{return arr;
}int main() {int * p1 = func1();for (int i = 0; i < 3; i++) {cout << p1[i] << ' ' ;}cout << endl;int * p2 = func2();for (int i = 0; i < 3; i++) {cout << p2[i] << ' ';}cout << endl;delete[] p2;int * p3 = func3();for (int i = 0; i < 3; i++) {cout << p3[i] << ' ';}cout << endl;return 0;
}
提示:盡管三種函數返回數組都能運行,但是不推薦使用這種行為,原因是如果忘記delete釋放內存十分危險,而static全局變量則要一直占用內存。因此,最推薦的方式是:在函數外部將數組搞定,然后通過傳參的形式把數組傳入函數(傳地址、傳引用)。
21.函數返回數組的改進
上節提到不推薦函數返回數組,如果真的需要在函數里面操作數組,推薦的操作步驟如下:
- 在函數聲明時接收數組傳入
- 調用函數前自行創建好數組
- 把數組的指針傳給函數
示例:
#include <iostream>
using namespace std;void plus_one_in_arr(int * arr, const int length) {for (int i = 0; i < length; i++) {arr[i] = i + 1;}
}int main() {int arr[10];plus_one_in_arr(arr, 10);for (int i = 0; i < 10; i++) {cout << arr[i] << endl;}return 0;
}
22.函數的默認值
函數在定義時可以指定默認值,也就是說調用函數時不傳入實參,使用函數默認值代替,例如:
#include <iostream>
using namespace std;void say_hello(const string msg="chloe") {cout << "Hi I am " << msg << endl;
}int main() {say_hello();say_hello("hana");return 0;
}
輸出:
Hi I am chloe
Hi I am hana
注意:一個常見的錯誤寫法是,提供默認值的參數右側的參數未提供默認值。例如:
void add(int x = 3, int y)
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)
本節內容代碼演示:
#include <iostream>
using namespace std;void say_hello(const string msg="chloe") {cout << "Hi I am " << msg << endl;
}int add(int x, int y, int z = 10) {return x + y + z;
}
int main() {say_hello();say_hello("hana");cout << "1 + 2 + 10 = " << add(1,2) << endl;cout << "1 + 2 + 3 = " << add(1,2,3) << endl;return 0;
}
23.【了解】手動編譯的流程和include指令
代碼經過編譯轉變成可執行程序exe文件,其中編譯又會經歷四個過程:
- 預處理:指的是將源代碼中的預處理指令,如宏展開(比如#define NUM 10)、條件編譯指令(比如#ifdef、#ifndef)、包含頭文件(比如#include<iostream>)等處理掉,預處理命令是 g++ -E xxx.cpp -o xxx.i
- 編譯:將預處理后的代碼轉換為匯編語言,編譯命令是 g++ -S xxx.i -o xxx.s
- 匯編:將匯編代碼轉換為二進制機器碼,匯編命令是 g++ -c xxx.s -o xxx.o
- 鏈接:將機器碼文件和所需的其他庫鏈接得到程序,編譯的命令是 g++ xxx.o -o xxx.exe
在clion集成開發環境中演示g++手動編譯流程:
1.新建一個文件夾“手動編譯演示”
2.切換成命令行模式
點擊左下方的終端圖標進入命令行,輸入“?cd .\xxx”(xxx是指當前cpp文件所在的文件夾),點擊回車就完成了切換目錄。
3.輸入預處理指令:
g++ -E .\手動演示.cpp -o 演示.i
可以看到當前目錄里生成了新的文件演示.i,文件長達3萬行。(補充:在源文件界面按住ctrl鍵就可以點進去查看iostream頭文件,可以看到該文件有80多行,但是包含其他的頭文件和宏展開、條件編譯處理如
?這些在預處理階段全部都要替換成對應的源代碼,從而生成一個中間文件(如 .i 文件),供編譯器后續分析和翻譯。
4.輸入編譯指令:
g++ -S .\演示.i -o 匯編.s
這一步將中間文件變成匯編文件,可以看到命令執行完畢后當前文件目錄下多出一個匯編.s的文件。
5.輸入匯編指令:
g++ -c .\匯編.s -o 機器碼.o
這一步將匯編文本轉變成(二進制)機器文件,可以看到命令執行完畢后當前文件目錄下多出一個機器碼.o文件。
注意:中間文件、匯編代碼和機器碼文件都不是標準程序!
6.輸入鏈接指令:
g++ .\機器碼.o -o 演示程序.exe
這是最后一把,完成這一步后,我們在命令行運行.exe文件:
.\演示程序.exe
就可以看到輸出:
至此,手動編譯就是成功了。
簡易版手動編譯:
有同學問,這種方法太繁瑣了記不住,并且我還不想用圖形化界面的三角執行鍵,有沒有不那么吃操作的方法呢?有的有的,我們可以在命令行輸入
g++ .\手動編譯演示.cpp -o 程序.exe
?就可以運行了:
甚至還有更簡單的,直接輸入
g++ 手動編譯演示.cpp
?(由于沒有設置輸出文件的名稱,得到了默認文件名a.exe),就可以在文件目錄下看到可執行文件了。
24.【了解】多文件函數編程
在開發大型C++項目時不會將全部代碼寫在一個文件中,我們在構架項目的時候可以將函數定義到其他文件中,以確保項目組織更加清晰。
在c++中一般有2類文件:
- .h文件(頭文件,c語言也有)
- .cpp文件(源代碼文件)
我們可以將
- 函數的聲明寫在.h文件中
- 函數的實現寫在同名的.cpp文件中
代碼演示:
創建一個新的文件夾起名為“多文件函數編程”,新建一個main.cpp文件,在該文件中按照以往的編程方式,先聲明2個函數,再在下面寫上它們的具體實現(類似變量的先聲明后實現):
#include <iostream>
using namespace std;
// 聲明
void add(int x, int y);
void add2(int x);// 實現
void add(int x, int y) {cout << x + y << endl;
}void add2(int x) {cout << x + 10 << endl;
}int main() {add(10, 20);add2(10);return 0;
}
然后新建一個my_func源文件,將函數聲明部分和實現部分移動到該文件(頭文件也要補充上),此時,缺少main文件和函數文件的鏈接,需要在main文件引入自定義庫函數:
#include <iostream>
#include "my_func.cpp"using namespace std;int main() {add(10, 20);add2(10);return 0;
}
接下來需要編譯,我們選擇使用命令行編譯,先使用cd命令切換到該文件目錄下,然后輸入
g++ .\main.cpp
就可以看到輸出結果:
30
20
到此為止就實現了多文件編程,但是不夠規范,沒有用到頭文件。
在當前文件夾下新建一個頭文件,起名為my_func(與my_func.cpp)同名,然后將聲明放進該頭文件中,my_func.cpp文件中只保留了實現:
//
// Created by Hippocrates on 25-5-25.
//#ifndef MY_FUNC_H
#define MY_FUNC_H// 聲明
void add(int x, int y);
void add2(int x);#endif //MY_FUNC_H
通過該頭文件,實現了函數的聲明和實現的分離,在以后的大型工作項目中便于管理和使用。
為了讓my_func.cpp文件和my_func.h文件聯系起來,在my_func.cpp文件中引入該頭文件:
#include "my_func.h"
再在命令行輸入編譯指令“g++ .\main.cpp"就可以了。
提示:如果將mian函數中的"#include "my_func.cpp" 改成"#include "my_func.h",直接輸入上面的編譯指令會報錯,因為my_func頭文件中沒有提供函數的實現,編譯器無法使用。正確的做法是將編譯指令改為"g++ .\main.cpp .\my_func.cpp",此時便可正常編譯了。