今天要学三个知识点:引用、内存分配及重載
一、引用 &
引用是C++对C的一个非常重要的扩充,引用的引入,重要的作用是用于参数和返回值,无需再考虑值传递地址传递的问题了
1.1 引用的概念
所谓引用,其实相当于给变量起个别名,但是,不会给引用分配内存空间:例如, 宋江的别名 及时雨
1.2 引用的定义
1> 定义格式:数据类型 &引用名 = 引用的目标;
例如:int num = 520; int &ref= num;
2> 引用的注意事项
1、定义引用时,必须给引用进行初始化,否则会报错
2、系统不会给引用重新分配内存空间,引用与引用的目标是同一内存空间
3、引用的目标一旦确定,后期不能进行修改
4、一个目标可以有多个引用,多个引用都是一个内存空间
5、定义引用时,需要使用&来说明身份,但是使用引用时,跟使用目标用法一样
3> 总结&的用途
1、&后面跟变量名,表明取得该变量的地址
2、作为双目运算符,一个&表示按位与运算
3、作为双目运算符,两个&&表示逻辑与运算
4、定义引用时, 表明身份的象征,一个&表明是左值引用,两个&&表明是右值引用
5、对于一个&使用时,如果左侧有数据类型,表明正在定义引用,如果左侧没有数据类型,表明是取地址运算符
1.3 引用的基本使用
#include <iostream>
using namespace std;
int main()
{
int num = 520; //定义一个变量
//定义一个引用,其目标为num
int &ref = num; //定义引用时必须初始化,否则报错
cout<<"num = "<<num<<" ref = "<<ref<<endl; //值相同
cout<<"&num = "<<&num<<" &ref = "<<&ref<<endl; //地址相同
cout<<"size of num = "<<sizeof(num)<<" size of ref = "<<sizeof(ref)<<endl; // 所占内存大小一致
cout<<"type of num = "<<typeid(num).name()<<" type of ref = "<<typeid (ref).name()<<endl; //数据类型一致
ref = 1314;
cout<<"num = "<<num<<" ref = "<<ref<<endl; //连个名字一块变
//再定义一个新的变量
int key = 999;
ref = key; //这是给ref的空间重新赋值,而不是给引用重新设置目标
cout<<"&num = "<<&num<<" &ref = "<<&ref<<" &key = "<< &key <<endl;
//再定义一个引用,目标为num
int &ref1 = num;
cout<<"&num = "<<&num<<" &ref = "<<&ref<<" &ref1 = "<< &ref1<<endl;
//再定义一个引用,目标为ref
int &ref2 = ref; //没有多级引用,引用的引用也是一级引用,相当于给目标设置引用
cout<<"&num = "<<&num<<" &ref = "<<&ref<<" &ref1 = "<< &ref1<<" &ref2 = "<< &ref2<<endl;
return 0;
}
1.4 引用作为函数参数
1> 引用作为函数参数,传递的是实参本身,本质上是地址传递
2> 相比于普通变量和指针做形参,传递效率更高
#include <iostream>
using namespace std;
//定义值传递函数
void swap1(int a, int b)
{
//交换三部曲
int temp = a;
a = b;
b = temp;
cout<<"swap1:: a = "<<a<<" b = "<<b<<endl;
}
//定义swap2函数
void swap2(int *p, int *q)
{
int *temp = p;
p = q;
q = temp;
cout<<"swap1:: *p = "<<*p<<" *q = "<<*q<<endl;
}
//定义swap3函数
void swap3(int *p, int *q)
{
int temp = *p;
*p = *q;
*q = temp;
cout<<"swap1:: *p = "<<*p<<" *q = "<<*q<<endl;
}
//定义引用传递函数
void swap4(int &a, int &b)
{
//交换三部曲
int temp = a;
a = b;
b = temp;
cout<<"swap4:: a = "<<a<<" b = "<<b<<endl; //520 1314
}
int main()
{
int num = 520;
int key = 1314;
//1、值传递
swap1(num, key);
cout<<"调用swap1后:num ="<<num<<" key = "<<key<<endl; //520 1314
//2、传递地址的值传递
swap2(&num, &key);
cout<<"调用swap2后:num ="<<num<<" key = "<<key<<endl; //520 1415
//3、 地址传递
swap3(&num, &key);
cout<<"调用swap3后:num ="<<num<<" key = "<<key<<endl; //1314 520
//4、引用传递
swap4(num, key);
cout<<"调用swap4后:num ="<<num<<" key = "<<key<<endl; //520 1314
return 0;
}
1.5 引用作为函数的返回值
1> 引用作为函数的返回值是一个左值
2> 引用作为函数的返回值返回生命周期比较长的变量
1、静态局部变量
2、全局变量
3、堆区申请的空间内容
4、主调函数中通过地址传递进来的参数的内容
#include <iostream>
using namespace std;
//定义一个普通函数,普通函数的返回值是一个右值,只可读,不可写
int fun1()
{
int num = 520;
return num;
}
//定义指针函数
int *fun2()
{
static int num = 666;
return #
}
//定义引用函数
int &fun3()
{
static int num = 1314;
return num; //返回堆区空间的引用
}
int main()
{
cout<<"fun1() = "<<fun1()<<endl; //520
//fun1() = 1314; //普通函数的返回值是一个右值
cout<<"*fun2() = "<<*fun2()<<endl; //666
*fun2() = 999; //指针函数的返回值是一个左值
cout<<"*fun2() = "<<*fun2()<<endl; //999
//定义引用接受函数返回的结果
int &ref = fun3();
cout<<"fun3() = "<<ref<<endl; //1314
fun3() = 555; //引用函数的返回值是一个左值
cout<<"ref = "<<ref<<endl;
return 0;
}
1.6 常引用
1> 常引用引用的目标可以是非常变量,本质上包含目标不被修改
2> 不可以通过常引用更改目标的值,但是可以通过目标自身进行更改
3> 常引用常用来修饰参数和返回值,表示包含参数或函数返回值不被修改
4> 常引用可以引用右值
#include <iostream>
using namespace std;
//定义加法函数,形参加上const保护形参数据不在函数体内被修改
int Add(const int &m,const int &n)
{
// m = 0;
// n = 0;
return m+n;
}
int main()
{
int num = 520;
//定义一个常引用,目标为非常变量
const int &ref = num;
cout<<"ref = "<<ref<<endl; //可以对数据进行读操作
//ref = 1314; //不能通过常引用对目标进行更改
num = 1314;
cout<<"ref = "<<ref<<endl; //1314
// int a = 3;
// int b = 5;
cout<<Add(3,5)<<endl;
//常引用引用右值的案例1
const int &r = 1314 + 100;
//常引用引用右值的案例2
const double &r1 = (double)num; //const修饰的引用,可以引用临时值
const int &r2 = Add(3,4); //const修饰的引用,可以引用普通函数的返回值
//double d = (double)num;
return 0;
}
1.7 右值引用(了解)
1> 定义格式:数据类型 &&引用名 = 引用目标;
2> 左值引用的目标必须是一个左值,右值引用的目标必须是一个右值
3> 可以使用move函数进行将左值转换成右值
#include <iostream>
using namespace std;
int main()
{
int num = 520;
int &r1 = num; //左值引用,能引用左值
//int &r2 = 520; //左值引用不能引用右值
int &&r3 = 1314; //右值引用可以引用右值
//int &&r4 = num; //右值引用不能引用左值
int &r5 = r3; //右值引用本身是一个左值
int &&r6 = move(num); //move函数的功能是将一个值转换成右值
return 0;
}
1.8 引用作为结构体的成员
如果结构体中有引用成员,那么对该成必须进行初始化工作
#include <iostream>
using namespace std;
//声明一个结构体类型
struct Stu
{
string name;
int age;
double &score; //引用成员
};
int main()
{
double s = 90;
struct Stu s1 = {"zhangpp", 18, s}; //其他成员都可以不进行初始化,但是引用成员必须初始化
cout<<"score = "<<s1.score<<endl; //90
return 0;
}
1.9 指针与引用的区别
1> 指针记录的是变量的地址,而引用变量本身
2> 定义引用时必须初始化,而定义指针不是必须初始化
3> 指针需要分配8字节的内存空间,而引用与目标是同一内存空间,无需额外分配
4> 指针可以有多级指针,但是引用只有一级引用
5> 指针后期可以更改指向,而引用一旦绑定后期就不能进行更改目标了
6> const修饰指针时,有修饰指向和值,而const修饰引用时,只有修饰值
7> 指针使用时,需要使用取值运算符进行解引用,而引用使用时跟目标的使用方式一致
8> 没有引用数组,但是有数组引用
#include <iostream>
using namespace std;
//函数形参使用数组接受,本质上是指针接收
void fun(int brr[], int n)
{
cout<<"sizeof brr = "<<sizeof(brr)<<endl; //8
cout<<"sizeof brr = "<<sizeof (brr[0])*n<<endl; //得到传入的数组大小 12
cout<<"&brr = "<<&brr<<endl;
}
//函数形参使用数组引用接受数组,接受的就是数组本身
void gun(int (&ref)[3])
{
cout<<"sizeof ref = "<<sizeof(ref)<<endl; //12
cout<<"&ref = "<<&ref<<endl; //
}
int main()
{
int arr[3] = {1,2,3};
fun(arr,3);
cout<<"&arr = "<<&arr<<endl;
cout<<"sizeof arr = "<<sizeof (arr)<<endl;
gun(arr); //调用函数时,传递数组即可
return 0;
}
二、C++中的动态内存分配和回收
1> C++也支持使用malloc、free来完成对堆区空间的申请和释放工作,该工作适用于对基本数据类型、结构体变量空间申请
2> C++是面向对象的编程,对于对象的空间申请有专门的关键字来完成,new和delete完成对象空间的申请和释放
2.1 单个内存的申请和释放
1> 申请格式:数据类型 *指针名 = new 数据类型;
例如:int *p1 = new int; //在堆区申请一个int大小空间的内存
2> 释放格式:delete 指针名;
例如:delete p1;
#include <iostream>
using namespace std;
int main()
{
int *p1 = new int; //在堆区申请一个int单位的空间
cout<<"*p1 = "<<*p1<<endl; //随机值
*p1 = 520; //使用堆区内存空间
cout<<"*p1 = "<<*p1<<endl; //520
//在堆区申请空间后,给初始值
int *p2 = new int(1314); //在堆区申请一个int单位的内存,并给定初始值为1314
cout<<"*p2 = "<<*p2<<endl; //1314
//是否内存空间
delete p1;
p1 = nullptr; //(void *)0
delete p2;
p2 = nullptr;
return 0;
}
2.2 连续内存空间的申请和释放
1> 申请格式:数据类型 *指针名 = new 数据类型[个数];
例如:int *p1 = new int[5]; //在堆区申请5个int大小的恐惧
2> 释放格式:delete [] 指针名;
例如:delete []p1;
#include <iostream>
using namespace std;
int main()
{
//在 堆区申请空间,不进行初始化工作
int *p1 = new int[5]; //在堆区申请5个连续的int大小的空间
for(int i=0; i<5; i++)
{
cout<<p1[i]<<" ";
}
cout<<endl;
//使用堆区空间
for(int i=0; i<5; i++)
{
p1[i] = 10+i;
}
for(int i=0; i<5; i++)
{
cout<<p1[i]<<" ";
}
cout<<endl;
//在堆区申请空间并初始化
int *p2 = new int[5]{1,2,3,4,5};
for(int i=0; i<5; i++)
{
cout<<p2[i]<<" ";
}
cout<<endl;
//释放内存空间
delete []p1;
p1 = nullptr;
delete [] p2;
p2 = nullptr;
return 0;
}
练习:在堆区申请一个长度为5的数组,用于存放5名学生的成绩,自己封装函数完成,对5名学生成绩的录入、升序排序、输出
要求:使用new和delete完成
#include <iostream>
#include <istream>
using namespace std;
int main()
{
int *p3=new int[5]; //在堆区申请空间
//输入5名学生成绩
for(int j=0;j<5;j++)
{
cin>>p3[j];
}
//输出
for(int i=0;i<5;i++)
{
cout<<p3[i]<<" "<<endl;
}
//排序
for(int k=1;k<5;k++)
{
for(int l=0;l<5-k;l++)
{
if(p3[l]>p3[l+1])
{
int temp=p3[l];
p3[l]=p3[l+1];
p3[l+1]=temp;
}
}
}
//输出
for(int o=0;o<5;o++)
{
cout<<"p3[o]="<<p3[o]<<endl;
}
//释放内存空间
delete [] p3;
p3 = nullptr;
return 0;
}
2.3 new\delete与malloc\free的区别(笔试面试题)
1> new申请空间时,可以给堆区空间进行初始化,而malloc申请时不能进行初始化
2> new\delete是关键字,而malloc\free是库函数
3> new申请空间时以数据类型为单位,而malloc申请空间时以字节为单位
4> new申请的空间返回的结果申请类型的指针,而malloc申请空间时返回void*类型,需要进行强转后使用
5> new申请空间时会调用构造函数,malloc不会(后期讲)
6> delete释放空间时,会调用析构函数,free不会(后期讲)
7> new、delete申请释放空间时,区分单个还是连续空间,而malloc不区分
三、C++对C的函数部分的扩充
3.1 函数重载
1> 在C语言中,同一作用域下不允许定义多个同名的函数,对于功能类似,但是只有数据类型不同的函数,也要定义多个不同名的函数,调用起来比较麻烦
2> C++中支持函数重载,即:在同一作用域下,可以定义多个同名的函数,但是要求参数列表必须不同
3> 所谓函数重载,是静态多态的一种,能够做到“一名多用”
4> 函数重载的要求:
1、函数名相同
2、形参列表必须不同:可以是参数个数不同、参数类型不同
3、作用域也要相同
4、跟返回值没有关系
5> 调用:当调用函数时,系统会根据传递的实参类型,自动匹配相应的重载函数
#include <iostream>
using namespace std;
//定义函数求两个数据的和
int sum(int m, int n) //sumii
{
return m+n;
}
//求两个小数的和
double sum(double m, double n) //sumdd
{
return m+n;
}
//求两个字符串的和
string sum(string m, string n)
{
return m+n;
}
//定义求三个整数的和
int sum(int m, int n, int k) //sumiii
{
return m+n+k;
}
int main()
{
cout << sum(2,5) << endl; //7 调用第一个函数
cout << sum(2.3,5.2) << endl; //7.5 调用第二个函数
cout << sum("hello ","world") << endl; //hello world 调用第三个函数
return 0;
}
练习:使用函数重载完成求两个整数的最大值、两个小数的最大值、三个整数的最大值、两个字符串的最大值,并完成调用(my_max)
#include <iostream>
using namespace std;
int my_max(int a, int b)
{
if(a > b)
{
return a;
}
else
{
return b;
}
}
int my_max(double a, double b)
{
if(a > b)
{
return a;
}
else
{
return b;
}
}
int my_max(int a, int b, int c)
{
if(a > b && a > c)
{
return a;
}
else if(b > a && b > c)
{
return b;
}
else if(c > a && c > b)
{
return c;
}
return 0;
}
string my_max(string s1, string s2)
{
if(s1 > s2)
{
return s1;
}
else
{
return s2;
}
}
double sum(double a, int b)
{
return a+b;
}
int main()
{
cout<<my_max(3, 5)<<endl;
my_max(2.3, 4.5);
my_max(1, 2, 3);
my_max("hello", "world");
return 0;
}
3.2 函数默认参数
1> C语言中定义函数时,不允许设置默认参数,函数形参的值,必须全部由实参进行传递后使用,实参的个数必须跟形参个数保持一致
2> C++定义函数时,允许给定默认参数,即:如果该参数有实参进行传递,则使用实参传递进来的值进行使用,如果实参没有对该参数进行传递,则使用默认参数
3> 默认参数的设置原则:靠右原则,只有右侧的形形参设置了默认参数后,左侧的形参才能设置默认参数,否则报错,原因是,函数实参向形参传递时是靠左原则
4> 当函数默认参数跟函数重载同时出现时,可以定义重载参数个数小于带默认参数的函数,但是,调用时会出现混乱情况
5> 当主调函数写在被调函数定义之前时,需要对对被调函数进行函数声明,函数的默认参数,写在函数声明部分,函数定义部分就不需要加默认参数了
#include <iostream>
using namespace std;
int sum(int m = 50, int n=100, int k =200 );//对函数进行声明
//当有函数进行设置默认参数时,再定义参数个数小于上述函数时,需要注意,是否重复,如果重复,
//定义时,没有问题,但是调用该函数时,会出现混乱情况
int sum(int a, int b)
{
return a+b;
}
int main()
{
cout << sum(1,2,3) << endl; //6 此时,形参的值全部都由实参进行传递
//cout << sum(1,2) << endl; //203
cout<<sum(1)<<endl; //301
cout<<sum()<<endl; //350
return 0;
}
//被调函数的定义
int sum(int m , int n, int k )
{
return m+n+k;
}
3.3 哑元
1> 在C++中的函数中,允许将函数参数设置成哑元,即某个形参只有类型名,没有形参名,在函数体内也不使用该形参
2> 作用:哑元参数只起到占位作用,没有实质性的用途
3> 使用场景
1、在进行程序代码优化时,可能某个函数的某几个参数被优化掉了,但是,该函数已经在程序中调用多次,那么,此时就可以将这些参数设置成哑元,只起到占位作用,函数体内无需使用
#include <iostream>
using namespace std;
//将第二个设置成哑元,只起到占位作用
int sum(int m, int, int k)
{
return m+k;
}
int main()
{
cout << sum(2,3,5) << endl; //10
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
cout << sum(2,3,5) << endl;
return 0;
}
2、在运算符重载时,进行区分自增自减运算符的前置和后置时,必须使用哑元完成(后期讲)
3.4 内联函数(inline)
1> C++中,允许定义内联函数,内联函数会建议编译器在编译程序的时候,将内联函数在调用处进行展开,运行时直接执行函数体内容,提高函数调用效率
2> 使用要求:要求函数体调用频繁,并且函数体内容较小,递归函数不允许定义成内联函数
3> 内联函数定义格式:在定义函数前加关键字inline即可
#include <iostream>
using namespace std;
//定义一个内联函数
inline int sum(int m, int n)
{
return m+n;
}
int main()
{
cout<<sum(1,2)<<endl;
return 0;
}
4> 有参宏和内联函数的区别
1、本质的区别:有参宏是宏替换,内联函数是函数调用
2、替换时机:有参宏替换发生在预处理阶段,内联函数替换发生在编译阶段
#include <iostream>
using namespace std;
#define MAX(x,y) x>y?x:y //定义有参宏
//定义一个内联函数
inline int my_max(int x, int y)
{
return x>y?x:y;
}
int main()
{
int a = 3;
int b = 2;
int c;
c = MAX(a++,b++); //a++ > b++? a++:b++; ===> 3>2?4
cout<<"a = "<<a<<" b = "<<b<<" c = "<<c<<endl; //5 3 4
a = 3;
b = 2;
c = my_max(a++, b++); //my_max(3,2)
cout<<"a = "<<a<<" b = "<<b<<" c = "<<c<<endl; // 4 3 3
return 0;
}
四、C++对结构体的扩充
1> C语言中的结构体,仅仅只是属性(变量)的聚合体,不允许在结构体中定义函数,如果想要定义函数,需要使用函数指针,完成函数回调
2> C++中的结构体内可以包罗万象,既可以封装属性、也可以封装函数、还可以封装一个结构体。。。
3> C++中的结构体在定义属性时,可以直接给定初始值,而C语言中不行
4> C++中的结构体在定义结构体变量时,可以不用加关键字struct,而C语言中不可以
5> C++中的结构体可以有访问权限控制,C语言中的结构体没有访问权限控制
6> C++中的结构体可以被继承,C语言中的结构体不可以
#include <iostream>
using namespace std;
//定义一个人的结构体
struct Person
{
public:
string name = "zhangsan";
private:
int age = 0;
protected:
double height = 1;
public:
//在结构体中封装函数
void show()
{
cout<<"name = "<<name<<" age = "<<age<<" height = "<<height<<endl;
}
};
//定义一个老板结构体,继承自人这个结构体
struct Boss:public Person
{
int money = 100000;
};
int main()
{
struct Person p1;
p1.show();
//cout<<p1.age<<endl; //私有属性不能被访问
cout<<p1.name<<endl; //公共成员,外界可以直接使用
//使用boos结构体定义变量
Boss b;
b.show();
b.money = 2000;
return 0;
}
总结:
①引用,本质是什么?
②內存申请以释放,在c语言中有什么类的
③重载,是c++的一大特色。需要重点学习、复习、再学习、再复习