【面试总结】小灰灰求职进行曲(二)C++语言方向

1 C++ 三大特性、初始化顺序、构造和析构的顺序

C++ 三大特性

封装,继承,多态

1 封装
隐藏类的属性实现细节,仅仅对外提供接口。
优点:隔离变化;便于使用;提高重用性;提高安全性;
缺点:如果封装太多,影响效率;使用者不能知道代码具体实现。

封装性实际上是由编译器去识别关键字public、private和protected来实现的,体现在类的成员可以有公有成员(public),私有成员(private),保护成员(protected)。私有成员是在封装体内被隐藏的部分,只有类体内说明的函数(类的成员函数)才可以访问私有成员,而在类体外的函数时不能访问的,公有成员是封装体与外界的一个接口,类体外的函数可以访问公有成员,保护成员是只有该类的成员函数和该类的派生类才可以访问的。

2 继承

c++语言允许单继承和多继承

被继承的是父类(基类),继承出来的类是子类(派生类),子类拥有父类的所有的特性。
继承方式有公有继承、私有继承(默认),保护继承

优点:继承减少了重复的代码、继承是多态的前提、继承增加了类的耦合性;
缺点继承在编译时刻就定义了,无法在运行时刻改变父类继承的实现;父类通常至少定义了子类的部分行为,父类的改变都可能影响子类的行为;如果继承下来的子类不适合解决新问题,父类必须重写或替换,那么这种依赖关系就限制了灵活性,最终限制了复用性。

公有继承中父类的公有和保护成员在子类中不变私有的在子类中不可访问
私有继承中父类的公有和保护成员在子类中变为私有,但私有的在子类中不可访问。
保护继承中父类的公有和保护成员在子类中变为保护,但私有的在子类中不可访问。

3 多态
多态性是指对不同类的对象发出相同的消息将会有不同的实现。

优点:大大提高了代码的可复用性;提高了了代码的可维护性可扩充性
缺点易读性比较不好,调试比较困难; 模板只能定义在头文件中,当工程大了之后,编译时间十分的变态;

C++有两种多态,称为动多态(运行期多态)和静多态(编译器多态),静多态主要是通过模板来实现,而动多态是通过虚函数来实现的。即在基类中存在虚函数(一般为纯虚函数)子类通过重写这些接口,使用基类的指针或者引用指向子类的对象,就可以调用子类对应的函数,动多态的函数调用机制是执行器期才能确定的,所以他是动态的。


构造函数可以是虚函数吗?(不可以)

构造函数不能定义为虚函数,虚函数调用是在部分信息下完成工作的机制,允许我们只知道接口
而不知道对象的确切类型,要创建一个对象,需要知道想要创建的确切类型,虚函数的作用在于
通过父类的指针或引用来调用子类的那个成员函数,而构造函数是在创建对象时自己主动调用
,不可能通过父类的指针或者引用去调用,因此,构造函数不应该被定义为虚函数。


析构函数可以是虚函数吗?(可以)为什么要将析构函数设置为虚函数

虚析构函数是为了解决父类指针指向子类对象时,释放子类对象的资源时释放不完全,造成的内存泄漏问题。如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假
设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因
而只会调用基类的析构函数
而不会调用派生类的析构函数。那么在这种情况下,派生类中申请
的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函
数应采用 virtual虚析构函数


类中数据成员初始化顺序,构造和析构的顺序

基本原则

  • 成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关
  • 如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
  • 类中 const成员常量必须在构造函数初始化列表中初始化
  • 类中 static成员变量只能在类内外初始化(同一类的所有实例共享静态成员变量)

初始化顺序

  • 基类的静态变量或全局变量
  • 派生类的静态变量或全局变量
  • 基类的成员变量
  • 派生类的成员变量

构造和析构的顺序
父类构造函数–>成员类对象构造函数–>自身构造函数(从大的到小的,析构顺序相反)

2 重载(overload)、重写/覆盖(override)、final和override说明符

重载(overload)

函数名相同参数类型不同或参数个数不同,发生重载。

  • 函数重载
  • 构造函数重载

重写/覆盖(override)

**函数名相同,参数列表,返回值完全相同,**父类的虚函数或纯虚函数,子类重写该方法,运行时发生动态绑定

  • 重写(覆盖)父类方法

final和override说明符

  • override (重写标识,可以检测是否错误)
  • final(禁用继承类,禁止重写方法)

如果我们使用override标记了某个函数优点(提示功能):但该函数并没有覆盖已存在的虚函数,此时编译器将报错

某个函数指定为 final ,意味着任何尝试覆盖该函数的操作都将引发错误。

3 C++ void func() const(类成员函数,不允许修改类的数据成员)

void func() const 的 const 表示该函数不能修改成员变量的值

class T_const{
public:
    void addNum(){
        t_a = 33;
        cout << "addNum() -> " << t_a + t_b << endl;
    }

    void addNum_Const() const {
        // t_a = 33; // Error  const 后置,表示该函数不可以修改成员变量
        cout << "addNum() -> " << t_a + t_b << endl;
    }

    void addOuter(int outer) const {
        outer = 11;  // 可以修改形参
        cout << "addNum() -> " << t_a + t_b + outer << endl;
    }

private:
    int t_a = 11;
    int t_b = 22;
};
4 C++ 模板 、全特化、偏特化

模板
泛型编程(不指定类型)

  • 函数模板
  • 类模板样例

函数模板是可以被重载的(类模板不能被重载),也就是说允许存在两个同名的函数模板。

//模板函数
template<typename T>
void add(T num1, T num2) {
        cout << num1 << " + " << num2 << " = "<< num1 + num2 << endl;
}

//模板类
template<typename T>
class Test_Class {
public:
        static void multi(T num1, T num2) {
                cout << num1 << " * " << num2 << " = "<< num1 * num2 << endl;
        }
};

模板为什么要特化,因为编译器认为,对于特定的类型,如果你能对某一功能更好的实现,那么就该听你的。
模板分为类模板与函数模板,特化分为全特化与偏特化。全特化就是限定死模板实现的具体类型偏特化就是如果这个模板有多个类型,那么只限定其中的一部分

全特化(类和函数,指定全部类型)

template<typename T1, typename T2>
class A{
        public:
                void function(T1 value1, T2 value2){
                        cout<<"value1 = "<<value1<<endl;
                        cout<<"value2 = "<<value2<<endl;
                }
};

template<>
class A<int, double>{ // 类型明确化,为全特化类
        public:
                void function(int value1, double value2){
                        cout<<"intValue = "<<value1<<endl;
                        cout<<"doubleValue = "<<value2<<endl;
                }
};

偏特化(只能为类,指定部分类型)

template<typename T1, typename T2>
class A{
        public:
                void function(T1 value1, T2 value2){
                        cout<<"value1 = "<<value1<<endl;
                        cout<<"value2 = "<<value2<<endl;
                }
};

template<typename T>
class A<T, double>{ // 部分类型明确化,为偏特化类
        public:
                void function(T value1, double value2){
                        cout<<"Value = "<<value1<<endl;
                        cout<<"doubleValue = "<<value2<<endl;
                }
};

重点总结

  • 类模板能全特化、偏特化不能被重载
  • 函数模板能全特化,不能被偏特化;

模板类调用优先级
全特化类 > 偏特化类 > 主版本模板类

5 智能指针相关

智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象防止堆内存泄漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。

  • auto_ptr
  • shared_ptr
  • unique_ptr
  • weak_ptr

四种智能指针的原理及实现

#include <memory>
void t_ptr(){
    int a = 99;
    shared_ptr<int> p1 = make_shared<int>(a);
    cout << p1.use_count() << endl;   // 1
    shared_ptr<int> p2(p1);           // copy
    cout << p1.use_count() << endl;   // 2

    int *src_ptr = p1.get();          // 获取原始指针

    cout << *src_ptr << endl;         // value: 99

    p1.reset();  // 初始化 或者 释放原来的管理
    cout << p2.use_count() << endl;   // 1
}
6 new delete,malloc free 的区别

C++经典面试题 | malloc和new的区别?

C++经典面试题 | new/new[]和delete/delete[]的区别原理

【校招面试 之 C/C++】第16题 C++ new和delete的实现原理

(1)malloc和new都是在堆上开辟内存的
malloc只负责开辟内存,没有初始化功能,需要用户自己初始化;new不但开辟内存,还可以进行初始化,如new int(10);表示在堆上开辟了一个4字节的int整形内存,初始值是10,再如new int[10] ();表示在堆上开辟了一个包含10个整形元素的数组,初始值都为0。

(2)malloc是函数,开辟内存需要传入字节数,如malloc(100);表示在堆上开辟了100个字节的内存,返回void*,表示分配的堆内存的起始地址,因此malloc的返回值需要强转成指定类型的地址;new是运算符,开辟内存需要指定类型,返回指定类型的地址,因此不需要进行强转。

(3)malloc开辟内存失败返回NULL,new开辟内存失败抛出bad_alloc类型的异常,需要捕获异常才能判断内存开辟成功或失败,new运算符其实是operator new函数的调用,它底层调用的也是malloc来开辟内存的,new它比malloc多的就是初始化功能,对于类类型来说,所谓初始化,就是调用相应的构造函数。
(4)malloc开辟的内存永远是通过free来释放的;而new单个元素内存,用的是delete,如果**new[]数组,用的是delete[]**来释放内存的。

new = malloc + 构造函数

delete = free + 析构函数


malloc/free为C的标准库函数,函数原型

void* malloc(size_t size)//参数代表字节个数
void free(void* pointer)//参数代表内存地址
#include <iostream>
#include <malloc.h>

using namespace std;

int main() {

    int num1 = 99;
    // [1] 申请内存
    int *p1 = (int *)malloc(sizeof(int));
    p1 = &num1;

    cout << *p1 << endl;
    // [2] 释放内存
    free(p1);
	
	int *p = new int[10];   
	delete []p;

    return 0;
}

new、delete则为C++的操作运算符,它调用的分别为赋值运算符重载operator new()和operator delete()。

#include <iostream>

using namespace std;

int main() {

    int *p2 = new int(10);
    cout << *p2 << endl; // 10
    delete p2;
    
    return 0;
}


7 类的size,virtual 类的size

类型占用字节数

32位编译器

char :			   1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32,32个bit,也就是4个字节。同理64位编译器)
short int : 	   2个字节
int:  			   4个字节  // int32_t
unsigned int : 	   4个字节
float:  		   4个字节
double:   		   8个字节
long:   		   4个字节
long long: 		   8个字节  // int64_t
unsigned long:     4个字节  

64位编译器

char : 			   1个字节
char*(即指针变量):  8个字节*****
short int : 	   2个字节
int:	           4个字节
unsigned int : 	   4个字节
float:  		   4个字节
double:            8个字节 
long:   		   8个字节****
long long:  	   8个字节
unsigned long:     8个字节****

类中成员内存对其(4字节的整数倍)

class Person1{
};

class Person2{
public:
    int age; // int32_t  4byte
};

class Person3{
public:
    int age; // int32_t  4byte
    char c;  // 1byte
};

class Person4{
public:
    int age;  // int32_t  4byte
    char c1;  // 1byte
    char c2;  // 1byte
};

class Person5{
public:
    char c1;  // 1byte
    int age;  // int32_t  4byte
    char c2;  // 1byte
};


class Person6{
public:
    int age; // int32_t  4byte

    void func1(){}
    void func2(){}
    void func3(){
        int tp = 99;
    }

};

void t_size(){
    // 1 1
    Person1 p1;
    cout << sizeof(Person1) << " " << sizeof(p1) << endl;

    // 4 4
    Person2 p2;
    cout << sizeof(Person2) << " " << sizeof(p2) << endl;

    // 8 8   介绍:char c本身为1字节, 4+1  为了内存对齐4字节 4+(1+3)=8
    Person3 p3;
    cout << sizeof(Person3) << " " << sizeof(p3) << endl;

    // 8 8   介绍:char c本身为1字节, 4+1+1  为了内存对齐4字节 4+(2+2)=8
    Person4 p4;
    cout << sizeof(Person4) << " " << sizeof(p4) << endl;

    // 12 12   介绍:char c本身为1字节, 1+4+1, 两端分别要内存对齐4字节 (1+3)+4+(1+3)=12
    Person5 p5;
    cout << sizeof(Person5) << " " << sizeof(p5) << endl;

    // 4 4  函数不占用内存大小
    Person6 p6;
    cout << sizeof(Person6) << " " << sizeof(p6) << endl;
}

虚函数对类大小的影响

class Vir1{
};

class Vir2{
public:
    virtual void func1(){};
};

class Vir3{
public:
    virtual void func1(){};
    int age; // 4byte
};

class Son1: public Vir1{
};

class Son2: public Vir2{
};

class Son3: public Vir3{
};

void t_size_vir(){
    // 1 1  空占 1byte
    Vir1 v1;
    cout << sizeof(Vir1) << " " << sizeof(v1) << endl;

    // 4 4  虚基类占 4byte (MinGW32, 一个虚函数指针大小)
    Vir2 v2;
    cout << sizeof(Vir2) << " " << sizeof(v2) << endl;

    // 8 8  虚基类占  虚指针+int32_t -> 8byte
    Vir3 v3;
    cout << sizeof(Vir3) << " " << sizeof(v3) << endl;

    // 1 1 空占 1byte
    Son1 s1;
    cout << sizeof(Son1) << " " << sizeof(s1) << endl;

    // 4 4(依据base)
    Son2 s2;
    cout << sizeof(Son2) << " " << sizeof(s2) << endl;

    // 8 8(依据base)
    Son3 s3;
    cout << sizeof(Son3) << " " << sizeof(s3) << endl;
}

多继承,基类同名虚函数

[1] 子类把两个父类的方法同时给覆盖掉

class A1{
public:
    virtual void info()=0;

    A1(){ printf("A1::A1()\n"); }
    ~A1(){ printf("A1::~A1()\n"); }
};

class A2{
public:
    virtual void info()=0;
    A2(){ printf("A2::A2()\n"); }
    ~A2(){ printf("A2::~A2()\n"); }
};

class ASon: public A1, public A2{
public:
    ASon(){ printf("ASon::ASon()\n"); }
    ~ASon(){ printf("ASon::~ASon()\n"); }
    void info() override {
        cout << "this is info func !" << endl;
    }
};
ASon a1;
a1.info();  // ok

-------------------
A1::A1()
A2::A2()
ASon::ASon()
this is info func !
ASon::~ASon()
A2::~A2()
A1::~A1()
ASon *a2 = new ASon();
a2->info(); // ok
delete a2;

-------------------
A1::A1()
A2::A2()
ASon::ASon()
this is info func !
ASon::~ASon()
A2::~A2()
A1::~A1()
A1 *a3 = new ASon();
a3->info(); // ok
delete a3;

-------------------
A1::A1()
A2::A2()
ASon::ASon()
this is info func !
A1::~A1()  
8 右值引用

参考博文

C++11 增加了一个新的类型,称为右值引用( R-value reference),标记为 &&

  • 左值是指存储在内存中、有明确存储地址(可取地址)的数据
  • 右值是指可以提供数据值的数据(不可取地址

在这里插入图片描述

class Test
{
public:
    Test()
    {
        cout << "Test::Test()" << endl;
    }
    ~Test(){
        cout << "Test::~Test()" << endl;
    }
};

Test getObj()
{
    return Test();
}

int t_yinyong()
{
    int a1;
//    int &&a2 = a1;        // error  a1是左值                 int &&为右值引用  *不合法
//    Test &t1 = getObj();   // error  getObj() 是右值(将亡值)   Test&为左值引用  *不合法

    Test &&t2 = getObj();    // OK  getObj() 是右值(将亡值)  Test &&是右值引用  合法
    const Test& t3 = getObj(); // OK 常量左值引用是一个万能引用类型,它可以接受左值、右值、常量左值和常量右值。
    return 0;
}
9 c/c++中struct、static的区别

1 struct在C和C++中的区别

2 static在C和C++中的用法和区别

10 拷贝构造

书写拷贝构造

class Cat{
public:
    Cat(){}   // 模认构造
    Cat(string _name, int _age): name(_name), age(_age){}
    ~Cat(){}  // 析构

    Cat(const Cat &obj): name(obj.name), age(obj.age){
        printf("Cat::Cat(const Cat &obj)");
    }

    string name;
    int age;
};


void t_copy(){
//    Cat c1("xhh", 18);
//    Cat c2(c1);

    Cat *c3 = new Cat("xhh", 18);
    Cat *c4 = new Cat(*c3);
    printf("name: %s  age: %d", c3->name.c_str(), c3->age); // name: xhh  age: 18
}

为什么拷贝构造需要传入引用

参数为引用,不为值传递,防止拷贝构造函数的无限递归,最终导致栈溢出

在这里插入图片描述

11 禁止构造函数、禁止动态分配方式、显示构造、隐式构造

禁止构造函数

设置构造函数为私有private或protected

class Cat{
private:
    Cat(){}   // 模认构造
    ~Cat(){}  // 析构

};

void t_copy(){
    Cat c1; // Cat c1 = Cat();  Error
}

禁止动态分配方式

重载操作符new和delete以及[]为私有private或protected

class Cat{
public:
    Cat(){}   // 模认构造
    ~Cat(){}  // 析构

private:
    void *operator new(size_t size){return nullptr;} // 重载new
    void operator delete(void *ptr) {}             // 重载delete
    void *operator new[](size_t size){return nullptr;} // 重载new[]
    void operator delete[](void *ptr) {}             // 重载delete

};

void t_copy(){
    Cat *c1 = new Cat;      // error
    delete c1;              // error

    Cat *c2 = new Cat[5];   // error
    delete[] c2;            // error

}

只能动态分配类对象

  • 采用静态成员函数去创建对象
  • 对外隐藏默认构造和析构
  • default创建函数的默认实现(只能对默认函数进行设置)
  • delete禁用一些函数的使用(禁用内部默认的拷贝构造)
class Dog{
protected:
    Dog() = default;   // 模认构造 通过附加说明符’= default’,编译器将创建此函数的默认实现
    ~Dog() = default;  // 析构
//    Dog(const Dog &obj) = delete; // delete(禁止一些函数的使用) 禁用拷贝构造函数

public:
    static Dog* create() {
        return new Dog();
    }
    static void destroy(Dog *ptr) {
        delete ptr;	    // 不要用 delete this;
    }

};

void t_copy(){
    Dog *d1 = Dog::create();
    Dog::destroy(d1);
}

显示构造explicit、隐式构造implicit

explicit关键字的作用就是防止类构造函数的隐式自动转换
使用explicit关键字,确实是可以禁止隐式构造。

class Pig{
public:
    Pig() = default;   // 模认构造
    ~Pig() = default;  // 析构

//    explicit Pig(string _name): name(_name){}
    explicit Pig(int _age): age(_age){}
    explicit Pig(string _name, int _age): name(_name), age(_age){}

    string name;
    int age;
};



void t_copy(){
    Pig p1(18);    // [1] 显式构造
    Pig p2 = 3;    // [2] 隐示构造 explicit -> error

    Pig p3("xhh", 18);   // [1] 显式构造
    Pig p4 = {"mcy", 3}; // [2] 隐式构造,初始化参数列表,C++11之前的版本不能通过,C++11新特性  explicit -> error
}
12 线程安全变量及关键字synchronized、volatile

synchronized

对方法(func)进行加锁

volatile

当变量被定义成volatile类型的时候,它就不会被编译器优化,在每次访问变量的时候都将重新在内存中读取它的值。

原子类型
在这里插入图片描述

std::atomic<T> t;
13 C++空类编译器自动生成的6个成员函数

对于空类,编译器不会生成任何的成员函数,只会生成1个字节的占位符。

有时可能会以为编译器会为空类生成默认构造函数等,事实上是不会的,编译器只会在需要的时候生成6个成员函数:一个缺省的构造函数、一个拷贝构造函数、一个析构函数、一个赋值运算符一对取址运算符一个this指针

缺省的6个函数

class Empty
{
  public:
    Empty();                            //缺省构造函数
    Empty(const Empty &rhs);            //拷贝构造函数
    ~Empty();                           //析构函数 
    Empty& operator=(const Empty &rhs); //赋值运算符
    Empty* operator&();                 //取址运算符
    const Empty* operator&() const;     //取址运算符(const版本)
};

使用函数

Empty *e = new Empty();    //缺省构造函数
delete e;                  //析构函数
Empty e1;                  //缺省构造函数                               
Empty e2(e1);              //拷贝构造函数
e2 = e1;                   //赋值运算符
Empty *pe1 = &e1;          //取址运算符(非const)
const Empty *pe2 = &e2;    //取址运算符(const)

内敛函数的实现

inline Empty::Empty()                          //缺省构造函数
{
}
inline Empty::~Empty()                         //析构函数
{
}
inline Empty *Empty::operator&()               //取址运算符(非const)
{
  return this; 
}           
inline const Empty *Empty::operator&() const    //取址运算符(const)
{
  return this;
}
inline Empty::Empty(const Empty &rhs)           //拷贝构造函数
{
  //对类的非静态数据成员进行以"成员为单位"逐一拷贝构造
  //固定类型的对象拷贝构造是从源对象到目标对象的"逐位"拷贝
}
 
inline Empty& Empty::operator=(const Empty &rhs) //赋值运算符
{
  //对类的非静态数据成员进行以"成员为单位"逐一赋值
  //固定类型的对象赋值是从源对象到目标对象的"逐位"赋值。
}
14 C++ 之xxxx_cast关键字的使用

C++提供了四个转换运算符

  • const_cast <new_type>(expression)

  • static_cast <new_type> (expression)

  • dynamic_cast <new_type> (expression)

  • reinterpret_cast <new_type> (expression)

1 const_cast <new_type> (expression)

onst_cast转换符是用来移除变量的const或volatile限定符。

void t_const_cast(){
    const int n1 = 99;
    const int *p1_n1 = &n1; // Ok
//    int *p2_n1 = &n1;     // [1] Error: invalid conversion from 'const int*' to 'int*'
    int *p3_n1 = const_cast<int *>(p1_n1); // [2] OK

    // “未定义行为(Undefined Behavior)”。所谓未定义,是说这个语句在标准C++中没有明确的规定,由编译器来决定如何处理。
    *p3_n1 = 88;
    
    cout << n1 << endl;         // 改掉了,但是打印还是 99
    cout << *p1_n1 << endl;     // 打印88
    cout << *p3_n1 << endl;     // 打印88
}
  • 那我们又为什么要去const呢?

(原因1):我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数确是const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。

void showInfo(string *str){
    cout << *str << endl;
}

void input_info(){

    const string str = "mcy 3";
//    showInfo(&str); // error
    showInfo(const_cast<string *>(&str)); // OK  const string *  -> string *
}

2 static_cast <new_type> (expression)

该运算符把expression转换为new_type类型,但没有运行时类型检查来保证转换的安全性
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

为什么需要static_cast强制转换?

  • 情况1:void指针->其他类型指针
  • 情况2:改变通常的标准转换
  • 情况3:避免出现可能多种转换的歧义

3 dynamic_cast <new_type> (expression)

该运算符把expression转换成new_type类型的对象。new_type必须是类的指针、类的引用或者void *。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。(安全)

为什么需要dynamic_cast强制转换?简单的说,当无法使用virtual函数的时候。

使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

class Animal{
public:
    virtual void info()=0;
};

class Cat: public Animal{
public:
    virtual void info(){};
};

class Dog: public Animal{
public:
    virtual void info(){};
};


void t_static_dynamic_cast(){

    // 上行转换
    Cat *c1 = new Cat;  // 0x01
    Dog *d1 = new Dog;  // 0x02
    Animal *a1 = static_cast<Animal *>(c1);      // ok  0x01
    Animal *a2 = dynamic_cast<Animal *>(d1);     // ok  0x02


    // 下行转换
    Animal *aa = new Cat;                // 0x03
    Cat *cc1 = static_cast<Cat *>(aa);   // ok 0x03
    Cat *cc2 = dynamic_cast<Cat *>(aa);  // ok 0x03

    Dog *dd1 = static_cast<Dog *>(aa);   // 0x03  没有类型检查,不安全
    Dog *dd2 = dynamic_cast<Dog *>(aa);  // ok nullptr

    cout << " --- " << endl;
}

4 reinterpret_cast <new_type> (expression)

new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。

void t_reinterpret_cast(){

    int n1 = 99;

    int *ptr = nullptr;
    int getNum = 0;

    // [1] 整数转为指针
    ptr = reinterpret_cast<int *>(&n1);
    cout << ptr << "  " << *ptr << endl; // 0x61fe84  99

    // [2] 指针转为整数
    getNum = reinterpret_cast<int>(ptr);
    cout << &getNum << "  " << getNum << endl;  // 0x61fe84(16进制) =  6422152  值有

}
15 虚继承如何解决了菱形继承的二义性问题

C++菱形继承产生二义性产生的原因

在VS工具中使用

cl -d1 reportSingleClassLayoutSon main.cpp

菱形继承
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

虚继承

在这里插入图片描述
在这里插入图片描述

16
17
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值