c++笔记_参数传递

本文详细介绍了C++中的参数传递方式,包括传值参数、指针形参、传引用参数以及const形参和实参的使用。特别强调了引用如何避免拷贝提高效率,以及在处理数组时的各种策略,如通过数组长度、标准库规范和显示传递大小。此外,还讨论了使用常量引用的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名:也就是说,引用形参是它对应的实参别名。

当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递或者函数被传值调用

一、传值参数

初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,变量的改变不会影响初始值:

int n = 0;			//int类型的初始变量
int i = n;			//i是n的值的副本
i = 42;				//i的值改变;n的值不变

传值参数,函数对形参做的所有操作都不会影响实参:

#include<iostream>
void sum(int i)
{
	int b = i--;
	std::cout<<b<<std::endl;
}

尽管sum函数改变了i的值,但是这个改动不会影响传入sum的实参。调用sum(i)不会改变i的值。

二、指针形参

指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值:

int n =0, i=42;
int *p =&n, *q=&i;			//p指向n;q指向i
*p =42;						//n的值改变;p不变
p=q;						//p现在指向了i;但是i和n的值不变

指针形参的行为与之类似:

//该函数接受一个指针,然后将指针所指的位置为0
void reset(int *ip)
{
	*ip=0;				//改变指针ip所指对象的值
	ip=0;				//只改变了ip的局部拷贝,实参未被改变
}

调用reset函数之后,实参所指的对象被置为0,但是实参本身并没有改变:

int i = 42;
reset(&i);					//改变i的值而非i的地址
cout<<"i="<<i<<endl;		//输出i=0

三、传引用参数

引用的操作实际上是作用在引用所引的对象上:

int n =0,i=42;
int &r=n;				//r绑定了n(即r是n的另一个名字)
r=42;					//现在n的值是42
r=i;					//现在n的值和i相同
i=r;					//i的值和n相同

引用形参的行为与之类似。通过使用引用形参,允许函数改变一个或多个实参的值。

//改写上一个reset程序,使其接受的参数是引用类型而非指针
void reset(int &ip)		//ip是传给reset函数的对象的另一个名字
{
	ip=0;				//改变了ip所引对象的值
}

和其他引用一样,引用形参绑定初始化它的对象。当调用reset的函数时,ip绑定我们给传函数的int对象,此时改变ip也就是改变ip所引对象的值。被改变的对象时传入reset的实参。

int j = 42;
reset(j);					//j采用传引用方式,它的值被改变
cout<<"j="<<j<<endl;		//输出j=0

形参ip仅仅是j的又一个名字。在reset内部队i的使用即是对j的使用。

1.使用引用避免拷贝

拷贝大的类类型对象或者容器对象比较低效,甚至有类类型根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

//编写一个函数比较两个string对象的长度
bool isShor(string &a,string &b)
{
	return a.size()<b.size();
}

因为string对象可能会非常唱,所以应尽量避免直接拷贝它们,这时候引用形参是比较明智的选择。

ps:如果函数无须改变引用形参的值,最好将其声明为常量引用。

2.使用引用形参返回额外信息

有时候函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。

举个例子,我们定一个名为find_char的函数,它返回在string对象中某个指定字符第一次出现的位置。同时,我们也希望函数能返回该字符出现的总次数。

//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的总次数
string::size_type find_char(const string &s,char c,string::size_type &occurs)		//可以给函数传入一个额外的引用实参,令其保存字符出现的次数
{
	auto ret =s.size();							//第一次出现的位置
	occurs =0;									//设置表示出现次数的形参的值
	for(decltype(ret) i=0;i!=s.size();++i){		
		if(s[i]==c){
			if(ret==s.size())					//记录c第一次出现的位置
				ret = i;						//将出现的次数加1
			++occurs;			
		}
	}
	return ret;									//出现次数通过occurs隐式地返回
}

四、const形参和实参

形参是const,顶层const作用于对象本身:

const int ci =42;			//不能改变ci,const是顶层的
int i=ci;					//正确:当拷贝ci时,忽略了它的顶层const
int *const p =&i;			//const是顶层的,不能给p赋值
*p=0;						//正确:通过p改变对象的内容是允许的,现在i变成了0

当实参初始化形参时会忽略掉顶层const。当形参有const时,传给它常量对象或者非常量对象都是可以的:

void fcn(const int i) {/*fcn能够读取i,但是不能向i写值*/}

1.尽量使用常量引用

把函数不会改变的形参定义成引用是一种比较常见的错误,会带给函数的调用者一种误导,即函数可以修改它的实参的值。此为,使用引用而非常量引用也会极大限制函数所能接受的实参类型。比如不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。

五、数组形参

数组的两个特殊性质对我们定义和使用作用在数组上的函数有所影响,分别是:
1.不允许拷贝数组以及使用数组时(通常)会将其转换成指针。
2.因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数,因此数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针

//尽管形式不同,但是这三个函数是等价的
//每个函数都有一个const int*类型的形参
void print(const int*);
void print(const int[]);			//可以看出来,函数的意图是作用于一个数组
void print(const int[10]);			//这里的维度表示我们期望数组含有多少元素,实际不一定

当编译器处理对print函数的调用时,只检查传入的参数是否const int*类型:

int i=0,j[2]={0,1};
print(&i);			//正确:&i的类型是int*
print(j);			//正确:j转换成int*并指向j[0]

传给函数是一个数组,则实参自动转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。


因为数组是以指针的形式传递给函数,所以一开始函数并不知道数组大小,我们可以提供一些额外的信息,管理指针形参有三种常用的技术。

1.使用标记指定数组长度

第一种方法:要求数组本身包含一个结束标记,使用这种方法的典型示例是C风格字符串。C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。函数在处理C风格字符串时遇到空字符停止:

void print(const char *cp)
{
	if(cp)						//若cp不是一个空指针
		while(*cp)				//只要指针所指的字符不是空字符
			cout<<*cp++;		//输出当前字符并指向指针向前移动一个位置
}

这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况。

2.使用标准库规范

第二种方法:传递指向数组首元素和尾后元素的指针:

void print(const int *beg,const int *end)
{
	//输出beg到end之间(不含end)的所有元素
	while(beg!=end)
		cout<<*bed++<<endl;
}

调用这个函数,我们需要传入两个指针:一个指向要输出的首元素,另一个指向尾元素的下一位置:

int j[2] = {0,1};
//j转换成指向它首元素的指针
//第二个实参是指向j的尾后元素的指针
print(begin(j),end(j));	

3.显示传递一个表示数组大小的形参

第三种方法:专门定义一个表示数组大小的形参,在C程序和过去的C++程序常常使用这种方法:

//const int ia[]等价于const int* ia
//size表示数组的大小,将它显示地传给函数用于控制对ia元素的访问
void print(const int ia[],size_t size)
{
	for(size_t i=0,i!=size;++i){
		cout<<ia[i]<<endl;
	}
}

通过形参size的值确定要输出多少个元素,调用print函数时必须传入这个表示数组大小的值:

int j[] ={0,1};
print(j,end(j)-begin(j));

4.数组引用参数

C++语音允许将变量定义成数组的引用,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上:

//正确:形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10])
{
	for(auto elem:arr)
		cout<<elem<<endl;
}

&arr两端的括号必不可少:

f(int &arr[10]);		//错误:将arr声明成了引用的数组
f(int (&arr)[10]);		//正确:arr是具有10个整数的整数数组的引用

### RT-DETRv3 网络结构分析 RT-DETRv3 是一种基于 Transformer 的实时端到端目标检测算法,其核心在于通过引入分层密集正监督方法以及一系列创新性的训练策略,解决了传统 DETR 模型收敛慢和解码器训练不足的问题。以下是 RT-DETRv3 的主要网络结构特点: #### 1. **基于 CNN 的辅助分支** 为了增强编码器的特征表示能力,RT-DETRv3 引入了一个基于卷积神经网络 (CNN) 的辅助分支[^3]。这一分支提供了密集的监督信号,能够与原始解码器协同工作,从而提升整体性能。 ```python class AuxiliaryBranch(nn.Module): def __init__(self, in_channels, out_channels): super(AuxiliaryBranch, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1) self.bn = nn.BatchNorm2d(out_channels) def forward(self, x): return F.relu(self.bn(self.conv(x))) ``` 此部分的设计灵感来源于传统的 CNN 架构,例如 YOLO 系列中的 CSPNet 和 PAN 结构[^2],这些技术被用来优化特征提取效率并减少计算开销。 --- #### 2. **自注意力扰动学习策略** 为解决解码器训练不足的问题,RT-DETRv3 提出了一种名为 *self-att 扰动* 的新学习策略。这种策略通过对多个查询组中阳性样本的标签分配进行多样化处理,有效增加了阳例的数量,进而提高了模型的学习能力和泛化性能。 具体实现方式是在训练过程中动态调整注意力权重分布,确保更多的高质量查询可以与真实标注 (Ground Truth) 进行匹配。 --- #### 3. **共享权重解编码器分支** 除了上述改进外,RT-DETRv3 还引入了一个共享权重的解编码器分支,专门用于提供密集的正向监督信号。这一设计不仅简化了模型架构,还显著降低了参数量和推理时间,使其更适合实时应用需求。 ```python class SharedDecoderEncoder(nn.Module): def __init__(self, d_model, nhead, num_layers): super(SharedDecoderEncoder, self).__init__() decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead) self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers) def forward(self, tgt, memory): return self.decoder(tgt=tgt, memory=memory) ``` 通过这种方式,RT-DETRv3 实现了高效的目标检测流程,在保持高精度的同时大幅缩短了推理延迟。 --- #### 4. **与其他模型的关系** 值得一提的是,RT-DETRv3 并未完全抛弃经典的 CNN 技术,而是将其与 Transformer 结合起来形成混合架构[^4]。例如,它采用了 YOLO 系列中的 RepNCSP 模块替代冗余的多尺度自注意力层,从而减少了不必要的计算负担。 此外,RT-DETRv3 还借鉴了 DETR 的一对一匹配策略,并在此基础上进行了优化,进一步提升了小目标检测的能力。 --- ### 总结 综上所述,RT-DETRv3 的网络结构主要包括以下几个关键组件:基于 CNN 的辅助分支、自注意力扰动学习策略、共享权重解编码器分支以及混合编码器设计。这些技术创新共同推动了实时目标检测领域的发展,使其在复杂场景下的表现更加出色。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值