内存对齐
- 结构体中数据成员的起始地址偏移量必须是自身大小的整数倍
- 整个结构体的大小必须是最大数据成员大小的整数倍
- 可以使用#pragma pack(n) 自定义所有偏移量的规则, 而无需考虑每一个数据成员和整个结构体
#include <iostream>
//#pragma pack(1)
using namespace std;
struct A{
char a;
int b;
short c;
};
struct B{
short c;
char a;
int b;
};
int main(){
cout<<sizeof(A)<<endl;
cout<<sizeof(B)<<endl;
return 0;
}
12
8
// 7 7 如果有#pragma pack(1)的话
- union共用体是几个变量公用一块内存, 结构体中共用体变量的偏移量是共用体中最大数据类型变量的整数倍. 所占大小是共用体中变量所需要的最大空间.
- 结构体在C++中只是一个默认访问控制权限为public的类, 空结构体大小为1
变长结构体
-
一些数据包中数据部分的长度是可变的, 如果固定长度面部了会造成浪费, 因此引入变长结构体
-
结构的最后一个数据成员是长度为0的数组, 并不会算入结构体所占内存空间的大小
-
可以动态开辟一个比结构体更大的空间, 让数组名去指向
-
三种实现
- 使用指针
- 使用长度为0的数组
- 使用长度为1的数组 (有些编译器不支持长度为0的数组)
-
内存方面 : 指针会额外占用结构体的内存空间, 长度为0的数组则不需要
-
数据连续存储方面 : 指针指向的空间和结构体中其余成员并不是连续存储, 而是单独开辟的. 而长度为0/1的数组则是连续存储的
-
释放方面 : 使用指针, 需要先释放指针再释放结构体. 而数组则可以直接释放结构体
-
分配内存 : 结构体必须先分配一次, 然后再分配指针一次
-
好处 : 分配一段连续的内存空间, 避免内存碎片化, 易于管理.
strcpy、memcpy、compare
- 注意解决内存重叠问题
char *strcpy(char *dest, const char *src) {
if (!dest || !src)
return NULL;
char *d = dest;
while (*src != '\0') {
*d++ = *src++;
}
*d = '\0';
return dest;
}
char* strcpy1(char* dest, const char* src) {
if(!dest || !src)
return NULL;
// 包括了末尾的'\0'
int size = (int)strlen(src) + 1;
char* d = dest;
// 有重叠部分
if (d > src && d < src + size) {
d = d + size - 1;
src = src + size - 1;
// 倒着拷贝
while(size--) {
*d-- = *src--;
}
} else {
while(size--)
*d++ = *src++;
}
return dest;
}
void* memcpy1(void* dest, const void* src, size_t n) {
if(!dest || !src)
return NULL;
char* d = (char*)dest;
const char* s = (const char*)src;
if(d > s && d < s + n) {
d = d + n - 1;
s = s + n - 1;
while(n--)
*d-- = *s--;
} else {
while(n--)
*d++ = *s++;
}
return dest;
}
int compare(const char* s1, const char* s2) {
// '\0'的ASCII是0, 小于所有字母, 所以短的字符串是小的, 相当于'\0'和一个字母比较
while(*s1 == *s2 && *s1 != '\0') {
++s1;++s2;
}
if(*s1 < *s2)
return -1;
else if (*s1 > *s2)
return 1;
else
return 0;
}
char* strcat(char* dest, const char* src) {
char* d = dest;
while(*d != '\0') d++;
// 不考虑内存重叠
while(*src!='\0') {
*d = *src;
++d;++src;
}
// 为了防止dest空间不足, 自动扩充(会把前面的内容重复一遍)
*d = '\0';
return dest;
}
int main() {
char src1[8] = "ghtcit";
cout<<strcpy1(src1+1, src1)<<endl;
char src2[20] = "ghtcit";
cout<<(char*)memcpy1(src2+1, src2, 3)<<endl;
cout<<compare("asf", "asfa")<<endl;
cout<<strcat(src2, "aaa")<<endl;
return 0;
}
ghtcit
ghtit
-1
gghtitaaa
Program ended with exit code: 0
vector中clear 和 erase
clear
- clear()清除vector中的所有元素, 因此size变为0, 但是并不释放内存
- 如果是指针对象, 并不能清除指向的内容, 需要先循环一遍, 释放指针, 在执行clear()
- 如果想要释放内存, 可以通过swap来实现
- swap 还可以用来整理空间, 也是利用匿名对象, 但是swap()的参数不能是匿名对象, 匿名对象只能用作调用者
vector<int*> v;
for(int i = 0; i < v.size(); ++i) {
delete v[i];
}
v.clear();
// 创建的匿名对象, 之后会被立即销毁, 该匿名对象size和cap都为0, 和v进行交换
vector<int> ().swap(v);
// temp是{}里的临时变量, 离开{}也被销毁
{
vector<int> temp;
v.swap(temp);
}
void test01() {
vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(6);
v.push_back(6);
v.push_back(5);
cout<<v.size()<<endl;
cout<<v.capacity()<<endl;
v.clear();
// 因为内存未被释放, 因此仍然可以访问到其中的值, 但是这样做事非法的
cout<<v[0]<<endl;
cout<<v.size()<<endl;
cout<<v.capacity()<<endl;
vector<int>().swap(v);
// 内存已被释放, 会报错
// cout<<v[0]<<endl;
cout<<v.size()<<endl;
cout<<v.capacity()<<endl;
}
clear之前:
5
8
clear之后
1
0
8
swap之后
0
0
Program ended with exit code: 0
void test03() {
vector<int> v;
v.reserve(1000);
v.push_back(1);
cout<<v.size()<<endl;
cout<<v.capacity()<<endl;
vector<int>(v).swap(v);
cout<<v.size()<<endl;
cout<<v.capacity()<<endl;
}
1
1000
1
1
Program ended with exit code: 0
erase
- erase两种重载形式, 一个是传入一个迭代器, 一个是传入一个迭代器范围
- 两种形式返回值都是一个迭代器, 指向最后一个删除元素的下一个位置
- 传入的迭代器参数本身也会改变成下一个位置
- erase也不会释放空间, 需要swap来辅助释放
void test04() {
vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(6);
v.push_back(6);
v.push_back(6);
/*
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
v.push_back(7);
auto iter = v.begin();
cout<<*iter<<endl; // 1
v.erase(iter, iter+3);
cout<<*iter<<endl; // 4
auto iter = v.begin()+3;
cout<<*iter<<endl; // 1
v.erase(iter-2, iter-1);
cout<<*iter<<endl; // 6 (3+2-1 +第一个参数后的绝对值, +第二个参数后的数+(-1))
*/
for(auto iter = v.begin(); iter != v.end(); ++iter) {
if(*iter == 6) {
// 因为erase一个元素后会返回一个迭代器指向下一个位置, 如果没有iter--, 那么之后再次++iter, 就会跳过一个元素
v.erase(iter);
iter--;
}
}
for(const auto& n : v)
cout<<n<<" ";
cout<<endl;
}
虚函数表
- 虚函数表不是函数, 只是用来存放函数地址的, 因此不在代码区
- 虚函数表是属于类的, 而不是对象, 全剧只有一个. 大小在编译时期就可以确定, 因此不在堆区, 栈区.
- 所以虚函数表在数据去, 只读数据去, rodata
单链表冒泡排序
ListNode* bubble(ListNode* head) {
int n = 0;
ListNode* temp = head;
while(temp != NULL) {
temp = temp->next;
++n;
}
while(n > 0) {
ListNode* cur = head;
while(cur->next != NULL) {
if(cur->val > cur->next->val)
swap(cur->val, cur->next->val);
cur = cur->next;
}
--n;
}
return head;
}
三数宏定义
#define MAX(a,b,c) ((a)>(b)?((a)>(c)?(a):(c)):((b)>(c)?:(b):(c)))