C++中的xxx_cast

本文深入解析C++中四种类型的强制转换:C风格、ANSI-C++风格、重新解释型、静态型和动态型转换,包括它们的用法、注意事项及应用场景,特别强调了动态类型转换在类层次间安全转换的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C 风格强制转型

(T) expression  或

T(expression) //函数风格(Function-style)

两种形式之间没有本质上的不同。

对于具有转换的简单类型而言风格转型工作得很好。然而,这样的转换符也能不分皂白地应用于类(class)和类的指针。

C++类型转换


ANSI-C++标准定义了四个新的转换符:reinterpret_cast, static_cast, dynamic_cast和const_cast,目的在于控制类(class)之间的类型转换。

1.1       reinpreter_cast

用法:reinpreter_cast<type-id> (expression)

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。

这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。reinpreter_cast是特意用于底层的强制转型,导致实现依赖(就是说,不可移植)的结果。

int n=9;

// reinterpret_cast 仅仅是复制 n 的比特位到 d,因此d 包含无用值。

double d=reinterpret_cast<double & > (n);

1.2       const_cast

用法:const_cast<type_id> (expression)

用于修改类型的const或volatile属性。除了const 或volatile修饰之外,type_id和expression的类型是一样的,一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型,而C不提供消除const的机制(已验证)。

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

#include<iostream>
using namespace std;

/*
用法:const_cast<type_id> (expression)
  该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
  一、常量指针被转化成非常量指针,并且仍然指向原来的对象;
  二、常量引用被转换成非常量引用,并且仍然指向原来的对象;
  三、常量对象被转换成非常量对象。 
  type_id 必须为指针或引用
*/

class B
{
public:
	int m_iNum;
	B() : m_iNum(50)
	{   }
};

void foo()
{
	const B *b1 = new B();
	//b1->m_iNum = 100;          // 编译错误
	// 做如下转换,体现出转换为指针类型
	B *b2 = const_cast<B*>(b1);
	b2->m_iNum = 200;
	cout<<"b1: "<< b1->m_iNum <<endl;
	cout<<"b2: "<< b2->m_iNum <<endl;

	const B b3;
	//b3.m_iNum = 100;     // 编译错误
	B b4 = const_cast<B&>(b3);          // b4是另外一个对象
	b4.m_iNum = 200;
	cout<<"b3: "<<b3.m_iNum <<endl;
	cout<<"b4: "<<b4.m_iNum <<endl;

	const B b5;
	//b5.m_iNum = 100;     // 编译错误

	// 或者左侧也可以用引用类型,如果对b6的数据成员做改变,就是对b5的值在做改变
	B &b6 = const_cast<B&>(b5);
	b6.m_iNum = 200;
	cout<<"b5: "<<b5.m_iNum <<endl;
	cout<<"b6: "<<b6.m_iNum <<endl;
	
	// force to convert 
	const int x = 50;
	int* y = (int *)(&x);       // 同样的地址,但是内容是不一样的
	*y = 200;
	cout << "x: "<<x<<" address: "<<&x<<endl;
	cout << "*y: "<<*y<<" address: "<<y<<endl;
	cout<<endl;
	
	const int xx = 50;
	int* yy = const_cast<int *> (&xx);     // 同样的地址,但是内容是不一样的
	*yy = 200;
	cout << "xx: "<<xx<<" address: "<<&xx<<endl;
	cout << "*yy: "<<*yy<<" address: "<<yy<<endl;
	cout<<endl;
	// int
	const int xxx = 50;
	int yyy = const_cast<int&> (xxx);     // yyy是另外一个int对象
	yyy = 200;
	cout << "xxx: "<<xxx<<" address: "<<&xxx<<endl;
	cout << "yyy: "<<yyy<<" address: "<<&yyy<<endl;
}

int main(void)
{
	foo();
	return 0;
}

运行结果如下:

1.3       static_cast

用法:static_cast < type-id > ( expression )

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它允许执行任意的隐式转换和相反转换动作。主要有如下几种用法:

1)用于基本数据类型之间的转换,如把int转换成char,non-const 对象转型为 const 对象(这里相反方向不可以,C++只有const_cast可以)。

2)把空指针转换成目标类型的指针。(之前的做法是用强制转换(type-id*))

3)把任何类型的表达式转换成void类型。

4)应用到类的指针上,它允许子类类型的指针转换为父类类型的指针(upercasting这是一个有效的隐式转换);也能够执行相反动作,即转换父类为它的子类(downcasting),这种转换的安全性需要开发人员来保证(主要是在非上下转型中)。

class Base {};

class Derived : public Base {};

Base *a = new Base;

Derived *b = NULL;

b = static_cast<Derived *>(a); //可以通过编译,但存在安全隐患(如访问//Derived的成员)

注意:

1static_cast不能转换掉expressionconstvolitale、或者__unaligned属性。

2.在非基本类型或上下转型中,被转换的父类需要检查是否与目的类型相一致,否则,如果在两个完全不相干的类之间进行转换,将会导致编译出错。

1.4       dynamic_cast

只用于对象的指针和引用,主要用于执行“安全的向下转型”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。

当用于多态类型时(包含虚函数),它允许任意的隐式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(即隐式转换的相反过程),dynamic_cast根据RTTI信息检查操作是否有效。即在转换时dynamic_cast会检查转换是否能返回一个被请求的有效的完整对象。这种检查不是语法上的,而是真实情况的检查。检测在运行时进行,如果被转换的指针不是一个被请求的有效完整的对象指针,返回值为NULL。

先看RTTI相关部分,通常,许多编译器都是通过vtable找到对象的RTTI信息的,这也就意味着,如果基类没有虚函数,也就无法判断一个基类指针变量所指对象的真实类型这时候dynamic_cast只能用来做安全的转换(upercasting,如从派生类指针转换成基类指针,而这种转换其实并不需要dynamic_cast参与。

class Base { virtual dummy() {} };

class Derived : public Base {};

class Other{} ;

Base* b1 = new Derived;

Base* b2 = new Base;

Derived* d1 = dynamic_cast<Derived *>(b1);  // succeeds

Derived* d2 = dynamic_cast<Derived *>(b2);  // fails: returns 'NULL'

//如果一个引用类型执行了类型转换并且这个转换是不可能的,运行时一个//bad_cast的异常类型会被抛出:

Derived d3 = dynamic_cast<Derived &>(*b1);  // succeeds

Derived d4 = dynamic_cast<Derived &>(*b2);  // fails: exception thrown

注意:Base需要有虚函数,否则会编译出错。

来源:为什么需要dynamic_cast强制转换?
简单的说,当无法使用virtual函数的时候

典型案例:
Wicrosoft公司提供给我们一个类库,其中提供一个类Employee.以头文件Eemployee.h和类库.lib分发给用户
显然我们并无法得到类的实现的源代码
//Emplyee.h
class Employee 
{
public:
    
virtual int salary();
};

class Manager : public Employee
{
public
    
int salary();
};

class Programmer : public Employee
{
public:
    
int salary();
};

我们公司在开发的时候建立有如下类:
class MyCompany
{
public:
    
void payroll(Employee *pe);
    
//
};

void MyCompany::payroll(Employee *pe)
{
    
//do something
}

但是开发到后期,我们希望能增加一个bonus()的成员函数到W$公司提供的类层次中。
假设我们知道源代码的情况下,很简单,增加虚函数:
//Emplyee.h
class Employee 
{
public:
    
virtual int salary();
    
virtual int bonus();
};

class Manager : public Employee
{
public
    
int salary();
};

class Programmer : public Employee
{
public:
    
int salary();
    
int bonus();
};

//Emplyee.cpp

int Programmer::bonus()
{
    
//
}
payroll()通过多态来调用bonus()
class MyCompany
{
public:
    
void payroll(Employee *pe);
    
//
};

void MyCompany::payroll(Employee *pe)
{
    
//do something
    //pe->bonus();
}

但是现在情况是,我们并不能修改源代码,怎么办?dynamic_cast华丽登场了!
在Employee.h中增加bonus()声明,在另一个地方定义此函数,修改调用函数payroll().重新编译,ok
//Emplyee.h
class Employee 
{
public:
    
virtual int salary();
};

class Manager : public Employee
{
public
    
int salary();
};

class Programmer : public Employee
{
public:
    
int salary();
    
int bonus();//直接在这里扩展
};

//somewhere.cpp

int Programmer::bonus()
{
    
//define
}

class MyCompany
{
public:
    
void payroll(Employee *pe);
    
//
};

void MyCompany::payroll(Employee *pe)
{
    Programmer 
*pm = dynamic_cast<Programmer *>(pe);
    
    
//如果pe实际指向一个Programmer对象,dynamic_cast成功,并且开始指向Programmer对象起始处
    if(pm)
    {
        
//call Programmer::bonus()
    }
    //如果pe不是实际指向Programmer对象,dynamic_cast失败,并且pm = 0
    
else
    {
        
//use Employee member functions
    }
}



dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class Base
{
public:
    
int m_iNum;
    
virtual void foo();
};

class Derived:public Base
{
public:
    
char *m_szName[100];
};

void func(Base *pb)
{
    Derived 
*pd1 = static_cast<Derived *>(pb);

    Derived 
*pd2 = dynamic_cast<Derived *>(pb);
}

在上面的代码段中,
如果pb实际指向一个Derived类型的对象,pd1和pd2是一样的,并且对这两个指针执行Derived类型的任何操作都是安全的;
如果pb实际指向的是一个Base类型的对象,那么pd1将是一个指向该对象的指针,对它进行Derived类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针(即0,因为dynamic_cast失败)。
另外要注意:Base要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class Base
{
public:
    
int m_iNum;
    
virtual void f(){}
};



class Derived1 : public Base
{

};

class Derived2 : public Base
{

};

void foo()
{
    derived1 
*pd1 = new Drived1;

    pd1
->m_iNum = 100;

    Derived2 
*pd2 = static_cast<Derived2 *>(pd1); //compile error

    Derived2 
*pd2 = dynamic_cast<Derived2 *>(pd1); //pd2 is NULL

    delete pd1;
}

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

1.5       小结

四种类型转换操作符对于隐式的类型转换没有必要。

static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随着不安全性。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时(基类需要包含虚函数),dynamic_cast具有类型检查的功能,牺牲了效率,但比static_cast安全。


<think>我们正在讨论C++中的`xxx.size`。在C++标准库中,许多容器(如`vector`、`string`、`map`、`set`等)都有一个成员函数`size()`,它返回容器中当前元素的数量。这里的`xxx`代表容器的实例名。 例如:-对于`std::vector<int>v;`,`v.size()`返回`v`中元素的个数。-对于`std::string s;`,`s.size()`返回字符串的长度(字符的个数)。注意:`size()`返回的是无符号整数类型(通常是`size_t`),因此在使用时要注意与有符号数的比较,避免出现意外的结果(因为当有符号数为负数时,与无符号数比较会变成很大的正数)。引用[1]中展示了一个例子:当使用`inti= num.size()-1;`和`unsignedint j= num.size()-1;`时,如果`num.size()`为0,则`num.size()-1`会是一个很大的无符号数(因为无符号数下溢),而用有符号`int`来存储可能会得到负数(但实际上这里发生了无符号数到有符号数的转换,当无符号数超出有符号数表示范围时,结果是实现定义的,通常会是负数)。这提醒我们要注意类型。引用[2]中展示了`map`的`size()`的使用:`cout<< "The mapsize is" <<mp.size() <<endl;`,这里`mp.size()`返回`map`中键值对的数量。 因此,`xxx.size()`表示容器`xxx`的当前大小(元素个数)。下面我们详细解释:1.返回值类型:`size_t`,这是一个无符号整数类型,在`<cstddef>`中定义,通常足够大以表示任何对象的大小。 2.时间复杂度:对于标准容器,`size()`通常是常数时间操作(O(1)),除了`std::list`和`std::forward_list`(C++11中`std::list`的`size()`也是常数时间,但`forward_list`没有`size()`方法)。 3.使用场景:当你需要知道容器中有多少元素时使用,比如循环遍历容器。 注意事项: -不要将`size()`返回的无符号数与有符号数直接比较,特别是当有符号数可能是负数时。例如:```cppstd::vector<int>v;//假设v为空,那么v.size()为0if (v.size() >-1) //这里-1会被转换为无符号数,变成一个很大的正数,所以条件为假```因此,建议使用`std::vector<int>::size_type`或者`auto`来存储`size()`的返回值,或者将无符号数转换为有符号数(但要确保容器大小不会超过有符号数的表示范围)。 相关问题: 1. C++中`size()`和`capacity()`有什么区别? 2.为什么`std::forward_list`没有`size()`方法? 3.在C++中,如何安全地比较容器的`size()`和整数? 4.除了`size()`,还有哪些方法可以获取容器的元素数量?</think>### C++ 中 `xxx.size` 的含义 在 C++ 中,`xxx.size` 通常表示**容器或字符串对象中元素的数量**,其中 `xxx` 是标准库容器(如 `vector`、`string`、`map` 等)的实例名。核心要点如下: 1. **基本作用** `size()` 是 C++ 标准库容器的成员函数,返回当前容器内元素的个数,返回值类型为 `size_t`(无符号整数)。 示例: ```cpp std::vector<int> v = {1, 2, 3}; std::cout << v.size(); // 输出:3 ``` 2. **常见容器的 `size()` 行为** - **`std::vector`/`std::string`**:返回元素数量或字符数 ```cpp std::string s = "Hello"; std::cout << s.size(); // 输出:5 ``` - **`std::map`/`std::set`**:返回键值对的数量 引用[2]示例中的 `mp.size()` 返回插入成功的键值对数量[^2]。 3. **关键注意事项** - **无符号整数风险**:`size_t` 是无符号类型,直接与有符号数比较可能导致意外行为。 引用[1]展示了当容器为空时,`num.size()-1` 会因无符号下溢变成极大值(如 `4294967295`)[^1]: ```cpp vector<int> num = {}; // 空容器 int i = num.size() - 1; // i = -1(有符号) unsigned int j = num.size() - 1; // j = 4294967295(无符号下溢) ``` - **时间复杂度**:多数容器(如 `vector`、`map`)的 `size()` 是 $O(1)$ 操作,但 `std::list` 在 C++11 前是 $O(n)$。 4. **与 `capacity()` 的区别** `size()` 表示**实际元素数量**,而 `capacity()` 表示容器**预分配的内存空间**(`vector` 特有)。 示例: ```cpp std::vector<int> v; v.reserve(100); // 预分配 100 个元素空间 v.push_back(1); std::cout << v.size(); // 输出:1(实际元素数) std::cout << v.capacity(); // 输出:100(预分配空间) ``` ### 使用建议 - **避免有符号/无符号混用**:比较时优先转成相同类型: ```cpp if (static_cast<int>(v.size()) > -1) // 显式转换 ``` - **空容器检查**:优先用 `empty()` 而非 `size()==0`(更语义化且避免无符号问题): ```cpp if (!v.empty()) { /* 非空时操作 */ } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值