string
源码:
void test(int n) {
// 样本测试,测试初始化string对象的函数
string str1 = "hello";
string str2 = "hello123";
string str3 = "hello123456";
string str4 = "hello123456789";
string str5 = "hello123456789123";
string str6 = "hello123456789123456";
string str7 = "hello123456789123456789";
}
int main()
{
int n = 1;
scanf_s("%d", &n);
test(n);
}
反汇编查看
我们现在把ecx放到数据窗口查看 发现都被填充成CC
调用构造函数后的地址 我们看00ab560就是string的首地址的四个字节是什么
我们过来后发现是一个堆空间 有两个数据(FD是堆的结尾) 0059f880发现又指向了我们的string的地址 这样他可能更方便的找到自己
我们接着看
这个字符串的长度为0E 缓冲区最大为0f
如果我们继续扩大我们的字符串string会怎么存储我们的字符串哪???
我们的字符串长度为11 缓冲区最大存储的字节数为1F 主要是第二个字段 已经不是我们的字符串了 变成一个地址了
我们跟过去看 保存了我们的字符串
所以可以定义以下结构
struct MyString {
struct MyString* pSelf;
union MyUnion
{
char szString[16];
char* pString;
}u;
int nStringLen;
int nMaxStringLen;
};
//定义此结构指针去操作他
string str1 = "hello";
MyString* pString = (MyString*)&str1;
strcpy(pString->u.szString, "hello");
pString->nStringLen = 9;
CString
相对来说就很简单
他的对象就只有4个字节。
是一个指针,指向堆空间,堆中存储的是字符串。 过于简单就不看反汇编了
过来看就是我们的字符串
list
void testList() {
// 双向循环链表, 节点
list<int> listObj;
listObj.push_back(1);
listObj.push_back(2);
listObj.push_back(3);
listObj.pop_back();
listObj.push_back(4);
}
调用初始化函数后 第一次初始化
list容器会调用两次初始化函数 第一次清零 第二次会有数据
两次初始化后的地址
接着push_back一个数据看变化
元素个数变成1 接下来我们看00e9cd98链表表头的地址
一共两个元素 就是链表的前驱和后继 我们看链表的前驱和后继都指向了一个地方 是因为我们就只有一个元素 可见这是一个双向循环链表
因为添加一个元素不好看出什么 让我们在添加一个
看内存 一共两个元素 就是我们push_back 的1和2
表头的位置 现在前驱和后继就不一样了
我们顺着链表继续看
还是前驱后继
2就是我们的数据 继续向后
是我们的数据1 前面依旧是前驱后继
继续向后 又回到我们的表头
所以结构是这样的
所以可以定义以下的结构
struct MyNode
{
struct MyNode* pNext;
struct MyNode* pPrev;
int nData;
};
struct MyList
{
struct MyList* pSelf;
struct MyNode* pRoot;
int nNodeCount;
};
MyList* pList = (MyList*)&listObj;
int size = pList->nNodeCount;
MyNode* pNode = pList->pRoot;
while (pNode->pNext != pList->pRoot)
{
pNode = pNode->pNext;
int n = pNode->nData;
printf("元素=%d\n", n);
}
Vector
void testVector() {
// 动态数组,数据存储在堆内存中
// 当元素发生改变之后,会动态增加内存。
vector<int> vecObj;
vecObj.push_back(1);
vecObj.push_back(2);
vecObj.push_back(3);
vecObj.pop_back();
vecObj.push_back(4);
vecObj.push_back(5);
vecObj.push_back(6);
vecObj.push_back(7);
//-------------------------------------
// 遍历vector
for (size_t i = 0; i < vecObj.size(); i++)
{
printf("vecObj[%d] = %d", i, vecObj[i]);
}
//-------------------------------------
// 遍历vector
vector<int>::iterator iter = vecObj.begin();
while (iter != vecObj.end())
{
int n = *iter;
printf("vecObj i = %d", n);
iter++; // 有临时对象产生
//++iter;// 无临时对象
}
}
第二次初始化 会填入指向自己的指针
push_back了两个元素
第一个是指向自己的指针
第二个是指向缓冲区的指针
第三个是指向元素后一个字节的指针
第四个是指向申请的缓冲区后一个字节的指针
0134e148也就是数组首地址两个元素1和2 上图的后两个指针都指向了FD的位置
在添加一个数据
在添加一个数据后发现原来放数据的缓冲区被释放了
我们的动态数组的第二个字段的缓冲区也变位置了
我们就可以猜测,在push_back的时候,动态申请新的空间,将数据拷贝。
如果空间不够了,再申请新的更大的空间,将原来的数据拷贝进来。
又执行了pop_back
我们看指向数组最后一个元素后面的指针发生了变化 因为数组元素变少了
其实数据还在 但是他变动了指针 也就是有效数据是1和2
再继续添加4 5 6 7
当前动态数组的结构 我们看0134F4E8存储数据的位置
上图的后两个指针分别指向了有效元素的后一个字节和有效缓冲区的后一个字节
结构是这样的
可以定义此结构
struct MyVector
{
struct MyVector* pSelf;
int* pDataStart;
int* pDataEnd;
int* pBufEnd;
};
// 定义结构,操作vector
MyVector* pVector = (MyVector*)&vecObj;
int size = ((int)pVector->pDataEnd - (int)pVector->pDataStart) / sizeof(int);
for (size_t i = 0; i < size; i++)
{
//pVector->pDataStart[i] = 3;
int n = pVector->pDataStart[i];
printf("元素=%d\n", n);
}
接下来我们看下遍历动态数组 我们看源码 他会调用三个函数 一个是size一个是[]运算符号重载
还有一个printf 普通的循环遍历
// 遍历vector
for (size_t i = 0; i < vecObj.size(); i++)
{
printf("vecObj[%d] = %d", i, vecObj[i]);
}
接下来看下迭代器遍历
//-------------------------------------
// 遍历vector
vector<int>::iterator iter = vecObj.begin();
while (iter != vecObj.end())
{
int n = *iter;
printf("vecObj i = %d", n);
iter++; // 有临时对象产生
//++iter;// 无临时对象
}
两次初始化 我们看内存
0134E0E0指向了两个四字节的数据
第一个四个字节指向的是vector的地址
第二个四字节指向的是自己
00000000没用
0134f4e8指向了存放数据的缓冲区
我们到0134e0e0看下
再看0134F4E8就是我们存储的数据
所以结构是这样的