面试题24:什么是面向对象编程
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式或编程模型,它基于对象的概念来设计和实现程序。在面向对象编程中,程序是由一系列对象组成的,这些对象是对现实世界中的实体或抽象概念的软件表示。每个对象都有其特定的属性和行为,这些属性和行为分别由对象的成员变量(或称为属性、字段等)和成员函数(或称为方法)来定义。
面向对象编程的三大基本特性是:封装( Encapsulation )、继承( Inheritance )和多态( Polymorphism )。
(1)封装(Encapsulation)
封装是指将对象的属性和方法隐藏在其内部,只通过有限的接口与外部进行交互。这样可以防止外部代码随意访问和修改对象的内部数据,从而提高代码的安全性和可维护性。
(2)继承(Inheritance)
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法,从而实现代码的重用。子类可以继承父类的所有非私有属性和方法,并可以添加或覆盖父类的方法。 C++ 支持三种类型的继承:公有继承( public )、保护继承( protected )和私有继承( private )。其中,公有继承是最常用的继承方式。
(3)多态(Polymorphism)
多态是指允许一个成员函数被多种数据类型实现(重载),或者一个成员函数在不同场景下有不同实现方式(重写)。多态性允许使用基类的指针或引用来调用派生类中的成员函数,在运行时根据实际对象的类型来确定调用哪个函数,从而增强了程序的灵活性和可扩展性。
面试题25:面向对象编程的优点有哪些
(1)通过继承增强代码的可重用性:可以很方便地重用父类的属性与方法。
(2)通过封装增强代码的可维护性:可以隐藏对象的内部实现细节,只通过有限的接口与外部进行交互,从而降低代码的耦合度,提高代码的可维护性。
(3)通过多态增强代码的可扩展性:可以在不修改现有代码的情况下,增加新的功能或重构现有功能的行为。
面试题26:类的构造函数有哪些种类
根据构造函数的参数列表和特性,可以将构造函数分为 6 种不同的类型:
(1)默认构造函数
默认构造函数是一种没有参数的构造函数。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。如果类定义了其他构造函数,但没有定义默认构造函数,编译器就不会自动生成默认构造函数,如下为样例代码:
cpp
class Animal1
{
};
class Animal2
{
public:
Animal2(string name) {}
};
Animal1 animal1; // OK:类中没有定义任何构造函数,编译器会自动生成一个默认构造函数
Animal2 animal2; // 错误:类定义了其他构造函数( Animal2(string name) ),但没有定义默认构造函数,编译器就不会自动生成默认构造函数
Animal2 animal2("aa"); // OK:
(2)参数化构造函数
参数化构造函数是带有参数的构造函数。它可以有一个或多个参数,用于在创建对象时初始化对象的成员变量。如下为样例代码:
cpp
class Animal
{
public:
Animal(string name) {}
};
Animal animal("aa"); // OK:
(3)拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它使用一个已存在的对象来初始化新创建的对象。它的参数是对同类型对象的常量引用。如果类没有显式定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数。如下为样例代码:
cpp
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
printf("call Animal()\n");
}
Animal(const Animal& animal)
{
printf("call Animal(const Animal& animal)\n");
}
};
int main() {
Animal animal1; // 调用无参数的构造函数
Animal animal2(animal1); // 调用拷贝构造函数
return 0;
}
上面代码的输出为:
call Animal()
call Animal(const Animal& animal)
(4)列表初始化构造函数
列表初始化构造函数使用成员初始化列表来初始化对象的数据成员,C++11 的列表初始化特性需要该种类型构造函数的支持。如下为样例代码:
cpp
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal(const string& name) : m_name(name)
{
printf("call Animal(const string& name) : m_name(name)\n");
}
private:
string m_name;
};
int main() {
Animal animal{"aa"}; // 调用列表初始化构造函数
return 0;
}
上面代码的输出为:
call Animal(const string& name) : m_name(name)
(5)委托构造函数
委托构造函数是一种特殊的构造函数,它调用同类的另一个构造函数来执行初始化。这可以通过使用类名并跟随参数列表来实现。C++11 及以后的版本支持委托构造函数。如下为样例代码:
cpp
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal() : Animal("default")
{
printf("Animal() : Animal(\"default\")\n");
}
Animal(const string& name) : m_name(name)
{
printf("call Animal(const string& name) : m_name(name)\n");
}
private:
string m_name;
};
int main() {
Animal animal;
return 0;
}
上面代码的输出为:
call Animal(const string& name) : m_name(name)
Animal() : Animal("default")
注意:先调用的是委托构造函数。
(6)移动构造函数
移动构造函数是一种特殊的构造函数,它使用右值引用参数将资源从一个对象移动到另一个对象,而不是复制。这通常用于优化性能,特别是在处理如 vector 、 string 等可能包含动态分配资源的类型时。如下为样例代码:
cpp
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal()
{
printf("call Animal()\n");
}
Animal(const Animal& animal)
{
printf("call Animal(const Animal& animal)\n");
}
Animal(Animal&& other) noexcept //移动构造函数
{
printf("call Animal(Animal&& other)\n");
}
};
int main() {
Animal animal1; // 调用无参数构造函数
Animal animal2 = animal1; // 调用拷贝构造函数
Animal animal3 = move(animal1); // 调用移动构造函数
return 0;
}
上面代码的输出为:
call Animal()
call Animal(const Animal& animal)
call Animal(Animal&& other)
面试题27:类的访问修饰符权限如何划分
本类 | 继承类 | 其他 | |
---|---|---|---|
private | √ | × | × |
protected | √ | √ | × |
public | √ | √ | √ |
其中值得注意的是 protected 访问修饰符的用法:该访问修饰符在设计继承层次结构时特别有用。它允许派生类访问基类的实现细节,同时仍然保持对这些细节的封装和隐藏。这有助于实现更灵活和可维护的代码结构。
注意 protected 和 private 的区别:
如果其他类从该类派生(无论是公开派生还是私有派生),那么派生类中的成员函数可以访问基类中的 protected 成员。这是 protected 与 private 的区别,因为 private 成员在派生类中是不可访问的。
面试题28:this指针在C++中是如何工作的
this 是一个特殊的指针,它代表对象自身。this 指针是隐式传递给每个成员函数的,它允许成员函数访问和修改调用它的对象的成员。this 指针在成员函数内部是自动可用的,不需要显式声明。它通常用于区分成员变量和参数名称相同的情况,或者在函数内部引用当前对象的其他成员:
cpp
class Animal
{
public:
Animal(string name)
{
//name = name; // 如果这里不使用 this ,则无法分别这个变量 name 是入参还是成员变量
this->name = name;
}
public:
string getName()
{
return this->name; // 此处的 this 可以用也可以不用
}
private:
string name;
};
在上面的代码中,构造函数 Animal(string name)
和成员函数 getName()
都使用了 this 指针。在构造函数 Animal(string name)
中,this->name
表示对象的 name
成员变量,而构造函数的入参也是 name
。使用了 this 指针可以使得代码更加清晰,尤其是在成员变量和参数名称相同或相似的情况下。
需要注意的是,在大多数情况下,this 指针的使用是隐式的,编译器会自动处理。只有在需要区分成员变量和函数参数,或者需要在成员函数中显式引用当前对象时,才需要显式地使用 this 指针。