前言
该系列是《C++Primer第五版》的笔记,包含本人认为值得记录和整理的主要的知识点,并不是全部内容,也不是具体的内容。
该系列文章的作用应该是作为复习或预习的参考,有哪些知识点忘记或想学,可以大致浏览下该文章,然后再去书中寻找详细解答。(本系列文章基本是按书本顺序罗列的知识点,便于大家去书中寻找)
所以看该文章前,需要有一定的C++基础,否则阅读起来可能有困难。
本文大致整理了第十九章的知识点,涉及到C++比较晦涩的知识,部分知识个人认为不是重点,但涉及的内容又比较深入,对于入门水平来说不好理解也不好应用。个人认为目前需要掌握的有枚举类、嵌套类、union
、局部类、dynamic_cast
。
链接目录
- 第二章:变量与基本类型
- 第三章:字符串、向量和数组
- 第四章:表达式
- 第五章:语句
- 第六章:函数
- 第七章:类
- 第八章:IO库
- 第九章:顺序容器
- 第十章:泛型算法
- 第十一章:关联容器
- 第十二章:动态内存
- 第十三章:拷贝控制
- 第十四章:重载运算与类型转换
- 第十五章:面向对象程序设计
- 第十六章:模板与泛型编程
- 第十七章:标准库特殊设施
- 第十八章:用于大型程序的工具
- 第十九章:特殊工具与技术
重载new和delete
new
调用operator new
:
- 先分配一块原始、未命名的内存空间
- 然后运行构造函数构造对象
- 返回指向该对象的指针
delete
调用operator delete
:
- 先执行所指对象的析构函数
- 调用
operator delete
函数释放内存
如果new
和delete
的对象是类类型,编译器首先在类和基类的作用域内查找,否则在全局作用域中查找匹配的函数。
operator new
和operator delete
是隐式静态的,他们调用在对象构造之前和对象销毁之后,所以必须是静态的,且不能操作类内的任何数据成员。
标准库版本的new
和delete
:
//可能抛出异常的版本
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_cast
:Base
类至少含有一个虚函数,Derived
是Base
的公有派生类。
//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
对象t1
和t2
表示同一种类型返回true
t1 != t2
:如果type_info
对象t1
和t2
表示不同的类型返回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
的成员,可以指定public
、protected
、private
,默认是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