c++中的缺省参数和左值引用(!!!)函数声明以及定义的区别

1缺省参数就是函数在声明或者定义的时候为参数指定一个默认值,在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

void TestFunc(int a = 0) {
cout<<a<<endl; }
int main()
{
TestFunc(); 
TestFunc(10);
}

如图所示在这里插入图片描述
// 没有传参时,使用参数的默认值 TestFunc()
传参时,使用指定的实参 TestFunc(10);
2缺省参数的分类
全缺省参数

void TestFunc(int a = 10, int b = 20, int c = 30) {
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl; }
半缺省参数
void TestFunc(int a, int b = 10, int c = 20) {
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl; }

(1)半缺省函数必须从右往左依次给不能随意给
(2)缺省参数不能再函数声明和定义的时候同时给定
eg:在a.h文件声明了一个test(int a=10),在a.cpp里面定义test(int a=20){cout<<a<<endl;}
则会出错
(3)缺省参数必须是常量或者全局变量
(4)c语言不支持
关键字extern “C”
要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译
2引用—本单节只针对左值引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
type:类型& 引用变量名(对象名) = 引用实体;

void Test()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}

在这里插入图片描述
可以看到二者的地址是一样的
引用使用应该注意
(1)引用类型必须和引用实体是同种类型的
(2) 引用在定义时必须初始化
(3) 一个变量可以有多个引用
(4) 引用一旦引用一个实体,再不能引用其他实体

void Test()
{
int a = 10;
// int& ra; // 此处是错误的因为没有在定义的时候初始化
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra); 
}

图中ra 和rra表示证明了一个变量可以有多个引用
比较特殊的常引用


void Test()
{
const int a = 10;//(1)
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;(2)
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double i= 12.34;(3)
//int& d = i; // 该语句编译时会出错,类型不同
const int& d = i;//(4)//d变成了中间生成的临时变量的别名!!!!
 }

(1)为什么出错?是因为const int a是常量,定义的引用却是int&—此处类似于权限放大,—a变量本身是常量不能修改,但是你的引用却可以所以不对
(2)是给整数取引用,常量有常量区,相当于给常量区变量取了别名,所以也要用const修饰不然也会报错
(3)则是类型不同导致的
(4)首先要明白这句话的含义double d = i,i给d是把i生成一个临时变量,临时变量的值给什么(对i进行提示或者进行截断),然后把临时变量给d
同理const double & d = i;–>中间也有临时变量,临时变量(具有常性是const修饰的),如果没有const修饰d变成临时变量的别名(临时变量具有常性不能给)但是加上const后保证不修改你所以是可以的
所以d并不是变成i的别名d变成了中间生成的临时变量的别名
引用的使用场景
(1)引用做参数—传值问题的解决

void swap(int& c, int& d){
	int temp = c;
	c = d;
	d = temp;
}

int main(){
int a = 10;
int b = 20;
swap(a, b);
cout << a << endl;
cout << b << endl;}
解释:
对于c语言
传值涉及拷贝的问题 c语言通过传地址来实现,就是交换a,b把a,b地址也就是指针传过去,对指针解引用就可以了
对于c++
提倡用引用,
在swap函数中用引用也初始化了,void swap(int& c, int& d),不是c和d的定义,严格来说只是对c和d的声明,而函数的定义是在函数调用的时候传参的时候他们才发生定义
声明和定义的区别:有没有分空间–>定义是不仅声明了还要开空间了函数则要开始实现它了

实参传形参,建立栈帧才会开空间—>传参的时候初始化参数
引用如何实现传参:–>c是a的别名d是b的别名,c和d改变就是ab的改变
更多的是在做参数时候,在函数里面改变 一个传过来的实参-(ab)就可以用引用
引用使用的场景:(1)输入型参数(参数给你你可以拿到这个值)—如上面的swap(2)输出型参数(参数给你你把数据传出给我)—也就是做返回值
此处有一个很让人困惑的问题要好好阅读不然会很迷茫

int &add(int a, int b){
	int c = a + b;
	return c;
}
int main(){
	int & ret = add(1, 2);
	cout << "add(1,2) is " << ret << endl;
	add(3, 4);
	cout << "add(1,2) is " << ret << endl;	
}

结果如下图:
在这里插入图片描述
但是如果在加上这句话更神奇会变成???
int main(){
int & ret = add(1, 2);
cout << "add(1,2) is " << ret << endl;
add(3, 4);
cout << “haha没想到把” << endl;
cout << "add(1,2) is " << ret << endl;
}
在这里插入图片描述
会发现成了随机值了…
如何解释?
可以发现此处add打印的结果和中间那行的输入有关系
原因:
(1)首先从底层出发理解*
首先理解
(1)int &add(int a ,int b){int c=a+b;return c}
(2)int add(int a,int b){int c=a+b;return c;},
(3)Int*add(Int a,int b){int c=a+b;return &c;}–>问题c是局部变量是属于当前函数的栈征,出了函数的作用域c变量就销毁了又返回了c的地址则会引发一个问题:野指针问题 ;
额外解释:野指针也叫垂悬指针,指针一般指向正常的变量或者地址,野指针malloc了一块内存指针没有置空)c属于add的栈征出了作用域函数销毁c也就是free(free(使用权不是你的了还给系统内存永远在那个位置,系统重新分配了)了),然后把c的地址给别人就出错了别人用c的地址没有使用权了.
int &add(int a ,int b){int c=a+b;return c}和int add(int a ,int b){int c=a+b;return c}差异对于int add(int a ,int b){int c=a+b;return c}int ret=add(1,2);当add中的c给ret,c所在的空间已经销毁了所以传值返回并不是直接用c返回是中间生成了一个临时变量,把c拷贝给了临时变量然后把临时变量给了ret.
eg:
在这里插入图片描述
(临时变量比较小时寄存器提供,如果变量较大(寄存器放不下了)则是上一个栈征提供的空间),临时变量不在add函数的栈征里其实是在main函数在调用add函数之前临时变量在main函数中开辟了空间(其实在上一个栈征已近开辟好了),这个临时变量不在add函数里,(如上图)
int &add(int a ,int b){int c=a+b;return c}return c也生成了一个临时变量类型为int&然后c给了这个临时变量(但是没有创建空间)反而这个值是c的别名因为是&(引用不是取地址),
int main(){
int & ret = add(1, 2)
add(3, 4);引用方式返回c返回是c的别名,ret以引用的方式接受,ret是c的别名
cout << "add(1,2) is " << ret << endl;
在这个主函数中int &add中return c是临时变量的别名然后ret又是临时变量的别名,用引用接收的意味着ret最终就是c的别名c已经销毁了所以ret访问本质上是很危险的
为什么变成7呢?
在main函数里面有个int &ret=add(3,4),在调用int&add函数的时候里面有个c这个c的值变成了3返回ret就是c(就是c的别名)返回之后栈征销毁了没有add(3,4)打印ret为3
但是再加一句add(3,4)就变成7了,在得到3后栈征就销毁了再调用一次add同一个函数c还是之前的位置加了个结果为7这个时候把7就给了c再去打印ret,ret还是c的值还是空间变量的别名,别名上面的值为7,
输出"haha没想到把"为什么是随机值?"haha没想到把"本质上是函数调用,调用往下建立栈征,这个位置为"haha没想到把"所以就成为了未知数据
引用做返回值返回的是c的别名就是返回它的拷贝
而为什么要用引用做返回值?就是为了解决一些问题出了作用域还存在,类的成员函数
eg:如果c是全局变量就是出了add作用域(c作用域大于add作用域)然后返回c作为引用是没有问题的—>类成员变量出了函数–>引用
引用的一种使用:–改变数组中的值
Eg

 int arryfunc(int pos){
Static int a[10];
Return a[pos];}-->是不行的返回的不是a[pos]的值而这个位置的拷贝读到这个值但是无法修改
int arryfunc(int pos){
static int a[10] = { 1, 2, 3 };
return a[pos];//返回的是左值拷贝的临时对象临时对象具有常性所以不能赋值过去 
}
int main(){
cout<<arryfunc(0)<<endl;
//结果是可以读的但是不可以修改
arryfunc(0)=10;
cout<<arryfunc(0)<<endl;
}
打印是失败的
但是在加上引用就可以了
int &arryfunc(int pos){
static int a[10] = { 1, 2, 3 };
return a[pos];
}
int main(){
arryfunc(0) = 10;
arryfunc(1) = 11;
arryfunc(2) = 12;
arryfunc(3) = 13;
cout<<arryfunc(0)<<endl;
cout << arryfunc(1) << endl;
cout << arryfunc(2) << endl;
cout << arryfunc(3) << endl;//结果为10,11,12,13}

引用做返回值必须是这个返回对象出了作用域还在如果没有static能运行过但是数值不符合
引用传参在函数里面改变了外面也能看见
返回一个对象返回引用返回对象别名出了作用域还在就可拿他返回否则就不能拿它返回,返回它可以在外面修改这个对象
引用的效率高

struct A{
	int a[10000];//4万字节
};
void TestFunc1(A a){
}
void TestFunc2(A& a){}

struct A aa;
int main(){
//以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 1000; i++)
TestFunc1(aa);
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 1000; i++)
TestFunc2(aa);
size_t end2 = clock();
cout << end1 - begin1 << endl;
cout << end2 - begin2 << endl;
}

在这里插入图片描述
引用为0秒调用值为6秒
传值方式传返回值都会形成一个拷贝
传引用别名消耗就被抵消了
返回的时候如果出了作用域,其栈上空间已经还给系统**,因此不能用栈上空间作为引用类型返回,–>不能返回栈上空间变量的引用**
引用底层本质还是指针—用汇编进行分析入下:

打断点调试运行后右键然后转换为汇编代码—>调试完就返回原来代码了
int a = 10;
00C05368 mov dword ptr [a],0Ah //mov 0Ah–>16进制的10—>a里面存的是10
int p = &a;//语法上p存a的地址开辟4/8个空间的指针变量存a的地址
00C0536F lea eax,[a] ///lea是取a的地址,把a的地址取出来放到eax
00C05372 mov dword ptr [p],eax //把eax值放到p,相当于开辟了一个为P的指针变量存a的地址—>实际引用底层开空间了–>引用语法上没有开空间实际在底层是开辟空间了用指针去实现的也是存地址来实现的
int &r = a;//语法上r为a的别名,没有开空间
00C05375 lea eax,[a] ///lea是取a的地址,把a的地址取出来放到eax
00C05378 mov dword ptr [r],eax //将eax值放到r
在看int&ret;虽然c被释了ret概念上是别名底层实现上是存的这块空间的地址所以能访问它,指针引用语法上两个东西指针开空间存地址,引用是存别名底层实现引用也开了空间存地址了本质指针和引用一样的
int add(int a, int b){
00BE41A0 push ebp
00BE41A1 mov ebp,esp
00BE41A3 sub esp,0CCh
00BE41A9 push ebx
int c = a + b;
00BE41BE mov eax,dword ptr [a] //a放到eax
00BE41C1 add eax,dword ptr [b] //把b add到eax–add相当于加等(把b的值加等到eax)
00BE41C4 mov dword ptr [c],eax//把eax mov给c,c放给了eax
const int& ret = add(1, 2);
00AB5EE8 push 2
00AB5EEA push 1
00AB5EEC call add (0AB116Dh)
00AB5EF1 add esp,8
00AB5EF4 mov dword ptr [ebp-18h],eax
00AB5EF7 lea eax,[ebp-18h]
00AB5EFA mov dword ptr [ret],eax //ret为别名,把eax值放到变量ret里面了
add(3, 4);
00AB5EFD push 4
00AB5EFF push 3
指针和引用的区别:

引用在定义时必须初始化指针么有要求
引用在初始化引用一个实体后不能再引用其他实体,而指针可以(开始指向a过会只向b)

没有空引用有空指针
Sizeof含义不一样:引用结果为引用类型大小(char类型为1int 类型为4short为2–>32位系统)指针始终是地址空间所占字节个数(32位4字节64位8字节)
引用自加自减(都是加1,不论int和char都一样),而指针(char
+1int
+4)加类型大小
*
有多级指针没有多级引用
访问实体方式不同:指针需要显示解引用,引用编译器自己处理
引用比指针使用起来相对更加安全(不会出现野指针的问题)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值