C++初识(2)

欢迎来到C++

1、缺省参数(默认参数)

1.1、认识及规则介绍

缺省参数(默认参数),就是给函数的形参 赋上默认值

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

void func(int a = 0)//缺省参数(默认参数)
{
	cout << a << endl;
}

int main()
{
	func(10);//传入参数一定有返回值
	func();  //不传入参数也会有返回值

	return 0;
}

缺省参数(默认参数)按形式,分为全缺省,和半缺省。

全缺省,就是函数的所有形参,都赋上默认值:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

void func(int a = 1, int b = 2, int c = 3)//全缺省
{
	cout << a << " ";
	cout << b << " ";
	cout << c << endl << endl;

}

int main()
{
	func();
	func(10);
	func(10, 20);
	func(10, 20, 30);

	return 0;
}

半缺省,就是部分形参赋上默认值:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

void func(int a, int b = 2, int c = 3)//半缺省
{
	cout << a << " ";
	cout << b << " ";
	cout << c << endl << endl;

}

int main()
{
	//func();
	func(10);
	func(10, 20);
	func(10, 20, 30);

	return 0;
}

对于半缺省,有两个规则:

  • 规则1:缺省参数的设置,必须从右往左。与函数实参对形参值传递从左往右的规则相匹配
  • 规则2:对于某些缺省参数未设置的形参,调用函数时必须对他们赋值

若缺省参数的设置不是从右往左,会报错:

调用函数时未对缺省参数未设置的形参赋值,会报错:

而对于缺省参数(默认参数),还有一个规定:

  • 如果函数同时存在声明与定义,那么缺省参数必须设置在函数声明中,不能设置在在函数定义中

这可能是用来防止在不同地方设置了不同默认值,导致机器识别混淆的情况发生。

示例:

1.2、应用

缺省参数(默认参数)的使用,为我们在一些问题的处理上提供了一些便利。

比如我们正在对一个顺序表进行操作。

1.2.1、规避性能的浪费

在不使用缺省参数(默认参数)的情况下,我们创建一个顺序表并初始化,大概是这样的:

我们在之前的学习中知道,向顺序表中传入数据,如果顺序表空间不够,需要扩容 。那么问题来了,当我们想一次性传入1000个(整型)数据,如果使用realloc扩容 按照之前从4*4个字节开始,每次扩容一倍,那么需要扩容9 次;然而系统并不是直接扩容,而是先拷贝,然后申请新空间并粘贴,再释放旧空间,经过这么多次全过程,势必有性能的浪费

这时我们使用缺省参数,即一开始规定需要扩容的空间。

如果没有额外的要求,默认值可以向上面一样设置为4;而如果有额外的要求,比如1000,那么我们就可以输入参数1000。这样一来,就可以规避性能的浪费

1.2.2、解决一些难以解决的问题

当我们使用顺序表的查找函数 时,对于一个有多个相同数据的顺序表,我们只能查找这个相同数据从左往右第一次出现的位置,想要寻找其他的位置非常困难。

这时我们使用缺省参数,规定开始查找的位置

这时,我们就可以查找到任意位置的相同数据。想找第一个,参数就设置成0 ,想找第二个,参数就设置成参数为0的函数的返回值......

当然,我们也可以设计一个函数,查找所有相同数据的位置:

c 复制代码
void test2(SL& sl, int val)
{
	//先确定有没有
	int pos = SLFind(sl, 5);
	if (pos == -1)
	{
		cout << "没找到。" << endl;
		return;
	}
	//找出所有相同值的下标
	pos = -1;
	while (pos < sl.size)
	{
		pos = SLFind(sl, val, pos+1);
		if (pos == -1)
		{
			break;
		}
		cout << pos << " ";
	}
}

比如传入数据:1、1、3、4、5,规定找"1"的位置,我们就能得到下标:

当然,也可以使用for循环实现。

2、函数重载

2.1、函数重载的认识

C语言中,不能存在同名函数。而在C++中,同名函数可以存在 ,但是需要函数的形参不同

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int Add(int x, int y)//整型加法
{
	cout << "int Add(int x, int y)" << " " << endl;
	return x + y;
}

double Add(double x, double y)//双精度浮点型加法
{
	cout << "double Add(double x, double y)" << " " << endl;
	return x + y;
}

int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.23, 2.34) << endl;
	
	return 0;
}

函数重载,可以有三种不同形式:

  1. 函数形参类型不同
  2. 函数形参个数不同
  3. 函数形参位置不同(可以理解为类型不同)

下面来看看不构成函数重载的情况:

2.2、不构成函数重载的情况

第一种:函数的参数相同,但类型不同

其实,这种形式从函数重载的定义上来看,就不会构成(函数重载),因为参数就已经相同了,导致编译器无法准确找到对应的函数:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int f()
{
	cout << "int f()" << endl;
}

void f()
{
	cout << "void f()" << endl;
}

int main()
{
	f();
	
	return 0;
}

所以设置不同的形参,可以解决这一点:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int f(int x)
{
	cout << "int f()" << endl;
	return;
}

void f()
{
	cout << "void f()" << endl;
}

int main()
{
	f();
	f(1);
	
	return 0;
}

第二种:两个函数(或多个函数),其中一个无参数,另一个全缺省

我们在缺省参数(默认参数)的学习中得知,对于一个全缺省的函数,调用时是可以不传入任何参数的。此时又出现了一个同名的没有参数的函数,那么当我们调用这个函数而不传入参数的时候,编译器就会分不清,调用的是没有参数的函数,还是全缺省的函数:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int f1()//无参数
{
	cout << "int f1()" << endl;
}

int f1(int x = 10)//全缺省
{
	cout << "int f1(int x = 10)" << endl;
}

int main()
{
	f1();
	
	return 0;
}

3、引用

引用,相当于我们给一个对象取别名 。一个对象的所有引用,与这个对象共用空间

3.1、引用的使用方法及规则

引用的使用方法是:类型 + &

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int main()
{
	int i = 10;
	int& j = i;//引用

	cout << i << endl;
	cout << j << endl;

	cout << &i << endl;//取地址
	cout << &j << endl;

	return 0;
}

引用的使用有三个规则:

规则1 :引用必须初始化。

所以这样的写法会报错:

规则2 :一个对象可以被多次引用。

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int main()
{
	int i = 10;

	int& j = i;//j引用i
	int& k = j;//k引用i

	cout << i << endl;
	cout << j << endl;
	cout << k << endl;

	cout << "-----------------------------------------------" << endl;
	cout << &i << endl;//共用空间,地址相同
	cout << &j << endl;
	cout << &k << endl;

	cout << "-----------------------------------------------" << endl;
	j = 20;//改变对象和引用中的任意一个,其他所有跟着变

	cout << i << endl;
	cout << j << endl;
	cout << k << endl;

	return 0;
}


规则3 :一个引用有了实体之后,就不能再去做另一个实体的引用。

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

int main()
{
	int i = 10;
	int& j = i;

	cout << "-----------------------------------------------" << endl;
	cout << i << endl;
	cout << j << endl;

	int k = 20;
	j = k;//这里不是使j引用i,而是将k的值赋值给j(i)

	cout << "-----------------------------------------------" << endl;
	cout << i << endl;
	cout << j << endl;
	cout << k << endl;

	cout << "-----------------------------------------------" << endl;
	cout << &i << endl;
	cout << &j << endl;
	cout << &k << endl;//地址不同

	return 0;
}

k 与 j 的地址不同,佐证了这一规则。

3.2、不能使用引用的场景

在讲引用的实际使用场景之前,先简单说说不能使用引用的场景

诸如链表、树中定义节点的地方,不能使用引用。因为这些节点往往会改变指向,而引用一旦对应了某一实体,就不能再对应另外的实体。

3.3、引用的实际使用

引用的使用,可以:

  • 减少拷贝提高效率
  • 改变引用对象的同时改变被引用的对象

引用的使用,可以在大部分场景下,代替指针。

引用的使用主要有两个地方:引用传参引用做返回值

3.3.1、引用传递参数

3.3.1.1、值交换函数

比如我们之前学过的值交换函数,我们知道值交换函数要用到传址调用

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

//值交换函数
void Swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

int main()
{
	int x = 1, y = 2;
	cout << "交换前:" << x << " " << y << endl;
	Swap(&x, &y);
	cout << "交换后:" << x << " " << y << endl;

	return 0;
}

在传址调用中,我们必须传入指针。如果直接使用传值调用,由于此时函数的形参只是实参的一份临时拷贝,改变形参不能改变实参,就达不到值交换的目的。

然而,我们使用引用,就不需要传入指针,因为引用与对象共用空间:

c 复制代码
#include<iostream>

using std::cout;
using std::endl;

//值交换函数
void Swap(int& rx, int& ry)
{
	int tmp = rx;
	rx = ry;
	ry = tmp;
}

int main()
{
	int x = 1, y = 2;
	cout << "交换前:" << x << " " << y << endl;
	Swap(x, y);
	cout << "交换后:" << x << " " << y << endl;

	return 0;
}
3.3.1.2、单链表传入参数的简化

我们在单链表创建的学习中,知道单链表节点是动态申请 的;而传值调用中,改变形参不会改变实参。所以我们当时使用了二级指针

但当我们学会了引用的使用,就不用那么麻烦了:

3.3.2、引用做返回值

3.3.2.1、错误的使用:返回局部变量的时候使用引用

首先我们要明白,编译器给一个函数开辟的栈帧中,如果创建了一个局部变量,并且最后返回了这个变量值,那么对于这个,过程底层的逻辑应该是:编译器首先使用另一个临时变量(寄存器)存储变量值,然后将这个临时变量返回,最后释放为函数开辟的栈帧。

这个过程,进行了一次临时拷贝

cpp 复制代码
#include<iostream>

using namespace std;

int Swap()
{
	int ret = 0;
	//TODO...
	return ret;
}

int main()
{
	int x = Swap();
	cout << x << endl;
	
	return 0;
}

这种临时变量是不可以被修改的:

这时,我们将函数改成引用做返回值:

cpp 复制代码
#include<iostream>

using namespace std;

int& Swap()
{
	int ret = 0;
	//TODO...
	return ret;
}

int main()
{
	int x = Swap();
	cout << x << endl;
	
	return 0;
}

我们会发现,打印的之还是0。但是编译器报了警告:

在这里,调用的函数Swap(),成了ret的引用(别名)。但是函数Swap()的栈帧已经释放,通过别名去访问ret,是一种危险的行为。

不是所有的出错情况,编译器都会报错。

比如数组越界,越界读不会报错:

这里我们看到退出码为0。但是对于越界读行为,还是报了警告。

而越界写,就会报错:

引用的底层实现,与指针是一样的。

所以,我们不能在返回局部变量的时候使用引用。

3.3.2.2、正确使用

既然我们不能在返回局部变量的时候使用引用,我们就不要使用全局变量。

比如:

c 复制代码
#include<iostream>

using namespace std;

int& Func()
{
	static int ret = 0;//使用static修饰,使ret变为全局变量
	//TODO...
	return ret;
}

int main()
{
	int x = Func();
	cout << x << endl;

	return 0;
}

引用的一个很好的运用案例,是运用到顺序表中的修改值函数

由于SLat()可以被看作SLat()这个函数的返回值的引用,所以我们修改引用,就修改了对应实体本身,非常简单地达到了修改值的目的:

相关推荐
澄澈青空~2 小时前
接续:DKOM 进程隐藏的原理
c++·安全
一眼万里*e2 小时前
如何快速学懂开源地面站
c++
m0_748250032 小时前
C++ 预处理器
开发语言·c++
爱装代码的小瓶子2 小时前
【c++进阶】c++11下类的新变化以及Lambda函数和封装器
java·开发语言·c++
m0_748250033 小时前
C++ 标准库概述
开发语言·c++
恒者走天下3 小时前
c++ cpp项目面经分享
c++
烟锁池塘柳03 小时前
C++程序脱离环境运行:详解OpenCV动态库依赖部署 (Deployment)
c++·opencv·webpack
被制作时长两年半的个人练习生3 小时前
首尾元素相同的间隔循环策略
c++·笔记·循环·ptx
千里马-horse3 小时前
React Native bridging 源码分析--ClassTest.cpp
javascript·c++·react native·react.js·bridging