参考链接:https://blog.csdn.net/jokerro/article/details/107734379
volatile关键字在C/C++中与const同级用来修饰变量,用它声明的变量可以被某些编译器中未知的因素修改,如操作系统、硬件、其它线程等。遇到这个关键字声明的变量,编译器就不再对其进行优化,当使用volatile声明变量的时候,系统总是从它所在的内存空间直接读取数据,而一般情况下,编译器会对代码进行优化。在连续读取某个内存地址存储的变量时,如果地址没有变化,且在该单线程中没有其它语句改变这个变量中的值时,编译器不会直接去读内存中变量的值,而是从寄存器中直接读取原先变量的值,所以当有操作系统、硬件、或者其它线程(大多数是其他线程争抢共享变量时会发生)对该变量进行修改,就会出现读错误,读出的数据并不是变量现在真正存储的值,从而导致错误的发生。
综上,volatile关键字可以保证每次对变量的访问都是从内存地址直接读取,而不是被编译器优化后可能导致的读错误。
学习参考链接:https://blog.csdn.net/liranke/article/details/5295133
用于非多态类型转换(静态转换),任何标准转换都可以用它,但是不能用于两个不相关的类型转换。
int main()
{
char ch = 'a';
short sh = 10;
int i1 = static_cast<char>(ch);//success
int i2 = static_cast<short>(sh);//success
cout << i1 << endl;//97
cout << i2 << endl;//10
double* d = new double;
void* v = static_cast<double*>(d);//success
int i = 20;
const int iConst = static_cast<const int>(i);//success
const int jCount = 30;
//int* p = static_cast<int*>(&jCount);//fail
//static_cast不能移除变量的const属性。
return 0;
}
class Base {
public:
int a;
void fun1() { cout << "Base::fun1" << endl; }
void fun2() { cout << "Base::fun2" << endl; }
};
class Derive : public Base {
public:
int b;
void fun2() { cout << "Derive::fun2" << endl; }
void fun4() { cout << "Derive::fun4" << endl; }
};
int main()
{
Base b;
Derive d;
Base* pB = static_cast<Base*>(&d);//派生类指针转换为基类指针
Derive* pD = static_cast<Derive*>(&b);//基类指针转换为派生类指针
pB->fun1();
pB->fun2();
//pB->fun4();//fail,因为fun4是派生类的成员函数,只能通过派生类对象进行访问
pD->fun1();//调用Base的func1
pD->fun2();//调用Derive的func2,符合就近原则
pD->fun4();//调用Derive的func4,fun4是派生类的成员函数,而不是父类的成员函数。
return 0;
}
class A
{
public:
int a;
void func1() { cout << "A:func1" << endl; }
};
class B
{
public:
int b;
B(A& a) { cout << "B::Constructor" << endl; }
void func2() { cout << "B:func2" << endl; }
};
int main()
{
A a;
B b = static_cast<B>(a);//将A类转换为B类
//b.func1();//fail,B类中不存在func1
b.func2();
/*
输出结果:
B::Constructor
B:func2
*/
return 0;
}
但是如果B类中不存在B(A& a)该拷贝构造函数,就会发生编译错误。
可见,如果A和B没有继承关系的两个互不相关的类,想要由A转换为B,则在B中必须定义“以A为参数”的构造函数。
学习参考链接:https://blog.csdn.net/liranke/article/details/5297087
将一种数据类型转换为另一种不同的数据类型,类似于C中的强制类型转换。
class A
{
public:
char a;
int c;
void func() { cout << "A::func1" << endl; }
};
class B
{
public:
int b;
void func2() { cout << "B::func2" << endl; }
};
void test_func(int* pi, char* pc, A* pA, B* pB, int i)
{
char* pc2 = reinterpret_cast<char*>(pB);//将B类指针转换为char类型指针
int* pi2 = reinterpret_cast<int*>(pc);//将char类型指针转化为int类型指针
int* pi3 = reinterpret_cast<int*>(i);//将int类型转换为int类型指针
A* pA2 = reinterpret_cast<A*>(pi);//int类型指针转换为A类指针
A* pA3 = reinterpret_cast<A*>(pB);//将B类指针转化为A类指针
long i2 = reinterpret_cast<long>(pA2);
cout << "i2 = " << i2 << endl;//i = 7338552
pA2->func();//A::func1
cout << "pA2->a " << pA2->a << endl;//pA2->a a
cout << "pA2->c " << pA2->c << endl;//pA2->c -858993460,即实际上是内存映射关系,int i = 97,映射为char类型,就是asciima中的a字符
pA3->func();//A::func1
//pA3->func2();//fail,虽然pB是B类型的指针,但是,pA3的类型是指向A的指针。符合就近原则
A a;
B b;
//B b2 = reinterpret_cast<B>(a);//fail,不允许
//A a1 = reinterpret_cast<A>(b);//fail,不允许
}
int main()
{
int i = 97;
int* p1 = &i;
char* ch = (char*)"hello";
A a;
B b;
test_func(p1, ch, &a, &b, i);
return 0;
}
class A
{
public:
char a;
int c;
void func() { cout << "A::func1" << endl; }
};
class B
{
public:
int b;
void func2() { cout << "B::func2" << endl; }
};
class D :public A, public B
{
public:
void func4() { cout << "D::func4" << endl; }
};
void test_func1(B* pb)
{
D* pd1 = reinterpret_cast<D*>(pb);//success
pd1->func();//A::func1
pd1->func2();//A::func2
pd1->func4();//A::func4
D* pd2 = static_cast<D*>(pb);//success
pd2->func();//A::func1
pd2->func2();//A::func2
pd2->func4();//A::func4
}
int main()
{
D d;
test_func1(&d);
return 0;
}
void thump(char* p) { *p = 'x'; }
typedef void (*PF)(const char*);
PF pf;
void g(const char* pc)
{
//thump(pc);//fail,参数不匹配
//pf = &thump;//fail,不能将“void (*)(char* p) ”类型的值分配给“PF”实体
//pf = static_cast<PF>(&thump);//fail,无效类型转换
pf = reinterpret_cast<PF>(&thump);//success
pf(pc);//success
}
int main()
{
char* str = "h";
g(str);
return 0;
}
可以知道,让pf去指向thump是很危险的,因为这样做就是欺骗类型系统,使它能允许将一个常量的地址传递到某个要修改它的地方去,这样就是为什么我们必须使用强制类型转换的地方。
static_cast就是利用C++类型之间的继承关系图和聚合关系图(编译器必须知道),根据一个子对象地址计算另外一个子对象的地址。reinterpret_cast不关心继承关系,直接把数据类型A的地址解释成另外一个数据类型B的地址。
所以,对于无继承关系的类的转换,static_cast需要进行构造函数的重载,参数必须是要被转换的类的类型。而reinterpret_cast则没有这个限制。
例如,对于
class A {};
class B {}
void test_fun() {
A a;
B *pB1 = reinterpret_cast<B*>(&a);//success
}
而对于如下
B b1 = static_cast<B>(a);//fail
如果将B类定义如下拷贝构造:
class B{
public:
B(A& a) {}
};
则
B b1 = static_cast<B>(a);//success
const_cast用于删除变量的const属性,方便赋值。
int main()
{
int variable = 10;
int* constP = &variable;
int* modifier = const_cast<int*>(constP);
*modifier = 20;
cout << "*modifier = " << *modifier << endl;
cout << "modifier = " << modifier << endl;
cout << "*constP = " << *constP << endl;
cout << "constP = " << constP << endl;
cout << "variable = " << variable << endl;
/*
输出结果:
*modifier = 20
modifier = 010FFA9C
*constP = 20
constP = 010FFA9C
variable = 20
*/
return 0;
}
分析:
以上代码,通过modifier指针对variable进行了值的修改。
我们定义了一个非const的变量,但用带const限定的指针去指向它,在某一处我们突然又想修改了,可是我们当前场景下只有指针,这时我们可以用const_cast来修改。
例子如下:
int a = 20;
const int* const p = &a;
int* const p1 = const_cast<int* const>(p);
*p1 = 21;
cout << a << endl;//21·
在看一个例子:
void output(int* val)
{
cout << val << endl;
}
int main()
{
const int i = 20;
const int* p = &i;
cout << p << endl;//输出:012FF934
//output(p);//fail
output(const_cast<int*>(p));//输出:012FF934
return 0;
}
分析,定义了常量i,以及常量指针p,将p作为参数传递给output函数,结果编译失败;
使用const_cast以后,将i的地址去除const后,传递给output函数,编译正常运行;
可见,const_cast的主要作用是去除const。
用于将一个父类对象的指针或引用转换为子类对象的指针或引用。(动态交换)必须包含一个多态类型(虚函数)。
class A
{
int a;
virtual void func() {}
};
class B :public A { int b; };
class C :public A { int c; };
int main()
{
B b;
A* a = dynamic_cast<B*>(&b);
if (nullptr == a)
cout << "error" << endl;
else
cout << "success" << endl;//输出:success,子类转换为父类成功
C* c = dynamic_cast<C*>(a);
if (nullptr == c)
cout << "error" << endl;//输出:error,父类转换为子类失败
else
cout << "success" << endl;
return 0;
}
//美术家
class Artist
{
public:
virtual void draw() { cout << "Artist draw" << endl; }
};
//音乐家
class Musician
{
public:
};
//教师
class Teacher
{
public:
virtual void teachStu() { cout << "Teacher teachStu" << endl; }
};
//既是美术家,又是音乐家,同时是教师的人
class People :public virtual Artist, public virtual Musician, public Teacher
{
public:
};
int main()
{
People* p1 = new People();
p1->draw();
p1->teachStu();
cout << "dynamic_cast" << endl;
Artist* a1 = dynamic_cast<Artist*>(p1);//success
//Artist* a1 = p1;//success,向上转换,C++总是能正确识别,即将派生类的指针赋值给基类指针
a1->draw();//success
//a1->teachStu();//fail
return 0;
}
说明:
People类:定义一个特殊的People类,它的特点都是:既是美术家,又是音乐家,同时是教师。
上面的代码,运行结果如下:
Artist draw
Teacher teachStu
dynamic_cast
Artist draw
分析:
下面两条语句均正确,都能由子类People转化为父类Artist。并且调用父类Artist的成员函数均能成功。
Artist *a1 = dynamic_cast<Artist*>(p1); 等价于 Artist *a1 = p1;
a1->draw(); //success: 打印people teachStu
可见,向上转换,无论是否用dynamic_cast,C++总是能够正确识别,即将派生类的指针赋值给基类指针。
void test1()
{
People* p1 = new People();
printf("dynamic_cast test:\n");
Artist* a1 = p1;//success
//People* p2 = (People*)a1;//fail
People* p3 = dynamic_cast<People*>(a1); //success
p3->draw();
p3->teachStu();
}
分析:
由基类到派生类指针的转换通过dynamic_cast来完成。
那么为什么使用dynamic_cast,基类指针a1就能转换为派生类指针p3呢?
这就是RTTI机制。
RTTI:Run Time Type Identification,即通过运行时类型识别。程序能够使用基类的指针或引用来检查这些指针或引用所指向对象实际的派生类型。
C++提供在RTTI机制中逻辑上,存在类似如下的内存模型图:
在这个内存模型中,每个类都会在内存中保存一份类型信息,编译器会将存在继承关系的类的类型信息使用指针“连接”起来,从而形成一个继承链(Inheritance Chain)。
dynamic_cast的使用注意事项:
现在,用RTTI来解释test1()中的函数代码:
p1是派生类指针,a1是基类指针,a1要转换的指针类型为People,根据RTTI机制,从类People类向上查找,能找到a1所指的类Artist,所以,dynamic_cast转换是合法的。
void test2()
{
People* p1 = new People();
printf("dynamic_cast test:\n");
Musician* m1 = p1;//success
//People* p2 = (People*)m1;//fail
//People* p3 = dynamic_cast<People*>(m1);//fail
}
分析:
根据dynamic_cast使用注意事项中“dynamic_cast”转换符只能用于含有虚函数的类(虚基类),Musician是非虚基类,所以无法进行动态强制转换。
void test3() {
People *p1 = new People();
printf("\ndynamic_cast test:\n");
Teacher *t1 = p1; //success
People *p2 = (People*)t1;//success,继承自Teacher,采用非虚继承的方式(不使用virtual),所以ok。
People *p3 = dynamic_cast<People*>(t1);//success:加了dynamic_cast,进行强转
}
分析: