c++小点 或者python 包含部分笔测题目总结

这里写目录标题

https://blog.csdn.net/MakeYouClimax/article/details/131067525

new malloc

malloc()new 都可以用于动态分配内存,但它们具有以下不同点:

  1. new 是 C++ 中的关键字,malloc() 是 C 语言库函数。new 可以在 C++ 中直接使用,而 malloc() 需要包含头文件 <stdlib.h><cstdlib>

  2. new 可以自动计算所需的内存大小,并返回类型安全的指针(例如,int* p = new int;),而 malloc() 必须手动计算所需的内存大小,并返回 void* 类型的指针,需要进行强制类型转换后才能使用(例如,int* p = (int*)malloc(sizeof(int));)。

  3. new 可以同时进行内存分配和对象初始化操作,而 malloc() 只分配内存空间,需要对每个对象进行手动初始化(使用构造函数)。

  4. new 可以自动执行相应的析构函数进行内存释放和资源回收,而 malloc() 需要手动调用 free() 函数进行内存释放和资源回收。

  5. 在使用 new 进行动态内存分配时,如果出现内存分配失败的情况,会抛出 std::bad_alloc 异常;而 malloc() 则会返回一个空指针,需要在代码中进行判断和处理。

综上所述,尽管 malloc()new 都可以用于动态内存分配,但在 C++ 中尽量使用 newdelete,因为它们更符合 C++ 的语言特性,而且可以自动处理对象的构造和析构等操作,避免了手动初始化和释放内存时的不便和错误。

以下测试有什么顺序 单元测试 系统测试 集成测试 冒烟测试

在软件测试中,四个测试阶段的顺序通常是:单元测试、集成测试、冒烟测试和系统测试。

  1. 单元测试(Unit Testing):单元测试用于验证软件系统中最小的可测试单元是否按照预期工作。这是软件测试中最基本的测试方法,它可以尽早地发现和解决编程错误或代码缺陷。

  2. 集成测试(Integration Testing):集成测试用于验证不同模块或组件之间的交互是否正常。在进行集成测试时,已经通过单元测试的函数或模块会被集成到整个系统中进行测试。

  3. 冒烟测试(Smoke Testing):冒烟测试通常在进行详细测试之前进行,用于检查系统的基本功能是否可用,以确保系统没有严重的问题。因为冒烟测试是一种快速的测试方法,所以它通常只覆盖了系统的核心功能和最重要的场景。

  4. 系统测试(System Testing):系统测试是对整个软件系统进行的测试,以验证系统的功能和性能是否达到了用户需求和规格要求的标准。在系统测试中,测试人员会使用各种测试技术和方法,例如功能测试、性能测试、安全测试等,来评估系统的表现。

需要注意的是,这些测试阶段并没有固定的顺序,取决于具体的软件开发过程和项目需求。但是,通常情况下,单元测试、集成测试、冒烟测试和系统测试的顺序是相对稳定的。

黑盒测试

黑盒测试主要关注验证系统或软件的功能是否按照规格说明书或用户需求正确工作,而不关心内部的实现细节。在黑盒测试中,测试人员并不了解系统的内部结构、代码或算法,只根据输入和输出来评估系统是否符合预期。

在进行黑盒测试时,测试人员会根据规格说明书、用户需求或其他相关文档,设计测试用例来覆盖不同的输入条件组合。这是因为在真实的应用场景中,系统的输入往往具有多种组合方式,不同的输入可能会导致不同的行为或结果。

通过使用不同的输入条件组合,黑盒测试可以发现系统可能存在的错误、异常情况或边界条件下的问题。例如,测试人员可以针对涉及日期、时间、数据范围等不同因素的组合来设计测试用例,以确保系统在各种情况下都能正常处理。

因此,可以说黑盒测试着重于输入的条件组合,通过尝试各种可能的输入组合来验证系统的功能和行为。这有助于提高系统的稳定性和可靠性,并发现潜在的问题。

栈有容量大小的说法吗

栈的容量大小是由所使用的内存空间限制的,因此可以说栈有容量大小。

在计算机中,栈是一种数据结构,用来存储和管理函数调用、局部变量以及其他程序执行中的相关信息。栈的大小由操作系统或编程语言定义,并且在编译或运行时分配一定大小的内存空间给栈来存储数据。

栈的大小可以受到多种因素的限制,如操作系统的设置、硬件资源以及编程语言的规定等。栈的大小通常有一个上限,超过这个上限会导致栈溢出的错误。

需要注意的是,栈的容量大小是固定的,并且在程序执行期间不会动态增加或减少。因此,在使用栈时,我们需要确保所存储的数据量不会超过栈的容量限制,以避免栈溢出等问题。

3n+nlgn+n*n+8为执行复杂度,那么时间复杂度为多少

根据给定的表达式 3n + n * log(n) + n^2 + 8,我们可以求出其时间复杂度。

其中,时间复杂度表示算法执行时间与输入规模的增长关系。在给定的表达式中,我们可以看到存在三个项:3n、n * log(n)、n^2,以及一个常数项 8。

对于大O符号的表示,我们通常只保留最高阶的项,并忽略低阶项和常数项。因此,我们可以得到该表达式的时间复杂度为 O(n^2)。

这是因为在该表达式中,n^2 项的阶数最高,而其他项的阶数较小。在大规模问题中,n^2 的增长速度最快,因此它主导了算法的执行时间。

需要注意的是,计算时间复杂度时忽略了常数项和低阶项,这是因为在算法分析中更关注随着输入规模增长而引起的性能变化。因此,在时间复杂度的表示中,我们只关注最高阶项。

cache 寄存器 内存三者速度

在计算机中,cache、寄存器和内存都是在不同层次或位置的存储介质,它们的速度依次递增。具体来说:

  1. 寄存器:寄存器是CPU内部可供程序使用的最快速的存储设备,位于CPU内部,与其他存储介质相比速度最快,一般可以在一个CPU时钟周期内完成读写操作。寄存器的容量非常有限(通常只有几十个字节),故其主要作用是存放CPU中需要频繁使用的数据和指令,以提高指令执行速度。

  2. Cache缓存:Cache是CPU与RAM之间的缓存,用来存储CPU运行时经常使用的数据和指令。由于Cache位于CPU和内存之间,相对于内存来说读写速度较快,一般可以在数个CPU时钟周期内完成读写操作。Cache大小一般在几百KB~几十MB左右,分为多级Cache,L1/L2/L3等,主要是为了尽可能地提高CPU的访问速度。

  3. 内存:内存(也称主存)是计算机存储器中的一种,位于CPU和外部存储器之间,是程序在运行时存储数据和指令的主要场所。与寄存器和Cache相比,内存的容量大得多(通常为几个GB级别),但速度较慢,一般需要几十到几百个时钟周期才能完成访问操作。

综上所述,寄存器的速度最快,Cache速度次之,内存速度最慢。因此,在程序设计中,尽可能地利用寄存器、Cache等高速缓存来提高计算效率,可以有效地提高程序的运行速度。

second = Integer.valueof(10)

二维点 如何判断点在三角形内

三角形三个点
一个点看 三个三角形面积 是否等于三角形总面积

确定一个点是否在给定的三角形内,可以使用以下方法:

  1. 根据给定的三角形的三个顶点坐标(A、B、C),计算三个边的方向向量:AB、BC、CA。

  2. 对于待确定的点P,计算它和三个顶点A、B、C之间的方向向量:AP、BP、CP。

  3. 如果P在三角形内部,则满足以下条件:
    AB PA BC PB CA PC 三组叉积方向相同

    叉积方向的判断可以通过计算向量的叉积来实现。若两个向量的叉积为正值,则表示方向相同;若叉积为负值,则表示方向相反。

  4. 若满足上述条件,则说明点P在三角形内部;否则,点P在三角形外部。

这种方法基于向量的性质,在二维空间中可以很好地判断点是否在三角形内部。需要注意的是,如果考虑到三角形的边界上的点,可以将等于0的情况也视为在三角形内部。

请注意,以上方法适用于平面上的三角形。如果在三维空间中给定三角形的三个顶点和一个点,判断点是否在三角形内部,则需要使用更复杂的算法,如射线投射、点与平面的关系等。

不必事先预估链表的存储空间吗

在��用链表时,通常不需要事先预估链表的存储空间。相比于数组等静态数据结构,链表的动态性允许在运行时根据实际需要动态分配和释放节点的内存。

在使用链表时,每个节点都可以根据需要动态创建,并通过指针链接起来。这意味着可以根据实际需求逐个添加或删除节点,无需提前确定链表的大小。

这种动态特性使得链表非常适用于需要频繁插入、删除或动态变化长度的场景。例如,在实现栈、队列、图等数据结构时,链表常被用作底层数据结构。

然而,需要注意的是,链表的动态性也导致了一些额外的开销。每个节点都需要存储指针来指向下一个节点,这会增加一定的空间开销。此外,由于链表的节点是分散存储的,可能会增加访问内存的开销。

综上所述,链表的动态性使得它具有灵活的存储空间管理能力,可以根据需要进行动态分配和释放节点的内存。这种特性使得链表成为一种强大且常用的数据结构之一。

链表的空间复杂度通常是O(n),其中n是链表的长度。

是的,链表的空间复杂度与其长度成正比。

链表是一种非连续的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。相比于数组,在链表中,每个节点可以在内存中的任意位置,通过指针连接起来形成链式结构。

对于单链表而言,每个节点需要额外的指针空间来指向下一个节点,因此节点本身的存储空间外还需要一定的指针空间。当链表长度增加时,节点数也相应增加,因此链表所占用的空间与链表的长度成正比。

需要注意的是,链表的空间复杂度还取决于编程语言和实现方式。有些编程语言可能会使用额外的存储空间来记录链表的头节点或尾节点等信息,这些额外的空间也需要考虑在内。此外,如果是双向链表或循环链表等特殊类型的链表,可能会增加额外的指针空间或标记位,因此空间复杂度可能会略有不同。

总结来说,链表的空间复杂度通常是O(n),其中n是链表的长度。

5个元素找到中位数

当有5个元素时,要得到其中位数,最少需要进行几次比较取决于你使用的具体算法。以下介绍两种常用的方法:

方法1:完全排序
通过完全排序所有元素,取第3个元素作为中位数。在这种情况下,最小的比较次数是10次,即对5个元素进行完全排序所需的最小比较次数。

方法2:部分排序
该方法以部分排序的方式找出中位数。具体步骤如下:

  1. 比较任意两个元素,并将较大的元素放在右侧,较小的元素放在左侧。这样,最大的元素将是第5个元素。
  2. 再次比较剩下的4个元素,并将较大的元素放在右侧,较小的元素放在左侧。这样,第4个和第5个元素将是最大的两个元素。
  3. 比较剩下的3个元素,并将较大的元素放在右侧,较小的元素放在左侧。这样,第3个、第4个和第5个元素将是最大的三个元素。
  4. 最后,比较剩下的2个元素,并将较大的元素放在右侧,较小的元素放在左侧。这样,第2个、第3个、第4个和第5个元素将是最大的四个元素。
  5. 最后一次比较剩下的两个元素,即可确定中位数。这样,总共需要进行6次比较。

因此,使用部分排序的方法,最少需要进行6次比较才能得到5个元素的中位数。请注意,这里的最少次数是指在最坏情况下的比较次数,实际运行中可能会出现更好的情况。

deque扩容

是的,std::deque(双端队列)在需要扩容时会进行内部的重新分配和复制操作。

std::deque 是一个由多个连续的存储块(一般为固定大小的数组)组成的数据结构。当在 std::deque 的前端或后端插入新元素时,如果当前的存储块已满,则会分配一个新的存储块,并将元素插入到新的存储块中。

这种动态扩容的方式使得 std::deque 具有高效的插入和删除操作,同时避免了数据的频繁复制。但是,需要注意的是,由于 std::deque 采用的是多块存储的方式,因此在访问特定位置的元素时,可能会比 std::vector 稍慢一些。

需要注意的是,虽然 std::deque 在插入和删除操作上具有较好的性能,但它相对于 std::vectorstd::list 来说,内存使用更高。因此,在选择数据结构时,需要根据实际需求进行权衡和选择。

希望这能回答您的问题。如果您有任何其他问题,请随时提问。

find if

find(iterator beg, iterator end, value);按值查找元素,找到返回指定位置迭代器,找不到返回结束迭代器位置

vector<Person>::iterator it = find_if(v.begin(), v.end(), Greater20());

adjacent_find(iterator beg, iterator end);

// 查找相邻重复元素,返回相邻元素的第一个位置的迭代器

// beg 开始迭代器

// end 结束迭代器



bool binary_search(iterator beg, iterator end, value);

// 查找指定的元素,查到 返回true 否则false

// 注意: 在无序序列中不可用

// beg 开始迭代器

// end 结束迭代器

// value 查找的元素

二分查找法查找效率很高,值得注意的是查找的容器中元素必须的有序序列


排序算法

sort //对容器内元素进行排序 
	sort(v.begin(), v.end(), greater<int>());
random_shuffle //洗牌 指定范围内的元素随机调整次序
**random_shuffle洗牌算法比较实用,使用时记得加随机数种子
merge // 容器元素合并,并存储到另一容器中
 容器元素合并,并存储到另一容器中
dest 目标容器开始迭代器
// 注意: 两个容器必须是有序的
reverse // 反转指定范围的元素

    std::string str = "Hello, World!";

    // 反转整个字符串
    std::reverse(str.begin(), str.end());

cout

count(iterator beg, iterator end, value);


count_if(iterator beg, iterator end, _Pred);

// 按条件统计元素出现次数

// beg 开始迭代器

// end 结束迭代器

// _Pred 谓词

总结: 统计自定义数据类型时候,需要配合重载 operator==

set map 查找

find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
find(key); //查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
count(key); //统计key的元素个数

有自带的cout

transform

搬运的目标容器必须要提前开辟空间,否则无法正常搬运

transform(v.begin(), v.end(), vTarget.begin(), TransForm());

	for_each(vTarget.begin(), vTarget.end(), MyPrint());

for each

//普通函数
void print01(int val) 
{
	cout << val << " ";
}
//函数对象
class print02 
{
 public:
	void operator()(int val) 
	{
		cout << val << " ";
	}
};

//for_each算法基本用法
void test01() {

	vector<int> v;
	for (int i = 0; i < 10; i++) 
	{
		v.push_back(i);
	}

	//遍历算法
	for_each(v.begin(), v.end(), print01); 普通函数
	cout << endl;

	for_each(v.begin(), v.end(), print02());
	cout << endl;
}

声明纯虚函数不能实例化对象

是的,这是正确的。在面向对象编程中,如果一个类中包含了一个纯虚函数(即只有声明而没有实现的虚函数),那么这个类就被称为抽象类。

抽象类不能被直接实例化为对象,也就是说不能创建该类的对象。因为抽象类中至少有一个函数没有具体的实现,所以它是不完整的,无法被实例化。抽象类主要用于作为其他类的基类,为派生类提供通用的接口和规范,具体的实现由派生类去完成。

抽象类可以包含非纯虚函数和数据成员,但只要存在一个纯虚函数,该类仍然是抽象类。只有当派生类实现了基类中的所有纯虚函数,才能通过派生类来创建对象。

在C++中,将一个函数声明为纯虚函数可以使用如下语法:

virtual 返回类型 函数名(参数列表) = 0;

总结起来,纯虚函数的存在使得一个类无法实例化,需要通过派生类来进行具体实现,从而获得完整的对象。

声明了虚函数就是虚基类

不完全正确。在C++中,声明了虚函数的类并不一定是虚基类(virtual base class)。

虚函数是在基类中声明为虚拟的成员函数,用于实现多态性。它允许通过基类指针或引用调用相应派生类中的函数。这个特性是通过在成员函数声明前加上 virtual 关键字来实现的。

虚基类(virtual base class)是在多重继承中使用的一个概念。当一个类作为其他类的基类,并且被这些类的派生类以虚继承方式继承时,它被称为虚基类。虚继承可以解决多个派生类共享同一个基类实例而导致的二义性问题。

因此,一个类如果声明了虚函数,则表示这个类拥有一个虚函数,但不一定是虚基类。只有当一个类被其他类以虚继承方式继承时,它才可以成为虚基类。

需要注意的是,虚函数和虚基类是两个不同的概念,分别用于实现多态性和解决多重继承中的二义性。

list 和forward_list可以O(1)尾部插入吗

不,std::forward_list 不支持 O(1) 时间复杂度的尾部插入操作。由于 std::forward_list 是一个单向链表容器,它只能在头部进行常数时间复杂度的插入操作。

在链表中,要在尾部进行插入操作,需要先找到链表的末尾节点,然后将新元素插入到该节点之后。但是 std::forward_list 中没有直接访问尾部节点的方式,因此在尾部进行插入操作需要从头部开始遍历链表,直到达到最后一个节点才能进行插入。这导致尾部插入的时间复杂度为 O(n),其中 n 是链表的长度。

如果需要频繁进行尾部插入操作,并且对插入操作的时间复杂度有严格要求,建议使用其他支持常数时间复杂度的容器,如 std::vectorstd::deque。这些容器提供了在尾部进行常数时间复杂度的插入操作。

list 是 STL 中的双向链表容器,由于它内部的链表结构,可以在常数时间内进行尾部插入。我们可以使用 list 容器的 push_back() 成员函数来实现在链表尾部插入元素,例如:

#include <list>

int main() {
    std::list<int> mylist;
    mylist.push_back(1);  // 尾部插入元素 1
    mylist.push_back(2);  // 尾部插入元素 2
    mylist.push_back(3);  // 尾部插入元素 3
    return 0;
}

forward_list 是单向链表,我们无法直接在尾部插入元素。需要使用 insert_after() 成员函数在指定位置之后插入新元素,例如:

#include <forward_list>

int main() {
    std::forward_list<int> mylist;
    mylist.insert_after(mylist.before_begin(), 1);  // 在前驱节点后插入元素 1
    mylist.insert_after(mylist.before_begin(), 2);  // 在前驱节点后插入元素 2
    mylist.insert_after(mylist.before_begin(), 3);  // 在前驱节点后插入元素 3
    return 0;
}


insert_after() 方法是 std::forward_list 容器提供的用于在指定位置之后插入元素的方法。具体来说,insert_after() 方法接受两个参数:第一个参数是一个迭代器,表示要插入位置的前一节点;第二个参数是要插入的元素值。


例子

非常抱歉,您是正确的。我再次犯了错误。

std::forward_list 只提供了在头部插入元素的方法 push_front(),而没有直接支持在尾部插入的方法。由于单向链表的特性,要在尾部进行插入操作,需要先找到倒数第二个节点,然后在其后插入新元素。

以下是一个使用 std::forward_list 在尾部插入元素的例子:

#include <iostream>
#include <forward_list>

int main() {
    std::forward_list<int> myList;

    // 在头部插入元素
    myList.push_front(3);
    myList.push_front(2);
    myList.push_front(1);

    // 在尾部插入元素
    auto it = myList.before_begin();
    for (; std::next(it) != myList.end(); ++it) {}

    myList.insert_after(it, 4);


其实就是利用迭代器顺序到最后一个位置  他这里不等于 end 就是end前最后一个元素。
然后在这个元素后面插入4.

    // 输出链表中的元素
    for (const auto& num : myList) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

这个例子先在链表的头部插入了 3、2 和 1 这三个元素,列表里顺序为 1 2 3
然后通过迭代器 it 找到了最后一个节点,最后使用 insert_after() 方法在其后插入新元素 4。
在这里插入图片描述

设计模式6大原则

设计模式的六大原则是指在软件设计过程中,为了提高代码的可理解性、可维护性和可复用性等方面的指导原则。这些原则可以帮助开发人员设计出灵活、可扩展且易于维护的软件系统。以下是六大设计原则:

  1. 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起变化的原因。即一个类应该只负责一项职责。

  2. 开放封闭原则(Open-Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即在不修改现有代码的情况下,通过扩展来增加新的功能。

  3. 里氏替换原则(Liskov Substitution Principle, LSP):子类型必须能够替换掉它们的父类型,而程序的行为不会受到影响。简单来说,子类对象可以替换父类对象,而程序的逻辑仍然保持一致。

  4. 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖低层模块,二者都应该依赖其抽象。即面向接口编程,依赖于抽象而不是具体实现。

  5. 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该依赖它不需要的接口。应该将大接口拆分为多个专门的小接口,使用者只需关注自己需要的接口。

  6. 迪米特法则(Law of Demeter, LoD):一个对象应该对其他对象有尽可能少的了解。也就是说,一个对象应该只与其直接合作的对象进行通信,不要调用不需要的方法。

这些设计原则提供了一些指导方针,可以帮助开发人员设计出具有灵活性、可维护性和可复用性的软件系统。通过遵循这些原则,可以提高代码的质量,并使得系统更易于理解和扩展。

软件测试种类,下面描述

软件测试是保证软件质量的重要环节,它可以发现和纠正软件中的问题和错误。下面列举了一些常见的软件测试种类:

  1. 单元测试(Unit Testing):对软件的最小可测试单元(如函数、方法)进行测试,以确认其独立地达到预期的功能。

  2. 集成测试(Integration Testing):将已经通过单元测试的模块或组件集成在一起,验证它们在联合工作时是否正常交互和协同。

  3. 系统测试(System Testing):对整个软件系统进行测试,以验证系统是否符合需求和规格,并满足用户预期。

  4. 冒烟测试(Smoke Testing):进行最基本的测试,以验证系统是否能够运行,主要用于快速发现系统中的严重问题。

  5. 功能测试(Functional Testing):针对软件功能进行测试,检查系统是否按照需求规格说明书中定义的功能运行。

  6. 性能测试(Performance Testing):评估软件在不同负载下的性能表现,包括响应时间、吞吐量、并发用户数等指标。

  7. 安全测试(Security Testing):检查软件系统的安全性,发现潜在的安全漏洞和弱点,并提出相应的修复建议。

  8. 兼容性测试(Compatibility Testing):验证软件在不同操作系统、浏览器、设备和网络环境下的兼容性。

  9. 可靠性测试(Reliability Testing):测试软件在长时间运行中的稳定性和可靠性,评估系统的故障恢复能力。

  10. 用户界面测试(User Interface Testing):检查用户界面的设计、交互和可用性,确保用户友好且易于操作。

  11. 回归测试(Regression Testing):在进行软件更改或修复后,重新执行既有的测试用例,以确保新的修改没有引入新的问题。

除了上述列举的测试种类,还有许多其他类型的测试,如压力测试、灰盒测试、白盒测试、黑盒测试、敏捷测试等,可以根据具体项目和需求选择适当的测试策略和方法。

系统测试通常包括对软件系统中已修复的缺陷进行二次验证,以确保修复后的代码没有引入新的问题,并且已修复的问题没有再次出现。系统测试的目标是验证整个系统在满足需求和规格的同时,各个组件和模块之间能够正常协同工作。

lambda python

def fn(n):
    return 1 if n<2 else n*fn(n-1)

print(list(map(fn,filter(lambda n:n%3,range(6)))))  [0, 1, 2, 3, 4, 5] 每个拿出 符合条件留下 编程 1 2  4  5
[1, 2, 24, 120]                           map将第二个参数中的每个数字用于第一个函数,并返回新的迭代器对象, 然后 list组成列表

在 Python 和 C++ 中,`lambda` 是用于创建匿名函数(即没有函数名的函数)的关键字。虽然在两种语言中都可以使用 `lambda` 创建函数,但是它们的语法和用法略有不同。

Python 中的 Lambda 函数用法示例:


# 使用 lambda 创建一个简单的加法函数
add = lambda x, y: x + y

result = add(3, 5)
print(result)  # 输出结果为 8


在上面的示例中,`lambda` 后面跟着参数列表,并使用冒号分隔。冒号后面是函数体,可以是简单的表达式或函数调用。这个 lambda 表达式创建了一个接受两个参数的函数,并返回这两个参数的和。我们将这个 lambda 表达式赋值给变量 `add`,然后可以像一般函数一样使用它。

C++ 中的 Lambda 函数用法示例:


#include <iostream>

int main() {
    // 使用 lambda 创建一个简单的加法函数
    auto add = [](int x, int y) { return x + y; };

    int result = add(3, 5);
    std::cout << result << std::endl;  // 输出结果为 8

    return 0;
}


在上面的示例中,`auto` 关键字用于自动推断 lambda 表达式的类型。`[]` 括号内可以指定捕获列表,用于在 lambda 表达式中访问外部变量。在这个示例中,我们没有指定捕获列表。

与 Python 类似,C++ 中的 lambda 表达式也可以像一般函数一样调用。在这个示例中,我们将 lambda 表达式赋值给变量 `add`,然后通过调用 `add(3, 5)` 来计算两个整数的和。

需要注意的是,在 C++11 中引入了 lambda 表达式的支持。为了使用 lambda 表达式,您需要在编译时启用 C++11 或更高版本的支持。

filter()

filter() 是 Python 内置函数之一,用于过滤序列元素并返回一个可迭代对象。它接受两个参数,第一个参数是一个函数,第二个参数是一个序列。
将第二个参数中的每个对象拿出来给到第一个然后判断 筛选,1的话留下

filter() 函数可以用于任何条件的判断,不仅仅限于奇偶数判断。它可以根据你提供的函数来筛选可迭代对象中的元素。

你可以使用任何自定义函数、内置函数或 lambda 表达式作为 filter() 函数的第一个参数。这个函数负责定义筛选的条件,可以根据自己的需求来编写。

以下是一些示例,展示了 filter() 函数的不同用途:

  1. 筛选大于某个阈值的元素:
numbers = [1, 2, 3, 4, 5, 6]

threshold = 3

filtered_numbers = list(filter(lambda x: x > threshold, numbers))

print(filtered_numbers)  # [4, 5, 6]
  1. 筛选字符串中包含特定字符的元素:
fruits = ["apple", "banana", "orange", "kiwi"]

letter = "a"

filtered_fruits = list(filter(lambda x: letter in x, fruits))

print(filtered_fruits)  # ['apple', 'banana', 'orange']
  1. 筛选长度超过特定值的字符串元素:
names = ["Alice", "Bob", "Charlie", "David"]

min_length = 5

filtered_names = list(filter(lambda x: len(x) > min_length, names))

print(filtered_names)  # ['Charlie']

这些示例说明了 filter() 函数的灵活性。你可以根据需要编写不同的函数来实现各种筛选条件,并使用 filter() 函数对可迭代对象进行过滤。

map()

map() 函数是 Python 中的一个内置函数,它用于将一个函数应用到可迭代对象的每个元素上,并返回一个包含结果的新的可迭代对象。

map() 函数的语法如下:

map(function, iterable)
  • function 是一个函数或可调用对象,用于对可迭代对象的每个元素进行操作。
  • iterable 是一个可迭代对象,例如列表、元组、集合等。

下面是一个示例,说明了 map() 函数的用法:

# 定义一个列表,包含一些整数
numbers = [1, 2, 3, 4, 5]

# 使用 lambda 表达式定义一个函数,将每个元素乘以 2
double = lambda x: x * 2

# 使用 map 函数和上述函数来对列表中的每个元素进行操作
result = list(map(double, numbers))

# 输出结果
print(result)  # [2, 4, 6, 8, 10]

在这个示例中,我们使用 lambda 表达式定义了一个函数 double,该函数将每个元素乘以 2。然后,将该函数作为第一个参数传递给 map() 函数,以对列表 numbers 中的每个元素进行操作。最终,将结果转换为列表并打印输出。

需要注意的是,map() 函数返回的是一个迭代器。如果要直接查看结果,需要将其转换为列表或其他可迭代对象。

你还可以将 map() 函数与其他内置函数(例如 sum()max()min() 等)一起使用,以便进行更复杂的操作。此外,你也可以将多个可迭代对象作为参数传递给 map() 函数,只要这些可迭代对象的长度相同,即可按元素顺序进行映射。

map()完成后获得新的可迭代对象,需要list(),为什么

map() 函数执行完毕后,它返回的是一个迭代器对象,并没有被直接计算出所有元素的值。这样做是为了在处理大量数据时节省内存空间。如果你尝试直接遍历迭代器对象,你只能访问到迭代器的第一个元素,之后则失效。

因此,如果想要查看 map() 函数最终的计算结果,可以将返回的迭代器转换为列表或其他可迭代对象,这样就能够访问到 map() 函数计算出的所有元素的值,而不仅仅是第一个元素。

这里使用 list() 函数将 map() 函数的返回值转换为一个列表对象,这样就能够访问到 map() 函数计算出的所有元素的值。需要注意的是,如果可迭代对象的长度很大,可能会导致内存溢出的问题。因此,在处理大量数据时,建议使用迭代器来逐个获取元素并进行计算,而不是一次性将所有元素都加载到内存中。

lambda -> 指定返回类型 cpp

在 C++ 中,-> 是 lambda 表达式的一个可选部分,用于指定返回值类型。

当 lambda 表达式的函数体只有一条语句时,编译器通常可以根据语句的返回值类型自动推导出 lambda 的返回类型,此时可以省略 -> 部分。

例如,下面是一个使用省略了 -> 的 lambda 表达式的示例:

#include <iostream>

int main() {
    auto multiply = [](int x, int y) { return x * y; };

    int result = multiply(3, 5);
    std::cout << result << std::endl;  // 输出结果为 15

    return 0;
}

在上述示例中,lambda 表达式 multiply 接受两个整数参数,并返回这两个参数的乘积。由于 lambda 表达式只有一条语句 return x * y;,编译器可以推导出返回类型为 int,因此省略了 -> int 部分。

然而,当 lambda 表达式的函数体包含多条语句时,或者需要显式指定返回类型时,就需要使用 -> 来指定返回类型。

下面是一个显式指定返回类型的 lambda 表达式示例:

#include <iostream>

int main() {
    auto divide = [](int x, int y) -> double {
        if (y != 0) {
            return static_cast<double>(x) / y;
        } else {
            throw std::runtime_error("Divide by zero exception");
        }
    };

    double result = divide(10, 2);
    std::cout << result << std::endl;  // 输出结果为 5.0

    return 0;
}

在上述示例中,lambda 表达式 divide 接受两个整数参数,并返回它们的除法结果。由于函数体包含多条语句和条件分支,我们显式指定了返回类型为 double,并在箭头 -> 后面指定了返回类型。

总结来说,对于简单的 lambda 表达式,编译器可以自动推导返回类型,此时可以省略 -> 部分。而对于复杂的 lambda 表达式,或者需要显式指定返回类型时,需要使用 -> 来指定返回类型。

【1,2】 append extend

b1= [1,2]
b2= [1,2]
c =[3,4]
print(b1.append(c))  none
print(b2.extend(c))   none
print(b1)   
print(b2)
[1, 2, [3, 4]]
[1, 2, 3, 4]

c++ end() begin()


要使用 begin()end() 函数,您需要包含 <iterator> 头文件。这个头文件提供了用于操作迭代器的一些函数和类模板。

 int arr1[] = {1,2,3,4,5};
 int *q = end(arr1) -1;
 int *p=begin(arr1);
 printf("%d\n", *q); 5
 printf("%d\n", *p);  1

printf() 和cout

printfcout 是在 C 和 C++ 中用于输出文本的两种不同的方法。

  1. printf 是 C 语言中的标准库函数,位于 <cstdio> 头文件中。它使用格式化字符串来指定输出的格式,并将结果打印到标准输出流中。例如,printf("%d", num); 将会以十进制格式输出整数 num

  2. cout 是 C++ 的标准输出流对象,位于 <iostream> 头文件中。它使用插入运算符 << 将要输出的内容插入到流中,并自动处理类型转换和格式化。例如,cout << num; 将以默认规则输出整数 num

主要区别如下:

  • 语法:printf 使用参数列表和格式化字符串的形式,而 cout 使用插入运算符 << 连接要输出的内容。

  • 类型安全:由于 printf 是使用 C 风格的变参函数,它在类型安全性上较差。而 cout 利用 C++ 的类型系统,可以在编译阶段进行类型检查,提供更好的类型安全性。

  • 可读性和易用性:cout 使用连续的插入运算符 <<,使得代码更易读、更易理解,并且可以直接输出多种类型的数据,而不需要显式地指定格式化字符串。相比之下,printf 的格式化字符串需要进行复杂的格式控制,不太直观。

  • 扩展性:cout 是 C++ 标准库的一部分,可以进行自定义类型的输出重载。也就是说,您可以通过重载 << 运算符,为自定义类型创建输出操作符的行为。这在实现用户自定义的输出格式时非常有用。

综上所述,对于大多数简单的输出任务,使用 cout 更加方便、类型安全和易读。但如果需要更复杂的格式控制或与 C 代码进行交互,或者对性能有更高的要求,那么使用 printf 可能更合适。

两者用法案例

以下是 printfcout 的用法案例:

#include <cstdio>
#include <iostream>

int main() {
    int num = 10;

    // 使用 printf 输出整数
    printf("The number is: %d\n", num);

    // 使用 cout 输出整数
    std::cout << "The number is: " << num << std::endl;

    return 0;
}

在这个示例中,我们使用了 printfcout 分别输出一个整数 num。输出的结果是相同的,都是打印出 “The number is: 10”。

注意到使用 printf 时,需要使用格式化字符串 %d 来指定整数的输出格式,并将变量 num 作为参数传递给 printf 函数。

而使用 cout 时,我们可以直接使用插入运算符 << 将要输出的内容连接起来。std::cout 表示标准输出流对象,<< 运算符将字符串和整数依次插入到流中。最后,使用 std::endl 来结束一行并刷新输出流。

总体而言,使用 printf 需要显式地指定输出格式,并且需要使用不同的格式控制符来处理不同类型的数据。而使用 cout 相对更简单、直观,无需显式指定格式,且可以处理多种类型的数据。

printf第一个参数必须用引号括起来

是的,printf 函数的第一个参数必须用引号括起来表示格式化字符串。格式化字符串用于指定输出的格式,并且可以包含文本和格式控制符。

格式化字符串需要使用引号括起来,可以是单引号或双引号:

printf("The number is: %d\n", num); // 使用双引号括起来
printf('The number is: %d\n', num); // 使用单引号括起来(错误示例)

在上面的代码中,第一个 printf 调用使用了双引号将格式化字符串 "The number is: %d\n" 括起来,是正确的写法。而第二个 printf 调用使用了单引号,这是错误的写法,会导致编译错误。

因此,在使用 printf 时,确保将格式化字符串用引号括起来,以指定输出的格式。

python 元组 不可改,但是他的内部元素可改

是的,a=(1,[1,2]) 是一个元组。在 Python 中,元组是由多个用逗号分隔的值组成的不可变序列。元组可以包含不同类型的元素,并且可以嵌套其他数据结构。

在这个例子中,a 是一个由两个元素组成的元组。第一个元素是整数 1,第二个元素是一个列表 [1, 2]。需要注意的是,尽管元组本身是不可变的,但如果元组中包含可变对象(如列表),则可以修改可变对象的内容。

例如,可以通过索引访问和修改元组的元素:

a = (1, [1, 2])
print(a[0])  # 输出: 1

a[1][0] = 3
print(a)  # 输出: (1, [3, 2])





a=(1,[1,2])
a[1].append([2,3])
print(a[1])
[1, 2, [2, 3]]

上述代码中,先输出了元组 a 的第一个元素,然后修改了列表 a 的第一个元素,结果会反映在原始的元组中。这说明尽管元组本身不可变,但其中的可变对象是可以修改的。

scanf

scanf 是 C 语言中用于输入的函数,它可以从标准输入流(通常是键盘)中读取不同类型的数据。

对于字符串输入,scanf 函数默认会在遇到空格、制表符或换行符时停止读取。这意味着它一次只能读取一个不带空格的字符串。如果你想要读取包含空格的字符串,可以考虑使用其他函数,如 fgets

以下是一个使用 scanf 读取字符串的示例:

#include <stdio.h>

int main() {
    char str[100];
    printf("请输入一个字符串:");
    scanf("%s", str);
    printf("您输入的字符串是:%s\n", str);
    return 0;
}

运行该程序后,它会等待用户输入一个字符串,直到遇到空格、制表符或换行符为止。然后,它会将输入的字符串打印出来。

需要注意的是,scanf 在读取字符串时存在安全性问题,因为它没有指定最大长度,可能导致缓冲区溢出。为了防止这种情况,建议使用 %s 格式限定符指定字符串的最大长度,如 scanf("%99s", str),其中 99 是缓冲区的最大长度。

总之,scanf 是 C 语言中用于输入的函数,可以读取不同类型的数据。对于字符串输入,scanf 默认会在遇到空格、制表符或换行符时停止读取。为了防止缓冲区溢出,建议指定字符串的最大长度。

我输入了一串数字 1 2 3 4 5 6,需要拿scanf来进行读取吗

如果你输入的是一串数字,如 “1 2 3 4 5 6”,你可以使用 scanf 来读取这些数字。

以下是一个使用 scanf 读取一串数字并存储到数组中的示例代码:

#include <stdio.h>

int main() {
    int nums[6];
    printf("请输入一串数字(以空格分隔):");
    for (int i = 0; i < 6; i++) {
        scanf("%d", &nums[i]);
    }
    printf("您输入的数字是:");
    for (int i = 0; i < 6; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");
    return 0;
}

运行该程序后,它会等待用户输入一串数字,直到遇到空格为止。然后,它会将输入的数字存储在一个整数数组中,并打印出来。

在循环中,我们使用 %d 格式限定符来告诉 scanf 函数读取一个整数。通过循环多次调用 scanf,我们可以一次读取一个数字并存储在数组中。

需要注意的是,以上示例假设你输入的是 6 个数字,并且用空格进行分隔。如果输入的数字个数不确定,或者有其他分隔符,请根据实际情况进行适当的修改。

总之,如果你输入了一串数字,你可以使用 scanf 来读取这些数字,并将它们存储到数组中。

scanf使用例子 第二个参数需要给到地址

当我们使用 scanf 函数时,通常会根据所需读取的数据类型来构造相应的格式化字符串。以下是一些常见的示例:

  1. 读取整数:
int num;
scanf("%d", &num);

这会提示用户输入一个整数,并将其存储在 num 变量中。

  1. 读取浮点数:
float f;
scanf("%f", &f);

这会提示用户输入一个浮点数,并将其存储在 f 变量中。

  1. 读取字符串:
char str[100];
scanf("%s", str);

这会提示用户输入一个字符串,并将其存储在字符数组 str 中。

  1. 读取多个值:
int num1, num2;
scanf("%d %d", &num1, &num2);

这会提示用户输入两个整数,并将它们分别存储在 num1num2 变量中。

需要注意的是,scanf 函数会在遇到空格、制表符或换行符时停止读取。因此,在上述示例中,如果用户在输入时使用空格或换行符分隔数据,scanf 将根据格式化字符串的指定将相应的值存储在相应的变量中。

另外,为了确保输入的安全性和正确性,我们通常会在读取之前进行适当的输入验证和错误处理。例如,检查 scanf 的返回值是否与预期的输入项目数相匹配,以及处理输入错误的情况等。

cin是一次输入一个吗 可以cin>>字符串类型吗,cin等待输入一次,遇到空格停止吗

是的,C++ 中的 cin 是一次读取一个输入,并且可以使用 cin >> 来读取字符串类型。

当你使用 cin >> 读取字符串时,它会读取空格之前的字符,并将其存储为字符串。读取到空格或者遇到换行符时,cin 将停止读取,因此它默认以空格或换行符作为输入的结束标志。

以下是一个示例代码,演示了如何使用 cin >> 读取字符串:

#include <iostream>
#include <string>

int main() {
    std::string str;
    std::cout << "请输入一个字符串:";
    std::cin >> str;
    std::cout << "您输入的字符串是:" << str << std::endl;
    return 0;
}

运行该程序后,它会等待用户输入一个字符串,直到遇到空格或换行符为止。然后,它会将输入的字符串打印出来。

需要注意的是,cin >> 读取字符串时,它只会读取空格之前的部分,并且不会读取空格本身。如果你需要读取包含空格的完整行,请考虑使用 getline(cin, str) 函数来读取。

linux多进程,多线程区别,多线程不能共享全局参数吗

在 Linux 系统中,多进程和多线程是实现并发的两种不同方式。

  1. 多进程:每个进程都有自己独立的地址空间,包括代码、数据和堆栈等。多个进程之间相互独立,不共享内存,需要使用 IPC 机制来进行进程间通信。由于进程拥有独立的地址空间,多进程之间的数据共享相对复杂且开销较大。

  2. 多线程:多线程是在同一个进程的上下文中创建多个执行流,共享同一地址空间和资源。线程共享全局变量、堆内存、文件描述符等资源,线程间的通信较为方便。多线程可以通过共享数据来实现线程之间的数据共享,但也需要考虑线程安全的问题,如使用互斥锁或其他同步机制来保护共享数据的访问。

关于多线程共享全局参数的问题,多线程是可以共享全局变量和静态变量的。这是因为多线程属于同一个进程,它们共享进程的地址空间,包括全局变量和静态变量。在多线程中,多个线程可以同时访问和修改全局变量或静态变量。但需要注意,如果多个线程同时对全局变量进行写操作,可能会引发竞态条件(Race Condition)等线程安全问题,因此在多线程编程中需要注意对共享数据的访问进行合适的同步和互斥操作。

总结而言,多进程和多线程是实现并发的两种方式,多线程可以共享全局变量和静态变量,但需要注意线程安全问题。多进程之间默认情况下不共享内存,需要使用 IPC 机制进行通信。

scanf(“%d”,&x)

各个容器的结构图

vector

在这里插入图片描述

deque

在这里插入图片描述
在这里插入图片描述

queue

在这里插入图片描述

stack

在这里插入图片描述

list

在这里插入图片描述
由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器
正确,C++ 标准库中的 std::list 是一个双向链表,每个节点包含了元素的值以及指向前一个节点和后一个节点的指针。由于链表的存储方式不是连续的内存空间,std::list 的迭代器只支持前移和后移操作,属于双向迭代器。

双向迭代器支持以下操作:

  • operator++:将迭代器向前移动一个位置。
  • operator--:将迭代器向后移动一个位置。
  • operator*:返回迭代器当前位置的元素的引用。

由于 std::list 的节点之间通过指针连接而非连续的内存空间,所以不能像一维数组一样通过偏移来访问元素。相反,我们需要使用迭代器来遍历和访问 std::list 中的元素。

需要注意的是,std::list 的迭代器不支持随机访问,即不能像 std::vectorstd::array 那样通过迭代器进行索引操作。如果需要随机访问元素,更适合使用支持随机访问的容器,如 std::vector

总结而言,std::list 是基于双向链表实现的容器,其迭代器只支持前移和后移操作,属于双向迭代器。这是由于链表的存储方式决定的,它提供了灵活的插入和删除操作,但访问元素的效率相对较低。

支持随机访问

vector
string
deque

string find 和rfind

find找不到返回-1
rfind找不到返回 std::string::npos 常量 从右往左查找

if (found != std::string::npos) {

string

char * 是一个指针
string是一个类,内部封装了char*

内部有很多方法,查找find,拷贝copy,删除delete,替换replace,插入insert

创建
string str;
const char* str = "hello";
string s2(str);


string s3(s2);
string s4(10,'a'); 10个a

string str5;
str5.assign("hello c++",5); 把字符串s的前n个字符赋给当前的字符串

拼接的话有 += 和append


s.replace(1,3,"11111");
int pose = s.find("de");从左往右
int pose = s.rfind("de");从右往左


比较
int ret = s1.compare(s2);
ret ==0
ret < 1
ret > 1
访问
s[1]

插入和删除
s.insert()  str.insert(1, "111");  在指定位置插入字符串
s.erase()  erase(int pos, int n = npos)

vector

vector<int> v;
v.front();首尾元素
v.back();


v.rend() 是虚的地址,容器 v 结尾的逆向迭代器的起始位置。换句话说,它是指向容器最后一个元素的逆向迭代器的下一个位置。与 v.end() 类似,v.rend() 也是表示结尾的位置,但是它是逆向的,用于逆向遍历容器。
for (auto it = v.rbegin(); it != v.rend(); ++it) {
        std::cout << *it << " ";
    }


v.end() 是虚的地址,整个vector所占空间的末尾字节  最后一个元素之后的虚拟元素的迭代器。注意,这个迭代器没有实际的元素与之对应,它只是用来表示结尾的位置。

v.begin()是有首个元素的第一个元素的迭代器位置
v.rbegin()用于获取容器 v 的逆向迭代器的起始位置的函数。逆向迭代器是一种特殊的迭代器,它可以逆向遍历容器的元素。
//v.last() 当前最后一个元素的末尾字节 存疑

v.push_back();
v.pop_back();




拷贝构造
vector<int> v1(v2);
vector<int> v1(v2.begin(),v2.end());
vector<int> v1(10,0); 100

v1=v2;

容量和大小
v.empty() 空则为1
v.capacity() 容器的容量
v.size() 返回容器中元素的个数 resize(int num, elem)



v.resize(n,elem)   resize(len) 如果len > capacity 则两个都为len
len小于等于capacity,则capacity不变 



v.reserve()改变当前容器的最大容量 vector预留空间
reverse(len) 的值大于当前capacity 重新分配一块能存len个对象的空间,然后copy再销毁之前的空间
len小于当前capacity的话,不对容器做任何改变


v.insert(pos , elem) 迭代器指向位置pos插入元素ele
v.erase(const_iterator pos); //删除迭代器指向的元素 
erase(const_iterator start, const_iterator end);//删除迭代器从start到end之间的元素
v.clear()


访问
v[1]
v1.at(i)

v1.swap(v2); 交换两个容器的内容时,它们的大小并不需要一致。实际上,两个容器可以具有不同的大小。

vector互换容器
vector<int> v;
//收缩内存
	vector<int>(v).swap(v); //匿名对象  vector<int>(v)比方说匿名对象x,拷贝了v里的内容,然后swap容器交换,v指向了x,x指向了v,所以现在v变为了收缩内存。然后当前行执行完,编译器发现x为匿名对象,系统自动回收
比方说v push_back 10000个数字 然后capacity容量在13w左右,size在10000个
然后resize  为3 那么 size变为3,但是容量还是在13w,太浪费,此时收缩内存,那么此时都变为3





	//预留空间 减少vector在动态扩展容量时的扩展次数
	v.reserve(100000); 如果预留了空间  /容器预留len个元素长度,预留位置不初始化,元素不可访问。

	int num = 0;
	int* p = NULL;
	for (int i = 0; i < 100000; i++) {
		v.push_back(i);
		if (p != &v[0]) { 指针是否指向首地址,不是的话说明开辟内存
			p = &v[0];
			num++;
		}
	}
如果不预留空间,容量动态扩展,他会不断更换容器,重复多次。
一开始利用reverse预留空间会减少扩展次数

queue

queue<T> que; //queue采用模板类实现,queue对象的默认构造形式
queue(const queue &que); //拷贝构造函数
赋值操作:

queue& operator=(const queue &que); //重载等号操作符
数据存取:

push(elem); //往队尾添加元素
pop(); //从队头移除第一个元素
back(); //返回最后一个元素
front(); //返回第一个元素
大小操作:

empty(); //判断堆栈是否为空
size(); //返回栈的大小

pair

pair<T1,T2> p;

deque

中控器, 缓冲区

deque<int> d;
deque(beg, end); //构造函数将[beg, end)区间中的元素拷贝给本身
deque(n, elem); //构造函数将n个elem拷贝给本身
deque(const deque &deq); //拷贝构造函数   deque<int>d4 = d3;
assign()

deque没有容量的概念
判断是否为空 --- empty
返回元素个数 --- size
重新指定个数 --- resize
push_back(elem); //在容器尾部添加一个数据
push_front(elem); //在容器头部插入一个数据
pop_back(); //删除容器最后一个数据
pop_front(); //删除容器第一个数据


insert()    insert(pos,elem); d.insert(d.begin(), 1000);
erase()    d.erase(d.begin());     d.erase(d.begin(), d.end());

at(int idx); //返回索引idx所指的数据  
d.at(i)
operator[]; //返回索引idx所指的数据
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素

除了用迭代器获取deque容器中元素,[ ]和at也可以

d[i]

deque排序 sort(iterator beg, iterator end)

使用时包含头文件 algorithm即可

deque 与vector 头部插入元素

deque 是双端队列,支持在队列的两端(即队首和队尾)进行快速添加、删除元素的操作。相比之下,vector 是在尾部添加、删除元素的效率更高,而在头部插入、删除元素的效率则较低。

这是因为,vector 的底层实现是一个连续的动态数组,添加或删除元素时需要对整个数组进行移动,以保证连续性。而 deque 的底层实现是一个双端队列,是由多个固定大小的缓冲区组成的,可以在队首和队尾进行常数时间复杂度的添加、删除操作,而不需要对整个容器进行移动。另外,deque 的内存空间可以动态申请和释放,也能够避免 vector 在动态扩容时可能导致的内存分配和内存拷贝浪费。

因此,当需要在头部频繁添加、删除元素时,使用 deque 比使用 vector 更加高效。而在尾部频繁添加、删除元素时,vector 的效率更高,因为其底层实现的动态数组结构适合在尾部添加和删除元素。

map

map<string,int> p;
auto map1 = p.cbegin(); 迭代器 常量迭代器。这意味着通过 map1 迭代器只能读取容器中元素的值,而不能修改这些值。
while( map1 != p.cend())
{
	map1->first
	map1->second
	++map;
}

stack

单调栈 递增栈递减栈网上概念各种都有
单调递增栈就是从栈底到栈顶是从小到大;
单调递减栈就是从栈底到栈顶是从大到小。

stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
stack(const stack &stk); //拷贝构造函数
赋值操作:

stack& operator=(const stack &stk); //重载等号操作符
数据存取:

push(elem); //向栈顶添加元素
pop(); //从栈顶移除第一个元素
top(); //返回栈顶元素
大小操作:

empty(); //判断堆栈是否为空
size(); //返回栈的大小

list

list<int>L1;
	L1.push_back(10);
	L1.push_back(20);

assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem); //将n个elem拷贝赋值给本身。
list& operator=(const list &lst); //重载等号操作符
swap(lst); //将lst与本身的元素互换。  
L1.swap(L2);


size(); //返回容器中元素的个数

empty(); //判断容器是否为空

resize(num); //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。//如果容器变短,则末尾超出容器长度的元素被删除。

resize(num, elem); //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。



push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。

front(); //返回第一个元素。
back(); //返回最后一个元素。


reverse(); //反转链表 
 L.reverse();
sort(); //链表排序

bool myCompare(int val1 , int val2)
{
	return val1 > val2;
}
L.sort(myCompare); //指定规则,从大到小


bool ComparePerson(Person& p1, Person& p2) {

	if (p1.m_Age == p2.m_Age) {
		return p1.m_Height  > p2.m_Height;
	}
	else
	{
		return  p1.m_Age < p2.m_Age;
	}

}

对于自定义数据类型,必须要指定排序规则,否则编译器不知道如何进行排序

高级排序只是在排序规则上再进行一次逻辑规则制定,并不复杂

set multiset

所有元素都会在插入时自动被排序
set/multiset属于关联式容器,底层结构是用二叉树实现。
set不允许容器中有重复的元素
multiset允许容器中有重复的元素

随机数种子

srand((unsigned int)time(NULL));

STL(Standard Template Library,标准模板库)

STL 从广义上分为: 容器(container) 算法(algorithm) 迭代器(iterator)
容器和算法之间通过迭代器进行无缝连接。
STL 几乎所有的代码都采用了模板类或者模板函数

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
这些容器分为序列式容器和关联式容器两种
​ 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系

vector与deque扩容

在 C++ 中,当 std::vector 容器进行扩容时,一般情况下会执行移动构造(move constructor)而不是拷贝构造(copy constructor)。

std::vector 是一个动态数组,当容量不足以容纳新元素时,它会自动扩容以适应更多的元素。扩容时,原有的元素需要从当前位置移动到新的内存位置上。

移动构造是一种将对象的资源所有权从一个对象转移到另一个对象的构造方法,它通常比拷贝构造更高效。在 vector 扩容过程中,移动构造可以直接将原有元素的资源指针等信息复制给新的位置,而不需要进行逐个拷贝。这样可以减少不必要的内存拷贝操作,提高性能。

所以,当 vector 进行扩容时,一般会使用移动构造来优化性能,而不是拷贝构造。但是,移动构造只会在可移动类型(例如指针、智能指针、独占资源的对象等)中使用。对于不可移动类型(例如内置类型、基本类型等),仍然会使用拷贝构造。

需要注意的是,有些类型可能没有显式的移动构造函数,但编译器会自动生成默认的移动构造函数。在这种情况下,编译器会根据对象的成员和属性选择调用移动构造还是拷贝构造。

总结来说,当 std::vector 进行扩容时,一般会使用移动构造来优化性能,但这取决于元素类型是否支持移动构造函数。不可移动类型仍然会使用拷贝构造。

*(int*) addr

addr是地址,先是将addr转换为int类型的指针,再进行解引用, 得到的是值

需要移动

对于 std::deque,当容量不足以容纳新元素时,它会进行扩容并且分配一个新的缓冲区(buffer),然后将原有的元素从旧的缓冲区移动到新的缓冲区中。因此,在 std::deque 的扩容过程中,并不是直接在原有的缓冲区上进行操作,而是需要进行元素的移动。

std::deque 是一个双端队列,它由多个缓冲区组成,每个缓冲区称为一个块(block),并且通过指针进行连接。当 std::deque 需要扩容时,它会分配一块更大的缓冲区,并将原有的元素移动到新的缓冲区中。这样可以保证在插入和删除元素时,复杂度仍然是常数时间复杂度。

需要注意的是,std::deque 的内部实现可能有所不同,但是它的整体思想是通过缓冲区的切换和元素的移动来进行扩容。

因此,与 std::vector 不同,std::deque 在扩容时会涉及元素的移动操作,而不仅仅是简单地在新的缓冲区上进行开辟。这样的设计可以在一定程度上减少内存拷贝操作,提高性能。

向deque中间插入会导致中间任意的iterator失效

是的,对于 std::deque(双端队列)来说,向中间插入或删除元素会导致中间位置的迭代器失效。

std::deque 是一个双端队列,它允许在队列的前端和后端进行高效地插入和删除操作。由于 std::deque 内部采用了分块的数据结构,当在中间位置进行插入或删除操作时,可能需要重新分配和移动多个数据块,这会导致原本指向这些数据块的迭代器失效。

因此,如果需要在 std::deque 的中间位置进行插入或删除操作,并且需要继续使用其他位置的迭代器,需要小心处理迭代器的有效性。在插入或删除元素后,被修改的迭代器将会失效,不能再用于访问或操纵元素。通常的做法是,在进行插入或删除操作后,重新获取需要使用的迭代器。

以下是一个使用 std::deque 进行中间插入操作的示例:

#include <iostream>
#include <deque>

int main() {
    std::deque<int> deq = {1, 2, 3, 4, 5};

    auto it = deq.begin() + 2; // 指向元素 3 的迭代器

    deq.insert(it, 6); // 在元素 3 前插入元素 6

    // 输出 deque 中的元素
    for (int i : deq) {
        std::cout << i << " ";
    }
    
    return 0;
}

在上述示例中,我们使用 insert 函数在 std::deque 的中间位置插入元素。然而,需要注意的是,该操作会使之前指向元素 3 的迭代器 it 失效。因此,在进行插入或删除操作后,如果需要继续使用迭代器来访问或操纵元素,必须重新获取有效的迭代器。

请记住,在使用 std::deque 进行中间插入或删除操作时,需要小心处理迭代器的有效性,以防止出现错误的访问或操纵行为。

vector 插入操作迭代器失效

list插入迭代器不失效

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。
无论在列表的任何位置插入或删除元素,现有的迭代器仍然有效。这是因为 std::list 的底层实现使用了指针来链接节点,插入或删除节点时只需要调整指针的指向,而不需要移动其他元素。

list特点

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list的优点:

采用动态存储分配,不会造成内存浪费和溢出
链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
list的缺点:

链表灵活,但是空间(指针域) 和 时间(遍历)额外耗费较大

内联函数

在 C++ 中,inline 是一个关键字,用于声明内联函数。

内联函数是一种特殊类型的函数,编译器会将其内联展开,即将函数的代码插入到每个调用该函数的地方,而不是通过函数调用的方式进行执行。这样可以减少函数调用的开销,提高程序的执行效率。

使用 inline 关键字来声明内联函数有以下几点注意事项:

  1. 内联函数通常定义在头文件中,因为编译器需要在每个调用点展开函数的定义。
  2. 内联函数的定义必须在调用点之前,这意味着要将内联函数的定义放在使用它的地方之前,或者提供函数的前置声明。
  3. inline 关键字只是对编译器的一种建议,编译器可以选择忽略该建议并将函数作为普通函数处理。通常,编译器会根据函数的复杂性和调用频率等因素来决定是否将函数内联展开。

以下是一个使用 inline 关键字声明内联函数的示例:

#include <iostream>

inline int sum(int a, int b) {
    return a + b;
}

int main() {
    int x = 5;
    int y = 10;
    int result = sum(x, y);  // 内联展开函数的调用

    std::cout << "Sum: " << result << std::endl;

    return 0;
}

在上述示例中,sum() 函数被声明为内联函数,并在 main() 函数中进行了调用。编译器会将 sum() 函数的代码插入到调用点,而不是生成函数调用指令。

需要注意的是,对于较小且频繁调用的函数,使用 inline 可能会带来一些性能上的提升。然而,并非所有的函数都适合声明为内联函数。编译器会根据函数的特性和上下文来决定是否进行内联展开。因此,inline 关键字只是一种建议,使用时应根据实际情况进行权衡。

限制

inline的使用时有所限制的,inline只适合函数体内部代码简单的函数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。
对于内联函数来说,没有复杂的控制结构和语句是一个常见的限制。内联函数通常适用于简单的函数,不包含复杂的流程控制、循环或递归语句。

此外,内联函数通常也适用于普通的类成员函数。与普通函数一样,内联成员函数在类定义内部进行定义和声明,并且可以使用 inline 关键字进行显式声明为内联函数。内联成员函数的定义通常放在类定义的头文件中。

对于内联函数,在函数调用时通常是以内联展开的形式进行的,而不是通过函数调用的方式(也就是说,没有函数调用的开销)。当编译器遇到内联函数的调用时,会将该函数的代码插入到调用的位置,因此可以避免函数调用的开销。

与普通函数调用不同,内联函数的调用实际上是直接在调用点展开代码。这意味着内联函数的操作被嵌入到调用它的地方,而不是在程序中创建一个独立的函数调用。

由于内联函数的展开是在编译时完成的,因此在运行时不存在额外的函数调用开销。这有助于提高程序的性能,特别是对于简单的、频繁调用的函数。

需要注意的是,虽然内联函数在一定程度上可以提高性能,但并不是所有的函数都适合内联展开。编译器会根据自身的策略和限制选择哪些函数进行内联展开。因此,在使用内联函数时,应根据实际情况进行权衡和测试,以确保获得预期的性能优化效果。

vector支持O(1)首尾插入,删除

抱歉,我之前的回答有误。对于 std::vector,在特定情况下,它可以实现 O(1) 时间复杂度的首位插入和删除操作。

虽然 std::vector 的底层实现是一个动态数组,正常情况下,插入和删除元素的时间复杂度是 O(n),其中 n 是元素的数量,因为这涉及到元素的移动和内存重分配。

但是,在某些特定的场景下,如果我们知道 std::vector 不会继续增长,并且可以确保容量足够,那么我们可以通过以下方式实现 O(1) 复杂度的首位插入和删除:

  1. 在首位插入元素时,我们可以使用 std::vectorinsert 函数,将新元素插入到 begin() 的位置。由于没有元素需要移动,因此该操作只涉及于插入一个元素,时间复杂度为 O(1)。
std::vector<int> vec;
vec.insert(vec.begin(), 10);  // O(1) 复杂度的首位插入
  1. 在首位删除元素时,我们可以使用 std::vectorerase 函数,将 begin() 处的元素擦除。同样地,由于没有元素需要移动,所以该操作只涉及于删除一个元素,时间复杂度为 O(1)。
std::vector<int> vec = {10, 20, 30};
vec.erase(vec.begin());  // O(1) 复杂度的首位删除

需要强调的是,这种 O(1) 复杂度的首位插入和删除只能在特定场景下实现,且需要事先保证容量足够,并且不再增长。如果 std::vector 继续增长,仍然会涉及到元素的移动和内存重分配,时间复杂度将恢复到 O(n)。

很抱歉,之前的回答仍然有误。对于 std::vector 来说,尾部插入和删除操作的平均时间复杂度是 O(1),即可以实现常数级别的时间复杂度。

由于 std::vector 使用动态数组作为底层实现,在内存空间的末尾进行插入或删除操作时,不需要移动其他元素,只需将新元素添加到末尾或删除末尾元素,并更新数组的长度。这个操作的时间复杂度是常数级别的,即 O(1)。

以下是使用 std::vector 进行尾部插入和删除的示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    // 尾部插入操作
    vec.push_back(10);  // O(1) 时间复杂度的尾部插入
    vec.push_back(20);
    vec.push_back(30);
    
    // 尾部删除操作
    vec.pop_back();  // O(1) 时间复杂度的尾部删除
    
    // 输出 vector 中的元素
    for (int i : vec) {
        std::cout << i << " ";
    }
    
    return 0;
}

上述代码中,push_back 函数用于在尾部插入元素,它的时间复杂度是 O(1);pop_back 函数用于从尾部删除元素,同样地,它的时间复杂度也是 O(1)。

因此,在 std::vector 中,尾部插入和删除操作都能够以常数级别的时间复杂度完成。由于 std::vector 是一个非常常用且高效的容器,这一特性使得它在处理需要频繁进行插入和删除操作的场景中表现出色。

列优先排列

(5,6) 每个4

A【3】【6】

(6*6+3)*4 + 150 = 306

函数定义声明

关于默认值,参数第一个有了默认值,后面都要有。

sql语句指令

下面SQL语句中不正确的是:

  1. “insert一次只能插一句”。这个说法是不正确的。在SQL中,我们可以使用单个INSERT语句一次插入多行数据,例如使用INSERT INTO … SELECT … 语句或者使用INSERT INTO … VALUES … 多次指定值。

  2. “orderby后面要跟groupby” 这个说法是错误的。在SQL中,我们可以在SELECT语句中使用ORDER BY子句对结果进行排序,而不需要紧跟着GROUP BY子句。它们是独立的子句,可以根据需要单独使用或者同时使用。

  3. “索引独立,不删除” 这个说法是不准确的。在数据库中,索引是用来提高查询性能的数据结构,但是有时候可能会发生索引的删除操作。例如,当我们不再需要某个索引,或者要对表结构进行重构时,可能会删除索引。删除索引可以减少存储空间的使用,并且可以加快写操作的速度。

在SQL语句中,不必考虑继承关系的是 array->

  1. array 不是SQL的关键字,它是一种数据类型,用于表示数组。在SQL中,数组通常作为一个整体进行处理,不需要考虑继承关系。

  2. -> 是用于访问JSON对象的运算符。JSON数据通常以一个整体的方式存储在数据库中,不需要考虑继承关系。

而相反地,underonly 是与继承关系相关的关键字。

  • under 用于创建表的继承关系,并将子表继承父表的所有字段和约束。
  • only 用于查询时指定只查询某个表而忽略其子表。

请注意,SQL中的具体语法和功能可能因数据库管理系统而有所不同。以上回答基于一般情况下的SQL语言特性。

页面置换

https://blog.csdn.net/weixin_45990326/article/details/120031133

linux内存管理

在Linux中,内存管理是操作系统的一个重要功能。以下是关于Linux内存管理的几个重要方面:

虚拟内存:Linux使用虚拟内存来管理物理内存和进程地址空间。每个进程都有自己独立的虚拟地址空间,它通过页表映射到实际的物理内存。这种机制使得每个进程可以访问比物理内存更大的地址空间,并且能够有效地共享内存。

内存分页:Linux将物理内存划分为固定大小的页(通常为4KB),而进程的虚拟地址空间也被划分为相同大小的页。当进程访问虚拟地址时,Linux会根据页表将虚拟页映射到物理页,并进行相应的页调度。

页面置换:当物理内存不足时,Linux使用页面置换算法将某些页从内存中换出到磁盘上的交换空间,以腾出空间给其他需要的页面。常用的页面置换算法包括最近未使用(LRU)、先进先出(FIFO)等。

内存回收:Linux通过内存回收机制来释放不再使用的内存。这包括对进程释放已经退出的内存、回收页表和缓存等。

内存碎片整理:由于动态内存分配和释放,会导致内存空闲块的碎片化。Linux使用各种策略来优化内存分配,如伙伴系统(Buddy System)和SLAB分配器等,以减少内存碎片的影响。

HugeTLB(大页面):为了提高内存访问的效率,Linux支持使用大页面(通常为2MB或1GB)来减少页表的开销和TLB(翻译后援缓存)的命中率。

这些是关于Linux内存管理的一些常见说法。Linux的内存管理十分复杂且灵活,能够根据系统需求对内存进行有效的管理和优化。

哈夫曼树

带权路径长度(Weighted Path Length,WPL)是指哈夫曼树中所有叶子节点的权值乘以到根节点路径长度之和,也就是节点路径长度与其权值的乘积之和。

在这里插入图片描述

哈夫曼树(Huffman Tree),也称为最优二叉树,是一种特殊的二叉树,用于数据压缩和编码的技术中。
具有最小带权路径长度。

在这里插入图片描述

构建哈夫曼树的步骤如下:

给定一组字符及其对应的频率或权重。
将每个字符视为一个独立的节点,并根据频率或权重进行排序。
选择两个频率或权重最低的节点,创建一个新节点作为它们的父节点,新节点的频率或权重为两个节点的频率或权重之和。
将新节点放回原来的节点集合中。
重复步骤 3 和 4,直到只剩下一个节点,这个节点就是哈夫曼树的根节点。
在哈夫曼树中,频率或权重较高的节点离根节点较近,而频率或权重较低的节点离根节点较远。由于经常出现的字符采用较短的编码,而不经常出现的字符采用较长的编码,所以哈夫曼树可以实现高效的数据压缩与解压缩。

3 ,5,7,9,11

                           35
            15                         20
    8              7              9          11
 3   5

操作系统中,优先满足服务时间短且优先级高的作业是什么算法

在操作系统中,针对优先满足服务时间短且优先级高的作业,常用的调度算法是短作业优先(Shortest Job First,SJF)调度算法。

短作业优先调度算法是基于作业的估计执行时间来进行调度的。它假设作业的执行时间是已知的,并且认为执行时间较短的作业应该具有更高的优先级,以便尽快得到服务。当一个作业到达时,系统会比较其估计的执行时间,然后将执行时间最短的作业先加入到CPU执行队列中。

短作业优先调度算法可以最大限度地减少作业的平均等待时间和周转时间,提高系统的响应速度。然而,由于需要事先估计作业的执行时间,这一算法并不适用于动态环境下的实时系统或对执行时间难以估计的情况。

需要注意的是,短作业优先调度算法存在一定的缺陷,即容易导致长作业饥饿的问题。因为长作业需要等待短作业完成才能得到执行,可能会造成长作业等待时间过长。为了解决这个问题,可以采用其他调度算法或引入抢占机制,如短作业优先的抢占式调度算法(Shortest Remaining Time First,SRTF)来提高长作业的服务。

总结而言,短作业优先调度算法旨在优先满足服务时间短且优先级高的作业,通过合理安排作业的执行顺序来提高系统的效率和响应性。

FIFO算法(First-In, First-Out,先进先出算法)是一种简单的调度算法,常用于操作系统中的进程调度和页面置换。

在操作系统中,FIFO算法将任务或页面按照到达时间的顺序进行排列,每次选择最早到达的任务或页面来执行或淘汰。具体来说,当一个新的任务或页面到达时,将其加入到任务队列或页面队列的末尾。当需要选择任务执行或淘汰页面时,选择队列头部的任务或页面,执行完成后,将其从队列中移除。

FIFO算法的优点是简单易实现,但其缺点是无法根据任务的紧急程度或页面的访问频率等因素进行优化,可能导致长任务或频繁访问的页面长时间等待。

银行家算法(Banker’s Algorithm)是一种用于避免死锁的资源分配算法,常用于操作系统中对进程的资源请求和分配管理。

在银行家算法中,系统将可用的资源以及每个进程对各种资源的最大需求量进行记录。当一个进程请求资源时,系统会检查是否可以安全地满足这个请求,即对当前分配的资源和未分配的资源进行模拟分配,看是否存在安全的执行顺序,如果存在,则分配资源;否则,暂时等待,直到满足安全条件。

银行家算法的优点是能够有效避免死锁发生,保证系统的安全性;但其缺点是需要记录和维护进程的资源信息,以及进行复杂的模拟分配,可能会带来一定的开销。

总结起来,FIFO算法是一种简单的调度算法,按照先进先出的原则进行任务执行或页面置换;银行家算法是一种用于避免死锁的资源分配算法,根据当前资源状态判断是否能够安全地满足进程的资源需求。

inode

在Linux中,每个文件和目录都有一个对应的inode(索引节点),它记录了文件或目录的元数据信息。inode数据结构包含了以下主要信息:

  1. 文件类型:inode记录了文件的类型,如普通文件、目录、符号链接等。

  2. 权限和所有权:inode记录了文件的访问权限和所有者信息,包括用户ID(UID)和组ID(GID)。

  3. 大小信息:inode记录了文件的大小,以字节为单位。

  4. 时间戳:inode记录了文件的三个时间戳信息:

    • 访问时间(atime):最后一次访问文件的时间。
    • 修改时间(mtime):最后一次修改文件内容的时间。
    • 改变时间(ctime):最后一次修改inode信息(如权限、所有权)的时间。
  5. 硬链接计数:inode记录了与该inode关联的硬链接数量。当创建一个硬链接时,其指向同一inode,该计数会增加。只有当硬链接计数为0时,才会真正删除文件。

  6. 存储块的指针:inode记录了存储文件实际内容的数据块(block)的指针。这些指针可以直接指向数据块,或者通过间接块、二级间接块等方式间接指向数据块。

总之,inode记录了文件或目录的元数据信息,包括文件类型、权限、所有权、大小、时间戳、硬链接计数以及存储块的指针等。通过inode,操作系统可以有效地管理文件系统中的文件和目录。

c++11 引入新特性

C++11引入了许多新的特性,以下是其中一些主要的特性:

自动类型推断(auto关键字):允许编译器根据初始化表达式的类型自动推断变量的类型,简化了代码书写。

统一的初始化语法(初始化列表):可以使用统一的初始化语法来初始化数组、结构体、类等,更加灵活方便。

右值引用和移动语义:引入了新的引用类型"&&",支持对临时对象进行有效的移动操作,提高了程序的运行效率。

lambda表达式:可以方便地定义匿名函数,简化了代码的编写和理解。

并发编程支持库(std::thread、std::mutex等):引入了一系列的标准库组件,用于支持并发编程,包括线程、互斥量、条件变量等。

更好的内存管理(std::shared_ptr、std::unique_ptr等):引入了智能指针,用于管理动态分配的对象,避免内存泄漏和手动释放的问题。

强类型枚举:引入了新的enum class语法,使枚举更加类型安全,避免了潜在的命名冲突。

新的标准库组件:引入了一些新的标准库组件,如std::array、std::unordered_map等,提供了更丰富的数据结构和算法支持。

以上是C++11引入的一些主要新特性,它们对于提高代码的可读性、安全性和性能都有很大的帮助。当然,还有其他一些特性没有在这里列出,请参考C++11标准文档获取更全面的了解。

小根堆 大跟堆

小根堆 父节点比子节点小于等于

将数值桉树排列。从最后一个树往前。不断排序。

C++面向对象的三大特性为:封装、继承、多态

在C++中 struct和class唯一的区别就在于 默认的访问权限不同
区别:

struct 默认权限为公共

class 默认权限为私有

class C1
{
	int  m_A; //默认是私有权限
};

struct C2
{
	int m_A;  //默认是公共权限
};

int main() {

	C1 c1;
	c1.m_A = 10; //错误,访问权限是私有

	C2 c2;
	c2.m_A = 10; //正确,访问权限是公共

	system("pause");

	return 0;
}

静态成员

静态成员变量和成员函数

静态成员变量 所有对象共享一份数据 。编译阶段分配内存。类内生命,类外初始化。
静态成员函数:所有对象共享一个函数。 只能访问静态成员变量。

通过对象访问,通过类名访问

p1.m_A
Person::m_A

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量

成员变量和函数分开存储

只有非静态成员变量才属于类的对象上
函数也不占对象空间,所有函数共享一个函数实例
静态成员变量不占对象空间
静态成员函数也不占对象空间

空类 空对象占用内存空间为 1 个字节
是为了区分空对象占内存的位置
每个空对象有一个独一无二的内存地址

当你初始化了一个 int 变量 变为4字节
初始化了一个static int m 还是4 因为静态变量不在类对象上
非静态成员函数 静态对象函数都不在类对象上

构造函数和析构函数

空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

const修饰成员函数

成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:

声明对象前加const称该对象为常对象
常对象只能调用常函数

友元

友元的三种实现

全局函数做友元
类做友元
成员函数做友元

仿函数

函数调用运算符 () 也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活

继承

函数 默认参数

//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

void func(int a, int) {
	cout << "this is func" << endl;
}
func(10,10); //占位参数必须填补

函数重载

函数重载满足条件:

同一个作用域下
函数名称相同
函数参数类型不同 或者 个数不同 或者 顺序不同
注意: 函数的返回值不可以作为函数重载的条件

引用作为重载条件

函数重载碰到函数默认参数

引用

引用必须初始化
引用在初始化后,不可以改变
不要返回局部变量引用
通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
用法:函数调用作为左值

本质:引用的本质在c++内部实现是一个指针常量.

常量引用

常量引用(const reference)是一种在 C++ 中使用的引用类型。它允许我们以只读方式引用对象,并防止对被引用对象进行修改。

多种函数 多种参数的方式 值传递 地址 指针

void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

//2. 地址传递
void mySwap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3. 引用传递
void mySwap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}






//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
	ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
	int a = 10;
    
    //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
	int& ref = a; 
	ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
    
	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;
    
	func(a);
	return 0;
}



//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
	//v += 10;
	cout << v << endl;
}

int main() {

	//int& ref = 10;  引用本身需要一个合法的内存空间,因此这行错误
	//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
	const int& ref = 10;

	//ref = 100;  //加入const后不可以修改变量
	cout << ref << endl;

	//函数中利用常量引用防止误操作修改实参
	int a = 10;
	showValue(a);

	system("pause");

	return 0;
}

new开辟数组

delect [] arr


//堆区开辟数组
int main() {

	int* arr = new int[10];

	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//释放数组 delete 后加 []
	delete[] arr;

	system("pause");

	return 0;
}

结构体

总结1:定义结构体时的关键字是struct,不可省略

总结2:创建结构体变量时,关键字struct可以省略

总结3:结构体变量利用操作符 ‘’.‘’ 访问成员


struct student
{
	//成员列表
	string name;  //姓名
	int age;      //年龄
	int score;    //分数
}


struct student arr[3]=
	{
		{"张三",18,80 },
		{"李四",19,60 },
		{"王五",20,70 }
	};

swap2(int * p1, int *p2)

swap2(int * p1, int *p2) 地址传递  swap2(&a, &b); //地址传递会改变实参
用指针接收地址

 swap1(int a ,int b) 

数组名传入到函数作为参数时,被退化为指向首元素的指针

//冒泡排序函数
void bubbleSort(int * arr, int len)  //int * arr 也可以写为int arr[]
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//打印数组函数
void printArray(int arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << endl;
	}
}

int main() {

	int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
	int len = sizeof(arr) / sizeof(int);

	bubbleSort(arr, len);

	printArray(arr, len);

	system("pause");

	return 0;
}

空指针 野指针

指针变量指向内存中编号为0的空间

**用途:**初始化指针变量

**注意:**空指针指向的内存是不可以访问的

//访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
cout << *p << endl;

野指针:指针变量指向非法的内存空间
//指针变量p指向内存地址编号为0x1100的空间
int * p = (int *)0x1100;

//访问野指针报错 
cout << *p << endl;

[ ]

在 C++ 容器中,能够通过 [ ] 运算符访问元素的有以下几个:

  1. vector:vector 是一个动态数组,可以通过 [ ] 运算符访问指定下标位置的元素。例如,对于一个 std::vector<int> v,可以通过 v[0] 来访问第一个元素。

  2. string:string 是一个字符类型的容器,可以像 char 数组一样使用 [ ] 运算符访问字符串中的每一个字符。例如,对于一个 std::string str,可以通过 str[0] 来访问第一个字符。

  3. array:array 是一个固定大小的数组,同样可以使用 [ ] 运算符访问指定下标位置的元素。例如,对于一个 std::array<int, 5> arr,可以通过 arr[0] 来访问第一个元素。

需要注意的是,虽然在访问容器元素时使用 [ ] 运算符很方便,但这种方式并不会对元素越界进行检查,因此如果使用不当,可能会导致程序运行出错。因此,在使用 [ ] 运算符访问容器元素时,应确保下标值在有效范围内。如果需要对下标值进行边界检查,可以使用 at() 方法代替 [ ] 运算符。该方法会进行边界检查,并在下标越界时抛出 std::out_of_range 异常。

在 C++ 中,setmap 是关联容器,而 dequequeuestack 是序列容器。它们在访问元素方面有一些不同的特点。

  1. setmapsetmap 是基于红黑树实现的关联容器。在这两种容器中,元素是按照键值进行排序和唯一化的。因此,不能使用 [] 运算符直接访问元素。要访问 setmap 中的元素,可以使用迭代器或成员函数来实现,如 find()count()lower_bound() 等。

  2. dequedeque 是双端队列,支持在两端高效地插入和删除元素。与 setmap 不同,deque 允许使用 [] 运算符通过索引访问元素。例如,myDeque[0] 可以用于访问 deque 中的第一个元素。

  3. queuestackqueuestack 是适配器容器,它们是在底层容器(如 dequelist)的基础上提供了特定的接口。它们通常使用 push()pop() 来操作元素,而不能直接使用 [] 运算符访问特定位置的元素。

需要注意的是,dequequeuestack 这些序列容器和适配器容器,并没有提供直接通过索引访问元素的功能,而只能通过特定的成员函数和操作来进行元素的插入、删除等操作。

综上所述,可以说,对于 setmap,不能使用 [] 运算符访问元素;而 deque 可以使用 [] 运算符访问元素;queuestack 一般不支持直接通过索引访问元素,需要使用其提供的特定接口进行操作。

enum

在 C++ 中,枚举类型(enum)是一种用户自定义的数据类型,它允许我们定义一个新的类型,并将其限制为一组预先定义好的值。枚举类型的定义使用 enum 关键字,通常在命名空间或类中定义,语法如下:

enum 枚举类型名 {
    枚举值1,
    枚举值2,
    //...
};

其中,枚举类型名是自定义的标识符,可以用来定义变量、参数、函数返回值等;枚举值是该枚举类型允许的取值,可以是整型常量、表达式等。注意,枚举值必须是唯一的,并且它们的值默认从0开始连续编号,可以手动指定枚举值的数值:

enum Color {
    Red,   // 默认为0
    Green, // 默认为1
    Blue = 5 // 显式指定为5
};

在上述代码中,定义了一个 Color 枚举类型,它包含三种取值,即 Red、Green 和 Blue。其中,Red 的取值为0,Green 的取值为1,而 Blue 的取值被显式指定为5。

枚举类型变量的定义与普通类型变量的定义类似,可以使用枚举类型名作为数据类型,或者直接使用枚举值赋值。例如:

Color c1 = Red;
Color c2 = Blue;
int i = Green; // 可以将枚举类型的值赋给整型变量

枚举类型还可以用于 switch 语句中,方便地实现对一组预定义的取值进行判断。例如:

Color c = Green;
switch (c) {
    case Red:
        cout << "This is red." << endl;
        break;
    case Green:
        cout << "This is green." << endl;
        break;
    case Blue:
        cout << "This is blue." << endl;
        break;
}

总之,枚举类型是 C++ 中一种非常常用的数据类型,它允许我们自定义一组预先定义好的取值,并将其限制为一种特定的类型。枚举类型能够增加代码的可读性和可维护性,方便程序的编写和调试。

vector求最大值最小值及其下标

https://blog.csdn.net/qq_41508747/article/details/90640948

vector求最大值最小值及其下标
int maxValue = *max_element(v.begin(),v.end()); 

namespace 嵌套namespace

C++ 中的 namespace 是用于将全局命名空间进行逻辑上的划分,以避免名称冲突,提高代码的可读性和维护性。

在 C++ 中,可以嵌套 namespace,即在一个 namespace 中定义另一个 namespace。这种嵌套的 namespace 的语法形式为:

namespace outer {
  namespace inner {
    // namespace inner 中的代码
  }
}

这样,我们就可以使用 outer::inner::name 的方式访问 namespace inner 中的名称,而不会与 outer 中的名称冲突。

通过嵌套 namespace,我们可以更加方便地组织代码和命名空间,使代码结构更加清晰。例如,我们可以将一个大型项目分解为多个模块,并在每个模块中使用不同的 namespace 进行命名,以保持不同模块之间的名称不会冲突。

需要注意的是,在嵌套的 namespace 中,内层 namespace 中的名称不能直接访问外层 namespace 中的名称,除非我们使用 using 指令或者提供完整的命名空间限定符。例如,在上面的示例中,如果希望在 namespace inner 中使用 namespace outer 中的名称,可以使用如下的方式:

namespace outer {
  namespace inner {
    void func() {
      using namespace outer;
      // 可以访问 outer 中的名称
    }
  }
}

综上所述,嵌套 namespace 是 C++ 中用于组织命名空间的一种重要机制,可以提高代码的可读性和维护性。

find( , , target)

如果 nums 中的最后一个元素为 2,且 target 的值也为 2,则使用 find(nums.begin(), nums.end(), target) 查找 2 时,会返回指向最后一个元素的迭代器,即 nums.end() - 1

但是,如果 target 的值不等于 2,或者 2 不在 nums 中,则会返回 nums.end()。因为 find 函数查找元素时,会从容器的起始位置开始逐个元素地遍历,直到找到目标元素为止,如果遍历到容器的末尾还没找到目标元素,则会返回 end() 迭代器。

需要注意的是,返回的 end() 迭代器指向的是一个不存在的元素,是一个“过界迭代器”,不能进行解引用操作,否则会导致未定义行为。因此,在使用返回的迭代器时,必须先检查迭代器是否等于 end(),以确保不会访问不存在的元素。

distance()

返回迭代器在容器中位置
pos -s.begin() 似乎需要看是否是连续的内存

pos为一个迭代器
int k = distance(s.begin(),pos)

在C++中,distance()函数是一个算法函数,用于计算两个迭代器之间的距离(元素个数)。它的用法如下:

template <class InputIterator>
typename iterator_traits<InputIterator>::difference_type
    distance(InputIterator first, InputIterator last);

参数说明:

  • first:指向区间起始位置的迭代器。
  • last:指向区间结束位置的迭代器。

返回值:

  • 返回类型为 typename iterator_traits<InputIterator>::difference_type,表示两个迭代器之间的距离(以迭代器的差值类型表示)。

示例使用:

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 计算容器中的元素个数
    int count = std::distance(numbers.begin(), numbers.end());
    std::cout << "Number of elements: " << count << std::endl;

    // 使用迭代器获取第三个元素
    auto third = numbers.begin();
    std::advance(third, 2);
    std::cout << "Third element: " << *third << std::endl;

    return 0;
}

输出:

Number of elements: 5
Third element: 3

在示例中,distance() 函数用于计算容器 numbers 中的元素个数,并打印输出。另外,通过结合 advance() 函数,我们也可以移动迭代器到指定位置,如示例中获取第三个元素。

lambda语法 cpp

http://c.biancheng.net/view/3741.html
其中 capture 是捕获列表,params 是参数表,opt 是函数选项,ret 是返回值类型,body是函数体。
https://zhuanlan.zhihu.com/p/384314474

https://blog.csdn.net/u014072827/article/details/119736781

[ capture ] ( params ) opt -> ret { body; };
auto plus = [] (int a, int b) -> int { return a + b; }
int c = plus(1, 2);

把对应数字放入bitset里可以快速得出每一位的01情况

int reverseBits(int num) {
    bitset<32> bst(num);


if(num&1) r++;

相当于取最后一位是否为1

num>>=1

相当于
num=num>>1;

attribute

attribute可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性 (Type Attribute)。 gnu对于函数属性主要设置的关键字如下。
请写个函数在main函数执行前先运行
https://blog.csdn.net/MakeYouClimax/article/details/129857396

unordered_map 和map

map 所有元素都会根据元素的键值自动排序

https://blog.csdn.net/BillCYJ/article/details/78985895
unordered_map的用法和map是一样的,提供了 insert,size,count等操作,并且里面的元素也是以pair类型来存贮的。其底层实现是完全不同的,上方已经解释了,但是就外部使用来说却是一致的。

通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。哈希表详细介绍

set 和 unordered set

set实际上存储的也是键值对
只不过他的键和值必须相等,所以看起来等同于 放入了一个值

set
底层实现通常是平衡二叉树
所有元素都会根据元素的键值自动排序
set与unordered_set一样,都是关联式容器,和 map 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等。

unordered_set
底层实现通常是hash-table
元素是无序的
插入、删除、查找元素的时间复杂度是常量的(排除偶尔的rehashing导致的线性复杂度)

单调栈

栈底到栈顶是从小到大.从小到大是递增栈
栈顶先出,栈底后出
先出大的,后出小的。

单调递增栈就是从栈底到栈顶是从小到大;
单调递减栈就是从栈底到栈顶是从大到小。

递增堆栈(ascending stack):堆栈由低地址向高地址生长。

递减堆栈(secending stack):堆栈由高地址向低地址生长。

例子[1,5,6,3,4],模拟单调递增栈:

开始压入1,5,6,符合栈的单调递增性,栈内元素为{1,5,6}。
压入3,比栈顶元素6小,则6弹出,栈内元素为{1,5}。
3又比栈顶元素5小,则5弹出,栈内元素为{1}。
3比栈顶元素1大,则压入栈,栈内元素为{1,3}。
4比栈顶元素3大,则压入栈,栈内元素为{1,3,4}。

priority_queue 和 queue

queue 和 priority_queue 都属于 C++ 标准模板库(STL)中的容器,但它们有一些不同之处。

queue 是一个基本容器,它遵循先进先出(FIFO)的原则。可以使用 push() 在队尾插入元素,使用 pop() 在队首删除元素,使用 front() 访问队首元素,使用 back() 访问队尾元素。

priority_queue 也是一个容器,它可以视为一个特殊的 queue,它按照元素的优先级进行排序,具有最高优先级的元素始终处于队首位置。可以通过定义一个比较函数来指定元素之间的优先级关系。可以使用 push() 插入元素,使用 pop() 删除队首元素,使用 top() 访问队首元素。

因此,queue 和 priority_queue 的主要区别在于它们的元素处理方式和其排序特性。

它们的使用和格式有一些不同:

首先,它们的头文件不同。`queue` 的头文件是 `<queue>`,而 `priority_queue` 的头文件是 `<queue>` 加上选项 `<functional>`。

其次,在定义时需要指定容器中存储元素的类型,例如:


queue<int> q; // 定义一个存储整型数据的 queue
priority_queue<int> pq; // 定义一个存储整型数据的 priority_queue


此外,在使用 `priority_queue` 时,还需要定义一个比较函数,用于确定不同元素间的优先级关系,例如:


bool cmp(int a, int b) {
    return a > b; // 从大到小排序
}

priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); // 定义一个从小到大排序的 priority_queue


注意,这里需要使用 `decltype` 关键字来声明比较函数的类型。

最后,在插入和删除元素时也有一些不同,例如:


q.push(10); // 向 queue 中插入一个值为 10 的元素
pq.push(10); // 向 priority_queue 中插入一个值为 10 的元素


q.pop(); // 从 queue 中删除队首元素
pq.pop(); // 从 priority_queue 中删除队首元素

int x = q.front(); // 获取 queue 中的队首元素
int y = pq.top(); // 获取 priority_queue 中的队首元素


这两个定义的含义和功能是不同的。

priority_queue<int> pq; 定义了一个存储 int 类型的元素的 priority_queue,默认情况下,使用 < 运算符来确定元素之间的优先级关系。因此,该容器中存储的元素会以递增的顺序排列,即最小(权)值的元素在队首,最大(权)值的元素在队尾。

priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); 定义了一个存储 int 类型的元素的 priority_queue,使用自定义的比较函数 cmp 来确定元素之间的优先级关系,排序方式可按照程序员自己的要求进行定制。在这种情况下,需要手动定义一个比较函数 cmp,并用 decltype 关键字指定比较函数的类型。因此,在插入或删除元素时,将使用 cmp 函数来决定元素之间的优先级关系,而不是默认的 < 运算符。

综上所述,当我们需要根据自己的需求来定义元素之间的优先级关系时,可以使用第二种方法,否则可以直接使用第一种方法。

综上所述,虽然它们都属于容器,但在使用和格式上还是有一些细微的差别。

priority_queue

priority_queue 中的 greater 也是一个关系仿函数(relation function object),因为它实现了一个二元比较运算符,可以用于比较两个元素的大小关系。关系仿函数是一个函数对象,通常被用于定义容器中元素的顺序。

template<class T> bool greater<T> 也是一个关系仿函数,因为它同样实现了一个二元比较运算符,可以用于比较两个类型为 T 的元素的大小关系。它也可以用于容器的排序操作,甚至可以被其他算法(如 std::sort)使用。

虽然这两个函数对象都具有相同的功能,但是它们的实现方式和用途略有区别。priority_queue 中的 greater 实现了一个类似小于号的二元比较运算符,用于实现基于小根堆的优先队列;而 template<class T> bool greater<T> 实现的二元比较运算符可以根据需要实现不同的比较方式。

总之,两者都是关系仿函数,只是使用的上下文略有不同。

非常抱歉,我之前的回答有误,造成了混淆,感谢您的指正。

priority_queue <int,vector<int>,greater<int> > p; 中,greater<int> 确实表示从小到大排序,即将较小的元素排在队列前面。

而在 template<class T> bool greater<T> 中,这里的 greater 表示一个函数对象(函数指针),负责比较两个类型为 T 类型的元素,用于定义如何对元素进行大小比较。在这个函数对象中,operator() 的实现是返回第一个参数是否大于第二个参数,因此实现的是从大到小排序,即将较大的元素排在前面。

需要注意的是,这两个 greater 是不同的概念,虽然它们都可以用于实现优先队列的比较操作。其中,priority_queue 中的 greater 是 STL 中提供的函数对象,用于定义优先队列中元素的默认排序方式;而 template<class T> bool greater<T> 中的 greater 是用户自定义的函数对象,用于向其他算法(如 std::sort)提供自定义的比较方式。

再次感谢您的指正,希望我的回答能够更加准确地回答您的问题。

set存放自定义数据

set<Person, comparePerson> s;

set

set/multiset属于关联式容器,底层结构是用二叉树实现。
所有元素都会在插入时自动被排序
insert()
erase
set<int,MyCompare> s2;
class MyCompare

函数模板

template<typename T>
或者
template<class T>
void my(T&a, T&b)
{}
使用时可以定义好 int 类型的a和b
然后
swap(a,b) zi dong lei xing tui dao
swap<int>(a,b)  xian shi zhi ding lei xing

必须推导出一致类型的T
必须要确定T的类型

普通函数可以自动类型推导 ,比如 add(int , char)
会自动把 char类型转换为ascii 码 进行 加操作
模板函数做不到,必须要明确 int

普通与函数模板 调用规则

具体化优先于常规模板

 template<class T>
bool myCompare(T& a, T& b)


template<> bool myCompare(Person &p1, Person &p2)

类模板

template<class NameType, class AgeType>
类模板与函数模板区别主要有两点:

类模板没有自动类型推导的使用方式
Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
类模板在模板参数列表中可以有默认参数

template<class NameType, class AgeType = int> 
Person <string> p("猪八戒", 999); 

空模板参数列表来强制调用函数模板

//2、可以通过空模板参数列表来强制调用函数模板

myPrint<>(a, b); //调用函数模板

类模板成员函数创建时机

普通类中的成员函数一开始就可以创建
类模板中的成员函数在调用时才创建

举个例子, 类模板中 涉及到了另外两个其他类, 但是只能二选一
如果没有创建类模板,没有确定了, 那么编译可以通过。 如果确定了,那么只能调用确定了的那个类的函数

子类继承父类 类模板

类模板成员函数 类外实现

set_difference

set_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),inserter(s3,s3.begin()));

容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

vector

可以用vector<int> v(0,n);  n个0
v[0]
v.push_back()
vector<int>::iterator pbegin = v.begin()
取值的话 *pegbin
迭代器只能it++  it--
for_each(v.begin(), v.end(), MyPrint);
void MyPrint(int val)
{
	cout << val << endl;
}

insert()
emplace()
swap可以使两个容器互换,可以达到实用的收缩内存效果

3.2.8 vector预留空间

动态开辟内存 如果不够用了 就重新开辟,所以原来p的指针指向的也就不是首地址了,会变动

string

string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。

str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾

查找和替换

int pos = str1.find("de");
if (pos == -1)

find查找是从左往后,rfind从右往左
find找到字符串后返回查找的第一个字符位置,找不到返回-1
replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串

int ret = s1.compare(s2);  -1 0 +1 

deque

双端数组,可以对头端进行插入删除操作
它可以通过 qu[0] qu[1] 访问

deque<int>d3(10,100);
d.push_back(20);
	d.push_front(100);
d.insert(d.begin(), 2,10000);

sort(d.begin(), d.end()); d为 deque<int>d;

queue

队列
push pop back front

stack

先进后出
push pop top empty size

list

链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的
List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的。

push_back
list < xxxx> l2(l1) l2(10,1000)
list容器中不可以通过[]或者at方式访问数据
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
erase(pos);//删除pos位置的数据,返回下一个数据的位置。
remove(elem);//删除容器中所有与elem值匹配的元素。
front(); //返回第一个元素。
back(); //返回最后一个元素。

reverse(); //反转链表
sort(); //链表排序 L.sort(myCompare); //指定规则,从大到小
这里的 compare 就只是一个函数

pair

pair< string , int > p2 = make_pair ( " " , 10 )

map

按键自动排序

map<int,int>m; //默认构造
m.insert(pair<int, int>(1, 10));

erase(key); //删除容器中值为key的元素。、

函数对象

重载函数调用操作符的类,其对象常称为函数对象
函数对象使用重载的()时,行为类似函数调用,也叫仿函数
函数对象(仿函数)是一个类,不是一个函数

4.1.2 函数对象使用
特点:
函数对象在使用时,可以像普通函数那样调用, 可以有参数,可以有返回值
函数对象超出普通函数的概念,函数对象可以有自己的状态
函数对象可以作为参数传递

4.2 谓词
4.2.1 谓词概念
概念:

返回bool类型的仿函数称为谓词
如果operator()接受一个参数,那么叫做一元谓词
如果operator()接受两个参数,那么叫做二元谓词

二元谓词作排序
sort(v.begin(), v.end(), MyCompare());

4.3 内建函数对象

sort(v.begin(), v.end(), greater<int>());

内建的是需要给他模板

vector::iterator it = find_if(v.begin(), v.end(), GreaterFive());

struct GreaterFive{
	bool operator()(int val) {
		return val > 5;
	}
}//1.一元谓词;

sort(v.begin(), v.end(), MyCompare());

//二元谓词
class MyCompare
{
public:
	bool operator()(int num1, int num2)
	{
		return num1 > num2;
	}
};

std::sort(points.begin(), points.end(), cmp);
static bool cmp(vector<int> &a,vector<int> &b)
    {
       return a[1] < b[1];
    }
在这种情况下,函数名作为参数传递给 std::sort 函数时,实际上是一个指向函数的指针。指针可以像普通函数一样调用,并传递需要比较的两个元素的地址。由于编译器知道 vector<int> 的类型,因此它可以根据参数来推断出 cmp 函数模板的类型。


因此,在这个例子中,函数名 cmp 相当于指向 cmp 函数的指针,而不是函数调用。因此,在传递函数名 cmp 时,不需要加括号和模板参数。如果写成 cmp()cmp<int>() 这样的方式,则会调用函数并传递错误的参数类型,导致编译错误。
在程序中,当函数名后面不带圆括号时,它表示对该函数的指针引用,而非函数调用。因此,在给 `std::sort` 算法传递比较函数时,可以直接使用 `cmp` 来表示该函数。

具体地说,在执行 `std::sort(points.begin(), points.end(), cmp)` 时,不需要在 `cmp` 后面添加圆括号。这是因为 `cmp` 表示函数名,可以直接被转换为指向函数的指针作为排序规则传递给 `std::sort` 算法,而无需进行函数调用。

总之,在将函数作为参数传递时,可以直接使用函数名,不需要加上圆括号,除非需要调用函数。

for_each

	//遍历算法
	for_each(v.begin(), v.end(), print01); //普通函数
	cout << endl;

	for_each(v.begin(), v.end(), print02()); //函数对象
	cout << endl;

transform

相邻重复元素 adjacent_find

1左移k位 2的k次方

std::move

它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义

cpp try catch

http://c.biancheng.net/view/2330.html

字符与字符串

字符自身++

class Solution {
public:
    vector<string> cellsInRange(string s) {
        vector<string> res;
        for (char row = s[0]; row <= s[3]; ++row) {
            for (char col = s[1]; col <= s[4]; ++col) {
                string cell(1, row);
                cell.push_back(col);
                res.push_back(cell);
            }
        }
        return res;
    }
};

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/cells-in-a-range-on-an-excel-sheet/solution/excel-biao-zhong-mou-ge-fan-wei-nei-de-d-uffw/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

数字二进制串转换为字符串

    string binaryString = "011000010110001001100011";
bitset<24> bits(binaryString);
string result = bits.to_string<char,string::traits_type,string::allocator_type>();
cout << result << endl; //输出"abc"

to string 不同

string s = bitset<32>(n).to_string() 和 string s = to_string(n) 是两个不同的操作,其区别在于转换的方式和结果。

bitset<32>(n) 创建了一个 std::bitset 对象,将 n 转换为二进制形式,该二进制数值占用 32 位(不足 32 位时会自动补前导零),最后使用 to_string 成员函数将 std::bitset 对象转换成字符串类型。所以,string s = bitset<32>(n).to_string() 返回的是 n 的二进制表示字符串,字符串长度为 32(不足时会补前导零)。

而 to_string(n) 是将整数 n 直接转换为字符串,表示数值本身,无需进行二进制转换。因此,string s = to_string(n) 返回的是 n 的十进制表示字符串。

需要注意的是,bitset 需要指定位数,当 n 的数值超过 bitset 指定的位数时,将只返回低位的二进制位,并且会丢失高位信息。例如: bitset<8>(256).to_string() 只会返回 "00000000",因为 256 的二进制值为 100000000,它被截断在了低8位。

综上,string s = bitset<32>(n).to_string() 和 string s = to_string(n) 的不同在于返回的字符串类型、字符集合、长度以及要求的输入类型。根据实际需求选择不同的转换方式。

include

在C++中,#include 指令用于将头文件的内容包含到源代码文件中。它有两种形式:#include<> 和 #include""#include<> 是用来引用标准库头文件的,例如:

c++
#include <iostream>
#include <vector>
#include <string>
 #include"" 则是用来引用非标准库头文件的,例如:

c++
#include "myheader.h"
#include "myfile.hpp"
其中 "myheader.h""myfile.hpp" 是存放在当前目录下的头文件。

使用 #include<> 时,编译器会在标准库的安装路径下搜索要包含的头文件;而使用 #include"" 时,编译器会在当前文件所在的路径下搜索要包含的头文件。

需要注意的是,< > 中的头文件名是不带路径的,编译器会按照自己的规则在系统预设的目录和环境变量的路径中查找;而 "" 中的头文件名可以包含相对或绝对路径,编译器会在指定的路径中查找头文件。

函数对象

重载函数调用操作符的类,其对象常称为函数对象
函数对象使用重载的()时,行为类似函数调用,也叫仿函数

#if

#if 是 C++ 中的一个条件编译预处理指令,它用于在编译时根据给定条件决定是否编译代码。

#if 的语法格式如下:

#if constant-expression
  // code to be compiled if true
#endif

其中,constant-expression 是一个常量表达式,它的值必须在编译时能够确定。如果 constant-expression 的值为真(非零),则编译器会编译 #if#endif 之间的代码;否则这些代码将被忽略不被编译。#endif#if 的结束标记。

除了 #if 外,C++ 中还有其他几个条件编译预处理指令,例如 #ifdef#ifndef#elif 等。

#ifdef#ifndef 分别表示如果某个宏被定义或未被定义,则编译代码。例如:

#ifdef MACRO_NAME
  // code to be compiled if MACRO_NAME is defined
#endif

#ifndef MACRO_NAME
  // code to be compiled if MACRO_NAME is NOT defined
#endif

#elif 则是 #if 的扩展,可以用于同时测试多个条件。例如:

#if condition1
  // code to be compiled if condition1 is true
#elif condition2
  // code to be compiled if condition1 is false and condition2 is true
#else
  // code to be compiled if both condition1 and condition2 are false
#endif

条件编译可以使程序更具灵活性和可移植性,因为它可以根据不同的编译器、操作系统或平台等来编译代码。

umask 三位八进制

umask 三位八进制,每一位代表一个权限位(由高到低分别为用户权限、组权限和其他人权限),数字 0 表示对应权限位开放,数字 1 表示对应权限位禁用。
这是因为默认情况下,新建目录的初始权限是 777,再减去 umask 的值。

tcpP三次握手涉及到分段

TCP三次握手涉及到分段 主要涉及到客户端发送SYN分段、服务器发送SYN-ACK分段和客户端发送ACK分段这三个分段。
FIN是标志位
FIN 是 TCP(传输控制协议)中的一个标志位,表示连接的终止。FIN 是 finish(结束)的缩写。
当一方希望关闭 TCP 连接时,它发送一个带有 FIN 标志的 TCP 分段给对方,以表示它不再有数据要发送了,请求对方关闭连接。接收到带有 FIN 标志的分段后,对方会发送 ACK 分段作为确认,并继续等待数据的接收。一旦对方也没有数据要发送了,它会发送自己的 FIN 分段,然后另一方回复一个 ACK 分段,从而完成连接的正常关闭。
在 TCP 的连接终止过程中,通常会有两个 FIN 分段的交换,即每个方向都需要进行一次。这是因为 TCP 是全双工的协议,允许双方同时发送和接收数据。
通过使用 FIN 标志位,TCP 可以可靠地关闭连接,确保数据的完整性和正确性。FIN 的使用是 TCP 连接的正常终止方式之一,与三次握手建立连接类似,FIN+ACK+FIN+ACK 也构成了四次挥手过程。

磁头

磁头位于100,磁通道序号增加,循环调度,访问顺序包含什么特征。
访问顺序具有循环顺序、磁道密度均匀和无最长等待时间等特征。
从当前位置开始,先访问其后面的磁道,当到达磁盘的最大磁道时,再从0处开始循环访问。

构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造

如果用户定义拷贝构造函数,c++不会再提供其他构造函数

Person(10)单独写就是匿名对象 当前行结束之后,马上析构

不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明

当使用拷贝构造函数来初始化匿名对象时,编译器会将其视为对象声明而不是对象初始化。这种行为是为了避免与函数声明混淆。

以下是一个例子来说明这种情况:

class MyClass {
public:
    MyClass() {
        std::cout << "Default constructor called" << std::endl;
    }

    MyClass(const MyClass& other) {
        std::cout << "Copy constructor called" << std::endl;
    }
};

int main() {
    MyClass();  // 这被编译器认为是对象声明而不是对象初始化

    return 0;
}

在上述示例中,我们定义了一个类 MyClass,其中包含默认构造函数和拷贝构造函数。在 main() 函数中,我们尝试通过调用 MyClass() 来创建一个匿名对象。

然而,由于没有给匿名对象命名并将其分配给任何变量,编译器会将其视为对象声明而不是对象初始化。因此,编译器不会调用拷贝构造函数,而只会调用默认构造函数。因此,上述示例中的输出将是 “Default constructor called”。

如果我们想要调用拷贝构造函数来初始化一个匿名对象,可以使用额外的括号来明确告诉编译器这是一个对象初始化而不是对象声明,如下所示:

(MyClass());  // 这里的括号明确告诉编译器这是一个对象初始化而不是对象声明

在这种情况下,编译器将根据拷贝构造函数来初始化匿名对象,并输出 “Copy constructor called”。

初始化列表

//初始化列表方式初始化
构造函数
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}

this指针概念

this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this

	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		//返回对象本身
		return *this;
	}

返回对象引用
这意味着返回的结果是引用调用该函数的对象,并且可以继续对该对象进行操作或赋值。通常,返回引用的主要目的是为了支持链式调用(chaining),也就是说,可以在同一语句中多次调用该函数,将多个操作链接在一起。使用引用返回对象本身的好处是可以减少不必要的对象拷贝开销。通过返回引用,避免了创建临时对象并进行拷贝构造的步骤,提高了代码的效率。

不引用的话相当于会拷贝构造一个新的对象。
这个新对象是通过拷贝调用函数的对象(*this)并进行修改生成的。需要注意的是,返回对象本身还是返回新的对象取决于设计需求和具体的使用场景。使用引用返回对象本身可以节省资源和提高效率,但需要注意引用的生命周期。如果需要返回新的对象并进行进一步操作,可以选择返回对象的副本。

空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

const修饰成员函数

常函数:

成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:

声明对象前加const称该对象为常对象
常对象只能调用常函数

友元

友元的三种实现

全局函数做友元
类做友元
成员函数做友元

class Building
{
	//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
	friend class goodGay;

运算符重载

总结1:对于内置的数据类型的表达式的的运算符是不可能改变的

总结2:不要滥用运算符重载

总结:重载左移运算符配合友元可以实现输出自定义数据类型

friend ostream& operator<<(ostream& out, Person& p);

++

MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//后置++
	MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
		m_Num++;
		return temp;
	}

调用后置递增运算符时,编译器会自动区分后置递增运算符和前置递增运算符,并调用相应的重载函数。

在代码中,当使用后置递增运算符(++)时,编译器会自动生成一个临时的、虚拟的参数(通常为int类型),以区别于前置递增运算符的调用。这个参数并不需要在代码中显式提供,编译器会自动处理。

c++编译器至少给一个类添加4个函数

默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator=, 对属性进行值拷贝

赋值运算符

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

	//重载赋值运算符 
	Person& operator=(Person &p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//编译器提供的代码是浅拷贝
		//m_Age = p.m_Age;

		//提供深拷贝 解决浅拷贝的问题
		m_Age = new int(*p.m_Age);

		//返回自身
		return *this;
	}

运算符重载实验

+  加号可以两个 成员函数实现  全局函数实现
+  左移   成员函数实现不了  p << cout 不是我们想要的效果  全局函数实现左移重载
+ 递增两个都可以
MyClass operator++(MyClass& obj, int) {
    MyClass oldValue(obj);  // 创建原对象的副本
    obj.value++;  // 修改原对象
    return oldValue;  // 返回修改前的副本
}

MyClass& operator++(MyClass& obj) {
    obj.value++;
    return obj;
}

关系运算符、赋值运算符和函数调用运算符的非成员函数重载与成员函数重载类似。下面分别介绍它们的非成员函数重载方法:

  1. 关系运算符的非成员函数重载:关系运算符包括 ==!=<><=>=。可以将它们定义为非成员函数,并按照以下形式进行重载:bool operator==(const MyClass& obj1, const MyClass& obj2)。其中,MyClass 是类名,obj1obj2 是要比较的两个对象。这样定义后,可以通过直接使用关系运算符来比较两个对象之间的关系。

  2. 赋值运算符的非成员函数重载:赋值运算符 (=) 用于将一个对象的值赋给另一个对象。可以将赋值运算符定义为非成员函数,并按照以下形式进行重载:MyClass& operator=(MyClass& obj1, const MyClass& obj2)。其中,obj1 是接收赋值的对象,obj2 是提供赋值的对象。这样定义后,可以通过直接使用赋值运算符将一个对象的值赋给另一个对象。

  3. 函数调用运算符的非成员函数重载:函数调用运算符 (()) 允许对象被像函数一样进行调用。可以将函数调用运算符定义为非成员函数,并按照以下形式进行重载:ReturnType operator()(const MyClass& obj, ArgType arg)。其中,MyClass 是类名,ReturnType 是返回类型,ArgType 是函数参数类型。这样定义后,可以通过直接使用函数调用运算符来调用对象。

下面是关系运算符、赋值运算符和函数调用运算符的非成员函数重载示例:

#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int val) : value(val) {}
    int getValue() const { return value; }
};

bool operator==(const MyClass& obj1, const MyClass& obj2) {
    return obj1.getValue() == obj2.getValue();
}

MyClass& operator=(MyClass& obj1, const MyClass& obj2) {
    if (&obj1 != &obj2) {
        obj1 = obj2.getValue();
    }
    return obj1;
}

int operator()(const MyClass& obj, int arg) {
    return obj.getValue() + arg;
}

int main() {
    MyClass obj1(42);
    MyClass obj2(42);

    std::cout << "obj1 == obj2: " << (obj1 == obj2) << std::endl;  // 使用关系运算符
    obj2 = obj1;  // 使用赋值运算符
    std::cout << "obj2 value after assignment: " << obj2.getValue() << std::endl;  // 输出赋值后的结果
    int result = obj2(10);  // 使用函数调用运算符
    std::cout << "Result of function call: " << result << std::endl;  // 输出函数调用的结果

    return 0;
}

在上述示例中,我们定义了一个名为 MyClass 的类,并以非成员函数的方式重载了关系运算符 ==、赋值运算符 = 和函数调用运算符 ()

main() 函数中,我们创建了两个 MyClass 对象 obj1obj2。首先使用关系运算符 == 比较两个对象是否相等,然后使用赋值运算符将 obj1 的值赋给 obj2。最后,我们使用函数调用运算符 ()obj2 当作函数来调用,传递一个参数并输出结果。

总结:关系运算符、赋值运算符和函数调用运算符的非成员函数重载方式与成员函数重载类似,可以通过直接定义非成员函数并按照指定的命名和参数列表进行重载。通过这种方式,可以使用运算符来比较、操作或调用对象,而不需要通过成员函数来实现。

匿名对象调用

cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl; 
MyAdd() 构造匿名 后面()调用

继承

继承的语法:class 子类 : 继承方式 父类

在这里插入图片描述

内存对齐

还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。
https://blog.csdn.net/zhizhengguan/article/details/111468991

分文件编写

#include “…/touh/swap.h”
task,json中
“args”: [/
“F:/mycpp/cpp/test02.cpp”

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
构造的顺序是 :先调用对象成员的构造,再调用本类构造
析构顺序与构造相反

继承构造和析构顺序

继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

继承后size大小

父类中所有非静态成员属性都会被继承, 私有属性被编译器隐藏,但是继承了
https://blog.csdn.net/MakeYouClimax/article/details/129293620

class Base1 {
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
public:
	Base2()
	{
		m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确
	}
public:
	int m_A;
};

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1 
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;   16
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

继承

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类

虚继承

菱形继承

class Animal
{
public:
	int m_Age;
};

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

多态

多态分为两类

静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
动态多态: 派生类和虚函数实现运行时多态
静态多态和动态多态区别:

静态多态的函数地址早绑定 - 编译阶段确定函数地址
动态多态的函数地址晚绑定 - 运行阶段确定函数地址

函数重载概述
函数名可以相同,提高复用性 参数不同,顺序不同,数量不同,类型不同

多态使用

//多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表 完全一致称为重写

void DoSpeak(Animal & animal)
{
	animal.speak();
}
void test01()
{
	Cat cat;
	DoSpeak(cat);  这里就是 父类的引用 传入的是子类的对象


	Dog dog;
	DoSpeak(dog);
}

AbstractCalculator *abc = new AddCalculator;
abc = new SubCalculator;
abc = new MulCalculator;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;

多态的优点:

代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护

纯虚函数和抽象类

当类中有了纯虚函数,这个类也称为抽象类

class Base
{
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

static 作用域

在C++中,static 可以用于声明静态变量或函数。当 static 用于变量时,它具有以下两个主要的作用域概念:
静态局部变量(Static Local Variables):在函数内部使用 static 关键字声明的局部变量具有静态生存期,即它们在函数调用结束后仍然存在,并且可以记录上一次函数调用的值。这意味着每次函数被调用时,静态局部变量的值都会保留。
静态全局变量(Static Global Variables):在函数外部或文件的全局范围内使用 static 关键字声明的变量具有文件作用域。这意味着这些变量只能在声明它们的文件中使用,其他文件无法访问它们。
函数内部(static函数):在函数内部使用 在函数外部使用 static 关键字声明的函数也具有文件作用域(internal linkage),只能在定义它的源文件内可见,其他文件无法直接调用这个函数。

‘hello’ ’ hello’

在C语言中,"hello"'hello' 是不同的。

  • "hello" 是字符串字面量,它用双引号括起来表示一个字符串。它是一个字符数组,以空字符 '\0' 结尾,可以包含多个字符。
char str[] = "hello";

在这个例子中,str 是一个字符数组,其中存储了字符串 "hello"

  • 'hello' 是一个字符字面量,它用单引号括起来表示一个字符。在C语言中,字符字面量只能包含一个字符。
char ch = 'h';

在这个例子中,ch 是一个字符变量,它存储了字符 'h'

总结:"hello" 是一个字符串字面量,表示一个字符数组,而 'hello' 是一个字符字面量,表示一个字符。在C语言中,它们是不同的概念。

static加锁保护

如果在函数中使用了变量 i、j 和 k,其中 i 是一个静态变量,并且这些变量在多个线程中被访问或修改,那么需要对 i 进行加锁保护。

静态变量具有全局生存期,可以被多个同时运行的线程所访问。当多个线程同时读取或修改 i 变量时,可能会引发竞态条件(Race Condition)问题,导致结果的不确定性或错误。

a=-1;b=4;k; k=(++a<0)&&!(b–<=0)

a 0 b 4 k 0

char a[] = {'a‘,‘b’,‘c’}和char a[] = {“abc”}

不完全一样。虽然两种方式都可以用来初始化字符数组,但它们在内存中的表示略有不同。

当使用 char a[] = {'a', 'b', 'c'} 这种方式初始化字符数组时,每个字符都是独立的元素,并且以字符的ASCII码值形式存储在内存中的连续位置上。这意味着 'a''b''c' 是独立的字符。

而当使用 char a[] = "abc" 这种方式初始化字符数组时,编译器会在字符串末尾自动添加一个空字符('\0')作为字符串的结束符。因此,实际上内存中存储的是 'a''b''c''\0' 这4个字符,它们占据了连续的内存空间。这样的字符数组被视为一个以 null 结尾的字符串。

总结起来,两种方式的区别在于是否在末尾附加了空字符 '\0',并且这会影响到后续对字符数组进行处理的方式。

cin cout

在C++中,std::cout用于标准输出,通常将输出内容打印到屏幕上,而std::cin用于标准输入,通常从键盘接收输入。

ans 1

 int w=1;
 int x =2;
 int y =3;
 int z=4;
 cout<< (w<x?w:y < z?y:z )<< endl; 

内存连续

栈区内存和堆区内存在概念上是不同的,它们具有不同的特点。

栈区内存:

  • 栈区内存用于存储局部变量和函数调用的上下文信息。
  • 栈区内存是由编译器自动分配和释放的,其分配和释放遵循栈的先进后出原则(LIFO)。
  • 栈区内存通常以固定大小进行分配,内存分配和释放速度很快。
  • 栈区内存的分配和释放是由编译器自动管理的,程序员无需手动操作。

堆区内存:

  • 堆区内存用于动态分配和释放内存空间,通常用于存储动态创建的对象和数据结构。
  • 堆区内存的分配和释放是由程序员手动操作的,需要使用特定的函数(如malloc、new)进行分配,并使用相应的函数(如free、delete)进行释放。
  • 堆区内存的分配和释放没有固定规则,可以在任何时候进行,可以灵活地分配和释放不同大小的内存块。
  • 堆区内存的分配和释放速度比栈区内存慢,因为需要考虑内存的管理和回收机制。

由于栈区内存和堆区内存的分配和释放机制不同,因此它们的内存分布也不同。

栈区内存中的变量和函数调用记录是连续存储的,每次函数调用时会在栈上创建一个新的栈帧,栈帧按照特定的方式进行管理。

而堆区内存中的对象和数据结构是通过动态分配的,其分配的内存块不一定是连续的。堆区内存的分配和释放由程序员手动操作,可以根据需要分配和释放不同大小的内存块,因此堆区内存的布局是动态的。

所以,栈区内存是连续的,而堆区内存不一定是连续的。

deque内存连续吗

不,std::deque(双端队列)的内存布局不一定是连续的。与 std::vector 不同,std::deque 的内部结构是由多个固定大小的缓冲区组成的,每个缓冲区可以存储多个元素。这样设计是为了实现高效的插入和删除操作。

std::deque 通过使用一个中央控制块(central control block)以及一系列的缓冲区来管理元素。中央控制块是一个指向缓冲区的指针数组,每个缓冲区都是一个连续的内存块,用来存储一定数量的元素。

当需要在 std::deque 的前端或后端插入或删除元素时,它会根据缓冲区的状态来进行调整。如果当前的缓冲区已满或空,std::deque 会分配一个新的缓冲区,并将其添加到中央控制块中,从而支持新的插入或删除操作。

这种内存布局使得 std::deque 具有动态扩展和收缩的能力,同时也允许在任意位置进行高效的插入和删除操作。然而,由于缓冲区不一定是连续的,因此 std::deque 不支持像指针算术或直接使用指针访问元素的操作。

总之,std::deque 的内存布局是由多个固定大小的缓冲区组成的,而不一定是连续的。这样的设计使得 std::deque 在高效支持插入和删除操作的同时,能够动态地管理内存。

queue内存连续

在 C++ 标准库中,std::queue 是由 std::deque(双端队列)实现的适配器容器,默认情况下不是连续的内存。

std::deque 是一个双端队列,其内部使用一系列连续的固定大小的缓冲区来存储元素,每个缓冲区都是独立分配的。当需要扩展队列的容量时,std::deque 会分配新的缓冲区,并将其链接到已有的缓冲区链表上,从而支持高效的在两端进行插入和删除操作。

std::queue 是一个队列容器的适配器,它通过封装底层容器(默认为 std::deque,也可以是 std::liststd::vector)来提供队列的功能。它只暴露了队列的特定接口,并限制了可用的操作。

因此,虽然 std::deque 在内部使用一系列连续的缓冲区来存储元素,但 std::queue 并没有暴露这种内部细节,因此我们不能假设 std::queue 的内存是连续的。对于 std::queue 来说,重要的是它提供了队列的特定操作,无论底层容器是如何实现的。

vector内存连续

是的,C++ 标准库中的 std::vector 是使用连续的内存来存储元素的。

std::vector 是一个动态数组,它在内部使用连续的内存块来存储元素。当元素数量超过当前分配的内存空间时,std::vector 会重新分配更大的内存块,并将现有元素复制到新的内存空间中。

由于内存是连续的,std::vector 支持高效的随机访问和迭代器遍历。这使得 std::vector 成为一种常用的容器,特别适用于需要快速访问元素的场景。

需要注意的是,在添加或删除元素时,如果导致 std::vector 的大小发生变化,可能会触发内存重新分配和元素复制操作,这可能会带来一些开销。如果频繁地改变容器的大小,可能会影响性能。为了避免频繁的内存重新分配,可以使用 reserve 函数提前预留足够的空间,或者在插入大量元素之前使用 emplace_backpush_back 以避免多次重分配。

总结而言,std::vector 在内部使用连续的内存来存储元素,这使得它能够提供高效的随机访问和迭代,但在添加或删除元素时需要注意可能触发的内存重新分配操作。

stack内存连续吗

在 C++ 标准库中,std::stack 并不直接管理内存,它是一个容器适配器,用于在底层容器的基础上提供栈(先进后出)的功能。

默认情况下,std::stack 使用 std::deque 作为其底层容器。与 std::vector 不同,std::deque 并不要求元素在内存中是连续存储的。std::deque 内部使用多个缓冲区来存储元素,每个缓冲区都可以独立分配。

虽然 std::deque 不要求元素内存连续,但对于 std::stack 来说,它隐藏了底层容器的实现细节,并提供了栈的操作接口。因此,我们不能假设 std::stack 的内存是连续的。

需要注意的是,如果你选择使用其他容器作为 std::stack 的底层容器(如 std::vectorstd::list),那么底层容器的内存连续性将由具体的容器决定。例如,如果使用 std::vector 作为底层容器,则 std::stack 的内存将是连续的,因为 std::vector 使用连续的内存存储元素。

综上所述,std::stack 自身并不保证底层容器的内存连续性,而是依赖于所选择的底层容器的特性。

define 阶段

预处理阶段
#define 是预处理阶段(Preprocessing Phase)中的一个预处理指令。在编译程序的过程中,源代码会经过多个阶段的处理,其中包括预处理阶段、编译阶段、汇编阶段和链接阶段。

预处理阶段是编译过程的第一个阶段,在其之前没有实际的编译操作发生。在这个阶段,预处理器会对源代码进行扫描,并根据预处理指令做相应的处理。#define 是预处理器提供的一个指令,用于定义宏(Macro)。

当程序中使用 #define 来定义宏时,在预处理阶段,预处理器会将宏的名称替换为宏定义中的内容。这种替换是文本级别的替换,没有类型检查和语法分析。预处理阶段在源代码被编译之前进行,它的主要目的是对源代码进行一些文本替换和简单的文本操作。

因此,#define 的处理发生在预处理阶段。在编译阶段之前,通过宏定义可以在源代码中进行文本替换,方便进行宏展开和代码重用。

编辑 编译 链接 运行

C++ 代码的编写与编译一般分为以下四个步骤:

  1. 编写源代码:使用文本编辑器等工具编写 C++ 源代码(后缀名通常为 .cpp)。
  2. 编译源代码:将源代码编译成机器可执行代码的过程,即将 C++ 源文件编译成可执行程序。这个过程通常分为预处理、编译和汇编三个阶段,可使用 C++ 编译器完成。在 Windows 平台上,常用的编译器有 Microsoft Visual C++、Clang 和 GCC 等。
  3. 链接可执行文件:编译完成后,需要将编译生成的目标文件链接到一起形成一个单独的可执行文件。这个过程称为链接(Linking)或连接(Linkage),可以使用链接器完成。在 Windows 平台上,常用的链接器有 Microsoft Visual C++ 工具集中的 link.exe、Clang 和 GCC 的 GNU ld 等。
  4. 运行可执行文件:将生成的可执行文件运行起来,即可看到程序的运行结果。

总的来说,编写 C++ 代码的流程是:编写源代码 -> 编译源代码 -> 链接可执行文件 -> 运行可执行文件。

字符串面值

在C++中,字符串字面值(string literals)通常被编译器视为常量字符数组,并且相同的字符串字面值可能会被优化为同一个地址。

char str1[] = "aa";
char str2[] = "aa";
const char str3[] = "aa";
const char str4[] = "aa";
const char * str5 = "aa";
const char * str6 = "aa";


cout<<(str1==str2) <<endl;  0
cout<<(str3==str4) <<endl; 0
cout<<(str5==str6 )<<endl; 1

运算符顺序 int x=11; (x++ * 1/3)

先乘再除
在这里插入图片描述

n个数选出最大m个最小的数,其时间复杂度为

对于选出最大的 m 个最小数的问题,一种常见的解决方案是使用堆(Heap)数据结构。具体步骤如下:

  1. 构建一个大小为 m 的最大堆(Max Heap)。
  2. 遍历 n 个数并将每个数与堆顶元素进行比较:
    • 如果当前数比堆顶元素小,则将堆顶元素替换为当前数,并进行堆的调整操作。
    • 如果当前数比堆顶元素大,则忽略当前数,继续遍历下一个数。
  3. 遍历完所有数后,最大堆中的元素即为最小的 m 个数。

时间复杂度分析:

  • 堆的构建过程需要 O(m) 的时间复杂度。
  • 遍历 n 个数并进行堆的插入和调整操作,每次操作的时间复杂度为 O(log m)。
  • 因此,插入和调整操作总共需要 O(n log m) 的时间复杂度。
  • 最终的时间复杂度为 O(m + n log m)。

请注意,以上分析基于使用二叉堆实现的最大堆。如果使用其他实现方式,如斐波那契堆,可能会得到更好的时间复杂度。

前序 中序 后序 二叉树

出栈 入栈

1 2 3 4 5 6
出栈

100 二分法最多几次

int a[] 合法

int a[3] = {0, 1, 2, 3};,它是不合法的。

int a[] = {} 合法 实际上是合法的。这将创建一个大小为 3 的整数数组 a,并将所有元素初始化为 0int a[] = {1};

int a[3]={};

对于第一行代码 int a[] = {1};,这是合法的。这将创建一个大小为 1 的整数数组 a,并将第一个元素初始化为 1。

对于第二行代码 int a[3]={};,也是合法的。这将创建一个大小为 3 的整数数组 a,并将所有元素初始化为 0

const可以用于重载吗

在 C++ 中,const 修饰符可以用于函数重载,可以根据参数是否为 const 来重载同名函数。

具体来说,如果两个函数的名称、参数类型和数量都相同,但其中一个函数的参数是 const 类型,那么它们就是两个不同的函数,可以被重载。例如:

void foo(int x);  // 第一个函数
void foo(const int x);  // 第二个函数,接受 const int 类型的参数

// 可以调用两个函数,它们是不同的函数
foo(123);
foo(static_cast<const int>(456));

在上述代码中,foo(int x)foo(const int x) 是两个不同的函数,可以通过参数是否为 const 来进行重载。

需要注意的是,只有参数是否为 const 是不够的,两个函数必须在参数类型和数量上有显式的不同之处,才能够进行重载。例如:

void bar(int& x);  // 第一个函数
void bar(const int& x);  // 第二个函数,接受 const int& 类型的参数

// 下面的代码会产生编译错误,因为重载决议无法选择正确的函数
int y = 123;
bar(y);  // 无法确定使用哪个函数

在上述代码中,bar(int& x)bar(const int& x) 的参数类型相同,即 intconst int&,因此这两个函数不能被重载。

综上所述,const 修饰符可以用于函数重载,但必须与显式的参数类型和数量不同才能够进行重载。

结构体数组 指针

结构体类型数组,数组里每个单位是一个结构体

值传递 const实参

在 C++ 中,当形参是值传递(pass-by-value)方式,并且实参是 const 类型时,在函数调用时会将 const 修饰符丢弃,将其作为非 const 类型进行传递,不会传导 const。

这是因为值传递会创建实参的副本,形参和实参是两个独立的变量。在创建副本时,const 修饰符会被忽略,因为形参是不可修改的,它只是一个新的变量,不会对原始实参造成影响。

以下是一个示例:

void foo(int x) {
    x = 42;  // 在函数内修改形参的值
}

int main() {
    const int value = 10;
    foo(value);  // 调用函数 foo

    // 因为值传递,函数内部的修改不会影响原始实参
    std::cout << value << std::endl;  // 输出 10
    return 0;
}

在上述示例中,foo 函数采用值传递的方式接受一个整数形参 x。虽然 value 是一个 const 整数,但在函数调用时,const 修饰符被忽略,将其作为普通的整数值传递给 x。因此,在函数内部修改形参 x 的值不会影响原始的 const 实参 value

需要注意的是,如果形参是指针或引用类型,则 const 限定符会传递给形参,形成一个指向常量的指针或引用。这样可以保证在函数内部无法通过形参修改原始实参的值。

总结起来,当形参是值传递方式,并且实参是 const 类型时,const 修饰符会被忽略,将其作为非 const 类型进行传递,不会传导 const。只有在使用指针或引用类型作为形参时,const 修饰符才会传递给形参。

插入节点是否成为叶子

在二叉树中,如果要插入一个节点,它不一定会成为叶子节点。它的位置取决于插入算法或逻辑。

通常情况下,插入节点的位置是根据特定规则来确定的。一种常见的规则是根据节点的值进行比较,将其插入到合适的位置以保持二叉搜索树的有序性。

以下是一个示例,演示了如何在二叉搜索树中插入一个新节点:

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

void insert(TreeNode*& root, int val) {
    if (root == nullptr) {
        root = new TreeNode(val); // 创建新节点作为叶子节点插入
        return;
    }

    if (val < root->val) {
        insert(root->left, val);  // 小于当前节点值,递归插入左子树
    } else {
        insert(root->right, val); // 大于等于当前节点值,递归插入右子树
    }
}

上述代码中,insert 函数通过递归地比较节点值,并根据比较结果选择左子树或右子树进行插入。如果子树为空,则创建一个新的叶子节点插入。

需要注意的是,如果二叉树已经存在某个节点的值等于要插入的节点的值,那么根据具体需求,可以进行相应的操作,例如覆盖该节点的值、跳过插入等。这取决于具体的插入逻辑。

总结来说,并不是每次插入节点都会成为叶子节点。节点的插入位置由插入算法或逻辑决定,可能会作为叶子节点插入,也可能插入为其他类型的节点。

关键字 不能重载

sizeof
delete
new
内置的关键字和运算符,它们的功能和行为是由编译器实现的,无法被用户重载或修改。它们的行为在标准中已经定义好了,不能根据用户的需要进行自定义的变化。

const用途

const 是 C++ 中的关键字,用于表示常量。它可以应用于变量、函数参数、成员函数和成员变量,用于指定其不可修改的特性。

const 的主要用途如下:

  1. 定义常量变量:通过 const 关键字,可以声明一个常量变量,即其值在初始化后不可更改。例如:const int MAX_VALUE = 100;。常量变量在程序中使用时具有只读属性,可以防止意外的修改。

  2. 函数参数修饰:在函数定义中,如果希望某个参数不被修改,可以将其声明为 const 类型。这样,在函数内部就无法修改该参数的值了,从而确保参数的只读性。这在避免不必要的变量修改和提高安全性方面非常有用。

  3. 成员函数的 const 修饰:在类中,可以使用 const 修饰成员函数,表示该函数不会修改对象的状态(即不会修改成员变量)。这样的成员函数可以在 const 对象上调用,以及在 const 成员函数内调用,提供了对只读对象的操作能力。

  4. 成员变量的 const 修饰:在类中,可以使用 const 修饰成员变量,表示该成员变量是一个常量,一旦初始化后就无法修改。这样的成员变量在对象的生命周期内保持不变,提供了对只读数据的访问。

使用 const 关键字有助于代码的可读性、安全性和性能优化。它可以帮助开发者明确表达意图,并约束代码行为,避免错误地修改常量数据或对象状态。

注释嵌套

在 C++ 中,注释有两种形式:// 单行注释和 /* */ 块注释。

// 单行注释可以嵌套在 /* */ 块注释中使用,但是 /* */ 块注释不能嵌套在另一个 /* */ 块注释中。

下面是一些例子来说明这个问题:

// 单行注释可以嵌套块注释
/* 这是一个嵌套 // 的单行注释 */

/* 块注释不能嵌套
   /* 这是错误的嵌套注释 */
*/

/* 块注释可以包含单行注释
   // 这是一个包含在块注释中的单行注释 
*/

在第一个例子中,// 单行注释嵌套在了 /* */ 块注释内部。

而在第二个例子中,试图在一个块注释中再次嵌套另一个块注释是非法的,会导致编译错误。

在第三个例子中,// 单行注释位于 /* */ 块注释内部,并且被视为普通文本处理,不会起到注释的作用。

总结来说,单行注释 // 可以嵌套在块注释 /* */ 内部,但块注释不能直接嵌套在另一个块注释内部。

浮点数 取模

在C++中,取模运算(余数运算)% 只适用于整数类型。

如果你尝试将一个 double 类型的数值进行取模运算,编译器会报错,因为这是语法错误。取模运算需要两个整数操作数,并返回其余数。

如果你想对浮点数进行取模运算,你可以使用库函数 fmod() 或者 remainder(),它们位于 <cmath> 头文件中:

#include <iostream>
#include <cmath>

int main() {
    double d = 1.45;
    double result = fmod(d, 3.0);

    std::cout << "Result: " << result << std::endl;

    return 0;
}

使用 fmod() 函数来计算浮点数的模,需要传入两个浮点数参数。以上述代码为例,result 的值将会是 1.45 除以 3.0 的余数,即 1.45

请注意,由于浮点数的精度问题,涉及浮点数的计算可能存在舍入误差。因此,在处理浮点数的取模运算时,可能会出现预期之外的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值