【C++】《C++ Primer 》第十九章
第十九章 特殊工具与技术
一、控制内存分配
1. 重载new和delete
new
表达式的工作机理:
string *sp = new string("a value"); //分配并初始化一个string对象
string *arr = new string[10]; // 分配10个默认初始化的string对象
上述代码实际执行了三步操作:
new
表达式调用一个名为operator new
(或operator new []
)的标准库函数,它分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象(或对象的数组)。- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值。
- 对象被分配了空间并构造完成,返回一个指向该对象的指针。
delete
表达式的工作机理:
delete sp; // 销毁*sp,然后释放sp指向的内存空间
delete [] arr; // 销毁数组中的元素,然后释放对应的内存空间
- 上述代码实际执行了两步操作:
- 对
sp
所指向的对象或者arr
所指的数组中的元素执行对应的析构函数。 - 编译器调用名为
operator delete
(或operator delete[]
)的标准库函数释放内存空间。
- 对
- 当自定义了全局的
operator new
函数和operator delete
函数后,我们就担负起了控制动态内存分配的职责。这两个函数必须是正确的。因为它们是程序整个处理过程中至关重要的一部分。 - 标准库定义了
operator new
函数和operator delete
函数的8个重载版本:
// 这些版本可能抛出异常
void *operator new(size_t); // 分配一个对象
void *operator new[](size_t); // 分配一个数组
void *operator delete(void*) noexcept; // 释放一个对象
void *operator delete[](void*) noexcept; // 释放一个数组
// 这些版本承诺不会抛出异常
void *operator new(size_t, nothrow_t&) noexcept;
void *operator new[](size_t, nothrow_t&) noexcept;
void *operator delete(void*, nothrow_t&) noexcept;
void *operator delete[](void*, nothrow_t&) noexcept;
- 应用程序可以自定义上面函数版本中的任意一个,前提是自定义的版本必须位于全局作用域或者类作用域中。
- 注意: 提供新的
operator new
函数和operator delete
函数的目的在于改变内存分配的方式,但是不管怎样,都不能改变new
运算符和delete
运算符的基本含义。 - 使用从C语言继承的函数
malloc
和free
函数能实现以某种方式执行分配内存和释放内存的操作:
#include <cstdlib>
void *operator new(size_t size) {
if(void *mem = malloc(size))
return mme;
else
throw bad_alloc();
}
void operator delete(void *mem) noexcept {
free(mem);
}
2. 定位new表达式
- 应该使用new的定位
new(placement new)
形式传递一个地址,定位new
的形式如下:
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size] {braced initializer list}
// place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。
- 当只传入一个指针类型的实参时,定位
new
表达式构造对象但是不分配内存。 - 调用析构函数会销毁对象,但是不会释放内存。
string *sp = new string("a value"); // 分配并初始化一个string对象
sp->~string();
二、运行时类型识别
- 运行时类型识别
(run-time type identification, RTTI)
的功能由两个运算符实现:typeid
运算符, 用于返回表达式的类型。dynamic_cast
运算符,用于将基类的指针或引用安全地转换曾派生类的指针或引用。
- 使用
RTTI
必须要加倍小心。在可能的情况下,最好定义虚函数而非直接接管类型管理的重任。
1. dynamic_cast运算符
- dynamic_cast运算符的使用形式如下:
dynamic_cast<type*>(e) // e必须是一个有效的指针
dynamic_cast<type&>(e) // e必须是一个左值
dynamic_cast<type&&>(e) // e不能是左值
// 以上,type类型必须时一个类类型,并且通常情况下该类型应该含有虚函数。
// e的类型必须符合三个条件中的任意一个,它们是:
// 1. e的类型是目标type的公有派生类;
// 2. e的类型是目标type的共有基类;
// 3. e的类型就是目标type的类型;
// 指针类型的dynamic_cast
// 假设Base类至少含有一个虚函数,Derived是Base的共有派生类。
if (Derived *dp = dynamic_cast<Derived*>(bp)) {
// 使用dp指向的Derived对象
} else { // bp指向一个Base对象
// 使用dp指向的Base对象
}
// 引用类型的dynamic_cast
void f(const Base &b) {
try {
const Derived &d = dynamic_cast<const Derived&>(b);
// 使用b引用的Derived对象
} catch (bad_cast) {
// 处理类型转换失败的情况
}
}
- 可以对一个空指针执行
dynamic_cast
,结果是所需类型的空指针。
2. typeid运算符
typeid运算符(typeid operator)
,它允许程序向表达式提问:你的对象是什么类型?typeid
表达式的形式是typeid(e)
,其中e
可以是任意表达式或类型的名字,它操作的结果是一个常量对象的引用。它可以作用于任意类型的表达式。- 通常情况下,使用typeid比较两条表达式的类型是否相同,或者比较一条表达式的类型是否与指定类型相同:
Derived *dp = new Derived;
Base *bp = dp;
if (typeid(*bp) == typeid(*dp)) {
// bp和dp指向同一类型的对象
}
if (typeid(*bp) == typeid(Derived)) {
// bp实际指向Derived对象
}
- 当typeid作用于指针时(而非指针所指向的对象),返回的结果是该指针的静态编译时类型。
// 下面的检查永远是失败的:bp的类型是指向Base的指针
if (typeid(bp) == typeid(Derived)) {
// 永远不会执行
}
3. 使用RTTI
- 用途:为具有继承关系的类实现相等运算符时。对于两个对象来说,如果它们的类型相同并且对应的数据成员取值相同,则说这两个对象是相等的。
// 类的层次关系
class Base {
friend bool operator==(const Base&, const Base&);
public:
// Base的接口成员
protected:
virtual bool equal(const Base&) const;
// Base的数据成员和其他用于实现的成员
};
class Derived: public Base {
public:
// Derived的其他接口成员
protected:
bool equal(const Base&) const;
// Derived的数据成员和其他用于实现的成员
};
// 类型敏感的相等运算符
bool operator==(const Base &lhs, const Base &rhs) {
// 如果typeid不相同,返回false;否则虚调用equal
return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}
// 虚equal函数
bool Derived::equal(const Base &rhs) const {
auto r = dynamic_cast<const Derived&>(rhs);
// 执行比较两个Derived对象的操作并返回结果
}
// 基类equal函数
bool Base::equal(const Base &rhs) const {
// 执行比较Base对象的操作
}
4. type_info类
三、枚举类型
- 枚举类型
(enumeration)
使我们可以将一组整型常量组织在一起。枚举属于字面值常量类型。 - 限定作用域的枚举类型(scoped enumeration):首先是关键字
enum class(或enum struct)
,随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员列表,最后是一个分号。
enum class open_modes {input, output, append};
- 不限定作用域的枚举类型
(unscoped enumeration)
:省略关键字class(或struct)
,枚举类型的名字是可选的。
enum color {red, yellow, green};
enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10};
四、类成员指针
成员指针:指可以指向类的非静态成员的指针。
1. 数据成员指针
- 和其他指针一样,在声明成员指针时也使用*来表示当前声明的名字是一个指针。与普通指针不同的时,成员指针还必须包含成员所属的类。
// pdata可以指向一个常量(非常量)Screen对象的string成员
const string Screen::*pdata;
// C++11
auto pdata = &Screen::contents;
- 当我们初始化一个成员指针或为成员指针赋值时,该指针没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时才提供对象的信息。
Screen myScreen, *pScreen = &myScreen;
auto s = myScreen.*pdata;
s = pScreen->*pdata;
2. 成员函数指针
- 因为函数调用运算符的优先级较高,所以在声明指向成员函数的指针并使用这些的指针进行函数调用时,括号必不可少:
(C::*p)(parms)
和(obj.*p)(args)
。
3. 将成员函数用作可调用对象
五、嵌套类
- 一个类可以定义在另一个类的内部,前者称为嵌套类(nested class)或嵌套类型(nested type)。嵌套类常用于定义作为实现部分的类。
- 嵌套类是一个独立的类,与外层类基本没有什么关系。特别是,外层类的对象和嵌套类的对象是相互独立的。
- 嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见。
六、union:一种节省空间的类
联合(union)
是一种特殊的类。一个union
可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。它不能含有引用类型的成员和虚函数。
// Token类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种
union Token {
// 默认情况下成员是共有的
char cval;
int ival;
double dval;
};
匿名union(anonymous union)
是一个未命名的union
,并且在右花括号和分号之间没有任何声明。
union {
char cval;
int ival;
double dval;
};
// 可以直接访问它的成员
cal = 'c';
ival = 42;
- 注意:
匿名union
不能包含受保护的成员或私有成员,也不能定义成员函数。
七、局部类
局部类(local class)
:可以定义在某个函数的内部的类。它的类型只在定义它的作用域内可见。和嵌套类不同,局部类的成员受到严格限制。- 局部类的所有成员(包括函数在内)都必须完整定义在类的内部。因此,局部类的作用与嵌套类相比相差很远。
- 局部类不能使用函数作用域中的变量。
int a, val;
void foo(int val) {
static inti si;
enum loc { a = 1024, b};
// Bar是foo的局部类
struct Bar {
Loc locVal; // 正确:使用一个局部类型名
int barVal;
void fooBar(Loc l = a) { // 正确:默认实参是Loc::a
barVal = val; // 错误:val是foo的局部变量
barVal == ::val; // 正确:使用一个全局对象
barVal = si; // 正确:使用一个静态局部对象
locVal = b; // 正确:使用一个枚举成员
}
};
}
八、固有的不可移植的特性
所谓不可移植的特性是指因机器而异的特性,当将含有不可移植特性的程序从一台机器转移到另一台机器上时,通常需要重新编写该程序。
1. 位域
- 类可以将其(非静态)数据成员定义成位域(bit-field),在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
- 位域在内存中的布局是与机器相关的。
- 位域的类型必须是整型或枚举类型。因为带符号位域的行为是由具体实现确定的,通常情况下我们使用无符号类型保存一个位域。
typedef unsigned int Bit;
class File {
Bit mode: 2;
Bit modified: 1;
Bit prot_owner: 3;
Bit prot_group: 3;
Bit prot_world: 3;
public:
enum modes {READ = 01, WRITE = 02, EXECUTE = 03};
File &open(modes);
void close();
void write();
bool isRead() const;
void setWrite();
}
// 使用位域
void File::write() {
modified = 1;
// ...
}
void File::close() {
if( modified)
// ...保存内容
}
File &File::open(File::modes m) {
mode |= READ; // 按默认方式设置READ
// 其他处理
if(m & WRITE) // 如果打开了READ和WRITE
// 按照读/写方式打开文件
return *this;
}
2. volatile限定符
- 当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为
volatile
。关键字volatile
告诉编译器不应对这样的对象进行优化。 const
和volatile
的一个重要区别是不能使用合成的拷贝/移动构造函数及赋值运算符初始化volatile
对象或者从volatile
对象赋值。
3. 链接指示:extern "C"
C++
使用链接指示(linkage directive)
指出任意非C++
函数所用的语言。- 要想把
C++
代码和其他语言(包括C
语言)编写的代码放在一起使用,要求我们必须有权访问该语言的编译器,并且这个编译器与当前的C++
编译器是兼容的。 C++
从C语言继承的标准库函数可以定义为C
函数,但并非必须:决定使用C
还是C++
实现的C
标准库,是每个C++
实现的事情。- 有时需要在C和C++中编译同一个源文件,为了实现这一目的,在编译C++版本的程序时预处理器定义
__cplusplus
。
#ifdef __cplusplus
extern "C"
#endif
int strcmp(const char*, const char*);
【C++】《C++ Primer 》第十九章的更多相关文章
- C++Primer 第十九章
//1.控制内存分配: //A:某些应用程序对内存分配有特殊的需求,因此我们无法将标准内存管理机制直接应用于这些程序.它们常常需要自定义内存分配的细节,比如使用关键字new将对象放置在特定的内存空间中 ...
- Python之路【第十九章】:Django进阶
Django路由规则 1.基于正则的URL 在templates目录下创建index.html.detail.html文件 <!DOCTYPE html> <html lang=&q ...
- 第十九章——使用资源调控器管理资源(1)——使用SQLServer Management Studio 配置资源调控器
原文:第十九章--使用资源调控器管理资源(1)--使用SQLServer Management Studio 配置资源调控器 本系列包含: 1. 使用SQLServer Management Stud ...
- 第十九章——使用资源调控器管理资源(2)——使用T-SQL配置资源调控器
原文:第十九章--使用资源调控器管理资源(2)--使用T-SQL配置资源调控器 前言: 在前一章已经演示了如何使用SSMS来配置资源调控器.但是作为DBA,总有需要写脚本的时候,因为它可以重用及扩展. ...
- 第十九章 Django的ORM映射机制
第十九章 Django的ORM映射机制 第一课 Django获取多个数据以及文件上传 1.获取多选的结果(checkbox,select/option)时: req.POST.getlist('fav ...
- Gradle 1.12用户指南翻译——第四十九章. Build Dashboard 插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- Gradle 1.12翻译——第十九章. Gradle 守护进程
有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com ...
- Gradle 1.12用户指南翻译——第二十九章. Checkstyle 插件
其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...
- Gradle 1.12用户指南翻译——第三十九章. IDEA 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
随机推荐
- JavaScript异步编程的四种方法
1.回调函数 f1(f2); 回调函数是异步编程的基本方法.其优点是易编写.易理解和易部署:缺点是不利于代码的阅读和维护,各个部分之间高度耦合 (Coupling),流程比较混乱,而且每个任务只能指定 ...
- 【游记】CSp2020
同步发表于洛谷博客 初赛 Day -2 做了个模拟(非洛谷),只有一丁点分,显然过不了 (盗张 i am ak f 的图) Day 0 颓,颓,颓,又做了一套模拟,坚定了退役的信心. Day 1 人好 ...
- day013|python之模块02&目录01
1 from...import 1.1 概念 1.1.1 首次导入模块会发生的事 会触发模块的运行,产生一个模块的名称空间 将运行模块文件过程中产生的名字丢到模块额名称空间 在当前名称空间产生一个名字 ...
- 使用HBase Shell命令
使用HBase Shell命令 或 使用HBase Java API完成: 列出HBase所有的表的相关信息,例如表名: 在终端打印出指定的表的所有记录数据: 向已经创建好的表添加和删除指定的列族或列 ...
- 移动端H5开发中的常见问题处理
1.问题之合成海报: 功能技术:http://html2canvas.hertzen.com 问题描述:合成模糊.合成区域内容错位,合成不完整,合成边缘白条等. 解决方案:如有页面布局正常合成错位的, ...
- 百度网站统计和CNZZ网站统计对比
一,前言 百度统计和cnzz统计是目前市面上比较流行的两种web统计工具,接下来将对两个统计工具做初步的体验测评 百度网站统计相关介绍:全球最大的中文网站流量分析平台,帮助企业收集网站访问数据,提供流 ...
- C# 海量数据瞬间插入到数据库的方法
C# 海量数据瞬间插入到数据库的方法 当我们在数据库中进行大量的数据追加时,是不是经常因为数据量过大而苦恼呢?而所谓的海量数据,一般也是上万级的数据,比如我们要添加一百万条数据,应该如何提高它的效率呢 ...
- tp5使用PHPExcel(下载引入/composer与模板/生成方式搭配使用)
PHPExcel使用 一:引入 tp5.0,tp5.1: 1:composer方式 a:根目录下执行:composer require phpoffice/phpexcel b:引入(可new \PH ...
- hive行存储与列存储
首先判断hive表是行存储还是列存储 判断方法: 1.使用hiveSQL"show create table table_name",这种方式,可以查看建表时候指定的那种方式; 2 ...
- Linux 安装 MySQL 8 数据库(图文详细教程)
本教程手把手教你如何在 Linux 安装 MySQL 数据库,以 CentOS 7为例. 1. 下载并安装 MySQL 官方的 Yum Repository wget -i -c https://re ...