C++第三课 函数增强机制(个人学习&复习用,有错误感谢指正qwq)

引入:

在上一课中我们提到,cout << 的背后,其实不是普通的语法,而是类对象和运算符一起完成的功能。而这种“协作”的本质,其实就是函数

C++ 中的大多数“高级玩法”,归根结底都离不开函数。函数不仅是程序的基本构建单元,也是很多强大机制的基础。本章我们就来认识一下 C++ 在函数方面做出的几个“增强”,让函数变得智能,用起来更灵活、更安全。

0.C++函数的基本使用指南

在讲函数增强机制之前,我们得先把以下的函数“基本功”打扎实。以下内容是对函数的定义、声明、作用和语法要点的快速复习。

什么是函数?

函数是一段可以重复使用的代码块,它接受输入(参数),执行操作,然后返回结果(或不返回)

我们不可能把所有的操作都堆在main函数里面,函数的存在让程序更模块化、清晰、可维护。

函数声明VS定义

•声明:提前告诉编译器“有这个函数”,但并未告知编译器如何实现。

int add(int a,int b);//声明

•定义:真正写出函数的实现细节

返回值类型 函数名(参数列表){
//函数体,写具体要做的事情
return 返回值;
}

声明是承诺,定义是兑现

注意事项:

•函数声明一般写在前面或.h文件里面,只写函数声明,不写函数体                                        

•声明中参数名可省略,但参数类型不可以省略

•有声明就必须有定义

•函数不能嵌套定义;

•同名函数想要实现不同的功能,要靠函数重载(本节正文会提到)

接下来我们就可以进入正题了!

1.缺省参数/默认参数(Default arguments)

     ---------------------有些话,不说也懂

什么是缺省参数?

有些函数的参数,并不是每次调用都需要传。例如,一个打印提示语的函数,默认提示是"Hello",只有在特殊情况下才换

这时就可以为参数设置一个默认值,这就是“默认值”(或叫“缺省参数”

默认参数的好处

•调用更简洁

•函数更灵活,多元化

•兼容更多使用场景

如果你传了值,程序用你的;如果没传,程序就用默认的

语法规则:

返回类型 函数名(参数1 = 默认值,参数2 = 默认值,...);

你可以给函数的部分或全部参数设置默认值,未传入的参数会自动使用默认值。

例子

void greet(string name = "Tom"){
     cout<<"Hello," << name << "!" <<endl;
}
int main(){
greet();//输出:Hello,Tom!(默认值)

greet("Stella");//输出你想传的值
return 0;
}

注意:

•默认值必须从右向左连续赋值,中间不能跳

•默认参数和函数重载不能混用过多,否则会让调用变得不清晰

•默认参数适合功能变化不大的函数,逻辑简单,易于理解

2.函数重载(Function Overloading)

    ---------------------同名但不同用,多态化的体现

什么是函数重载?

有时候,你希望多个函数“同名不同功能(便于使用者理解)”比如都叫Print(),但有的打印整数、有的打印小数、有的打印字符串

为了不取一堆奇怪的函数名,C++允许你用相同的函数名、不同的参数列表来定义多个函数。
这就叫函数重载(Function Overloading)

语法规则:

同一个作用域内,可以定义多个名字相同的函数,只要它们满足这些重载规则:

•参数的类型不同

•参数的个数不同

•参数的顺序不同(较少见)

返回值类型不能单独作为重载条件

例子:

void print();
//无参
void print(int a);
//参数个数不同
void print(double d);
//参数类型不同
void print(int a,double b);
//参数类型和数量都不同

注意:

默认参数+重载也容易冲突


void show(int a = 1);  // 版本1:带默认参数的函数
void show();           // 版本2:无参函数

// 调用
show();  // 歧义!

根据C++的重载决议规则,当有多个函数可以同样好地匹配一个调用时,就会产生歧义。在这种情况下:

  • 两个函数都是完全匹配(不需要任何转换)

  • 没有哪一个函数比其他函数更"特化"

  • 因此编译器无法自动选择,只能报错

3.引用(高效传参)

😮 引用的本质:编译器自动解引用的“安全指针”

我们都知道,在C语言中,函数参数的传递是按值传递(pass by value)的,这意味着当你将一个变量作为参数传递给函数时,函数内部得到的是该变量的副本,而不是原始变量本身。

因此,在函数内部修改参数的值,并不会影响外部的原始变量。因此如果想让函数修改外部变量,只能用指针。比如交换两个变量的值:

#include <iostream>
using namespace std;
void swap(int* a, int* b){//a 是一个指针变量,存储的是某个 int 类型变量的内存地址
    int temp = *a;// 把指针 a 指向的值(如 x)存入 temp
    *a = *b; // 把指针 b 指向的值写给指针a指向的值
    *b = temp;// 把 temp(原 x 的值)赋给 b 指向的值(修改 y)
}
int main() {
    int x = 10, y = 20;

    cout << "交换前: x = " << x << ", y = " << y << endl;

    // 调用 swap,传入 x 和 y 的地址
    swap(&x, &y);

    cout << "交换后: x = " << x << ", y = " << y << endl;

    return 0;
}

这虽然能实现功能,但也带来很多隐患和麻烦:

 •调用时必须传地址,写法繁琐 swap(&x, &y);

 •需要小心地使用 * 解引用,一不留神就可能出错

 •指针可以为 null,也可以乱指地址,容易造成程序崩溃


于是,C++ 引入了“引用”——一种更安全、更简洁的方式来“高效传参”。
引用其实就是C++为了解决指针带来的“繁琐和不安全”而引入的语法糖

什么是引用?

引用(Reference),是为已有变量起的一个别名,用来“无拷贝”地访问原变量。

语法规则:

int a = 10;
int& b = a; // b就是a的别名,b和a共用一份内存

一旦 b 被创建,它就绑定了 a,以后无论是 b++ 还是 a++,它俩的值都会同步变化。

引用传参 = 高效 + 安全 + 简洁

而操作引用就是操作变量本身 而操作普通变量只是操作这个变量复制出来的一个值

以下是用引用传参去写交换两个变量值的例子:

#include <iostream>
using namespace std;
void swap(int& a,int& b){
int temp = a;
a = b;
b = temp;
}
int main() {
    int x = 10, y = 20;

    cout << "交换前: x = " << x << ", y = " << y << endl;
// 调用swap函数
    // 注意:直接传递变量,不需要取地址操作
    // x和y会分别绑定到swap函数的a和b引用上

    swap(x, y);

    cout << "交换后: x = " << x << ", y = " << y << endl;

    return 0;
}

这里提一个我们以后会经常用到的东西:

常量引用(const reference)

void showMessage(const string& msg){
cout<<"Message:"<<msg<<endl;
}

用途:

•当你只想传参又不想改变它;

•通常用于传递大型对象时避免拷贝,同时又不需要修改

const引用可以绑定临时值(如函数返回值或字面量)是值传递做不到的。

引用传参的优势在于:

 •避免不必要的拷贝,提升性能

当传递大型对象(如结构体、类实例)时,值传递(Pass by Value) 会触发完整的对象拷贝,导致额外的内存和计算开销。

引用传递 直接操作原对象,无需复制,显著提高效率,尤其适用于高频调用或大数据场景。

 •语法简洁,降低出错风险

相比指针(* 和 & 操作),引用在语法层面更接近普通变量,无需手动解引用(Dereference),减少因指针误用(如空指针、野指针)导致的运行时错误。

 •类型安全(Type Safety)

引用必须在初始化时绑定有效对象(不能为 null),而指针可能指向无效地址。这减少了空引用(Null Reference)风险,增强代码健壮性。

引用和指针的底层实现是一样的,引用的语法比指针的语法更简洁,但又不能完全替代指针。

为了更直观的理解两者的异同,我们来看一个对比表

                                              指针和引用

比较维度指针引用
定义语法int* p =&a;int& r =a;
是否必须初始化否,可以先声明再赋值是,必须在声明时绑定目标
是否可以为空可以为nullptr不允许空引用
是否重新绑定可以随时指向其他变量一旦绑定,终身不变
访问值的方式使用*p解引用直接使用引用
使用安全性存在野指针、空指针等风险更安全,排除常见低级错误
内存表现占用额外内存存放地址一般无额外开销(但取决于编译器)
适合场景更灵活,适合数组、动态内存、底层操作更简洁,适合函数传参、返回别名等
是否能替代彼此引用不能完全替代指针指针也不能简洁地替代引用语法

注意:

什么时候不能用引用?
✖:返回局部变量的引用

这是最常见,最危险的错误:

int& getNumber(){
int num =10;
return num;//错误,num是局部变量,函数结束时就销毁了

这会导致悬垂引用,后续访问这个引用会引发未定义行为。

 总结

C++ 让函数不仅仅是代码的封装,更是效率、安全性、表达力的体现。通过“默认参数”、“重载”和“引用”等特性,我们可以:

写出更灵活的函数接口;

复用函数名,减少命名复杂度;

提高传参效率,同时避免错误。


 三个关键词记住本课: 简洁、灵活、高效。

 下一课预告:类和对象——你的程序开始拥有“生命”!

类是什么?对象和类有什么关系?

第四课你将学会用代码“造物”,学习如何用类创建属于自己的数据类型,并通过对象来操作它。

练习题(带思考)

✅ 基础练习

1. 写一个函数 printInfo,用来输出对象的属性信息。它有两个参数:name(string)和 age(int),其中 age 有默认值 18。


2. 实现两个重载函数 add,一个加 int,一个加 double。


3. 写一个函数 swapValue(int& a, int& b) 交换两个数,并在 main 函数中调用验证。

✅ 思考题

1. 默认参数和函数重载能否同时用于一个函数?在什么情况下会冲突?


2. 为什么引用必须初始化,而指针不必须?你能想到可能的场景例子吗?

以下是笔者整理的一些课程相关知识在面试中的常见考法,作为复习和扩展练习参考。

✅ 基础面试题

一、默认参数(Default Arguments)

Q1:默认参数为什么必须从右向左连续指定?
A:因为调用函数时是按顺序匹配参数的,若中间跳过,会造成调用歧义。

Q2:默认参数可以在函数声明和定义中同时指定吗?
A:不可以。默认参数只能出现在函数声明中,定义时不能再次指定。否则编译器报错或行为未定义。

二、函数重载(Function Overloading)

Q1:以下代码是否构成重载?为什么?

int func(int a);
double func(int a);  // 错误

A:不构成重载。C++ 的函数重载只看参数列表(参数数量、类型、顺序),不看返回值类型。

Q2:类里面两个成员函数只有const不一样,算重载吗?为什么?(含类和对象内容)
A:算重载!因为低层级制不同:

  • const成员函数:隐含的this指针类型是 ClassName*(可修改对象)。

  • const成员函数:隐含的this指针类型是 const ClassName*(不可修改对象)。

class MyClass {
public:
    void print() {          // 隐含 this 是 MyClass*
        std::cout << "Non-const\n";
    }
    void print() const {    // 隐含 this 是 const MyClass*
        std::cout << "Const\n";
    }
};

int main() {
    MyClass obj;
    const MyClass cObj;

    obj.print();    // 调用非const版本,输出 "Non-const"
    cObj.print();   // 调用const版本,输出 "Const"
}

三、引用(Reference)

Q1:引用和指针的本质区别是什么?
A:

引用是别名,不能为 null,不能改变绑定;

指针是地址变量,可以为 null,可以改指向;

引用安全,指针灵活。


Q2:为什么函数参数推荐用 const T&?
A:

避免拷贝(效率高);

const 保证不被修改(安全);

特别适合传递大对象。

 进阶面试题

一、重载决议(Overload Resolution)

Q1:以下调用会匹配哪个重载?为什么?

void print(int);
void print(double);

float x = 3.14f;
print(x);  // ?

A:匹配 print(double),因为从 float → double 是提升,而不是缩窄,比转成 int 更合适。
 

Q2:默认参数与重载冲突时怎么办?
A:尽量不要混用默认参数和重载,会引发歧义。例如:
 

void f(int a, int b = 1);
void f(int a);
f(5);  // 哪个版本?冲突!

二、引用的底层实现

Q1:引用的底层是指针吗?
A:是的,大多数编译器内部将引用实现为 T* const(指向 T 的常量指针),但在语法层面它是“自动解引用”的。
 

端午加更!明天还会有一篇。其余时间正常周四更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值