面试题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)默认构造函数
默认构造函数是一种没有参数的构造函数。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。如果类定义了其他构造函数,但没有定义默认构造函数,编译器就不会自动生成默认构造函数,如下为样例代码:

class Animal1
{
};

class Animal2
{
public:
	Animal2(string name) {}
};

Animal1 animal1;		// OK:类中没有定义任何构造函数,编译器会自动生成一个默认构造函数 

Animal2 animal2;		// 错误:类定义了其他构造函数( Animal2(string name) ),但没有定义默认构造函数,编译器就不会自动生成默认构造函数 
Animal2 animal2("aa");	// OK:

(2)参数化构造函数
参数化构造函数是带有参数的构造函数。它可以有一个或多个参数,用于在创建对象时初始化对象的成员变量。如下为样例代码:

class Animal
{
public:
	Animal(string name) {}
};

Animal animal("aa");	// OK:

(3)拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它使用一个已存在的对象来初始化新创建的对象。它的参数是对同类型对象的常量引用。如果类没有显式定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数。如下为样例代码:

#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 的列表初始化特性需要该种类型构造函数的支持。如下为样例代码:

#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 及以后的版本支持委托构造函数。如下为样例代码:

#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 等可能包含动态分配资源的类型时。如下为样例代码:

#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:类的访问修饰符权限如何划分

其中值得注意的是 protected 访问修饰符的用法:该访问修饰符在设计继承层次结构时特别有用。它允许派生类访问基类的实现细节,同时仍然保持对这些细节的封装和隐藏。这有助于实现更灵活和可维护的代码结构。
注意 protected 和 private 的区别:
如果其他类从该类派生(无论是公开派生还是私有派生),那么派生类中的成员函数可以访问基类中的 protected 成员。这是 protected 与 private 的区别,因为 private 成员在派生类中是不可访问的。

面试题28:this指针在C++中是如何工作的

this 是一个特殊的指针,它代表对象自身。this 指针是隐式传递给每个成员函数的,它允许成员函数访问和修改调用它的对象的成员。this 指针在成员函数内部是自动可用的,不需要显式声明。它通常用于区分成员变量和参数名称相同的情况,或者在函数内部引用当前对象的其他成员:

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 指针。

02-12 15:06