文章目录
- 1 C++ 三大特性、初始化顺序、构造和析构的顺序
- 2 重载(overload)、重写/覆盖(override)、final和override说明符
- 3 C++ void func() const(类成员函数,不允许修改类的数据成员)
- 4 C++ 模板 、全特化、偏特化
- 5 智能指针相关
- 6 new delete,malloc free 的区别
- 7 类的size,virtual 类的size
- 8 右值引用
- 9 c/c++中struct、static的区别
- 10 拷贝构造
- 11 禁止构造函数、禁止动态分配方式、显示构造、隐式构造
- 12 线程安全变量及关键字synchronized、volatile
- 13 C++空类编译器自动生成的6个成员函数
- 14 C++ 之xxxx_cast关键字的使用
- 15 虚继承如何解决了菱形继承的二义性问题
- 16
- 17
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++经典面试题 | 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的区别
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 虚继承如何解决了菱形继承的二义性问题
在VS工具中使用
cl -d1 reportSingleClassLayoutSon main.cpp
菱形继承
虚继承