笔记
上一笔记接续(练习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_1cout<<"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;//调用交换函数1swap_1(num, key);cout<<"main::num = "<<num<<" key = "<<key<<endl; //520 1314//调用交换函数2swap_2(&num, &key);cout<<"main::num = "<<num<<" key = "<<key<<endl; //520 1314//调用交换函数3swap_3(&num, &key);cout<<"main::num = "<<num<<" key = "<<key<<endl; //1314 520//调用交换函数4swap_4(num, key);cout<<"main::num = "<<num<<" key = "<<key<<endl; //520 1314return 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; //520int &value = fun(); //此时的value和num是同一个变量cout<<"value = "<<value<<" &value = "<<&value<<endl; //520fun() = 1314; //引用函数的返回结果是一个左值cout<<"value = "<<value<<" &value = "<<&value<<endl; //1314int &ref = hun(); //在主程序中获取被调函数中开辟的堆区空间cout<<"ref = "<<ref<<endl; //520delete &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 = # //定义一个指针变量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; //20int 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; //8cout << sum(3.3,5.5) << endl; //8.8cout << sum("3","5") << endl; //35return 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; //6cout << sum(1,2) << endl; //103 //调用时出问题cout << sum(1) << endl; //201cout << sum() << endl; //300return 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; //6cout << sum(1,2) << endl; //103 //调用时出问题cout << sum(1) << endl; //201cout << sum() << endl; //300return 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;
}
思维导图