一、C++中静多态和动多态的区别
在 C++ 里,多态是一个关键特性,它允许你以统一的方式处理不同类型的对象。多态可分为静多态和动多态,下面为你详细介绍:
静多态
静多态也叫编译时多态,其在编译阶段就确定要调用的函数。静多态主要借助函数重载和模板来实现。
函数重载
函数重载指的是在同一作用域内定义多个同名函数,但这些函数的参数列表不同。编译器会依据调用时传入的参数类型和数量,在编译阶段确定要调用的具体函数。
#include <iostream>
using namespace std;
// 函数重载示例
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int main() {
int result1 = add(1, 2);
double result2 = add(1.5, 2.5);
cout << "Result1: " << result1 << endl;
cout << "Result2: " << result2 << endl;
return 0;
}
模板
模板是 C++ 的一种强大特性,它能够编写通用的代码,在编译时依据实际使用的类型生成具体的代码。
#include <iostream>
using namespace std;
// 模板示例
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int result1 = add(1, 2);
double result2 = add(1.5, 2.5);
cout << "Result1: " << result1 << endl;
cout << "Result2: " << result2 << endl;
return 0;
}
动多态
动多态也叫运行时多态,它在运行阶段才确定要调用的函数。动多态主要通过虚函数和继承来实现。
#include <iostream>
using namespace std;
// 基类
class Shape {
public:
// 虚函数
virtual void draw() {
cout << "Drawing a generic shape." << endl;
}
};
// 派生类
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a circle." << endl;
}
};
// 派生类
class Square : public Shape {
public:
void draw() override {
cout << "Drawing a square." << endl;
}
};
int main() {
Circle circle;
Square square;
// 基类指针指向派生类对象
Shape* shape1 = &circle;
Shape* shape2 = □
// 运行时确定调用的函数
shape1->draw();
shape2->draw();
return 0;
}
两者的区别:
绑定时间:静多态在编译阶段完成绑定,动多态在运行阶段完成绑定
性能:静多态由于在编译阶段就确定调用的函数,所以没有额外的运行时开销;而动多态需要在运行时查找虚函数表,会有一些性能损失。
灵活性:动多态更灵活,能在运行时依据对象的实际类型来确定调用的函数;静多态则需要在编译时就明确类型。
二、C++中的空类默认产生哪些成员函数
在C++中,当你定义一个空类,编译器会为这个类自动地生成一些特殊的成员函数。这些成员函数通常被称为“编译器生成的成员函数”或“默认成员函数”。以下是这些成员函数:
1、默认构造函数 (Default Constructor):
如果你没有为类定义任何构造函数,编译器会为你生成一个默认构造函数。这个构造函数不接受任何参数,并且不执行任何操作(除了可能的内存初始化,这取决于编译器和编译选项)。
2、 析构函数
析构函数在对象生命周期结束时被调用,负责释放对象占用的资源。如果没有显式定义析构函数,编译器会生成默认析构函数,其函数体为空。
3、拷贝构造函数
拷贝构造函数 (Copy Constructor): 这个构造函数用于创建一个新对象,作为现有对象的副本。如果你没有定义拷贝构造函数,编译器会为你生成一个。
4、拷贝赋值运算符
拷贝赋值运算符用于将一个对象的值赋给另一个已存在的对象。若没有显式定义拷贝赋值运算符,编译器会生成默认拷贝赋值运算符,它同样会按成员逐个复制对象的数据。
5、移动构造函数 (Move Constructor) 和 移动赋值运算符 (Move Assignment Operator):
从C++11开始,编译器还会为类生成移动构造函数和移动赋值运算符(如果类中有指针成员或可以高效移动的资源)。这些函数用于支持对象的移动语义,而不是传统的拷贝语义。
移动构造函数用于将一个临时对象的资源转移到新对象,避免不必要的拷贝操作。如果没有显式定义移动构造函数,编译器会在满足一定条件时生成默认移动构造函数。
class EmptyClass {
public:
// 默认移动构造函数
EmptyClass(EmptyClass&& other) noexcept {
// 转移资源
}
};
移动赋值运算符用于将一个临时对象的资源转移到已存在的对象,避免不必要的拷贝操作。如果没有显式定义移动赋值运算符,编译器会在满足一定条件时生成默认移动赋值运算符。
class EmptyClass {
public:
// 默认移动赋值运算符
EmptyClass& operator=(EmptyClass&& other) noexcept {
if (this != &other) {
// 转移资源
}
return *this;
}
};
需要注意的是,这些编译器生成的函数通常是隐式的,也就是说,它们不会在代码中明确地出现。然而,你可以通过定义你自己的版本来覆盖它们。此外,在某些情况下(例如,当类中有引用成员或删除的函数时),编译器可能不会生成所有这些函数。
三、C++中抽象类定义接口的好处:
1.明确约定与规范:
通过定义抽象类,可以明确地指定一个类应该提供哪些功能,即定义了一个接口。这使得其他类在实现这个接口时,必须遵循一定的规范,实现特定的方法或属性。这有助于保证代码的清晰性和一致性,使得不同开发者之间的合作更加顺畅。
2、多态性的基础:
抽象类是实现多态性的重要手段之一。通过基类的指针或引用,可以指向派生类的对象,并调用其实现的接口方法。这种机制使得程序在运行时能够根据对象的实际类型来执行相应的操作,从而增强了程序的灵活性和扩展性。
3、隐藏实现细节:
抽象类只定义了接口,而不关心具体的实现细节。这使得使用者只需关心接口提供的功能,而无需了解底层的实现细节。这有助于降低代码的耦合度,提高代码的可维护性和可重用性。
4、强制实现:
当一个类继承自抽象类时,它必须提供抽象类中所有纯虚函数的实现。这确保了派生类不会遗漏任何必要的功能,从而提高了代码的可靠性。
5、代码复用:
通过继承抽象类,多个派生类可以共享相同的接口,从而实现了代码的复用。这避免了在多个类中重复编写相同的代码,提高了开发效率。
6、易于扩展:
当需要添加新功能时,只需在抽象类中新增一个纯虚函数,并要求所有派生类实现它即可。这样,所有使用基类指针或引用的代码都可以无缝地支持新功能,而无需修改现有代码。
四、指针和引用的区别
C语言有指针,C++兼容C,所以C++也有指针,但指针使用起来较为复杂,容易出错,C++发明一个更好用的引用.引用可能是通过指针来实现的,但它比指针简单,使用也更方便,在C++中尽量避免使用指针,可以使用引用,智能指针或容器来替代指针.
具体区别如下:
1.引用在定义时必须初始化,而指针不必须.
2.指针的指向可以改变,而引用不能修改.(注意值可以改)
3.sizeof大小不同.
sizeof(指针)在32位平台4字节,64位平台8字节;
sizeof(引用)就是求这个变量本身的字节数.
4.有空指针和野指针,但没有空引用.所以引用相对更安全.
五、内联函数和宏的区别
- 内联函数在运行时可以调试,宏不能
- 编译器会对内联函数进行自动检查,而宏不能
- 内联函数可以访问类的成员变量,宏不能。
- 在类中声明的同时定义的函数,可以自动转化为内联函数。
- 内联函数增加了安全性,和效率。Assert是宏不是函数
- 如果函数体内代码较长,不宜使用内联函数。函数体内如果有循环也不使用,开销比调用函数的大。
const和宏的区别:
const是在堆栈分配空间,而宏是直接传递到变量本身,生命周期在编译阶段。const常量存在于程序的数据段,#define常量存在于程序的代码段。
Typedef和宏的区别:
前者增加了可读性,在编译阶段,只是起了一个别名,后者只是宏替换,发生在预编译阶段
六、sizeof一个空类是多少,含有普通成员函数呢,含有虚函数呢,含有static函数或者static变量呢?
答:
- 空类至少有一个字节的空间,类要实例化,必须有地址,所以隐含了一个地址。也是为了方便寻址。
- 普通成员函数不会影响类的大小。因为普通成员函数是类的一部分,其代码存于代码段,而非对象的内存空间里。含有普通成员函数后:还是1
- 含有虚函数是4个字节(32位)(或8个字节(64位),根据平台的大小而定),因为有虚函数表指针。这是因为编译器会为包含虚函数的类创建一个虚函数表(VTable),并且在每个对象里添加一个指向虚函数表的指针(vptr)。在 32 位系统中,指针大小为 4 字节;在 64 位系统中,指针大小为 8 字节。
- static静态函数和static静态变量也不影响类的大小。静态成员函数和静态成员变量不属于类的对象,而是属于类本身。它们存储在全局数据区,并非对象的内存空间里。所以,含有静态成员函数或静态成员变量的类的大小和空类相同,至少为 1 字节。
以上为 第一部分的C++方向面试常考题目,如果觉得有帮助可以点赞支持一下,会持续更新输出有用的内容,感兴趣可以关注我!