一、explicit关键字
explicit关键字,作用时表面该构造函数是显式的不能进行隐式的转换。
#include <iostream>
#include <string>
using namespace std;
class Human {
public:
explicit Human(int age) {//既然加了explicit就不能进行隐式的转换
this->age = age;
}
Human(int age, string name) {
this->age = age;
this->name = name;
}
private:
int age;
string name;
};
int main() {
Human h1(18);//(√)
Human h2 = 18;//隐式构造(×)
Human h3= { 18,"老王" };//隐式构造 初始化参数列表,C++11 前编译不能通过,C++11新增特性(√)
return 0;
}
什么是隐式构造,隐式构造?
举例说明:
Human h1(18);//显式构造
Human h2=18;//隐式构造,当加了explicit后构造这样的对象会报错。
Human h3={18,“老王”};
总结:加explict关键字是为了避免分歧,只使用显式构造会让人更加容易看懂代码。
引用的本质是常量指针
二、类型转换
1、传统的类型转换 (C语言)
隐式转换以及强制类型转换
举例说明:
double a=3.14159;
int i=a; //隐式转换
int i1=(int)a; //强制类型转换
2、C++的类型转换:
C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。
格式:TYPE b = 类型操作符<TYPE> ( a )
类型操作符= static_cast | reinterpreter_cast | dynamic_cast | const_cast
①:static_cast(比较温和,类似传统类型转换的隐式转换,在编译的时候会进行检测,离谱的转换不行)
static_cast<type>(i);
对type没什么类型的要求
主要用法:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。上行 指针或引用(派生类到基类)转换安全,下行不安全
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- 把空指针转换成目标类型的空指针。把任何类型的表达式转换成void类型。
#include<iostream>
using namespace std;
class Human {
public:
virtual void test() {
cout << __FUNCTION__ << endl;
}
};
class Boy :public Human{
public:
void test() {
cout << __FUNCTION__ << endl;
}
};
int main() {
//第一种用法
Boy* boy = new Boy();
Human* father=boy;
father = static_cast<Human*>(boy);
Boy* boy2 = static_cast<Boy*>(father);//这样也是可以的但是不安全,在编译的时候会通过,需要程序员自己把握
//第二种用法
double PI = 3.14159;
int i = static_cast<int>(PI);
//第三种用法
int* p = static_cast<int*>(NULL);
Boy* dp = static_cast<Boy*>(NULL);
return 0;
}
②reinterpret_cast重新解释类型(挂羊头,卖狗肉) 不同类型间的互转,数值与指针间的互转
能用其他的类型转换就用其他的,这种类型转换类似C中的强转
用法: TYPE b = reinterpret_cast ( a )
TYPE必须是一个指针、引用、算术类型、函数指针.
忠告:滥用 reinterpret_cast 运算符可能很容易带来风险。 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一(如果内存空间的大小一样那没有什么大的风险)
#include <iostream>
using namespace std;
class Animal {
public:
void cry() {
cout << "Animal::cry" << endl;
}
};
class Dog :public Animal
{
public:
void cry()
{
cout << "Dog::cry" << endl;
}
};
class Cat :public Animal
{
public:
void cry()
{
cout << "Cat::cry" << endl;
}
};
int main(void) {
//用法一 数值与指针之间的转换
int* p = reinterpret_cast<int*>(0x99999);
int val = reinterpret_cast<int>(p);
//用法二 不同类型指针和引用之间的转换
Dog dog1;
Animal* a1 = &dog1;
Dog* dog1_p = reinterpret_cast<Dog*>(a1);
Dog* dog2_p = static_cast<Dog*>(a1); //如果能用static_cast ,static_cast 优先
dog1_p->cry();
//Cat* cat1_p = static_cast<Cat*>(a1);
//Cat* cat2_p = static_cast<Cat*>(dog1_p);//NO! 不同类型指针转换不能使用static_cast
Cat* cat2_p = reinterpret_cast<Cat*>(dog1_p);
cout << "Cat* cat2_p = reinterpret_cast<Cat*>(dog1_p):";
cat2_p->cry();
Animal& a2 = dog1;
Dog& dog3 = reinterpret_cast<Dog&>(a2);//引用强转用法
system("pause");
return 0;
}
③dynamic_cast动态转换
- 将一个基类对象指针cast到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。失败返回null,成功返回正常cast后的对象指针;
- 将一个基类对象引用cast 继承类对象,dynamic_cast 会根据基类对象是否真正属于继承类来做相应处理。失败抛出异常bad_cast
用dynamic_cast的场景一般都是找出父类指针指向的是哪个继承类,一般父类是存在虚函数的,要不然没有的话感觉其实dynamic_cast感觉就没什么用处了。看下面代码👇
#include <iostream>
using namespace std;
class Animal {
public:
virtual void cry() = 0;
};
class Cat:public Animal
{
public:
void cry()
{
cout << "喵喵瞄" << endl;
}
void play()
{
cout << "爬爬树" << endl;
}
};
class Dog:public Animal
{
public:
void cry()
{
cout << "汪汪汪" << endl;
}
void play()
{
cout << "溜达溜达" << endl;
}
};
//引用的类型
void animalPlay(Animal& animal) {
animal.cry();
try {
Dog& pDog = dynamic_cast<Dog&>(animal);
pDog.play();
}catch (std::bad_cast bc) {//用bad_cast对象来接
cout << "不是狗,那应该是猫" << endl;
}
try {
Cat& pCat = dynamic_cast<Cat&>(animal);
pCat.play();
}catch (std::bad_cast bc) {
cout << "不是猫,那应该是上面的狗" << endl;
}
}
//指针的类型
void animalPlay(Animal* animal) {
animal->cry();
Dog* pDog = dynamic_cast<Dog*>(animal);
if (pDog) {
pDog->play();
}
else {//pDog == NULL
cout << "不是狗,别骗我!" << endl;
}
Cat* pCat = dynamic_cast<Cat*>(animal);
if (pCat) {
pCat->play();
}
else {//pDog == NULL
cout << "不是猫,别骗我!" << endl;
}
}
int main(void) {
Dog* dog1 = new Dog();
Animal* a1 = dog1;
//animalPlay(a1);
Dog dog2;
animalPlay(dog2);
Cat* cat1 = new Cat();
Animal* a2 = cat1;
//animalPlay(a2);
Cat cat2;
animalPlay(cat2);
system("pause");
return 0;
}
④const_cast
去const属性,仅针对指针和引用
#include<iostream>
using namespace std;
void demo(const char* p) {
//方式1:
char* p1 = const_cast<char*>(p);
p1[0] = 'A';
//方式2:
const_cast<char*>(p)[0] = 'A';
}
void demo(const int& a) {
int& p = const_cast<int&>(a);
p = 1;
}
int main() {
char p[] = "1234678";
demo(p);
cout << p << endl;
int a = 10;
demo(a);
cout << a;
//常量字符串不行去掉const修改,去const之前还是要考虑指针指向的内存能否修改
//const char* p1 = "1234567";
//demo(p1);
system("pause");
return 0;
}
概括:
1.static_cast静态类型转换,编译的时c++编译器会做编译时的类型检查;隐式转换;基本类型转换,父子类之间合理转换
2.若不同类型之间,进行强制类型转换,用reinterpret_cast<>() 进行重新解释
3.dynamic_cast<>(),动态类型转换,安全的虚基类和子类之间转换;运行时类型检查
4.const_cast<>(),去除变量的只读属性
总结:在C++中的static_cast和reinterpret_cast就把C中的隐式转换和强制类型转换给概括了,对于reinterpret_cast来说很难保证数据的完整性,能用其他的类型转换就别用reinterpret_cast,要注意每个类型转换的场景,无论是进行什么样的类型转换一定要明白转换后是一个什么样的类型。
三、C++11新特性变参模板、完美转发和emplace
变参模板 - 使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建
完美转发 - 使得接收下来的参数 能够原样的传递给对象的构造函数,这带来另一个方便性
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <algorithm>
using namespace std;
class student {
public:
student() {
cout << "无参构造函数被调用!" << endl;
}
student(int age, string name, int test) {
this->age = age;
//strncpy_s(this->name, name, 64);
cout << "有参构造函数被调用!" << endl;
cout << "姓名:" << name.c_str() << " 年龄:" << age << endl;
}
student(const student& s) {
this->age = s.age;
//strncpy_s(this->name, s.name, 64);
cout << "拷贝构造函数被调用!" << endl;
}
~student() {
cout << "析构函数被调用" << endl;
}
public:
int age;
string name;
};
int main(void) {
//vector<int> vectInt(10);
deque<int> dqInt;
list<int> lstInt;
vector<student> vectStu(10);
cout << "vectStu size:" << vectStu.size() << endl;
cout << "vectStu capacity:" << vectStu.capacity() << endl;
//插入学生
//方法一 先定义对象,再插入
//student xiaoHua(18, "李校花");
//vectStu.push_back(xiaoHua);
//方法二 直接插入临时对象
//vectStu.push_back(student(19, "王大锤"));
//c++11 新特性: 变参模板和完美转发的表演啦
vectStu.emplace_back(19, "王大锤", 11); //push_back直接构造到vectStu中不要进行拷贝构造
cout << "vectStu size (1):" << vectStu.size() << endl;
cout << "vectStu capacity(1):" << vectStu.capacity() << endl;
vectStu.emplace(vectStu.end(), 18, "lixiaohua", 12); //相当于 insert.
cout << "vectStu size (2):" << vectStu.size() << endl;
cout << "vectStu capacity (2):" << vectStu.capacity() << endl;
system("pause");
return 0;
}
结果:
总结:利用emplace可以直接对对象进行构造而不需要先构造一个临时的对象再操作,带有很大的方便性,有兴趣的朋友可以自己运行一下上面的代码,会对内存操作的理解更加的深刻。