版本
各个版本特性
编译器及工具
2.Clang
4.MinGW
5.Cygwin
6.Cmake
7.Scons
8.emake
Debug
2.x64dbg
资料
https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list
https://github.com/jobbole/awesome-cpp-cn
https://learn.microsoft.com/en-us/cpp/cpp/cpp-language-reference?view=msvc-170
内存
内存分为很多物理存储单元,一个存储单元是8bit。内存最小单元是字节,不是位。
32 位 CPU 的地址总线是 32 位,地址总线的位数决定了 CPU 可寻址的内存空间大小。2 的 32 次方等于 4294967296,单位是字节(Byte),换算成 GB 就是 4GB(1GB = 1024MB,1MB = 1024KB,1KB = 1024B) 64 位 CPU 的地址总线为 64 位,其可寻址的内存空间为 2 的 64 次方字节。2 的 64 次方等于 18446744073709551616 字节,换算后约为16EB(1EB = 1024PB,1PB = 1024TB,1TB = 1024GB)实际情况中,受硬件设计、操作系统以及内存模块等多种因素的限制,64 位 CPU 实际支持的内存容量通常远小于这个理论值。例如,常见的 64 位服务器操作系统可能支持到几 TB 到几十 TB 的内存。
CPU 通过地址转换机制和内存管理单元(MMU)来访问虚拟内存,具体过程如下:
- 生成虚拟地址:在程序运行时,CPU 根据指令生成的内存地址是虚拟地址,这个地址是相对于进程的虚拟地址空间而言的。
- 地址转换:虚拟地址会被发送到 MMU。MMU 中维护着页表等数据结构,用于将虚拟地址转换为物理地址。它会根据虚拟地址中的页号,在页表中查找对应的物理页号。
- 查找页表:页表存储在内存中,MMU 需要访问页表来获取转换信息。为了提高查找速度,现代 CPU 通常会有一个特殊的缓存,称为翻译后备缓冲器(TLB)。TLB 中存储了最近使用过的虚拟地址到物理地址的映射关系。当 MMU 接收到虚拟地址时,它首先会在 TLB 中查找,如果找到匹配的映射,就可以直接获取物理地址,而无需访问内存中的页表,这大大加快了地址转换的速度。
- 访问物理内存:如果在 TLB 中没有找到相应的映射,MMU 就会访问内存中的页表来查找物理地址。找到物理地址后,CPU 就可以根据这个地址访问物理内存中的数据或指令。
-
缺页处理:如果所需的页面不在物理内存中(即发生缺页中断),操作系统会被中断,然后操作系统会负责将所需的页面从磁盘交换空间加载到物理内存中,并更新页表和 TLB。加载完成后,CPU 会重新执行导致缺页的指令,再次进行地址转换和内存访问。 通过以上过程,CPU 能够透明地访问虚拟内存,使得每个进程都能拥有独立的、连续的虚拟地址空间,而无需关心物理内存的实际布局和分配情况。这不仅方便了程序的编写和运行,还提高了系统的安全性和资源利用率。
- 1byte = 8bit
- 1kb = 1024byte
- 1mb = 1024kb
- 1gb = 1024mb
-
1tb = 1024gb
- 1word = 2byte
- 1double word = 4byte
64位通用目的寄存器
16位:AX BX CX DX SI DI SP BP
- AX(累加器):是算术运算的主要寄存器,常用于乘法、除法等运算,也可用于输入输出操作。例如,在乘法运算中,一个乘数通常放在 AX 中。
- BX(基址寄存器):可用于存放内存地址,作为基地址来访问内存中的数据。在一些数据结构的访问中,常以 BX 作为基址,再通过偏移量来获取具体的数据元素。
- CX(计数器):常用于循环操作和串操作指令中,作为计数器来控制循环的次数或串操作的长度。例如,在循环指令中,可将循环次数存入 CX,每执行一次循环,CX 的值减 1,直到 CX 为 0 时循环结束。
- DX(数据寄存器):常与 AX 配合使用,在 32 位乘法或除法运算中,DX 用于存放高 16 位数据。在输入输出操作中,也可用于存放端口地址。
- SI(源变址寄存器):在串操作指令中,通常用于存放源操作数的地址偏移量,指向源数据串。
- DI(目的变址寄存器):在串操作指令中,用于存放目的操作数的地址偏移量,指向目的数据串,用于指定数据存储的目标位置。
- SP(堆栈指针寄存器):始终指向堆栈的栈顶,用于指示当前堆栈的操作位置。在进行压栈(PUSH)和出栈(POP)操作时,SP 的值会自动调整,以保证堆栈操作的正确执行。
- BP(基址指针寄存器):通常用于访问堆栈中的数据,作为堆栈中数据的基地址。与 SP 不同,BP 更侧重于在函数调用等场景中,用于访问函数的局部变量和参数。
ax,bx,cx,dx可以只使用高8位或低8位,ax:ah/al bx:bh/bl cx:ch/cl dx:dh/dl
32位:EAX EBX ECX EDX ESI EDI ESP EBP
64位:RAX RBX RCX RDX RSI RDI RSP RBP
R8 R9 R10 R11 R12 R13 R14 R15
RAX是整个64位寄存器,EAX指的是RAX的低32位,AX指的RAX的0-15位。AH指的RAX的8-15位,AL指的RAX的0-7位。
RSI RDI RSP RBP以及R8~R15,没低16位中的高8位部分
RAX RAX是一个比较特殊的寄存器:用于返回函数返回值,被调函数调用结束之前会把返回值写入RAX中,主调函数从RAX取出返回值。RAX可以访问直接的内存地址单元,其他寄存器需要通过寄存器存放内存地址来和内存地址进行数据交换
RBP rbp寄存器用于指向当前栈的基址,实际上也是上一个调用栈的栈顶地址
RSP rsp寄存器用于指向当前栈顶的地址,实际的汇编语言里可能是出于优化目的rsp不一定始终指向栈顶
传参常用寄存器 r9d/r8d/edx/ecx
const用法
const 修饰成员变量
int money = 18;
int const bank = money;
int *card = &money;
//指针所指数据是常量
const int *wechat = &money;
//指针本身是常量
int * const aiplay = &money;
//指针和指针所指数据都不能修改
const int * const bcoin = &money;
顶层const和底层const
指针本身是个一个对象,它又可以指向另外一个对象,因此指针是不是常量以及指针所指的是不是一个常量就是二个相互独立的问题。我们使用术语 “top-level const” 来表示指针本身是一个 const。当一个指针可以指向一个 const 对象时,我们将那个 const 称为 “low-level const”。 更一般来说,top-level const可以表示任意的对象常量,这对任何数据类型抖适用。low-level const则于指针和引用等复合类型的基础类型部分有关。比较特殊的是指针类型既可以是top-level const也可以是low-level const。
int i = 0;
int* const p1 = &i;//不能改变p1的值,这是top-level const
const int ci = 42;//不能改变ci的值,这是top-level const
const int *p2 = &ci;//允许改p2的值,这是low-level const
const int *const p3 = p2;//靠右的const是top-level const,靠左的const是low-level const
const int &r = ci;//用于声明引用的const都是底层const (我的理解是 这里考虑的引用r的基础类型部分)
当执行对象拷贝时,常量是top-level const还是low-level const有区别。top-level const不受影响,执行拷贝操作并不会改变拷贝对象的值,因此拷入和拷出的对象是否是常量都没什么影响。low-level const有限制,拷入和拷出的对象必须具有相同的底层const资格,或者二个对象的数据类型必须能转换。一般来说,非常量可以转换为常量,反之则不行。
Top level case: You make a copy of the const object, so it doesn’t matter if your copy is const or not, because (assuming const-correct types) you can’t modify the original via the copy.
Low level case: You make a copy of a handle to another object. The original handle does not allow modification of the object it refers to. Allowing to ignore the low level const would mean you can obtain a handle that allows you to modify the referred object, breaking const correctness.
const修饰函数参数
void getMoney(const int value) {
value = 5;//编译失败
}
const修饰成员函数
class Service {
int money;
void getMoney(int value) const {
money = value;//编译失败
}
};
const修饰函数返回值
///const data,non-const pointer
const int * getMoney(){
int *money = new int();
return money;
}
const int *card = getMoney();
变参的函数
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include<stdarg.h>
void format(const char *p_format,...) {
va_list argp;
va_start(argp, p_format);
const unsigned int BUFFER_SIZE = 16384;
char buf[BUFFER_SIZE + 1];
vsnprintf(buf, BUFFER_SIZE, p_format, argp);
printf("%s",buf);
va_end(argp);
};
int main()
{
format("you are %s / %s","boy","girl");
system("Pause");
return 0;
}
传参数
传值 by value
//调用这个函数时先额外申请两个内存单元,存形参a和b的值1和2
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main()
{
int m = 1;
int n = 2;
swap(m, n);
system("Pause");
return 0;
}
传址
//调用这个函数时先额外申请两个内存单元,存形参a和b的地址
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int m = 1;
int n = 2;
swap(&m, &n);
system("Pause");
return 0;
}
引用 by reference
//函数时并不重新分配内存空间,形参和实参相同
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main()
{
int m = 1;
int n = 2;
swap(m, n);
system("Pause");
return 0;
}
虚函数
虚函数,在父类必须实现该函数; 纯虚函数,在父类中不需要实现该函数,子类必须实现。
#include <iostream>
using namespace std;
class BaseView {
public:
BaseView() { }
virtual void Paint() { cout << "Paint BaseView" << endl; };
virtual void Print() = 0;
};
class Button:BaseView {
public:
Button() { Paint(); }
virtual void Paint() { cout << "Paint Button" << endl; };
virtual void Print() { cout << "Print Button" << endl; };
};
int main()
{
Button btn;
btn.Paint();
system("Pause");
return 0;
}
//结果: Paint BaseView Paint Button Paint Button
explicit构造函数
#include <iostream>
using namespace std;
class BaseView {
public:
int id = 0;
explicit BaseView(int _id) { id = _id; }
};
int main()
{
BaseView bv1(10);
BaseView bv2 = BaseView(10);
BaseView bv3 = 10; //编译错误 不允许隐式的转换
system("Pause");
return 0;
}
继承
#include <iostream>
using namespace std;
class BaseView {
public:
BaseView() { cout << "BaseView()" << endl; };
~BaseView() { cout << "~BaseView()" << endl; };
};
class Button:public BaseView {
public:
Button() { cout << "Button()" << endl; };
~Button() { cout << "~Button()" << endl; };
};
int main()
{
Button* btn = new Button();
delete btn;
btn = 0;
/*
输出:
BaseView()
Button()
~Button()
~BaseView()
*/
system("Pause");
return 0;
}
重载
成员函数被重载的特征:
1.相同的范围(在同一个类中); 2.函数名字相同; 3.参数不同; 4.virtual 关键字可有可无。
#include <iostream>
using namespace std;
class BaseView {
public:
BaseView() { }
};
class Button:BaseView {
public:
Button() { }
void init() {};
void init(int width, int height) {};
void init(int width, int height,int length) {};
};
int main()
{
Button btn;
system("Pause");
return 0;
}
int func(int a); 和 int func(const int a); 不构成函数重载 对于 int func(int a); 和 int func(const int a);,在参数传递时采用的是值传递方式。在值传递中,const 修饰符不会影响函数调用时参数的匹配。也就是说,当你调用 func 函数并传入一个整数时,编译器无法依据这个整数是否被 const 修饰来判断该调用哪个函数,因为在值传递过程中,函数接收到的是参数的副本,const 修饰符在这个副本上是否存在对调用者来说是不可见的。
覆盖
覆盖是指子类函数覆盖父类函数,特征是:
1.不同的范围(分别位于子类与父类); 2.函数名字相同; 3.参数相同; 4.基类函数必须有virtual 关键字。
#include <iostream>
using namespace std;
class BaseView {
public:
BaseView() { }
virtual void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
};
class Button:BaseView {
public:
Button() { }
void init(int width, int height) { cout << "Button.init(int width, int height)" << endl; };
};
int main()
{
Button btn;
btn.init(10,20);
system("Pause");
return 0;
}
//结果:Button.init(int width, int height)
隐藏
如果子类的函数与父类的函数同名,并且参数也相同,但是父类函数没有virtual关键字。此时,父类的函数被隐藏(注意别与覆盖混淆)。
#include <iostream>
using namespace std;
class BaseView {
public:
BaseView() { }
void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
};
class Button:BaseView {
public:
Button() { }
void init(int width, int height) { cout << "Button.init(int width, int height)" << endl; };
};
int main()
{
Button btn;
btn.init(10,20);
system("Pause");
return 0;
}
//结果:Button.init(int width, int height)
隐藏
如果子类的函数与父类的函数同名,但是参数不同。此时,不论有无virtual关键字,父类的函数将被隐藏(注意别与重载混淆)
#include <iostream>
using namespace std;
class BaseView {
public:
void init(int width, int height) { cout << "BaseView.init(int width, int height)" << endl; };
virtual void resetSize(int width, int height) { cout << "BaseView.resetSize(int width, int height)" << endl; };
virtual void resetPos(int x, int y) { cout << "BaseView.resetPos(int x, int y)" << endl; };
};
class Button:public BaseView {
public:
void init(float width, float height) { cout << "Button.init(float width, float height)" << endl; };
virtual void resetSize(float width, float height) { cout << "Button.resetSize(float width, float height)" << endl; };
virtual void resetPos(int x, int y) { cout << "Button.resetPos(int x, int y)" << endl; };
};
int main()
{
Button btn;
btn.init(10, 20);
btn.resetSize(10, 20);
btn.resetPos(10,20);
Button btn2;
BaseView *bptr1 = &btn2;
Button *bptr2 = &btn2;
//bad
bptr1->init(10, 20);
bptr2->init(10,20);
//bad
bptr1->resetSize(10, 20);
bptr2->resetSize(10, 20);
//good
bptr1->resetPos(10, 20);
bptr2->resetPos(10, 20);
/*
结果:
Button.init(float width, float height)
Button.resetSize(float width, float height)
Button.resetPos(int x, int y)
BaseView.init(int width, int height)
Button.init(float width, float height)
BaseView.resetSize(int width, int height)
Button.resetSize(float width, float height)
Button.resetPos(int x, int y)
Button.resetPos(int x, int y)
*/
system("Pause");
return 0;
}
static_cast
static_cast主要用于在相关类型之间进行转换,这些类型通常是有一定关联的,比如基本数据类型之间的转换(如int和float)、类层次结构中的向上转换(将派生类指针或引用转换为基类指针或引用)。它是一种编译时的类型转换操作,编译器会在编译阶段检查转换是否合理。
int i = 5;
float f = static_cast<float>(i);
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
public:
void func() override {}
};
int main() {
Derived d;
Base* b = static_cast<Base*>(&d);
return 0;
}
dynamic_cast
dynamic_cast主要用于在类的继承层次结构中进行安全的向下转换(将基类指针或引用转换为派生类指针或引用)或者交叉转换(在有多个继承分支的情况下)。与static_cast不同的是,dynamic_cast会在运行时检查转换是否有效,如果转换无效(比如基类指针实际指向的对象不是目标派生类的对象),对于指针类型的转换,它会返回nullptr;对于引用类型的转换,它会抛出std::bad_cast异常。
int main() {
Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b);
if (d) {
// 转换成功后的操作
d->func();
}
else {
// 转换失败后的操作
// 这里可能是b实际指向的不是Derived对象
}
delete b;
return 0;
}
int main() {
Base& b_ref = *new Derived;
try {
Derived& d_ref = dynamic_cast<Derived&>(b_ref);
d_ref.func();
}
catch (std::bad_cast& e) {
// 处理异常,当转换失败时会进入这里
}
return 0;
}
reinterpret_cast
reinterpret_cast是一种比较危险的类型转换操作,它可以将一种类型的指针转换为另一种完全不同类型的指针,或者将一个整数转换为指针,或者将指针转换为整数等。它只是简单地对二进制数据进行重新解释,不考虑类型之间的语义关系。这种转换通常用于底层的、和硬件或系统接口相关的编程场景,如直接操作内存地址等,但很容易导致程序出现错误。
int main() {
int i = 10;
// 将int*转换为char*
char* c = reinterpret_cast<char*>(&i);
// 这种操作可能会破坏类型的安全性
// 这里只是简单地将int类型的内存表示按照char类型来解释
return 0;
}
const_cast
修改const限定符示例 有时候,我们可能会遇到这样的情况,比如有一个函数接收一个const参数,但在函数内部其实需要对这个参数进行一些非const的操作。这可能是因为这个函数被设计得不够合理,或者是因为在某些特定的情况下(比如对一个原本不是const的对象进行了const引用传递,但在函数内部又需要修改它),需要去除const属性来进行操作
void updateCounter(const int* const pCounter) {
// 这里通过const_cast去掉指针和指针所指对象的const属性
int* nonConstCounter = const_cast<int*>(pCounter);
(*nonConstCounter)++;
}
int main() {
int counter = 0;
updateCounter(&counter);
return 0;
}
修改volatile限定符示例 volatile关键字用于告诉编译器,变量的值可能会在程序的控制流之外被改变,比如被硬件或者其他异步线程修改。const_cast也可以用于修改volatile限定符。
volatile const int* pReg = getHardwareRegisterAddress();
int* nonVolatileReg = const_cast<int*>(pReg);
// 进行一些非volatile的操作,例如在特定的初始化阶段设置寄存器的初始值
*nonVolatileReg = 0;
enum class
在 C++ 中,enum class(枚举类)是一种强类型的枚举。它解决了传统枚举(enum)的一些问题,比如枚举值的作用域问题和类型安全性问题。
enum class Color {
RED,
GREEN,
BLUE
};
Color c = Color::RED;
// int i = c; // 这是错误的,不能隐式转换
int i = static_cast<int>(c); // 正确,需要显式转换
enum OldColor {
RED,
GREEN,
BLUE
};
OldColor c = OLD_COLOR::RED;
int i = c; // 可以隐式转换为整数
枚举类中的枚举值的名字是在枚举类的作用域内的,这避免了名字冲突。
enum Fruit {
APPLE,
BANANA,
ORANGE
};
enum Vegetable {
CARROT,
BROCCOLI,
APPLE // 这里会产生命名冲突,因为APPLE已经在Fruit枚举中定义过了
};
enum class AnotherFruit {
APPLE,
BANANA,
ORANGE
};
enum class AnotherVegetable {
CARROT,
BROCCOLI,
APPLE // 不会产生命名冲突,因为作用域不同
};
Switch中使用
Color c = Color::GREEN;
switch (c) {
case Color::RED:
std::cout << "It's red." << std::endl;
break;
case Color::GREEN:
std::cout << "It's green." << std::endl;
break;
case Color::BLUE:
std::cout << "It's blue." << std::endl;
break;
}
类型精准
enum OldEnum {
VALUE1 = 1,
VALUE2 = 2,
VALUE3 = 3
};
enum class NewEnum : unsigned char {
VALUE1 = 1,
VALUE2 = 2,
VALUE3 = 3
};
lvalue和rvalue
An lvalue has an address that your program can access. Examples of lvalue expressions include variable names, including const variables, array elements, function calls that return an lvalue reference, bit-fields, unions, and class members.
A prvalue expression has no address that is accessible by your program. Examples of prvalue expressions include literals, function calls that return a nonreference type, and temporary objects that are created during expression evaluation but accessible only by the compiler.
右值引用必须绑定右值的引用,通过&&来获得右值引用。右值引用必须绑定一个将要销毁的对象。 左值引用,我们不能将其绑定到要求转化的表达式/字面常量/返回右值的表达式。
int &l = 0;//lvaule reference不接受 rvalue,非 const 左值引用 'l' 到 int 类型不能绑定到类型 int 的右值
int &&r = 10;//rvaule reference 接受 rvaule
const char (*p)[12] = &"hello world"; //hello world这个字面值是lvalue,它可以取地址。
“std::move ()” 是 C++ 标准库中的一个函数。它的作用是将一个对象的状态从一个位置 “转移” 到另一个位置,而不是进行传统的拷贝操作。它主要用于提高性能,特别是在处理资源拥有型对象(如智能指针等)时,可以避免不必要的拷贝,直接将资源的所有权转移给另一个对象。例如,将一个临时对象的资源转移给一个正在构造的对象,或者在容器中进行高效的元素移动操作。
#include <algorithm>
#include <iostream>
#include <string>
class student
{
public:
student(const int id, const std::string& name);
student(const student &s);
student(student &&s) noexcept;
student& operator=(const student& s);
student& operator=(student&& s) noexcept;
~student();
int id;
std::string name;
};
//constructor
student::student(const int id, const std::string& name)
{
this->id = id;
this->name = name;
std::cout << "Constructor called for " << name << '\n';
}
//copy constructor
student::student(const student& s)
{
this->id = s.id;
this->name = s.name;
std::cout << "& Constructor called for " << name << '\n';
}
//move constructor
student::student(student&& s) noexcept
{
this->id = s.id;
this->name = std::move(s.name);
std::cout << "&& Constructor called for " << name << '\n';
}
//copy assignment operator
student& student::operator=(const student& s)
{
if (this != &s)
{
this->id = s.id;
this->name = s.name;
}
std::cout << "= Constructor called for " << name << '\n';
return *this;
}
//move assignment operator
student& student::operator=(student&& s) noexcept
{
if (this != &s)
{
this->id = s.id;
this->name = std::move(s.name);
}
std::cout << "&&= Constructor called for " << name << '\n';
return *this;
}
//destructor
student::~student()
{
std::cout << "Destructor called for " << name << '\n';
}
int main(int argc, char* argv[])
{
student s1(1, "John");
student s2 = std::move(s1);//把s1的资源转移到s2,s1的资源被释放,s2的资源被保留
/*
Constructor called for John
&& Constructor called for John
Destructor called for John //s2
Destructor called for //s1
*/
return 0;
}
array
int arr[10];//含有10个整数的数组
int *parr[20];//含有20个整数指针的数组
int (*Parry)[10] = &arr;//Parray是一个指针,指向一个含有10个整数的数组
int (&Rarray)[10] = arr;//Rarray是一个引用,指向一个含有10个整数的数组
string nums[] = {"one","two"};
string *p = &nums[0];//p指向nums的第一个元素
string *p2 = nums;//等价于p2 = &nums[0].用到数组名字的地方,编译器都会自动将其替换为一个指向数组首元素的指针
char str[12] = "hello world"; //"hello world" 的内容就存于栈内存里
const char* str2 = "hello world";//"hello world" 会被存储在只读数据段(也称作常量区)
func
bool fun(int a, int b);//该函数的类型是 bool(int,int),函数类型是和函数名无关的
bool (*p)(int a, int b);//p是一个指向函数的指针,这个函数有二个参数,一个返回值。
p = fun;//当我们把函数名作为一个值使用时,改函数自动转化为指针.p指向名为fun的函数
p = &fun;//等价于上面一句。
//我们可以直接使用指向函数的指针调用改函数,无须提前解引用指针
bool b1 = pf(1,2);//调用func函数
bool b2 = (*pf)(1,2);//等价于上一句
bool b3 = func(1,3);//另一个等价
local stack object
返回局部对象的指针会导致悬垂指针问题,属于严重的编程错误
#include <iostream>
class Student {
public:
Student()
{
std::cout << "Student() at " << this << std::endl;
age = 0;
}
Student(const Student& other)
{
std::cout << "Student(const Student& other) at " << this << std::endl;
age = other.age;
}
~Student()
{
std::cout << "~Student() at " << this << std::endl;
}
int age;
};
Student* Test()
{
Student d;
std::cout << "Address of d in Test(): " << &d << std::endl;
return &d;
}
int main()
{
Student* a = Test();
std::cout << "Address of a in main(): " << a << std::endl;
a->age = 100;
return 0;
}
结果输出
Student() at 000000368B7CFB14
Address of d in Test(): 000000368B7CFB14
~Student() at 000000368B7CFB14
Address of a in main(): 000000368B7CFB14
Test函数返回的地址和main函数中的指针a指向同一个内存地址。然而,当Test函数结束时,d被析构,释放了该内存。尽管a仍然指向该地址,但此时该内存区域已经不属于Student对象,任何对它的访问都是未定义的。在实际运行中,由于内存回收机制的延迟,a->age = 100;可能看似正常,但实际上这是在修改已经被释放的内存,可能会导致不可预测的结果。
获取虚函数表地址
若类中存在虚函数,每个对象都会有一个指向虚函数表的指针。通常,这个指针位于对象内存布局的起始位置。
#include <iostream>
class Base {
public:
virtual void func1() {
std::cout << "Base::func1()" << std::endl;
}
virtual void func2() {
std::cout << "Base::func2()" << std::endl;
}
};
int main() {
Base obj;
// 获取对象的地址
void** objPtr = reinterpret_cast<void**>(&obj);
// 获取虚函数表的地址
void** vtablePtr = *objPtr;
std::cout << "Address of the object: " << &obj << std::endl;
std::cout << "Address of the virtual table: " << vtablePtr << std::endl;
return 0;
}
Lambda
[capture list] (parameter list) mutable(可选) exception(可选) -> return type(可选) { function body }
捕获列表(capture list):用于指定 Lambda 表达式可以访问的外部变量。捕获列表可以为空,也可以包含一个或多个变量,变量之间用逗号分隔。捕获方式有值捕获和引用捕获两种
#include <iostream>
//func1 使用值捕获的方式捕获了变量 x,在 Lambda 表达式内部只能读取 x 的值;func2 使用引用捕获的方式捕获了变量 x,在 Lambda 表达式内部可以修改 x 的值
int main() {
int x = 10;
// 值捕获
auto func1 = [x]() {
std::cout << "Value of x: " << x << std::endl;
};
// 引用捕获
auto func2 = [&x]() {
x = 20;
std::cout << "Modified value of x: " << x << std::endl;
};
func1();
func2();
return 0;
}
Lambda 表达式是一种匿名函数对象
闭包类型的构造函数用于初始化捕获的变量。根据捕获方式(值捕获或者引用捕获)的不同,构造函数的行为也有所不同: 值捕获:在构造函数里复制捕获的变量。 引用捕获:在构造函数里存储捕获变量的引用。
闭包类型会重载 operator(),这样闭包对象就能够像函数一样被调用。operator() 的参数列表和返回类型与 Lambda 表达式的参数列表和返回类型是一致的。
闭包类型会为每个捕获的变量创建一个成员变量。对于值捕获,成员变量存储的是捕获变量的副本;对于引用捕获,成员变量存储的是捕获变量的引用。
#include <iostream>
int main() {
int x = 10;
auto lambda = [x](int y) { return x + y; };
int result = lambda(5);
std::cout << "Result: " << result << std::endl;
return 0;
}
//等效代码
#include <iostream>
// 编译器生成的闭包类型
class __lambda_5_13 {
int x; // 成员变量,存储捕获的变量 x
public:
// 构造函数,用于初始化捕获的变量
__lambda_5_13(int _x) : x(_x) {}
// 函数调用运算符重载
int operator()(int y) const {
return x + y;
}
};
int main() {
int x = 10;
// 创建闭包对象
__lambda_5_13 lambda(x);
int result = lambda(5);
std::cout << "Result: " << result << std::endl;
return 0;
}