C++Primer笔记-特殊工具与技术

前言

该系列是《C++Primer第五版》的笔记,包含本人认为值得记录和整理的主要的知识点,并不是全部内容,也不是具体的内容。
该系列文章的作用应该是作为复习或预习的参考,有哪些知识点忘记或想学,可以大致浏览下该文章,然后再去书中寻找详细解答。(本系列文章基本是按书本顺序罗列的知识点,便于大家去书中寻找)
所以看该文章前,需要有一定的C++基础,否则阅读起来可能有困难。

本文大致整理了第十九章的知识点,涉及到C++比较晦涩的知识,部分知识个人认为不是重点,但涉及的内容又比较深入,对于入门水平来说不好理解也不好应用。个人认为目前需要掌握的有枚举类、嵌套类、union、局部类、dynamic_cast

链接目录

重载new和delete

new调用operator new

  • 先分配一块原始、未命名的内存空间
  • 然后运行构造函数构造对象
  • 返回指向该对象的指针

delete调用operator delete

  • 先执行所指对象的析构函数
  • 调用operator delete函数释放内存

如果newdelete的对象是类类型,编译器首先在类和基类的作用域内查找,否则在全局作用域中查找匹配的函数。

operator newoperator delete是隐式静态的,他们调用在对象构造之前和对象销毁之后,所以必须是静态的,且不能操作类内的任何数据成员。

标准库版本的newdelete

//可能抛出异常的版本
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;

不允许重载void *operator new(size_t, void*)

delete的重载必须返回void,第一个形参必须是void*
new的重载必须返回void*,第一个形参必须是size_t且该参数不能有默认实参

malloc函数与free函数:定义在头文件cstdlib

  • malloc接受一个size_t参数,返回指向分配空间的指针,返回0表示分配失败
  • free接受一个malloc返回的void*,释放相关内存

定位new表达式

定位new是一种将内存分配和对象构造分开的技术,定位new会在一个已经完成分配的内存地址上构造一个对象。

//定位new的形式,place_address是指针,initializers是初始值列表
new(place_address) type
new(place_address) type(initializers)
new(place_address) type[size]
new(place_address) type[size]{braced initializer list}

显式的析构函数调用:能够销毁对象,但是不会释放内存

string *sp = new string("a value");
sp->~string();

运行时类型识别

运行时类型识别(RTTI)

  • typeid运算符,用于返回表达式的类型
  • dynamic_cast运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用

这两个运算符适用于使用基类指针或引用执行派生类的操作,且该操作不是虚函数,但是此操作含有风险。

dynamic_cast运算符

三种形式:type是一个类类型,通常应该含有虚函数,e是一个指针

  • dynamic_cast<type*>(e):e必须是一个有效的指针
  • dynamic_cast<type&>(e):e必须是左值
  • dynamic_cast<type&&>(e):e不能是左值

e的类型必须符合以下三个条件之一:转成指针失败返回0,转成引用失败抛异常

  • e的类型是type的公有派生类
  • e的类型是type的公有基类
  • e的类型就是type的类型

指针类型的dynamic_castBase类至少含有一个虚函数,DerivedBase的公有派生类。

//bp是一个指向Base的指针
if(Derrived *dp = dynamic_cast<Derived*>(bp)){
	//使用dp指向的Derived对象
}else{//bp指向的对象无法转换成Derived
	//使用bp指向的Base对象
}

在条件部分执行dynamic_cast可以确保类型转换和结果检查在同一条表达式中完成。

引用类型的dynamic_cast

void f(const Base &b){
	try{
		const Derived &d = dynamic_cast<const Derived&>(b);
		//使用b引用的Derived对象
	}catch(bad_cast){
		//处理转换失败的代码
	}
}

typeid运算符

typeid(e)e是任意表达式或类型名,返回一个常量对象的引用。如果表达式是数组或函数时,不会执行向指针的隐式转换,得到的是数组或函数类型而非指针类型。

使用typeid运算符:需要注意,指针需要解引用,否则比较的是指针。

Derived *dp = new Derived;
Base *bp = dp;
if(typeid(*bp) == typeid(*dp)){
	...
}
if(typeid(*bp) == typeid(Derived)){
	...
}

只有当类型含有虚函数时,才会对表达式求值进行运行时检查,如果不含虚函数,则通过静态检查,在编译期间就完成。

type_info类

定义在头文件typeinfo中,创建type_info类的唯一途径是使用typeid运算符

  • t1 == t2:如果type_info对象t1t2表示同一种类型返回true
  • t1 != t2:如果type_info对象t1t2表示不同的类型返回true
  • t.name():返回C风格字符串,表示类型的名字

枚举类型

  • 限定作用域的:enum class name{...}
  • 不限定作用域的:enum name{...},名字是可选的,也可以enum {...}
enum color{red, yellow, green};//不限定作用域
enum stoplight{red, yellow, green};//错误,重复定义
enum class peppers{red, yellow, green};//正确,作用域内隐藏外部名字
color eyes = green;//正确
peppers p = green;//错误
peppers p = peppers::green;//正确

默认情况枚举值从0开始,依次加一,也可以手动指定,如果没有显式提供初始值,则值等于之前枚举成员的值加1

enum class intTypes{
	charTyp = 8, shorTyp = 16, intTyp = 16//枚举值可以重复
};

不限定作用域的枚举类型可以自动转换成整型,限定作用域的则不行,可以通过static_cast转换。

int i = color::red;//正确
int j = peppers::red;//错误

指定enum的类型:限定作用域的默认是int,不限定作用域的未知

enum intValues : unsigned long long{
	charTyp = 255, shortTyp = 65535, longTyp = 4294967295UL;
};

枚举类型的前置声明:

enum int Values : unsigned long long;//不限定作用域的必须指定成员类型
enum class color;//可以使用默认成员类型int

形参匹配与枚举类型:enum是一个单独的类,即使存在某个整型值与枚举成员相等,也不会匹配。

enum Tokens{INLINE = 128};
void f(Tokens);
void f(int);
int main(){
	f(128);//匹配f(int)
	f(INLINE);//匹配f(Tokens)
	return 0;
}

类成员指针

指向类非静态成员的指针,指向静态成员的指针实际上和普通指针没什么区别。

//样例类
class Screen{
public:
	typedef std::string::size_type pos;
	char get_cursor() const {return contents[cursor];}
	char get() const;
	char get(pos ht, pos wd) const;
private:
	std::string contents;
	pos cursor;
	pos height, width;
};

数据成员指针

指针的声明:

//指向Screen类的const string成员的指针(指向string成员也没问题)
const string Screen::*pdata;
pdata = &Screen::contents;//指向contents成员
//等价
auto pdata = &Screen::contents;

通过.*->*来访问成员指针:

Screen myScreen, *pScreen = &myScreen;
//获得contents成员
auto s = myScreen.*pdata;
s = pScreen->*pdata;

获取数据成员指针受限于访问控制规则,如果是private的,则成员指针只能在Screen类内部或友元内部获取。但可以通过函数返回给外部,让外部能够访问内部的数据。

嵌套类

一个类定义在另一个类的内部,嵌套类同样受访问权限控制:

  • public的嵌套类可以不受限的被访问
  • protected的嵌套类只能被外层类、友元和派生类访问
  • private的嵌套类只能被外层类的成员和友元访问

嵌套类可以访问外层类的成员。

声明一个嵌套类:

class TextQuery{
public:
	class QueryResult;//定义可以出现在外部
};

class TextQuery::QueryResult{
	...
};

//嵌套类的构造函数
TextQuery::QueryResult::QueryResult(){
	...
}

嵌套类和外层类实际上是相互独立的。

union:一种节省空间的类

union不能含有引用类型的成员,类类型可以是union的成员,可以指定publicprotectedprivate,默认是public
union可以定义包括构造函数和析构函数在内的成员函数,但不能继承也不能作为基类,所以不能有虚函数。

定义和使用union

union Token{
	char cval;
	int ival;
	double dval;
};

Token first_token = {'a'};//初始化cval
Token last_token;//未初始化
last_token.cval = 'z';//赋值cval,其他数据变成未定义
Token *pt = new Token;//未初始化
pt->ival = 42;//赋值ival,其他数据变成未定义

匿名union

union{
	char cval;
	int ival;
	double dval;
};
//在当前作用域内可以直接访问
cval = 'c';
ival = 42;

局部类

局部类的成员必须完整定义在类的内部,不允许声明静态数据成员。

局部类只能使用外层作用域的类型名、静态变量及枚举成员,普通局部变量不能被局部类使用。

int a, val;
void foo(int val){
	static int si;
	enum Loc{a = 1024, b};
	struct Bar{//默认是public的
		Loc locVal;
		int varVal;
		void fooBar(Loc l = a){
			barVal = val;//错误,val是局部变量
			barVal = ::val;//正确,全局变量可以使用
			barVal = si;//正确,可以使用静态变量
			locVal = b;//正确,可以使用枚举类型
		}
	};
}

固有的不可移植的特性

C++有两种不可移植的特性:

  • 位域
  • volatile

位域

当程序需要向其他程序或硬件设备传递二进制数据时,通常会使用到。

位域的类型必须是整型或枚举类型:声明方式是名字后面跟冒号及一个常量表达式,表达式用于指定成员所占位数。

typedef unsigned int Bit;
class File{
	Bit mode:2;//占2位
	Bit modified:1;//占1位
	Bit prot_owner:3;//占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() cosnt;
	void setWrite();
};

使用位域:

void File::write(){
	modified = 1;
}
void File::close(){
	if(modified)
		//保存内容
}
File &File::open(File::modes m){
	mode |= READ;
	if(m & WRITE)
		//按照读/写方式打开文件
	return *this;
}

volatile限定符

对象的值可能在程序的控制或检测之外被改变,例如在多线程环境下,声明成volatile
关键字volatile告诉编译器不对该对象进行优化,volatile用法和const类似,volatile的成员函数只能被volatile的对象调用。

volatile int display_register;

合成的拷贝对volatile对象无效,我们不能用合成的拷贝控制成员初始化volatile对象。要使用拷贝控制,我们必须自己定义。

class Foo{
public:
	Foo(const volatile Foo&);//从一个volatile对象进行拷贝
	//将一个volatile对象赋值给一个非volatile对象
	Foo& operator=(volatile const Foo&);
	//将一个volatile对象赋值给一个volatile对象
	Foo& operator=(volatile const Foo&) volatile;
};

链接指示:extern "C"

C有时需要调用其他语言编写的函数,使用链接指示指出任意非C函数所用的语言。

声明一个非C++的函数:extern后面跟的字符串代表编写函数所用的语言

//单句形式
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>
}

指向extern "C"函数的指针:

//pf指向一个C函数,接受int返回void
extern "C" void (*pf)(int);

void (*pf1)(int);
extern "C" void (*pf2)(int);
pf1 = pf2;//错误,C++函数指针和C函数指针不能互相转换

导出C++函数到其他语言:

//编译器会为该函数生成适用于指定语言的代码
extern "C" double calc(double dparm){/*  */}

重载函数与链接指示:依赖于目标语言是否支持

未引入知识点

  • 成员函数指针
  • 将成员函数用作可调用对象
  • 含有类类型成员的union
上一篇 下一篇

评论 | 0条评论