(注:在看本文是如果感觉内容有点突兀,请先浏览《C++语法基础(上)》这篇文章帮助更好理解)
一.缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)
• 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++ 规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
示例代码:
以上代码,展示了全缺省参数和半缺省参数的函数定义与使用。 add 函数是全缺省参数示例, sub 函数为半缺省参数示例。通过在 main 函数中调用,呈现不传参时使用缺省值,传参时使用指定实参的效果。
• 带缺省参数的函数调用,C++ 规定必须从左到右依次给实参,不能跳跃给实参。
• 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
二.引用
1.引用的概念与定义
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:《水浒传》中李逵,宋江叫“铁牛”,江湖上人称“黑旋风”;林冲,外号豹子头。所以一个引用也可以有多个别名。
类型& 引用别名 = 引用对象;
以上C++ 代码,在 main 函数中,先定义了变量 a 并赋值为2 。接着定义了 b 和 c 作为 a 的引用,由于引用与被引用变量共用内存空间,对 c 执行自增操作后, a 、 b 、 c 的值都变为3 ,运行结果验证C++ 中引用的这一特性。
2.引用的特性
①引用在定义时必须初始化
②一个变量可以有多个引用
③引用一旦引用一个实体,再不能引用其他实体
3.引用的使用
在C++ 实践中,引用主要发挥于引用传参与引用返回值两大场景:
- 引用传参:与指针传参功能近似,却更具便捷性。通过引用传递参数,可规避对象拷贝,提升程序运行效率,且能直接操作实参,改变其值。
- 引用返回值:应用场景较为复杂,这里先作简要介绍,后续在类和对象章节将深入探讨。它同样能减少拷贝开销,有时还可用于实现对特定对象的持续引用操作 。
引用与指针在实际运用中相互配合,虽功能有所重叠,但各自特性鲜明,无法相互取代。值得注意的是,C++ 的引用与Java 等语言的引用差异显著。C++ 中引用一经定义,便无法更改指向;而Java 里的引用则无此限制,可灵活变更指向。
部分以C 代码为主的《数据结构》教材,会采用C++ 引用替换指针传参,旨在简化程序逻辑,规避指针操作的复杂性。
引用传参和指针传参
以上C++ 代码展示了两种实现交换功能的函数:一个通过引用传参( Swap(int& x, int& y) ),另一个通过指针传参( Swap(int* x, int* y) ) 。在 test 函数中对这两个函数进行调用测试,分别传入变量和变量地址。运行结果表明,二者都能成功实现变量值的交换,直观体现引用和指针在参数传递用于修改实参值时的作用。
4.const引用
在C++ 里, const 引用即常量引用 ,它是对变量的只读引用(但不能赋值)。
从定义来讲,当用 const 修饰引用时,就创建了这种只读关系,一旦绑定到某个对象,通过该引用不能修改所绑定对象的值 。比如 const int& ret = a; ,此后不能执行 ret = 新值; 这样的操作 。
其主要作用体现在两方面:
①避免数据拷贝:在函数参数传递中,若传递大对象,按值传递会产生拷贝开销,使用 const 引用传递,能避免拷贝,提升程序性能 。例如函数 void func(const 大对象类型& obj) ,调用时不会对实参进行拷贝 。
②保证数据不可变性:能确保在函数内部不会意外修改引用的数据,为数据安全性提供保障 。在多函数调用、多人协作开发等场景下,可防止数据被误改,让代码逻辑更清晰、可靠 。
此外, const 引用还有个特殊之处,它可以和声明类型不匹配 ,比如 const int& r = 3.14; (编译器会创建临时量来实现转换和绑定 ),但普通引用不允许这样做 。
以下示例:
代码展示了 const 引用的特性。在 main 函数中,定义变量 a 并赋值为2 ,接着创建 const int& ra 作为 a 的常量引用。由于 ra 是 const 引用,不能对其执行自增操作(如 ra++ 被注释掉 ),但原变量 a 可以自增。运行结果显示 a 自增后的值为3 , ra 的值也同步变为3 ,体现 const 引用只读且与原变量关联的特性。
这段 代码在 main 函数中,先定义了常量 a 和变量 b ,然后分别创建了它们的 const 引用 ra 和 rb 。代码注释指出, int& ra = a; 这种写法是错误的 。因为 a 是 const 类型,普通引用不能绑定到 const 对象上,而 const 引用能确保不会通过引用意外修改对象的值,此例直观体现const 引用的使用规则和对象绑定限制。
这段 代码在 main 函数中,展示了 const 引用的特殊用法及普通引用的限制。定义变量 a 后, const int& ra = 30; 直接绑定字面量,体现 const 引用可绑定临时值。而 int& rb = a * 3; 等普通引用绑定临时值或类型不匹配值会编译报错。 const int& rb = a * 3; 、 const int& rd = d; 能成功,表明 const 引用可在类型转换等情况下,通过创建临时变量实现绑定,凸显 const 引用与普通引用在对象绑定上的差异。
5.引用与指针对比
(1 )定义与初始化
- 指针:指针是一个变量,存储的是另一个变量的内存地址。它可以先定义,之后再赋值。例如 int *ptr; 先定义了一个整型指针 ptr ,后续可以 ptr = # 让它指向变量 num 。指针初始化不是必须的,但未初始化就使用会导致未定义行为。
- 引用:引用是已存在变量的别名,必须在定义时初始化。例如 int num; int &ref = num; 定义 ref 作为 num 的引用,此后 ref 一直关联 num ,无法再引用其他变量。
(2)内存占用
- 指针:指针本身需要占用内存空间来存储所指向变量的地址。在32位系统中,指针通常占用4字节;64位系统中,指针一般占用8字节。
- 引用:从概念上讲,引用不占用额外内存空间,因为它只是原变量的别名。但在实际实现中,某些编译器可能会为引用分配与指针相同大小的空间来实现引用的功能。
(3)使用方式
- 指针:使用指针访问其所指向变量的值时,需使用解引用操作符 * 。例如 int num = 10; int *ptr = # int value = *ptr; ,这里通过 *ptr 获得 ptr 所指向的 num 的值。指针还支持指针运算,如 ptr++ 可移动指针指向同一类型数组的下一个元素。
- 引用:使用引用就像使用原变量本身,直接通过引用名访问和操作关联变量。例如 int num = 10; int &ref = num; int value = ref; ,直接使用 ref 即可,无需额外操作符。
(4)空值情况
- 指针:指针可以指向空值,即 nullptr (C++11 引入,之前用 NULL )。例如 int *ptr = nullptr; ,在使用指针前通常需要检查其是否为空,以避免空指针解引用错误。
- 引用:引用不能指向空值,因为定义引用时必须初始化,且一旦初始化就不能再改变引用对象。
(5)可修改性
- 指针:指针本身的值(即所指向的地址)可以改变,使其指向不同的变量。例如 int num1 = 10, num2 = 20; int *ptr = &num1; ptr = &num2; ,指针 ptr 先指向 num1 ,之后可重新指向 num2 。
- 引用:引用一旦初始化,就不能再引用其他变量,始终与初始化时的变量绑定。
(6)应用场景
- 指针:常用于动态内存分配与释放,操作数组,实现数据结构(如链表、树)等场景。指针的灵活性使其在复杂数据操作中发挥重要作用。
- 引用:主要用于函数参数传递和返回值,避免对象拷贝开销,同时保证对原对象的直接操作。在运算符重载等场景也常使用引用,以提供自然的语法和高效的操作。
三.nullptr
在C++ 中, 引入nullptr 是为了替代NULL ,虽然两者都可以 用于表示指针为空的状态,但两者存在差异。
从类型角度, NULL 本质是整数 0 或预处理器宏定义为 0 ,常被视为整型,虽可赋值给指针,但会造成类型不匹配隐患。而 nullptr 是C++11引入的关键字,类型为 std::nullptr_t ,专门用于表示空指针,与任何指针类型都能隐式转换,类型安全性更好。
使用场景上, NULL 在C和早期C++代码中广泛使用,不过在C++ 中易导致函数重载歧义,因它可被当成整数参与重载决议。 nullptr 则消除了这种歧义,在C++11及以后代码中,推荐使用 nullptr 表示空指针,使代码更清晰、安全,符合现代C++ 类型系统规范。
这段C++ 代码定义了两个重载函数 f(int x) 和 f(int* ptr) 。在 main 函数中进行调用测试, f(0) 调用了 f(int x) 。原本想通过 f(NULL) 调用指针版本的函数,却因 NULL 被定义为0 ,实际调用了 f(int x) 。 f((int*)NULL) 能正确调用指针版本,而 f((void*)NULL) 编译报错。使用 nullptr 则可准确调用 f(int* ptr) ,清晰展现函数重载下指针类型调用及 NULL 与 nullptr 的区别。