c++面试题目

C++和C中的struct和class的区别

  1. C++的struct可以当作class来用,他和C++中class的唯一的区别是,class中的成员默认是private,而struct的成员默认为public。

  2. C中的struct只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数。

  3. 关于使用大括号初始化, class和struct如果定义了构造函数的话,都不能用大括号进行初始化。 如果没有定义构造函数,
    struct可以用大括号初始化。如果没有定义构造函数,且所有成员变量全是public的话,可以用大括号初始化。

  4. 关于默认访问权限,class中默认的成员访问权限是private的,而struct中则是public的。

  5. 关于继承方式,class继承默认是private继承,而struct继承默认是public继承

    在这里插入图片描述

虚函数

//实现多态的三个条件

//1 要有继承

//2 要有虚函数重写

//3 用父类指针(父类引用)指向子类对象

//c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类重新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
成员函数被重载的特征是:

(1)具有相同的作用域(即同一个类定义中);

(2)函数名字相同

(3)参数类型,顺序 或 数目不同(包括const参数和非const函数)

(4)virtual关键字可有可无。

覆盖是指派生类重新实现(或者改写即重写)了基类的成员函数,其特征是:

(1)不同的作用域(分别位于派生类和基类中);(即虚函数的重写)

(2)函数名称相同

(3)参数列表完全相同;

(4)基类函数必须是虚函数。

(即我们可以得到,覆盖只是针对于虚函数)

隐藏是指派生类的成员函数遮蔽了与其同名的基类成员函数,具体规则如下:

(1) 派生类的函数与基类的函数同名,但是参数列表有所差异。此时,不论有无virtual关键字,基类的函数在派生类中将被隐藏。(注意别与重载混合)

(2)派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。此时,基类的函数在派生类中将被隐藏。(注意别与覆盖混合)

  • 1.虚函数(impure virtual)

C++的虚函数主要作用是“运行时多态”,即动态联编,父类中提供虚函数的实现,为子类提供默认的函数实现。

子类可以重写父类的虚函数实现子类的特殊化。

  • 2.纯虚函数(pure virtual)

C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。

C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。

C++中的纯虚函数也是一种“运行时多态”,动态联编。

class A
{
public:
    virtual void out1(string s)=0;//纯虚函数
    virtual void out2(string s)//虚函数
    {
        cout<<"A(out2):"<<s<<endl;
    }
};
  • 3.普通函数(no-virtual)

普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数。

普通函数是父类为子类提供的“强制实现”。

因此,在继承关系中,子类不应该重写父类的普通函数,因为函数的调用至于类对象的字面值有关。

虚函数的机制

只有数据成员的对象
类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
};

可知对象布局:
在这里插入图片描述
可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!类对象的大小就是所有成员变量大小之和。
虚函数在类中,需要一个虚函数表,无论类中友多少个虚函数,指针的大小都是4,因为多一个虚函数只是在虚函数表中增加一个项罢了。
同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

  • 1、她是编译器在编译时期为我们创建好的, 只存在一份
  • 2、定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表
  • 3、类的布局:虚函数表指针+成员变量定义.
  • 4、无论是通过Derive1的指针还是Base1的指针来调用此方法, 调用的都将是被继承类重写后的那个方法(函数), 多态发生了!!!
  • 5、子类中的虚函数被放到基类的虚函数表的后面
  • 6、基类都有虚函数的多继承中,Derive1的虚函数表保存到第1个拥有虚函数表的那个基类的后面.
  • 7 、多集成中,哪个基类有虚函数,那个基类就放在前面

最终的实现机制如下:
如果不是虚函数, 直接调用指针对应的基本类的那个函数
如果是虚函数, 则查找虚函数表, 并进行后续的调用. 虚函数表在定义一个时, 编译器就为我们创建好了的. 所有的, 同一个类, 共用同一份虚函数表.

友元函数

  • 访问1
    这三个访问说明符分别是public、private和protected。其中,public表示该成员可以被所有的人访问;private则与public刚好相反,它表示该成员只能被类内的成员函数访问;而protected表示该成员只能被类内函数和该类的派生类对象访问。

  • 友元函数
    在“访问1”中提到,类的private和protected成员不能被外部函数访问。如果外部函数确实需要访问这些成员,则可以将外部函数声明为该类的友元。

  • 友元的作用:提高了程序的运行效率(使得普通函数可以直接访问类的保护数据,避免了类成员函数的频繁调用,即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

在C++中友元可以使友元函数也可以是友元函数

  • 友元函数:
    友元函数是可以直接访问类的私有成员的非成员函数。是定义在类外的普通函数,不属于任何类,但需要在类的定义中声明,声明时在函数前加friend关键字即可: friend 类型 函数名(形式参数)

  • 友元类:
    友元的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员),当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类: friend class 类名;

  • 使用友元类注意:

    1、友元关系不能被继承。

    2、友元关系是单向的,不具有交换性。

    3、友元关系不具有传递性。
    4、成员函数有this指针,友元函数没有this指针

    5、友元函数是不能被继承的

    new和malloc区别

    new和malloc的区别是C/C++一道经典的面试题,我也遇到过几次,回答的都不是很好,今天特意整理了一下。

  1.   属性
    

new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。

  1.   参数
    

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。

  1.   返回类型
    

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

  1.   分配失败
    

new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

  1. 自定义类型
 new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。

malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

  1.  重载
    

C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。

//operator new 和 operator delete的原型如下:
void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array
前面两个均是 C++ 标准库函数,你可能会觉得这是函数吗?请不要怀疑,这就是函数!C++ Primer 
书上说这不是重载 new 和 delete 表达式(如 operator= 就是重载 = 操作符),
因为 new 和 delete 是不允许重载的。她们都是用来申请和释放内存的,
并且 operator new 申请内存之后不对内存进行初始化,直接返回申请内存的指针。



new和delete作为运算符的时候才可以被重载。
6. 内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

申请超大空间

C++中,在函数(包括main函数)中定义超大数组,内存为栈所分配的最大空间为4M,因此在子函数或者main函数中定义超大数组的方式是万万行不通的。
有时候觉得数组使用起来不方便,可以采用定义vector的方式,但需要注意的是,vector通常只能分配出几百MB的空间。
由于vector在所需要的是一片连续的内存空间。

vector <char> vec0;
vector <string> vec1;
vector <int> vec2;
vector <double> vec3;
 
cout<<vec0.max_size()<<endl;//4294967295,即4G个元素
cout<<vec1.max_size()<<endl;//134217727,即128M个元素
cout<<vec2.max_size()<<endl;//1073741823,即1G个元素
cout<<vec3.max_size()<<endl;//536870911,即512M个元素
//注意max_size()函数返回的是vector容器最大能存放的元素的个数,并不是字节数
 

RT-THREAD介绍

RT-Thread中支持静态和动态两种定义方式。

  • 用线程来举例的话,rt_thread_init对应静态定义方式,rt_thread_create对应动态定义方式。

  • 使用静态定义方式时,必须先定义静态的线程控制块,并且定义好堆栈空间,然后调用rt_thread_init来完成线程的初始化工作。采用这种方式,线程控制块和堆栈占用的内存会放在RW段,这段空间在编译时就已经确定,它不是可以动态分配的,所以不能被释放。而只能使用rt_thread_detach函数将该线程控制块从对象管理器中脱离。

  • 使用动态定义方式rt_thread_create时,RT-Thread会动态申请线程控制块和堆栈空间。在编译时,编译器是不会感知到这段空间的,只有在程序运行时,RT-Thread才会从系统堆中申请分配这段内存空间,当不需要使用该线程时,调用rt_thread_delete函数就会将这段申请的内存空间重新释放到内存堆中。

  • 这两种方式各有利弊,
    静态定义方式会占用RW空间,但是不需要动态分配内存,运行时效率高。
    动态方式不会占用额外的RW空间,占用空间小,但是运行时需要动态分配内存,效率没有静态方式高。
    总的来说,这两种方式就是空间和时间效率的平衡,可以根据实际环境需求选择采用具体的分配方式。
    1.components.c文件的140行看到#ifdef RT_USING_USER_MAIN宏定义判断,这个宏是定义在rtconfig.h文件内的,而且处于开启状态。同时我们可以在146行看到#if defined (__CC_ARM)的宏定义判断,__CC_ARM就是指keil的交叉编译器名称。
    2.除中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占之外,其他部分是可以抢占的,包括线程调度器本身。
    3.线程总共支持256个优先级,数值越小优先级越高。
    4.线程控制块由结构体struct rt_thread表示
    rt_thread_t表示线程的句柄。
    5.线程的五种状态:
    RT_THREAD_INIT 初始状态
    RT_THREAD_READY 就绪状态
    RT_THREAD_RUNNIG 运行状态
    RT_THREAD_SUSPEND 挂起状态
    RT_THREAD_CLOSE 结束状态
    6.当系统中无其他线程运行时,调度器将调度到空闲线程。空闲线程通常是一个死循环,永远不会被挂起。

概述

RT-Thread实时操作系统是一个分层的操作系统,它包括了:

• 组件层components,这些是基于RT-Thread核心基础上的外围组件,把一些功能模块划分成独立的一个个组件模块,做到组件与组件之间的低耦合,组件内部的高内聚。

例如文件系统,命令行shell接口,lwIP轻型TCP/IP协议栈,GUI图形用户界面等。

• 硬实时内核kernel,这层是RT-Thread的核心,包括了内核系统中对象的实现,例如多线程及其调度,信号量,邮箱,消息队列,内存管理,定时器等实现。

•分支接口porting,主要由libcpu以及不同硬件平台的bsp构成,即RT-Thread支持的一个个芯片移植,外设驱动等
在这里插入图片描述
RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。RT-Thread具备一个IoT OS平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过两千万台,成为国人自主开发、国内最成熟稳定和装机量最大的开源RTOS。

RT-Thread拥有良好的软件生态,支持市面上所有主流的编译工具如GCC、Keil、IAR等,工具链完善、友好,支持各类标准接口,如POSIX、CMSIS、C++应用环境、Javascript执行环境等,方便开发者移植各类应用程序。商用支持所有主流MCU架构,如ARM Cortex-M/R/A, MIPS, X86, Xtensa, C-Sky, RISC-V,几乎支持市场上所有主流的MCU和Wi-Fi芯片。

3.个人使用体会

1)支持的CPU众多,支持当前应用中的主流架构的CPU,并且都移植好相关bsp包,几乎是获取源码即可运行,免去新手复杂的环境构建步骤。
2)占用资源小,官方介绍最小配置时,内核可以到3K ROM和1K RAM 的占用。正常使用一片STM32F103C8T6(20k RAM,64 k flash)都能跑起来。
3)支持的任务(线程)数量无限制,256个优先级,支持时间片轮询。这对处理多个任务或者对于时间片要求比较严格的任务非常有优势。
4)完善的外设,如spi、i2c、uart等总线驱动,及TCP/IP协议栈、文件系统、数据库(sqlite3)等资源的支持。使用时,常用的底层不需经过大量修改,只需根据框架增加特殊的驱动,然后将主要的精力花在应用开发上。
5)Linux风格,熟悉Linux的可以很快上手。提供finsh shell、mash shell,类似Linux的命令行模式,能够解析C代码,在命令终端执行代码,对于调试、打log、内存查看等非常有用。为了节省资源,代码发行时关闭finsh shell即可。
6)本人已在使用RTT的项目有3款,以及内部使用的工具2款,有带GUI的也有只使用内核的,产品经过长时间的连续不断电使用,目前为止没有发现因为是RTT的bug而导致的问题。因此RTT经过十多年的沉淀还是非常稳定的。
7)写该文章的目的之一,就是让嵌入式同行者知道并使用RTT,而且RTT是经过实际领域应用考验的,打破他们在此之前对RTT稳定性的怀疑态度。

vector 和list区别

  • 1.vector数据结构
    vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
    因此能高效的进行随机存取,时间复杂度为o(1);
    但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
    另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。

  • 2.list数据结构
    list是由双向链表实现的,因此内存空间是不连续的。
    只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);
    但由于链表的特点,能高效地进行插入和删除。
    **- 3、区别**
    vector拥有一段连续的内存空间,能很好的支持随机存取,
    因此vector::iterator支持“+”,“+=”,“<”等操作符。

list的内存空间可以是不连续,它不支持随机访问,
因此list::iterator则不支持“+”、“+=”、“<”等

vector::iterator和list::iterator都重载了“++”运算符。

总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
如果需要大量的插入和删除,而不关心随机存取,则应使用list。

常用的软件设计模式

设计模式的六大原则:
总原则:开闭原则(Open Close Principle)开闭原则就是说对扩展开放,对修改关闭。

  • 1、单一职责原则
  • 2、里氏替换原则
  • 3、依赖倒转原则
  • 4、接口隔离原则
  • 5、迪米特法则
  • 6、合成复用原则

1.1. 创建型
创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更好的架构优势。创建型模式主要有:

  • 简单工厂模式(并不是23种设计模式之一)
  • 工厂方法
  • 抽象工厂模式
  • 单例模式、
  • 生成器模式
  • 原型模式。

1.2. 结构型
用于帮助将多个对象组织成更大的结构。结构型模式主要有:

  • 适配器模式adapter、
  • 桥接模式bridge、
  • 组合器模式component、
  • 装饰器模式decorator、
  • 门面模式、
  • 亨元模式flyweight
  • 代理模式proxy。

1.3. 行为型
用于帮助系统间各对象的通信,以及如何控制复杂系统中流程。行为型模式主要有:

  • 命令模式command、
  • 解释器模式、
  • 迭代器模式、
  • 中介者模式、
  • 备忘录模式、
  • 观察者模式、
  • 状态模式state、
  • 策略模式、
  • 模板模式
  • 访问者模式。

单列模式的实现方式-代码
它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。显然单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。从具体实现角度来说,就是以下三点:一是单例模式的类只提供私有的构造函数,二是类定义中含有一个该类的静态私有对象,三是该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,

#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        static Singleton m_Instance;
        return &m_Instance;
    }
 
    int GetTest()
    {
        return m_Test++;
    }
 
private:
    Singleton(){ m_Test = 10; };
    int m_Test;
};
 
int main(int argc , char *argv [])
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
 
    singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
}

大端小段

  • 大端模式:
    大端模式就是指把数据的高字节保存在内存的低地址中,数据的低字节保存在内存的高地址中,这和我们一般的阅读顺序是一致的。

  • 小端模式:
    小端模式与大端模式相反,数据的高字节位置保存在内存的高地址处,数据的低字节保存在内存的低地址处。这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
    在这里插入图片描述

  • 判断
    在32位平台下,int占4个字节,而char类型的指针是占一个字节的,如果我们把int强传为char类型的指针,只会保存一个字节的数据,那么我们只需要判断char里面的第一个字节和int里面的第一个字节是否是一致即可判断。
    如果一致则为小端模式,反之为大端模式。

  • 1、方法一

//下面代码我们令 int a=1 如果是小端模式,int下1会存放在在低地址处,而强传为char类型的指针,1也在低地址处,所以可以判断。
//移位也可也实现
#include <iostream>
using namespace std;
int main()
{
    int a = 1;
    char b=(char *)&a;
    if (b == 1)
        cout << "小端模式" << endl;
    else
        cout << "大端模式" << endl;
    return 0;
}

  • 2、联合体
#include <iostream>
using namespace std;
union Test
{
    int a;
    char b;
};
int main()
{
    Test t;
    t.a = 1;
    if (t.b == 1)
        cout << "小端机器" << endl;
    else
        cout << "大端机器" << endl;
    return 0;
}

绝对地址赋值

int *ptr;

ptr = (int *)0x67a9;//ptr的地址是0x67a9

*ptr = 0xaa55;//地址x67a9上保存的值是0xaa55

海康面试1

  • 1、计算机网络有几层(传输层有哪些协议)
    7层
    应用层(Application)、表示层(Presentation)、会话层(Session)、传输层(Transport)、网络层(Network)、数据链路层(Data Link)、物理层(Physical)。
    5层

在这里插入图片描述

  • 2、TCP/UDP区别
    TCP是面向连接的、可靠的流协议。
    UDP是不具有可靠的、无连接的数据报协议。

  • 3、 TCP三次握手协议过程
    TCP是个面向连接的协议,传输数据之前需要建立连接,结束后需要断开连接。
    建立连接一般称为三次握手:
    一般服务器端处于监听状态LISTEN,而客户端处于CLOSED状态。
    ①客户端先向服务器建立连接,向服务端发送SYN报文段请求建立连接,序列号为x,然后客户端进入SYN_SENT状态
    ②接着服务端发出SYN+ACK报文段,序列号为y,确认序列号为x+1,SYN代表请求建立连接,ACK代表收到了之前的报文端。
    ③当客户端收到了SYN+ACK报文段,接着发出ACK确认,序列号为y+1。最终双方都进入ESTABLISHED建立状态。这样就完成了三次握手。
    断开连接—四次挥手
    TCP是个全双工的通信协议,数据传输都是双向的。所以关闭连接需要关闭从客户端到服务端的连接,还要关闭服务端到客户端的连接。
    ①客户端向服务器发送FIN的报文段,进入FIN_WAIT1状态。
    ②服务端收到FIN的报文段,发出ACK确认后进入CLOSE_WAIT状态,客户端收到ACK后进去FIN_WAIT状态。
    ③服务端发出FIN的报文段进入LAST_ACK状态。
    ④客户端收到FIN报文段后,发出ACK确认报文段后,进入TIME_WAIT状态。服务端收到ACK后就关闭连接。

  • 4、数组和链表区别
    数组:
    数组在内存上是一块连续的内存空间,这也是为什么可以随机访问的原因,因为在内存上是连续的,所以元素都是紧邻的,只要一个一个访问即可。
    数组容量大小一但确定了就不能改变,所以这也是我们常常在编写程序时常范的错误,数组越界
    数组在增加和删除上效率是比较低的,为什么呢?因为每次增加或者删除数组中的元素所在的内存地址都要重新进行移动。
    数组的查询速度比链表的查询速度要快

链表:
链表在内存上是不连续的,是分散的,所有链表中的元素不是紧邻的,我们只有知道了上一个节点的信息,才能知道下一个节点的内容,当然了链表也有单链表和双链表之分,单链表即节点中存放了下一个节点的地址,双链表顾名思义,节点中不但存放了下一个节点的地址,也存放了上一个节点地址。
大小可以随意改变,链表可以动态的去改变自己的容量大小,而不会担心会数组越界。
链表在增加和删除上效率还是蛮高的,因为只要改变节点中保存的地址值即可,而不用进行内存地址的移动。

  • 5、重写和重载的概念
    重载是编写函数名相同,参数列表不同的函数。覆盖也叫重写,重写的函数必须有一致的参数表和返回值。什么情况下用重载,什么情况下用覆盖?派生类重写基类的虚函数用覆盖。实现同一功能有不同参数列表的函数用重载。他们是怎么实现的?重载是在编译阶段就知道要调用的函数地址,属于早绑定,是静态的;覆盖要在运行时绑定函数地址,属于晚绑定,是动态的。

  • 6、C++多态的机制
    多态就是一个接口多种状态,用虚函数实现,根据具体对象调用具体的函数。在运行时才知道要调用的到底是哪个函数。

  • 7、给一个集合,集合中有0-9共10个数,有多少个子集
    如集合{1,2,3}的所有子集为 空集,{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}数一数,一共8个,由此推测为2的三次方,即2的三次幂。
    证明:
    方法1:

一共集合有n个元素,它的子集的个数就是对这n个元素做组合,一共有n个位置可以组合,每个位置上该元素可以出现也可以不出现,所以最后总的个数为2的n次方。

方法2:

具有n个元素的集合的子集其实就是空集,含有一个元素的集合,含有两个元素的集合…含有n个元素集合,这集合的和就是,如图1所示。
在这里插入图片描述

  • 8、 链表中在某个位置中插入一个节点,如何插入
    结点在插入过程中存在以下几种情况:
    (1)如果原表是空表,只需使链表的头指针head指向被插结点即可。
    (2)如果如果被插结点值最小,则应插入第一个结点之前,这种情况下使头指针head指向被插结点,被插结点的指针域指向原来的第一结点即可。
    (3)如果在链表中某位置插入,使插入位置的前一结点的指针域指向被插结点,被插结点的指针域指向插入位置的后一结点即可。
    (4)如果被插结点值最大,则在表尾插入,使原表尾结点指针域指向被插结点,被插结点指针域指向NULL即可。
node * insert(node * head, int x)
{
     node * last, * current, * p;
     //要插入的结点
     p = (node *)malloc(sizeof(node));
     p->num = x;
     //空表插入
     if(head == NULL)
     {
         head = p;
         p->next = NULL;
         return head;
     }
     //找插入位置
     current = head;
     while(x > current->num && current->next != NULL)
     {
          last = current;
          current = current->next;
     }
     if(x <= current->num)
     {
         if(head == current)//在第一结点之前插入
         {
             p->next = head;
             head = p;
             return head;
         }
         else//中间位置插入
         {
             p->next = current;
             last->next = p;
             return head;
         }
     }
     else//链尾插入
     {
         current->next = p;
         p->next = NULL;
         return head;
     }
}
  • 9、线程和多线程的区别
    什么是线程?
    线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,
    即不同的线程可以执行同样的函数。

什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,
也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,
这样就大大提高了程序的效率。

多线程的不利方面:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能造成很多Bug
多线程与单线程的区别
生活举例
你早上上班,正要打卡的时候,手机响了。。你如果先接了电话,等接完了,在打卡,就是单线程。
如果你一手接电话,一手打卡。就是多线程。
2件事的结果是一样的。。你接了电话且打了卡。

  • 10 、代码中常用的数据结构有哪些
    所有的容器归根到底都是内存空间的排列方式和在空间上施加各种各种不同的限制所得的。空间排列方式只有线性和链式两种方式,链式是通过记录每一个数据的地址来实现查找下一位数据的。而每一个容器所具有的特性就决定了它所适用的情况,总的来看容器常用的无非是增删改查操作,下面将从适用场景、常用操作来进行总结。
    array数组
    内存空间为连续的一段地址,适用于提前已知所要存储的数据类型和数量、进行大量的查、改操作,不适用于含有大量交换、删除、增加数据的操作,该容器无法动态改变大小,所以说提前已知存储数据类型和数量。图片介绍了数组的初始化、赋值、遍历、获取大小、获取特定位置数据的方法。
    array <int 10> a;
    a[i]=1;
    queue队列
    该容器内存结构最好为链式结构,最知名的特点是先进先出,能动态调整大小,适用于包含大量增、删操作的情况,但不适用于含有大量查找操作的数据。图片介绍了队列初始化、赋值、弹出操作。
    push(i)
    pop();
    stack 栈
    栈在内存上可为连续或者链式,于队列相反的是它为先进后出,适用于压栈出栈操作,如可用于图的遍历、递归函数的改写等,图片介绍了栈的创始化、压栈、出栈等操作。
    stack a;
    a.push(i);
    a.pop();
    list 链表
    链表在内存结构上为链式结构,也就决定它可以动态增加,适用于包含大量增加、删除的应用,但不适用于包含大量查询的操作,图片介绍了链表的创建、添加数据、删除数据、获取数据等操作
    map
    map为关联式容器,提供一对一服务,每个关键字在容器中只能出现一次,适用于一对一服务。
    set 集合
    set集合最大的特点是里面的元素按序排列不重复,图片演示集合初始化、插入、删除、查找等操作。
    vector向量
    vector向量和array不同,它可以根据数据的大小而进行自动调整,图片仅展示初始化、插入、删除等操作。

  • 11、报文中前几个字节包含什么

TCP报文一般有20个字节。
1.16位的源端口号加上16位的目的端口号,这两个值加上IP首部中的源端IP地址和目的端IP地址,用于多路复用或多路分解来自或送至上层应用的数据。
2. 32位的序列号,其用来标识从TCP发送方向TCP接收端发送的数据字节流,它表示该报文段首字节的字节流编号。
3. 32位的确认序号标识发送确认一段所期望收到的下一个序号。序号字段和确认号字段用于TCP发送方和接收方来实现可靠数据传输服务。
4. 4比特的首部长度指示了以32比特为单位的TCP的首部长度。一般TCP首部长度为20字节,则该字段为0110。
5. 6位作为保留字段。
6. 6比特的标志字段。URG比特用来指示报文段存在着被发送方的上层实体置为“紧急”的数据。ACK比特用于指示确认字段中的值是有效的。RST, SYN, FIN用于连接的建立和拆除。PSH比特指示接受方应立即将数据交给上层。
7. 16位的接收窗口字段,该字段指示接收方愿意接受的字节数量,用于流量控制。
8. 16位校验和。
9. 16位紧急数据指针为紧急数据的最后一个字节。
在这里插入图片描述
IP 数据报
TCP数据一般是被封装在一个IP数据报中,如下图
在这里插入图片描述
在这里插入图片描述

  1. 4比特版本号。规定了数据报IP协议的版本。如IPv4和IPv6。通过查看版本号,路由器能够确定如何解释IP数据报剩余部分。
  2. 4位首部长度。用于确定IP数据报中数据部分从哪里开始。
  3. 8位服务类型。用于区分不同类型的数据报。
  4. 16位数据报长度,为IP数据报的总长度(首部加数据)。最大长度理论为65535字节,但是通常数据报很少超过1500字节。
  5. 16位比特标志,3位标志,13位片偏移。这三个字段与IP分片有关。1)标识字段唯一地标识一个IP数据包,每发送一份报文,其值会加1。2)标志位字段占3位,第一位为保留位,第二位为DF,为1表示禁止IP分片,为0代表允许分片。第三位为MF,为1代表此IP数据报不是最后一片,为0代表IP数据报是最后一片或者未分片。3)片偏移字段占13位,表示一个IP分组分片封装原IP分组数据的相对偏移量,以8字节为单位。
  6. 8位TTL生存时间字段设置了数据包可以经过的最多路由数。路由器每转发一次分组,TTL减1,如果TTL=0,则路由器丢弃该分组。
  7. 协议字段占8位,指示IP分组封装的是哪个协议的数据包。1表示ICMP协议,2表示IGMP协议,6代表TCP协议,17表示UDP协议。
  8. 首部检验和字段占16位,实现对IP分组首部的差错检测。
  9. 源IP地址和目的IP地址字段各占32位,分别标识发送分组的源主机/路由器和接受分组的目的主机/路由器的IP地址
  10. 选项字段一个可变长的可选信息,携带安全,源选路径,时间戳和路由记录等内容。其中包含填充字段,用于32位对齐。

变量和0比较

bool: if (!var){ ... } //bool
int : if (var == 0){ ... } //int 
float :if ( (var >= -0.00001) && (var <= 0.00001)) //float
if(p = NULL) //指针
if(p != NULL)

错误做法

//bool
if(flag == TRUE)
if(flag == 1)
if(flag == FALSE)
if(flag == 0)
//int 
if(value)
if(!value)
//float 
if(x == 0.0)
//指针
if(p)
if(!p)

mysql

  • 1、mysql 中的升序和降序
    mySQL里desc和asc的意思
    desc是descend 降序意思
    asc 是ascend 升序意思
    sql = “select 表内容名 from 数据库表名 Putout=true order by 读取的排序表名 asc”

例如
sql = “select * from user where Putout=true order by time desc” //按最新时间来排序
sql = “select * from user where Putout=true order by time asc” //按早时间来排序

  • 2、效率select *与select
    在数据量不大的表中 如果列没有索引 select id 和select * 执行的代价是一样的 但是操作的数据量不同
    如果列有索引 那么select id 比select * 以及select 无索引列的执行代价要小很多 操作的数据量也少很多

在数据量较大的表中 select id 比select * 执行的代价要低 操作的数据量要少 查询的时间要少

如果列有索引 那么select 有索引列 比select *以及select 无索引列 的执行代价要小很多 操作的数据量也少很多 查询的时间要少很多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章