RoadMap5:C++的数据类型

本文主要C++的基本数据类型如 int, float 等静态数据类型,然后进一步探讨C++中的动态数据类型以及运行时类型辨识(Run-Time Type Identification, RTTI机制)。

1. C++基本的静态类型

在 c++ 中的静态数据类型指的是:变量的数据类型在编译的时候已经被决定,而不是在运行的过程,也就是说变量只能被特定类型的数据进行幅值。(可以类比于 python,python在定义一个变量、编译一个变量时,是不需要指定类型的,在执行的过程通过数据传入实时定义类型)。故而C++是一门 静态类型语言,在编译时会进行类型的匹配矫正。

常见的数据类型及其尺寸:

数据类型全称尺寸
intinteger (包括正负)4 bytes (64位系统)
shortshort integer2 bytes (64位系统)
longlong integer4 bytes (64位系统, linux 8 bytes)
long longlong long integer8 bytes
floatfloating-point type4 bytes
doubledouble-precision floating-point8 bytes
charsingle character (ASCII)1 byte
boolboolean (True/False)1 byte

auto数据类型并不是动态的数据类型,在编译过程中,编译器会根据其初始化值自动推断其数据类型,以下展示auto关键字的常见用法:

#include <iostream>
#include <vector>
using namespace std;

// 0. 用 auto 作为函数返回值类型
auto add(int a, int b){
    return a+b;
}

int main(){
    // 1. 用 auto 作为变量类型进行变量定义,注意必须在声明的同时进行变量的初始化
    auto autoVar = 9.6;
    // auto autoVar;    //报错,因为没有进行赋值初始化

    // 2. 用 auto 作为 vector 的迭代器
    vector<int> myVector = {1, 2, 3, 4, 5};
    for(auto it = myVector.begin(); it != myVector.end(); it++){
        cout << "number: " << *it << endl;
    }
    // 等价表达
    for(vector<int>::iterator it = myVector.begin(); it != myVector.end(); it++){
        cout << "number: " << *it << endl;
    }
}

常见的衍生数据类型:

  1. Array 数组:使用连续的空间存储多个数值,例如:
int nums[5] = {1, 2, 3, 4, 5};  // 数据类型 数组名称[数组长度] = {数据定义}
  1. *指针:一种用于存储地址的变量,如:
int num = 5;
int* pNum = &num;  // 数据类型* 指针名称 = &被指变量名称
  1. &引用:用于变量间共享地址操作(别名),如:
int nums = 5;
int& numRef = num;  // 数据类型& 变量名称 = 变量名称

用户自定义类型:

  1. struct:结构体,用一个变量存储不同的数据类型,且所有的数据变量、函数或类都是公共的(public,即任意成员属性都可以直接访问或修改),如:
// 结构体类型的定义
struct Person{        // struct 结构体类型名称{ 属性1; 属性2;...;属性n;};
	string name;
	int age;
	float height;
};
// 变量定义
Person p1 = {'Peter', 30, 168.0} ;   // 结构体类型 变量名称 = {属性1, 属性2,...,属性n};
  1. class:类,类似于结构体,不过其成员函数可以设置为公有(public)或者私有(private,即指定部分成员属性不能访问或修改,可能有疑问,不能访问的属性要来何用,可以解释的一个方向是:如要写一个加权求和函数 y=k1x1+k2x2,这个权重k1,k2是不需要访问/修改的,且我们希望在类的所有的函数(作用域)都访问这个权重,这样的话就可以把权重k1,k2设为私有);
// 创建类
class Score{
	public:
		string name;
		float score1;
		float score2;
		void printScore(){
			cout << k1 * score1 + k2 * score2 << endl;
		}
	private:
		float k1 = 0.618;
		float k2 = 0.348;
}
// 实例化对象
int main(){
    // 实例化对象
    Score s;
    s.name = "peter";
    s.score1 = 95;
    s.score2 = 50;
    s.printScore();
};
  1. union:联合,它是一种特殊的类。通过关键字union进行定义,一个union可以有多个数据成员。联合中只能有一个数据成员可以有值。当给联合中某个成员赋值之后,该联合中的其它成员就变成未定义状态了。如:
union Data{
	int num;
	float decimal;
};

int main(){
    // 实例化对象
    Data s;
    s.num = 4;
    std::cout << "num: " << s.num << "\tdecimal: " << s.decimal << std::endl; // Output: num: 4  decimal: 5.60519e-45
    s.decimal = 4.01;
    std::cout << "num: " << s.num << "\tdecimal: " << s.decimal << std::endl; // Output: num: 1082151404 decimal: 4.01
};

2. 动态类型

C++是一门静态数据类型的语言,但是也提供了一定程度的动态数据类型的接口,即变量的数据类型在运行的时候才能决定。但是在使用时,也需要在代码中指定数据类型,无法达到 python 那种肆意妄为。所以动态类型在C++用的很少,数据冷门且没用的知识!以下主要讨论空指针和any数据类型两种操作。

  1. void* :空指针,空指针是一种通用的指针,可以指向任意的数据类型。通常被用于存储任意类型变量的引用,如:
    std::string name = "Peter";
    int age = 18;

    void* ptr;  // 空类型的指针
    ptr = &name; //存储字符串类型的引用
    // 调用指针时,表达式必须是指向完整对象类型的指针,即 static_cast<类型*>
    std::cout << "name: " << *(static_cast<std::string*>(ptr))  << std::endl;  // Output: name: Peter
    ptr = &age;
    std::cout << "age: " << *(static_cast<int*>(ptr))  << std::endl;  // Output: age: 18
  1. std::any :相比于空指针,在C++17引入的 any 任意数据类型更加直接粗暴,直接看例子,一看就懂!
#include<iostream>
#include<any>   //注意引用 any 头文件

int main(){
	std::any value;
    // 整型
    value = 4;
    // 类似于 空指针,需要指定运行时的数据类型,通过关键字:std::anycast<类型>(变量)
    std::cout << "int: " << std::any_cast<int>(value) << std::endl;  // 输出:int: 4
    //浮点数,使用float会报错,原因不详;
    value = 4.0001;
    std::cout << "double: " << std::any_cast<double>(value) << std::endl; // 输出:double: 4.0001
};

3. 运行时类型辨识(RTTI机制)

RTTI机制是动态数据类型的另一种方法,与空指针和any相比,RTTI机制常用于类(class)数据类型,处理基类和派生类的关系,值得注意的是:RTTI只适用于包含虚函数的类。因为只有对于这种类层次结构,才应该将派生类的地址赋给基类指针。通俗来说,这里的动态属性体现在:基类的指针既可以指向基类,也可指向由基类产生的派生类;

以下主要讨论 typeid 和dynamic_cast 两种操作。

  1. typeid:它允许程序向表达式提问对象是什么类型,返回值是对一个常量对象的引用,该对象的类型是标准库类型 type_info 或者 type_info 的公有派生类型;举个例子,我有两个基类对象指针 base_ptr1和 base_ptr2,但是我并不清楚它们指向基类还是派生类,因此可以用 typeid 查看一下:
#include<iostream>
#include<typeinfo>  // 引入关键头文件
// 定义一个基类
class Base{
    virtual void dummy(){};   // virtual 关键字定义虚函数,RTTI机制适用于包含虚函数的类
};
// 由基类产生的派生类
class Derived1: public Base{};  // 派生类 Derived1 公开继承基类

int main(){
    Base* base_ptr1 = new Derived1;  // 定义一个基类指针,指向派生类1
    Base* base_ptr2 = new Base;    // 定义一个基类指针,指向基类
    std::cout << "Type of ptr1: " << typeid(*base_ptr1).name() << std::endl;   // 输出:Type of ptr1: 8Derived1
    std::cout << "Type of ptr2: " << typeid(*base_ptr2).name() << std::endl;    // 输出:Type of ptr2: 4Base

    delete base_ptr1, base_ptr2; //删除指针,防止内存泄漏
};
  1. dynamic_cast:动态转换,看到 cast 就应该想到跟类型转换相关,dynamic_cast 的作用是把指针的类型转化成其基类或者派生类的类型。举个例子,我有1个基类对象指针 base_ptr1和两个派生类指针 derived_ptr1、derived_ptr2。我想把派生类1指针 derived_ptr1转化为基类是可以的,但是转化成派生类2的对象指针是不行的。当转化失败的时候就会返回0。
#include<iostream>
#include<typeinfo> // 引入关键头文件
// 定义一个基类
class Base{
    virtual void dummy(){};   // virtual 关键字定义虚函数,RTTI机制适用于包含虚函数的类
};

class Derived1: public Base{};  // 派生类 Derived1 公开继承基类
class Derived2: public Base{};  // 派生类 Derived2 公开继承基类

int main(){    
	Base* base_ptr1 = new Derived1;  // 定义一个基类指针,指向派生类1
    Derived1* derived1 = dynamic_cast<Derived1*>(base_ptr1);
    if (derived1){
        std::cout << "Successful!";     // 输出: Successful! 因为 Derived1类是Base类的派生类
    }
    else{
        std::cout << "Failed!";
    }
    Derived2* derived2 = dynamic_cast<Derived2*>(base_ptr1);
    if (derived2){
        std::cout << "Successful!";
    }
    else{
        std::cout << "Failed!";     // 输出: Failed! 因为 Derived1类和 Derived2类没有派生关系
    }
    delete base_ptr1, derived1, derived2; //删除指针,防止内存泄漏
};

4. 类型转换

C++中提供了5种类型转换方法,包括C语言中的直接转换、static_castconst_castdynamic_castreinterpret_cast,以下代码块展示五种类型转换方法的使用场景以及一些错误的用法。

#include<iostream>
using namespace std;

void modifyVariance(int* ptr){
    *ptr = 66;
};

int main(){
    // 1. c 语言直接类型转换
    // 使用场景:变量的类型直接转换
    int initial_c = 1;
    float transform_c = float(initial_c);
    cout << "Variance after transform by c: " << typeid(transform_c).name() << endl;  // Variance after transform by c: f

    // 2. static_cast
    // 使用场景:常见变量类型或指针的类型转换,相比于C转换,优势在于:
    //(1) 在编译过程中会检查转换是否有意义,更安全;
    // char* char_ptr = static_cast<char*>(&initial_c);         报错:编译器检测到并认定为无效的类型转换

    //(2) 应用的类型更加广泛(类、指针)
    class Base { /* ... */ };
    class Derived : public Base { /* ... */ };

    Base *initial_bPtr = new Derived;
    Base initial_obj;
    // Derived transform_obj = (Derived)initial_obj;                    报错:无法直接按照 C 语言方法转换类对象
    // Derived *transform_bPtr = Derived* (&initial_bPtr);              报错:无法直接按照 C 语言方法转换类指针类型
    // Derived transform_obj = static_cast<Derived>(initial_obj);       报错:只能转换类指针
    Derived *transform_bPtr = static_cast<Derived *>(initial_bPtr); 
    cout << "Variance after transform by static_cast: " << typeid(initial_bPtr).name() << endl;  // Variance after transform by static_cast: PZ4mainE7Derived
    delete initial_bPtr;
    // delete transform_bPtr;                                           报错:类型转换前后共享内存,故而导致重复释放

    // 3. const_cast
    // 使用场景:将常量 const 或 易变变量 volatile 转化为正常变量或者把一个指针/引用转化为 const/volatile 类型
    const int initial_const = 6;
    // int transform_const = const_cast<int>(initial_const);   报错:const_cast 中的类型必须是指针、引用或指向对象类型成员的指针
    // modifyVariance(&initial_const);                         报错:const int* 类型的实参和函数中的 int* 类型的形参不匹配
    int* transform_const = const_cast<int*>(&initial_const);
    modifyVariance(transform_const);
    cout << "Variance after transform by const_cast: " << initial_const << endl;  // Variance after transform by const_cast: 6   并没有改变原来常量值,证明 const_cast 转换得到的结果与原变量并非共享内存;
    cout << "Variance after transform by const_cast: " << *transform_const << endl;  // Variance after transform by const_cast: 66

    // 4. dynamic_cast
    // 使用场景:在程序运行时(动态),将包含多态(虚函数)的基类指针/引用转化为其派生类指针,若源对象并非目标转化类型,则返还空指针
    Base* initial_Ptr = new Derived;
    // Derived* transform_Ptr = dynamic_cast<Derived*>(initial_Ptr);       报错:运行时 dynamic_cast 的操作数必须包含多态类类型

    class BaseVirtual { 
        public:
            virtual void display(){cout<<"this is base class!"<<endl;};
    };
    class DerivedVirtual : public BaseVirtual {
        public:
            void display() override {cout<<"this is drived class!"<<endl;};
    };

    BaseVirtual* initial_PtrVirtual = new DerivedVirtual;
    Derived* transform_PtrVirtual = dynamic_cast<Derived*>(initial_PtrVirtual);   
    cout << "Variance after transform by static_cast: " << typeid(initial_PtrVirtual).name() << endl;  // Variance after transform by static_cast: PZ4mainE11BaseVirtual    
    cout << "Variance after transform by static_cast: " << typeid(transform_PtrVirtual).name() << endl;  // Variance after transform by static_cast: PZ4mainE7Derived
    delete initial_PtrVirtual, transform_PtrVirtual;     // 由于转换前后为深拷贝,所以需要分别进行释放(与static_cast不同)

    // 5. reinterpret_cast
    // 使用场景:强力转换,用于底层代码的编写或其他转换方法无法转化的情形。谨慎使用。
    int num = 42;
    char *char_ptr = reinterpret_cast<char *>(&num);
    for (size_t i = 0; i < sizeof(int); ++i) {
        // Print the individual bytes of the integer as characters
        std::cout << "Byte " << i << ": " << char_ptr[i] << std::endl;  // Byte 0: *, Byte 1:, Byte 2:, Byte 3:
    }
}

恭喜你!终于读完本篇文章,对于新手来说并不友好,读到这里你小小的脑袋可能会有些大大的疑惑,如:

  1. 虚函数是什么意思?
  2. 类的继承有什么作用?
  3. 指针的用法是如何?
  4. std::xx命名空间有什么用?
  5. 基类指针转化为派生类在实际中有什么应用场景?

  6. 且听后续娓娓道来!
  • 28
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值