03 C++类型转换

本文详细介绍了C++中static关键字的不同用途,包括静态局部变量、静态成员变量、静态成员函数、静态全局变量和单例模式。此外,还探讨了const关键字在声明常量和限制修改的重要性,以及类型转换和explicit关键字的使用。

c++中的static


在C++编程语言中,static关键字有几种不同的用途,具体取决于它所修饰的对象或函数:

  1. 静态局部变量

当static用于局部变量时,它改变了局部变量的存储期限。通常,局部变量在函数调用结束后就不存在了,但静态局部变量在程序执行期间一直存在,直到程序结束。它们的初始值只设置一次,并且在函数调用之间保持它们的值。

  1. 静态成员变量

在类中,static关键字可以用来声明类的成员变量。这样的变量不是属于类的某个具体对象的,而是被类的所有对象共享。无论创建了多少个类的对象,静态成员变量只有一个副本。

  1. 静态成员函数

类的成员函数也可以被声明为static。静态成员函数不依赖于类的对象,它们可以独立于任何对象而被调用。静态成员函数只能访问静态成员变量和其他静态成员函数。

  1. 静态全局变量

在全局作用域中,static关键字用来声明一个文件内的全局变量。这样的变量只在定义它的文件内可见,不会与其他文件中的同名变量冲突。

  1. 静态函数

在全局作用域中,static关键字可以用来声明一个函数,使得该函数只在定义它的文件内可见。这有助于避免在其他文件中同名函数的冲突。

static关键字在C++中的使用非常灵活,可以用于控制变量和函数的作用域、生命周期和链接属性。正确使用static可以增强程序的模块性和安全性。

  • static修饰类的函数成员,不能在该函数中使用this指针,也不能使用非静态数据成员
    - static修饰类的成员函数 先于 类的对象存在,如果是公有静态函数可以在类的外部直接通过类名调用

C++中的单例


​ 单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在C++中,实现单例模式通常涉及以下步骤:

1.  将构造函数设为私有,以防止外部通过new关键字创建类的实例。
2.  在类中定义一个静态的私有成员变量,用于存储单例实例。
3.  提供一个静态的公有方法(通常称为`GetInstance`),用于获取单例实例。如果实例尚未创建,则在该方法中创建实例。
class Singleton {
public:
    // 获取单例实例的静态方法
    static Singleton& GetInstance() {
        static Singleton instance; // 局部静态变量,线程安全(C++11起)
        return instance;
    }
    // 禁止复制构造函数和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    // 示例方法
    void ShowMessage() {
        std::cout << "Hello from Singleton!" << std::endl;
    }
private:
    // 私有构造函数,防止外部创建实例
    Singleton() {
        // 初始化操作
    }
    // 私有静态成员变量,用于存储单例实例
    static Singleton* m_Instance;
};

// 初始化静态成员变量
// Singleton* Singleton::m_Instance = nullptr; // 如果需要懒汉式初始化,则需要此行

int main() {
    Singleton::GetInstance().ShowMessage();
}
//其中 = delete;是一个特殊标识符 显式地禁用某个函数
    

​ 在C++中,= delete是一个特殊的标识符,用于显式地禁用某个函数。当你在构造函数、拷贝构造函数、赋值运算符或其他成员函数后面加上= delete时,你是在告诉编译器,你不希望这个函数被使用,如果有人尝试调用它,编译器应该报错。

​ 通过删除拷贝构造函数和赋值运算符,单例类保证了其实例的唯一性,因为不能通过这些操作来创建或复制实例。这样,唯一能够获取单例实例的方式就是通过GetInstance静态方法。

const关键字


在C++中,const关键字用于声明一个变量、对象或函数参数为常量,意味着它们的值在初始化后不能被修改。const的使用可以提供额外的类型安全性和清晰的代码意图。以下是const在不同上下文中的使用方式:

  1. 常量变量: 使用const声明的变量必须在声明时初始化,之后不能更改其值。

    const int max_value = 100;
    // max_value = 200; // 错误,不能修改常量变量的值
    
  2. 常量引用const可以用来创建对变量的常量引用,这意味着通引用不能修改所引用的值

    int value = 42;
    const int& ref = value;
    // ref = 43; // 错误,不能通过常量引用修改值
    
  3. 常量指针const可以用于指针,有两种用法:指向常量的指针和常量指针。

    • 指向常量的指针(pointer to const)不能用于修改其所指向的数

      int value = 42;
      const int* ptr = &value;
      // *ptr = 43; // 错误,不能通过指向常量的指针修改数据
      
    • 常量指针(const pointer)是指针本身的值是常量,即不能更改指针所指向的地址。

      int value = 42;
      int* const ptr = &value;
      // ptr = nullptr; // 错误,不能修改常量指针的值
      
  4. 函数参数: 函数的参数可以使用const来保证在函数内部不会修改传入的参数

    void print(const std::string& message) {
        // 函数内部不能修改message的值
    }
    
  5. 成员函数: 类的成员函数可以使用const修饰符来表明该函数不会修改类的成员变量。这样的函数被称为常量成员

    class MyClass {
    public:
        void modify() {
            // 可以修改成员变量
        }
        void showInfo() const {
            // 不能修改成员变量
        }
    };
    
  6. 常量表达式和编译时常量: 在C++11及以后的版本中,const可以与constexpr关键字结合使用,来声明一个在编译时就可以确定的常量表达式。

    constexpr int factorial(int n) {
        return n <= 1 ? 1 : (n * factorial(n - 1));
    }
    const int result = factorial(5); // 在编译时计算
    

    使用const可以提高代码的可读性和维护性,同时可以帮助编译器进行更多的优化。在编写C++代码时,应当尽可能使用const保护不应该被修改的数据

const 成员函数


在C++中,将类成员函数声明为const的目的是告诉编译器这个函数不会修改类的任何非静态成员变量。这样的函数被称为常量成员函数或const成员函数。通过声明为const,你可以确保函数只读取对象的状态,而不是修改它。

class MyClass {
public:
    MyClass(int x) : x_(x) {}

    // 常量成员函数
    int getValue() const {
        return x_;
    }

    // 非常量成员函数
    void setValue(int value) {
        x_ = value;
    }

private:
    int x_;
};

getValue是一个const成员函数,它保证不修改类的任何成员变量。这意味着你可以在常量对象上调用

const MyClass obj(42);
std::cout << obj.getValue() << std::endl; // 正确,可以在常量对象上调用const成员函数

然而,你不能在常量对象上调用非常量成员函数,因为这些函数可能会修改对象的状态:

const MyClass obj(42);	//常量对象
// obj.setValue(43); 	// 错误,不能在常量对象上调用非常量成员函数

如果你尝试在const成员函数中修改成员变量,编译器将会报错:

class MyClass {
public:
    // 错误,尝试在const成员函数中修改成员变量
    void incorrectFunction() const {
        x_ = 10; // 编译错误
    }
};

==const成员函数的一个重要用途是在函数重载==中使用。你可以有一个成员函数的const版本和非const版本,编译器会根据对象的类型选择合适的版本:

class MyClass {
public:
    void doSomething() {
        // 非const版本的实现
    }
    void doSomething() const {
        // const版本的实现
    }
};

​ 在这个例子中,如果对象是const的,那么const版本的doSomething会被调用;如果对象不是const的,那么非const版本的doSomething会被调用。

总之,const成员函数是C++中的一种机制,用于确保成员函数不会修改对象的状态,从提高代码的安全性和可读性。

类型转换


C语言中的类型转换

在C语言中,类型转换通常是通过强制类型转换实现的,强制类型转换有两种形式:

  1. 传统强制类型转换 (C-style cast)

    (type) expression
        double d = 3.14;
        int i = (int)d;
    

    这种方式可以用于任何类型的转换,但它不区分整数提升、浮点数转换、指针转换等,可能会导致潜在的问题。

  2. 函数风格强制类型转换 (Function-style cast)

    type (expression)
        void func(double d) {std::cout << d << std::endl;}
        int main() {
            int i = 42;
            func(i); // i会被隐式转换为double
            return 0;
        }
    

C++中的类型转换

😡C++在C的基础上加了四种类型转换操作符,以提供更明确和安全的类型转换:

  1. static_cast 标准转换

    static_cast<type>(expression)
        int i = 42;
        double d = static_cast<double>(i); // 将int转换为double
    

    static_cast可以在相关类型之间进行转换,它可以在编译时进行检查。它通常用于执行标准转换,如基本数据类型之间的转换、类层次结构中上下转换(只要没有虚继承),以及用户定义的类型转换(通过重载类型转换运算符)。

  2. 😡 dynamic_cast

    dynamic_cast<type>(expression)
    class Base {
        public:
            virtual void print() const { std::cout << "Base" << std::endl; }
        };
    
        class Derived : public Base {
        public:
            void print() const override { std::cout << "Derived" << std::endl; }
        };
    
        Base* basePtr = new Derived();
        Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);	对象的向下转型
    
        if (derivedPtr) {
            derivedPtr->print(); // 输出 "Derived"
        } else {
            std::cout << "Cast failed" << std::endl;
        }
    

    dynamic_cast主要用于对象的向下转型(从基类指针到派生类指针)和多态类型转换。它会在运行时检查类型是否正确。如果转换是不可能的,例如,转换的对象不是目标类型的实例,那么dynamic_cast会返回空指针(对于指针类型)或抛出std::bad_cast异常(对于引用类型)。

  3. reinterpret_cast 低级转换

    reinterpret_cast<type>(expression)
        int i = 42;
        int* p = &i;
        void* voidPtr = reinterpret_cast<void*>(p); // 将int*转换为void*
    

    reinterpret_cast用于低级转换,它会重新解释位模式,通常用于将指针转换为整数或反之。这种转换是非常危险的,因为它不进行任何类型检查,只是简单地将位模式从一个类型复制到另一个类型。

  4. const_cast

    const_cast<type>(expression)
        const char* constStr = "Hello, World!";
        char* nonConstStr = const_cast<char*>(constStr); // 去除const属性
    
    // 注意:修改通过const_cast去除const属性后的指针指向的内容是未定义行为
    

    const_cast用于去除或添加const属性。它只能用于改变表达式的constvolatile属性,不能用于其他类型的转换。

隐式类型转换

在C和C++中,编译器会在某些情况下自动执行类型转换,这称为隐式类型转换。隐式类型转换发生在以下几种情况:

  • 算术转换
    • 当不同类型的值在表达式中进行运算时,编译器会将它们转换为共同的类型。例如,intfloat相加时,int会被转换为float
  • 赋值转换
    • 当将一个值赋给另一个类型的变量时,如果可能,编译器会自动转换类型。
  • 函数调用转换
    • 当传递参数给函数时,如果参数类型与形参类型不匹配,编译器会尝试将实参转换为形参类型。
  • 数组到指针转换
数组到指针转换:在大多数情况下,数组名会被自动转换为指向数组第一个元素的指针。
  int arr[10];
  int* ptr = arr; // arr被隐式转换为int*
  • 整型提升
char c = 'A';
int i = c; // c被隐式转换为int
//char short 等类型会被自动提升为int或更大的整数类型。

自定义数据类型

隐式类型转换的风险一般存在于自定义类型转换间。尤其需要注意自定义类的构造函数。例如:

class MyString
{
public:
    MyString(int n) {} // 本意:预先分配n个字节给字符串
    MyString(const char* p) {} // 用C风格的字符串p作为初始化值
};

void main()
{
    MyString s1 = "China"; 			//隐式转换,等价于MyString s1 = MyString("China")
    MyString s2(10); 				//分配10个字节的空字符串
    MyString s3 = MyString(10); 	//分配10个字节的空字符串
    MyString s4 = 10; 				//也是分配10个字节的空字符串
    MyString s5 = 'A'; 				//分配int('A')个字节的空字符串
    
    // s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
}

要想禁止隐式类型转换,可以使用C++关键字explicit。

#include<iostream>

using namespace std;

class MyString
{
public:
    //explicit 用途: 防止隐式类型转换方式 来初始化对象
    explicit MyString(int n) {
        cout<<"MyString(int n)"<<endl;
    } // 本意:预先分配n个字节给字符串
    
    MyString(const char* p) {
        cout<<"MyString(const char* p)"<<endl;
    } // 用C风格的字符串p作为初始化值
};

int main()
{	    MyString s4 = 1008611; 
    //1、在没有加explicit关键字之前,编译器会自动进行隐式类型转换 MyString s4 = MyString(1008611); 编译通过
    //2、在加了explicit关键字之后,编译器不会给你做类型转换,此时编译错误,目的是告诉程序员不要使用这种写法,因为这种写法会让别人误解

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值