vector.size() && vector.capacity()
相关介绍
size()
- 返回容器里面当前存放的元素个数
capacity()
- 在不重新分配内存的情况下,vector或string能存放的元素的最大个数
shrink_to_fit()
- 将
capacity()
的大小改变成size()
的大小
- 将
reserve(n)
- 要求分配至少能容纳n个元素的内存空间
范围
capacity()
reserve(n)
- 仅适应于vector和string
shrink_to_fit()
- 仅适应于vector、string和deque
解析
当我们创建一个vector或string的对象时,我们可能会选择初始化它或不初始化它。我们如果不初始化它的时候,size和capacity一般都是0。当我们初始化它,或者开始往里面存放东西,那它就会被分配一块内存空间,用于存放数据元素。
一般来说,这个内存的大小是根据我们存放到容器里面的初始元素总共所占用的空间为参考,来分配的(这个分配的规则我目前不清楚)。但是清楚的是,分配的时候会多预留出一块空间以便后面使用。
分配空间当时存放在这块空间里面的元素数量,就是size()大小。而分配的整块空间,能够存放该类型的数据的最大个数,就是capacity()大小。
为什么要使用这样的分配策略?
- 首先从vector和string本身的特定出发,他们需要一块连续的内存空间来存放自己的数据。连续的内存空间,就意味着可以随机访问。同时,也意味着,无论你第一次分配了多大的空间,随着不断往里面添加东西,它总有把全部空间用光的时候,用光了,再添加,那就需要重新分配空间,把元素挪过来,添加需要新添加的元素,销毁旧的空间。
- 上文有说过,预留出一块空间以便后面使用,再结合第一点,就能看出好处就是很大程度上减少了需要重新分配空间的次数(如果不是这样的策略,那么vector或string只要里面添加了东西,就要重新分配一块更大的空间,然后把以前的元素先挪到新的空间,再添加进去新的元素。最后再销毁旧的内存空间。)这是从内存分配次数这一点出发考虑
- 另一点,从性能上考虑,减少了需要重新分配内存空间的次数,也意味着,减少了同样次数的内存操作流程(分配新空间+挪元素+添加新元素+销毁旧空间)。这样来看,上述分配策略反而是性能更好。
C/C++调用约定
WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??
__stdcall
- 被调用函数在返回前自己完成清除工作
- 函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定
__cdecl
- 调用者完成被调用函数的清除工作
- 函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。
- 实现可变参数的函数只能使用该调用约定。
__fastcall
- 被调用函数在返回前自己完成清除工作
- 用于对性能要求非常高的场合
- __fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送
thiscall
- 仅仅应用于"C++"成员函数
- this指针存放于CX寄存器,参数从右到左压
- thiscall不是关键词,因此不能被程序员指定
Socket
Server
- 创建socket环境
WSAStartup
- 创建socket
socket
- 绑定端口
bind(socket,ip包,包大小)
- ip包
- 协议(
AF_INET
) - 端口号
htons("3456")
- IP地址
0.0.0.0
- 协议(
- 监听网络端口
listen
- 等待客户端连接
accept
- 接收客户端请求
recv
- 处理客户端请求
- 处理请求逻辑
- 发送结果
send
- 关闭socket
closesocket
- 关闭socket环境
WSACleanup
Client
- 创建socket环境
WSAStartup
- 创建socket
socket
- 连接服务器
connect(socket,ip包,包大小)
- ip包
- 协议(
AF_INET
) - 端口号
htons("3456")
- IP地址
0.0.0.0
- 协议(
- 发送请求
send
- 接收服务端回复
recv
- 关闭socket
closesocket
- 关闭socket环境
WSACleanup
派生体系-转型
使用
dynamic_cast
进行转型,针对的是public
和private
dynamic_cast
是运行时处理的,并在运行时要进行运行时类型检查而有虚函数,才有虚函数表,有虚函数表,才有那些运行时类型检查的信息,因为,这些信息存放在虚函数表中。
向上转型
派生类转基类
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 |
class Base { public: virtual void test() { cout << "base" << endl; } public: int b; }; class Derived:public Base { public: virtual void test() { cout << "derived" << endl; } public: int d; }; int main() { Derived d; Base b = d;//赋值:产生切割 b.test();//base Base & b2 = d;//引用:不切割 b2.test();//derived Base * b3 = &d;//指针:不切割 b3.test();//derived } |
向下转型
派生类转基类
向下转型有可能会出现问题,在编译时不被发现
为什么?
因为可能出现一些指针或引用相关的函数调用,而指针或引用(父类),没有定义这些行为,所以导致出现未定义的行为。
如何解决这个问题?
使用虚函数
有虚函数--->有虚函数表--->表中会存在相关类型信息以及行为名称
static
修饰全局变量
- 加不加static都是静态存储方式
- 加了static,只初始化一次,防止在其他源文件中被引用
修饰局部变量
- 变成静态存储方式,变量的生命周期变长,作用范围不变
- 只初始化一次
修饰函数
- 加了static之后,函数在内存中只有一份
- 没加的话,普通函数在每个被调用中维持一份复制品
修饰类函数成员
- 表示该静态成员不属于类的实例
- 访问的时候,
类名::fun()
来访问 - 该函数内部不能访问类的其他数据成员,只能访问静态成员
const
修饰全局变量
限制全局变量的作用范围为当前源文件
1 2 3 |
//test.cpp int a = 1; const int b = 2; |
1 2 3 4 5 6 7 |
//main.cpp extern int a; int main() { cout << a << endl;//1 } |
1 2 3 4 5 6 7 |
//main.cpp extern const int b; int main() { cout << b << endl;//报错:无法解析的外部符号 } |
修饰局部变量
1 2 3 4 5 6 7 8 9 |
int main() { const int b = 2; int* p = (int*)(&b); *p = 3; cout << b << endl;//2 cout << *p << endl;//3 } //b里面存放的值被改变了,但使用变量输出的时候显示的却是初始值 |
1 2 3 4 5 6 7 8 9 |
int main() { const volatile int b = 2; int* p = (int*)(&b); *p = 3; cout << b << endl;//3 cout << *p << endl;//3 } //使用变量输出局部变量的真实值 |
修饰指针
指针自身是一个对象,它的值是所指向对象的地址,是一个整数。
- 顶层const
- 指针本身是个常量
- 底层const
- 指针指向的对象是个常量
口诀:
const星左,底被指
const星右,顶指针
解读:
1 2 3 4 5 |
int a = 1; int b = 2; const int * p1 = &a; int * const p2 = &b; //从内到外看 |
首先,p1是一个指针,指向一个int型对象,这个对象是个常量,所以p1是一个底层const
首先,p2是一个常量,然后发现p2是一个常量指针,它指向一个int型对象,所以p2是一个顶层const
修饰函数参数
- 函数参数为值传递
- 传递到函数内部是实参的一份拷贝,所以再内部再怎么改变这份拷贝的值,都不会影响实参的值
- 不需要将参数声明为const
- 函数参数为指针
- 只会进行浅拷贝,所以传递到函数内部的是一份拷贝的指针,不是原始对象
- 加上底层const来防止指向对象被篡改
- 加上顶层const来防止指针指向被篡改
- 函数参数为引用传递
- 引用就是一个别名,它不是对象。使用传递到函数内部的是就是原始对象本身
- 加上底层const来防止指向对象被修改
修饰函数返回值
令函数返回一个常量,可以有效防止因用户错误造成的意外。
修饰成员函数
- 防止成员函数修改类的数据成员的内容
mutable
修饰的数据成员可以在后置const的函数内部被修改值
多态
条件
- 调用函数的对象必须是指针或引用
- 被调用的函数必须是虚函数,且完成了函数的重写
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 |
class Person { public: virtual void func() { cout << "Person called..." << endl; } } class Child:public Person { public: virtual void func() { cout << "Child called..." << endl; } } int main() { Person p; Child c; p.func(); c.func(); } |
析构
在继承的时候,如果基类的析构是虚函数,继承下去的派生类的析构函数也会变成虚函数
为什么需要将析构函数定义成虚函数
虽然基类指针可以指向派生类,但是在delete的时候,如果调用的析构不是虚函数,系统会直接调用基类的析构函数,这个时候派生类就有一部分没有被释放,会导致内存泄漏。
抽象类
在析构函数的后面写上
= 0
,这个函数就变成了纯虚函数拥有纯虚函数的类,叫做抽象类,也叫接口类
抽象类的特定:
- 不能实例化对象
- 抽象类的派生类只有在重写了纯虚函数之后,派生类才能实例化出对象。
override && final
override
virtual void fun() override {}
用来检查函数是否重写
final
class A final {}
表示这个类不能再被继承了void fun() final {}
表示这个函数不能被重写
迭代器失效问题
添加元素
i | vector,string | vector,string存储空间被重新分配,指向容器的迭代器、指针和引用全部失效 |
ii | deque | 迭代器失效,指针和引用不失效 |
iii | list,forward_list | 全都不失效 |
删除元素
i | vector,string | 被删除元素之前的迭代器有效 |
ii | deque | 删除尾元素,尾后迭代器失效,其他情况不会失效 |
iii | list,forward_list | 全都不失效 |
单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
calss Singleton { public: static Singleton getInstance() { if(instance == nullptr) instance = new Singleton(); else return instance; } private: Singleton() {} static Singleton * instance; } |
快速排序
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 |
#include <iostream> using namespace std; voud quickSort(int begin,int end,int * arr)//【begin,end】 { if(begin < end) { int temp = arr[begin];//将区间第一个数作为基准数 int i = begin;//从左往右查找,指向当前左位置 int j = end;//从右往左查找,指向当前右位置 //不重复遍历 while(i < j) { //当右边的数大于基准数时,略过 //不满足条件跳出循环,此时j对应的元素小于基准数 while(i < j && arr[j] > temp) j--; arr[i] = arr[j]; while(i < j && arr[i] <= temp) i++; arr[j] = arr[i]; } arr[i] = temp; quickSort(begin,i-1,arr); quickSort(i+1,end,arr); } else return; } |
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//nums为有序数组 int binarySearch(int nums[],int target) { int lhs = 0; int rhs = nums.length() - 1; while(lhs <= rhs) { int mid = (lhs + rhs) / 2; if(nums[mid] == target) return mid; else if(nums[mid] < target) lhs = mid + 1; else rhs = mid - 1; } return -1; } |
设计模式分类
- 创建型
- 工厂方法模式
- 抽象工厂模式
- 单例模式
- 建造者模式
- 原型模式
- 结构型
- 适配器模式
- 装饰器模式
- 代理模式
- 外观模式
- 桥接模式
- 组合模式
- 享元模式
- 行为型
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 2020_11_2302/17
- ♥ 2022_02_24_0103/01
- ♥ 2023_02_0902/11
- ♥ 2020_11_2002/17
- ♥ 2019_11_0511/07
- ♥ 2022_02_24_0203/01