文档章节

C++传值和传引用

o
 osc_isezqdgg
发布于 2019/09/18 14:17
字数 1975
阅读 5
收藏 0
c++

精选30+云产品,助力企业轻松上云!>>>

传值参数

首先你肯定明白一个道理:当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会涌向初始值

int n = 0;
int i = 1;	// i是n的副本
i = 42;		// i的值改变,n的值不改变

传值参数的机理完全一样,由于<u>每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化</u>,所以函数对形参做的所有操作不会影响实参,如果我们想让函数改变我们传入的实参本身们就可以用到指针形参访问函数外部对象

指针形参

先看一段代码:

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

当执行指针拷贝操作时,拷贝的是指针的值(地址),拷贝之后,两个指针是不同的指针,因为指针可以使我们间接地访问所指向的对象,所以通过指针可以修改对象的值

指针形参也差不都

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

void reset(int i)
{
	i = 0; 
}

int main()
{
	int i = 42;
	reset(i);					// 改变i的值而非i的地址
	cout << "i = " << i << endl;
	reset(&i);					// 改变i的值而非i的地址
	cout << "i = " << i << endl;
}

调用这个reset函数之后,实参所指向的对象被置为0,但是实参本身并没有改变,输出如下

i = 42

i = 0

熟悉C语言的程序员可能经常使用指针形参访问函数外部的对象,但在C++中建议使用引用类型的形参代替指针

传引用参数

我们知道对于引用的操作实际上是作用在所引用的对象上的:

int n = 0;
int i = 1;
int &r = n; // r绑定了n
r = 42;     // 现在n=42
r = i;		// 现在n的值和i相同=1
i = r;      // i的值和n相同

引用形参与之类似,通过引用形参,允许函数改变实参的值

// 接受一个int对象的引用,然后将对象的值置为0
void reset(int& i)
{
	i = 0; //改变了i所引对象的值
}

当调用这一版本的reset时,直接传入对象而无须传递地址,被改变的对象是传入reset的实参

void reset(int& j)
{
	j = 0; //改变了i所引对象的值
}

int main()
{
	int j = 42;
	reset(j);
	cout << "j = " << j << endl;
}

输出:

j = 0

避免拷贝

举个例子,我们准备编写一个函数比较两个string对象的长度,对象可能会非常长,所以应该尽量避免直接拷贝它们,这时候引用形参就可以避免拷贝

因为无需改变对象内容,所以把形参定义为对常量的引用

bool isShoter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

const形参和实参

先简单回顾下const

const int ci = 42;	//顶层const,不能改变ci
int i = ci;			//√ 拷贝时候忽略了ci的顶层
int* const p = &i;  //顶层const,不能给p赋值(改变p指向的对象)
*p = 0;				//√ 可以通过p改变对象内容,i = 0

关于const的详解:https://www.cnblogs.com/zhxmdefj/p/11524403.html

一样,形参初始化时会忽略掉顶层const

void fucn(const int i) {
	//fucn能读i,但不能向i写值
}
//调用fucn时,既可以传入const int,也可以传入int

但因此也要注意一个问题

void fucn(const int i) {
	//fucn能读i,但不能向i写值
}

void fucn(int i) {}

因为顶层const被忽略掉了,所以上面两个fucn的参数可以完全一样,因此第二个fucn会报错

指针或引用形参与const

形参初始化方式和变量初始化方式是一样的,先回顾下初始化规则:

int i = 42;
const int* cp = &i;	//√ cp不能改变i
const int& r = i;	//√ r能改变i
const int& r2 = 42;	//√ 
int* p = cp;		//× p的类型和cp不匹配
int& r3 = r;		//× r3的类型和r不匹配
int& r4 = 42;		//× 不能用字面值初始化非常量引用

具体解析:https://www.cnblogs.com/zhxmdefj/p/11524403.html

将同样的规则应用到参数传递:

void reset(int* ip)
{
	*ip = 0; // 改变指针ip所指向对象的值
	ip = 0;  // 只改变了ip的局部拷贝,实参未被改变
}

void reset(int& j)
{
	j = 0; //改变了i所引对象的值
}

int main()
{
	int i = 0;
	const int ci = i;
	string::size_type ctr = 0;
	reset(&i);		//√ 调用void reset(int* ip)
	reset(&ci);		//× 不能用指向const int对象的指针初始化int*
	reset(i);		//√ 调用void reset(int& j)
	reset(ci);		//× 不能把普通引用绑定到const对象ci上
	reset(42);		//× 不能把普通引用绑定到字面值上
	reset(ctr);		//× ctr是无符号类型,类型不匹配
}

尽量使用常量引用

把函数不会改变的形参定义成普通引用,往往会给函数调用者一误导:函数可以修改它实参的值

同时还会极大地限制函数所能接受的实参类型(比如上面的reset不能接受const)

数组形参

数组的两个特性:1.不允许拷贝数组 2.使用数组时会将其转换为指针

因此,我们不能以值传递的方式使用数组,并且传递数组时,实际上传递的是指向首元素的指针

//等价
void print(const int*);
void print(const int[]);
void print(const int[10]);

上面三个函数是等价的,每个函数的唯一形参都是const int*类型

void print(const int*);

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

管理数组实参

使用标记指定数组长度

(C风格字符串存储在字符数组,最后一个字符后面跟着一个空字符)

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

这种方法适用于有明显结束标记的情况

使用标准库规范

传递指向数组首元素和尾后元素的指针

//输出beg到end之间(不含end)所有元素
void print(const int* beg, const int* end) {
	while (beg != end)
		cout << *beg++ << endl; //输出当前元素,并向后移动一个位置
}

显示传递一个表示数组大小的实参

C程序和古老的C++程序员常常使用这种技法

void print(const int* ia, size_t size) {
	for (size_t i = 0; i != size; ++i)
		cout << ia[i] << endl;
}

P.S 关于数组形参和const:当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针

数组引用形参

C++允许将变量定义成数组的引用,所以形参也可以是数组的引用

void print(int(&arr)[10]) {
	for (auto elem : arr)
		cout << elem << endl;
}

注意(&arr)两端括号必不可少

f(int &arr[10])		//arr是有10个整形引用的数组
f(int (&arr)[10])	//arr是有10个整数的整形数组的引用

传递多维数组

多维数组其实就是数组的数组,所以首元素本身就是一个数组,阵阵就是一个指向数组的指针

//martrix指向数组的首元素,该数组的元素是由10个整数构成的
void print(int(*martrix)[10], int rowSize) {
}

(*martrix)两端括号必不可少

//等价定义
void print(int martrix[][10], int rowSize) {
}
o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
c++学习笔记---指针

数组名是指针常量。int a[]={1,2,3},如果a++则是常量++,所以是错误的。 传值&传引用&传指针 相关概念 效率比较:传引用=传指针

教父独白
2012/07/09
33
0
C++ 0x 之左值与右值、右值引用、移动语义、传导模板

左值与右值 左值与右值的概念要追溯到 C 语言,由 C++ 语言继承了上来。C++ 03 3.10/1 如是说:“Every expression is either an lvalue or an rvalue.”左值与右值是指表达式的属性,而非对...

雅各宾
2013/11/27
235
2
C++二级指针和指针引用传参

前提 一级指针和引用 已经清晰一级指针和引用。可参考:指针和引用与及指针常量和常量指针 或查阅其他资料。 一级指针和二级指针 个人觉得文字描述比较难读懂,直接看代码运行结果分析好些,...

osc_p0ka0957
2019/09/07
1
0
C++の函数

题图:NoCopy 字数:1747 | 读完仅需2分钟 C++の函数 连续停更三天,让你们久等了,今天继续。今天我们开始了解C++中的函数的概念。 说到函数,我们应该比较清楚了,不论哪一门语言都有这个概...

leoay
2019/12/30
0
0
c++基础(lambda)

在 c++ 中 lambda 函数有点类似匿名函数,怎么解释呢,我们无需定义一个函数结构随后再去使用这个函数。而是直接在使用时将其声明使用。 再进一步解释可以将函数赋值给变量,然后通过变量再去...

天王盖地虎626
01/22
8
0

没有更多内容

加载失败,请刷新页面

加载更多

使用命名管道承载gRPC

最近GRPC很火,感觉整RPC不用GRPC都快跟不上时髦了。 gRPC设计 gRPC是一种与语言无关的高性能远程过程调用 (RPC) 框架。刚好需要使用一个的RPC应用系统,自然而然就盯上了它,但是它真能够解...

osc_nq69o22c
34分钟前
16
0
06-敏捷开发框架-apis 脚本库 引用位置无关性设计

动态引入技术的设计,对我们来说非常重要。 同时也说明动态语言的使用对我们来说也是非常重要。 没有动态语言的支撑,有些想法可能不容易实现,或者有替代方案,可能会花更大的代价。 前端开...

osc_5zg9z6t1
36分钟前
21
0
(三)学习了解OrchardCore笔记——灵魂中间件ModularTenantContainerMiddleware的第一行①的模块部分

  了解到了OrchardCore主要由两个中间件(ModularTenantContainerMiddleware和ModularTenantRouterMiddleware)构成,下面开始了解ModularTenantContainerMiddleware中间件第一行代码。   ...

osc_kdarxvx0
38分钟前
15
0
50Mn18Cr4V锻锻环件

电机无磁护环怎么锻性能才能《高高》?50Mn18Cr4V高锰无磁钢在变形温度为900~1 100℃、应变速率为0.1 ~10s-1条件下的热变形行为. 结果,VC第二相的应变诱导析出对50Mn18Cr4V的热变形行为产生...

无磁钢
38分钟前
16
0
【遇见offer】一汽-大众实习生专场来啦!成长+学习+福利,一个也不能少~

在上次一汽-大众的社招直播之后,实习生的专场招聘也终于来啦! 针对2020年暑期,我们提供了非常多的实习岗位给大家选择。 如果你想得到大厂实习的宝贵经验,如果你想得到更快速的成长,如果...

osc_b88oux8w
39分钟前
25
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部