当前位置: 首页 > article >正文

2025.1.8(c++对c语言的扩充——堆区空间,引用,函数)

笔记

上一笔记接续(练习2的答案)

练习:要求在堆区连续申请5个int的大小空间用于存储5名学生的成绩,分别完成空间的申请、成绩的录入、升序排序、成绩输出函数以及空间释放函数,并在主程序中完成测试

要求使用new和delete完成

头文件

#ifndef TEST_H
#define TEST_H

#include<iostream>
using namespace std;

//声明申请空间函数
int * apply(int size);

//声明录入成绩的函数
void input_score(int *arr, int size);

//声明输出成绩的函数
void output_score(int *arr, int size);

//声明排序函数
void sort_score(int *arr, int size);

//声明释放空间函数
void free_space(int *arr);




#endif // TEST_H

源文件

#include"test.h"
#include<algorithm>


//申请空间函数的定义
int *apply(int size)
{
    //在堆区申请空间
    int *arr = new int[size];
    if(NULL==arr)
    {
        cout<<"空间申请失败"<<endl;
        return NULL;
    }

    cout<<"空间申请成功"<<endl;
    return arr;
}

//录入成绩的函数
void input_score(int *arr, int size)
{
    for(int i=0; i<size; i++)
    {
        cout<<"请输入第"<<i+1<<"个学生的成绩:";
        cin >> arr[i];
    }

    cout<<"录入完毕"<<endl;
}


//输出成绩的函数
void output_score(int *arr, int size)
{
    cout<<"学生成绩分别是:";
    for(int i=0; i<size; i++)
    {
        cout<<arr[i]<<"\t";
    }
    cout<<endl;

}

//排序函数
void sort_score(int *arr, int size)
{

    for(int i=1; i<size; i++)
    {
        for(int j=0; j<size-i; j++)
        {
            if(arr[j]>arr[j+1])
            {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
    cout<<"排序成功"<<endl;
}

//释放空间函数的定义
void free_space(int *arr)
{
    if(NULL!=arr)
    {
        delete []arr;           //释放堆区内存空间
        arr = NULL;
    }
}

主程序(验证代码正确性)

#include <iostream>
#include"test.h"

using namespace std;

int main()
{
    //调用申请
    int *arr = apply(5);

    //录入学生信息
    input_score(arr, 5);

    //输出学生信息
    output_score(arr, 5);

    //排序
    sort_score(arr, 5);

    //输出排序后的结果
    output_score(arr, 5);

    //销毁空间
    free_space(arr);
    arr = NULL;

    cout << "Hello World!" << endl;
    return 0;
}

7.3 malloc\free与new\delete的区别

共同点:都能够手动完成堆区空间的申请和释放,返回的都是堆区空间的地址

差异点:

1.        malloc\fjree是库函数的调用            new\delete是关键字

2.        malloc申请空间时无法对其进行初始化,new申请空间时可以对其进行初始化

3.        malloc申请空间以字节为单位,new申请空间时以数据类型为单位

4.        malloc申请的空间默认返回结果是void * 类型,使用时需要强制转换,
           new申请空间默认为申请时的数据类型来返回对应的类型指针,(正常使用时)无需强制转换

5.         malloc申请空间时,不区分单个空间和连续空间,而new和delete区分

6.        new申请空间时,会自动调用类的构造函数,而malloc不会

7.        delete释放空间时,会自动调用类的解构函数,而free不会

八、引用(reference)

8.1 引用的引入

1> 在C语言中,向函数中传递数据的方式有两种,分别是值传递和地址传递。当实参传递的是变量的地址时,可能是值传递也可能是地址传递;当实参传递的是普通变量时,一定是值传递。

2> C++中引入的“引用”的概念,不用区分值和地址传递了,可以直接传递该变量本身。当进行引用传递时,无需在被调函数中创建新的变量或者指针作为载体。被调函数无需为实参分配任何空间。

8.2 引用的概念

1> 引用相当于给变量起个“别名”

2> 一个变量的引用和引用的目标使用的是同一个内存空间,就像 宋江和及时雨的关系

3> 引用的分类:左值引用和右值引用

4> 左值引用的定义格式:数据类型 &引用名 = 引用目标;

5> 右值引用的定义格式:数据类型 &&引用名 = 引用目标;

6> 总结 & 的使用方式

        1、两个&作为双目运算符,表示逻辑与

        2、一个&作为双目运算符,表示按位与

        3、一个&作为单目运算符,表示取得某个变量的地址

        4、定义引用时,一个&表示定义的是左值引用

        5、定义引用时,两个&表示定义的是右值引用

8.3 引用的注意事项

1> 引用的使用跟普通变量一样,直接使用即可

2> 引用在定义时,必须用目标对其进行初始化,否则报错

3> 引用与引用的目标是同一个内存空间,系统不会为引用单独分配内存空间

4> 引用和引用的目标一般要求必须是同一数据类型(继承时除外)

5> 引用一旦指定,后期就不能进行更改目标了

6> 一个目标,可以定义多个引用,多个引用和引用目标都是同一个内存空间

#include <iostream>

using namespace std;

int main()
{
    int num = 520;          //定义一个普通变量

    //定义一个引用,目标为num
    //int &ref_1 ;                     //定义引用时,必须使用目标为其初始化,否则报错
    int &ref_1 = num;           //从此,num就有了一个别名  ref_1

    cout<<"ref_1 = "<<ref_1<<endl;           //对数据具有读功能

    ref_1 = 1314;                     //对数据具有写功能
    cout<<"ref_1 = "<<ref_1 << "  num = "<< num <<endl;         //两个变量名都更改
    cout<<"&ref_1 = "<<&ref_1 << "  &num = "<< &num <<endl;       //两个变量名地址相同
    cout<<"sizeof(ref_1) = "<<sizeof(ref_1) << "  sizeof(num) = "<< sizeof(num) <<endl;    //所占内存地址大小相同
    cout<<"tipe id of ref_1 = "<<typeid (ref_1).name() << "   tipe id of num =  "<<typeid (num).name()<<endl; //类型相同

    //double &ref_2 = num;          //不同类型的引用不能进行绑定

    int value = 999;
    //将ref_1引用到value上
    ref_1 = value;               //使用value的值给ref_1(num)重新赋值
    cout<<"ref_1 = "<<ref_1 << "  num = "<< num << "    value = "<<value<<endl;
    cout<<"&ref_1 = "<<&ref_1 << "  &num = "<< &num << "    &value = "<<&value<<endl;

    //一个目标可以定义多个引用
    int &ref_2 = num;
    int &ref_3 = ref_1;
    cout<<"&ref_1 = "<<&ref_1 << "  &num = "<< &num << "    &ref_2 = "<<&ref_2<<"   &ref_3 = "<<&ref_3<<endl;

    return 0;
}

8.4 引用作为函数的形参(重点)

1> 引用作为函数的形参,就没有了值传递和地址传递的概念了,传递的就是实参本身

2> 案例

#include <iostream>

using namespace std;

//定义交换函数1
void swap_1(int num, int key)
{
    int temp = num;
    num = key;
    key = temp;

    cout<<"swap_1::num = "<<num<<"    key = "<<key<<endl;       //1314   520
}

//定义交换函数2
void swap_2(int *ptr, int *qtr)
{
    int *temp = ptr;
    ptr = qtr;
    qtr = temp;

    cout<<"swap_2::*ptr = "<<*ptr<<"    *qtr = "<<*qtr<<endl;     //1314   520
}

//定义交换函数3
void swap_3(int *ptr, int *qtr)
{
    int temp = *ptr;
    *ptr = *qtr;
    *qtr = temp;

    cout<<"swap_3::*ptr = "<<*ptr<<"    *qtr = "<<*qtr<<endl;     //1314   520
}

//定义交换函数4
void swap_4(int &n, int &k)
{
    int temp = n;
    n = k;
    k = temp;

    cout<<"swap_4::num = "<<n<<"    key = "<<k<<endl;       //520   1314
}


/***************************主程序********************/
int main()
{
    int num = 520;
    int key = 1314;

    //调用交换函数1
    swap_1(num, key);
    cout<<"main::num = "<<num<<"    key = "<<key<<endl;        //520  1314

    //调用交换函数2
    swap_2(&num, &key);
    cout<<"main::num = "<<num<<"    key = "<<key<<endl;        //520   1314

    //调用交换函数3
    swap_3(&num, &key);
    cout<<"main::num = "<<num<<"    key = "<<key<<endl;        //1314   520


    //调用交换函数4
    swap_4(num, key);
    cout<<"main::num = "<<num<<"    key = "<<key<<endl;          //520   1314
    

    return 0;
}

8.5 引用作为函数的返回值(重点) ---> 引用函数

1> 引用作为函数的返回值,返回的是一个左值

2> 要求只能是生命周期比较长的变量才能作为返回值结果

3> 生命比较长的成员

        1、全局变量

        2、静态局部变量

        3、堆区空间的内容

        4、主调函数以地址或者引用的形式传过来的变量

#include <iostream>

using namespace std;


//定义一个引用函数
int &fun()
{
    static int num = 520;
    cout<<"fun::&num = "<<&num<<endl;

    return num;         //不能返回局部变量的空间  可以返回静态局部变量的空间
}

//引用函数返回堆区空间的地址
int &hun()
{
    return *new int(520);      //在堆区空间申请一个int大小的空间并初始化为520,并将该空间返回
}


int main()
{

    int key = fun();            //此时的key和num是不同的变量
    cout<<"key = "<<key<<"    &key = "<<&key<<endl;             //520

    int &value = fun();         //此时的value和num是同一个变量
    cout<<"value = "<<value<<"    &value = "<<&value<<endl;      //520

    fun() = 1314;                 //引用函数的返回结果是一个左值
    cout<<"value = "<<value<<"    &value = "<<&value<<endl;      //1314

    
    int &ref = hun();              //在主程序中获取被调函数中开辟的堆区空间
    cout<<"ref = "<<ref<<endl;         //520
    delete &ref;                  //释放堆区空间

    return 0;
}

8.6 常引用

1> 对于变量而言,变量的内容既可读又可写

2> 但是,有时函数的形参是引用变量时,在函数体内仅仅只是为了读取数据中的内容,而不

        是为了更改形参

        此时,为了保护新参不被修改,我们可以加常属性 const

3> 引用和目标进行搭配使用

        1、普通引用 普通变量

        2、普通引用 常变量

        3、常引用 普通变量

        4、常引用 常变量

#include <iostream>

using namespace std;

int main()
{
    /**************普通引用   普通变量*******************/
    int num = 520;
    int &ref_1 = num;
    cout<<"num = "<<num<< "    ref_1 = "<<ref_1<<endl;       //对空间都有读属性
    num = 1314;             //普通变量对空间具有写属性
    ref_1 = 999;             //普通引用对空间也具有写属性
    
    
    /****************普通引用   常变量********************/
    const  int value = 999;        //定义一个常变量
    //int &ref_2 = value;            //普通引用不能绑定常变量
    
    /***************常引用    普通变量**********************/
    int temp = 1111;           //定义普通变量
    const int &ref_3 = temp;         //常引用的目标为普通变量
    cout<<"temp = "<<temp<< "    ref_3 = "<<ref_3<<endl;       //对空间都有读属性
    temp = 222;            //普通变量具有写属性
    //ref_3 = 777;             //常引用没有写属性
    
    /******************常引用     常变量******************/
    const int key = 444;              //定义常变量
    const int &ref_4 = key;          //定义常引用
    cout<<"key = "<<key<< "    ref_4 = "<<ref_4<<endl;       //对空间都有读属性
    //key = 666;            //没有写属性
    //ref_4 = 777;         //没有写属性
    
    
    return 0;
}

8.7 引用与指针的关系

1> 指针变量也是有8字节的内存空间,既然有内存空间,就可以给该内存空间起个别名

2> 指针引用的定义格式: 数据类型 * & = 引用目标;

3> 指针引用定义后,使用方式跟原指针一致

#include <iostream>

using namespace std;

int main()
{
    int num = 520;        //定义普通变量
    int *ptr = &num;        //定义一个指针变量

    int * & ptr_ref = ptr;            //定义了一个指针的引用,后期 ptr_ref就可以跟ptr一样被使用

    cout<<"*ptr = "<<*ptr<<"    *ptr_ref = "<<*ptr_ref<<endl;     //对数据具有读功能
    cout<<"&ptr = "<<&ptr<<"    &ptr_ref = "<<&ptr_ref<<endl;      //地址一致
    cout<<"ptr = "<<ptr<<"    ptr_ref = "<<ptr_ref<<"   &num = "<<&num<<endl;      //内容一致
    
    int key = 1314;
    ptr_ref = &key;            //ptr = &key;     //让指针变量重新指向新的空间

    return 0;
}

8.8 引用与数组的关系

1> C语言中,没有数组的引用,当向一个函数传递数组时,本质上使用的是指针接收的参数

2> C++中支持数组引用,表示给数组起个别名

3> 数组引用定义格式:数据类型 (&引用名)[数组长度] = 其他数组名;

#include <iostream>

using namespace std;

//定义功能函数
void fun(int arr[], int n)          //arr接受的是主调函数中数组的起始地址,并不是数组本身
{
    cout<<"fun::sizeof(arr) = "<<sizeof(arr)<<endl;       //?
    for(int i=1; i<n; i++)
    {
        for(int j=0; j<n-i; j++)
        {
            if(arr[j]>arr[j+1])
            {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

//定义功能函数
void hun(int (&arr)[5])              //该函数中的arr接受的就是主调函数中的数组本身
{
    cout<<"fun::sizeof(arr) = "<<sizeof(arr)<<endl;       //20

    int n = sizeof(arr)/sizeof (arr[0]);      //求数组长度

    for(int i=1; i<n; i++)
    {
        for(int j=0; j<n-i; j++)
        {
            if(arr[j]>arr[j+1])
            {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}


int main()
{
    int arr[] = {3,8,3,2,4};
    int len = sizeof(arr)/sizeof (arr[0]);

    //fun(arr, len);
    hun(arr);

    return 0;
}

8.9 右值引用(了解)

1> 右值引用的引入目的:为了解决左值引用的瑕疵问题

2> 左值引用只能引用左值目标,右值引用只能引用右值目标

3> 右值引用定义格式:数据类型 &&引用名 = 右值目标;

#include <iostream>

using namespace std;

//定义求最值函数
int my_max(int &m, int &n)
{
    return m>n?m:n;
}


int my_max(int &&m, int &&n)
{
    return m>n?m:n;
}

int main()
{
    int num = 5;
    int key = 3;
    int res = my_max(num,key);
    
    my_max(100,200);              //不能传递,原因是形参是左值引用,左值引用只能引用左值
    
    
    int &&ref_1 = 520;          //右值引用可以引用常量、临时值、将亡值
    //int &&ref_2 = num;            //右值引用只能引用右值,不能引用左值
    int &&ref_3 = move(num);       //使用move函数,将左值移动成右值
    
    return 0;
}

8.10 引用与指针的区别(重点)

1> 可以有指针数组,但是没有引用数组

2> 引用必须初始化,但是指针可以不用初始化

3> 指针拥有独立的内存空间,而引用没有,引用与其目标共用同一块内存

4> 指针可以改变指向,但是引用一旦指定目标,后期不能更改

5> 引用不能为空,可以有空指针

6> 指针进行算术运算时,是对地址空间进行偏移,而引用进行算术运算时,就是目标进行的算术运算

7> 指针有二级指针,但是没有二级引用

8> 指针的目标必须是左值的地址,而引用可以有左值引用也可以有右值引用

9> 有万能指针,但是没有万能引用

九、C++对C语言的函数的扩充

9.1 函数重载(overload)

1> 引入目的:程序员在定义函数时,有时仅仅是因为函数的参数不同,导致同一功能的函数

        需要定义多个。例如,求两个整数的和、两个小数的和、两个字符串的和等等,函数内

        部实现逻辑都一样,仅仅只是因为函数参数类型不同,导致需要定义多个函数

2> C++中引入函数重载的概念,表示能够在同一个作用域下定义多个同名的函数

3> 要求:

        1、作用域相同

        2、函数名相同

        3、形参列表必须不同(参数个数、参数类型)

        4、跟返回值没有关系

4> 当调用函数时,系统会根据实参的类型和个数,自动匹配相关函数进行调用

#include <iostream>

using namespace std;

 int sum(int m, int n)      //定义求两个整数的和
 {
     return m+n;
 }

 //求两个小数的和
 double sum(double m, double n)
 {
     return m+n;
 }
 
 
 float sum(float m, float n)
 {
     return m+n;
 }

 //求两个字符串的和
 string sum(string m, string n)
 {
     return m+n;
 }



int main()
{
    cout << sum(3,5) << endl;          //8
    cout << sum(3.3,5.5) << endl;      //8.8
    cout << sum("3","5") << endl;      //35

    return 0;
}

练习:定义两个整数求最大值、两个小数求最大值、两个字符串求最大值函数,并完成相关的测试

9.2 默认参数

1> 引入背景:

        程序员在定义函数时,有某个参数或者某几个参数,可以由主调函数传递,也可以不需

        要主调函数传递时,此时就可以定义默认参数,对于设置了默认参数的形参变量,如果

        主调函数传递该数据,那么就使用主调函数中传递的数据,如果主调函数不传递数据,

        那么就使用默认提供的参数

2> 设置格式:返回值类型 函数名 (参数1, 参数2=初始值, 参数3=初始值)、

3> 函数形参的默认参数的设置要求:靠右原则,只有某个参数的右侧的所有的形参都设置了

        初始值,当前这个形参才能设置

        因为函数的实参向形参传递的方向是靠左原则

4> 当包含默认参数的函数和函数重载同时出现时,应避免重复性定义

5> 当分文件定义时,默认参数的设置需要写在声明部分,定义部分就不需要写了

#include <iostream>

using namespace std;

int sum(int  = 100, int = 100, int  = 100);       //函数声明

//这个函数与上个函数重载,定义的使用没有问题
/*
int sum(int num, int key)
{
    return num+key;
}*/





int main()
{
    cout << sum(1,2,3) << endl;      //6
    cout << sum(1,2) << endl;        //103       //调用时出问题
    cout << sum(1) << endl;           //201
    cout << sum() << endl;            //300

    return 0;
}



//定义三个数求和函数
int sum(int m, int n, int k)
{
    return  m+n+k;
}

9.3 内联函数

1> C++支持内联函数:设置了内联函数的函数,会建议编译器在编译时将函数体展开

        由于是在编译阶段,在被调函数处展开,那么在运行时,就无需再为该函数分配内存空

        间了      能够大大提高运行效率

2> 定义格式:在定义函数前加关键字 inline

3> 要求:

        1、函数体较小,否则会造成主程序膨胀

        2、调用比较频繁

        3、递归函数不允许设置成内联函数

        4> 有时,即使设置了内联函数,也不一定会在编译时展开

#include <iostream>

using namespace std;

//该函数就是内联函数
inline int sum(int  = 100, int = 100, int  = 100);       //函数声明

//这个函数与上个函数重载,定义的使用没有问题
/*
int sum(int num, int key)
{
    return num+key;
}*/



int main()
{
    cout << sum(1,2,3) << endl;      //6
    cout << sum(1,2) << endl;        //103       //调用时出问题
    cout << sum(1) << endl;           //201
    cout << sum() << endl;            //300

    return 0;
}



//定义三个数求和函数
inline int sum(int m, int n, int k)
{
    return  m+n+k;
}

9.4 哑元

1> 引入背景:程序员在定义函数时,有时某个形参或者某几个形参,在函数体内没有实质性的作用,但是,也还需要一个参数进行占位,此时就可以定义该参数为哑元。

2> 定义格式:定义形参时,只给类型,不给形参名,函数体内也不用

3> 使用场景:

1、程序优化:原本程序调用某个函数需要5个参数,但是,发布运行一段时间后,由于技术的革新,导致该函数只需要3个参数就能正常运行,但是,由于该函数在整个程序很多地方都已经被调用了,如果直接改变该函数的参数个数,那么需要将每个调用函数处都进行修改,非常不方便,此时,就可以使用哑元,仅仅只是占位作用

2、在进行自增或自减运算符重载时,使用哑元用于区分是前置还是后置(后期讲)

#include <iostream>

using namespace std;

//此时第二个和第三个参数就是哑元,唯一的作用就是占位作用
int sum(int m, int , int , int g)
{
    return  m+g;
}



int main()
{
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    cout << sum(2,3,4,5) << endl;
    
    return 0;
}

 思维导图


http://www.kler.cn/a/473387.html

相关文章:

  • 在 macOS 中,设置自动将文件夹排在最前
  • SQL Server中可以通过扩展事件来自动抓取阻塞
  • KCP解读:C#库类图
  • C 语言奇幻之旅 - 第16篇:C 语言项目实战
  • LangChain速成课程_构建基于OpenAI_LLM的应用
  • unity学习14:unity里的C#脚本的几个基本生命周期方法, 脚本次序order等
  • Mysql面试相关
  • 使用 vue3 赋值后视图没变化的问题
  • 蓝桥杯训练
  • T-SQL语言的语法
  • 使用 SQLite3 的基本操作步骤
  • Azkaban其一,介绍、体系架构和安装
  • Linux-----结构体与联合体,大小端模式
  • 高等数学学习笔记 ☞ 函数的求导法则
  • Maven核心与单元测试
  • Linux-Ubuntu之I2C通信
  • iOS 逆向学习 - iOS Architecture Media Layer
  • Ubuntu 上安装 Docker
  • Kotlin OpenCV 画画
  • QPS和TPS 的区别是什么?QPS 大了会有什么问题,怎么解决?
  • Java基础概念
  • EasyExcel上传校验文件错误信息放到文件里以Base64 返回给前端
  • springboot + vue+elementUI图片上传流程
  • TypeScript语言的数据库交互
  • 【JavaEE进阶】获取Cookie/Session
  • OpenCV相机标定与3D重建(48)对三台相机进行极线校正(rectification)函数rectify3Collinear()的使用