C++基础入门(中):函数重载、引用、内联函数

目录

引言

一、 函数重载

1.1 概念

1.2  示例

① 参数类型不同

② 参数个数不同

③ 参数类型顺序不同

注意

1.3 原理

举个例子

注意

二、 引用

2.1 引用的声明和定义

2.2 引用的特性

2.3 引用与指针的区别

2.4 常引用

2.4.1 常引用特点

2.5 引用应用

三、 内联函数

3.1 如何声明内联函数

3.2 内联函数的优点

3.3 内联函数的注意事项


引言

本系列旨在为初学者提供一份全面且易懂的C++入门指南。我们将从C++的关键字开始,逐步探索C++的各个方面,包括命名空间、输入输出、函数特性以及C++11的一些新增特性,如auto关键字、基于范围的for循环nullptr。每个主题都会有简明扼要的解释和示例代码,帮助你更好地理解和运用这些知识。

无论你是刚刚踏入编程的大门,还是想要从其他编程语言转换到C++,本指南都将帮助你建立对C++的扎实基础。通过学习本系列内容,你将掌握C++中的核心概念和常用特性,为后续学习和项目开发打下坚实的基础。

一、 函数重载

1.1 概念

在C++编程中,函数重载是一项非常有用的特性。它允许我们在同一个作用域内声明多个同名函数,但这些函数的参数类型或参数个数不同或参数顺序不同。通过函数重载,我们可以用相同的函数名来表示不同的操作,从而提高代码的灵活性和可读性。

1.2  示例

① 参数类型不同

int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}


double Add(double left, double right)
{
    cout << "double Add(double left, double right)" << endl;
    return left + right;
}

② 参数个数不同

void fun(int a)
{
    cout << "fun(int a)" << endl;
}

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

③ 参数类型顺序不同

void fun(int a, double b)
{
    cout << "fun(int a, double b)" << endl;
}

void fun(double a, int b)
{
    cout << "fun(double a, int b)" << endl;
}

当调用这些函数时,使用者根据需求选择函数并按函数原型传入实参,编译器会根据传递给函数的参数类型和个数来选择合适的函数进行调用。这样,我们可以用相同的函数名来表示不同的操作。

注意

函数重载的有效性是根据函数的参数列表进行区分的,而不考虑返回类型。在C++中,函数的返回类型不会影响函数重载的选择过程。这是因为函数的返回类型在函数调用时并不会用于解决函数重载的二义性。

函数的重载解决方案是由编译器根据传递给函数的参数类型和个数来选择正确的函数进行调用。编译器在调用函数时,会根据实际传递的参数来查找匹配的函数声明。如果找到了参数列表匹配的函数声明,就会调用相应的函数,而不管这些函数的返回类型是否相同。

1.3 原理

  1. 名称修饰(Name Mangling):在C++中,函数重载涉及到函数名的区分。由于函数重载可能存在相同的函数名,为了在编译过程中能够区分这些函数,C++编译器会对函数名进行名称修饰,也称为名称重整(Name Mangling)。名称修饰是将函数名和参数列表信息编码成一个唯一的字符串,从而在编译后生成不同的函数名。这样,相同的函数名在编译后会变得不同,因此可以区分函数重载。

  2. 参数匹配:当进行函数调用时,C++编译器会根据传递的参数类型和个数来匹配合适的函数。编译器会根据传递给函数的实际参数类型和形式参数类型进行匹配,以找到与之匹配的函数声明。参数匹配是在编译时完成的,而不是在运行时。

  3. 函数调用解析:一旦编译器匹配到合适的函数声明,它就会生成对应的函数调用。由于名称修饰的作用,函数的实际调用名可能与源代码中的函数名不完全相同。

举个例子

int add(int a, int b);
float add(float a, float b);

编译器可能会将add(int, int)函数的名称修饰为"_Z3addii",将add(float, float)函数的名称修饰为"_Z3addff"。因此,源代码中调用的函数名和实际生成的函数名可能不同

int sum1 = add(5, 10);          // 实际调用的函数名可能为"_Z3addii"
float sum2 = add(3.14f, 2.71f); // 实际调用的函数名可能为"_Z3addff"

注意

名称修饰的具体方式和规则在不同的编译器中可能有所不同,这也导致了不同编译器对函数重载的处理方式可能略有差异。为了保持向后兼容性,C++提供了extern "C"的机制,用于在C++代码中声明使用C语言的函数名称修饰方式。

二、 引用

在C++中,引用是一种用于给变量起别名的机制。引用允许我们通过一个已存在的变量名称来访问另一个变量的值,而不是创建一个新的存储空间。引用为C++引入了更直观、更简洁的语法,并提供了一种更安全和高效的方式来操作变量。

2.1 引用的声明和定义

引用是使用&符号来声明的。在声明引用时,我们必须将其初始化为引用的目标,即要引用的变量。一旦引用被初始化后,它将始终引用该变量,无法重新绑定到其他变量

类型& 引用变量名(对象名) = 引用实体;

int num = 10;
int& ref = num;  // 引用声明和初始化

首行代码表明在空间中开辟空间存入数值10,并且命名为num,第二行代码表明我给此空间取另外一个名字ref,两个名字均可对此空间进行操作,且效果相同

注意:引用类型必须和引用实体是同种类型的,当然也有例外,后续详解。

2.2 引用的特性

  1. 别名:引用为变量提供了一个别名。通过引用,我们可以使用不同的名称来访问同一个变量。

  2. 无空引用:C++要求引用在声明时必须进行初始化,并且一旦初始化后就不能再改变其引用的对象。

  3. 没有独立地址:引用本身并不占用内存空间,它只是变量的一个别名。所以无法对引用获取地址。

  4. 传递参数:引用在函数参数传递中特别有用,它可以通过引用来传递参数,以实现函数内部对参数的修改对原始变量的影响。

2.3 引用与指针的区别

引用和指针是C++中两种不同的变量别名机制,它们之间有以下主要区别:

  1. 初始化和重新绑定:引用在声明时必须进行初始化,并且一旦初始化后就不能重新绑定到其他变量。而指针可以不进行初始化,并且可以重新指向其他变量。

  2. 空引用:C++不允许创建空引用,即引用必须绑定到一个已经存在的对象。但是,指针可以指向空(nullptr)。

  3. 语法:引用使用&符号进行声明,指针使用*符号进行声明。

  4. 内存占用:引用本身不占用额外的内存空间,而指针需要存储目标对象的地址,占用一定的内存空间。

2.4 常引用

常引用(const reference)是引用的一种特殊类型,在C++中用于限制通过引用访问的对象的修改。通过将引用声明为常引用,我们告诉编译器,引用所指向的对象是只读的,不允许通过引用对对象进行修改

常引用通过在引用声明中添加const关键字来实现,其声明形式为:

const T& ref = 引用实体;
//T是要引用的对象类型

2.4.1 常引用特点

  1. 只读访问:通过常引用,我们只能读取所引用对象的值,不能修改它。任何试图通过常引用修改对象的操作都会导致编译错误。

  2. 初始化要求:常引用必须在声明时进行初始化,且只能引用一个同类型的变量或常量。

  3. 临时对象:常引用可以绑定到临时对象(右值),但由于临时对象在表达式结束后会被销毁,因此只能访问临时对象的值,不能修改。

   针对第三点示例

int& ref = 10; //错误 10为右值,不能通过ref进行修改
const int& ref = 10; //正确 此时ref为常引用不可修改内容

2.5 引用应用

1.函数参数传递:通过引用传递参数,可以实现函数内部对原始变量的修改。简化使用指针。

void increment(int& x) {
    x++;
}

int num = 10;
increment(num);
// 现在num的值为11

2.返回值:函数可以通过引用返回结果,避免创建临时变量。

int& findLargest(int& a, int& b) {
    return (a > b) ? a : b;
}

int a = 10, b = 15;
int& largest = findLargest(a, b);
largest = 20;
// 现在b的值为20,即largest为b的引用

3.避免复制:引用可以避免对象的复制,提高代码的执行效率。

void processLargeObject(const LargeObject& obj) {
    // 处理大对象的操作,不需要复制对象
}

此处使用了常引用,代表在函数内不会对对象进行修改。

注意

在第二点做返回值时,要注意不能将临时变量引用返回

int& fun()
{
    int a = 10;
    return a;
}

例如此代码,变量a在函数执行结束之后便会被立刻销毁,若此时任然根据引用去当作别名使用,对导致结果的不确定性,是不合法的操作。

三、 内联函数

内联函数(Inline Function)是C++中的一种函数优化机制,它用于在编译器编译时将函数体的代码直接插入到调用处,而不是通过函数调用的方式进行执行。这样做可以避免函数调用的开销,提高代码的执行效率。

3.1 如何声明内联函数

在C++中,使用inline关键字来声明一个内联函数。通常情况下,你可以在类内部定义的成员函数上使用inline关键字来声明它们为内联函数。对于类外部定义的函数,你可以在函数声明和定义处使用inline关键字。

在类内部定义的内联函数:

class MyClass {
public:
    inline void myFunction() {
        // 函数体代码
    }
};

在类外部定义的内联函数

// 在函数声明处使用inline关键字
inline void myFunction();

// 在函数定义处使用inline关键字
inline void myFunction() {
    // 函数体代码
}

3.2 内联函数的优点

  1. 减少函数调用开销:在函数调用时,会将当前执行状态保存到栈中,然后跳转到被调用函数,执行完毕后再返回调用点,这一过程涉及多次栈帧的创建和销毁。而内联函数的代码会直接插入到调用点,避免了这些额外的开销,从而提高代码的执行效率。

  2. 节省内存空间:内联函数的代码会被直接插入到每个调用点,不会在内存中产生函数的副本,因此节省了代码的空间。

3.3 内联函数的注意事项

  1. 内联函数的复杂性:内联函数通常适用于简短的函数,因为函数体过于复杂会导致代码膨胀,反而会降低性能。编译器对内联化的复杂函数可能会拒绝内联化

  2. 编译器决策:使用inline关键字只是对编译器提出了内联的建议,最终是否将函数作为内联函数由编译器决定。编译器可能会忽略inline关键字,尤其是对于过于复杂的函数或递归函数。

  3. inline不建议声明和定义分离,分离会导致链接错误。

  4. 避免滥用内联函数:尽管内联函数能够带来性能上的优势,但并不是所有函数都适合内联化。内联函数的代码会复制到每个调用点,如果函数体过大或频繁调用,会导致代码膨胀,可能会反而降低性能。

猜你喜欢

转载自blog.csdn.net/weixin_57082854/article/details/132069044