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),就可以在文件目录下看到可执行文件了。