C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示

C++ 学习笔记(19)new/delete表达式、定位new、typeid、dynamic_cast、type_info、枚举类型、成员函数指针、union、位域、volatile限定符、链接指示

参考书籍:《C++ Primer 5th》
C++ 学习笔记(12)动态内存、智能指针、new和delete、动态数组、allocator


19.1 控制内存分配

19.1.1 重载newdelete

  • new表达式步骤:
    1. 调用operator newoperator new[]的标准库函数,分配了一块足够大的未命名内存空间。
    2. 运行相应的构造函数,并传入初始值。
    3. 返回一个指向该对象的指针。
  • delete表达式步骤:
    1. 运行相应的析构函数。
    2. 调用operator deleteoperator delete[]的标准库函数,释放内存空间。

在这里插入图片描述

  • 重载带有noexcept说明符运算符时,同样需要加上noexcept。

  • 自定义版本必须位于全局作用域或类作用域。

    • 作为类的成员时,是隐式静态的,因为new是在对象创建前使用,delete是在对象销毁以后使用,且都不能操纵类的任何数据成员。
  • new的返回类型必须是void *,第一个形参类型必须是size_t,且不能有默认实参(分配对象所需字节数)。可以提供额外形参,除了一个函数用户不能重载:

    • void *operator new(size_t, void*);
  • delete的返回类型必须是void,第一个形参必须是void*。用指向待释放内存的指针,来初始化void*形参。

  • nothrow_t:空结构体,用来表明函数是否抛出异常。

  • 实际上并没有重载new表达式delete表达式,只是改变内存分配方式。因为无法自定义new表达式或delete表达式的行为。

  • malloc函数:

    • 参数size_t:分配字节数。
    • 返回void*:分配了空间的指针。失败返回0。
  • free函数:

    • 参数void*:是malloc返回的指针的副本,将其内存释放。

19.1.2 定位new表达式

在这里插入图片描述

  • place_address 必须是一个指针。
  • 定位new调用operator new(size_t, void *):函数不分配内存,只是返回指针实参;再由new表达式负责初始化对象。
  • 不同于allocator的construct,定位new表达式的指针不需要指向动态内存。
  • 和allocate的destroy类似,调用析构函数可以清除对象,但不会释放对象所在空间。

19.2 运行时类型识别

  • 运行时类型识别(run-time type identification, RTTI):
    • typeid运算符,用于返回表达式的类型。
    • dynamic_cast运算符,用于基于类的指针或引用安全地转换成派生类的指针或引用。
  • 使用RRTI时,最好定义虚函数而非直接管理类型。

19.2.1 dynamic_cast运算符

  • 有三种形式(type为类类型,通常含有虚函数):
    • dynamic_cast<type*>(e) :e必须为有效的指针。
    • dynamic_cast<type&>(e) :e必须为一个左值。
    • dynamic_cast<type&&>(e) :e不能是左值。
  • e的类型必须符合以下三个条件中任意一个:
    • 是目标type的公有派生类。
    • 是目标type的公有基类。
    • 是目标type的类型。
  • 转换失败时:
    • 目标是指针类型,返回0。
    • 目标是引用类型,抛出bad_cast异常。
// 指针类型
// bp指针指向基类Base(至少含有一个虚函数),Derived是Base的公有派生类。
if (Derived *dp = dynamic_cast<Derived*>(bp))
{
	// 转换成功。dp指向Derived对象。
} 
else 
{
	// 转换失败。使用dp指向的Base对象
}

// ----------------------------------------------------------------------------- //

// 引用类型
// 因为不存在空引用,对于引用失败,应该用捕获异常的方法。
void f(const Base &b)
{
	try {
		// 使用b引用的Derived对象。
		const Derived *d = dynamic_cast<Derived&>(b);
	} catch (bad_cast) {
		// 转换失败处理。
	}
}

19.2.2 typeid运算符

  • typeid(e):e是任意表达式或类名。返回结果是type_info类型(或派生类)常量对象的引用。
    • e表达式如果是引用,则typeid返回该对象的类型。
    • e表达式如果是数组或函数,不会转成指针,而是原来的类型。
  • typeid作用指针时,返回的结果是该指针的静态编译时类型。
    • 只有当类型含有虚函数时,编译器才会对表达式求值。
    • 不含虚函数时,返回表达式的静态类型。
class A {};		// 非多态
class B : public A {};

class A2 { virtual void foo() {}; };	// 多态
class B2 : public A2 {};

void main()
{
	B *b = new B;
	A *a = b;
	if (typeid(*a) == typeid(*b)) { cout << "test one ok!" << endl; }	// 未执行
	if (typeid(*a) == typeid(B)) { cout << "test two ok!" << endl; }	// 未执行
	if (typeid(a) == typeid(B)) { cout << "test three ok!" << endl; }	// 未执行

	B2 *b2 = new B2;
	A2 *a2 = b2;
	if (typeid(*a2) == typeid(*b2)) { cout << "test four ok!" << endl; }	// 执行,类型相同
	if (typeid(*a2) == typeid(B2)) { cout << "test five ok!" << endl; }	// 执行,类型相同
	if (typeid(a2) == typeid(B2)) { cout << "test six ok!" << endl; }	// 未执行,比较的是:A2*和B2类
}

19.2.3 使用RTTI

class Base
{
	friend bool operator==(const Base&, const Base&);	// 整个类(包括继承)的相等运算符
protected:
	virtual bool equal(const Base&) const;		// 每个类自己定义的euqal函数
};

class Derived : public Base
{
protected:
	bool equal(const Base&) const;
};

// ==运算符的比较
bool operator==(const Base &lhs, const Base &rhs)
{
	return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);	// 先判断类型是否相同,再调用自定义的相等判断
}

// 基类自定义判断
bool Base::equal(const Base &rhs) const
{
	// ...自定义的相等判断
}

// 继承类自定义判断
bool Derived::equal(const Base &rhs) const
{
	auto r = dynamic_cast<const Derived&>(rhs);	// 继承类转基类,不会抛出异常
	if (!Base::equal(rhs)) return false;	// 可调用基类判断,先处理基类的成员判断
	// ...其他自定义的相等判断
	return true;
}

19.2.4 type_info

在这里插入图片描述

  • type_info类没有默认构造函数,也没有拷贝移动等操作。因此无法定义或拷贝或赋值。唯一创建方法是使用typeid运算符。
  • 在不同编译器上,type_info类有所区别。

19.3 枚举类型

  • 枚举属于字面值常量类型。
  • 限定作用域的枚举类型:包含关键字class或struct,枚举类型名,枚举成员列表。
  • 不限定作用域的枚举类型:省略关键字class或struct,枚举类型的名字是可选的。
// 不限定作用域的枚举类型
enum color { red, green };
enum stoplight { red, green };	// 错误。重复定义枚举成员
enum class peppers { red, green };	// 正确。限定作用域的枚举类型,隐藏了枚举成员

color eyes = green;		// 正确,不限定作用域的值
peppers p = green;		// 错误,color枚举类型不能赋给peppers
peppers p2 = peppers::red;	// 正确,指定作用域

int i = color::red;		// 正确,不限定作用域的枚举可隐式转换成int
int j = peppers::red;	// 错误,限定作用域的不会隐式转换
int k = (int)peppers::red;	// 可以强制转换
  • 默认情况下:
    • 限定作用域的enum成员类型是int。
    • 不限定作用域的enum成员不存在默认类型。所以在前置声明时必须指定成员大小。
// 不限定作用域的枚举类型,必须指定成员类型
enum intValues : usigened long long;
// 限定作用域,默认int
enum class open_modes;

19.4 类成员指针

  • 成员指针:可以指向类的非静态成员的指针。不同于普通指针,成员指针必须包含成员所属的类,即在*前添加类名calssname::

19.4.2 成员函数指针

class Screen
{
public:
	Screen & up();
	Screen& down();

	// Action是别名,Screen的成员指针,可指向Screen成员的一类函数。
	using Action = Screen & (Screen::*)();
	enum Directions { UP, DOWN };	// 用来控制输入的枚举类型
	Screen& move(Directions);		// 提供给外部调用的接口
private:
	static Action Menu[];		// 函数列表,通过索引调用对应函数
};

Screen::Action Screen::Menu[] = { &Screen::up, &Screen::down };		// 0:up   1:down

Screen& Screen::move(Directions cm) { return (this->*Menu[cm])(); }	// 通过函数表直接调用对应函数

void main
{
	Screen s;
	s.move(Screen::DOWN);	// 等价 s.down();
}

19.4.3 将成员函数用作可调用对象

  • mem_fun函数:可以根据成员指针的类型推断可调用对象的类型。
auto fp = &string::empty;	// 一般获取的成员函数都是指针类型,不能作为对象
find_if(sv.begin(), sv.end(), fp);	// 错误。fp是成员指针,需要调用 ->*

// 使用function转换成对象
function<bool (const string&)> fcn = &string::empty;
find_if(sv.begin(), sv.end(), fcn);	// 正确。fcn是函数对象

// 使用mem_fn
find_if(sv.begin(), sv.end(), mem_fn(&string::empty));	// 正确,直接生成可调用对象
auto f = mem_fn(&string::empty);	// f可接受对象或对象的指针
f(*svec.begin());		// 正确,传入对象,f调用.*
f(&svec[0]);			// 正确,传入指针,f调用->*

// 使用bind,必须将函数指向对象的隐式形参转换成显式的。
auto fb = bind(&string::empty, _1);
fb(*svec.begin());		// 正确
fb(&svec[0]);			// 正确


19.6 union:一种节省空间的类

  • union可以有多个数据成员,但在任意时刻只有一个数据成员有值(其他成员变成未定义状态)。分配union对象的存储空间至少能存储其最大的数据成员。

  • 不能含有引用类型的成员。

  • 默认情况下,成员都是公有的。

  • 不能继承其他类或作为基类。

  • 匿名union:未命名的union。定义了匿名union,编译器就自动为该union创建一个未命名对象。

    • 定义所在作用域内,该union的成员都是可以直接访问的。
    • 不能包含proteced或private成员,也不能定义成员函数。
union
{
	char cval;
	int ival;
};
cval = 'c';		// union对象的成员,此时ival是未定义状态
ival = 2;		// 赋值了另一个成员,此时cval是未定义状态
  • union只包含内置类型的成员时,编译器依次合成默认构造函数或拷贝控制成员。
  • 如果含有类类型成员,且需要修改类类型成员时,就必须运行构造或析构函数。如果定义了自定义默认构造函数或拷贝成员,合成版本的会被声明为删除的。

19.7 局部类

  • 如果局部类定义在某个函数内部,局部类无法使用函数内部的局部变量。

19.8 固有的不可移植的特性

  • 不可移植(nonportable):因机器而异,通常需要重新编写。

19.8.1 位域

  • 类的非静态数据成员可以定义成位域(bit-field)。
  • 位域在内存中的布局与机器有关。
  • 指针无法指向类的位域。
  • 最好将位域设为无符号类型。
struct S
{
	unsigned int b : 3;	// 三位无符号位域,值的范围在[0,7]
};
void main()
{
	S s = { 7 };
	cout << s.b << endl; // 输出:7
	++s.b; // 无符号上溢(保证回卷)
	cout << s.b << endl; // 输出:0
}

19.8.2 volatile限定符

  • 确切含义与机器有关,通常用于直接处理硬件的数据,值不直接由程序控制,volatile告诉编译器不应对对象做优化。
  • 不能使用合成拷贝移动操作volatile对象。
volatile int v;
int *vloatile vip;		// vip:volatile指针,指向int
vloatile int *ivp;		// ivp:是一个指针,指向vloatile int
vloatile int *vloatile vivp;		// vivp:volatile指针,指向vloatile int

int *ip = &v;		// 错误
ivp = &v;		// 正确
vivp = &v;		// 正确

19.8.3 链接指示:extern “C”

  • 链接指示(linkage directive):指出任意非C++函数所用的语言。
// 单语句链接指示:声明使用C语言版本的strlen函数
extern "C" size_t strlen(const char *);

// 复合语句链接指示
extern "C"
{
	int strcmp(const char*, const char*);
	char *strcat(char*, const char*);
}

extern "C"
{
	#include <string.h>	// 操作C风格字符串的C函数
}

void (*pf1)(int);				// pf1指向一个C++函数
extern "C" void (*pf2)(int);		// pf2指向一个C函数
pf1 = pf2;			// 错误,C和C++是不同类型
  • 链接指示不仅对函数有效,而且对返回类型或形参类型的函数指针也有效。
// 在同时编译C和C++时,做判断以正确编译
#ifdef __cplusplus
extern "C"
#endif
  • C语言不支持重载,所以不可以链接指示相同函数名。
extern "C" void print(const char *);
extern "C" void print(int);			// 错误,相同函数名

extern "C" double calc(double);
extern int clac(int);				// 正确,C++与C函数的重载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值