C++函数与参数(值传递、地址传递、模板函数、引用传递、函数重载)

目录

 

一、值传递(传值函数)

                1.BayMer程序1 计算三个数的和

                2.文字说明

二、地址传递

                1.BayMer程序2 交换两个数

                ​​​​​​​2.文字说明

三、模板函数

                1.BayMer程序3 多类型大小比较器

                2.文字说明

四、引用传递

                1.Baymer程序4 利用引用传递计算三位数乘法

                2.文本说明

五、返回值

                1.以值的方式返回

                2.以地址的方式返回(错误做法)

                3.以引用的方式返回

六、函数重载

七、全文巩固习题


一、值传递(传值函数)

1.BayMer程序1 计算三个数的和
#include<iostream>

int add(int a, int b, int c)
{
    return a + b + c;
}

int main()
{
    using namespace std;
    int num1 = 1, num2 = 2, num3 = 3;
    int sum = add(num1, num2, num3);
    cout << "1+2+3=" << sum << endl;
    return 0;
}
2.文字说明

        在Baymer程序1中,主函数中的num1、num2、num3为实参,add函数中的a、b、c为形参。在运行时,实参复制给形参。复制过程中调用了形参所属数据类型的拷贝构造函数。在类型允许隐式转换的前提下,若实参与形参的数据类型不同,将会发生类型转换。

        此处简述类型转化。上述代码中,若将add函数的形参a改为double类型,则num1赋值给a时,会发生隐式类型转换。但在有些情况下,如a仍是为string类型,num1无法转换为string类型。

        因为add函数中的形参a、b、c是对num1、num2、num3的值拷贝,a、b、c与num1、num2、num3指向的内存空间各不相同。当add函数运行结束后,各形参调用相应数据类型的析构函数来释放形式参数。当一个函数运行结束时,形参的值不会被复制给对应的实参。因此,此时,若对a、b、c进行修改,主函数中的num1、num2、num3不会发生任何变化。即,值传递不会修改实参的值。

二、地址传递

1.BayMer程序2 交换两个数
#include<iostream>

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    using namespace std;

    int num1 = 10;
    int num2 = 20;

    swap(&num1, &num2);
    cout << "The value of num1 and num2 after runing swap:" << endl;
    cout << "num1 = " << num1 << endl;
    cout << "num2 = " << num2 << endl;
    return 0;
}
2.文字说明

        在主函数中,调用swap函数时,将num1及num2的地址复制给swap函数的指针参数。此时a与num1、b与num2指向同一块内存空间。因而,swap中对a、b所指向的内存空间的改值操作,必定会导致主函数中对应变量的变化。

        该方式需要对指针进行操作,解引用(取*操作)让操作变得相对繁琐。且对指针的操作,可能引发不可预知的错误。因此,对实参所指向的内存空间进行操作时,并不推荐使用地址传递,而更推荐引用传递(下下部分说明)。

三、模板函数

1.BayMer程序3 多类型大小比较器
#include<iostream>

template<typename T>
void compare(T a,T b)
{
    if(a > b)
    {
        cout << "Larger" << endl;
    }
    else if(a < b)
    {
        cout << "Smaller" << endl;
    }
    else
    {
        cout << "Equal" << endl;
    }
}

int main()
{
    using namespace std;
    
    //整型比较
    int num1 = 5;
    int num2 = 8;
    compare(num1, num2);
    
    //浮点型比较
    double d1 = 1.5;
    double d2 = 6.8;
    compare(d1, d2);

    //字符型比较
    char c1 = 'a';
    char c2 = 'z';
    compare(c1, c2);

    return 0;
}
2.文字说明

        假设,我们需要对5和8进行比较、1.5和6.8进行比较、‘a’和‘z’进行比较。为每个类型编写一个比较函数的话,整个程序就过于冗余。与其对每一种可能的形参类型编写一个对应函数的新版本,不如编写一段通用代码。这也就是模板函数。

        在BayMer程序3中,编写了一个用于比较大小的通用类型模板函数。在整型比较时,编译器将模板函数中的T替换为int。同理,在double型比较时,编译器将模板函数中的T替换为double,以此类推。将compare编写为模板函数后,我们就不必了解形参的类型了(用户自定义类型可能无法进行直接比较)。

四、引用传递

1.Baymer程序4 利用引用传递计算三位数乘法
#include<iostream>

template<typename T>
T mul(T& a, T& b, T& c)
{
    return a * b * c;
}

template<typename T>
T mulConst(const T& a, const T& b, const T& c)
{
    return a * b * c;
}

int main()
{
    using namespace std;

    int num1 = 1;
    int num2 = 2;
    int num3 = 3;
    int ret = mul(num1, num2, num3);
    
    cout << "ret = " << ret << endl;

    ret = mulConst(num1, num2, num3);
    
    cout << "ret = " << ret << endl;

    return 0;
}
2.文本说明

        在Baymer程序1中,由于函数add为值传递方式,因此在传入a、b、c时需要调用其对应类型的拷贝构造函数,返回a、b、c相加的结果时,需要调用一次拷贝构造函数和a、b、c三个变量对应类型的析构函数。若此时函数的参数是数组,需要传递的参数是1000*1000的矩阵,传入参数时需要调用100万次拷贝构造函数,返回时要调用100万次析构函数。这显然是很浪费实践和空间的。

        在上述情况下,若将a、b、c转换为引用类型(如代码中的mul函数所示),此时传入a、b、c的值时,并不会调用拷贝构造函数,函数执行结束后也不会调用对应类型的析构函数。此时的a、b、c相当于给传入num1、num2、num3取别名,它们指向的内存空间与num1、num2、num3,类似于地址传递,但使用它们的时候,并不需要解引用操作(即取*操作)。

        上述的mul函数还存在一定问题。若主函数只希望mul函数进行乘法操作,但却被mul函数中的某个操作修改了数值。怎么避免这种情况呢?这就需要常量引用参数了,在引用参数前加上const关键字,就构成了常量引用参数。此时,mulConst中,若有操作想试图修改参数数值,编译器将会报错。

五、返回值

        参数有值传递、地址传递、引用传递。那返回值是否有对应的值返回、地址返回、引用方式返回呢?

1.以值的方式返回
#include<iostream>
using namespace std;

class MyType
{
public:
	MyType(int num)
	{
		this->m_num = num;
		cout << "构造函数" << endl;
	}
	MyType(const MyType& myType)
	{
		this->m_num = myType.m_num;
		cout << "拷贝构造函数" << endl;
	}
	~MyType()
	{
		cout << "析构函数" << endl;
	}
	int m_num;
};

MyType addOne(MyType num)
{
	num.m_num += 1;
	return num;
}

int main()
{
	MyType num1(5);

	addOne(num1);

	cout << "after add one: " << num1.m_num << endl;

	return 0;
}

程序运行结果:

构造函数
拷贝构造函数
拷贝构造函数
析构函数
析构函数
after add one: 5
析构函数

我们从运行结果分析整个程序的运行过程:在创建num1时必定调用MyType的构造函数。调用addOne函数时,num1复制给num,因此调用了拷贝构造函数。在函数完成加1操作后,程序在栈区开辟一个临时的内存空间用于存储返回值,num复制给临时返回值,因此调用了拷贝构造函数。在addOne函数运行结束后,调用了临时返回值的析构函数和num的析构函数。主函数输出运行结束后,调用num1的析构函数

        由上述分析可知,值返回过程中会在栈区开辟临时内存空间。

2.以地址的方式返回(错误做法)
#include<iostream>
using namespace std;

int* add(const int& a, const int& b)
{
	int sum = a + b;
	return &sum;
}

int main()
{
	int num1 = 1;
	int num2 = 2;

	int* ret = add(num1, num2);

	cout << "ret = " << *ret << endl;
	cout << "ret = " << *ret << endl;
	cout << "ret = " << *ret << endl;

	return 0;
}

程序运行结果:

ret = 3
ret = 2047187336
ret = 2047187336

        我们从运行结果分析整个程序运行过程:num1、num2被引用传递至add函数中后,add函数在栈区为sum开辟一个内存空间,并保存num1和num2的结果。函数运行结束后,释放函数中所有局部变量的地址空间。之后,再将sum的地址返回给主函数。

        在函数运行结束后,sum的地址空间已经被释放。此时,对sum原地址的访问本就是非法访问,可能导致程序错误或崩溃。第一次对ret进行解引用能输出正确结果,是因为编译器对结果进行了保留。后续再对ret进行解引用,就无法得到正确结果了。

3.以引用的方式返回
#include<iostream>
using namespace std;

const int& addConst(const int& a, const int& b)
{
	int sum = a + b;
	return sum;
}

int& add(const int& a, const int& b)
{
	int sum = a + b;
	return sum;
}

int main()
{
	int num1 = 1;
	int num2 = 2;

	int ret = add(num1, num2);

	cout << "ret = " << ret << endl;

	const int constRet = addConst(num1, num2);

	cout << "constRet = " << constRet << endl;

	return 0;
}

        我们从运行结果分析整个程序运行过程:将num1、num2传递给add函数后,add函数在栈区为sum开辟内存空间,并存储num1和num2的和。在函数运行结束前,将sum以引用的方式返回,主函数使用ret进行接收。retConst运行方式类似。

        在函数运行结束前,使用的引用返回方式等价于"int& ret = sum",即sum的地址空间现在被ret所指向,内存空间不会被释放。

六、函数重载

#include<iostream>
using namespace std;

// ---1号---
int add(int& a, int& b)
{
    cout << "int& a, int& b" << endl;
    return a + b;
}

// ---2号---
int add(const int& a, const int& b)
{
    cout << "const int& a, const int& b" << endl;
    return a + b;
}

// ---3号---
int add(const int& a, const int& b, const int& c)
{
    return a + b + c;
}

// ---4号---
int add(const int& a, const double& b)
{
    return a + b;
}

// ---5号---
int add(const double& b, const int& a)
{
    return a + b;
}

// ---6号---
template<typename T1, typename T2, typename Ret>
Ret add(const T1& a, const T2& b)
{
    cout << "模板函数" << endl;
    return a + b;
}

int main()
{
    int num1 = 1;
    int num2 = 2;
    cout << "num1 + num2" << add(num1, num2) << endl;
    return 0;
}

        构成函数重载的条件,除了要满足类名相同外,还要满足下面的任意一条:①参数个数不同(如上述代码的3号和4号函数)②参数个数相同时,参数的类型不同(如上述代码的2号和4号函数)③参数个数和类型相同时,参数顺序不同(如上述代码的4号和5号函数))⑤参数类型相同个数相同顺序相同时,有const修饰和无const修饰也能构成重载(如上述代码的1号和2号函数)。

        注意:在模板函数和已经确定类型的函数都能够被执行时,优先调用确定类型的函数。

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值