面试题1:函数定义与声明有什么区别?

函数定义与声明的区别主要体现在以下几个方面:
内存分配:
定义:函数定义会为函数分配内存空间,并且可能会为函数内部的局部变量分配内存。定义提供了函数在程序中的唯一描述,包括函数的实现细节,如函数体中的代码。
声明:函数声明不会分配内存空间。它仅仅告诉编译器函数的名称、返回类型和参数类型,但不会提供函数的实际实现。
实现与原型:
定义:函数定义包含了函数的实现,即函数体中的代码,这是函数具体执行的操作。
声明:函数声明通常称为函数原型,它提供了函数的签名,包括函数名、返回类型和参数列表,但不包括函数体。
出现次数:
定义:在C++程序中,函数的定义只能出现一次。如果尝试在多个地方定义相同的函数,会导致编译错误。
声明:函数声明可以在程序中多次出现。通常,函数声明会放在头文件中,而定义会放在源文件中。这样,当其他文件包含这个头文件时,就可以使用这个函数,而不需要知道它的具体实现。
作用域:
定义:函数定义具有作用域,它定义了函数在程序中的可见性和可访问性。
声明:函数声明也具有作用域,但它只是告诉编译器函数的存在和它的接口,而不涉及具体的实现。
总结来说,函数定义提供了函数的完整实现,包括函数体中的代码,而函数声明则提供了函数的接口信息,包括函数名、返回类型和参数列表,但不包括函数体。在 C++ 程序中,函数定义只能出现一次,而函数声明可以多次出现,通常用于在其他文件中声明函数的存在和接口。

面试题2:什么是函数重载?它有什么作用?

函数重载( Function Overloading )是 C++ 中一种重要的编程特性,它允许在同一作用域内定义多个同名函数,但这些函数的参数列表(即参数的个数、类型或顺序)必须不同。通过函数重载,我们可以使用相同的函数名来表示不同的操作,从而提高代码的可读性和可维护性。
函数重载的作用主要体现在以下几个方面:
提高代码的可读性和可维护性
重载函数使得程序员可以根据函数名来推测函数的功能,而不需要记住每个函数的具体细节。这有助于减少代码中的错误,并提高代码的可读性和可维护性。
避免命名冲突
通过函数重载,我们可以使用相同的函数名来表示不同的操作,而无需为每个操作创建不同的函数名。这有助于避免命名冲突,并使代码更加整洁和易于理解。
提供更灵活的函数调用方式
函数重载允许根据实际需求选择不同的函数。例如,我们可以根据参数的类型、个数或顺序来调用不同的函数,从而实现更灵活的函数调用方式。
需要注意的是,函数重载并不是通过函数的返回值类型来区分的(在函数调用时,编译器需要根据提供的参数来确定应该调用哪个重载函数。如果允许通过返回值类型来重载函数,编译器在编译时可能无法确定应该调用哪个函数,因为返回值类型是在函数执行后才知道的。这会导致编译时的歧义和不确定性)。也就是说,如果两个函数的参数列表完全相同,但返回值类型不同,那么它们将被视为两个不同的函数,而不是重载函数。这是因为函数的返回值类型不参与函数调用的过程,因此不能作为区分重载函数的依据。
总的来说,函数重载是 C++ 中一种非常有用的特性,它允许定义多个同名函数,并通过参数列表的不同来区分这些函数。这有助于提高代码的可读性和可维护性,避免命名冲突,并提供更灵活的函数调用方式。

面试题3:什么是内联函数?为什么使用它?

内联函数( Inline Function )是 C++ 中的一种特殊函数,它通过在编译时将函数体直接插入到函数调用点处,而不是通过常规的函数调用机制来执行。这样可以消除函数调用的开销,提高程序的执行效率。
内联函数通常用于频繁执行的小函数,这些函数体代码较小,但调用次数很多。由于每次函数调用都会带来一定的开销,如参数传递、栈帧创建和销毁等,因此使用内联函数可以减少这些开销,提高程序的执行效率。
内联函数的使用通过在函数定义前添加 inline 关键字来声明。然而,是否真正将函数内联展开是由编译器决定的,编译器会根据函数的实现和调用情况来决定是否进行内联展开。
使用内联函数的好处主要包括:
提高执行效率:通过消除函数调用的开销,内联函数可以提高程序的执行效率,特别是对于那些频繁执行的小函数。
减少函数调用的开销:内联函数避免了函数调用的开销,包括参数传递、栈帧创建和销毁等,从而减少了程序的时间消耗。
然而,使用内联函数也需要注意以下几点:
代码膨胀:由于内联函数会将函数体直接插入到调用点处,这可能导致编译后的代码体积增大,从而可能增加缓存未命中的概率,降低程序的执行效率。
不适用于复杂函数:内联函数通常只适用于简单的小函数,对于复杂的大型函数,内联可能会导致代码膨胀和编译时间增加。
内联函数的声明和定义:内联函数必须在调用之前被声明或定义,否则编译器无法知道它是内联函数。同时,内联函数的定义通常放在头文件中,以便在多个源文件中共享。
下面是一个简单的内联函数样例:

#include <iostream>  
  
// 声明内联函数  
inline int add(int x, int y) 
{  
    return x + y;  
}  
  
int main() 
{  
    // 调用内联函数  
    int result = add(1, 2);  
    std::cout << "the result is: " << result << std::endl;  
    return 0;  
}

由于 add 函数被定义为内联函数,编译器在编译时会尝试将 add 函数的调用替换为函数体的直接拷贝。这意味着在 main 函数中调用 add(1, 2) 时,编译器可能会直接将其替换为 return 1 + 2; ,从而消除函数调用的开销。

面试题4:什么是 const 函数和 const 参数?

const 函数
const 函数是指那些不会修改任何成员变量的函数。也就是说, const 成员函数只能调用其他的 const 成员函数,并且不能修改任何类的成员变量(除非这些变量被声明为 mutable )。
这样的函数通常在两种情况下使用:
(1)不希望修改对象的状态:例如,你可能有一个代表矩形的类,该类有一个计算面积的方法。这个方法不应该修改矩形的任何属性(如长度或宽度),因此,它可以被声明为 const 。
(2)对象是一个常量: const 修饰的对象只能调用该对象的 const 成员函数。
下面是一个 const 函数的例子:

class MyClass {  
public:  
    int getVal() const 
	{  
		//m_val=2;		//错误: const 函数不能修改任何成员变量
        return m_val;  
    }  
  
private:  
    int m_val;  
};

在这个例子中, getVal 函数是 MyClass 类的一个成员函数,并且被声明为 const 函数。由于 getVal 是 const 的,它不能修改类的任何成员变量(除非这些变量被声明为 mutable )。需要注意的是, const 函数并不意味着函数的返回值不能被修改。实际上, const 函数可以返回任何类型的值,包括可以被修改的类型。 const 关键字仅表示该函数不会修改调用它的对象的状态。
const参数
const 参数是指那些在函数体内不能被修改的参数。当将一个参数声明为 const 时,意味着这个参数在函数体内不会被修改,这有助于编译器进行更好的优化。
此外, const 参数也可以提高代码的可读性和可维护性,因为它清晰地表明了这个参数在函数体内不会被修改。
下面是一个使用 const 参数的例子:

int getStrLength(const std::string& str) // const参数
{ 
    return str.length(); // 不能修改str
}

在这个例子中, str 是一个 const 引用参数,这意味着不能在 getStrLength 函数内部修改 str 。如果尝试这样做,编译器会报错。
总结, const 函数和 const 参数都是 C++ 中重要的编程概念,它们有助于编写更安全、更可读的代码,并允许编译器进行更好的优化。

面试题5:函数返回多个值的方法有哪些?

在C++中,函数通常只能返回一个值。然而,有几种方法可以模拟函数返回多个值的情况:
(1)使用结构体或类
可以创建一个结构体或类来封装多个值,并将这个结构体或类作为函数的返回值。这样,函数就可以"返回"多个值,实际上是返回了一个包含多个值的对象。

struct MyStruct 
{
    int val1;
    double val2;
};

MyStruct getMultipleValues() 
{
    MyStruct res;
    res.val1 = 1;
    res.val2 = 1.2;
    return res;
}

(2)使用指针或引用
可以通过指针或引用传递一个变量到函数中,让函数修改这个变量的值。此时,函数"返回"多个值的方式是通过修改这些变量的值。

void getMultipleValues(char* val1, double& val2) 
{
    *val1 = 1;
    val2 = 1.2;
}

int main() {
    int val1;
    double val2;
    getMultipleValues(val1, val2);
    // 现在 val1, val2 包含了函数返回的值
}

(3)使用 std::tuple
C++11 引入了 std::tuple ,它允许将不同类型的值组合在一起。可以返回一个 std::tuple ,并通过 std::tie 或解构赋值来获取返回值。

#include <tuple>

std::tuple<int, double> getMultipleValues()
{
    return std::make_tuple(1, 1.2);
}

int main() {
    auto result = getMultipleValues();
    int val1;
    double val2;
    std::tie(val1, val2) = result;
    // 现在 val1, val2 包含了函数返回的值
}

(4)使用 std::pair
对于两个值的情况,可以使用 std::pair 。

#include <utility>

std::pair<int, double> getMultipleValues() {
    return std::make_pair(1, 1.2);
}

int main() 
{
    auto result = getMultipleValues();
    int val1 = result.first;
    double val2 = result.second;
    // 现在 val1, val2 包含了函数返回的值
}

(5)使用 std::array 或 std::vector
对于固定大小或可变大小的多个相同类型的值,可以使用 std::array 或 std::vector 。

#include <array>

std::array<int, 2> getMultipleValues() {
    return {1, 2};
}

int main() {
    auto res = getMultipleValues();
    int val1 = res[0];
    int val2 = res[1];
    // 现在 val1, val2 包含了函数返回的值
}

在实际编程中,选择哪种方法取决于具体的使用场景和个人偏好。 std::tuple 和 std::pair 通常用于返回不同类型值的场景,而结构体或类则更适合于返回逻辑上紧密相关的多个值。使用引用或指针作为输出参数可以避免额外的内存分配开销,但可能会使代码更难阅读和理解。

02-26 16:30