头文件
.h 文件中能包含:
- 类成员数据的声明,但不能赋值
- 类静态数据成员的定义和赋值,但不建议
- 类的成员函数的声明
- 非类成员函数的声明
- 常数的定义:如:constint a=5;
- 静态函数的定义
- 类的内联函数的定义
不能包含:
- 所有非静态变量(不是类的数据成员)的声明
- 默认命名空间声明不要放在头文件,using namespace std; 等应放在 .cpp 中,在 .h 文件中使用 std::string
类型限定符
限定符 | 含义 |
---|---|
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
存储类
存储类 | 含义 |
---|---|
auto | 声明变量时根据初始化表达式自动推断该变量的类型 |
register | 用于定义存储在寄存器中而不是 RAM 中的局部变量,用于需要快速访问的变量,不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置) |
static | 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享;修饰全局变量时,会使变量的作用域限制在声明它的文件内 |
extern | 全局变量的引用,全局变量对所有的程序文件都是可见 |
mutable | |
thread_local | 仅可在它在其上创建的线程上访问,变量在创建线程时创建,并在销毁线程时销毁,每个线程都有其自己的变量副本 |
继承类型
class Rectangle: public Shape
{
...
}
继承类型 | 说明 |
---|---|
public | 基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问 |
protected | 基类的公有和保护成员将成为派生类的保护成员。 |
private | 基类的公有和保护成员将成为派生类的私有成员。 |
预定义宏
宏 | 描述 |
---|---|
__LINE__ | 程序编译时包含当前行号 |
__FILE__ | 程序编译时包含当前文件名 |
__DATE__ | 包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期 |
__TIME__ | 包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间 |
信号
信号 | 描述 |
---|---|
SIGABORT | 程序的异常终止,如调用 abort |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作 |
SIGILL | 检测非法指令 |
SIGINT | 程序终止(interrupt)信号 |
SIGSEGV | 非法访问内存 |
SIGTERM | 发送到程序的终止请求 |
C++ 信号处理库提供了 signal 函数,用来捕获突发事件,raise 函数来生成信号:
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
exit(signum);
}
int main ()
{
int i = 0;
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
raise(SIGINT);
return 0;
}
线程
c++ 11 之后有了标准的线程库:
#include <iostream>
#include <thread>
std::thread::id main_thread_id = std::this_thread::get_id();
void hello()
{
std::cout << "Hello Concurrent World\n";
if (main_thread_id == std::this_thread::get_id())
std::cout << "This is the main thread.\n";
else
std::cout << "This is not the main thread.\n";
}
void pause_thread(int n) {
std::this_thread::sleep_for(std::chrono::seconds(n));
std::cout << "pause of " << n << " seconds ended\n";
}
int main() {
std::thread t(hello);
std::cout << t.hardware_concurrency() << std::endl;//可以并发执行多少个(不准确)
std::cout << "native_handle " << t.native_handle() << std::endl;//可以并发执行多少个(不准确)
t.join();
std::thread a(hello);
a.detach();
std::thread threads[5]; // 默认构造线程
std::cout << "Spawning 5 threads...\n";
for (int i = 0; i < 5; ++i)
threads[i] = std::thread(pause_thread, i + 1); // move-assign threads
std::cout << "Done spawning threads. Now waiting for them to join:\n";
for (auto &thread : threads)
thread.join();
std::cout << "All threads joined!\n";
}
强制类型转换
const_cast
const_cast 用于去除对象的 const 或 volatile 属性
void Func(double& d) { ... }
void ConstCast()
{
const double pi = 3.14;
Func(const_cast<double&>(pi)); // No error.
}
static_cast
static_cast 强制转换只会在编译时检查,但没有运行时类型检查来保证转换的安全性。同时,static_cast 也不能去掉 expression 的 const、volitale、或者 __unaligned 属性。
其主要应用场景有:
-
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
-
用于基本数据类型之间的转换,如把 int 转换成char,把 int 转换成enum。这种转换的安全性也要开发人员来保证。
-
把 void 转换成目标类型的空指针。
-
把任何类型的表达式转换成 void 类型。
Sub sub;
Base *base_ptr = static_cast<Base*>(&sub);
dynamic_cast
dynamic_cast 运算符的主要用途:将基类的指针或引用安全地转换成派生类的指针或引用。并用派生类的指针或引用调用非虚函数。
如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数。
前提条件:当我们将 dynamic_cast 用于某种类型的指针或引用时,只有该类型至少含有虚函数时(最简单是基类析构函数为虚函数),才能进行这种转换。否则,编译器会报错。
在指针类型中,基类指针所指对象为基类类型,在这种情况下 dynamic_cast 在运行时做检查,转换失败,返回结果为 0;
在引用类型中,并不存在空引用,所以引用的 dynamic_cast 检测失败时会抛出一个 bad_cast 异常。
Base * base = new Base;
if (Derived *der = dynamic_cast<Derived*>(base))
{
cout << "转换成功" <<endl;
der->Show();
}
else
{
cout << "转换失败" <<endl;
}
reinterupt_cast
reinterpret_cast 用来处理无关类型转换,通常为操作数的位模式提供较低层次的重新解释。
推荐使用在:
- 从指针类型到一个足够大的整数类型
- 从整数类型或者枚举类型到指针类型
- 从一个指向函数的指针到另一个不同类型的指向函数的指针
- 从一个指向对象的指针到另一个不同类型的指向对象的指针
- 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
- 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
错误的使用 reinterpret_cast 很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用 reinterpret_cast 方式。
智能指针
unique_ptr
由 unique_ptr 管理的内存,只能被一个对象持有,所以,unique_ptr不支持复制和赋值。想要把一个 unique_ptr 的内存交给另外一个 unique_ptr 对象管理。只能使用 std::move 转移当前对象的所有权。转移之后,当前对象不再持有此内存,新的对象将获得专属所有权。
auto w = std::make_unique();
auto w2 = std::move(w); // w2 获得内存所有权,w 此时等于 nullptr
unique_ptr在默认情况下和裸指针的大小是一样的,所以内存上没有任何的额外消耗,性能是最优的。
shared_ptr
多个智能指针可以共享同一个对象。shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,引用计数会 +1。当一个 shared_ptr 离开作用域时,引用计数会 -1。当引用计数为0的时候,则 delete 内存。
auto w = std::make_shared();
shared_ptr的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。
weak_ptr
允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空。
内存空间
区 | 描述 |
---|---|
堆 | 操作系统维护的一块动态分配内存,malloc 在堆上分配的内存块,使用 free 释放内存 |
栈 | 由编译器自动分配释放,存放函数的参数值,局部变量的值等 |
自由存储区 | C++ 中通过 new 与 delete 动态分配和释放对象的抽象概念 |
全局区(静态区) | 全局变量和静态变量分配在此一块内存中 |
常量存储区 | 存储常量字符串, 程序结束后由系统释放 |