Day5: 5道C++ 面向对象高频题整理

1、强制类型转换运算符

在C++中,强制类型转换运算符(也称为类型转换操作符)用于在不同类型之间进行显式的类型转换。C++提供了四种类型的强制类型转换运算符,分别是static_cast、dynamic_cast、reinterpret_cast和const_cast。

这些强制类型转换运算符的用法如下:

  • static_cast:用于进行静态类型转换,可以将一种类型的值转换为另一种类型,前提是转换是合法的。例如,可以将一个整数转换为浮点数、将指针或引用进行类型转换等。静态类型转换在编译时进行检查,因此需要开发人员保证转换的安全性。
    int x = 10;
    double y = static_cast<double>(x); // 将整数x转换为浮点数
    
    Base* basePtr = new Derived(); // Derived是Base的派生类
    Derived* derivedPtr = static_cast<Derived*>(basePtr); // 将基类指针转换为派生类指针
    

  • dynamic_cast:用于进行动态类型转换,主要用于处理多态情况下的类型转换。它可以在运行时检查对象的实际类型,并进行安全的向下转型(派生类到基类)或跨继承层次的类型转换。如果转换是不安全的,dynamic_cast将返回空指针(对于指针转换)或抛出std::bad_cast异常(对于引用转换)。
Base* basePtr = new Derived(); // Derived是Base的派生类
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 安全地将基类指针转换为派生类指针

Base* basePtr2 = new Base();
Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2); // 转换失败,derivedPtr2将为nullptr
  • reinterpret_cast:用于进行低级别的类型转换,可以将任何指针或引用转换为其他类型的指针或引用,甚至没有关联的类型。reinterpret_cast通常用于处理底层的二进制数据表示,但它的使用需要非常谨慎,因为它会绕过类型系统的安全检查。
int x = 10;
void* voidPtr = reinterpret_cast<void*>(&x); // 将int指针转换为void指针

Derived* derivedPtr = new Derived();
Base* basePtr = reinterpret_cast<Base*>(derivedPtr); // 将派生类指针转换为基类指针
  • const_cast:用于去除表达式的常量性,可以将const或volatile限定符添加或删除。const_cast主要用于修改指针或引用的底层const属性,但是不能用于修改本身是常量的对象。
const int x = 10;
int* nonConstPtr = const_cast<int*>(&x); // 去除常量性,但修改非常量对象是未定义行为

const int y = 20;
int& nonConstRef = const_cast<int&>(y); // 去除常量性,但修改非常量对象是未定义行为

需要注意的是,尽管强制类型转换运算符可以用于特定的类型转换,但过度使用它们可能会导致代码难以理解和维护。因此,在使用强制类型转换运算符时,应当谨慎并确保转换的合理性和安全性。

2.简述类成员函数的重写、重载和隐藏的区别

在C++中,类成员函数的重写、重载和隐藏是面向对象编程中常用的概念,它们的含义和使用方式如下:

  • 重写:在派生类中,如果基类的虚函数(virtual function)在派生类中有相同的函数签名(即函数名和参数类型),那么基类的虚函数被派生类的函数重写。这种情况下,通过基类的指针或引用调用该函数时,会执行派生类的函数,这是多态性的一种体现。
class Base {
public:
    virtual void func() { cout << "Base::func()" << endl; }
};

class Derived : public Base {
public:
    void func() override { cout << "Derived::func()" << endl; } // 重写基类的func()函数
};
  • 重载:在同一个类中,如果有两个或多个函数名相同,但参数列表(包括参数数量和类型)不同,那么这些函数就构成了重载函数。函数的返回类型不能作为重载的依据。
class MyClass {
public:
    void func() { cout << "func()" << endl; } // func()函数的一个版本
    void func(int x) { cout << "func(int)" << endl; } // func()函数的另一个版本,形成重载
};
  • 隐藏:在派生类中,如果存在与基类同名但不同参数列表的函数,那么基类中的同名函数在派生类中会被隐藏。这意味着你不能通过派生类的对象或指针来调用被隐藏的基类函数,除非显式地通过基类的名字调用。
class Base {
public:
    void func() { cout << "Base::func()" << endl; }
};

class Derived : public Base {
public:
    void func(int x) { cout << "Derived::func(int)" << endl; } // 隐藏了基类的func()函数
};

在这个例子中,如果你创建了一个Derived对象,并尝试调用func()函数(没有参数),编译器会报错,因为Derived::func(int)隐藏了Base::func()

这三种概念在实际编程中应用频繁,理解它们对于编写和理解面向对象的C++代码至关重要。

3.类型转换分为哪几种?各自有什么样的特点?

在C++中,类型转换主要分为以下几种类型:

  • 隐式类型转换(Implicit Type Conversion):编译器自动进行的类型转换。例如,从小的数值类型(如int)转换为大的数值类型(如double),或者从派生类转换为基类。隐式转换通常在赋值、函数调用、算术运算和逻辑运算等操作中发生。
int x = 10;
double y = x; // 隐式转换,从int转换为double

class Base {};
class Derived : public Base {};
Derived d;
Base& b = d; // 隐式转换,从Derived转换为Base
  • 显式类型转换(Explicit Type Conversion):由程序员显式进行的类型转换,使用C++的强制类型转换运算符,如static_cast、dynamic_cast、reinterpret_cast和const_cast。
int x = 10;
double y = static_cast<double>(x); // 显式转换,使用static_cast从int转换为double

Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 显式转换,使用dynamic_cast从Base*转换为Derived*
  • 构造函数和类型转换函数的转换:C++允许通过构造函数和类型转换函数进行类型转换。一个单参数的构造函数定义了如何从参数类型转换为类类型,而类型转换函数定义了如何从类类型转换为其他类型。
class MyComplex {
public:
    MyComplex(double real, double imag = 0) {} // 构造函数,定义了如何从double转换为MyComplex
    operator double() {} // 类型转换函数,定义了如何从MyComplex转换为double
};

double d = 1.0;
MyComplex c = d; // 通过构造函数进行转换

double d2 = static_cast<double>(c); // 通过类型转换函数进行转换

以上三种类型转换有各自的特点和适用场合。隐式类型转换简单方便,但可能导致精度丢失或意料之外的结果。显式类型转换提供了更多的控制,但需要程序员确保转换的安全性。构造函数和类型转换函数的转换则相对灵活,可以自定义转换规则,但需要更多的代码来实现。

4.RTTI是什么?其原理是什么?

RTTI,全称是”Run-Time Type Identification”,中文叫做”运行时类型信息”。它是C++的一个特性,它能让我们在程序运行的时候获取到对象的类型信息,或者说是能让我们知道一个对象是什么类型的。

RTTI的工作原理主要有两个方面:

  • type_info:这是一个类,当我们用typeid操作符去获取一个对象的类型信息时,就会得到这个类的一个实例。这个实例包含了类型的一些信息,比如类型的名字。
  • dynamic_cast:这是一个转换操作符,我们可以用它来在运行时检查一个对象是否能被安全的转换到某个类型。

举个例子吧,比如我们有个动物的基类Animal,然后有两个派生类DogCat。我们有一个Animal的指针,但是我们不知道它实际指向的是Dog还是Cat

如果我们想要调用Dog或者Cat特有的方法,我们就需要知道这个指针实际指向的是哪个类。这时候,我们就可以利用RTTI的dynamic_cast来检查这个指针能不能被转换到DogCat,如果能,那就说明这个指针指向的就是那个类。

Animal* myAnimal = getAnimalSomehow(); // 这个函数返回一个Animal指针,但是我们不知道它实际指向的是Dog还是Cat。

Dog* myDog = dynamic_cast<Dog*>(myAnimal);
if (myDog != nullptr) {
    // 如果myDog不是nullptr,说明myAnimal实际指向的是Dog,那么我们就可以安全的调用Dog的方法了。
    myDog->bark();
} else {
    // 否则,myAnimal可能指向的是Cat。
    Cat* myCat = dynamic_cast<Cat*>(myAnimal);
    if (myCat != nullptr) {
        // 如果myCat不是nullptr,说明myAnimal实际指向的是Cat,那么我们就可以安全的调用Cat的方法了。
        myCat->meow();
    }
}

这样,我们就可以利用RTTI在运行时确定对象的类型,然后做出相应的操作。

5.c++中四种cast转换

在C++中,有四种类型的类型转换运算符,或者说是”cast”。这四种转换运算符分别是:static_castdynamic_castconst_castreinterpret_cast。每一种都有它们自己的用途和限制。

  • static_cast:这是最常用的类型转换操作符。它可以在各种不同类型之间进行转换,包括基本数据类型,指针,引用等等。但是,它不能去掉const属性,也不能在没有相关继承关系的类之间进行转换。

    例如,我们可以把一个double类型的数转换为int类型:

double value = 3.14;
int intValue = static_cast<int>(value); // intValue 现在是 3
  • dynamic_cast:这个转换操作符主要用在多态类型的对象上,也就是在有继承关系的类之间进行转换。它在运行时检查转换的安全性。如果转换不安全,比如把父类对象转换为子类类型,那么转换就会失败,返回一个nullptr。

  • const_cast:这个转换操作符主要用来去掉对象的const属性。但是要注意,去掉const属性并修改对象的值是未定义行为,可能会导致程序崩溃。

    例如,我们可以去掉一个const int的const属性:

const int value = 10;
int* mutableValue = const_cast<int*>(&value);

reinterpret_cast:这个转换操作符可以在任何两种类型之间进行转换,包括指针,引用,基本数据类型等等。但是,它是最不安全的转换操作符,因为它会直接进行二进制的转换,不会进行任何类型的检查。

例如,我们可以把一个int类型的数转换为一个指针:

int value = 42;
void* pointer = reinterpret_cast<void*>(value);

这四种类型转换操作符都有各自的用途和限制,使用时需要谨慎,确保转换的安全性。

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值