面经汇总

[TOC]

算法

C/C++

基础

c和c++的区别 比较C++和C

C++与C的区别终于说清楚了! - 知乎 (zhihu.com)

嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

记住这是第一方案!!!!

while(1)
{
}

一些程序员更喜欢如下方案:

for(;;){
}

汇编语言的无限循环是:

Loop:
...
goto Loop;

根据熟悉的语言,谈谈两种语言的区别?

主要浅谈下C/C++和JAVA语言的区别:

1)java语言给开发人员提供了更为简洁的语法;取消了指针带来更高的代码质量;完全面向对象,独特的运行机制是其具有天然的可移植性;Java语言不需要程序对内存进行分配和回收。Java语言不使用指针,并提供了自动的废料收集,在Java语言中,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题。

2)C/C++开发语言,C语言更偏向硬件底层开发,C++语言是目前为止我认为语法内容最多的一种语言。c++用析构函数回收垃圾,C/C++在执行速度上要快很多,毕竟其他类型的语言大都是C开发的,更多应用于网络编程和嵌入式编程。

讲讲C和C++在分配内存上的差异(malloc/free对比new/delete)

比较C++和Python

对 JavaScript 了解吗,执行上与 C++ 的区别

c++三大特性 / 面向对象三大特性 / C++的特点

[C++的三大特性 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/419964412#:~:text=C%2B%2B的三大特性 1 一、 封装 2 1. 目的 3,7 2. 简述 8 3. 特性 More items)

C++中的静态变量和普通变量

[C++类中静态变量和普通变量的区别 - CeasonCing - 博客园 (cnblogs.com)](https://www.cnblogs.com/ceason/articles/12852751.html#:~:text=C%2B%2B类中静态变量和普通变量的区别,静态变量: 1.静态变量会被编到程序的exe里面,从程序启动到结束,它一直存在;)

C++内存模型

C/C++内存模型 - 知乎 (zhihu.com)

C++内存管理

学会迁移,可以说到malloc,从malloc说到操作系统的内存管理,说道内核态和用户态,然后就什么高端内存,slab层,伙伴算法,VMA可以巴拉巴拉了,接着可以迁移到fork()。

C++内存分为那几块?

(堆区,栈区,常量区,静态和全局区)

每块存储哪些变量?

C++内存分布,栈上面还有什么? 栈在实际编程的时候有哪些应用场景

(9条消息) c++内存分布_leneey的博客-CSDN博客_c++内存分布

栈的特点和使用场景 - 简书 (jianshu.com)

C++内存分为哪几块,分别存储什么变量

:那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。 :在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 自由存储区:由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。 全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。 常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

关于静态内存分配和动态内存分配的区别及过程

1)静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源; 2)静态内存分配是在栈上分配的,动态内存是堆上分配的; 3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要; 4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。 5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员; 6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

内存对齐的原则

  1. 从0位置开始存储;
  2. 变量存储的起始位置是该变量大小的整数倍;
  3. 结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
  4. 结构体中包含结构体,从结构体中最大元素的整数倍开始存;
  5. 如果加入pragma pack(n) ,取n和变量自身大小较小的一个。

c++ 定位 / 查看 内存泄漏

C/C++内存泄漏及检测 - 吴秦 - 博客园 (cnblogs.com)

(1)在windows平台下通过CRT中的库函数进行检测; (2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置 (3)Linux下通过工具valgrind检测

内存泄漏怎么解决

如何避免内存泄漏

segment default发生了什么

(9条消息) Segment default_MessiGo的博客-CSDN博客

说一下零拷贝

项目中的buffer 的细节:buffer的空间变化,是否会更频繁的申请分配(经常会赋值拷贝)。这种方式的好处

C++标准,C++11新特性

头文件中的 ifndef/define/endif 干什么用?

预处理,防止头文件被重复使用,包括pragma once都是这样的

宏定义求两个元素的最小值

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

用预处理指令#define 声明一个常数,用以表明1年中有多少秒

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

预处理器标识#error的目的是什么

抛出错误提示,标识外部宏是否被定义!

指针和引用的区别

解答1

指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问; 指针可以改变地址,从而改变所指的对象,而引用必须从一而终; 引用在定义的时候必须初始化,而指针则不需要; 指针有指向常量的指针和指针常量,而引用没有常量引用; 指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。

解答2

1)引用是直接访问,指针是间接访问。 2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间 3)引用绑定内存空间(必须赋初值),一个变量别名不能更改绑定,可以改变对象的值。 总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性

指针与数组千丝万缕的联系

  1. 一个一维int数组的数组名实际上是一个int* const 类型;
  2. 一个二维int数组的数组名实际上是一个int (*const p)[n];
  3. 数组名做参数会退化为指针,除了sizeof

堆和栈的区别

堆栈存储的数据分别是哪些?

什么是智能指针,主要为了实现什么功能?

将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

为了更容易且更安全的管理动态内存,C++推出了智能指针(smart pointer)类型来管理动态对象。智能指针存储指向动态对象的指针,用于动态对象生存周期的控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。智能指针的主要作用就是用栈智能指针离开作用域自动销毁时调用析构函数来释放资源。

智能指针是怎么实现的?什么时候改变引用计数?

C++智能指针及其简单实现 - 峰子_仰望阳光 - 博客园 (cnblogs.com)

  1. 构造函数中计数初始化为1;
  2. 拷贝构造函数中计数值加1;
  3. 赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
  4. 析构函数中引用计数减一;
  5. 在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。

智能指针用过哪些,讲讲特点

智能指针 C++11中智能指针有哪些及其优缺点,shared_ptr是怎么释放内存的;

shared_ptr 实现机制

shared_ptr 如何实现

如何让多个 shared_ptr 对象共享引用计数

让 shared_ptr 线程安全该怎么设计

shared_ptr 的使用可能造成内存泄露吗

weak_ptr 作用

unique_ptr 实现机制

unique_ptr 与 shared_ptr 不同

智能指针,shared_ptr 和 unique_ptr 的区别

share_prt与weak_ptr的区别?

//share_ptr可能出现循环引用,从而导致内存泄露
class A
{
public:
    share_ptr p;
};

class B
{
public:
    share_ptr p;
}

int main()
{
    while(true)
    {
        share_prt pa(new A()); //pa的引用计数初始化为1
        share_prt pb(new B()); //pb的引用计数初始化为1
        pa->p = pb; //pb的引用计数变为2
        pb->p = pa; //pa的引用计数变为2
    }
    //假设pa先离开,引用计数减一变为1,不为0因此不会调用class A的析构函数,因此其成员p也不会被析构,pb的引用计数仍然为2;
    //同理pb离开的时候,引用计数也不能减到0
    return 0;
}

/*
** weak_ptr是一种弱引用指针,其存在不会影响引用计数,从而解决循环引用的问题
*/

shared_ptr 是否是线程安全

shared_ptr,引用计数何时增加和减少

如何用普通指针初始化 shared_ptr

用普通指针初始化 shared_ptr 这种用法有什么坏处

auto 什么时候推导

C++四种类型转换

static_cast, dynamic_cast, const_cast, reinterpret_cast

const_cast用于将const变量转为非const static_cast用的最多,对于各种隐式转换,非const转const,void*转指针等, static_cast能用于多态想上转化,如果向下转能成功但是不安全,结果未知; dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。 reinterpret_cast几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用; 为什么不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

内联函数有什么优点?内联函数与宏定义的区别?

宏定义在预编译的时候就会进行宏替换; 内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。 内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。 使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a * b,这很危险,正确写法:#define MUL(a, b) ((a) * (b))

delete和delete[]的用法? delete; delete[]; 的区别

new具体是怎么开辟内存的(底层实现);

new和malloc区别?

(9条消息) new与malloc的区别以及实现方法_oscarwin的博客-CSDN博客_new和malloc都是在堆上进行

解答1

new分配内存按照数据类型进行分配,malloc分配内存按照大小分配; new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。 new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化; new是一个操作符可以重载,malloc是一个库函数; new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会; malloc分配的内存不够的时候,可以用realloc扩容。new没用这样操作; new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。因此对于new,正确的姿势是采用try…catch语法,而malloc则应该判断指针的返回值。 new和new[ ]的区别,new[ ]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[ ],同理,delete[ ]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

解答2

new分配内存按照数据类型进行分配,malloc分配内存按照大小分配; new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。new的实现原理?但是还需要注意的是,之前看到过一个题说int* p = new int与int* p = new int()的区别,因为int属于C++内置对象,不会默认初始化,必须显示调用默认构造函数,但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后,在C++11中两者没有区别了,自己测试的结构也都是为0; new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化; new是一个操作符可以重载,malloc是一个库函数; new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会; malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作; new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。因此对于new,正确的姿势是采用try…catch语法,而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯,C++也可以采用new nothrow的方法禁止抛出异常而返回NULL; new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n; 如果不够可以继续谈new和malloc的实现,空闲链表,分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理,free为什么直到销毁多大的空间?

new和malloc的内存对齐是怎样的

new/delete和malloc/free

const关键字

作用

const修饰成员变量,必须在构造函数列表中初始化; const修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变

用法

首先说说const的用法(绝对不能说是常数)

1)在定义的时候必须进行初始化

2)指针可以是const 指针,也可以是指向const对象的指针

3)定义为const的形参(形参是函数被调用时用于接收实参值的变量),在函数内部是不能被修改的

4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量

5)类的成员函数可以返回的是常对象,即被const声明的对象

6)类的成员变量是常成员变量时,不能在声明时初始化,必须在构造函数的列表里进行初始化

(注:千万不要说const是个常数,会被认为是外行人的!!!!哪怕说个只读也行)

下面的声明都是什么意思? const int a; a是一个常整型数 int const a; a是一个常整型数 const int *a; a是一个指向常整型数的指针,整型数是不可修改的,但指针可以 int * const a; a为指向整型数的常指针,指针指向的整型数可以修改,但指针是不可修改的 int const * a const; a是一个指向常整型数的常指针,指针指向的整型数是不可修改的,同时指针也是不可修改的 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

const如何做到只读

这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定(因为结构体类型不是内置数据类型,编译器不知道如何直接替换,因此必须要访问内存去取数据)。

const修饰全局变量

const修饰局部变量

const修饰指针,const int *

const修饰指针指向的对象, int * const

const修饰引用做形参

extern关键字

作用

C/C++中extern关键字详解 - chao_yu - 博客园 (cnblogs.com)

extern声明变量在在外部定义?

extern修饰函数?

extern C的作用?用法?

告诉编译器该段代码以C语言进行编译。

int (*s[10])(int) 这是个什么

static关键字

static 变量可以用在哪些地方,分别有什么特点

static 修饰的普通变量初始化在 main 函数执行前还是后

类的 static 方法可以访问非 static 对象吗

作用

(9条消息) static详解_oscarwin的博客-CSDN博客_static

用法(三个明显的作用一定要答出来,作用范围)

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。 3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用 4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义 5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数(“this”指针能找到对象的所有非静态成员变量的地址)

static全局变量与普通的全局变量有什么区别

static全局变量只初使化一次,防止在其他文件单元中被引用;

static局部变量和普通局部变量有什么区别

static局部变量只被初始化一次,下一次依据上一次结果值; 静态局部变量实际上是放在程序数据区中的,局部变量存储在栈区

static函数与普通函数有什么区别

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

static修饰局部变量?

static全局变量?(限定变量在一个编译单元内,一个编译单元就是指一个cpp和它包含的头文件,这个回答可以结合编译需要经历的几个过程来答)

static修饰普通函数?

static修饰成员变量?

static修饰成员函数?

static 局部函数声明的static可以在其他局部函数中使用吗 类中的static可以使用其他类的static变量吗

sizeof()是在编译期间还是运行期间确定?sizeof指针是多大?

sizeof 各种基本类型 结构体 类

memcpy与strcpy的区别

signed char表示的数据范围

volatile关键字

简述

访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。 一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。

volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻),使用实例有哪些?(重点)

1)访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量, volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。

总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再编译优化,以免出错。

2)使用实例如下(区分C程序员和嵌入式系统程序员的最基本的问题。):

并行设备的硬件寄存器(如:状态寄存器) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 多线程应用中被几个任务共享的变量

3)一个参数既可以是const还可以是volatile吗?解释为什么。

可以。一个例子:只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

4)一个指针可以是volatile 吗?解释为什么。 可以。尽管这并不很常见。一个例子:当中断服务子程序修该一个指向一个buffer的指针时。

下面的函数有什么错误:

int square(volatile int *ptr) {
    return *ptr * *ptr;
}

下面是答案: 这段代码有点变态。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:


int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:


long square(volatile int *ptr){
    int a;
    a = *ptr;
    return a * a;
}

作用?

在编译时和运行时分别的作用?

聊深入理解c++对象模型那本书。

go C++ python 现状与未来发展

std::bind 用过吗?

C++lambda表达式

C++反射机制的实现

i++和++i的区别

i++是先用后加,直接执行i+1然后返回i的引用 ++i是先加后用,先创建i的副本然后i+1,最后返回副本

++i是否是原子操作

明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每个步骤都可能被中断。

判断大小端

union un
{
    int i;
    char ch;
};

void fun()
{
    union un test;
    test.i = 1;
    if(ch == 1)
        cout << "小端" << endl;
    else
        cout << "大端" << endl;
}

面向对象

封装,继承,多态

C++11为类初始化了那些函数

struct和class的区别

C++类构造函数初始化列表执行顺序

完善类的构造函数、复制构造函数、拷贝函数

必须在构造函数初始化式里进行初始化的数据成员有哪些

(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面 (2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 (3) 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化

怎么理解C++多态 继承和多态

多态,重载和重写,虚函数

为什么返回值不一样不是重载

C++多态实现 虚函数表

(9条消息) 浅谈C++多态性_hackbuteer1的博客-CSDN博客_c++多态

“一个接口,多种方法”。C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。 虚函数表,编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。

C++多态的实现?

多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。 动态多态实现有几个条件: (1) 虚函数; (2) 一个基类的指针或引用指向派生类的对象; 基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。 每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。 虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。

虚函数表

(9条消息) C++ 虚函数表解析_haoel的博客-CSDN博客_虚函数表

虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?

编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。

动态绑定是如何实现的?

主要都是结合虚函数表来答就行。

静态多态和动态多态

(9条消息) C++ 静态多态和动态多态 浅析_稚枭天卓的博客-CSDN博客

静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。

C++有哪些多态机制

C++多态是怎么体现的,派生类的内存分布,多重继承的虚函数表有几个

多继承如果实现?分别讲有没有虚函数的情况

讲菱形继承?

C++ 继承封装多态的特点 多态是怎么实现的 有什么好处

C++继承的优缺点

虚函数是什么 纯虚函数是什么 虚函数、纯虚函数的作用

虚函数作用

  1. 虚函数用于实现多态,这点大家都能答上来
  2. 但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。

虚函数表/虚函数表指针 虚函数指针?多重继承?

虚函数指针,虚函数表在内存中什么位置

虚函数指针构造是在构造函数代码前还是代码后

C++继承中,父类和子类的构造函数和析构函数的调用顺序

建立对象时,会先调用父类的构造函数再调用子类的构造函数。 销毁对象时,会先调用子类的析构函数再调用父类的析构函数。

析构函数和构造函数

析构函数

https://blog.csdn.net/qq_33757398/article/details/81335974

CSDN首页 博客 下载 学习 社区 GitCode 云服务 猿如意 析构函数 搜索

会员中心 足迹 动态 消息 创作中心 发布 析构函数

深山里的小白羊

于 2018-08-01 19:45:58 发布

30541 收藏 163 分类专栏: C++基础 C++基础 版权

C++基础 同时被 2 个专栏收录 31 篇文章12 订阅 订阅专栏

C++基础 16 篇文章2 订阅 订阅专栏 1.什么是析构函数 析构函数于构造函数相对应,构造函数是对象创建的时候自动调用的,而析构函数就是对象在销毁的时候自动调用的的

特点:

1)构造函数可以有多个来构成重载,但析构函数只能有一个,不能构成重载

2)构造函数可以有参数,但析构函数不能有参数

3)与构造函数相同的是,如果我们没有显式的写出析构函数,那么编译器也会自动的给我们加上一个析构函数,什么都不做;如果我们显式的写了析构函数,那么将会覆盖默认的析构函数

4)在主函数中,析构函数的执行在return语句之前,这也说明主函数结束的标志是return,return执行完后主函数也就执行完了,就算return后面还有其他的语句,也不会执行的

#include #include using namespace std;

class Cperson { public: Cperson() { cout << "Beginning" << endl; }

~Cperson()
{
    cout << "End" << endl;
}

};

int main() { Cperson op1;

system("pause");
return 0;

}

执行结果:

从这里也可以发现,此时析构函数并没有被执行,它在system之后,return之前执行

2.指针对象执行析构函数 与栈区普通对象不同,堆区指针对象并不会自己主动执行析构函数,就算运行到主函数结束,指针对象的析构函数也不会被执行,只有使用delete才会触发析构函数

#include #include using namespace std;

class Cperson { public: Cperson() { cout << "Beginning" << endl; }

~Cperson()
{
    cout << "End" << endl;
}

};

int main() { Cperson *op2 = new Cperson; delete(op2);

system("pause");
return 0;

}

执行结果:

在这里可以发现,已经出现了End,说明析构函数已经被执行,也就说明了delete触发了析构函数

3.临时对象 格式:类名();

作用域只有这一条语句,相当于只执行了一个构造函数和一个析构函数

除了临时对象,也有临时变量,例如语句int(12);就是一个临时变量,当这句语句执行完了,变量也就释放了,对外部没有任何影响,我们可以通过一个变量来接受这一个临时的变量,例如:int a=int(12);这与int a=12;不同,后者是直接将一个整型数值赋给变量a,而前者是先创建一个临时的变量,然后再将这个变量赋给变量a

#include #include using namespace std;

class Cperson { public: Cperson() { cout << "Beginning" << endl; }

~Cperson()
{
    cout << "End" << endl;
}

};

int main() { Cperson();

system("pause");
return 0;

}

执行结果:

4.析构函数的作用 当我们在类中声明了一些指针变量时,我们一般就在析构函数中进行释放空间,因为系统并不会释放指针变量指向的空间,我们需要自己来delete,而一般这个delete就放在析构函数里面

#include #include using namespace std;

class Cperson { public: Cperson() { pp = new int; cout << "Beginning" << endl; }

~Cperson()
{
    delete pp;
    cout << "End" << endl;
}

private: int *pp; };

int main() { Cperson();

system("pause");
return 0;

}

5.malloc、free和new、delete的区别 malloc不会触发构造函数,但new可以

free不会触发析构函数,但delete可以

#include #include using namespace std;

class Cperson { public: Cperson() { pp = new int; cout << "Beginning" << endl; }

~Cperson()
{
    delete pp;
    cout << "End" << endl;
}

private: int *pp; };

int main() { Cperson *op1 = (Cperson *)malloc(sizeof(Cperson)); free(op1);

Cperson *op2 = new Cperson;
delete op2;

system("pause");
return 0;

}

执行结果:

从结果上来看,只得到了一组Beginning、End说明只有一组触发了构造函数和析构函数,这一组就是new和delete

深山里的小白羊 关注

31

163 打赏

0

专栏目录 构造函数和析构函数的作用是什么?什么时候需要自己定义构造函数和析构函数? azhegps的博客 1万+ 构造函数的作用:用于新建对象的初始化工作。析构函数的作用:用于在撤销对象前,完成一些清理工作,比如:释放内存等。每当创建对象时,需要添加初始化代码时,则需要定义自己的构造函数;而对象撤销时,需要自己添加清理工作的代码时,则需要定义自己的析构函数。 ... 析构函数的定义 热门推荐 烦的博客 2万+ 1.析构函数的定义 析构函数:当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统会自动执行析构函数。析构函数往往用来做“清理善后”的工作(例如在建立对象时用new开辟了一段内存空间,则在该对象消亡前应在析构函数中用delete释放这段存储空间)。 C++规定析构函数的名字是类名的前面加一个波浪号()。其定义形式为: ~类名(){ 函数体 } 析构函数不返回任何值,没有返回类型,也 析构函数详解 weixin_43831728的博客 9268 析构函数详解 析构函数的概念 前面通过构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。 析构函数用来完成对象资源清理的工作 析构函数的特性 析构函数名是在类名前加上字符 ~。 无参数无返回值。 一个类有且只有一个析构函数。若用户没有显式定义,系统会自动生成默认的析构函数。 当对象生命周期结束时,C++编译系统系统会自动调用析构函数 c++中构造函数及析构函数特性 KFLING的博客 1439 一、在c++中,关于构造函数有以下几点说明: 1、构造函数最重要的作用是创建对象本身。 2、c++规定,每个类必须有一个构造函数,没有构造函数,则无法创建对象。 3、c++规定,如果一个类没有提供任何的构造函数,那么c++编译器会提供一个默认的构造函数,且这个默认的构造函数是不带参数的构造函数,它只负责对象的创建,不能进行对象的初始化。 4、只要一个类定义了一个构造函数,不管这个构造函数是 析构函数的作用 什么是析构函数 01-02 析构函数的作用是当对象生命期结束后,收回对象占用的资源,析构函数的特点是: 1、析构函数名是在类名前加以符号“”。 2、析构函数没有参数、返回类型和修饰符。 3、一个类中至多有一个析构函数,如果程序员没有定义析构函数,那么系统会自动地加入一个析构函数。 4、不能显式地调用析构函数,而是由系统自动调用。 定义析构函数格式为: 类名() {…} 由于c#得到自动内存管理的支持,当对象生命期结束后,系统能够自动回收那些对象应该释放的资源等,所以一般不需要程序员的关心,对于一般的对象,程序中不需要定义析构函数。 阿会楠:《Visual c#.net程序设计教程》笔记 析构函数的理解 Gu_Qingshan的博客 457 析构函数是与构造函数相对应的一个函数。构造函数用来对类对象进行初始化,而析构函数则是用来销毁对象。析构函数的函数体往往是空,代表其不进行任何操作。 使用new语句为对象申请空间之后,若在之后的程序中使用delete函数将空间回收,则会自动调用析构函数。在这里,笔者产生了一个疑惑:明明只用delete函数就可以将对象的空间释放,为什么要在设计语言之初要多创造这样一个析构函数?难道仅仅只是为了与构造函数对称? 经过查阅,我找到了舒夜无痕前辈的关于析构函数的学习心得,其中较为严谨的推理并阐述了显式析构函数与隐 C++析构函数 最新发布 m0_45463480的博客 1057 C++析构函数 一、析构函数的定义 ~类名(){} 例子: ~Student(){ } 2.析构函数没有返回值,没有参数 3.对象结束的时候自动调用析构函数 4.析构函数的作用是用于释放[堆空间] 5.析构函数不可以重载 6.执行前先执行构造函数,执行后即将消失时再执行析构函数 7.若在主函数中,则析构函数在主函数结束时再调用,因为此时对象的作用域是主函数全部,此时打印就不会打印析构里的东西,只会在return 0 ; (结束函数的作用)后再打印 二、知识拓展和总结 malloc不会触发构造函数,new会 析构函数的使用 Small_dian的专栏 1194 析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号。 在C++中“~”是位取反运算符,从这点也可以想到:析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。 具体地说如果出现以下几种情况,程序就会执行析构函数: ①如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象 c++ 默认继承_C++ 类的默认函数 weixin_39990410的博客 199 (给CPP开发者加星标,提升C/C++技能)来源:KeepHopeswww.cnblogs.com/yuwanxian/p/10924835.html在C++中,一个类有八个默认函数:1、默认构造函数;2、默认拷贝构造函数;3、默认析构函数;4、默认重载赋值运算符函数;5、默认重载取址运算符函数;6、默认重载取址运算符const函数;7、默认移动构造函数(C++11);8、默认重载移动赋... C++析构函数的简单范例 土豪gold的博客 9645 问题:C++析构函数的简单范例 本程序通过VC++ 6.0编译与测试,具体代码如下: #include using namespace std; class Destructor { public: Destructor(); //构造函数 ~Destructor(); //析构函数 }; Destructor::Destructor() { cou... 6、析构函数 江南又一春 529 如果一个类中使用new符号动态分配了内容,那么就一定需要使用delete进行释放,那么将delete语句放在哪里合适呢? 放在析构函数中。因为一个对象在销毁之前一定会执行析构函数。 只要有对象消亡,就一定有析构函数被调用。 ... c++析构函数 scarificed的博客 3175 分析: 在main函数中创建了t0,t1,t2,t3几个对象,这里先说一下C++创建对象的三种不同方式: 1、Test p1(1); //栈中分配内存 2、Test p2 = Test(2);        //栈中分配内存,跟方法1相同,是方法1的完整模式 3、Test *p3 = new Test(3);     //堆中分配内存 方法1、2中都是在栈中分配内存,在栈中内存由系统自动的去分配和释放,而使用new创建的指针对象是在堆中分 什么是析构函数 有为雪碧君的博客 560 构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。 以C++语言为例,析构函数名也应与类名相同,只是在函数名前面加一个波浪符,例如stud( ),以区别于构造函数。它不能带任何参数,也没有返回... 类的基本知识点:析构函数 _Previous的博客 477 下面整理列出关于类的析构函数的知识点:1、类的析构函数也是类的成员函数。它在对象被撤销之时由系统隐式调用。作用是在对象撤销时执行一些清理任务。如使用delete将内存还给系统等。2、C++语言规定析构函数名是类名前加波浪号“”。3、析构函数没有任何返回类型。也一定没有任何参数。4、可以通过“.”显式调用析构函数;但更多的情况下,是在对象生存期结束时自动被调用的。可以通过下面的例子简单感受下析构函数 构造函数和析构函数 qq_41232519的博客 336 文章目录一、构造函数二、析构函数 一、构造函数 struct Person { int age; int level; //构造函数 Person() { printf("Person对象创建了\n"); } //构造函数重载 Person(int age,int leve ) { this->age = age; this->level = level; } } 构造函数的特点: 1、与类同名 2、没有返回值 “相关推荐”对你有帮助么?

非常没帮助

没帮助

一般

有帮助

非常有帮助 ©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页 关于我们 招贤纳士 商务合作 寻求报道

400-660-0108

[email protected]

在线客服 工作时间 8:30-22:00 公安备案号11010502030143 京ICP备19004658号 京网文〔2020〕1039-165号 经营性网站备案信息 北京互联网违法和不良信息举报中心 家长监护 网络110报警服务 中国互联网举报中心 Chrome商店下载 账号管理规范 版权与免责声明 版权申诉 出版物许可证 营业执照 ©1999-2022北京创新乐知网络技术有限公司

深山里的小白羊 码龄7年 暂无认证 184 原创 1万+ 周排名 2520 总排名 91万+ 访问

等级 8323 积分 332 粉丝 1098 获赞 338 评论 3988 收藏 五一创作勋章 专栏达人 持之以恒 笔耕不辍 勤写标兵 私信 关注 搜博主文章

热门文章 3.Eclipse创建第一个Java项目 53578 C(string.h)字符串操作函数总结 53377 静态变量与动态变量 51721 内联函数 50423 利用Python求解带约束的最优化问题 42313 最新评论 U-RISC 电镜图像神经元分割 第四名解决方案 深山里的小白羊: https://pan.baidu.com/s/1k6tqGuaol2OWHvd-rronaQ,1p56

U-RISC 电镜图像神经元分割 第四名解决方案 LLLufy: 比赛官网上不去了,可以提供一下数据集吗?谢谢

利用Python求解带约束的最优化问题 Hit_Chr: 学到了,minimize这玩意是局部最优,初始值调不对一直优化到原点表情包

Unable to find vcvarsall.bat Eastwind710: 记录一下路径 Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat

三种Perceptual_metric:VGG distance,Inception Score,FID Score的源码 Aries_coco: https://download.pytorch.org/models/vgg19-dcbb9e9d.pth

您愿意向朋友推荐“博客详情页”吗?

强烈不推荐

不推荐

一般般

推荐

强烈推荐 最新文章 浅谈机器学习算法 t-SNE可视化-Python实现 Leetcode之二分查找系列 2022年14篇2021年14篇2020年31篇2019年32篇2018年94篇

目录 1.什么是析构函数 2.指针对象执行析构函数 3.临时对象 4.析构函数的作用 5.malloc、free和new、delete的区别

分类专栏

日用小技能 64篇

神经网络加速与压缩

matlab 5篇

C++基础 16篇

面经 5篇

文章 1篇

数学基本知识 1篇

指针 10篇

C\C++字符串处理 3篇

C语言指针 10篇

C++基础 31篇

STL基础使用 8篇

Java之路 5篇

静动态链接库 10篇

OpenCV与VS2017 2篇

Git使用方法 3篇

Windows与MFC编程 5篇

Python 9篇

李飞飞计算机视觉教程 2篇

统计学习 3篇

CUDA 2篇

算法分析设计 6篇

opencv与python 7篇

举报

子类实例化的时候 析构和构造的顺序是什么

纯虚函数如何定义

//纯虚函数定义
virtual ~myClass() = 0;

构造可以使用纯虚函数吗

析构函数能抛出异常吗

答案肯定是不能。 C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。

(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。 (2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

C++析构函数可以是虚函数吗,为什么?

为什么对于存在虚函数的类中析构函数要定义成虚函数

为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。

C++中虚析构函数的作用

构造函数中是否可以调用虚成员函数

构造函数和析构函数中能调用虚函数吗?

移动构造函数和拷贝构造函数区别

基类析构函数为什么要是虚函数

拷贝构造函数以及深拷贝和浅拷贝的区别

写一个类A有一个虚函数、类B继承自类A(这里忘了把析构函数写成虚的,面试官提醒了一下,顺便将了一下原理)

free一个类的指针的时候发生了什么

类大小的计算

操作符重载

一个空类编译器会自动生成哪些函数

类静态成员函数的特点、静态成员函数可以是虚的么。

有一个函数,返回string对象,调用几次copy constructor ?不考虑编译器的优化,重新设计这个函数,如何减少copy?

以下代码共调用多少次拷贝构造函数:_360公司笔试题_牛客网 (nowcoder.com)

(2 封私信 / 30 条消息) C++按值返回调用几次构造函数? - 知乎 (zhihu.com)

C++:调用返回值为对象的函数引起的copy constructor调用问题-CSDN社区

[C++11:使用引用减少拷贝构造函数使用次数 - cvlearner - 博客园 (cnblogs.com)](https://www.cnblogs.com/cvwyh/p/10735019.html#:~:text=C%2B%2B11:使用引用减少拷贝构造函数使用次数 1 使用另一个同类型的对象来初始化新创建的对象。,2 复制对象把它作为参数传递给函数。 3 复制对象,并从函数返回这个对象。)

(9条消息) C++中减少内存拷贝的小技巧_petition的博客-CSDN博客

一个class的前向声明

(9条消息) C++类的前向声明(forward declaration)_YLEOY的博客-CSDN博客_向前声明

C++ 类的前向声明的用法 - 王陸 - 博客园 (cnblogs.com)

(9条消息) C++中的前向声明(ForwardDeclaration)_GeekWill的博客-CSDN博客_c++前向声明

STL & 数据结构 & 算法

C++ 常使用哪些 STL 容器

stl容器选几个讲讲 / c++的STL中,有哪几种容器?

1. 序列容器(7个)

vector:提供了自动内存管理功能(采用了STL普遍的内存管理器allocator),可以动态改变对象长度,提供随机访问。在尾部添加和删除元素的时间是常数的,但在头部或中间就是线性时间。 deque:双端队列(double-ended queue),支持随机访问,与vector类似,主要区别在于,从deque对象的开始位置插入和删除元素的时间也是常数的,所以若多数操作发生在序列的起始和结尾处,则应考虑使用deque数据结构。为实现在deque两端执行插入和删除操作的时间为常数时间这一目的,deque对象的设计比vector更为复杂,因此,尽管二者都提供对元素的随机访问和在序列中部执行线性时间的插入和删除操作,但vector容器执行这些操作时速度更快些。 list:双向链表(是循环的)。目的是实现快速插入和删除。 forward_list(C++11):实现了单链表,不可反转。相比于list,forward_list更简单,更紧凑,但功能也更少。 queue:是一个适配器类。queue模板让底层类(默认是deque)展示典型的队列接口。queue模板的限制比deque更多,它不仅不允许随机访问队列元素,甚至不允许遍历队列。与队列相同,只能将元素添加到队尾、从队首删除元素、查看队首和队尾的值、检查元素数目和测试队列是否为空。 priority_queue:是另一个适配器类,支持的操作与queue相同。两者之间的主要区别在于,在priority_queue中,最大的元素被移到对首。内部区别在于,默认的底层类是vector。可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数:

priority_queue pq1;                     // default version
priority_queue pg2(greater);       // use greater to order
greater<>函数是一个预定义的函数对象。

stack:与queue相似,stack也是一个适配器类,它给底层类(默认情况下为vector)提供了典型的栈接口。

2. 关联容器

4种有序关联容器:set、multiset、map和multimap,底层基于树结构.

了解什么数据结构说一下,排序算法的复杂度,排序

栈和队列

  • 队列和栈的区别?(从实现,应用,自身特点多个方面来阐述,不要只说一个先入先出,先入后出,这个你会别人也会,要展现出你比别人掌握的更深)
  • 典型的应用场景

栈溢出要怎么处理

栈和队列的相同和不同

不同点: 1). 删除数据元素的位置不同,栈的删除操作在表尾进行,队列的删除操作在表头进行。 2). 应用场景不同;常见栈的应用场景包括括号问题的求解,表达式的转换和求值,函数调用和递归实现,深度优先搜索遍历等;常见的队列的应用场景包括计算机系统中各种资源的管理,消息缓冲器的管理和广度优先搜索遍历等。 3). 顺序栈能够实现多栈空间共享,而顺序队列不能。 相同点: 1). 都是线性结构。 2). 插入操作都是限定在表尾进行。 3). 都可以通过顺序结构和链式结构实现。 4). 插入与删除的时间复杂度都是O(1),在空间复杂度上两者也一样。 5). 多链栈和多链队列的管理模式可以相同。

广搜用什么数据结构

链表

  • 链表和插入和删除,单向和双向链表都要会
  • 链表的问题考虑多个指针和递归 (1) 反向打印链表(递归) (2) 打印倒数第K个节点(前后指针) (3) 链表是否有环(快慢指针)等等。

完成链表类以及增查删改,反转

数组链表,栈和堆

  • 二叉树结构,二叉查找树实现;
  • 二叉树的六种遍历;
  • 二叉树的按层遍历;
  • 递归是解决二叉树相关问题的神级方法;
  • 树的各种常见算法题 – (9条消息) 轻松搞定面试中的二叉树题目_WalkingInTheWind的博客-CSDN博客

平衡二叉树如何构造

定义: 平衡二叉树又称为AVL树,它或者是一棵空树,或者是有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左右子树的深度之差的绝对值不超过1。 二叉树的的平衡因子为:该结点的左子树的深度减去它的右子树的深度,则平衡二叉树的所有结点的平衡因子为只可能是:-1、0和1。 构造: LL型:插入位置为左子树的左节点,向右旋转 RR型:插入位置为右子树的右节点,向左旋转 LR型:插入位置为左子树的右节点,先左旋再右旋。 RL型:插入位置为右子树的左节点,先右旋再左旋。(后面2种需要先调整插入位置,再调整最小补平衡树。此处仅简单罗列,最好参考具体图例进行学习) 双向链表中删除一个结点操作 找到被删除的结点p

p->prior ->next = p->next
p->next ->prior = p->prior
delete p

普通二叉树的应用?

说一下红黑树

  • 节点为红色或者黑色;
  • 根节点为黑色;
  • 从根节点到每个叶子节点经过的黑色节点个数的和相同;
  • 如果父节点为红色,那么其子节点就不能为红色。

红黑树与AVL树的区别

  • 红黑树与AVL树都是平衡树,但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1);
  • 红黑树效率更高,因为AVL为了保证其完全平衡,插入和删除的时候在最坏的情况下要旋转logN次,而红黑树插入和删除的旋转次数要比AVL少。

红黑树和普通二叉树的区别

Trie树(字典树)

(9条消息) Trie树详解及其应用_hackbuteer1的博客-CSDN博客_字典树详解

  • 每个节点保存一个字符
  • 根节点不保存字符
  • 每个节点最多有n个子节点(n是所有可能出现字符的个数)
  • 查询的复杂度为O(k),k为查询字符串长度

STL里的内存池实现

STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。

二级分配器设计的非常巧妙,分别给8k,16k,…, 128k等比较小的内存片都维持一个空闲链表,每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存,那么就找到最小的大于等于10k的块,也就是16K,从16K的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。 如果要分配的内存大于128K则直接调用一级分配器。 为了节省维持链表的开销,采用了一个union结构体,分配器使用union里的next指针来指向下一个节点,而用户则使用union的空指针来表示该节点的地址。

STL支持多线程吗 / STL是线程安全的吗

stl容器是线程不安全的

https://blog.csdn.net/guochampion/article/details/80261176 CSDN首页 博客 下载 学习 社区 GitCode 云服务 猿如意 stl容器是线程不安全的 搜索

会员中心 足迹 动态 消息 创作中心 发布 STL容器是线程不安全的----以及加锁实现多线程访问安全

guochampion

于 2018-05-09 23:07:04 发布

4784 收藏 10 分类专栏: C++/C

C++/C 专栏收录该内容 5 篇文章0 订阅 订阅专栏 STL的线程安全. 说一些关于stl容器的线程安全相关的话题。

一般说来,stl对于多线程的支持仅限于下列两点:(貌似Effective STL中有描述)

1.多个读取者是安全的。即多个线程可以同时读取一个容器中的内容。 即此时多个线程调用 容器的不涉及到写的接口都可以 eg find, begin, end 等.

2.对不同容器的多个写入者是安全的。即多个线程对不同容器的同时写入合法。 但是对于同一容器当有线程写,有线程读时,如何保证正确? 需要程序员自己来控制,比如:线程A读容器某一项时,线程B正在移除该项。这会导致一下无法预知的错误。 通常的解决方式是用开销较小的临界区(CRITICAL_SECTION)来做同步。以下列方式同步基本上可以做到线程安全的容器(就是在有写操作的情况下仍能保证安全)。

  1.每次调用容器的成员函数的期间需要锁定。

  2.每个容器容器返回迭代器的生存期需要锁定。

  3.每个容器在调用算法的执行期需要锁定。

  和小罗的关于task_server的多线程安全的交流: 是这样的, 当你调用map的任何接口时, 比如 end(), begin(), find()等时, 可能会返回一个iterator, 如果有别的线程正在修改这个map, 你的iterator就变得无效了, 再用这个iterator行为就可能出问题. 或者在find()函数内部, 会访问到map内部的红黑树的数据结构, 而这个红黑树是有可能被别的线程调整的(比如别的现在往map中插入一个不存在的记录). 所以, 是危险的.

我们通常通过为容器加锁来保证容器的线程安全性。

为容器加锁:

多个读取者是安全的。多线程可能同时读取一个容器的内容,这将正确地执行。当然,在读取时不能有任何写入者操作这个容器。

对不同容器的多个写入者是安全的。多线程可以同时写不同的容器。

针对容器的锁实现:

template class Lock { public: Lock() =delete; Lock(const Container &container): c(container), released(false){ getMutexFor(c); } ~Lock(){ if(false == released){ try{ releaseMutexFor(c); } catch(...){

        }
    }
}
void releaseMutex(){
    releaseMutexFor(c);
    released = true;
}

private: const Container &c; bool released; }; 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 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

文章知识点与官方知识档案匹配,可进一步学习相关知识 云原生入门技能树容器编排(生产环境 k8s)kubelet,kubectl,kubeadm三件套5989 人正在系统学习中

guochampion 关注

2

10

1

专栏目录 vc++中的线程锁(线程锁保持线程同步) 11-05 简单学习用例,利用线程锁对线程同步进行控制,保证对公共资源的访问不出现错误! 【c++容器】C++ STL容器如何解决线程安全的问题 最新发布 StudyWinter的博客 258 对于vector,即使写方(生产者)是单线程写入,但是并发读的时候,由于潜在的内存重新申请和对象复制问题,会导致读方(消费者)的迭代器失效。实际表现也就是招致了core dump。另外一种情况,如果是多个写方,并发的,也会导致core dump。当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。 评论1条写评论 Coding-Prince 热评 学习了, STL容器的线程安全_猴子居士的博客_stl线程安全 9-30 vector是顺序容器,STL中还有一类关联容器其线程安全问题也不容小觑。比如map、unordered_map。我们可能会有这样一种场景:在并发环境下,收集一些Key-Value,存储在某一个公共的容器中。这里也谈一下不用锁的方案,当然做不到放之四海皆准。 STL(常用的算法,各种容器,迭代器)STL容器是线程安全的吗 9-27 STL中算法大致分为四类: 非可变序列算法:指不直接修改其所操作的容器内容的算法。 可变序列算法:指可以修改它们所操作的容器内容的算法。 排序算法:包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作。 STL容器线程安全问题 qq_41033011的博客 690 一般来说,STL 对于多线程的支持仅限于下列两点: 多个读取者是安全的。即多个线程可以同时读取一个容器中的内容。即此时多个线程调用容器,只要不涉及到写的接口都可以。比如,find,begin,end 等等。 对不同容器的多个写入者是安全的。即多个线程对不同容器的同时写入合法。但是对于同一容器当有线程写,有线程读时,如何保证正确?需要程序员自己来控制,比如:线程A读容器某一项时,线程B正在移除该项。这会导致无法预知的错误。通常的解决方式是用开销较小的临界区来做同步。用以下方式同步基本上可以做到线程安全的容器 条款12:对STL容器线程安全性的期待现实一些 Black_Man的专栏 1277 条款12:对STL容器线程安全性的期待现实一些标准C++的世界是相当保守和陈旧的。在这个纯洁的世界,所有可执行文件都是静态链接的。不存在内存映射文件和共享内存。没有窗口系统,没有网络,没有数据库,没有其他进程。在这种情况下,当发现标准没有提到任何关于线程的东西时你不该感到惊讶。你对STL的线程安全有的第一个想法应该是它将因实现而不同。当然,多线程程序是很普遍的,所以大部分STL厂商努力使他 多线程程序中的STL与锁 zhengwenwei_123的专栏 434
多线程程序中,STL与锁配合使用,作为多个线程访问的资源保护机制。 vector容器的简单介绍与使用 在路上的博客 259 C++中Vector容器的使用方式了解Vector容器C++中容器使用方式读取容器的内容直接改变容器的大小 了解Vector容器 Vector容器就是能够存储很多数据,但是必须是相同类型的。类似数组,但是按需分配。 容器的构造方式 1、需要添加头文件 2、vector <类型> 容器名 C++中容器使用方式 #include #include

1) stl的线程安全. 说一些关于stl容器的线程安全相关的话题。 一般说来,stl对于多线程的支持仅限于下列两点:(貌似Effective STL中有描述) 1.多个读取者是安全的。即多个线程可以同时读取一个容器中的内容。 即此时多个线程调用 容器的不涉及到写的接口都可以 eg find, begin, end 等. 2.对不同容器的多个写入者是安全的。即多个线程对不同容器的同时写入合法。 但是对于同一容器当有线程写,有线程读时,如何保证正确? 需要程序员自己来控制,比如:线程A读容器某一项时. std string与线程安全_C++ STL容器如何解决线程安全的问题? weixin_35714577的博客 1043 众所周知,STL容器不是线程安全的。对于vector,即使写方(生产者)是单线程写入,但是并发读的时候,由于潜在的内存重新申请和对象复制问题,会导致读方(消费者)的迭代器失效。实际表现也就是招致了core dump。另外一种情况,如果是多个写方,并发的push_back(),也会导致core dump。解法一:加锁是一种解决方案,但是加std::mutex互斥锁确实性能较差。对于多读少写的场景可以... STL - 算法 weixin_30532973的博客 81 一、查找算法(13个):判断容器中是否包含某个值1) adjacent_find: 在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator。否则返回last。重载版本使用输入的二元操作符代替相等的判断。2) binary_search: 在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判... STL的多线程安全问题 热门推荐 马农 1万+ 。以下列方式同步基本上可以做到线程安全的容器(就是在有写操作的情况下仍能保证安全)。 1.每次调用容器的成员函数的期间需要锁定。 2.每个容器容器返回迭代器的生存期需要锁定。 3.每个容器在调用算法的执行期需要锁定。 stl是不是线程安全的 m0_46598535的博客 582 STL的线程安全解决方案 C++ STL之unique_lock与lock_guard Peace 453 std::lock_guard http://www.cplusplus.com/reference/mutex/lock_guard/ 用于托管一个mutex 变量,负责对齐加锁解锁。 A lock guard is an object that manages a mutex object by keeping it always locked.On construction, the mutex object is locked by the calling thread, and ... 多线程中使用stl容器 wuyapu的专栏 1807 在实际中,有时候要在多线程中使用一些stl容器,为了保证安全,需要不断的加锁,解锁下面我实现了一个类自动完成这些操作,感觉比较方便,特贴出来共享 /**** * 线程安全的容器类 * 可以为所有的容器提供临界区支持 * 使其成为线程安全的 * 如 CSafeContainer > p; * STL容器不是线程安全的 Wang的专栏 1644 容器线程安全 STL容器是线程不安全,尤其string ChengChengCheng的博客 316 STL容器是线程不安全的----以及加锁实现多线程访问安全 https://blog.csdn.net/guochampion/article/details/80261176 C++STL容器部分操作多线程不安全 课前的博客 3491 最近项目中发现一个c++stl容器多线程查找可能出现CPU占用率100%的问题。 问题是这样的,线程A和线程B共享一个stl::map。其中线程A对map有查找的操作,线程B对map有删除的操作。收集其core文件后发现线程A一直在map的查找中循环。看map查找函数中确实用一个while循环实现。确定为线程B删除了线程A查找的值导致线程A中陷入了map表查找死循环中。 总结:C++ STL容器的线程安全? weixin_33796205的博客 242 接上一篇STL容器删除操作继续,STL使用起来方便,还有自动内存管理,再结合合适的泛型算法大大提高了我们编程的效率,于是我们“肆无忌惮”的使用在自己的程序中。但是只要你有“肆无忌惮”的态度,你终归是要走上弯路的,比如其线程安全问题。先从《Effective STL》进行搬运。线程安全的情况多个读取者是安全的。多线程可能同时读取一个容器的内容,这将正确地执行。当然,在读取时不能 有任何写入者操作这个... STL容器线程安全性 3202 条款12:对STL容器线程安全性的期待现实一些 标准C++的世界是相当保守和陈旧的。在这个纯洁的世界,所有可执行文件都是静态链接的。不存在内存映射文件和共享内存。没有窗口系统,没有网络,没有数据库,没有其他进程。在这种情况下,当发现标准没有提到任何关于线程的东西时你不该感到惊讶。你对STL的线程安全有的第一个想法应该是它将因实现而不同。 当然,多线程程序是很普遍的,所以大部分STL厂商努力使他们的 “相关推荐”对你有帮助么?

非常没帮助

没帮助

一般

有帮助

非常有帮助 ©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页 关于我们 招贤纳士 商务合作 寻求报道

400-660-0108

[email protected]

在线客服 工作时间 8:30-22:00 公安备案号11010502030143 京ICP备19004658号 京网文〔2020〕1039-165号 经营性网站备案信息 北京互联网违法和不良信息举报中心 家长监护 网络110报警服务 中国互联网举报中心 Chrome商店下载 账号管理规范 版权与免责声明 版权申诉 出版物许可证 营业执照 ©1999-2022北京创新乐知网络技术有限公司

guochampion 码龄7年 暂无认证 3 原创 25万+ 周排名 102万+ 总排名 5万+ 访问

等级 634 积分 14 粉丝 20 获赞 1 评论 110 收藏 私信 关注 搜博主文章

最新评论 STL容器是线程不安全的----以及加锁实现多线程访问安全 Coding-Prince: 学习了,

您愿意向朋友推荐“博客详情页”吗?

强烈不推荐

不推荐

一般般

推荐

强烈推荐 最新文章 约束优化方法之拉格朗日乘子法与KKT条件——转载 点击率预估综述 CTR预估的几种方式 2018年20篇

分类专栏

学习者 9篇

GBDT 2篇

C++/C 5篇

举报

STL的allocator

stl 里面有哪些排序函数,sort是稳定的吗,stl稳定的排序函数有哪些?

STL vector和list适用于什么场景

vector容器

vector 增删改查复杂度分别是什么

vector 支持越界检查吗

讲讲 vector 扩容

vector扩容的方式及底层实现方法,vector可以存放任意类型的数据结构么(比如结构体、类等等);

不考虑内存vector存储几十万的数据怎么存?

vector数据增长之后的内存是怎么控制的

vector扩容机制,初始化时指定容量,clear函数怎么清除的?

push_back 的复杂度一定是 O(1) 吗

push_back 和 emplace_back 区别

emplace_back 可以传对象吗

vector底层实现原理,内存扩容怎么实现的,频繁拷贝怎么解决。

什么是哈希

哈希函数

哈希表

哈希表是怎么实现的

(拉链和分散地址)

Hash策略常见的有哪些?

STL中hash_map扩容发生什么?

(1) 创建一个新桶,该桶是原来桶两倍大最接近的质数(判断n是不是质数的方法:用n除2到sqrt(n)范围内的数) ; (2) 将原来桶里的数通过指针的转换,插入到新桶中(注意STL这里做的很精细,没有直接将数据从旧桶遍历拷贝数据插入到新桶,而是通过指针转换) (3) 通过swap函数将新桶和旧桶交换,销毁新桶。

介绍一下hash表的桶的个数为啥是质数个

哈希表的作用,怎么解决哈希冲突

布隆过滤器

几十亿个数经常要查找某一个数在不在里面,使用布隆过滤器,

原理作用

布隆过滤器可能出现误判,怎么保证无误差?

map 和hashmap适用于什么场景

map和unordered_map的区别,底层实现

map 除了查询复杂度高还有什么缺点

C++里面的map(map和unordred_map),然后介绍一下两者不同的遍历方式

unordered_map产生冲突怎么解决,会导致什么问题?

map和set

STL map是什么实现的

set的底层实现

STL里set和map是基于什么实现的 红黑树的特点

set和map都是基于红黑树实现的。 红黑树是一种平衡二叉查找树,与AVL树的区别是什么?AVL树是完全平衡的,红黑树基本上是平衡的。 为什么选用红黑数呢?因为红黑数是平衡二叉树,其插入和删除的效率都是N(logN),与AVL相比红黑数插入和删除最多只需要3次旋转,而AVL树为了维持其完全平衡性,在坏的情况下要旋转的次数太多。 红黑树的定义: (1) 节点是红色或者黑色; (2) 父节点是红色的话,子节点就不能为红色; (3) 从根节点到每个页子节点路径上黑色节点的数量相同; (4) 根是黑色的,NULL节点被认为是黑色的。

模板

类模板/函数模板

template用来干什么的

模板特化,偏特化

(9条消息) 模板的全特化与偏特化_thefutureisour的博客-CSDN博客_模板的特化和偏特化

(1) 模板特化分为全特化和偏特化,模板特化的目的就是对于某一种变量类型具有不同的实现,因此需要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化。

编译原理

C++编译过程

源文件到可执行文件的过程 可执行文件在计算机中怎么运行的 静态库和动态库有什么区别,应该怎么选择?

编译和链接阶段是怎么处理符号的?怎么保证找到实际想要的类的定义?如果这个类在动态链接库中呢?

(9条消息) C++/C链接过程详解_caipengxiang的博客-CSDN博客

(9条消息) 静态链接库与动态链接库----C/C++_光速跑者21的博客-CSDN博客

程序理解

class B

{
    int b;

public:
    virtual ~B() {
        cout << "B::~B()" << endl;
    }
};

class D : public B

{
    int i;

    int j;

public:
    virtual ~D() {
        cout << "D::~D()" << endl;
    }
};

void test(void) {
    B *pb = new D[2];

    pb[1] = xxxx;

    delete[] pb;

    return 0;
}

这样 delete有没有问题?

参考书籍

《C++ primer》

《effective C++》

《STL源码解析》

《深度搜索C++对象模型》

计算机网络

基础

ping www.baidu.com的整个过程 / 浏览器中输入一个URL发生什么,用到哪些协议?

浏览器中输入URL,首先浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。

得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。

Ping和TraceRoute实现原理

(1) Ping是通过发送ICMP报文回显请求实现。 (2) TraceRoute通过发送UDP报文,设置目的端口为一个不可能的值,将IP首部中的TTL分别设置从1到N,每次逐个增加,如果收到端口不可达,说明到达目的主机,如果是因为TTL跳数超过,路由器会发送主机不可达的ICMP报文。

socket套接字

网络编程知道哪些?写一下socket编程的函数。

ARP协议的作用

IP协议的作用

域名解析过程,ARP的机制,RARP的实现

(9条消息) TCP/IP(4)--IP协议相关技术_oscarwin的博客-CSDN博客

RARP用于无盘服务器,开机后通过发送RARP包给RARP服务器,通过mac地址得到IP地址

用Wireshark分析了什么

安全相关

至少了解攻击的原理和基本的防御方法,常见的攻击方法有一下几种

SQL注入 XSS CSRF SYN洪水攻击 APR欺骗

网络模型

网络各层协议都熟悉吗?

说一下五层网络各层的协议及作用

网络四层模型和七层模型 OSI七层模型?

TCP/IP模型则对应是五层网络模型,分为物理层,数据链路层,网络层,传输层,应用层。其中,IP协议工作在网络层

七层模型,TCP/IP在哪一层;

四层模型每一层有哪些协议

网络层功能

传输层功能

TTL功能,在哪层

集线器、交换机和路由器都在那一层?以及各自作用

虚拟路由器的动态路由协议?

为什么启用ipv6 ipv4地址数量有多少

ip分片与重组,什么时候分片?在哪里重组?依据什么重组?怎么判断重组全部完成?

router的namespace如何访问floating ip的?

如果网络层不分片怎么办?

Unix的域套接字

TCP

TCP,IP

TCP连接过程、相关底层调用函数

TCP和UDP

(9条消息) TCP/IP(5)--TCP与UDP_oscarwin的博客-CSDN博客

TCP和UDP作用与区别

TCP基于有连接,UDP基于无连接。有连接就是TCP在传输前先发送连接请求和应答包,确定双方能够正常传输后,才开始进行数据传输。无连接就是UDP在发送数据之前,并不考虑对方能否接受到,甚至目的地址可能都是无效; TCP能保证可靠传输,UDP不能保证可靠传输TCP。所谓可靠就是TCP能保证把数据一定送到目的地址。为了实现可靠,TCP采用有连接的,超时重传,应答机制等。而UDP则没有这些,也不能保证数据一定能送到; TCP结构复杂,消耗资源多,建立过程较慢较复杂。UDP结构相对简单,消耗资源少,建立过程较快; TCP基于流模式,UDP是数据报模式。TCP把数据看成一连串无结构的字节流,没有边界,一段段传输构成了整个数据块。通过发送缓冲区和接受缓冲区来存储数据流。而UDP数据报模式,每一个数据报都是一个独立的对象,有着指定的大小。 TCP连接只能是点到点,而UDP可以一对一,一对多或者多对多。TCP只能是点到点原因很简单,因为TCP的传输前要先建立连接。因此,广播和多播只能采用UDP数据报的方式。 TCP有确认,重传,拥赛控制机制,UDP在没有建立连接或者对方已经退出的情况下任然会继续发送数据,导致通信流量的浪费。

IP首部,TCP首部,UDP首部

TCP和UDP应用场景

如何实现可靠的UDP

DNS 协议,是用UDP,为什么

TCP客户与服务器模型,用到哪些函数

(9条消息) TCP/IP(7)-TCP Server与TCP Client(linux套接字)_oscarwin的博客-CSDN博客_tcpserver和tcpclient区别

UDP客户与服务器模型,用到哪些函数

(9条消息) TCP/IP(8)-UDP Server与UDP Client(linux套接字)_oscarwin的博客-CSDN博客

TCP请求到接收的全过程,越详细越好 / 详细说明TCP状态迁移过程

(9条消息) TCP/IP(6)--TCP协议详解_oscarwin的博客-CSDN博客

tcp三次握手(详细、状态转移)

TCP四次挥手过程及各种状态

三次握手、四次挥手 了解网络编程是吧,三次握手为什么要三次

如果只有两次,假设主机A发送的第一个请求包延时,主机A在等待一段时间后重新发送一个请求包,完成数据连接并断开。但是这个时候上次的发的请求包才到达主机B,这时主机B认为是又一次连接,因此发送一个请求包给A,但是A并没有发送新的请求因此会丢失该数据包。最后,B就一直等待A发送数据,浪费了资源。另外,3次握手也更加安全,加大了攻击的难度。如果只有两次,一个发送一个应答,那么攻击着可以采用IP欺骗,发动SYN洪水攻击,并且服务端还都是ESTABLISHED状态。 TCP通信是一种全双工的通信,可以进行半关闭(与半打开区别:半打开是连接后的客户端和服务端有一端异常关闭了),所谓半关闭是指可以只关闭从A到B的方向,而B到A的方向还可以继续传输。因此,需要在客户端和服务器端分别进行关闭。

三次握手为什么不是两次或者四次?

在三次握手过程中,如果服务器一直收不到客户端的ack会发生什么

当客户端发送第三个ACK给服务器丢失时,在过程中服务器处于SYNC_RECV状态,在收到第三个ACK后会进入ESTABLISHED状态。再等待一段时间(3s, 6s, 12s,)后没有收到客户端ACK,服务器会重新发出SYN+ACK,来触发客户端重新发送ACK。发送一定次数(默认是5,可以修改)还未收到客户端ACK后,服务器断开连接。若客户端认为连接已经建立并发送数据,服务器会发送RTS包响应错误。

三次握手和四次挥手状态变化

2MSL是什么状态?作用是什么?

TCP如果不是三次握手会怎么样?TCP怎么流控的?

TCP重发机制,Nagle算法

TCP的窗口滑动

tcp包中 syn等标志位放在哪一部分

那了解SYN攻击吗

TCP首部标志位

TCP数据传输确认报文(ack号的计算)

tcp校验块长度

TCP粘包怎么产生的,有什么应对措施以及每种措施的优缺点。

TCP 如何实现可靠

TCP拥塞控制

拥塞控制,流量控制

TCP的拥塞控制使用的算法和具体过程

流量控制解决了什么问题,怎么实现,接收窗口为0了怎么办

发送窗口、拥塞窗口、接收窗口之间的关系?

TIMEWAIT TimeWait状态?

TimeWait,MSL是多大?为什么设置这么大?

socket连接中用到的一些函数

数据包从一个主机发送到另一个主机发生了什么

HTTP/HTTPS

semlinker/awesome-http: HTTP、HTTP Cache、CORS、HTTPS、HTTP/2、Fiddler、WireShark、Web Crawler (github.com)

简单来说就是在HTTP协议上加上了SSL+TLS加密部分。用来保证传输数据的安全,并且可以验证网站服务器的真实身份。HTTPS标准端口是443,工作在传输层。相对应的HTTP标准端口是80,工作在应用层。

http/https 1.0、1.1、2.0

http的主要特点: 简单快速:当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了 灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能,使用服务器支持长连接,解决无连接的问题) 无状态:无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即客户端发送HTTP请求后,服务器根据请求,会给我们发送数据,发送完后,不会记录信息。(使用 cookie 机制可以保持 session,解决无状态的问题)

http1.1的特点 a、默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求 b、管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应 c、断点续传

http2.0的特点

HTTP 2.0与HTTP 1.1区别 - FrankYou - 博客园 (cnblogs.com)

a、HTTP/2采用二进制格式而非文本格式 b、HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应 c、使用报头压缩,HTTP/2降低了开销 d、HTTP/2让服务器可以将响应主动“推送”到客户端缓存中

get post的区别

区别一: get重点在从服务器上获取资源,post重点在向服务器发送数据; 区别二: get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的; post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的; 区别三: Get传输的数据量小,因为受URL长度限制,但效率较高; Post可以传输大量数据,所以上传文件时只能用Post方式; 区别四: get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等; post较get安全性较高;

返回状态码

200:请求被正常处理 204:请求被受理但没有资源可以返回 206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。 301:永久性重定向 302:临时重定向 303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上 304:发送附带条件的请求时,条件不满足时返回,与重定向无关 307:临时重定向,与302类似,只是强制要求使用POST方法 400:请求报文语法有误,服务器无法识别 401:请求需要认证 403:请求的对应资源禁止被访问 404:服务器无法找到对应资源 500:服务器内部错误 503:服务器正忙

http 协议头相关

http数据由请求行,首部字段,空行,报文主体四个部分组成 首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段

https与http的区别?如何实现加密传输? https如何加密的

  • https就是在http与传输层之间加上了一个SSL
  • 对称加密与非对称加密

http怎么解析出一个请求出来

分析过HTTP吗,讲讲HTTP的字段

HTTP和HTTPS HTTPS的连接

HTTP状态码

https握手过程

服务端HTTP建立网络连接成功后的端口号是什么?端口号范围?一个端口只能管理一个链接吗?

Reactor

介绍一下reactor模型是啥

深入理解Reactor 网络编程模型 - 知乎 (zhihu.com)

Reactor模型 - CodeBear - 博客园 (cnblogs.com)

Reactor 模型详解 - 掘金 (juejin.cn)

怎么理解reactor这种高并发的模式

Traceroute

Traceroute的原理

traceroute使用与实现原理分析 - 知乎 (zhihu.com)

(9条消息) traceroute 工作原理_Li-Yongjun的博客-CSDN博客_traceroute工作原理

Ping和traceroute的原理 - 腾讯云开发者社区-腾讯云 (tencent.com)

如何知道IP包经过那些路由器

Tinyhttpd

(9条消息) 从零开始的tinyhttpd项目_长门yuki的博客-CSDN博客

(9条消息) Tinyhttpd源码分析与总结_baddy你个小菜鸡的博客-CSDN博客

tinyhttpd有什么地方可以优化? epoll具体怎么去对tinyhttpd优化?

[(9条消息) Tinyhttpd for Windows_magictong的博客-CSDN博客](https://blog.csdn.net/magictong/article/details/53201038#:~:text=优化 1、 给客户端返回数据时,合并了需要发送的数据,使用send一次发送,而不是每次发送几个字符,不过这里没有进行send失败的错误处理,学习代码吧,如果是商用代码,send失败是需要重试的(当然,商用代码一般都是使用异步socket),这个地方之前作者分成多次发送的目的可能是为了体现网络数据传输的原理。 2、 合并了一些公用代码。 3、,代码里面直接写死了绑定80端口,如果需要由系统自动分配端口就把这句代码:u_short port %3D 80修改为u_short port %3D 0,绑死80端口是为了使用浏览器测试时比较方便。)

(9条消息) 服务器项目--Tinyhttpd_with_threadpool_epoll_田田天天甜甜的博客-CSDN博客

epoll详解——从功能到内核 - 知乎 (zhihu.com)

FTP

FTP 协议,端口号 主动模式,被动模式,为什么 如果让你实现FTP协议,你觉得有哪些字段(全程靠面试官引导)

RDMA介绍,为什么需要RDMA? RDMA做了什么?dpdk? RDMA编程都做些什么? RDMA项目介绍,举例 RDMA实际部署会遇到各种问题,你觉得有哪些技术难点? 知道业界的RDMA部署情况吗? RoCE和IB的发展趋势? 介绍一下你做的RDMA内容 ryu是什么?和odl的区别?介绍一下ryu做的东西 限速是怎么实现的? QoS是什么?怎么标记QoS? NFV是什么?为什么出现? RDMA传输与网卡交互的整个过程 介绍一下RDMA,RDMA和TCP/IP的结合?RDMA和DPDK? 详细介绍RDMA和TCP/IP的区别 RoCE组网会遇到的问题 openstack项目做了什么? openstack的网络组件之间的关系? openstack虚拟机访问外网的整个过程 介绍openstack neutron br-int和br-tun的区别?可以合并吗? 租户在br-int上的隔离是怎么做的? dhcp在哪个节点?怎么实现隔离? ovs了解吗?介绍一下做的东西?跟neutron有结合吗? neutron的了解程度? ovs queue的底层原理? ICMP请求到接收的全过程,越详细越好

RDMA拥塞控制、TCP拥塞控制

网卡接收数据到应用的全过程,越详细越好 网卡接收数据到内核?网卡什么时候产生中断? 网卡1000M的速率能发多少数据包? 内核是在中断的时候从网卡把数据全部取出来再经过协议栈吗?(中断上下部)

虚拟网络拓扑如何构建? 同一个物理机两个虚拟机通信需要经过哪些设备? 为什么有qbr 物理机ping虚拟机不同怎么排查?

MSS的大小是控制什么的? MTU和MSS 返回RST是为什么?

MSL和TTL的关系? netfilter了解吗?钩子函数? linux内核收发数据包的整个过程?netfilter的过程?哪里需要路由检测? NAT有哪些?

参考书籍

《图解TCP/IP》

《TCP/IP详解 卷一》

《图解HTTP》

《HTTP权威指南》

操作系统

基础

操作系统是什么

哈夫曼编码

32位系统最大支持多大的内存,为什么只能支持这么大的,这个32位具体指的是什么

float在计算机中如何表示

操作系统中的内存管理

内存模型

内存池

说一下页缓存

虚拟内存和物理内存

虚拟内存到物理内存的映射

共享内存如何实现

共享内存实现分为两种方式一种是采用mmap,另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程,就能实现这两个进程的通信,采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射,不指定映射的文件,但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现,只是其映射的是一种特殊文件系统下的文件,该文件是不能通过read和write访问的。

mmap和XSI机制中的共享内存的区别

1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。

2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。

系统调用与库函数(open, close, create, lseek, write, read)

共享内存有什么限制,注意的事情?

c++的共享内存实现

讨论了项目:内存池,主要问:为什么要做这个,说一说架构与优点。

为什么出现单线程出现磁盘io吞吐量会下降

自增运算符的步骤(内存,寄存器),有两个线程分别自增100次,问最后结果的最大最小值?

进程&线程

(9条消息) 进程与线程_oscarwin的博客-CSDN博客

进程和线程

线程,进程区别,python的线程和进程

孤儿进程与僵死进程

孤儿进程是怎么产生的?

僵死进程是怎么产生的?

僵死进程的危害?

如何避免僵死进程的产生?

进程与线程的区别

进程是资源分配的基本单位,线程是cpu调度和程序执行的最小单位。同一个进程中并行运行多个线程,就是对在同一台计算机上运行多个进程的模拟。 进程有独立的地址空间,而同一进程中的线程共享该进程的地址空间。 线程之间的通信比较方便。同一进程下的线程共享数据(比如全局变量,静态变量,打开的文件,子进程),如何处理好这些访问的同步与互斥正是编写多线程程序的难点。 多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。 线程的执行与进程是有区别的。每个独立的线程有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。 Linux中进程具有父子关系,形成进程树,但是线程是平等的没有父子关系.面向多核的服务器端编程中,需要习惯多进程并且Linux下创建进程开销小

同一进程中的线程之间变量共享问题

线程私有:线程栈,寄存器,程序寄存器 共享:堆,地址空间,全局变量,静态变量

有了进程为什么还要线程

一个任务可以分成多个子任务并行执行,他们是对一个对象在操作。 线程不需要像进程一样维护那么多信息,因此创建和销毁速度更快,拥有同一个地址空间,访问很容易 任务有CPU密集和IO等待,的过程,最大化利用CPU

线程比进程具有哪些优势?

什么时候用多进程?什么时候用多线程?

一个进程是怎么启动的

怎么创建进程

了解多线程编程吗,会有什么问题

进程间的通信方式 多进程通信机制 多进程通信 进程间通讯的方法

进程间通信(IPC)介绍 - ZH奶酪 - 博客园 (cnblogs.com)

信号量:信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 命名管道 (named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

匿名管道与命名管道的区别

匿名管道只能在具有公共祖先的两个进程间使用。

共享文件映射mmap

mmap建立进程空间到文件的映射,在建立的时候并不直接将文件拷贝到物理内存,同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存,映射一个匿名文件,可以实现父子进程间共享内存。

常见的信号有哪些?

SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM

IPC 进程间通信方式

进程间通信方式、线程间通信方式

操作系统 进程和线程的区别 多进程和多线程的优缺点 进程和线程区别?

多线程同步机制 多线程同步 多线程如何同步

在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段 在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使)

互斥锁与自旋锁的区别

互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。

知道这些锁之间的区别,使用场景?

线程和进程共享什么

多线程,任务怎么分发给多个线程,详细一点:使用了队列,是否加锁了,是否用了条件变量,使用条件变量有什么好处

同步和互斥

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。 同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源,如“第一类读写者模型”。

互斥锁、信号量、条件变量之间的区别?

你讲到了锁,用过哪些锁

锁的实现原理

c++之多线程中“锁”的基本用法

https://zhuanlan.zhihu.com/p/91062516 首发于 算法和工程札记

写文章 点击打开actor1996的主页 c++之多线程中“锁”的基本用法 c++之多线程中“锁”的基本用法 全人工无智能 全人工无智能 机器学习/人工智能 ​关注他 阿金金金等 189 人赞同了该文章 c++11

  1. 锁:mutex 锁,是生活中应用十分广泛的一种工具。锁的本质属性是为事物提供“访问保护”,例如:大门上的锁,是为了保护房子免于不速之客的到访;自行车的锁,是为了保护自行车只有owner才可以使用;保险柜上的锁,是为了保护里面的合同和金钱等重要东西……

在c++等高级编程语言中,锁也是用来提供“访问保护”的,不过被保护的东西不再是房子、自行车、金钱,而是内存中的各种变量。此外,计算机领域对于“锁”有个响亮的名字——mutex(互斥量),学过操作系统的同学对这个名字肯定很熟悉。

Mutex,互斥量,就是互斥访问的量。这种东东只在多线程编程中起作用,在单线程程序中是没有什么用处的。从c++11开始,c++提供了std::mutex类型,对于多线程的加锁操作提供了很好的支持。下面看一个简单的例子,对于mutex形成一个直观的认识。

Demo1——无锁的情况

假定有一个全局变量counter,启动两个线程,每个都对该变量自增10000次,最后输出该变量的值。在第一个demo中,我们不加锁,代码文件保存为:mutex_demo1_no_mutex.cpp

#include #include #include #include #include #include

int counter = 0; void increase(int time) { for (int i = 0; i < time; i++) { // 当前线程休眠1毫秒 std::this_thread::sleep_for(std::chrono::milliseconds(1)); counter++; } }

int main(int argc, char** argv) { std::thread t1(increase, 10000); std::thread t2(increase, 10000); t1.join(); t2.join(); std::cout << "counter:" << counter << std::endl; return 0; } 为了显示多线程竞争导致结果不正确的现象,在每次自增操作的时候都让当前线程休眠1毫秒 如果没有多线程编程的相关经验,我们可能想当然的认为最后的counter为20000,如果这样想的话,那就大错特错了。下面是两次实际运行的结果:

[root@2d129aac5cc5 demo]# ./mutex_demo1_no_mutex counter:19997 [root@2d129aac5cc5 demo]# ./mutex_demo1_no_mutex counter:19996 出现上述情况的原因是:自增操作"counter++"不是原子操作,而是由多条汇编指令完成的。多个线程对同一个变量进行读写操作就会出现不可预期的操作。以上面的demo1作为例子:假定counter当前值为10,线程1读取到了10,线程2也读取到了10,分别执行自增操作,线程1和线程2分别将自增的结果写回counter,不管写入的顺序如何,counter都会是11,但是线程1和线程2分别执行了一次自增操作,我们期望的结果是12!!!!!

轮到mutex上场。

Demo2——加锁的情况

定义一个std::mutex对象用于保护counter变量。对于任意一个线程,如果想访问counter,首先要进行"加锁"操作,如果加锁成功,则进行counter的读写,读写操作完成后释放锁(重要!!!); 如果“加锁”不成功,则线程阻塞,直到加锁成功。

#include #include #include #include #include #include

int counter = 0; std::mutex mtx; // 保护counter

void increase(int time) { for (int i = 0; i < time; i++) { mtx.lock(); // 当前线程休眠1毫秒 std::this_thread::sleep_for(std::chrono::milliseconds(1)); counter++; mtx.unlock(); } }

int main(int argc, char** argv) { std::thread t1(increase, 10000); std::thread t2(increase, 10000); t1.join(); t2.join(); std::cout << "counter:" << counter << std::endl; return 0; } 上述代码保存文件为:mutex_demo2_with_mutex.cpp。先来看几次运行结果:

[root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex counter:20000 [root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex counter:20000 [root@2d129aac5cc5 demo]# ./mutex_demo2_with_mutex counter:20000 这次运行结果和我们预想的一致,原因就是“利用锁来保护共享变量”,在这里共享变量就是counter(多个线程都能对其进行访问,所以就是共享变量啦)。

简单总结一些std::mutex:

  1. 对于std::mutex对象,任意时刻最多允许一个线程对其进行上锁
  2. mtx.lock():调用该函数的线程尝试加锁。如果上锁不成功,即:其它线程已经上锁且未释放,则当前线程block。如果上锁成功,则执行后面的操作,操作完成后要调用mtx.unlock()释放锁,否则会导致死锁的产生
  3. mtx.unlock():释放锁
  4. std::mutex还有一个操作:mtx.try_lock(),字面意思就是:“尝试上锁”,与mtx.lock()的不同点在于:如果上锁不成功,当前线程不阻塞。
  5. lock_guard 虽然std::mutex可以对多线程编程中的共享变量提供保护,但是直接使用std::mutex的情况并不多。因为仅使用std::mutex有时候会发生死锁。回到上边的例子,考虑这样一个情况:假设线程1上锁成功,线程2上锁等待。但是线程1上锁成功后,抛出异常并退出,没有来得及释放锁,导致线程2“永久的等待下去”(线程2:我的心在等待永远在等待……),此时就发生了死锁。给一个发生死锁的 :

Demo3——死锁的情况(仅仅为了演示,不要这么写代码哦)

为了捕捉抛出的异常,我们重新组织一下代码,代码保存为:mutex_demo3_dead_lock.cpp。

#include #include #include #include #include #include

int counter = 0; std::mutex mtx; // 保护counter

void increase_proxy(int time, int id) { for (int i = 0; i < time; i++) { mtx.lock(); // 线程1上锁成功后,抛出异常:未释放锁 if (id == 1) { throw std::runtime_error("throw excption...."); } // 当前线程休眠1毫秒 std::this_thread::sleep_for(std::chrono::milliseconds(1)); counter++; mtx.unlock(); } }

void increase(int time, int id) { try { increase_proxy(time, id); } catch (const std::exception& e){ std::cout << "id:" << id << ", " << e.what() << std::endl; } }

int main(int argc, char** argv) { std::thread t1(increase, 10000, 1); std::thread t2(increase, 10000, 2); t1.join(); t2.join(); std::cout << "counter:" << counter << std::endl; return 0; } 执行后,结果如下图所示:

[root@2d129aac5cc5 demo]# ./mutex_demo3_dead_lock id:1, throw excption.... 程序并没有退出,而是永远的“卡”在那里了,也就是发生了死锁。

那么这种情况该怎么避免呢? 这个时候就需要std::lock_guard登场了。std::lock_guard只有构造函数和析构函数。简单的来说:当调用构造函数时,会自动调用传入的对象的lock()函数,而当调用析构函数时,自动调用unlock()函数(这就是所谓的RAII,读者可自行搜索)。我们修改一下demo3。

Demo4——避免死锁,lock_guard

demo4保存为:mutex_demo4_lock_guard.cpp

#include #include #include #include #include #include

int counter = 0; std::mutex mtx; // 保护counter

void increase_proxy(int time, int id) { for (int i = 0; i < time; i++) { // std::lock_guard对象构造时,自动调用mtx.lock()进行上锁 // std::lock_guard对象析构时,自动调用mtx.unlock()释放锁 std::lock_guardstd::mutex lk(mtx); // 线程1上锁成功后,抛出异常:未释放锁 if (id == 1) { throw std::runtime_error("throw excption...."); } // 当前线程休眠1毫秒 std::this_thread::sleep_for(std::chrono::milliseconds(1)); counter++; } }

void increase(int time, int id) { try { increase_proxy(time, id); } catch (const std::exception& e){ std::cout << "id:" << id << ", " << e.what() << std::endl; } }

int main(int argc, char** argv) { std::thread t1(increase, 10000, 1); std::thread t2(increase, 10000, 2); t1.join(); t2.join(); std::cout << "counter:" << counter << std::endl; return 0; } 执行上述代码,结果为:

[root@2d129aac5cc5 demo]# ./mutex_demo4_lock_guard id:1, throw excption.... counter:10000 结果符合预期。所以,推荐使用std::mutex和std::lock_guard搭配使用,避免死锁的发生。

  1. std::lock_guard的第二个构造函数 实际上,std::lock_guard有两个构造函数,具体的(参考:cppreference):

explicit lock_guard( mutex_type& m ); (1) (since C++11) lock_guard( mutex_type& m, std::adopt_lock_t t ); (2) (since C++11) lock_guard( const lock_guard& ) = delete; (3) (since C++11) 在demo4中我们使用了第1个构造函数,第3个为拷贝构造函数,定义为删除函数。这里我们来重点说一下第2个构造函数。

第2个构造函数有两个参数,其中第二个参数类型为:std::adopt_lock_t。这个构造函数假定:当前线程已经上锁成功,所以不再调用lock()函数。这里不再给出具体的例子,如果想了解这种构造函数是如何工作的,可以看这里,链接中给的例子很简洁。

总结:本篇主要讲述c++多线程编程中锁的基本用法,主要展示了std::mutex和std::lock_guard的用法。其实c++还提供了std::lock_guard的加强版:std::unique_lock,以后找机会我们再补充它的使用方法,简单来说:相比于std::lock_guard, std::unique_lock提供了更多的接口,也就使其更加灵活,但性能方面也会有些受损。

(水平有限,有问题随时反馈,我会尽快回复和修正^_^)

编辑于 2021-07-13 20:43 「真诚赞赏,手留余香^_^」 赞赏 还没有人赞赏,快来当第一个赞赏的人吧! C++ 多线程 死锁 ​赞同 189​ ​17 条评论 ​分享 ​喜欢 ​收藏 ​申请转载 ​

赞同 189

分享

写评论 | 你和作者是 互联网 行业同行

17 条评论 默认 最新 亚瑟的微风 亚瑟的微风 最后面提到的应该是std::unique_lock而不是std::uniqe_lock,拼写错误。

2020-11-18 ​回复 ​2 全人工无智能 全人工无智能 作者 谢谢,已更 2021-07-13 ​回复 ​1 不停奔跑 不停奔跑 你的例子中 mtx.lock();作用于counter, 那么mtx.lock();之后能不能加入超过一个以上的临界变量

2021-05-10 ​回复 ​1 Ben Ben 死锁那个例子,我的好像没锁死,另一个线程在加 counter 2021-12-09 ​回复 ​1 大智若愚 大智若愚 点赞 2019-11-10 ​回复 ​3 知乎用户LOnnpr 知乎用户LOnnpr 可以转载吗?

2020-05-29 ​回复 ​1 全人工无智能 全人工无智能 作者 可以啊 2020-05-30 ​回复 ​1 一个达布刘 一个达布刘 请问mutex可以声明为局部变量吗

2020-05-13 ​回复 ​1 天下苦秦久矣 天下苦秦久矣 可以声明成局部变量,但是这样没有意义了,mutex本身就是用于多线程访问资源的互斥,必须保证可以被多线程公共访问

2020-05-13 ​回复 ​5 sunshine sunshine 那个抛出异常,线程2一直在等线程1应该不叫死锁吧,死锁不应该是线程1与线程2相互等待嘛,不是很理解死锁,所以有点疑惑[发呆] 05-23 ​回复 ​赞 下山买菜 下山买菜 从定义上说,确实不满足的死锁的第四个条件:循环等待,但是结果都是一样的,大家都不能正常工作了

09-12 ​回复 ​赞 知非改过 知非改过 讲得通俗易懂,旁征博引,小白也能看得懂,赞 2021-07-13 ​回复 ​赞 那里 那里 demo4最后counter的值为什么不是20000呢 2021-06-19 ​回复 ​赞 abcd abcd t1线程抛出异常了啊,只有t2执行了 2021-06-28 ​回复 ​赞 亚瑟的微风 亚瑟的微风 对于你的第一个和第二个例子,如果线程数改为10个,那么是否使用锁,在时间开销上相差10倍。这性能差距太大了。

2020-11-18 ​回复 ​赞 亚瑟的微风 亚瑟的微风 第一个例子,不容易复现bug。建议改成10个线程,稳出bug

2020-11-18 ​回复 ​赞 abcd abcd 我开了10个线程,跑了十几次,还是没复现出bug。。。 2021-06-28 ​回复 ​赞

写评论 | 你和作者是 互联网 行业同行

文章被以下专栏收录 算法和工程札记 算法和工程札记 推荐阅读 C++ 多线程(五):读写锁的实现及使用样例 大家好,欢迎来到 @自由技艺 的 C++ 系列专题,今天的主题是多线程中的读写锁。首先,我们将介绍下读写锁的原理,其次基于互斥量用 C++ 实现读写锁,最后,本文给出读写锁的使用样例。0、前…

自由技艺 发表于C++ 系... 无锁队列的实现 C++ 无锁队列的实现 C++ 我以性命担保 C++ 多线程安全无锁日志系统 服务器编程中,一个优秀的日志系统需要满足几个条件 高效,日志系统不应占用太多资源 简洁,引入一个类似log4cpp这么庞大的库,不是一个令人愉快的做法 线程安全,服务器中各个线程都能同时…

叶东富 发表于网络高并发... 一个有限状态机的C++实现 先放一个用C++11实现的FSM的代码: kuafu咱们先来看一下什么是有限状态机(Finite-state machine, FSM), 先给一个 百度百科的解释简单说就是作一件事可能会经过多个不同状态的转换, 转换依赖…

扫帚的影子 发表于C++的那...

选择语言 选择语言

死锁

(9条消息) 死锁_oscarwin的博客-CSDN博客

多个进程都被阻塞,并一直处于这样的状态,这种状态称为死锁。 死锁分为两类:资源死锁和通信死锁。资源死锁是由于多个进程或线程同时获取多个资源时发生;通信死锁是由于网络延时或丢包,导致两个进程都在等待对方的数据。可以通过超时重发机制解决。

死锁的避免

模拟死锁 一个锁会出现死锁嘛

死锁的形成条件 死锁的四个条件?怎么解决?

互斥条件。一个资源不能同时被多个进程访问 占有和等待条件。一个资源已经被另一个进程占用,那么该进程只能等待。 不可抢占。已经分配给某个进程的资源,不能通过调度的方式抢占该资源。 环路等待条件。多个进程和资源组成环路。

直接忽略该问题; 检测死锁并恢复;抢占式恢复。回滚恢复。恢复到上一个没有占用资源的状态,进程的部分工作丢失杀死部分进程。从发生死锁的进程中,杀死部分进程,释放占用的资源 仔细分配资源,动态避免死锁; 破坏死锁发生条件,避免死锁。虚拟化技术,严格按照进程顺序执行

死锁检测算法,如何实现

C++里面的锁。自旋锁当中aba问题怎样解决

怎么知道是io浪费了时间的,想出来的怎么实际确认的

多线程中的私有数据怎么保护(这里没明白,我觉得线程的私有数据不是独占的吗,应该不用保护吧)

一个函数调用发生了什么

linux系统

Linux的API

LINUX中进程和线程使用的几个函数?

fork函数

fork与vfork区别

fork和vfork都用于创建子进程。但是vfork创建子进程后,父进程阻塞,直到子进程调用exit()或者excle()。 对于内核中过程fork通过调用clone函数,然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体,然后复制其他关于内存,文件,寄存器等信息。fork采用写时拷贝技术,因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表,因为父进程会一直阻塞,直接使用父进程页表。

exit()与_exit()区别

exit()清理后进入内核,_exit()直接陷入内核。

IO模型

五种IO模型:阻塞IO,非阻塞IO,IO复用,信号驱动式IO,异步IO

linux - Linux IO模式及 select、poll、epoll详解_个人文章 - SegmentFault 思否

linux下非阻塞io库 epoll - 简书 (jianshu.com)(搞懂这篇文章,关于IO复用的问题就信手拈来了)

I/O多路复用技术 .什么是IO复用,什么是非阻塞IO

阻塞IO和非阻塞IO

同步、异步、阻塞IO、非阻塞IO

select和epoll的区别

epoll的LT和ET epoll LT,ET模式 项目中 epoll 采用的是 ET 还是 LT 模式,他们各自有什么优缺点,问什么要设计这两种模式

select poll epoll 的区别

select:是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回** 存在的问题:

  1. 内置数组的形式使得select的最大文件数受限与FD_SIZE;
  2. 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;
  3. 轮寻排查当文件描述符个数很多时,效率很低;

poll:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。**

epoll:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。

  • 为什么使用IO多路复用,最主要的原因是什么?
  • epoll有两种触发模式?这两种触发模式有什么区别?编程的时候有什么区别?
  • 上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。

命令行

linux的wget,ls,du和awk的用法?

linux命令,sed,awk , grep

  • sed, awk, grep三个超强大的命名,分别用与格式化修改,统计,和正则查找

熟悉linux常用命令,那用过tcpdump吗

Linux命令 在一个文件中,倒序打印第二行前100个大写字母

cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev

与CPU,内存,磁盘相关的命令(top,free, df, fdisk)

网络相关的命令netstat,tcpdump等

ipcs和ipcrm命令

查找当前目录以及字母下以.c结尾的文件,且文件中包含”hello world”的文件的路径

创建定时任务

linux软链接和硬链接的区别;

linux文件系统包含哪些种类,ext文件系统用什么数据结构实现的;

Linux怎么进行malloc的

Linux的子进程如何生成

linux下内存管理

(9条消息) 操作系统对内存管理_oscarwin的博客-CSDN博客

linux虚拟地址与物理地址如何对应

虚拟内存的作用?

虚拟内存的实现?

操作系统层面对内存的管理?

内存池的作用?STL里内存池如何实现?

进程空间和内核空间对内存的管理不同?

Linux的slab层,VAM?

Linux是如何避免内存碎片的

  1. 伙伴算法,用于管理物理内存,避免内存碎片;
  2. 高速缓存Slab层用于管理内核分配内存,避免碎片。

伙伴算法

高端内存

进程调度

Linux进程分为两种,实时进程和非实时进程

优先级分为静态优先级和动态优先级,优先级的范围

调度策略,FIFO,LRU,时间片轮转

交互进程通过平均睡眠时间而被奖励

线性地址,逻辑地址,物理地址是什么关系,如何映射的

linux下C++性能调优?linux基本命令?

linux 创建一个文件 修改一个文件的权限 组权限只读,个人能读能写

linux 一个可执行文件,不让他的输出输出

linux的GDB调试

gdb调试过程 break是怎么打断点的

ssh相关,什么如果网络断了,在服务器上跑的程序会怎么样,tmux原理

线程池,内存池 自己动手实现一遍

参考书籍

《现代操作系统》

《APUE》

《UNP》

《LINUX内核设计与实现》

《深入理解LINUX内核》

分布式系统

map_reduce原理

负载均衡

CDN

设计模式

项目中是否用到了设计模式,了解哪些设计模式,能举个例子么。

了解哪些设计模式

设计模式有哪些?写一个单例模式

单例模式线程安全的写法

设计模式中的状态机模式

STL里的迭代器使用了迭代器模式

MVC的理解

数据库

什么是分区表,有什么作用

什么是全局二级索引,有什么作用

(9条消息) MySQL数据库_oscarwin的博客-CSDN博客

SQL语言(内外连接,子查询,分组,聚集,嵌套,逻辑)

数据库的索引

索引(index)是一种排序数据结构,为了提高在属性A上查找具有某个特定值的元组的效率。它是一棵二叉查找树的键值对,大型关系的索引实现技术是DBMS实现最重要的核心问题。

索引的类型

B树索引,实际上是分为B树索引和B+树索引两种方法。MySQL用的B+树,MongoDB用的B树。 B树:1. 对于数据库查找,索引的文件都存在磁盘上,磁盘IO的是非常消耗时间的,因此要减少磁盘IO的次数,而B树相比于二叉树,或者其变种AVL和RD-Tree的高度更低,因此磁盘IO的次数更好;2. B树是平衡树,查找,删除和插入是相对稳定的。可以很好的利用局部性原理。 B+树:1. 所有的数据都保存在叶节点上,查找数据都必须到达叶子节点,因此其查找效率是恒定的; 2. B+树的叶子节点有指针指向下一个叶子节点,形成了一个链表。而叶子节点间又是排序的,因此对于范围查找只需要先找到头的位置,然后遍历链表就可以了; 3. B+树的非叶子节点不需要存储数据,只作为索引,因此B+数的阶数可以更高,故单个节点保存的数据个数会更多,使得B+树更加矮胖,因此磁盘IO的次数会更少。

索引的优化

从索引上优化:索引的列单独放在比较符号的一侧;索引不要太长,越长的索引会导致整个索引项越庞大,导致查询效率低;建立多列索引,而不是为每个列建立一个独立的索引;将选择性更高的列放在前面 从SQL语言上优化:尽量减少全表查找,对查询进行优化;尽量避免null判断;用具体字段代替“*”;很多情况下可以用exist代替in

MySQL索引方法?索引的优化?

事物ACID特性

原子性(Atomicity):一个事务是不可分割的,要么都发生,要么都不发生; 一致性(Consistency):事务开始和结束前后,数据保存一致性,A和B转账,必须保证转账前后A和B账户的总额不变; 隔离性(Isolation):多个事务并发执行,避免事务之间的干扰。一个事务在提交前所做的修改对其他事务通常是不可见的; 持久性(Durability):事务一旦提交,所做的修改就永久保存在数据库中,即使此时服务器崩溃,修改的数据也不会丢失。

事务的四个隔离级别

查询优化(从索引上优化,从SQL语言上优化)

B-与B+树区别?

介绍 B+Tree

B+ Tree page 如何在内存和磁盘上转移

B+ Tree 相比于其他索引结构的优点在哪

数据库的命令

c++或go用什么数据库框架(应该是用什么库,怎么用的意思)

Mysql MyISAM和InnoDB的区别

MySQL的联合索引(又称多列索引)是什么?生效的条件?

知道 MySQL 和 Postgres 的性能差异吗

了解多表 join 吗

知道哪些 join 方法,分别介绍一下

分库分表

OLTP 和 OLAP 各适用于什么场景

OLTP 和 OLAP 实现上有何区别

数据集隔离级别 隔离级别

mysql 四种隔离级别 - gao_jian - 博客园 (cnblogs.com)

主键索引和非主键索引的区别

MVCC

Innodb怎么解决幻读的

第三范式了解吗

(9条消息) 什么是第一,第二,第三范式_xd大勇的博客-CSDN博客_什么叫第一范式

第三范式 - 维基百科,自由的百科全书 (wikipedia.org)

redis的线程模型

实际业务数据量有多大 测试数据量有多大,有多少行

参考书籍

《数据库系统概念》

《高性能MySQL》

coding

排序

(9条消息) 排序算法比较_oscarwin的博客-CSDN博客

  • 排序算法当然是基础内容了,必须至少能快速写出,快排,建堆,和归并
  • 每种算法的时间空间复杂度,最好最差平均情况

排序方法,他们有什么优缺点

各种排序算法的时间复杂度

基数排序

手写快排 快速排序 手撕快排

快速排序、堆排序

各个排序的实现、时间复杂度、应用场景

三种时间复杂度为O(n)的排序

输出排名30%的

手撕代码环节:快速排序

位运算

(9条消息) 位运算面试题常用技巧_oscarwin的博客-CSDN博客

分别设置和清除一个整数的第三位?

 #define BIT3 (0x1<<3)

static int a;

void set_bit3(void){ 

    a |= BIT3;

} 

void clear_bit3(void){ 

    a &= ~BIT3;

} 

数组

前 K个最大的数

在一个数组中有一个数字只出现1次外,其它数字都成对出现,如何找出这个数字?如果有两个数字出现1次呢?

编程题:给你数组,有n-1 个不重复数,取值范围在1~n。输出缺少的数字。

给定一个数组,相邻元素的值差1,查找某个数字x在数组中出现的下标

写题,一个是一个2n大小的数组,其中一个数出现了n次,剩下的数都是出现一次,找出那个数

写一个递归版求数组最小值(一听这个需求觉得很蒙,不过还是写出来了)

m*n 的网络,从左上角到右下角一共有多少种走法?(dp/回溯) [LC62 Unique Paths]

62. 不同路径 - 力扣(LeetCode)

调整数组左边奇数右边偶数

做题:力扣456题,力扣11题。

456. 132 模式 - 力扣(LeetCode)

11. 盛最多水的容器 - 力扣(LeetCode)

做题:力扣47题。

47. 全排列 II - 力扣(LeetCode)

排序数组中查找某个数字的左右边界

原地合并a,b两个有序数组,a数组空间足够,大概这个意思

LC41 First Missing Positive

41. 缺失的第一个正数 - 力扣(LeetCode)

做算法 一个含N个数的随机无序数组,每次操作可以使随机N-1个数+1,求需要至少多少次能使整个数组所有数相等。

编程题:斐波那契数列。

914. 卡牌分组

https://leetcode.cn/problems/x-of-a-kind-in-a-deck-of-cards/ 给定一副牌,每张牌上都写者一个整数。 此时,你需要选定一个数字 $\mathrm{x}$ ,使我们可以将整副牌按下述规则分成 1 组 或更㝖组:

  • 每组都有 $x$ 张牌。
  • 组内所有的牌上都㝍者相同的整数。 仅当你可选的 $x>=2$ 时返回 true。

[for search] 给定一副牌,每张牌上都写者一个整数。 此时,你需要选定一个数字 x ,使我们可以将整副牌按下述规则分成 1 组 或更㝖组: 每组都有 x 张牌。 组内所有的牌上都㝍者相同的整数。 仅当你可选的 x>=2 时返回 true。

[编程题]雀魂启动!

链接:https://www.nowcoder.com/questionTerminal/448127caa21e462f9c9755589a8f2416
来源:牛客网


首页
题库
面试
学习
求职
讨论区
竞赛

创作者中心
59


首页 > 试题广场 > 雀魂启动!
[编程题]雀魂启动!
热度指数:14864时间限制:C/C++ 1秒,其他语言2秒空间限制:C/C++ 32M,其他语言64M
算法知识视频讲解
小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。
于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:

总共有36张牌,每张牌是1~9。每个数字4张牌。
你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
14张牌中有2张相同数字的牌,称为雀头。
除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)

例如:
1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌
1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌
1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。

现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。

输入描述:
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。


输出描述:
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0
示例1
输入
1 1 1 2 2 2 5 5 5 6 6 6 9
输出
9
说明
可以组成1,2,6,7的4个刻子和9的雀头
示例2
输入
1 1 1 1 2 2 3 3 5 6 7 8 9
输出
4 7
说明
用1做雀头,组123,123,567或456,789的四个顺子
示例3
输入
1 1 1 2 2 2 3 3 3 5 7 7 9
输出
0
说明
来任何牌都无法和牌

备注:
请不要根据自己的常识为题目增加未提及的条件

对于20%的数据,保证和牌时一定是4个刻子+雀头的形式
马上挑战算法知识视频讲解
添加笔记
求解答(77)
邀请回答
收藏(526)
分享
提交结果有问题?
解析
暂无官方题目解析,去下面的讨论区看看吧!
84个回答

13篇题解
添加回答
10

现在 从36张牌中抽取了13张牌 在剩下的23张牌中 再取一张牌 取到哪几种数字牌可以和牌 



oo201710311708385
C++ 借助map解决
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
#include
#include
#include
using namespace std;
bool isHu(map mp, int num){
    if(num<=0)return true;
    while(mp[mp.begin()->first]==0)mp.erase(mp.begin());
    map::iterator it = mp.begin();
    if(num%3!=0 && (it->second)>=2){
        mp[it->first]-=2;
        if(isHu(mp, num-2))return true;
        mp[it->first]+=2;
    }
    if((it->second)>=3){
        mp[it->first]-=3;
        if(isHu(mp, num-3))return true;
        mp[it->first]+=3;
    }
    if((it->second)>0 && mp[(it->first)+1]>0 && mp[(it->first)+2]>0){
        mp[it->first]--;
        mp[(it->first)+1]--;
        mp[(it->first)+2]--;
        if(isHu(mp, num-3))return true;
        mp[(it->first)]++;
        mp[(it->first)+1]++;
        mp[(it->first)+2]++;
    }
    return false;
}
int main(){
    map mp;
    int tmp;
    for(int i=0;i<13;i++){
        cin>>tmp;
        mp[tmp]++;
    }
    vector ans;
    for(int i=1;i<10;i++){
        if(mp[i]<4){
            ++mp[i];
            if(isHu(mp, 14))
                ans.push_back(i);
            --mp[i];
        }
    }
    if(ans.empty())cout<<0< res = new ArrayList<>();
        for (int i = 0; i < 13; i++) {
            int num = sc.nextInt();
            state[num - 1]++;
        }
        for (int i = 0; i < 9; i++) {
            if (state[i] < 4) {
                int num = i + 1;
                System.arraycopy(state, 0, helpArr, 0, 9);
                helpArr[i]++;
                if (canHu(helpArr, 14, false)) res.add(num);
            }
        }
        if (res.isEmpty()) System.out.println(0);
        else {
            StringBuffer sbf = new StringBuffer();
            sbf.append(res.get(0));
            for (int i = 1; i < res.size(); i++) {
                sbf.append(" ");
                sbf.append(res.get(i));
            }
            System.out.println(sbf.toString());
        }
    }

    private boolean canHu(int[] arr, int total, boolean hasHead) {
        if (total == 0) return true;
        if (!hasHead) {
            for (int i = 0; i < 9; i++) {
                if (arr[i] >= 2) {
                    arr[i] -= 2;
                    if (canHu(arr, total - 2, true)) return true;
                    arr[i] += 2;
                }
            }
            return false;
        } else {
            for (int i = 0; i < 9; i++) {
                if (arr[i] > 0) {
                    if (arr[i] >= 3) {
                        arr[i] -= 3;
                        if (canHu(arr, total - 3, true)) return true;
                        arr[i] += 3;
                    }
                    if (i + 2 < 9 && arr[i + 1] > 0 && arr[i + 2] > 0) {
                        arr[i]--;
                        arr[i + 1]--;
                        arr[i + 2]--;
                        if (canHu(arr, total - 3, true)) return true;
                        arr[i]++;
                        arr[i + 1]++;
                        arr[i + 2]++;
                    }
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        new Main().sln();
    }
}

发表于 2019-08-08 14:37回复(21)举报
29

牛客7587184号
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
def isHu(nums):
    """
    判断是否可以胡牌
    :param nums:
    :return:
    """
    if not nums:
        return True
    n = len(nums)
    count0 = nums.count(nums[0])
    # 没出现过雀头,且第一个数字出现的次数 >= 2,去掉雀头剩下的能不能和牌
    if n % 3 != 0 and count0 >= 2 and isHu(nums[2:]) == True:
        return True
    # 如果第一个数字出现次数 >= 3,去掉这个刻子后看剩下的能和牌
    if count0 >= 3 and isHu(nums[3:]) == True:
        return True
    # 如果存在顺子,移除顺子后剩下的能和牌
    if nums[0] + 1 in nums and nums[0] + 2 in nums:
        last_nums = nums.copy()
        last_nums.remove(nums[0])
        last_nums.remove(nums[0] + 1)
        last_nums.remove(nums[0] + 2)
        if isHu(last_nums) == True:
            return True
    # 以上条件都不满足,则不能和牌
    return False

def main(nums):
    """
    遍历所有可以抓到的牌看能不能胡牌
    :return:
    """
    d = {}
    for i in nums:
        d[i] = d.get(i,0) + 1
    card_list = set(range(1,10)) - {i for i,v in d.items() if v==4}
    res = []
    for i in card_list:
        if isHu(sorted(nums + [i])):  # 如果这种抽牌方式可以和牌
            res.append(i)  # 加入和牌类型列表
    res = ' '.join(str(x) for x in sorted(res)) if res else '0'
    print(res)


s = input()
nums = [int(x) for x in s.split()]
main(nums)

发表于 2019-05-14 13:37回复(12)举报
12

Ferrari740
看了大佬的思路,写个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
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
#include
#include
#include
using namespace std;
bool ishu(vectornum)
{
    if (num.empty())
        return true;
    int count0 = 0;
    for(int i=0;i= 2)
    {
        vector newnum(num.begin() + 2, num.end());
        if (ishu(newnum))
            return true;
    }
    if (count0 >= 3)
    {
        vector newnum(num.begin() + 3, num.end());
        if (ishu(newnum))
            return true;
    }
    if(count(num.begin(),num.end(),num[0]+1)>0 && count(num.begin(), num.end(), num[0] + 2)>0)
    {
        vector newnum(num.begin() + 1, num.end());
        newnum.erase(find(newnum.begin(), newnum.end(), num[0] + 1));
        newnum.erase(find(newnum.begin(), newnum.end(), num[0] + 2));
        if (ishu(newnum))
            return true;
    }
    return false;
}
bool hupai(vectornum, int n)
{
    if (count(num.begin(), num.end(), n) == 4)
        return false;
    num.push_back(n);
    sort(num.begin(),num.end());
    return ishu(num);
}
int main()
{
    vector num;
    vector res;
    for (int i = 0; i < 13; ++i)
    {
        int tmp;
        cin >> tmp;
        if (tmp > 0 && tmp < 10)
            num.push_back(tmp);
        else
        {
            cout << "输入错误" << endl;
            return 0;
        }
    }
    for (int n = 1; n < 10; ++n)
    {
        if (hupai(num, n))
            res.push_back(n);
    }
    if (res.empty())
        cout << 0;
    else
        for (int i = 0; i < res.size(); ++i)
            cout << res[i]<<" ";
}

发表于 2019-06-28 20:32回复(0)举报
11

安迪小黑
参考大佬的思路,自己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
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
#include 
(720)#include 
using namespace std;

vector card(9);

//检查剩余的牌能否组成n个顺子或刻子
bool hasTrible(int n){
    if(n==0) return true;
    for(int i=0; i=3,则必定是刻子
        if(card[i]>2){   
            card[i] -= 3;
            if(hasTrible(n-1)){   //检查余下的牌能否组成n-1个顺子或刻子
                card[i] += 3;
                return true;
            }
            card[i] += 3;
        }
        //否则只能是顺子
        else if(i0 && card[i+1]>0 && card[i+2]>0){
                card[i]--;
                card[i+1]--;
                card[i+2]--;
                if(hasTrible(n-1)){
                    card[i]++;
                    card[i+1]++;
                    card[i+2]++;
                    return true;
                }
                card[i]++;
                card[i+1]++;
                card[i+2]++;
        }
    }
    return false;
}
//检查14张牌能否和牌
bool isWin(){
    for(int i=0; i<9; ++i){  //依次把1~9作为雀头拿出来,检查剩下的12张牌能否顺或刻子
        if(card[i]<2)
            continue;
        card[i] -= 2;
        if(hasTrible(4)){    
            card[i] += 2;
            return true;
        }
        card[i] += 2;
    }
    return false;
}

int main(){
    vector res;
    int tmp;
    for(int i=0; i<13; ++i){
        cin >> tmp;
        card[tmp-1]++;
    }
    for(int i=0; i<9; ++i){  //1~9依次添加,检查是否可以和牌
        if(card[i]>3)
            continue;
        card[i]++;
        if(isWin())           //如果添加的这张牌可以和牌,则将其加入输出结果
            res.push_back(i+1);
        card[i]--;
    }
    if(res.empty()) res.push_back(0);
    for(int i=0; i getRes(Integer[] data){
        //存储可行解
        ArrayList res=new ArrayList<>();
        //统计1~9出现的次数,count[i]统计的是i+1的次数。i从0~8
        for (Integer e : data) {
            count[e-1]++;
        }
        for (int i = 1; i <= 9; i++) {
            //1.visit
            if(count[i-1]<4){
                count[i-1]++;//说明该张牌可以被插入
            }else {
                continue;
            }
            //2.operate:after copying
            int[] countCp = Arrays.copyOf(count, count.length);
            if( canFindQueTou(countCp) ){
                res.add(i);
            }
            //3.backTrack
            count[i-1]--;
        }
        return res;
    }
    //找雀头
    private static boolean canFindQueTou(int[] count){
        int cnt=0;//记录删掉的牌
        for (int integer = 1; integer <= 9; integer++) {
            if(count[integer-1]>=2){//找到可以作为雀头的integer
                //1.visit
                count[integer-1]-=2;
                //2.operation
                cnt+=2;
                int[] countCp = Arrays.copyOf(count, count.length);
                if(canGoOn(countCp)){//&&后面也能正常进行
                    return true;
                }
                //3.backTrack
                count[integer-1]+=2;
            }
        }
        return false;
    }
    //给其它牌配顺子或者刻子!
    private static boolean canGoOn(int[] count) {
        for (int i = 1; i <= 7; i++) {
            if(count[i-1]==1||count[i-1]==2){//如果该牌只有1张或者两张,必然作为刻子
                if(count[i]>=count[i-1]&&count[i+1]>=count[i-1]){
                    count[i]-=count[i-1];
                    count[i+1]-=count[i-1];
                    count[i-1]-=count[i-1];
                    continue;
                }
                return false;
            }
            if(count[i-1]==3){//如果该牌有3张,作为顺子是当前可行step;3张都作为刻子看情况
                //能做刻子就做刻子,否则才做顺子?这种策略真的不会出错吗?
                //有没有做顺子可以做刻子不行的情况?
                //111 222 333 顺刻都行
                //111 2222 3333 顺子:2222 3333  刻子: 2 3
                if(count[i]>=count[i-1]&&count[i+1]>=count[i-1]){//3张都可以作为刻子的情形
                    count[i]-=count[i-1];
                    count[i+1]-=count[i-1];
                    count[i-1]-=count[i-1];
                    continue;
                }else {//作为顺子
                    count[i-1]-=count[i-1];
                    continue;
                }
//                return false;
            }
            if(count[i-1]==4){//如果该牌有4张,1张作为刻子
                if(count[i]>=1&&count[i+1]>=1){
                    count[i]-=1;
                    count[i+1]-=1;
                    count[i-1]-=1;
                    i--;//继续当前牌的判断,退役到上头该牌有3张的那种情况了
                    continue;
                }
                return false;
            }
        }
        return (count[7]==0||count[7]==3)&&(count[8]==0||count[8]==3);
    }


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) { //分隔符:换行||空格
            Integer[] data=new Integer[13];
            for (int i = 0; i < 13; i++) {
                data[i]=Integer.parseInt(sc.next());
            }
            ArrayList res = getRes(data);
            if(res.size()==0){
                System.out.print(res.size());
            }else {
                for (int i = 0; i < res.size(); i++) {
                    System.out.print(res.get(i)+((i!=res.size()-1)?" ":""));
                }
            }
        }
    }
}

发表于 2019-10-11 11:15回复(0)举报
2

小杰_
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
# 小包最近迷上了一款叫做雀魂的麻将游戏,但是这个游戏规则太复杂,小包玩了几个月了还是输多赢少。
# 于是生气的小包根据游戏简化了一下规则发明了一种新的麻将,只留下一种花色,并且去除了一些特殊和牌方式(例如七对子等),具体的规则如下:
#     总共有36张牌,每张牌是1~9。每个数字4张牌。
#     你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
#     14张牌中有2张相同数字的牌,称为雀头。
#     除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)
# 例如:
# 1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌
# 1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌
# 1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。
# 现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。
# 原理:如果该手牌胡牌,那么每个数字必然是,雀头、刻子、顺子的成员,
# 递归算法 : 从最小的数字开始尝试,如果把其当成雀头成员,该数字划掉两个,并看余下的数字能否划空
# 如果是刻子成员,该数字划掉三个,并查看余下数字能否划空
# 如果是顺子成员,划掉该值
# a, a + 1, a + 2,并查看余下数字能否划空
# 如果上述三种尝试都无法划空数组,说明存在数字无法是
# 雀头、刻子、顺子的成员,
#将一个数字牌补入13个牌之中,判断是否和牌,是则输出,不是则下一个数字牌

def IShepai(str):
    lenth=str.__len__()
    if lenth == 0:
        return True
    count1=str.count(str[0])

    if lenth%3!=0 and count1>=2 and IShepai(str[2:])==True:
        return True
    if  count1 >= 3 and IShepai(str[3:])==True:
            return True
    if str[0] + 1 in str and str[0] + 2 in str:
        str1 = str[1:]
        str1.remove(str[0]+1)
        str1.remove(str[0]+2)
        if IShepai(str1) == True:
            return True
    return False
if __name__ == '__main__':
    a=input().split(" ")
    a=[int(x) for x in a]

    for i in range(1,10):
        al=sorted(a + [i])
        if al.count(i)>4:
            continue
        else:
            if IShepai(al) == True:
                print(i,end=" ")
    print()


发表于 2019-09-07 16:05回复(0)举报
2

Python23matin
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
def is_win(cards):
    if len(cards) == 2 and cards[0] == cards[1]:
        return True
    for i in range(len(cards) - 2):
        if i < len(cards)-4 and  cards[i] == cards[i + 4]:
            return False
    for i in range(len(cards) - 2):
        tmp = cards[i]
        #shunzi
        if (tmp in cards) and (tmp + 1 in cards) and (tmp + 2 in cards):
            this_cards = cards.copy()
            this_cards.remove(tmp)
            this_cards.remove(tmp + 1)
            this_cards.remove(tmp + 2)
            if is_win(this_cards):
                return True
        #kezi
        elif tmp == cards[i + 2]:
            this_cards = cards.copy()
            this_cards.remove(tmp)
            this_cards.remove(tmp)
            this_cards.remove(tmp)
            if is_win(this_cards):
                return True
    return False

def main():
    nums = list(map(int, input().split()))
    # nums = list(map(int, "1 1 1 3 4 5 5 5 5 6 7 9 9".split()))
    res = []
    for i in range(1, 10):
        cards = nums.copy()
        cards.append(i)
        cards.sort()
        if is_win(cards):
            res.append(i)

    res = ' '.join(str(x) for x in sorted(res)) if res else '0'
    print(res)

if __name__ == "__main__":
    main()
编辑于 2019-07-16 20:44回复(1)举报
1

牛客982849291号
map记录牌的张数,size记录有几张牌,k记录是否已经使用刻头
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
#include
using namespace std;
bool dfs(vectormap,int size,bool k){
    if(size==0){
        return true;
    }
    bool a = false,b = false,c = false;
    for(int i = 1;i<10;i++){
        if(!k&&map[i]>=2){
            map[i]-=2;
            a = dfs(map,size-2,true);
            map[i]+=2;
        }
        if(map[i]>=3){
            map[i]-=3;
            b = dfs(map,size-3,k);
            map[i]+=3; 
        }
        if(i<=7&&map[i]>0&&map[i+1]>0&&map[i+2]>0){
            --map[i];--map[i+1];--map[i+2];
            c = dfs(map,size-3,k);
            ++map[i];++map[i+1];++map[i+2];
        }
    }
    return a||b||c;
}
int main(){
    vectormap(10);
    int k;
    while(cin>>k){
        map[k]++;
    }
    for(int i = 0;i<10;i++){
        if(map[i]+1<=4){
            ++map[i];
            if(dfs(map,14,false)){
                cout<
#include
using namespace std;
int a[15],b[10],c[10],d[10];
bool shunzi(int arr[]){
    int e[10];
    for(int i=1;i<10;i++)
        e[i]=arr[i];
    for(int i=1;i<=7;i++){
        if(e[i]==0)
            continue;
        int mn=min(e[i], min(e[i+1], e[i+2]));
        e[i]-=mn;
        e[i+1]-=mn;
        e[i+2]-=mn;
        if(e[i]!=0)
            return false;
    }
    return e[8]==0 && e[9]==0;
}
bool check(int arr[]){
    for(int i=1;i<=9;i++){
        if(arr[i]<2) continue;
        for(int j=0;j<(1<<9);j++){
            for(int k=1;k<=9;k++)
                d[k]=arr[k];
            d[i]-=2;
            bool mark=false;
            for(int k=0;k<9;k++){
                if(j>>k&1){
                    if(d[k+1]<3){
                        mark=true;
                        break;
                    }
                    else
                        d[k+1]-=3;
                }
            }
            if(!mark && shunzi(d))
                return true;
        }
    }
    return false;
}
int main(void){
    for(int i=1;i<=9;i++)
        b[i]=4;
    for(int i=1;i<14;i++){
        scanf("%d",&a[i]);
        b[a[i]]--;
        c[a[i]]++;
    }
    for(int i=1;i<=9;i++){
        if(b[i]==0) 
            continue;
        c[i]++;
        if(check(c))
            printf("%d ",i);
        c[i]--;
    }
    printf("\n");
    return 0;
}


发表于 2021-01-15 20:28回复(0)举报
1

C/C++nbgao
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
#include 
using namespace std;

bool F(map M, int t){
    if(t<=0)
        return true;
    while(M[M.begin()->first]==0)
        M.erase(M.begin());
    map::iterator it = M.begin();
    int i=it->first, cnt=it->second;
    if(t%3!=0 && cnt>=2){
        M[i] -= 2;
        if(F(M, t-2))
            return true;
        M[i] += 2;
    }
    if(cnt>=3){
        M[i] -= 3;
        if(F(M, t-3))
            return true;
        M[i] += 3;
    }
    if(cnt>0 && M[i+1]>0 && M[i+2]>0){
        M[i]--;
        M[i+1]--;
        M[i+2]--;
        if(F(M, t-3))
            return true;
        M[i]++;
        M[i+1]++;
        M[i+2]++;
    }
    return false;
}

int main(){
    map M;
    int x;
    for(int i=0;i<13;i++){
        cin>>x;
        M[x]++;
    }
    vector v;
    for(int i=1;i<10;i++){
        if(M[i]<4){
            M[i]++;
            if(F(M, 14))
                v.push_back(i);
            M[i]--;
        }
    }
    if(v.empty())
        cout<<0<=3){
                count[i-1]-=3;
                boolean subHashTriples = hasTriples(n-1);
                count[i-1]+=3;
                if(subHashTriples){
                    return true;
                }
            }
            if(i<=7  && count[i-1]>0 && count[i] > 0 && count[i+1]>0){
                --count[i-1];
                --count[i];
                --count[i+1];
                boolean subHasTriples = hasTriples(n-1);

                ++count[i-1];
                ++count[i];
                ++count[i+1];
                if(subHasTriples){
                    return true;
                }
            }
        }
        return false;
    }
}

发表于 2020-02-08 17:07回复(11)举报
5

牛客306773401号
本题要求找出所有可以和牌的数字,最多的情况为9种,因此,只需要从1->9依次验证即可,以下为验证方法:
首先,如果手牌中某张牌已经有4张了,是不可以再取到的,直接continue;
然后,开始判断14张牌能否和牌:
1
2
3
4
5
6
7
8
9
for(int i=1;i<10;i++){
        if(rec[i]==4)continue;
        rec[i]++;
        if(fun(rec,14)){
            cout<second)>=2){
        mp[it->first]-=2;
        if(fun(mp, num-2))return true;
        mp[it->first]+=2;//走到这步,说明上步失败了,还原mp
    }

当第一个数有3个以上时,说明有刻子可以选,num-3;
1
2
3
4
5
if((it->second)>=3){
        mp[it->first]-=3;
        if(fun(mp, num-3))return true;
        mp[it->first]+=3;//也失败了,还原
    }

当上面两种都不行,就判断第一个数与后两个数是否都大于0,是就可以组成顺子,num-3;
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
if((it->second)>0 && mp[(it->first)+1]>0 && mp[(it->first)+2]>0){



        mp[it->first]--;



        mp[(it->first)+1]--;



        mp[(it->first)+2]--;



        if(fun(mp, num-3))return true;



        mp[(it->first)]++;



        mp[(it->first)+1]++;



        mp[(it->first)+2]++;



    }
   4.如果三种情况都失败,就returun false;

完整代码:
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
#include 
#include 
using namespace std;
bool fun(map mp,int num){
    if(num<=0)return true;
    while(mp[mp.begin()->first]==0)mp.erase(mp.begin());
    map::iterator it = mp.begin();
    if(num%3!=0 && (it->second)>=2){
        mp[it->first]-=2;
        if(fun(mp, num-2))return true;
        mp[it->first]+=2;
    }
    if((it->second)>=3){
        mp[it->first]-=3;
        if(fun(mp, num-3))return true;
        mp[it->first]+=3;
    }
    if((it->second)>0 && mp[(it->first)+1]>0 && mp[(it->first)+2]>0){
        mp[it->first]--;
        mp[(it->first)+1]--;
        mp[(it->first)+2]--;
        if(fun(mp, num-3))return true;
        mp[(it->first)]++;
        mp[(it->first)+1]++;
        mp[(it->first)+2]++;
    }
    return false;

}
int main(){
    map rec;
    int tmp;
    bool flag=false;
    for(int i=0;i<13;i++){
        cin>>tmp;
        rec[tmp]++;
    }
    for(int i=1;i<10;i++){
        if(rec[i]==4)continue;
        rec[i]++;
        if(fun(rec,14)){
            cout<= 2 {
            card[i] -= 2
            if  hasTri(card, num - 2) {
                card[i] += 2
                return true
            }
            card[i] += 2
        }
    }

    return false 
}

func hasTri(card []int, num int) bool {
    if num == 0 {
        return true
    }
    for i, _ := range card {
        if card[i] >= 3 {
            card[i] -= 3
            if  hasTri(card, num - 3) {
                card[i] += 3
                return true
            }
            card[i] += 3
        }
        if  i <= 7 && i>0 && card[i] > 0 && card[i-1] > 0 && card[i+1] > 0 {
            card[i]--
            card[i-1]--
            card[i+1]--
            if  hasTri(card, num - 3) {
                card[i]++
                card[i-1]++
                card[i+1]++
                return true
            }
            card[i]++
            card[i-1]++
            card[i+1]++
        }
    }
    return false 
}

func main() {
    card := make([]int, 9)
    n := 0
    count := 0
    for i:=0;i<13;i++ {
        fmt.Scanf("%d", &n)
        card[n-1]++
    }
    for i, _ := range card {
        if card[i] < 4{
            card[i]++
        }else{
            continue
        }
        if IsWin(card,14) {
            fmt.Printf("%d ", i + 1)
            count ++ 
        }
        card[i]--
    }
    if count == 0 {
        fmt.Println(count)
    }
}

发表于 08-25 10:40回复(0)举报
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
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
76
77
import java.util.*;

public class Main{
    public static void main(String[] args){
        HashMap map=new HashMap<>();
        Scanner scanner=new Scanner(System.in);
        while(scanner.hasNext()){
            int temp=scanner.nextInt();
            //System.out.println("输入序列为:");
            //System.out.println(temp);
            map.put(temp,map.containsKey(temp)?map.get(temp)+1:1);
        }
        List lst=new ArrayList<>();
        for(int i=1;i<=9;i++){
            if(map.containsKey(i) && map.get(i)==4) continue;
            if(!map.containsKey(i) && !map.containsKey(i+1) && !map.containsKey(i-1)){
                //System.out.println(i);
                continue;
            }
            if(isOk(i,map)){
                lst.add(i);  //判断是否成功
            } 
        }
        for(int num:lst){
            System.out.print(num+" ");
        }
    }

    public static boolean isOk(int i,HashMap map){
        map.put(i,map.containsKey(i)?map.get(i)+1:1);
        //System.out.println(i + " " +map.get(i));
        boolean flag=false;
        //如果只有一张,只能尝试凑顺子,
        //如果有两张, 尝试凑两个顺子 || 或者当雀头
        //如果有三张    尝试当刻字  || 一个雀头一个顺子
        //如果有四张    尝试一个刻字一个顺子  || 一个雀头两个顺子
        //这里先遍历着,把雀头拿掉,然后再加一个count, 进行递归,看是否满足条件
        for(Map.Entry entry : map.entrySet()){
            if(entry.getValue()<2) continue;
            //System.out.println(entry.getKey() + " " +entry.getValue());
            map.put(entry.getKey(),entry.getValue()-2);
            flag=isGood(map,0);
            map.put(entry.getKey(),entry.getValue()+2);
            if(flag) break;
        }
        if(map.get(i)==1) map.remove(i);
        else map.put(i,map.get(i)-1);
        return flag;
    }

    public static boolean isGood(HashMap map,int count){
        if(count==4) return true;
        for(Map.Entry entry:map.entrySet()){
            int value=entry.getValue();
            int key=entry.getKey();
            if(value>=3){
                //此时可以考虑刻子
                map.put(key,value-3);
                boolean flag=isGood(map,count+1);
                map.put(key,value);
                if(flag) return true;
            }
            //此时考虑顺子,由于是遍历,一直让改数为第一个
            if(value>=1 && map.containsKey(key+1)&& map.get(key+1)>=1 && map.containsKey(key+2) && map.get(key+2)>=1){
                map.put(key,value-1);
                map.put(key+1,map.get(key+1)-1);
                map.put(key+2,map.get(key+2)-1);
                boolean flag=isGood(map,count+1);
                map.put(key,value);
                map.put(key+1,map.get(key+1)+1);
                map.put(key+2,map.get(key+2)+1);
                if(flag) return true;
            }
        }
        return false;
    }
}

发表于 08-14 11:40回复(0)举报
0

牛客863963759号
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
def isHu(nums):
    if not nums:
        return True
    else:
        n = len(nums)
        count0 = nums.count(nums[0])
        # if not remove bird head
        if(n%3!=0) and count0 >=2 and isHu(nums[2:]):
            return True
        # if ketou
        elif count0>=3 and isHu(nums[3:]):
            return True
        # shun zi
        elif nums[0]+1 in nums and nums[0]+2 in nums:
            tmp = nums.copy()
            tmp.remove(nums[0])
            tmp.remove(nums[0]+1)
            tmp.remove(nums[0]+2)
            if isHu(tmp):
                return True
        else:
            return False

def main(nums):
    # get card list
    d = {}
    for each in nums:
        d[each] = d.get(each, 0) + 1
    card_list = set(range(1, 10)) - {k for k,v in d.items() if v==4}
    # get res
    res = []
    for card in card_list:
        if isHu(sorted(nums + [card])):
            res.append(card)
    res = ' '.join(map(str, sorted(res))) if res else '0'
    print(res)

if __name__ == '__main__':
    nums = list(map(int, input().split(' ')))
    main(nums)

发表于 05-31 13:49回复(0)举报
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
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
76
77
78
79
80
81
import java.util.*;

public class Main {
    public static List solve(int[] cntArr) {
        List ans = new ArrayList<>();
        for (int i = 1; i < 10; i++) {
            if (cntArr[i] < 4) {
                int[] tmpArr = Arrays.copyOf(cntArr, cntArr.length);
                tmpArr[i] += 1;
                if (isDz(tmpArr)) {
                    ans.add(i);
                }
                tmpArr[i] -= 1;
            }
        }
        return ans;
    }

    public static boolean isDz(int[] cntArr) {
        // 对子
        for (int i = 1; i < 10; i++) {
            if (cntArr[i] > 1) {
                cntArr[i] -= 2;
                if (isHu(cntArr, 12, 4)) {
                    return true;
                }
                cntArr[i] += 2;
            }
        }
        return false;
    }

    public static boolean isHu(int[] cntArr, int count, int n) {
        if (count == 0 && n == 0) {
            return true;
        }
        for (int i = 1; i < 10; i++) {
            if (cntArr[i] > 2) {
                cntArr[i] -= 3;
                if (isHu(cntArr, count - 3, n - 1)) {
                    return true;
                }
                cntArr[i] += 3;
            }
        }
        for (int i = 1; i < 8; i++) {
            if (cntArr[i] > 0 && cntArr[i + 1] > 0 && cntArr[i + 2] > 0) {
                cntArr[i] -= 1;
                cntArr[i + 1] -= 1;
                cntArr[i + 2] -= 1;
                if (isHu(cntArr, count - 3, n - 1)) {
                    return true;
                }
                cntArr[i] += 1;
                cntArr[i + 1] += 1;
                cntArr[i + 2] += 1;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int[] cntArr = new int[10];
        int tmp;
        for (int i = 0; i < 13; i++) {
            tmp = sc.nextInt();
            cntArr[tmp]++;
        }
        List ans = solve(cntArr);
        if (ans.size() < 1) {
            System.out.println(0);
        } else {
            System.out.print(ans.get(0));
            for (int i = 1; i < ans.size(); i++) {
                System.out.print(" ");
                System.out.print(ans.get(i));
            }
        }
    }
}

发表于 04-23 16:51回复(0)举报
首页

上一页

1
2
3
4
5

下一页

尾页
富文本编辑器 插入代码

C/C++JavaPythonC#JavaScript提交观点
问题信息
C++工程师 iOS工程师 安卓工程师 运维工程师 算法工程师 测试工程师 PHP工程师 动态规划 模拟 穷举 字节跳动 2019 Java工程师
上传者:小小
难度:
84条回答 526收藏 23392浏览
可能感兴趣
Java工程师专区
在找机会?这些岗位可以内推
更多职位
高级Java工程师薪资面议
深圳保诚科技有限公司简历稀缺
JAVA软件工程师5K-10K
顺通网络简历稀缺
Java架构师(上海)40K-65K
神策数据简历稀缺
查看更多相似职位
社招精选面经
社招版 

Java工程师精选面经合集
+订阅
1055家公司 15381篇面经 26410人订阅
GitHub爆火java八股文攻略,吃透offer拿到手软!
0人收藏 0人点赞5小时前
牛客《22版最新Java面试八股文》已助3200+人进大厂
10人收藏 6人点赞8小时前
GitHub获星96K的Java八股文,秋招必看面试攻略合集
0人收藏 1人点赞9小时前
剑指大厂,不能错过的2022最强Java面试八股文,全网爆赞
4人收藏 1人点赞10小时前
Java全栈面试八股文,涵盖21大知识专题,让面试手到擒来!
0人收藏 0人点赞10小时前
查看更多面经
  热门推荐






企业题库排行榜 24小时

华为
 155

招商银行·招银网络科技
 3
练最多题单排行榜 24小时

华为机试
 45544

SQL入门篇
 44236

模板速刷TOP101
 22429

SQL必知必会
 17678

SQL进阶篇
 14525
通过挑战的用户查看代码

心跳的旋律
2022-10-12 09:04:14

追击者732
2022-10-09 23:18:20

545634634
2022-10-09 20:04:42

牛客62547...
2022-10-09 01:42:12

俞喆文
2022-10-08 22:33:48
相关试题
下面描述中,符合结构化程序设计风格...
搜狐 Java工程师 C++工程师 iOS工程师 安卓工程师 运维工程师 前端工程师 算法工程师 PHP工程师 2018
评论(1)
一个10*10的矩阵(可以理解为棋...
去哪儿 模拟
评论(0)
(verbal)最近的研究显示,许...
言语理解与表达 2019 普华永道 人力资源 审计 税务服务 风险管理 管理咨询 行政管理
评论(3)来自职能类模拟题14
给定两个单链表(为简化,假设两个链...
链表
评论(1)
“万事皆可达,唯有情无价”是[$#...
吉比特 产品运营 2018 游戏策划
评论(1)

移动版
关于我们
加入我们
意见反馈
企业服务
校企合作
联系我们
免责声明
友情链接
资源导航
公司地址:北京市朝阳区北苑路北美国际商务中心K2座一层-北京牛客科技有限公司
联系方式:010-60728802
投诉举报电话:010-57596212(朝阳人力社保局)
牛客科技©2022 All rights reserved [email protected]
京ICP备14055008号-4
增值电信业务经营许可证
营业执照
人力资源服务许可证
京公网安备 11010502036488号

MT25 超链接

MT25 超链接 题目 题解(2) 讨论(14) 排行 中等 通过率:49.63% 时间限制:1秒 空间限制:32M 知识点 哈希 warning 校招时部分企业笔试将禁止编程题跳出页面,为提前适应,练习时请使用在线自测,而非本地IDE。 描述 很多网页上会显示一些其他网页的超链接,如一些搜索网页会逐条列出搜索到的条目。 在一些网页中,被用户点击过的超链接会换一种颜色显示。假设某网页一开始有 n 条超链接,从上到下由 1 到 n 编号,每条超链接都显示成一个字符串,最开始所有的超链接用蓝色显示。 现在给出用户点击过哪些超链接,一条超链接只要被点击过了,就会由蓝色变成紫色,请输出最后仍为蓝色的超链接。

数据范围: 1 \le n \le 100 \1≤n≤100 , 1 \le m \le 100 \1≤m≤100 输入描述: 第一行输入一个数 n ,接下来 n 行,每行一个字符串,表示每个超链接的名称,名称只由小写字母构成,长度不超过 20,且所有名称互不相同。 接下来输入一个数 m ,表示用户点击了 m 个超链接,最后 m 行表示用户点击过的超链接名称,这 m 个超链接中可能有重复。 输出描述: 输出若干行,每行一个名称,所有仍为蓝色的超链接名称,名称的顺序按照字典序排序,如果全为紫色则不输出。 示例1 输入: 5 sina qq taobao jd baidu 3 qq baidu baidu 复制 输出: jd sina taobao 复制 相似企业真题 TOP C++ ACM 模式 是否格式化代码? 12345678910 #include using namespace std;

int main() { int a, b; while (cin >> a >> b) { // 注意 while 处理多个 case cout << a + b << endl; } } // 64 位输出请用 printf("%lld") 进入调试

判断url访问次数是否异常【是否收到恶意攻击】

输入一系列ip-时间戳对,10s内同一个ip访问次数超过五次,定义为被恶意攻击

bool is_safe(const int ip, const int timestamp){

}

岛屿

有一片海域 中间有一些小岛 找出一共有多少个实心矩形小岛 海域以二维数组给出 数组中包含0和1两种元素 0代表海 1代表地面 被一片海包围的地面成为小岛 小岛通过水平和竖直方向与相邻地连接而成 可以假设数组边缘之外都是海 实心矩形小岛的定义为 四条边与二维数组边界平行 内部均为1的小岛

/* 有一片海域,中间有一些小岛,找出一共有多少个「实心矩形小岛」。
 海域以二维数组给出,数组中包含 0 和 1 两种元素,0 代表海,1 代表地面。被一片海包围的地面成为小岛,小岛通过水平和竖直方向与相邻的陆地连接而成。可以假设数组边缘之外都是海。
「实心矩形小岛」的定义为: 四条边与二维数组边界平行、内部均为 1 的小岛

int numRectangleIslands(vector>& grid){
    // Your code here...
}

def numRectangleIslands(self, grid):
    # Your code here...
    pass

Input:
{{1, 1, 0, 0, 0},
{1, 1, 0, 0, 0},
{0, 0, 0, 1, 1},
{0, 0, 0, 1, 1}}

Output:
2

Input:
{{1, 1, 0, 1, 1},
{1, 0, 0, 0, 0},
{0, 0, 0, 0, 1},
{1, 1, 0, 1, 1}}

Output:
2
*/

#include 
#include 
using namespace std;

void dfs(vector> &g, int i, int j, int &cnt, vector> &vec, vector> &visited) {
    if (visited[i][j])    return;
    visited[i][j] = true;
    if (g[i][j] == 0) 
        return;
    ++ cnt;
    vec[0][0] = min(vec[0][0], i);
    vec[0][1] = min(vec[0][1], j);
    vec[1][0] = min(vec[1][0], i);
    vec[1][1] = max(vec[1][1], j);
    vec[2][0] = max(vec[2][0], i);
    vec[2][1] = min(vec[2][1], j);
    vec[3][0] = max(vec[3][0], i);
    vec[3][1] = max(vec[3][1], j);
    for (int h = -1; h <= 1; ++ h) {
        for (int v = -1; v <= 1; ++ v) {
            if (h == 0 && v == 0 || h != 0 && v != 0)
                continue;
            int x = i + h, y = j + v;
            if (x < 0 || x >= 4 || y < 0 || y >= 5)
                continue;
            dfs(g, x, y, cnt, vec, visited);
        }
    }
}

int main() {
    vector> g = {{1, 1, 0, 1, 1},
                            {1, 0, 0, 0, 0},
                            {0, 0, 0, 0, 1},
                            {1, 1, 0, 1, 1}};
    vector> visited(4, vector(5, false));
    int res = 0;
    for (int i = 0; i < 4; ++ i) {
        for (int j = 0; j < 5; ++ j) {
            vector> v = {{4, 5}, {4, 0}, {0, 5}, {0, 0}};
            int cnt = 0;
            dfs(g, i, j, cnt, v, visited);
            if (v[0][0] == v[1][0] && v[0][1] == v[2][1] 
                && v[3][0] == v[2][0] && v[3][1] == v[1][1]
                && cnt == (v[3][0] - v[0][0] + 1) * (v[3][1] - v[0][1] + 1))
                ++ res;
        }
    }
    cout << res;
    return 0;
}

DFS过程中维护左上、左下、右上、右下四个点,并维护访问到的位置数 一次 DFS 结束后根据四个顶点判断是否为矩形 并计算四个顶点组成的矩形的面积,和本次访问到的位置数作对比,若不相等,则该矩阵不为实心

旋转数组查找

/* 升序排列的整数数组 nums 在预先未知的某个点上进行了旋转(例如, [0,1,2,4,5,6,7] 经旋转后可能变为 [4,5,6,7,0,1,2] )。

请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

int findInRotatedArray(vector arr, int target) {
    // Your code here...
}

def findInRotatedArray(arr, target):
    # Your code here...
    pass

示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:
输入:nums = [1], target = 0
输出:-1
*/

#include 
#include 
using namespace std;
int main() {
//     vector v = {4,5,6,7,0,1,2};
    vector v = {1};
    int target = 0;
    int l = 0, r = v.size() - 1;
    bool found = false;
    while (l <= r) {
        int m = (r - l) / 2 + l;
        if (target == v[m]) {
            cout << m;
            found = true;
            break;
        }
        else if (v[m] >= v[l]) {
            if (target < v[m] && target >= v[l])    r = m - 1;
            else     l = m + 1;
        }
        else {
            if (target > v[m] && target <= v[r])    l = m + 1;
            else     r = m - 1;
        }
    }
    if (!found)    cout << -1;
    return 0;
}

设计

写一个LRU,在面试官提醒下加了析构函数管理内存,然后问道了为什么不用智能指针,然后又把智能指针讲了一遍

两个栈实现一个队列

手撕算法题 :实现左移的功能,空间复杂度o(1)。

浮点数判断是否相等

memcpy的实现(特殊情况处理,优化) 写了 memcpy 代码

手写memcpy函数

void* memcpy(void* dst, const void* src, size_t size)
{
    if(dst == NULL || src == NULL)
    {
        return NULL;
    }
    void* res = dst;
    char* pdst = (char*)dst;
    char* psrc = (char*)src;

    if(pdst > psrc && pdst < psrc + size) //重叠
    {
        pdst = pdst + size - 1;
        psrc = pdst + size - 1;
        while(size--)
        {
            *pdst-- = *psrc--;
        }
    }
    else //无重叠
    {
        while(size--)
        {
            *dst++ = *src++;
        }
    }
    return ret;
}

实现strcpy,要考虑内存重叠和特殊情况处理

[手写strcpy和memcpy代码实现 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/375301228#:~:text=手写strcpy 1 源字符串要使用const类型,避免在程序中被修改; 2 在函数入口处要加上断言,检查源字符串指针和目标字符串指针是否有为空的,否则会产生不可预料的错误;,3 使用while循环要简单而清晰,使用尽量精简的代码; 4 返回值要是 char* ,且返回了目标字符串指针的原值,使得函数可以支持链式表达式,增加了函数的附加值。)

(9条消息) c++:strcpy函数的实现 - 字符串拷贝_DinnerHowe的博客-CSDN博客_c++字符串复制函数strcpy

对内存重叠的深入认识 - 原来... - 博客园 (cnblogs.com)

char* strcpy(char* dst, const char* src)
{
    assert(dst);
    assert(src);
    char* ret = dst;
    while((*dst++ = *src++) != '\0');
    return ret;
}
//该函数是没有考虑重叠的

char* strcpy(char* dst, const char* src)
{
    assert((dst != NULL) && (src != NULL));
    char* ret = dst;
    int size = strlen(src) + 1;
    if(dst > src || dst < src + len)
    {
        dst = dst + size - 1;
        src = src + size - 1;
        while(size--)
        {
            *dst-- = *src--;
        }
    }
    else
    {
        while(size--)
        {
            *dst++ = *src++;
        }
    }
    return ret;
}

手写strcat函数

char* strcat(char* dst, const char* src)
{
    char* ret = dst;

    while(*dst != '\0')
        ++dst;

    while((*dst++ = *src) != '\0');
    return ret;
}

手写strcmp函数

int strcmp(const char* str1, const char* str2)
{

    while(*str1 == *str2 && *str1 != '\0')
    {
        ++str1;
        ++str2;
    }
    return *str1 - *str2;
}

链表

单链表逆序?写源码 [LC206 Reverse Linked List] 链表翻转

206. 反转链表 - 力扣(LeetCode)

如何判断链表是否有环; 单链表删除某个节点 手撕代码 删除链表中重复的节点 单链表去重 实现一个复杂链表的深拷贝(用的hash表)

上来先做题:力扣92题。

92. 反转链表 II - 力扣(LeetCode)

字符串

一大堆字符串,里面有很多重复的,找出来并删除 手写代码 字符串反转 有时间和空间复杂度限制

344. 反转字符串 - 力扣(LeetCode)

541. 反转字符串 II - 力扣(LeetCode)

手写代码 字符串循环移位 面试官让优化复杂度 没想出来(要用到上一题的字符串反转)

(9条消息) 字符串循环移位_ValDC_Morning的博客-CSDN博客_字符串循环移位

写一个strstr

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

字符串中第一个不重复的字符 代码题:最长公共字串 反转字符串

[手撕代码]假设一个字符串只含有字符 "*" 和a~z字母。现在请你写一段代码,把所有的字符 * 放到字母的左边,字母则按照原有的顺序都放在右边。 例如 * c * m * b * n * t * --> * * * * * *cmbnt

统计一篇英文文章出现频率最高的十个单词

692. 前K个高频单词 - 力扣(LeetCode)

面试题 16.02. 单词频率 - 力扣(LeetCode)

(9条消息) topK问题——统计一篇很长的英文文章中频次出现最高的10个单词_lizhentao0707的博客-CSDN博客

(9条消息) 统计英文文本中出现频率最高的10个单词_yicoder的博客-CSDN博客

写代码,给一个句子,单词空格分割的,将单词的顺序反过来

做算法 字符串通配符'?'与' * ',给定源字符串与目标字符串,编程判断是否能匹配(注意*bc与abcbc能匹配)

44. 通配符匹配 - 力扣(LeetCode)

字符串去掉不可见字符,我说了两种,他没理解我说的原地法,就又说了一种扫一遍-计数-删除的方法,效率略慢但都是O(N)。

(9条消息) 如何用C/C++实现去除字符串头和尾指定的字符_一只会铲史的猫的博客-CSDN博客_string怎么去掉字符串头和尾

剑指offer中的替换字符串中的空格

剑指 Offer 05. 替换空格 - 力扣(LeetCode)

二叉树

给定二叉树前序,中序遍历,重构二叉树 镜像二叉树(剑指offer27,递归、非递归) 判定是否是镜像二叉树 二叉树前序遍历的非递归实现; 打印二叉树第k层元素 手撕算法题:二叉树的最大深度?

LC199 Binary Tree Right Side View

199. 二叉树的右视图 - 力扣(LeetCode)

求一个二叉树的最小高度

111. 二叉树的最小深度 - 力扣(LeetCode)

面试题 04.02. 最小高度树 - 力扣(LeetCode)

310. 最小高度树 - 力扣(LeetCode)

几何

向量的点乘和叉乘的用法? 二维空间内,点到直线的距离? 圆内接三角形是锐角三角形的概率

【概率面试题连载】23. 圆周上随机取3个点形成锐角三角形 - 知乎 (zhihu.com)

一道机器学习岗位面试题:求圆上任取三个点组成锐角三角形的概率 - 知乎 (zhihu.com)

概率

多线程&设计模式

写一个回调函数 写一个单例 c++怎么进行多线程编程 写一个多线程的生产者消费者,想队列中加数据,取数据(这里写的也是磕磕绊绊的,在面试官的提醒下写完了) 多生产者多消费者访问一个队列,如何提高读写速率? 手撕死锁

sql

写sql语句

场景

个人对于数据结构的认识,可以结合实际谈谈

这个问题比较广泛,本人只是简单用食堂作为一个场景来描述。从学生进入食堂开始,分为排队、打饭、打菜和就座等几个连续过程。下面简要描述一下相关技术的结合: 严格排队 -> 队列,先进先出;出现插队 -> 链表结点加入 打菜 -> 访问食堂大妈节点访问Nginx等负载均衡代理服务器 -> 分配到目标机器访问数据库资源(数据库访问策略) -> 返回客户端餐盘 就座 -> 遍历整个食堂(采用不同的遍历算法) 可以充分发挥想象力。

假如动态地对帖子热度排序,怎么设计

数据量大,内存放不下,怎么排序

读数据库中的帖子热度值时,别的线程会写入吗

海量数据问题

一百万个数,每个数都是0-1000,使用哪种排序最快

十亿整数(随机生成,可重复)中前K最大的数

类似问题的解决方法思路:首先哈希将数据分成N个文件,然后对每个文件建立K个元素最小/大堆(根据要求来选择)。最后将文件中剩余的数插入堆中,并维持K个元素的堆。最后将N个堆中的元素合起来分析。可以采用归并的方式来合并。在归并的时候为了提高效率还需要建一个N个元素构成的最大堆,先用N个堆中的最大值填充这个堆,然后就是弹出最大值,指针后移的操作了。当然这种问题在现在的互联网技术中,一般就用map-reduce框架来做了。 大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),然后小文件装入内存快排,排序结果输出到文件。最后建堆归并。

十亿整数(随机生成,可重复)中出现频率最高的一千个

设计一个查询ip地址的哈希函数,如果是千万数据的怎么设计。

经典Hash函数的实现 - BarryW - 博客园 (cnblogs.com)

(9条消息) 哈希分治法 - 统计海量数据中出现次数最多的前10个IP_「已注销」的博客-CSDN博客_从十万个ip中找出重复数top10的ip

10万个手机号找出重复最多的十个,10亿个手机号呢

海量数据中找出前k大数(topk问题) - 知乎 (zhihu.com)

(9条消息) [面试题]海量数据处理-从10亿个数中找频率最高的1000个数_benjamin_sunny_li的博客-CSDN博客

经典面试问题: Top K 之 ---- 海量数据找出现次数最多或,不重复的。 - 指尖下的幽灵 - 博客园 (cnblogs.com)

你觉得公有云和私有云之间的差距是什么?在已经有公有云的情况下如何构建私有云呢?

如果让你设计一个IM系统,架构一下

给了一个场景写相关的sql

聊mysql锁,给了一个交易场景,让设计锁的方案。

(9条消息) MySQL——锁机制和数据库并发问题解决方案_万里顾—程的博客-CSDN博客

音频审核相关的系统架构和系统容灾

设计一个域名转换服务器,提供两个接口,一个是将长链接转换为短链接,一个是将短连接转换为长链接。用户在浏览器输入短链接会自动转为长链接。

计算机组成原理

CPU工作原理 流水线 乱序执行

CPU时钟如何设置

GPU工作原理 为什么比CPU快

智力

智力题 4刀把一个圆柱形蛋糕切16块

(9条消息) 切蛋糕n刀最多切成几分_Q&Cui的博客-CSDN博客

(2 封私信 / 28 条消息) 一个蛋糕切四刀,每刀都能切到整个蛋糕,最多能切成几块呢? - 知乎 (zhihu.com)

25匹马5条跑道,要决出前三名,最快跑几次? 7次

25匹马5条跑道,要决出前五名,需要跑几次? 最少8次最多9次

机器学习/深度学习

深度学习面经专题

SLAM && 多传感器

SLAM面经专题

三维重建

自动控制

PID控制原理 有哪些场景适用 哪些不适用

git

一个历史commit[在它的上方有非常多commits]代码中暴露了密钥,如何修复问题

脚本遍历base在此commit上的所有分支的所有commit,依次删除,再重新push 【操作前先备份】

其他

QT信号与槽

为什么选择优化区块执行时间

MPT 原理,节点怎么存的

系统吞吐率测试实验如何设置的

pprof 如何使用的

RPC 框架的原理和底层实现

RPC 和 Restful API 对比,各自适合在什么场景下使用

双 buffer 缓存系统的原理

对 Django 和 Vue 的了解

详细讲 MIT6.824 Lab

出现网络分区,多数派分区重新选出 leader,此时 client 发送请求给旧 leader,后续会怎么样

网络恢复后会发生什么情况

参考链接

(9条消息) C++后台开发校招面试常见问题_oscarwin的博客-CSDN博客 https://zhuanlan.zhihu.com/p/546948468

【2022届校招】图森未来 自动驾驶 C++ 面经 - 知乎 (zhihu.com)

update

你可能感兴趣的:(经验分享)