面试题 1 :解释一下 C++ 中的类是什么,它有哪些基本特性?
C++ 中的类(class)是面向对象程序设计的基本构成单位,它是一种自定义的数据类型,用于封装数据以及操作这些数据的方法。类是创建对象的模板,它定义了对象的属性和行为。对象是根据类创建的实例。
类具有以下基本特性:
封装
封装是将数据(也称为属性或成员变量)和操作这些数据的方法(也称为成员函数或方法)结合在一起的过程。这样,数据就被隐藏或封装在类内部,只能通过类提供的方法进行访问和修改。这有助于保护数据免受外部代码的非法访问和修改,同时也提高了代码的可维护性和可重用性。
继承
继承是从已有的类(称为父类或基类)创建新类(称为子类或派生类)的过程。子类继承了父类的所有属性和方法,同时还可以定义自己的新属性和方法。这样,可以重用已有的代码,提高代码的可重用性和可扩展性。
多态
多态是指允许一个接口(或函数名)有多种实现方式。在 C++ 中,多态通常通过虚函数(virtual functions)实现。子类可以重写父类的虚函数,从而实现多态。这样,在运行时可以根据对象的实际类型调用相应的函数,提高了代码的灵活性和可扩展性。
抽象
抽象是一种隐藏具体实现细节的机制,只展示必要的信息给用户。在 C++ 中,可以通过抽象类(含有至少一个纯虚函数的类)来实现抽象。抽象类不能被实例化,只能被其他类继承。这样,可以定义一种通用的接口或行为,让子类去实现具体的细节。
总的来说,C++ 中的类是一种强大的工具,它使得代码更加模块化、可重用、可维护和可扩展。通过封装、继承、多态和抽象等特性,类可以更好地模拟现实世界中的事物和行为,从而实现更加复杂和灵活的程序设计。
面试题 2 :C++ 中的友元函数和友元类是什么?它们有什么作用?
在 C++ 中,友元(Friend)是一种特殊的机制,它允许某些非成员函数或非类成员访问类的私有(private)和保护(protected)成员。这种机制打破了数据的封装和隐藏,因此应当谨慎使用。
友元函数
友元函数是一种特殊的非成员函数,它可以访问一个类的私有成员和保护成员。要在类内部声明一个友元函数,只需在类定义中使用 friend 关键字,后面跟着函数声明。友元函数不是类的成员,但可以在其定义中访问类的私有成员。如下为样例代码:
#include <iostream> // 定义一个类,其中包含私有成员变量和友元函数
class MyClass
{// 声明一个友元函数 friend void showMyClassPrivate(MyClass& obj);public:MyClass(int value) : privateData(value) {}private:int privateData; // 私有成员变量
};// 定义友元函数
void showMyClassPrivate(MyClass& obj)
{// 由于是友元函数,可以直接访问 MyClass 的私有成员 std::cout << "MyClass privateData: " << obj.privateData << std::endl;
}int main()
{MyClass myObject(1);// 不能直接访问私有成员 // std::cout << myObject.privateData << std::endl; // 这将导致编译错误 // 可以使用友元函数来访问私有成员 showMyClassPrivate(myObject);return 0;
}
在上面代码中,MyClass 有一个私有成员变量 privateData。正常情况下,外部代码无法直接访问这个私有成员。然而,代码里面声明了一个友元函数 showMyClassPrivate,并在这个函数内部访问了 MyClass 的私有成员 privateData。
当运行这个程序时,showMyClassPrivate 函数能够打印出 MyClass 实例 myObject 的私有成员 privateData 的值,尽管这个成员在正常情况下是不可访问的。
友元类
友元类是一种特殊的类,它可以访问另一个类的私有成员和保护成员。要在类内部声明一个友元类,只需在类定义中使用 friend 关键字,后面跟着类名。友元关系不具有传递性,即如果类 A 是类 B 的友元,类 B 是类 C 的友元,这并不意味着类 A 是类 C 的友元。如下为样例代码:
#include <iostream> // 第一个类,包含私有成员变量和一个友元类声明
class MyClass
{// 声明一个友元类 friend class MyFriendClass;public:MyClass(int value) : privateData(value) {}private:int privateData; // 私有成员变量
};// 友元类定义
class MyFriendClass
{
public:// 该函数可以访问MyClass的私有成员,因为MyFriendClass是MyClass的友元 void showMyClassPrivate(const MyClass& obj) {std::cout << "MyClass privateData: " << obj.privateData << std::endl;}
};int main()
{MyClass myObject(1);MyFriendClass friendObject;// 不能直接访问私有成员 // std::cout << myObject.privateData << std::endl; // 这将导致编译错误 // MyFriendClass可以访问MyClass的私有成员 friendObject.showMyClassPrivate(myObject);return 0;
}
在这个例子中,MyClass 有一个私有成员变量 privateData。正常情况下,外部代码无法直接访问这个私有成员。然而,代码里面声明了一个友元类 MyFriendClass,并在这个类的成员函数中访问了 MyClass 的私有成员 privateData。
当运行这个程序时,showMyClassPrivate 函数能够打印出 MyClass 实例 myObject 的私有成员 privateData 的值,尽管这个成员在正常情况下是不可访问的。
作用
封装破坏:在某些情况下,可能需要破坏封装性以允许某些函数或类访问私有成员。例如,当操作符重载需要与类的私有成员交互时。
简化代码:在某些情况下,将函数声明为友元可以避免编写冗长的 getter 和 setter 方法。
提高性能:由于友元函数可以直接访问类的私有成员,因此可能会提高一些操作的性能。然而,这通常不是使用友元的主要原因,因为性能通常不是C++编程中的首要考虑因素。
注意事项
使用友元函数和友元类时要小心,因为它们可能会破坏封装性并导致代码难以维护。
在大多数情况下,更好的做法是使用公共接口(如 getter 和 setter 方法)来访问类的私有成员,而不是使用友元。
如果确实需要使用友元,请确保充分理解其潜在的风险和后果,并仔细考虑是否有其他更合适的替代方案。
面试题 3 :解释一下 C++ 中的静态成员变量和静态成员函数是什么,它们与普通成员变量和成员函数有什么区别?
C++ 中的静态成员变量和静态成员函数是类的特殊成员,它们与普通成员变量和成员函数的主要区别在于它们的生命周期和作用域,以及它们的共享性质。
静态成员变量
静态成员变量是类的所有对象共享的变量。这意味着,无论创建多少个类的实例,都只有一个静态成员变量的副本。静态成员变量在内存中只存储一次,而不是为每个对象分别存储。
特点
(1)生命周期: 静态成员变量的生命周期是整个程序的运行期间,而不是局限于某个对象的生命周期。
(2)作用域: 静态成员变量的作用域是在类的内部和外部都可以访问。
(3)共享性: 所有对象共享同一个静态成员变量。
(4)初始化: 静态成员变量必须在类外部进行初始化,通常是在类的实现文件中。
样例
class MyClass
{
public: static int val; // 声明静态成员变量
}; int MyClass::val = 0; // 定义并初始化静态成员变量
静态成员函数
静态成员函数属于类本身,而不是类的某个特定对象。因此,静态成员函数不能访问类的非静态成员变量和非静态成员函数,因为它们需要类的实例来访问。
特点
(1)作用域: 静态成员函数可以在类的内部和外部访问。
(2)不依赖对象: 静态成员函数不需要类的实例就可以调用。
(3)不能访问非静态成员: 静态成员函数不能直接访问类的非静态成员变量和非静态成员函数。
样例
class MyClass
{
public: static void printVal() { std::cout << "val: " << val << std::endl; } static int val;
}; int MyClass::val = 0; int main() { MyClass::printVal(); // 调用静态成员函数,不需要对象实例 return 0;
}
与普通成员变量和成员函数的区别
(1)生命周期和作用域: 普通成员变量和成员函数的生命周期和作用域都局限于类的某个对象。当对象被销毁时,其成员变量也会被销毁。而静态成员变量和静态成员函数在整个程序运行期间都存在,并且可以在类的外部访问。
(2)共享性: 普通成员变量是每个对象都拥有的独立副本。而静态成员变量是所有对象共享的,无论创建多少个对象,都只有一个静态成员变量的副本。
(3)访问限制: 静态成员函数不能访问类的非静态成员变量和非静态成员函数,因为它们需要类的实例来访问。而普通成员函数可以访问类的所有成员(包括静态和非静态的)。
(4)调用方式: 普通成员函数需要通过类的对象来调用,而静态成员函数可以直接通过类名来调用,不需要对象实例。
面试题 4 :解释一下 C++ 中的虚函数和纯虚函数是什么,它们有什么区别?
在 C++ 中,虚函数(Virtual Functions)和纯虚函数(Pure Virtual Functions)是面向对象编程中多态性的关键概念。它们都与类的继承和派生类重写基类中的函数有关。
虚函数(Virtual Functions)
虚函数是一种特殊的成员函数,它允许派生类重写基类中的函数。当你在基类中声明一个函数为虚函数时,任何从该基类派生的类都可以重写这个函数。在运行时,根据对象的实际类型(而不是它的声明类型)来调用正确的函数版本。这被称为动态绑定或运行时多态性。
特点
(1)声明方式: 使用 virtual 关键字声明。
(2)实现: 在基类中提供实现(函数体)。
(3)派生类重写: 派生类可以重写虚函数,也可以不重写。
(4)实例调用: 通过基类指针或引用来调用虚函数时,会调用实际对象的版本。
样例
#include <iostream> class Base
{
public:virtual void func() {std::cout << "Base::func()" << std::endl;}
};class Derived : public Base
{
public:void func() override {std::cout << "Derived::func()" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->func(); // 输出 "Derived::func()" delete ptr;return 0;
}
纯虚函数(Pure Virtual Functions)
纯虚函数是一种特殊的虚函数,它在基类中没有实现(没有函数体)。纯虚函数要求所有派生类都提供实现。包含纯虚函数的类被称为抽象类,不能直接实例化。纯虚函数通常用于定义接口或定义基类中的行为,而让派生类来决定具体的实现。
特点
(1)声明方式: 使用 = 0 在函数声明后指定。
(2)实现: 在基类中没有实现(没有函数体)。
(3)派生类重写: 派生类必须重写纯虚函数。
(4)实例调用: 抽象类不能直接实例化,因此不能直接调用纯虚函数。
样例
#include <iostream> class AbstractBase
{
public:virtual void func() = 0; // 纯虚函数
};class Derived : public AbstractBase
{
public:void func() override{std::cout << "Derived::bar()" << std::endl;}
};int main()
{AbstractBase* ptr = new Derived();ptr->func(); // 输出 "Derived::func()" delete ptr;return 0;
}
区别
(1)实现: 虚函数在基类中有实现,而纯虚函数在基类中没有实现。
(2)派生类要求: 派生类可以选择是否重写虚函数,但必须重写纯虚函数。
(3)实例化: 包含虚函数的类可以实例化,而包含纯虚函数的类(抽象类)不能直接实例化。
(4)用途: 虚函数主要用于实现多态性,而纯虚函数主要用于定义接口和抽象基类。
面试题 5 :什么是 C++ 中的 RAII(资源获取即初始化)技术?它有什么作用?
RAII(Resource Acquisition Is Initialization)是 C++ 中的一种编程技术,也被称为资源获取即初始化。这种技术的核心思想是将资源的生命周期与对象的生命周期绑定在一起,以确保资源在不再需要时能够自动释放。
在 C++ 中,资源的概念非常广泛,可以包括内存、文件句柄、网络连接、互斥锁等等。这些资源在使用完毕后通常需要显式地释放,否则可能会导致内存泄漏、资源耗尽等问题。然而,手动管理资源的释放往往容易出错,尤其是在复杂的程序中,很容易忘记释放资源或者多次释放同一资源。
RAII 技术通过将资源的获取与对象的初始化绑定在一起,自动管理资源的释放。具体来说,当创建一个对象时,该对象的构造函数会负责获取所需的资源;当对象离开其作用域或被销毁时,其析构函数会自动释放这些资源。这样,资源的释放就与对象的生命周期紧密地联系在一起,不再需要程序员手动管理。
RAII 技术的主要作用包括:
(1)简化资源管理:通过自动管理资源的释放,程序员无需关心资源的生命周期,从而降低了出错的可能性。
(1)防止资源泄漏:由于资源的释放与对象的生命周期绑定,当对象被销毁时,其析构函数会自动释放资源,从而避免了资源泄漏的问题。
(1)提高代码的可读性和可维护性:通过将对资源的获取和释放封装在对象的构造函数和析构函数中,代码更加简洁和清晰,也更容易理解和维护。
在 C++ 中,许多标准库和框架都采用了 RAII 技术来管理资源,例如智能指针(如 std::unique_ptr、std::shared_ptr 等)、文件流(如 std::ifstream、std::ofstream 等)、锁(如 std::mutex、std::lock_guard 等)等。这些类和对象都使用了 RAII 技术来自动管理资源的获取和释放。