C++基本知识 —— 缺省参数·函数重载·引用
- 1. 缺省参数
- 2. 函数重载
- 3. 引用
- 3.1 引用的基础知识
- 3.2 引用的作用
- 3.3 const 引用
- 3.4 指针与引用的关系
1. 缺省参数
什么是缺省参数?缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数的时候,如果没有指定实参则采用该形参的缺省值(默认值),否则使用指定的实参。缺省参数也就做默认参数,缺省值一般都是字面量常量,也可以是全局变量;传参时实参只能从左向右传,且若传参时,只传了一个实参,那么函数的参数列中的第一个形参的值就是这个实参的值;缺省参数分为全缺省和半缺省参数。全缺省就是全部形参使用的都是缺省值。下面用具体的代码来解释上述概念:
// 这就是一个全缺省函数
void Func(int x = 10, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{//不传参 x y z 使用的都是缺省值Func(); //只传一个实参,那么 x 会使用指定的实参,y z 使用的是缺省值Func(1);//传两个实参,那么 x y 会使用指定的实参,z 使用的是缺省值Func(1, 2);//全部都传实参,那么 x y z 都会使用指定的实参Func(1, 2, 3);return 0;
}
运行结果:
C++规定在传实参时,不能间隔跳跃传参,只能从左到右连续的传,如下所示:
// 这样传参是错误的
Func(1, , 3);
Func(, 2, 3);
半缺省就是一部分的形参使用的是缺省值,且半缺省参数的缺省值得要从右往左给,不能从左往右给,也不能跳跃给缺省值,如下所示:
//半缺省函数
void Func(int x, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}void Func(int x, int y, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}
为什么必须要从右往左给形参缺省值呢?为什么不能从左往右给缺省值呢?具体原因如下所示:
void Func(int x = 10, int y = 20, int z)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{Func(1, 2);return 0;
}
当如上图所示传实参时:调用Func函数时,会产生歧义,不知道实参1和实参2的值是传给哪个形参,很明显实参2传递给形参 z 的,而实参1的值是传递哪个形参呢?是 x 还是 y ,这是不明确的,而从右往左给缺省值就不会存在这样的歧义:
void Func(int x, int y = 20, int z = 30)
{cout << "x = " << x << "\ty = " << y << "\tz = " << z << endl;
}int main()
{Func(1, 2);return 0;
}
第一个实参一定是传给第一个形参,第二个实参一定是传给第二个形参,不会存在什么歧义问题。若是传一个实参,那么这个实参一定是传给函数参数列表中的第一个形参;若是传两个实参,那么这两个实参一定是传给函数的参数列表中的前两个形参。
当函数为半缺省函数时,没有缺省值的形参必须要有与之相对应的实参,也就是说,至少要传一个实参。
函数的声明与定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值,缺省值不仅仅只能是具体的值,还可以是表达式。如下所示:
声明:
定义:
2. 函数重载
什么是函数重载?函数重载指的是:一个函数名可以有多重的意义。C++中支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,而C语言不支持。
int Add(int x, int y)
{return x + y;
}double Add(double x, double y)
{return x + y;
}short Add(short x, short y)
{return x + y;
}//……………………
这里的参数不同可以是参数的数据类型不同,参数的个数不同,参数的顺序不同,如下所示:
//以下两个 Add 函数构成函数重载 —— 参数类型不同
int Add(int x, int y)
{return x + y;
}double Add(double x, double y)
{return x + y;
}//以下两个 Add 函数构成函数重载 —— 参数个数不同
short Add(short x, short y)
{return x + y;
}short Add(short x, short y, short z)
{return x + y + z;
}//以下两个 Add 函数构成函数重载 —— 参数顺序不同
long Add(int x, long y)
{return x + y;
}int Add(long x, int y)
{return x + y;
}
但是有时构成重载的两个函数在被调用时会产生歧义,如下代码所示:
//以下两个 Func 函数构成函数重载 —— 参数的个数不同
void Func()
{cout << "Func( )" << endl;
}void Func(int n = 4)
{cout << "Func( int n )" << endl;
}
若是传参调用 Func 函数,调用的一定是第二个 Func 函数;若是不传参调用,那会调用哪个函数?其实这两个函数都会调用,因为无参的函数既可以调用本就是无参的函数,也可以调用全缺省函数。这时代码就会报错,解决方法是不要将这两个函数同时写,用第二个函数去替代。
注意:函数的返回值类型不同不能作为函数重载的条件,因为返回值可以不被接收。
3. 引用
3.1 引用的基础知识
引用的概念:引用不是新定义一个变量,而是给已存在的变量取了一个别名。引用的定义:类型& 引用别名 = 引用对象。如下所示:
// ra 就是 a 的别名
int a = 10;
int& ra = a;
编译器不会为引用变量开辟内存空间,引用变量和引用对象共用同一块内存空间:
由此图就可以得知引用变量与引用对象指向的是同一块内存空间,ra 和 a 的地址都是一样的,且变量的值也是一样的。
对引用变量操作还会改变其引用对象,如下所示:
ra 和 a 的值都被改变了。
此外也可以对同一个变量取多个别名,也可以对别名取别名,如下所示:
它们的值是一样的,地址也是一样的。
在引用中尤其要注意以下的代码:
int x = 20;
int& rx = x;
int y = 30;
rx = y;
请问这里的 rx = y 是在给 y 取别名吗?而 y 的别名是 rx 吗?其实并不是,这里只是将 y 的值赋值给了 rx ,修改了 rx 和 x 的值,来看一下调试窗口:
这里就说明了引用不会改变指向,只要它是一个变量的引用,那么它永远都是那个变量的别名,没有语法能改变引用的指向。C++为了区分引用与取地址,只有&跟在类型的后面才称之为引用,单独一个&是取地址操作符,对变量执行取地址操作。
根据上述的分析,总结出引用的特性:
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,就不能再引用其它实体了
3.2 引用的作用
在C语言中,若想要交换两个变量的值,需要将这两个变量的地址传给形参,如此才能完成两个变量值的交换;但是在C++中,可以使用引用,如下所示:
// rx 是 x 的别名,ry 是 y 的别名
void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 10;int y = 20;cout << "交换前:x = " << x << "\ty = " << y << endl;Swap(x, y);cout << "交换后:x = " << x << "\ty = " << y << endl;return 0;
}
运行结果:
由此可以得出引用的功能:做函数的参数,修改形参影响实参。
在C语言中,使用传值传参时,若实参的内存很大,那么往往会改为传址传参,因为这里会涉及拷贝问题;但是在C++中,可以使用引用,如下所示:
struct Test
{int arr[100000];int size = 0;int capacity = 0;
};// rtst 是 tst 的别名
void Func(struct Test& rtst)
{}int main()
{Test tst;Func(tst);return 0;
}
由此可以得出引用的功能:做函数的参数,减少拷贝,提高效率。
既然可以给整型变量取别名,那可不可以给指针变量取别名呢?当然可以。在C语言中,若想交换两个指针的内容,可以将指针的地址传给形参,也就是使用二级指针;在C++中,可以使用引用来解决这个问题:
void Swap(int*& rpx, int*& rpy)
{int* tmp = rpx;rpx = rpy;rpy = tmp;
}int main()
{int x = 10;int y = 20;int* px = &x;int* py = &y;cout << "交换前:px = " << px << "\tpy = " << py << endl;Swap(px, py);cout << "交换后:px = " << px << "\tpy = " << py << endl;return 0;
}
运行结果:
若想要修改函数的返回值该怎么做呢?可以使用传引用返回,那么为什么不能使用传值返回呢?首先得要理解一个概念,只要是传值返回,返回的都不是原来的值,都会拷贝生成一个临时变量,返回的是这个临时变量,下面来介绍具体原因:
由此可以得出引用功能:引用作为函数的返回值,如此就可以修改返回对象;当函数的返回值的内存较大时,用引用作为函数的返回值,可以减少拷贝,提高效率。
虽然引用非常的强大,但是并不是所有的函数都要用引用作为返回值,若所有的函数的返回值都用引用,那么会造成意想不到的后果,如下代码所示:
跟C语言中学的野指针一样,这里类似于野指针,由于是引用,可以称之为野引用。
当返回的对象出作用域后未被销毁,那么可以使用传引用返回;若返回的对象出作用域后被销毁了,则不可以使用传引用返回,应该使用传值返回。
从函数栈帧的角度去考虑:
所以并不是所有的函数都要用引用作为返回值,当函数变量为局部变量的时候,出了作用域该变量就销毁了,此时返回该局部变量的别名是一个危险的行为。
3.3 const 引用
1. 若想要引用一个const 对象,那么就必须要用使用 const 引用
int main()
{int x = 10;int& rx = x;const int y = 20;const int& ry = y;return 0;
}
若这里引用 const 修饰的对象时,并没有使用 const 引用,那么程序会报错,这是因为这里涉及到了权限的放大 —— 原本变量 y 被 const 修饰后不能被修改,现在对 y 取别名后,就可以通过修改别名来修改变量 y ,但是 y 本身是不能被修改的,别名却可以修改 y ,这样是不可取的。权限是不能放大的。const 引用也可以引用普通对象,因为普通对象的访问权限在引用过程中可以缩小,但是不能放大。如下所示:
int main()
{int x = 10;int& rx = x;const int& r = x;int& const p = x;return 0;
}
这里就涉及到了权限的缩小,之前 x 的值是可以修改的,但是现在被 const 引用修饰后,x 的值就不可以修改了,这就是权限的缩小。这里 const 写在类型的左右边都可以,但是一般写在类型的左边。
接下来根据以下代码来回答下面的问题:
int a = 1;
int& ra = a;
int b = a;
该代码涉及权限的放大或缩小吗?都不涉及,只有指针和引用才涉及权限的放大与缩小,这里只是将变量 a 的值赋值给变量 b 。
2. 要注意的是类似int& rb = a * 4;double c = 3.14;int& rc = c;这样一些场景下a * 4的结果保存在一个临时对象中;int& rc = c也是类似的情况,在类型转换时会将中间值用临时对象来保存。也就是说,rb与rc引用的都是临时对象,而C++规定临时对象具有常性。所以在这里就涉及到了权限的放大,必须要用const引用才行
int main()
{int a = 10;int b = 20;double c = 3.14;const int& r1 = a; //给变量取别名const int& r2 = b; //给变量取别名const int& r3 = 30; //给常量取别名 //将double类型的变量c的整型部分给给临时对象const int& r4 = c; //当表达式运算完毕后会计算出一个结果,这个结果存储在临时对象中//r5引用的是表达式运算的结果,也就是临时对象const int& r5 = a * 4; return 0;
}
之后在写函数的参数的时候,就使用引用来作为形参,视情况考虑是否加const。当函数的参数使用const引用后,参数就可以是临时对象了,使用 const 引用的好处是既可以接收普通变量,也可以接收 const 修饰的变量。如下代码所示:
int main()
{int a = 10;int b = 20;double c = 3.14;const int& r1 = a; const int& r2 = b; Func(r1);Func(r2);Func(10);Func(c);Func(a * 4);return 0;
}
3.4 指针与引用的关系
引用可以完全替代指针吗?当然是不可以的。指针有一个引用做不到的事情,就是改变指向。在数据结构中是时常要改变指向的,在这方面引用就无法做到了,必须使用引用。在实践中指针与引用相辅相承,功能有重叠性,但是各有各的特点,互相是不可替代的:
1. 语法概念上,引用是对一个对象的取别名,不会开辟空间;指针是存储一个对象的地址,需要开辟空间
2. 引用在定义时必须要初始化,指针可以初始化也可以不初始化,但是建议初始
3. 引用在初始化时引用一个对象后,就不能再引用其它对象了,而指针可以不断的改变其指向的对象
4. 引用可以直接访问其指向的对象,而指针需要解引用后,访问的才是其指向的对象
5. 指针很容易出现空指针和野指针问题,引用很少出现,引用使用起来更加的安全
6. sizeof的含义不同,引用结果为引用类型的大小,但是指针始终是地址空间的所占字节个数(32位机器平台下是4个字节,64位机器平台下是8个字节)
底层指针与引用之间的区别:
从指令汇编角度来看,引用是由指针实现的。