思维导图:
一,缺省参数
如何理解缺省参数呢?简单来说,缺省参数就是一个会找备胎的参数!为什么这样子说呢?来看一个缺省参数就知道了!代码如下:
#include<iostream>
using std::cout;
using std::endl;
int Add(int a = 70, int b = 5)
{
return a + b;
}
int main()
{
int ret1 = Add();
int ret2 = Add(100, 80);
cout <<"ret1:" << ret1 << endl;
cout <<"ret2:" << ret2 << endl;
}
这个程序打印的结果是什么呢?通过程序运行可以看到打印的结果是:
ret1:75 ret2:180
通过这个程序的运行你大概知道缺省参数是个什么东西了吧!再来一个代码:
#include<iostream>
using std::cout;
using std::endl;
void Print(int a = 5, int b = 6, int c = 7)
{
cout << a << " ";
cout << b << " ";
cout << c << endl;
}
int main()
{
Print();
Print(9);
Print(8, 8);
Print(6, 6, 6);
return 0;
}
这段代码打印的结果是什么呢?程序运行打印结果如下:
5 6 7
9 6 7
8 8 7
6 6 6
可以看到当我们在调用Print这个函数时如果在外面传入值的话,在Print函数运行时就会使用外面传入的值。如果外面没有传入值的话就会使用函数参数被赋予的值。这个被赋予的值就是缺省值!所以对应的参数就被叫做缺省参数!缺省参数分为全缺省和半缺省!半缺省不是缺省一半而是缺省部分参数。
使用缺省参数时要注意的点
1.在外面传入数值时要从左往右传。假如跳着传就会报错。
如对第二个函数Print,如果这样传参就会报错:
Print(,9,);//跳过第一个传参直接传给第二个参数
2.缺省参数要从右往左缺省。
如对第二个函数Print的缺省值调整成下面的样子就会报错:
void Print(int a = , int b = 6, int c = 7)
3.声明和定义中不能都给缺省值,要给最好在声明中给。
缺省参数的作用
上面说了一大堆关于缺省参数的东西,那这个缺省参数究竟有什么作用呢?下面我们来回顾一个叫做顺序表的东西。为了将顺序表的空间开辟出来,我们大概需要经历三步:
typedef struct List
{
int *a;
int size;
int capacity;
}List;
void ListInit(List* pList)
{
pList->a = NULL;
pList->size = 0;
pList->capacity = 0;
}
void PushBack(List* pList)
{
//检查是否为NULL,malloc空间
// 检查是否需要扩容,realloc
//……
}
int main()
{
List s;
ListInit(&s);
PushBack(&s);
return 0;
}
但是有了缺省参数我们便可以一步搞定了,一开始想开多大就开多大。代码如下:
typedef struct List
{
int* a;
int size;
int capacity;
}List;
void ListInit(List* pList, int n = 4)
{
int* a = (int*)malloc(sizeof(int) * n);
pList->size = 0;
pList->capacity = n;
}
int main()
{
List s;
ListInit(&s);//不像开辟就默认开辟四个(缺省值个)空间
List p;
ListInit(&p, 100);//像开辟一百个空间就开辟一百个空间
return 0;
}
虽然在C语言中可以用宏定义来解决,但是宏定义不够灵活。当我们每次开辟不同的空间大小时每次都要改宏,当我们要搞两个顺序表时就要定义两个宏,三个顺序表就要三个宏,非常麻烦!!!
二,函数重载
函数重载这个概念就是祖师爷对于C语言语法的补充了。什么叫做函数重载呢?构成函数重载的条件是什么呢?下面我们就来探讨一下。
函数重载的条件
1.函数名相同
2.函数的参数类型不同or参数个数不同or参数的类型顺序不同。
比如下面的代码:
int Add(int x, double y)
{
return x + y;
}
//类型不同
int Add(int x, int y)
{
return x + y;
}
//个数不同
int Add(int x, double y, int z)
{
return x + y + z;
}
//顺序不同
int Add(double x, int y)
{
return x + y;
}
这些情况便构成函数重载。在判断函数重载时有一个情况是非常容易迷惑人的就是返回值不同是不会构成函数重载的!例如下列情况。
代码:
int Add(int x, double y)
{
return x + y;
}
double Add(int x, double y)
{
return x + y;
}
char Add(int x, double y)
{
return x + y;
}
//……还有很多只是返回值的类型不同的情况都不构成函数重载!
函数重载的作用
有了函数重载,那我们便可以少取名了。要使用相同功能但是数据类型or个数不同的函数时改一下参数的类型or个数or顺序就可以了!!!
三,引用
cpp中的引用可谓是一大进步。有了引用以后指针的很多功能都能被引用代替,极大的提高了cpp程序的使用感受。既然引用如此可爱那我们一定要去认识一下它。
1.引用的格式
到底在cpp中如何写才能被称为是引用呢?
现在来看看引用的格式特点:类型+&+变量名。当你看到这样的语句时便可以知道这个变量就是一个引用。如:
int a = 1; int& b = a;//b就是对a的一个引用。
2.引用的本质
如果用xxx引用一个变量,有一个通俗的说法叫做给这个变量取了一个”xxx“的别名。
就比如周树人的笔名叫作鲁迅,那提到鲁迅是不是就是提到周树人呢?周树人的家门前有两棵树那鲁迅家门前有没有两棵树呢?这两个的答案当然就是肯定的,因为一个人的名字可以有很多但是人只有一个。cpp中的引用的意思就和这两个例子的意思差不多!!!现在来验证一下:
int main() { int a = 1; int& b = a;//b就是对a的一个引用。 //取两者的地址验证这两个变量是否指向同一个地址 cout << "address of a:" << &a << endl; cout << "address of b:" << &b << endl; return 0; }
结果:
address of a:001EFB84 address of b:001EFB84
通过验证可以看到引用b和a是指向同一块空间的两个不同的名字。
3.引用的特点
1.引用必须初始化
int main() { int a = 1; int& b ;//未对引用b初始化 return 0; }
上面的代码就会报错。报错:
2.引用只能引用一个实体,就像人可以有很多名字但是人只有一个一样。
//引用多个实体 int main() { int a = 1; int& b = a; int c = 0; int& b = c; return 0; }
这样也会报错。报错:
但是这样是可以的,因为这是赋值:
//赋值 int main() { int a = 1; int& b = a; int c = 0; b = c; return 0; }
3.一个实体可以有多个别名就像一个人可以有多个名字一样。
//一个实体可以有多个别名 int main() { int a = 0; int& b = a; int& c = a; int& d = a; //…… return 0; }
4. 引用不能引用空指针
//不能引用空指针 int main() { int& a = nullptr; return 0; }
报错:
//不能引用空指针 int main() { int& a = NULL; return 0; }
报错:
四,使用引用的两种形式
1.引用作参数(输出型参数)
当函数中的一个参数的作用是用来做输出型参数时便可以考虑使用引用做参数。
补充:
1.输出型参数:输出型参数的作用就是为了改变外面传入参数的数据。
2.输入性型参数:输入型参数就是给函数内部传入数据。
这里经典的要传输入型参数的函数就是swap函数:
void swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; }
这个swap函数使用的是指针来交换a,b两个变量的数据。
//引用 void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } int main() { int a = 0; int b = 10; cout << "swap前a:" << a << endl; cout << "swap前b: " << b << endl; swap(a, b);//swap函数调用 cout << endl; cout << "swap后a:" << a << endl; cout << "swap后b: " << b << endl; return 0; }
结果:
swap前a:0 swap前b: 10 swap后a:10 swap后b: 0
二,引用做返回值
用引用做返回值是一个得十分谨慎的事情。因为引用卓返回值是有前提的,前提就是出了返回值函数的作用域以后返回值的实体不能被销毁。
1.其它类型的函数返回是怎么做的呢?
图解:
通过上面的过程n的数据便被返回到ret中接收。
2.引用作返回值
引用做返回值就和一般类型的返回不同了。因为传引用返回是不产生临时变量的。
但是在n出了函数add以后,n就被销毁了。这个时候就要看运气了。当n里面的值还没有被清理时便可以让ret接收,当n中的被清理以后ret接收的值就是随机值了。所以当返回值在被接收前是会被销毁的参数时就要谨慎使用了。
应该传引用返回的场景
当变量是静态变量,堆上的变量,全局变量时便可以大胆的使用传引用返回。因为这些变量是不会被系统轻易销毁的。又因为使用传引用变量可以减少数据的拷贝,所以传引用返回的效率会更高。所以在可以使用传引用返回的场景应该多多使用传引用返回。
补充:利用传引用返回来修改顺序表里面的值和读取第i个位置的值。
C语言版本:
//C语言读取第i个位置的值
int Find(List* ps, int i)
{
assert(ps);
return ps->a[i];
}
//修改第i个位置的值
void SLModify(List* ps, int i, int x)
{
assert(i < ps->size);
ps->a[i] = x;
}
C++利用引用版本:
int& SLAC(List& ps,int i)
{
assert(i < ps.size);
return ps.a[i];
}
从这里便可以看出cpp的引用有多香了!哈哈!
补充:
使用引用时不能发生权限的扩大。可以发生权限的平移和缩小。
如:
int main() { double a = 1; int b = a; }
这个程序是可以运行的,但是换成下面的代码便会报错:
int main() { double a = 1; int& b = a; }
这是为什么呢?这是因为,在第一个程序中双精度浮点型的a要传给整型的b就会发生如下图所示的过程:
在双精度浮点型的a传给b时会发生隐式转换,这个过程会产生一个临时变量。临时变量有一个特点就是具有常性。所以在将a的值再赋值给b时就会发生权限的扩大!那该如何搞定这个错误呢?解决方法如下:
int main() { double a = 1; const int& b = a;//在b前面加一个const修饰符 }
这样b便也有了常性,这样就相当于权限的平移了!