C++ RTTI和类型转换运算符

目录

 

一、RTTI

1、dynamic_cast运算符

  2、dynamic_cast实现原理

3、typeid 运算符和type_info类

4、typeid 实现原理

二、类型转换运算符

1、static_cast 运算符

2、const_cast

3、reinterpret_cast


一、RTTI

     RTTI是Runtime Type Identification的缩写,即运行时类型识别,主要用于运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型,进而调用实际类型的特定方法。C++在编译器层面提供了typeid和dynamic_cast两个运算符来支持RTTI。

1、dynamic_cast运算符

     dynamic_cast不能获取某个基类指针或者引用指向或者引用的实际类型,但是能够判断该基类指针或者引用能否安全的转换为某个实际类型,如果能够转换则返回该实际类型的指针或者引用,如果不能够转换则返回空指针,因为没有空引用所以这种情况下会抛出bad_cast异常。注意使用dynamic_cast要求基类必须提供虚方法,否则直接报错源类型不是多态的。

利用dynamic_cast对基类指针做转换的示例如下:

#include <iostream>

using std::cout;

class ClassA {
public:
	virtual ~ClassA() {
	}
	;
	virtual void say() {
		cout << "ClassA\n";
	}
	;
};

class ClassB: public ClassA {
public:
	void say() {
		cout << "ClassB\n";
	}
	;
	void sayB() {
		cout << "sayB\n";
	}
	;
};

class ClassC: public ClassB {
public:
	void say() {
		cout << "ClassC\n";
	}
	;
	void sayB() {
		cout << "ClassC sayB\n";
	}
	;
	void sayC() {
		cout << "sayC\n";
	}
	;
};

int main() {
	//a的指针类型是ClassA,无法调用a的实际类型ClassC的特定方法
	ClassA * a = new ClassC;
//	ClassA * a = new ClassB;
	//如果ClassB不包含虚函数,则直接报错源类型不是多态的
	ClassB * b = dynamic_cast<ClassB*>(a);
	//如果不能转换,b是空指针,if为false
	if (b) {
		//say是虚方法,依然按照a实际指向的类型ClassC调用其say方法
		b->say();
		//sayB不是虚方法,按照b的指针类型ClassB调用其sayB方法
		b->sayB();
	}
	ClassC * c = dynamic_cast<ClassC*>(a);
	if (c) {
		c->say();
		c->sayC();
	}

	return 0;
}

 利用dynamic_cast对基类引用做转换的示例如下:

#include <iostream>
#include <typeinfo>

using std::cout;
using std::bad_cast;

class ClassA {
public:
	virtual ~ClassA() {
	}
	;
	virtual void say() {
		cout << "ClassA\n";
	}
	;
};

class ClassB: public ClassA {
public:
	void say() {
		cout << "ClassB\n";
	}
	;
	void sayB() {
		cout << "sayB\n";
	}
	;
};

class ClassC: public ClassB {
public:
	void say() {
		cout << "ClassC\n";
	}
	;
	void sayB() {
		cout << "ClassC sayB\n";
	}
	;
	void sayC() {
		cout << "sayC\n";
	}
	;
};

int main() {
	//模拟方法调用中的将子类实例传给基类引用参数
	ClassB cb;
	ClassA & a = cb;
	try {
		ClassB & b = dynamic_cast<ClassB &>(a);
		b.say();
		b.sayB();
	} catch (bad_cast & e) {
		cout << "cast error,errmsg->" << e.what() << "\n";
	}
	try {
		ClassC & c = dynamic_cast<ClassC &>(a);
		c.say();
		c.sayC();
	} catch (bad_cast & e) {
		cout << "cast error,errmsg->" << e.what() << "\n";
	}

	return 0;
}

  2、dynamic_cast实现原理

       dynamic_cast要求基类必须提供虚函数,因此其实现应该跟虚函数表有关,反汇编第一个测试用例,执行ClassB * b = dynamic_cast<ClassB*>(a);时的汇编代码如下:

 <main()+34>: mov    -0x18(%rbp),%rax   将ClassA指针a的地址拷贝到rax中
 <main()+38>: test   %rax,%rax        对rax中的值判断是否为空,即a是否是空指针
 <main()+41>: jne    0x40098f    <main()+50>  如果不是空指针,则跳转到main+50的代码处
 <main()+43>: mov    $0x0,%eax
 <main()+48>: jmp    0x4009a6    <main()+73>
 <main()+50>: mov    $0x0,%ecx    准备__dynamic_cast调用的四个参数
 <main()+55>: mov    $0x400e30,%edx
 <main()+60>: mov    $0x400e50,%esi
 <main()+65>: mov    %rax,%rdi
 <main()+68>: callq  0x400850    <__dynamic_cast@plt>  调用__dynamic_cast函数
 <main()+73>: mov    %rax,-0x20(%rbp)  将调用结果从rax拷贝到栈帧中

__dynamic_cast函数是底层C++ lib包中提供的实现,由C++ Itanium ABI定义,0x400e30和0x400e50两个是编译期确认的两个全局变量,分别是ClassA 和ClassB的类型信息。其大致实现原理跟虚函数调用一样,在虚函数表索引为-1处通过type_info类保存了变量的实际类型信息,根据该实际类型和目标类型的继承关系判断类型转换是否安全,但是该类型信息无法通过info vbl查看,如下图:

3、typeid 运算符和type_info类

     typeid运算符的入参可以是一个类型名或一个结果为对象的表达式,对象可以是任意类型,注意判断某个基类指针实际指向的类型时必须对该指针变量解引用。该运算符返回一个类型为type_info的对象的const引用,type_info类在头文件typeinfo中定义,其重载了==和!=运算符,可以借此对类型进行比较,type_info类还有一个name()方法可以返回类型信息,不同编译器厂商的实现不一致。注意typeid的入参为空指针时会抛出bad_typeid异常。

基于上述第二个测试用例,main方法修改如下:

int main() {
	ClassA * a = new ClassC;
//	ClassA * a = new ClassB;
    cout<<"typeid(a).name()-->"<<typeid(*a).name()<<"\n";
	if(typeid(ClassC)==typeid(*a)){
		cout<<"is ClassC,name->"<<typeid(ClassC).name()<<"\n";
	}
	if(typeid(ClassB)==typeid(*a)){
		cout<<"is ClassB,name->"<<typeid(ClassB).name()<<"\n";
	}

	return 0;
}

执行结果如下:

上述示例中ClassA包含了虚函数,typeid准确的识别出了变量a的真实类型,如果是普通的不包含虚函数的ClassA了?将该用例的virtual关键字去掉,执行结果如下:

 说明typeid同dynamic_cast一样,只有在类包含虚函数的情况下才能正确识别出基类指针或者引用实际指向或者引用的类型,与dynamic_cast不同的是,如果基类没有包含虚函数, typeid不会编译报错,而是返回目标变量在编译期确认的类型信息。

4、typeid 实现原理

     在ClassA是虚函数下反汇编cout<<"typeid(a).name()-->"<<typeid(*a).name()<<"\n";,其中typeid操作符相关汇编代码如下:

cmpq   $0x0,-0x18(%rbp)  判断指针a是否非空,如果是空的则执行main()+111
je     0x400a0c    <main()+111>
mov    -0x18(%rbp),%rax    将指针a的地址拷贝到rax中
mov    (%rax),%rax   将rax中的地址处的后8个字节拷贝到rax中,即获取虚函数表的地址
mov    -0x8(%rax),%rax  将虚函数表地址前8个字节拷贝到rax中,即type_info对象的地址
mov    %rax,%rdi   将type_info 对象的地址拷贝到rdi中,准备函数调用
callq  0x400b24    <std::type_info::name()    const>

     在ClassA是非虚函数下反汇编的代码如下:

非虚函数下,表示type_info类实例的地址变成一个编译期确认的全局变量了。    

      综上可知编译器在编译时为每个类都生成了一个记录其类型信息相关的全局type_info实例,如果是包含虚函数的类,该实例的地址保存在虚函数表索引为-1处,可通过虚函数表获取类型信息。可以用代码模拟上述行为,如下所示:

int main() {
	using std::type_info;
	typedef void (*FUNC)();
	ClassC a;
	ClassA * b = &a;
	long *vp = (long *) (*(long*) &a);
	const type_info & ti = typeid(*b);
	type_info * f = (type_info *) vp[-1];
	cout << "name1->" << ti.name() << "\n";
	cout << "name2->" << f->name() << "\n";
	cout << "is true:" << (f == &ti) << "\n";
	cout << "end\n";

	return 0;
}

执行结果如下:

参考:C++对象模型之RTTI的实现原理

           C/C++杂记:运行时类型识别(RTTI)与动态类型转换原理

二、类型转换运算符

     C中对指针变量的类型转换没有限制,这可以让C轻松获取或者修改任意数据结构在内存中的数据,非常灵活强大,如上一节中用代码获取虚函数表中的type_info实例,但是这样也导致了内存安全问题。同时因为代码编译层面无法对上述转换做校验也导致了很多潜在的类型转换Bug,这些bug不一定导致程序崩溃,其行为是不可预知的。如下示例:

int main() {
	//在ClassA是虚函数下
//	ClassA * a = new ClassC;
	ClassA * a = new ClassA;
	ClassB * b=(ClassB*)a;
	b->sayB();

	return 0;
}

上述代码在理论上应该报错,基类实例无法向下做类型转换,但是编译正常,运行结果也正常,如下:

sayB方法中未使用类属性,如果使用了获取到的类属性的值就是未知的,有可能是非法内存访问而导致程序崩溃,也可能是其他变量占用的内存获取的值未知。因为类似这种指针变量的强制类型转换在C中是非常普遍的,也是C强大的特性之一,C++作为C的超集,必须兼容这种特性,所以就产生上述问题。

    C++为了在代码编译层面对指针类型转换做校验,尽可能暴露类型转换可能导致的问题,添加了4个类型转换运算符,分别是dynamic_cast,static_cast,const_cast,reinterpret_cast,dynamic_cast在上一节中已经清楚了,再看另外3个的用法,注意这4个都只适用于指针或者引用类型变量。

1、static_cast 运算符

      static_cast的用法同dynamic_cast,dynamic_cast是在运行期检查源类型变量能否转换成目标类型变量,要求源类型必须提供虚函数,并且转换完成后调用虚函数时依然使用变量的实际类型的虚函数实现;static_cast是在编译期检查源类型变量能否转换成目标类型变量,要求源类型与目标类型有继承关系或者是内置的默认类型转换,如double转换成int,不要求源类型提供虚函数,如果不符合要求编译报错,转换完成后调用虚函数时使用目标类型的实现,如下示例:

int main() {
	//在ClassA是虚函数下
	ClassA * a = new ClassC;
//	ClassA * a = new ClassA;
	ClassB * b=static_cast<ClassB*>(a);
	b->sayB();
	//编译不报错
	int * i=(int *)a;
	//编译不报错
//    int * s=static_cast<int *>(a);

	return 0;
}

2、const_cast

      const_cast用于去掉原变量的const/volitale限制,注意dynamic_cast/static_cast对const变量做类型转换时目标变量也必须带上const,否则编译报错,如下示例:

int main() {

	const ClassA * ca=new ClassC;
	//两处的const都是必须的,否则编译报错
	const ClassB * cb=dynamic_cast<const ClassB *>(ca);
	//编译报错,转换成const以后不能调用类实例方法,因为这可能修改类属性,只能调用类静态方法
//	cb->sayB();

	const int a = 10;
	//编译报错,要求pt必须是const int *
//	int* pt = &a;
	const int* pt = &a;
	//编译报错,pt对应的变量不可修改
//	*pt=12;
	int* b = const_cast<int*>(pt);
	*b = 20;
	cout << "b = " << *b << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "&a = " << &a << endl;

	//编译报错,int不是指针或者引用类型
//	int b2 = const_cast<int>(a);
//	b2=13;

	return 0;
}

执行结果如下:

有趣的是这里打印a的值并未改变,但是*b的值却改变了,b也确实指向a,反汇编找答案。打印*b的汇编代码如下:

mov    (%rax),%ebx表示将rax地址指向的内存的后4个字节拷贝到rbx中,即读取变量a的值。

打印变量a的代码如下:

0xa是十六进制的10,即这里打印a是将a作为一个字符常量处理了,编译完成就写死成10了,而没有从a的内存地址读取实际的值,这应该是编译器自己的优化了。为了规避上述优化,将测试代码调整如下:

int main() {

	cout<<"请输入一个数:"<<endl;
	int input;
	cin>>input;

	const int a = input;
	const int* pt = &a;
	int* b = const_cast<int*>(pt);
	*b = 20;
	cout << "b = " << *b << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "&a = " << &a << endl;

	return 0;
}

 执行结果如下:

这回变量a的值变了,反汇编打印a的代码如下:

 mov    %ebx,%esi就是将变量a的值拷贝到esi中,因为a是在运行期确认的,所以这里无法将其视为字符常量处理,必须在栈中为其分配内存。

3、reinterpret_cast

    reinterpret_cast做的校验非常有限,例如不能将指针变量强转成4字节的int变量,不能将指向函数的指针转换成指向数字的指针,一般情况下跟C中指针强转的效果一样,因此需要谨慎使用,如下示例:

#include <iostream>

using std::cout;
using std::endl;

class ClassA {
private:
	int a=1;
public:
	void say(){cout<<"ClassA";};
};


int main() {

	ClassA * a = new ClassA;
	int * b = reinterpret_cast<int *>(a);
    cout<<*b<<endl;

    //跟reinterpret_cast效果等价
    int * c=(int *)a;
    cout<<*c<<endl;

    //报错损失精度,因为指针是8字节,int是4字节
    //	int b2 = reinterpret_cast<int>(a);
    long b2=reinterpret_cast<long>(a);
    cout<<b2<<endl;

    ClassA a2;
    //报错类型转换无效
//    int b2 = reinterpret_cast<int>(a2);

	return 0;
}

    参考:C++的类型转换运算符总结

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第0章 导读(译者的话) 第1章 关于对象(Object Lessons) 加上封装后的布局成本(Layout Costs for Adding Encapsulation) 1.1 C++模式模式(The C++ Object Model) 简单对象模型(A Simple Object Model) 表格驱动对象模型(A Table-driven Object Model) C++对象模型(Th e C++ Object Model) 对象模型如何影响程序(How the Object Model Effects Programs) 1.2 关键词所带来的差异(A Keyword Distinction) 关键词的困扰 策略性正确的struct(The Politically Correct Struct) 1.3 对象的差异(An Object Distinction) 指针的类型(The Type of a Pointer) 加上多态之后(Adding Polymorphism) 第2章 构造函数语意学(The Semantics of constructors) 2.1 Default Constructor的建构操作 “带有Default Constructor”的Member Class Object “带有Default Constructor”的Base Class “带有一个Virual Function”的Class “带有一个virual Base class”的Class 总结 2.2 Copy Constructor的建构操作 Default Memberwise Initialization Bitwise Copy Semantics(位逐次拷贝) 不要Bitwise Copy Semantics! 重新设定的指针Virtual Table 处理Virtual Base Class Subobject 2.3 程序转换语意学(Program Transformation Semantics) 明确的初始化操作(Explicit Initialization) 参数的初始化(Argument Initialization) 返回值的初始化(Return Value Initialization) 在使用者层面做优化(Optimization at the user Level) 在编译器层面做优化(Optimization at the Compiler Level) Copy Constructor:要还是不要? 摘要 2.4 成员们的初始化队伍(Member Initialization List) 第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data Member 3.4 “继承”与Data Member 只要继承不要多态(Inheritance without Polymorphism) 加上多态(Adding Polymorphism) 多重继承(Multiple Inheritance) 虚拟继承(Virtual Inheritance) 3.5 对象成员的效率(Object Member Efficiency) 3.6 指向Data Members的指针(Pointer to Data Members) “指向Members的指针”的效率问题 第4章 Function语意学(The Semantics of Function) 4.1 Member的各种调用方式 Nonstatic Member Functions(非静态成员函数) Virtual Member Functions(虚拟成员函数) Static Member Functions(静态成员函数) 4.2 Virtual Member Functions(虚拟成员函数) 多重继承下的Virtual Functions 虚拟继承下的Virtual Functions 4.3 函数的效能 4.4 指向Member Functions的指针(Pointer-to-Member Functions) 支持“指向Virtual Member Functions”之指针 在多重继承之下,指向Member Functions的指针 “指向Member Functions之指针”的效率 4.5 Inline Functions 形式对数(Formal Arguments) 局部变量(Local Variables) 第5章 构造、解构、拷贝 语意学(Semantics of Construction,Destruction,and Copy) 纯虚拟函数的存在(Presence of a Pure Virtual Function) 虚拟规格的存在(Presence of a Virtual Specification) 虚拟规格中const的存在 重新考虑class的声明 5.1 无继承情况下的对象构造 抽象数据类型(Abstract Data Type) 为继承做准备 5.2 继承体系下的对象构造 虚拟继承(Virtual Inheritance) 初始化语意学(The Semantics of the vptr Initialization) 5.3 对象复制语意学(Object Copy Semantics) 5.4 对象的功能(Object Efficiency) 5.5 解构语意学(Semantics of Destruction) 第6章 执行期语意学(Runting Semantics) 6.1 对象的构造和解构(Object Construction and Destruction) 全局对象(Global Objects) 局部静态对象(Local Static Objects) 对象数组(Array of Objects) Default Constructors和数组 6.2 new和delete运算符 针对数组的new语意 Placement Operator new的语意 6.3 临时性对象(Temporary Objects) 临时性对象的迷思(神话、传说) 第7章 站在对象模型的类端(On the Cusp of the Object Model) 7.1 Template Template的“具现”行为(Template Instantiation) Template的错误报告(Error Reporting within a Template) Template中的名称决议方式(Name Resolution within a Template) Member Function的具现行为(Member Function Instantiation) 7.2 异常处理(Exception Handling) Exception Handling快速检阅 对Exception Handling的支持 7.3 执行期类型识别(Runtime Type Identification,RTTI) Type-Safe Downcast(保证安全的向下转型操作) Type-Safe Dynamic Cast(保证安全的动态转型) References并不是Pointers Typeid运算符 7.4 效率有了,弹性呢? 动态共享函数库(Dynamic Shared Libraries) 共享内存(Shared Memory)
第0章 导读(译者的话) 第1章 关于对象(Object Lessons) 加上封装后的布局成本(Layout Costs for Adding Encapsulation) 1.1 C++模式模式(The C++ Object Model) 简单对象模型(A Simple Object Model) 表格驱动对象模型(A Table-driven Object Model) C++对象模型(Th e C++ Object Model) 对象模型如何影响程序(How the Object Model Effects Programs) 1.2 关键词所带来的差异(A Keyword Distinction) 关键词的困扰 策略性正确的struct(The Politically Correct Struct) 1.3 对象的差异(An Object Distinction) 指针的类型(The Type of a Pointer) 加上多态之后(Adding Polymorphism) 第2章 构造函数语意学(The Semantics of constructors) 2.1 Default Constructor的建构操作 “带有Default Constructor”的Member Class Object “带有Default Constructor”的Base Class “带有一个Virual Function”的Class “带有一个virual Base class”的Class 总结 2.2 Copy Constructor的建构操作 Default Memberwise Initialization Bitwise Copy Semantics(位逐次拷贝) 不要Bitwise Copy Semantics! 重新设定的指针Virtual Table 处理Virtual Base Class Subobject 2.3 程序转换语意学(Program Transformation Semantics) 明确的初始化操作(Explicit Initialization) 参数的初始化(Argument Initialization) 返回值的初始化(Return Value Initialization) 在使用者层面做优化(Optimization at the user Level) 在编译器层面做优化(Optimization at the Compiler Level) Copy Constructor:要还是不要? 摘要 2.4 成员们的初始化队伍(Member Initialization List) 第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data Member 3.4 “继承”与Data Member 只要继承不要多态(Inheritance without Polymorphism) 加上多态(Adding Polymorphism) 多重继承(Multiple Inheritance) 虚拟继承(Virtual Inheritance) 3.5 对象成员的效率(Object Member Efficiency) 3.6 指向Data Members的指针(Pointer to Data Members) “指向Members的指针”的效率问题 第4章 Function语意学(The Semantics of Function) 4.1 Member的各种调用方式 Nonstatic Member Functions(非静态成员函数) Virtual Member Functions(虚拟成员函数) Static Member Functions(静态成员函数) 4.2 Virtual Member Functions(虚拟成员函数) 多重继承下的Virtual Functions 虚拟继承下的Virtual Functions 4.3 函数的效能 4.4 指向Member Functions的指针(Pointer-to-Member Functions) 支持“指向Virtual Member Functions”之指针 在多重继承之下,指向Member Functions的指针 “指向Member Functions之指针”的效率 4.5 Inline Functions 形式对数(Formal Arguments) 局部变量(Local Variables) 第5章 构造、解构、拷贝 语意学(Semantics of Construction,Destruction,and Copy) 纯虚拟函数的存在(Presence of a Pure Virtual Function) 虚拟规格的存在(Presence of a Virtual Specification) 虚拟规格中const的存在 重新考虑class的声明 5.1 无继承情况下的对象构造 抽象数据类型(Abstract Data Type) 为继承做准备 5.2 继承体系下的对象构造 虚拟继承(Virtual Inheritance) 初始化语意学(The Semantics of the vptr Initialization) 5.3 对象复制语意学(Object Copy Semantics) 5.4 对象的功能(Object Efficiency) 5.5 解构语意学(Semantics of Destruction) 第6章 执行期语意学(Runting Semantics) 6.1 对象的构造和解构(Object Construction and Destruction) 全局对象(Global Objects) 局部静态对象(Local Static Objects) 对象数组(Array of Objects) Default Constructors和数组 6.2 new和delete运算符 针对数组的new语意 Placement Operator new的语意 6.3 临时性对象(Temporary Objects) 临时性对象的迷思(神话、传说) 第7章 站在对象模型的类端(On the Cusp of the Object Model) 7.1 Template Template的“具现”行为(Template Instantiation) Template的错误报告(Error Reporting within a Template) Template中的名称决议方式(Name Resolution within a Template) Member Function的具现行为(Member Function Instantiation) 7.2 异常处理(Exception Handling) Exception Handling快速检阅 对Exception Handling的支持 7.3 执行期类型识别(Runtime Type Identification,RTTI) Type-Safe Downcast(保证安全的向下转型操作) Type-Safe Dynamic Cast(保证安全的动态转型) References并不是Pointers Typeid运算符 7.4 效率有了,弹性呢? 动态共享函数库(Dynamic Shared Libraries) 共享内存(Shared Memory)
目 录 译者序 前言 第1章 对象的演化 1.1基本概念 1.1.1对象:特性十行为 1.1.2继承:类型关系 1.1.3多态性 1.1.4操作概念:OOP程序像什么 1.2为什么C++会成功 1.2.1较好的C 1.2.2采用渐进的学习方式 1.2.3运行效率 1.2.4系统更容易表达和理解 1.2.5“库”使你事半功倍 1.2.6错误处理 1.2.7大程序设计 1.3方法学介绍 1.3.1复杂性 1.3.2内部原则 1.3.3外部原则 1.3.4对象设计的五个阶段 1.3.5方法承诺什么 1.3.6方法应当提供什么 1.4起草:最小的方法 1.4.1前提 1.4.2高概念 1.4.3论述(treatment) 1.4.4结构化 1.4.5开发 1.4.6重写 1.4.7逻辑 1.5其他方法 1.5.1Booch 1.5.2责任驱动的设计(RDD) 1.5.3对象建模技术(OMT) 1.6为向OOP转变而采取的策略 1.6.1逐步进入OOP 1.6.2管理障碍 1.7小结 第2章 数据抽象 2.1声明与定义 2.2一个袖珍C库 2.3放在一起:项目创建工具 2.4什么是非正常 2.5基本对象 2.6什么是对象 2.7抽象数据类型 2.8对象细节 2.9头文件形式 2.10嵌套结构 2.11小结 2.12练习 第3章 隐藏实现 3.1设置限制 3.2C++的存取控制 3.3友元 3.3.1嵌套友元 3.3.2它是纯的吗 3.4对象布局 3.5类 3.5.1用存取控制来修改stash 3.5.2用存取控制来修改stack 3.6句柄类(handleclasses) 3.6.1可见的实现部分 3.6.2减少重复编译 3.7小结 3.8练习 第4章 初始化与清除 4.1用构造函数确保初始化 4.2用析构函数确保清除 4.3清除定义块 4.3.1for循环 4.3.2空间分配 4.4含有构造函数和析构函数的stash 4.5含有构造函数和析构函数的stack 4.6集合初始化 4.7缺省构造函数 4.8小结 4.9练习 第5章 函数重载与缺省参数 5.1范围分解 5.1.1用返回值重载 5.1.2安全类型连接 5.2重载的例子 5.3缺省参数 5.4小结 5.5练习 第6章 输入输出流介绍 6.1为什么要用输入输出流 6.2解决输入输出流问题 6.2.1预先了解操作符重载 6.2.2插入符与提取符 6.2.3通常用法 6.2.4面向行的输入 6.3文件输入输出流 6.4输入输出流缓冲 6.5在输入输出流中查找 6.6strstreams 6.6.1为用户分配的存储 6.6.2自动存储分配 6.7输出流格式化 6.7.1内部格式化数据 6.7.2例子 6.8格式化操纵算子 6.9建立操纵算子 6.10输入输出流实例 6.10.1代码生成 6.10.2一个简单的数据记录 6.11小结 6.12练习 第7章 常量 7.1值替代 7.1.1头文件里的const 7.1.2const的安全性 7.1.3集合 7.1.4与C语言的区别 7.2指针 7.2.1指向const的指针 7.2.2const指针 7.2.3赋值和类型检查 7.3函数参数和返回值 7.3.1传递const值 7.3.2返回const值 7.3.3传递和返回地址 7.4类 7.4.1类里的const和enum 7.4.2编译期间类里的常量 7.4.3const对象和成员函数 7.4.4只读存储能力 7.5可变的(volatile) 7.6小结 7.7练习 第8章 内联函数 8.1预处理器的缺陷 8.2内联函数 8.2.1类内部的内联函数 8.2.2存取函数 8.3内联函数和编译器 8.3.1局限性 8.3.2赋值顺序 8.3.3在构造函数和析构函数里隐藏行为 8.4减少混乱 8.5预处理器的特点 8.6改进的错误检查 8.7小结 8.8练习 第9章 命名控制 9.1来自C语言中的静态成员 9.1.1函数内部的静态变量 9.1.2控制连接 9.1.3其他的存储类型指定符 9.2名字空间 9.2.1产生一个名字空间 9.2.2使用名字空间 9.3C++中的静态成员 9.3.1定义静态数据成员的存储 9.3.2嵌套类和局部类 9.3.3静态成员函数 9.4静态初始化的依赖因素 9.5转换连接指定 9.6小结 9.7练习 第10章 引用和拷贝构造函数 10.1C++中的指针 10.2C+十中的引用 10.2.1函数中的引用 10.2.2参数传递准则 10.3拷贝构造函数 10.3.1传值方式传递和返回 10.3.2拷贝构造函数 10.3.3缺省拷贝构造函数 10.3.4拷贝构造函数方法的选择 10.4指向成员的指针(简称成员指针) 10.5小结 10.6练习 第11章 运算符重载 1

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值