黑马程序员C++2024新版笔记 第4章 函数和结构体

news2025/5/26 1:34:27

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 返回值;
}

返回值类型声明函数运行完成后提供的结果类型,与返回值相关。

返回值提供声明的结果给调用者。

函数名称即为函数名。

参数列表向函数提供待处理的数据。

函数体就是实现函数功能的代码。

函数执行流程:

  1. 调用者通过函数名调用函数,通过参数列表传输数据
  2. 函数用返回值给调用者提供结果

本节内容代码演示:

#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;
}

当函数不提供返回值时,需要:

  1. 声明函数返回值类型为void(void表示空,用于声明无返回值函数)
  2. 不需要写return语句
  3. 调用者无法得到返回值

本节内容代码演示:

#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_index
            if (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.引用传参

引用传参是最常见的传参方式,下面是已经学过的三种传参方式的总结:

  1. 值传递:会复制值,无法对实参本身产生影响。
  2. 地址传递:会复制地址,对实参本身可以产生影响,指针写法较麻烦。
  3. 引用传递:既可以像普通变量一样操作内容,又可以对参数本身产生影响。

引用传递写法(接收引用对象):

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在函数内声明,属于局部变量,作用范围仅在函数内部,因此函数执行完成后就会销毁sum的内存区域。

如何规避?——动态内存管理

代码演示:

1.错误写法(静态分配内存),add函数执行完后就被回收了,因此返回result指针的地址是悬垂指针,不能安全使用。

#include <iostream>
using namespace std;

// 返回指针的函数,就在函数返回值声明和函数名之间加上*即可
int *add(int a, int b)
{
    int sum;
    sum = a + b;
    return &sum;    // 直接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 &sum;
}

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.函数返回数组的改进

上节提到不推荐函数返回数组,如果真的需要在函数里面操作数组,推荐的操作步骤如下:

  1. 在函数声明时接收数组传入
  2. 调用函数前自行创建好数组
  3. 把数组的指针传给函数

示例:

#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文件,其中编译又会经历四个过程:

  1. 预处理:指的是将源代码中的预处理指令,如宏展开(比如#define NUM 10)、条件编译指令(比如#ifdef、#ifndef)、包含头文件(比如#include<iostream>)等处理掉,预处理命令是 g++ -E xxx.cpp -o xxx.i
  2. 编译:将预处理后的代码转换为汇编语言,编译命令是 g++ -S xxx.i -o xxx.s
  3. 汇编:将汇编代码转换为二进制机器码,汇编命令是 g++ -c xxx.s -o xxx.o
  4. 链接:将机器码文件和所需的其他库链接得到程序,编译的命令是 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),就可以在文件目录下看到可执行文件了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据仓库,扫描量

有五种通用技术用于限制数据的扫描量&#xff0c;正如图3 - 4所示。第一种技术是扫描那些被打上时戳的数据。当一个应用对记录的最近一次变化或更改打上时戳时&#xff0c;数据仓库扫描就能够很有效地进行&#xff0c;因为日期不相符的数据就接触不到了。然而&#xff0c;目前的…

Vue3性能优化: 大规模列表渲染解决方案

# Vue3性能优化: 大规模列表渲染解决方案 一、背景与挑战 背景 在大规模应用中&#xff0c;Vue3的列表渲染性能一直是开发者关注的焦点。大规模列表渲染往往会导致卡顿、内存占用过高等问题&#xff0c;影响用户体验和系统整体性能。 挑战 渲染大规模列表时&#xff0c;DOM操作…

【RocketMQ 生产者和消费者】- 生产者启动源码 - MQClientInstance 定时任务(4)

文章目录 1. 前言2. startScheduledTask 启动定时任务2.1 fetchNameServerAddr 拉取名称服务地址2.2 updateTopicRouteInfoFromNameServer 更新 topic 路由信息2.2.1 topic 路由信息2.2.2 updateTopicRouteInfoFromNameServer 获取 topic2.2.3 updateTopicRouteInfoFromNameSer…

超全GPT-4o 风格提示词案例,持续更新中,附使用方式

本文汇集了各类4o风格提示词的精选案例&#xff0c;从基础指令到复杂任务&#xff0c;从创意写作到专业领域&#xff0c;为您提供全方位的参考和灵感。我们将持续更新这份案例集&#xff0c;确保您始终能够获取最新、最有效的提示词技巧。 让我们一起探索如何通过精心设计的提…

Android 自定义SnackBar和下滑取消

如何自定义SnackBar 首先我们得了解SnackBar的布局&#xff1a; 之前我看有一些方案是获取内部的contentLayout&#xff0c;然后做一些处理。但是现在已经行不通了&#xff1a; RestrictTo(LIBRARY_GROUP) public static final class SnackbarLayout extends BaseTransientB…

Netty学习专栏(三):Netty重要组件详解(Future、ByteBuf、Bootstrap)

文章目录 前言一、Future & Promise&#xff1a;异步编程的救星1.1 传统NIO的问题1.2 Netty的解决方案1.3 代码示例&#xff1a;链式异步操作 二、ByteBuf&#xff1a;重新定义数据缓冲区2.1 传统NIO ByteBuffer的缺陷2.2 Netty ByteBuf的解决方案2.3 代码示例&#xff1a;…

详解 C# 中基于发布-订阅模式的 Messenger 消息传递机制:Messenger.Default.Send/Register

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

多场景游戏AI新突破!Divide-Fuse-Conquer如何激发大模型“顿悟时刻“?

多场景游戏AI新突破&#xff01;Divide-Fuse-Conquer如何激发大模型"顿悟时刻"&#xff1f; 大语言模型在强化学习中偶现的"顿悟时刻"引人关注&#xff0c;但多场景游戏中训练不稳定、泛化能力差等问题亟待解决。Divide-Fuse-Conquer方法&#xff0c;通过…

Java 函数式接口(Functional Interface)

一、理论说明 1. 函数式接口的定义 Java 函数式接口是一种特殊的接口&#xff0c;它只包含一个抽象方法&#xff08;Single Abstract Method, SAM&#xff09;&#xff0c;但可以包含多个默认方法或静态方法。函数式接口是 Java 8 引入 Lambda 表达式的基础&#xff0c;通过函…

分布式锁总结

文章目录 分布式锁什么是分布式锁&#xff1f;分布式锁的实现方式基于数据库(mysql)实现基于缓存(redis)多实例并发访问问题演示项目代码(使用redis)配置nginx.confjmeter压测复现问题并发是1&#xff0c;即不产生并发问题并发30测试,产生并发问题(虽然单实例是synchronized&am…

使用MybatisPlus实现sql日志打印优化

背景&#xff1a; 在排查无忧行后台服务日志时&#xff0c;一个请求可能会包含多个执行的sql&#xff0c;经常会遇到SQL语句与对应参数不连续显示&#xff0c;或者参数较多需要逐个匹配的情况。这种情况下&#xff0c;如果需要还原完整SQL语句就会比较耗时。因此&#xff0c;我…

client.chat.completions.create方法参数详解

response client.chat.completions.create(model"gpt-3.5-turbo", # 必需参数messages[], # 必需参数temperature1.0, # 可选参数max_tokensNone, # 可选参数top_p1.0, # 可选参数frequency_penalty0.0, # 可选参数presenc…

深入浅出人工智能:机器学习、深度学习、强化学习原理详解与对比!

各位朋友&#xff0c;大家好&#xff01;今天咱们聊聊人工智能领域里最火的“三剑客”&#xff1a;机器学习 (Machine Learning)、深度学习 (Deep Learning) 和 强化学习 (Reinforcement Learning)。 听起来是不是有点高大上&#xff1f; 别怕&#xff0c;我保证把它们讲得明明…

基于 ColBERT 框架的后交互 (late interaction) 模型速递:Reason-ModernColBERT

一、Reason-ModernColBERT 模型概述 Reason-ModernColBERT 是一种基于 ColBERT 框架的后交互 (late interaction) 模型&#xff0c;专为信息检索任务中的推理密集型场景设计。该模型在 reasonir-hq 数据集上进行训练&#xff0c;于 BRIGHT 基准测试中取得了极具竞争力的性能表…

vector中reserve导致的析构函数问题

接上一节vector实现&#xff0c;解决杨辉三角问题时&#xff0c;我在最后调试的时候&#xff0c;发现return vv时&#xff0c;调用析构函数&#xff0c;到第四步时才析构含有14641的vector。我设置了一个全局变量i来记录。 初始为35&#xff1a; 当为39时&#xff0c;也就是第…

微软开源多智能体自定义自动化工作流系统:构建企业级AI驱动的智能引擎

微软近期推出了一款开源解决方案加速器——Multi-Agent Custom Automation Engine Solution Accelerator,这是一个基于AI多智能体协作的自动化工作流系统。该系统通过指挥多个智能体(Agent)协同完成复杂任务,显著提升企业在数据处理、业务流程管理等场景中的效率与准确性。…

安卓无障碍脚本开发全教程

文章目录 第一部分&#xff1a;无障碍服务基础1.1 无障碍服务概述核心功能&#xff1a; 1.2 基本原理与架构1.3 开发环境配置所需工具&#xff1a;关键依赖&#xff1a; 第二部分&#xff1a;创建基础无障碍服务2.1 服务声明配置2.2 服务配置文件关键属性说明&#xff1a; 2.3 …

SOC-ESP32S3部分:10-GPIO中断按键中断实现

飞书文档https://x509p6c8to.feishu.cn/wiki/W4Wlw45P2izk5PkfXEaceMAunKg 学习了GPIO输入和输出功能后&#xff0c;参考示例工程&#xff0c;我们再来看看GPIO中断&#xff0c;IO中断的配置分为三步 配置中断触发类型安装中断服务注册中断回调函数 ESP32-S3的所有通用GPIO…

战略-2.1 -战略分析(PEST/五力模型/成功关键因素)

战略分析路径&#xff0c;先宏观&#xff08;PEST&#xff09;、再产业&#xff08;产品生命周期、五力模型、成功关键因素&#xff09;、再竞争对手分析、最后企业内部分析。 本文介绍&#xff1a;PEST、产品生命周期、五力模型、成功关键因素、产业内的战略群组 一、宏观环境…

python第三方库安装错位

问题所在 今天在安装我的django库时&#xff0c;我的库安装到了python3.13版本。我本意是想安装到python3.11版本的。我的pycharm右下角也设置了python3.11 但是太可恶了&#xff0c;我在pycharm的项目终端执行安装命令的时候还是给我安装到了python3.13的位置。 解决方法 我…