数组:
std::array中,可以通过at()来访问数组的内容,如果越界了就会抛出越界异常
数组安全:由于数组的本质是向操作系统申请了一块内存,因此越界的数组将会访问到不该访问的地址,这种越界将会造成程序崩溃,BUG错误,更可怕的是,数组越界漏洞,可能会让攻击者拿到操作系统的控制权。
容器:
1 2 3 4 5
| std::vector<数据类型> 变量名; std::vector<int> s; std::vector<int> s{1,2,3}; std::vector<int> s(5); std::vector<int> s(5,100);
|
容器的几个新用法:
1 2 3 4 5 6 7 8
| std::vector<int> s; s.push_back(值); s.pop_back(值); s.insert(); s.assign(10,100); s.erase(); s.clear(); s.empty();
|
容器例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <iostream> #include <vector>
int main() { std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto v : vec) { std::cout << v << " "; } std::cout << std::endl;
return 0; }
#include <iostream> #include <vector>
int main() { std::vector<std::string> vec = {"Alice", "Bob", "Charlie"};
for (auto& name : vec) { name += " (modified)"; }
for (const auto& name : vec) { std::cout << name << std::endl; }
return 0; }
#include <iostream> #include <map>
int main() { std::map<std::string, int> m = {{"Alice", 25}, {"Bob", 30}, {"Eve", 35}};
for (const auto& [key, value] : m) { std::cout << key << ": " << value << std::endl; }
return 0; }
#include <iostream>
int main() { int arr[] = {10, 20, 30, 40, 50};
for (auto num : arr) { std::cout << num << " "; } std::cout << std::endl;
return 0; }
#include <iostream> #include <unordered_map>
int main() { std::unordered_map<std::string, int> um = {{"One", 1}, {"Two", 2}, {"Three", 3}};
for (const auto& pair : um) { std::cout << pair.first << ": " << pair.second << std::endl; }
return 0; }
|
结构化绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <map>
int main() { std::pair<int, std::string> p = {1, "Alice"};
auto [id, name] = p;
std::cout << "ID: " << id << ", Name: " << name << std::endl;
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream> #include <tuple>
int main() { std::tuple<int, double, std::string> t = {1, 3.14, "Hello"};
auto [a, b, c] = t;
std::cout << a << ", " << b << ", " << c << std::endl; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <iostream> #include <map>
int main() { std::map<std::string, int> m = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};
for (const auto& [key, value] : m) { std::cout << key << ": " << value << std::endl; }
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11
| #include <iostream>
int main() { int arr[3] = {10, 20, 30};
auto [x, y, z] = arr; std::cout << x << ", " << y << ", " << z << std::endl;
return 0; }
|
解构用户自定义类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> #include <string>
struct Person { int id; std::string name; double salary; };
int main() { Person p = {101, "Alice", 75000.0};
auto [id, name, salary] = p; std::cout << "ID: " << id << ", Name: " << name << ", Salary: " << salary << std::endl;
return 0; }
|
特殊应用:非聚合类型(涉及重载)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream> #include <tuple>
class Person { private: int id; std::string name; double salary;
public: Person(int i, std::string n, double s) : id(i), name(n), salary(s) {}
friend struct std::tuple_size<Person>; friend auto std::get<0>(const Person&) -> int; friend auto std::get<1>(const Person&) -> std::string; friend auto std::get<2>(const Person&) -> double; };
namespace std { template <> struct tuple_size<Person> : std::integral_constant<size_t, 3> {}; template <> auto get<0>(const Person& p) { return p.id; } template <> auto get<1>(const Person& p) { return p.name; } template <> auto get<2>(const Person& p) { return p.salary; } }
int main() { Person p(101, "Bob", 85000.0);
auto [id, name, salary] = p; std::cout << "ID: " << id << ", Name: " << name << ", Salary: " << salary << std::endl;
return 0; }
|
指针
指针语法:数据类型 * 变量名称
1 2 3 4 5 6 7
| int* a; int *b;
int* pa = &a;
|
指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| int a[]{ 10001,20001,30001,40001 }; int* ptr{ &a[0] };
std::cout << ptr << std::endl; std::cout << &a[0] << std::endl; std::cout << *ptr << std::endl; std::cout << (*ptr)++ << std::endl; std::cout << a[0] << std::endl; std::cout << "------------分界线----------------" << std::endl;
std::cout << ptr << std::endl; std::cout << *ptr++ << std::endl; std::cout << a[0] << std::endl; std::cout << *ptr << std::endl; std::cout << ptr << std::endl; std::cout << "------------分界线----------------" << std::endl;
int** pptr{ &ptr }; std::cout << "数组a[0]的地址为: " << ptr << std::endl; std::cout << "数组a[0]的地址为: " << &a[0] << std::endl; std::cout << "ptr的地址为:" << pptr << std::endl; std::cout << *pptr << std::endl; std::cout << **pptr << std::endl;
*pptr = &a[1]; std::cout << *pptr << std::endl;
int*** ppptr{ &pptr }; std::cout << ***ppptr << std::endl; std::cout << "------------分界线----------------" << std::endl;
|
常量指针
1 2 3 4 5 6 7 8 9 10 11
|
const int a{ 100 }; const int b{ 200 }; int c{ 300 }; const int* p{ &a };
std::cout << *p << std::endl; p = &b; p = &c;
|
指针常量
1 2 3 4 5 6 7
| int a{ 100 }; int b{ 200 }; int* const p{ &a };
*p = 999; std::cout << a << std::endl;
|
指向常量的常量指针
1 2 3 4 5 6 7
|
const int a{ 100 }; const int b{ 200 }; const int* const p{ &a };
|
补充(指针有关的类型转换 )
1 2 3 4 5 6
| const int a{ 100 }; const int b{ 200 };
int* pa{ (int*)&a }; *p = 9500; std::cout << *pa << std::endl;
|
指针数组补充
指针数组是一个数组,数组的每个元素都是指针。
p 是一个包含5个指针的数组。 每个指针可以指向一个int类型的变量。
1 2 3 4 5 6 7 8 9
| int a = 10, b = 20, c = 30; int* p[3]; p[0] = &a; p[1] = &b; p[2] = &c;
printf("a = %d, b = %d, c = %d", *p[0], *p[1], *p[2]);
|
数组指针 是一个指针,它指向一个数组。
p 是一个指针,指向包含5个整型元素的数组。
1 2 3 4 5 6 7
| int a[5] = {1, 2, 3, 4, 5}; int (*p)[5]; p = &a;
printf("First element: %d\n", (*p)[0]); printf("Second element: %d\n", (*p)[1]);
|
数组指针是一个指针,它指向整个数组。
使用*p 解引用数组指针,可以获得数组本身, 然后可以通过下标操作访问数组元素。
数组指针在多维数组操作中非常常用。
1 2 3 4 5 6 7 8 9 10 11
| int a[5]{ 1,2,3,4,5 }; int* ptrA{ a }; int* ptrB{ &a[0] }; int* ptrC{ a + 1 };
std::cout << ptrC[1] << std::endl; std::cout << ptrA[1] << std::endl; std::cout << sizeof(a) <<std::endl; std::cout << sizeof(ptrA) <<std::endl;
|
1 2 3 4 5 6 7 8 9
| int test[2][5] { {1001,1002,1003,1004,1005}, {2001,2002,2003,2004,2005} }; int* ptest{ (int*)test }; std::cout << test[1][4] << std::endl; std::cout << ptest[9] << std::endl;
|
1 2 3 4 5 6 7 8 9 10 11 12
| int test[2][5] { {1001,1002,1003,1004,1005}, {2001,2002,2003,2004,2005} };
int* ptestA[5]; int (*ptest)[5]{test}; ptest = ptest + 1; std::cout << ptest << std::endl; std::cout << ptest[0][1] << std::endl; std::cout << sizeof(ptest) << std::endl;
|
数组再补充
1 2 3 4 5 6 7 8
| int test[2][5] { {1001,1002,1003,1004,1005}, {2001,2002,2003,2004,2005} };
std::cout << test[0] << std::endl;
|
C语言内存分配
1 2 3 4 5 6 7 8 9 10 11 12 13
| int* p = (int*)malloc(x * sizeof(int)); int* pa = (int*)calloc(x, sizeof(int)); std::cout << "------------------------" << std::endl; int* p = (int*)malloc(4); p = (int*)realloc(p, 8);
free(p); free(pa);
|
C++内存分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| int* pa = new int;
int* p = new int[5];
--------------------------------------------------- p = new int[x]; *p = 500; p[0] = 500;
delete pa; delete[] p;
--------------------------------------------------
int a[5]{1001,1002,1003,1004,1005}; int* p = new int[5]; memcpy(p,a,5*sizeof(int));
int* pa = new int[100];
memset(p,0,100*sizeof(int));
void* meeset(void* _dst, int val, size_t size)
|
使用动态内存分配风险
如果释放内存后没有清零,很危险,使用new可能会报错
重复释放
内存碎片
不推荐C语言和C++释放内存的语句混用
引用
1 2 3 4 5 6 7 8 9 10
|
int a{500}; int b{100}; int& la{a}; la = 500;
la = b;
|
智能指针
1 2 3 4 5 6 7 8 9
| std::unique_ptr<int[]> intPtr{std::make_unique<int[]>(5)}; std::unique_ptr<int> intPtrA{std::make_unique<int>(5)};
intPtr.reset();
int* a = new int[5]; a = intPtr.get();
a = intPtr.release();
|
智能指针的转移
1 2 3 4
| std::unique_ptr<int[]> intPtr{std::make_unique<int[]>(5)}; std::unique_ptr<int[]> intPtrA{};
intPtrA = std::move(intPtr);
|
共享智能指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| int* a{};
std::shared_ptr<类型> 变量名称{}; std::shared_ptr<int> ptrA{}; std::shared_ptr<int> ptrB{std::make_shared<int>(5)};
std::shared_ptr<int[]> ptrC{new int[5]{1,2,3,4,5}}; std::cout << ptrA << " " << ptrC[0] << " " << *ptrB;
std::shared_ptr<int> ptrA{std::make_shared<int>(5)}; std::shared_ptr<int> ptrB{ptrA};
std::cout << ptrB << " " << *ptrB << std::endl; std::cout << ptrA << " " << *ptrA;
long std::shared_ptr.use_count();
std::cout << ptrA.use_count() << std::endl;
std::shared_ptr.reset();
ptrB.reset(); std::cout << ptrB << " ";
|
指针和结构体
通过指针访问自定义数据类型(基础部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| typedef struct Role { int HP; int MP; }* PRole;
int main() { Role user; PRole puser = &user; puser->HP = 50; puser->MP = 100; user.HP = 50; user.MP = 50; std::cout << (*puser).HP << std::endl; std::cout << puser->HP << std::endl; }
|
内存对齐问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| typedef struct Role { char op; int HP; int MP; short x; short x1; }* PRole;
int main() { Role user; PRole puser = &user; std::cout << sizeof(Role); }
|
内存对齐是指在内存中存储数据时,数据的起始地址按特定的规则对齐,而不是任意的内存地址。
- 目的:提高 CPU 访问内存的效率,因为现代 CPU 通常按字节、字或更大单位(如 4 字节或 8 字节)读取内存。
- 结果:可能会在数据之间引入填充字节(padding),以满足对齐规则
对齐原则
每个成员的地址必须是对齐系数的整数倍。
- 对齐系数 = 成员大小 或 默认对齐数,取较小值。
结构体总大小必须是结构体最大对齐系数的倍数。
指定几个字节对齐
1 2 3 4 5 6 7 8 9 10 11
| #pragma pack(2) typedef struct { char a; int b; short c; }PackedExample; #packma pack();
printf("sizeof(PackedExample): %zu\n", sizeof(PackedExample));
|
指针安全
悬挂指针和野指针
悬挂指针: 悬挂指针产生于指针所指向的内存已被释放或者失效后,指针本身没有及时更新或清空。在该内存释放之后,任何通过这个悬挂指针的引用或操作都是不安全的,因为这块内存可能已经重新分配给了其他的数据。
示例:当一个指针指向动态分配(比如使用malloc
或new
)的内存,并且随后该内存被释放掉(使用free
或delete
),而没有将指针设置为NULL
,此时这个指针就变成了悬挂指针。
野指针: 野指针通常是指未初始化的指针,它没有被设置为任何有效的地址。由于它可能指向任意位置,对野指针的解引用是危险的,并且可能会导致难以预测的行为甚至程序崩溃。
示例:声明了一个指针变量但是没有给它赋予确定的初始值,然后就开始使用这个指针。
尽管两者看似相似,但是产生原因和解决方式有所不同:
- 悬挂指针问题可以通过确保指针在释放关联的内存资源后立即被设为
NULL
来避免。
- 野指针问题则需要确保每个指针变量在使用前都被明确初始化为一个合法的地址或
NULL
。
处理这两种类型的指针时,编程中的最佳实践是始终确保你的指针在声明后得到适当的初始化,在资源被释放之后更新状态,并且在解引用之前检查其有效性。
指针存在的俩问题:
1️⃣ 指针没有了,内存空间还在
2️⃣ 内存空间释放了,指针还有
野指针(悬挂指针)
1 2 3 4 5 6 7 8 9 10 11 12
| int main() { int* p; { int* a = new int[5]; p = a; a[2] = 555; } std::cout << p[2]; }
|
改正方法:使用 智能指针
1 2 3 4 5 6 7 8 9
| int* p; { std::unique_ptr<int[]> a{ std::make_unique<int[]>(50) }; a[2] = 250; p = a.get(); std::cout << p[2]; }
std::cout << p[2];
|
另外一种情况,存在栈里面,括号 之后栈空间没有被回收(如果分配到函数里面,那么就不会出现这种问题)
1 2 3 4 5 6 7
| int* p; { int a[5]{1, 2, 3, 4, 5}; p = a; std::cout << p[0] << std::endl; } std::cout << p[0] << std::endl;
|
内存空间释放了,但是指针还在
1 2 3 4 5 6 7 8
| int* p; { int* a = new int[5]; p = a; std::cout << p[0] << std::endl; delete[] a; } std::cout << p[0];
|
补充知识:.get()函数
1️⃣std::shared_ptr和
std::unique_ptr 在智能指针中,.get() 用于获取所管理的原始指针。返回指正指针内部所管理的原始指针,但不会更改其所有权或者生命周期管理。
2️⃣在输入流(std::istream)中,.get()用于从输入流中读取字符。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <iostream> #include <sstream>
int main() { std::istringstream input("HEllo World"); char c; while (input.get(c)) std::cout << c; return 0; }
|
堆和栈
堆的本质就是空闲内存,C++中把堆称为自由存储区,只要是你的程序加载后,没有占用空闲的内存,都是自由存储区,我们用new或者malloc申请的一块新内存区域,都是操作系统从堆上操作的。
栈是程序编译时就已经确定大小的一段内存区域,主要是用于临时变量的存储,栈的效率高于堆,但是容量有限.
汇编知识(补充)
在汇编语言中,LEA
(Load Effective Address)指令用于将一个内存地址加载到寄存器中,而不是直接访问该内存地址的值。简单来说,LEA
计算内存地址的有效值,并将其存入目标寄存器,而不是从该内存地址加载数据。