嵌入式相关题目

1.变量的定义和声明有什么区别?

①变量的定义为变量申请地址和存储空间,变量的声明不会申请地址和存储空间。

②变量的申明可以在多个地方,但是变量的定义只能在一个地方。

③加入extern修饰的是变量声明,说明此变量将在文件以外或者文件后面部分定义。

注:很多时候一个变量只是使用不分配内存空间,直到使用时才初始化,分配内存空间。

//例:
int main()
{
    extern int A;//变量的声明。
    //声明A是一个已经定义了的外部变量。
    //注:声明时可以去掉类型。
    extern A;
}
int A; //变量定义,分配内存空间,定义了A为整形内部变量。

2.简述#ifdef,#else,#ifndef,#endif的作用。

#ifdef和#endif将特定功能模块包含进去,以向特定用户提供该功能,在调试时可轻易将该模块屏蔽。

#ifndef和#endif与以上功能相似,但不同的是前者需要声明后才能有效果,后者不能声明才会有效果。

3.写出int、bool、float以及指针变量与“零值”比较的if语句。

//int
if(0==a);   if(0!=a);
//bool
if(a);      if(!a);
//float
const float A = 0.0001
if(a>=a&&a<=a);
//指针
if(a==NULL);  if(a!=NULL);

4.结构体可以直接赋值吗?

声明时可以直接初始化,同一结构体的不同成员之间可以相互赋值,但是当结构体中有指针“成员”时,一定要小心。

注:当有一段内存被多个指针使用时,当一个指针释放这段内存后,可能会导致其他内存的非法操作,所以在释放时一定要保证其他指针不再操作。

5.sizeof和strlen的区别。

①sizeof是一个运算符,但strlen 是一个函数。

②sizeof可以测量其他内存的长度,但是strlen只能测量含有“\0”的字符串的长度。

③sizeof测量的是内存空间大小,strlen测量的是实际长度。

④编译器在编译时就算出了sizeof的大小,在运行时才能算出strlen的大小。

⑤数组做sizeof的参数不退化,但是做strlen的参数在编译时就退化成了指针。

6.c语言的关键字static和c++的关键字有什么区别?

在c语言中修饰内部成员变量和外部成员变量、函数,在c++中除了上述功能之外,还可以修饰类的局部变量和函数。

注:编程时static的静态性和全局性的特点可以使不同时间调用的函数之间进行通信,而c++的静态成员可以在多个对象实例间进行通信。

7.1 c语言中的malloc和c++中的new之间有什么区别?

①new、delete是操作符,可以重载,只能在C++中使用;

②malloc和free都是函数,可以覆盖,在C和C++中都可以使用;

③new可以调用对象的构造函数,delete可以调用对象的析构函数;

④malloc仅仅开辟内存,free仅释放内存,不能调用构造和析构函数;

⑤new 和delete返回的是某种类型的指针,malloc和free返回的是void指针。

注:malloc申请的内存要用free释放,new申请的内存要用free释放,不能混用。

7.2 new/delete与malloc/free的区别是什么?

①new会自动计算需要分配的内存空间,而malloc需要手工计算字节数。

int *p = new int[2];
int *q = (int *)malloc(2*sizeof(int));

② new与delete直接带具体类型的指针,malloc和free返回void类型的指针。

③new类型是安全的,但malloc不是

int *p = new float[2];//会报错
int p = malloc (2sizeof(int));//正确

④new一般分为两步:new操作和构造。new操作对应于malloc,new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。

⑤new调用构造函数,malloc不能,delete调用析构函数,free不能。

⑥malloc/free、需要库文件stdlib.h的支持,new/delet则不需要。 

8.写一个标准的宏。

#define  MAX(x,y)    ((x)>(y)?(x):(y))

9.i++和++i的区别。

i++是先运算后增1,i++是先增1,后运算。

10.volatile有什么用?

①状态寄存器一类的并行设备硬件寄存器;

一个中断服务子程序会访问到的非自动变量;

③多线程间被几个任务共享的变量。

11.一个值既可以是cnost又可以是volitile吗?

可以,一个值既是const又是volitile证明这个值在程序内是只读的、不可以被改变的,只在程序外部可以被改变,并且编译器不会优化这个变量,在使用时要小心的去读取他的值,而不是从寄存器读取备份。

注:const只是不允许程序中的代码改变某一变量,只在编译期有效,并没有实际禁止某段内存的读写特性。

12.*a和&a有什么不同。

&a只代表a的地址;

*a在不同情况下有不同的应用:

①声明语句中,只代表一个地址:  int *a

②其他语句中,指向某一个变量的地址:b=*a;

③代表两值相乘:b*a;

13.用c编写一个死循环语句:

while(1)
{

}

for(;;)
{

}

提示:先考虑内存、处理器等条件。 

14.结构体内存对其问题。

#include
struct S1
{
    int i:8;
    char j:4;
    int a:4;
    double b;
};
 
struct S2
{
    int i:8;
    char j:4;
    double b;
    int a:4;
};
 
struct S3
{
    int i;
    char j;
    double b;
    int a;
};
 
int main()
{
    printf("%d\n",sizeof(S1));  // 输出8
    printf("%d\n",sizeof(S1);  // 输出12
    printf("%d\n",sizeof(Test3));  // 输出8
    return 0;
}

答案:16、20、16

注:结构体是一种复合类型变量,其内容可以是常用数据类型,也可以是复合数据结构,一般情况下会自动进行内存对其,从而提高运算效率。

15.全局变量和局部变量有什么区别,是怎么实现的,操作系统和编译器是怎么知道的?

全局变量是整个程序都可以访问的变量,在整个程序中,不管在哪儿访问全局变量都可以。

局部变量只有在申明的函数、类(本模块)中使用,其他模块不能直接调用。本模块结束,局部变量消失,所占据的内存释放。

操作系统和编译器是通过内存分配的位置知道的,全局变量存储在全局数据区,在程序开始是运行时被加载,局部变量存储在堆栈区。

16.简述c/c++程序编译的内存分配情况:

①从静态存储区域分配:

    这段内存在程序编译时就已经分配好,在程序运行期间都有效,速度快、不容易出错,因为系统会善后。

②在栈上分配

    在执行函数时,函数内局部变量在栈上创建,函数执行结束后这些内存被自动释放,栈内存分配置于处理器的内存集中,效率极高,但是分配内存有限,大约2M。

③在堆上分配

    在堆上分配是采用malloc/free、new/delete申请任意大小内存,使用非常灵活便捷,但是在用完这段内存后,要记得立马释放内存空间,否则容易产生内存泄漏。

一个c或c++程序存储区被分为堆区、栈区、文字常量区、程序代码区、全局区。

17.简述strcpy、sprintf、memcpy的区别:

①操作对象不同,strcpy操作对象是两个字符串,sprintf的操作对象是多种数据类型,memcpy的操作对象是任意两种类型间的可操作地址。

②执行效率不同,memcpy最高,strcpy次之,sprintf最低。

③实现功能不同,strcpy实现两个字符串间的拷贝,sprintf实现其他类型到字符串的转化,memcpy实现内存块间的拷贝。

三者都能实现拷贝功能,但是针对的对象不同。

18.请解析((void()())o)()的含义:

void(*o)():无返回值,无形参的函数指针

(void (*)( ))0:把0转变成一个返回值为void,参数为空的函数指针。

(void()())o是一个函数的名字

((void()())o)()是上一个函数的调用。

19.c语言的指针和引用和c++有什么不同。

①指针有自己的空间,而引用只是别名。

②sizeof(指针)为4,sizeof(引用)是对象的大小。

③作为参数传递时,指针需要被解引用才能对对象进行操作,引用可以直接进行操作。

④可以有const指针,但没有const引用。

⑤指针在使用时可以指向其他对象,但是引用只能是一个对象的引用,不能改变。

⑥指针可以有多级指针,而引用只有一级。

⑦指针和引用使用++运算符的意义不一样。

⑧如果返回动态内存分配的对象或内存,只能使用指针,使用对象可能会导致内存泄漏。

20.typedef和define有什么区别?

①用法不同,typedef用来给变量取别名,而define用来定义常量以及书写频繁的宏;

②执行时间不同,typedef是编译的一部分,有类型检查功能,define在预编译阶段,在编译之前,只进行字符串替换;

③作用域不同,define没有作用域限制,只要使用都正确;typedef有作用域限制;

④对指针的操作不同,define和typedef定义的指针有很大区别。

21.指针常量与常量指针的区别?

指针常量是指定义了一个指针,这个指针的值只能在定义是初始化,其他地方不能改变。

常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过指针改变常量的值。

常量指针和指针常量都是定义了一个指针,但不同的是指针常量强调指针的不可变性,常量指针强调常量的不可变性。

常量指针:const char*p;    指针常量:char  *const  p;

22.简述队列和栈的异同:

都是线性存储结构,但是二者插入和删除数据的操作不同,队列是先进先出,栈是后进后出。

注:区别栈区和堆区,栈区由编译器自动分配释放,堆由程序员手动分配释放;堆是顺序随意,而栈是后进先出;

23.设置地址为0x67a9的整形变量的值为0x6aa1;

int *p;
p=(int *)0x67a9;
*p = 0x6aa1;

24.编码实现函数atio(),实现一个程序,把一个字符串转化成一个整形数组。

#include 

int MyAtoi(const char*str)
{
	int num = 0;//保存转换后的数值;
	int  isNegative = 0;//记录字符串中是否有负号;
	
	int n = 0;
	char *p = str;
	if(p==NULL)
	{
		return -1;
	}
	while(*p++!='\0')
	{
		n++;
	}
	
	p=str;
	if(p[0]=='-')//判断数组是否有负号
	{
		isNegative = 1;
	}
	
	char temp = '0';
	for(int i=0;i'9'||temp<'0')//滤除字符串开始的零字符;
		{
			continue;
		}
		if(num!=0||temp!='0')
		{
			temp -= 0x30;//字符转化为字符;
			num+=temp *int (pow(10,n-1-i));
		}
	}
	if(isNegative)//字符串中有负号,数值取反;
	{
		return 0-num;
	}
	else
	{
		return num;//返回转化后的值;
	}
}

25.c语言的结构体和c++有阿什么区别?

①c语言的函数体是不能有函数成员的,而c++的可以有;

②c语言的结构体中树组成员是没有private、public和protected访问限定的,而c++的类的成员有这些访问限定。

③c语言的结构体是没有继承关系的,c++有丰富的继承关系;

26.如何避免“野指针”

①指针变量在定义时没有初始化,解决办法:在定义时赋值为NULL;

②指针p被free或delete后,没有置为NULL,解决办法:指针指向的内存空间被释放后指针应该置为NULL;

③指针操作超越了变量的作用范围,解决办法:在变量的作用域结束前释放掉变量的地址空间并让指针指向NULL。

27.句柄和指针的联系和区别是什么?

windows系统用句柄标记系统资源,隐藏系统的信息,是32it的uint。指针则标记某个物理地址,两者是不同的概念。

28.说一说extern 'C'

extern 'C'的只要作用就是为了能够正确实现C++代码调用其他C语言代码,加上extern ‘C’后,会指示编译器这部分代码按照c语言而不是c++的方式进行编译。由于c++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;c语言并不支持函数重载,因此编译c语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

这个功能十分有用处,因为在c++出现以前,很多代码都是c语言写的,而且很底层的库也是c语言写的,为了更好的支持原来的c代码和已经写好的c语言库,需要在c++中尽可能支持c,而extern 'C'就是其中的一个策略。

①c++代码通用c语言代码。

②在c++的头文件中使用

③在多人协同开发时,可能有的人比较擅长开发c语言,而有的人擅长c++,这样的情况下也有可能会用到。

29.c++中struct和class的区别?

①默认权限不同,struct默认为public继承,class默认为private继承;

②class可以定义模板参数,但是struct不能定义模板参数;

③c++中的struct定义必须百分百保证c语言中的struct的向下兼容性,而c++中的最基本的对象单元规定为class而不是struct就是为了避免各种兼容性要求的限制;

④对struct定义的扩展使c语言的代码能够更容易的移植到c++中。

30.c++类可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表。

31.c++中类成员的访问权限

c++中类成员权限可以分为public/protected/private三个关键字来控制成员变量和成员函数之间的访问权限,他们分别表示公有的、受保护的、私有的。在类内部,任何成员变量之间都可以访问;类外部,只能通过对象访问成员,并且通过对象只能访问public的成员。

32.什么是右值引用,和左值引用有什么区别?

左值:能取地址或者具体对象,表达式结束后依然存在的持久对象;

右值:不能取地址,匿名对象,表达式结束后就不再存在的临时对象;

区别:左值引用可以赋值,右值不能;

        左值可变,右值不可变。

33.面向对象的三大特征:

封装性:将客观事务封装成类,每个类对自身的数据和方法实行protection(public/prottected/privated);

继承性:广义继承的三种形式:实现继承、可视继承、接口继承

多态性:将父类对象设置成为一个或更多他的子对象相等的技术。

34.c++中的强制类型转换?

①const_cast:将const变量转换为非const;

②static_cast:用于各种隐式转换,比如const转const,void*转指针等,用于多态向上转换,向下转换可能成功但不安全;

③dynamic_cast:用于动态类型转换,只能用于含有虚函数的类,用于类层次间的向上或向下转型。之能转指针或引用。向下转换时,如果是非法的,对于指针返回NULL,对于引用抛异常,要深入了解内部转换的原理。

向上转换:值得是子类向基类的转换;

向下转换:指的是基类向子类的转换。

他通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

④reinteerpret_cast:几乎什么都可以转,比如int转指针,可能会出现问题,尽量少用。

⑤c的强制类型转换:表面上看起来什么都能转,但是转换不够明确,不能进行错误检查,容易出错。

35.c++的空类有那些成员函数?

①缺省构造函数

②缺省拷贝构造函数

③缺省析构函数

④赋值运算符

⑤取址运算符

⑥取址运算符const

36.对c++中的smart pointer四个智能指针shared_ptr,unique_ptr,week_ptr,auto_ptr的理解

后三个c++11支持,第一个已被弃用。

智能指针的作用是管理一个指针,因为有可能申请的空间在函数结束后忘记释放,造成内存泄漏,使用智能指针可以很大程度上避免此问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源,所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放。

37.对拷贝构造函数和赋值运算符的理解:

①拷贝构造函数生成新的类对象,而赋值运算符不能。

②有拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象,是否和新建对象相同,而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉。

38.在c++中,malloc申请的内存空间能否通过delete释放?使用new申请的空间能否用free释放?

不能,

①malloc、free是为了兼容c,new和delete完全可以取代malloc/free的,malloc/free操作的对象都是可以明确大小的,而且不能用在动态类上。

②new和delete会自动进行类型检查和大小。

③malloc/free不能执行构造函数和析构函数,所以动态对象它是不行的;

④理论上说malloc申请的内存可以通过delete释放,不过一般不这么写,而且不能保证每个c++运行时都能正常。

39.用c++设计一个不能被继承的类;

template  class A
{
    friend T;
private:
    A(){};
    ~A(){};
};

class B:virtual publicA
{
 public:
    B(){};
    ~B(){};
};

class C : virtual public B
{
public:
    C(){};
    ~C(){};
};

void main(void)
{
    B b;
   return;
}

40.用c++自己实现一个string类:

#include 
#include 
using namespace std;

class String 
{
public:
	String(const char *str = NULL); //默认构造函数
	String(const String &str);//拷贝构造函数
	~String();//析构函数
	String& operator = (const String &str);
	
private:
	char *m_data;
	int m_size;
};

String::String(const char*str)//构造函数
{
	if(str==NULL)
	{
		m_data = new char[1];
		m_data[0]='\0';
		m_size = 0;
	}
	else
	{
		m_size = str.m_size;
		m_data = new char[m_size +1];
		strcpy(m_data,str.m_data);
	}
}

String::String(const String &str)//拷贝构造函数
{
	m_size = str.m_data;
	m_data = new char[m_size +1];
	strcpy(m_data,str.m_data);
}

String::~String()//析构函数
{
	delete[]m_data;
}

String &String::operator = (const String &str)
{
	if(this == &str)//检查自赋值
	{
		return *this;
	}
	
	delete[]m_data;//释放原有内存资源
	m_size = strlen(str.m_data);
	m_data = new char[m_size +1];//对m_data加null判断
	strcpy(m_data,str.m_data);
	return *this;//返回本对象的引用
}

41.访问基类的私有虚函数:

#include 
using namespace std;

class A
{
public:
	virtual void g()
	{
		cout<<"A::g"<

42.对虚函数和多态的理解;

多态的实现分为静态多态和动态多态,静态多态主要是重载,在编译时就已经确定;动态多态是用虚函数实现的,在运行期间动态绑定。

例:一个父类类型的指针指向一个子类对象的时候,使用父类的指针去调用子类中重写了父类中的子函数的时候,会调用子类重写过后的函数;在父类声明加了virtual关键字的函数,在子类中重写时不加virtual关键字也是虚函数。

虚函数的实现:在有虚函数的类中,类的最开始是一个具有虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的指针,实际的虚函数在代码段中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数的时候,会将其继承到的虚函数中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

43.简述类成员函数的重写、重载和隐藏的区别

(1)重写和重载的不同:

    ①范围的区别:被重写和重写的函数在两个类中,而重载和被重载的函数在一个类中。

    ②参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数参数列表一定不同。

    ③virtual的区别:重写的基类中被重写的函数必须要有virtual修饰,而重载函数和被重载函数可以被virtual修饰,也可以没有。

(2)隐藏和重写、重载的不同:

    ①与重载的范围不同,和重写一样,隐藏函数和贝英仓函数不在同一个类中。

    ②参数的区别:隐藏函数和被隐藏函数的参数列表可以相同,也可以不同,但是函数名肯定要相同。当参数不相同时无论参数中的参数是否被virtual修饰,积累的函数都是被隐藏,而不是被重写。

44.链表和数组有什么区别?

①存储形式:数组时一块连续的空间,声明是就要确定长度,链表是一块不连续的动态空间,长度可变,每个节点要保存相邻节点指针;

②数据查找:数组的线性查找速度快,查找操作直接使用偏移地址,链表需要按顺序检索节点,效率低;

③数据插入或删除:链表可以快速删除或插入节点,而数组可能需要大量的数据移动;

④越界问题:链表不存在越界问题,数组有越界问题。

45.用两个栈实现一个队列的功能:

typedef struct node
{
	int data ;
	node *next;
}node,*LinkStack;

//创建空栈
LinkStack CreateNULLStack(LinkStack &S)
{
	S = (LinkStack)malloc(sizeof(node));
	if(NULL ==s)
	{
		printf("Fall to malloc a new node\n");
		return NULL;
	}
	S->data = 0;
	S->next = NULL;
	
	return S;
}

//栈的插入
LinkStack Push(LinkStack &S,int data)
{
	if(NULL == s)
	{
		printf("There no node in stack\n");
		return NULL;
	}
	LinkStack p = NULL;
	p = (LinkStack)malloc(sizeof(node));
	
	if(NULL = p)
	{
		printf("Fail to malloc a new node \n");
		return S;
	}
	
	if(NULL == S->next)
	{
		p->next = NULL;
	}
	else
	{
		p->next = S->next;
	}
	p->data = data;
	S->next = p;
	return S;
}

//出栈函数
node Pop(LinkStack &S)
{
	node temp;
	temp.data = 0;
	temp.next = NULL;
	
	if(NULL==S)
	{
		printf("There no node in stack \n");
		return temp;
	}
}

46.为什么虚构函数一定要写成虚函数?

由于类的多态性,基类指针可以指向派生类的对象,如果删除该基类的指针,就会调用该指针指向的派生类析构函数,而派生类的析构函数有自动调用基类的析构函数,这样整个派生类的对象完全被释放,如果析构函数不被声明成虚函数,则编译器进行静态绑定,再删除基类指针时,只会调用基类的析构函数而不会调用派生类析构函数,就会造成派生类对象析构不完全,造成内存泄漏,所以将虚构函数声明为虚函数是十分有必要的。在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的情况发生,要将基类的析构函数声明为虚函数。

例:

public:
	Parent()
	{
		cout<<"parent construct function"<

运行结果:parent construct function
                  son construct function
                  parent destructor function

将基类的析构函数声明为虚函数:

#include 
using namespace std;

class Parent
{
public:
	Parent()
	{
		cout<<"parent construct function"<

 运行结果:parent construct function
                   son construct function
                   son destructor function
                   parent destructor function

47.vector的底层原理:

vector底层是一个动态数组,包含三个迭代器,start和finish之间是已经被使用的动态范围,end_of_storage是整块连续空间包括备用空间的尾部;

当空间转不下数据时,会自动申请另一片更大的空间,然后把原来的数据拷贝到新的内存空间,接着释放原来的那片空间(vector内存增长机制);

当释放或者删除里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。因此,对vector的任何操作一旦引起了空间的重新配置,指向源vector的所有迭代器都会失效了。 

48.vector中的reserve和resize的区别;

①reserve是直接扩充到已经确定的大小,可以减少多次开辟和释放空间的问题,就可以提高效率,其次还可以减少多次要拷贝数据的问题。resize只是保证vector中的空间大小,最少达到参数所指定的大小n。resrve只有一个人参数。

②resize可以改变有效空间大小,也有改变默认值的功能。capacity的大小也会跟着改变,resize()可以有多个参数。

49.vector中的size和capacity的区别?

size便是当前vector有多少元素:

capacity函数则表示他已经分配的内存中可以容纳多少元素。

50.vector中的erase方法与algorithn中的remove方法区别?

vector中的erase方法真正删除了元素,迭代器不能访问了;

remove只是简单的将元素移到了该容器的最后面,迭代器还是可以访问到。因为algorithn通过迭代器进行操作,不知道容器的内部结构,所以无法进行真正的删除。

51.vector迭代器失效的情况:

①当插入一个元素到vector中,由于引起了内存重新分配,所以指向原内存的迭代器全部失效;

②当删除容器中的一个元素后,该迭代器所指向的元素已经被删除,那么也造成迭代器失效。erase方法会返回下一个有效的迭代器,所以当我们要删除某个元素时,需要it=vec.erase(it);

52.正确释放vector的内存(clear()、swap()、shrink_to_fit())

①vec.clear():清空内存,但是不释放内存;

②vector.swap(vec):清空内容,且释放内存,得到一个全新的vector;

③vec.shrink_to_fit():请求容器降低其capacity和size匹配;

④vec.clear():vec.shrink_to_fit()::清空内容,且释放内存。

53.list的底层原理:

①list底层是一个双向链表,使用链表存储数据,不会将数据存储到一整块连续的内存空间中,各元素占据的内存空间是独立的分散的、他们之间的线性关系通过指针来维持,每次插入或释放一个元素,就开辟或释放一个内存空间;

②list不支持随机存取,如果需要大量的植入或删除,不关心随机存取。

54.分别使用vector、list、deque是在什么情况下?

①vector可以随机存取元素(通过公式直接计算元素地址),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器必vector迭代器复杂的多;

②list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁,比如写多读少的场景;

③需要从收尾两端进行插入或删除操作的时候需要选择deque。

55.priority_queue的底层原理:

priority_queue:优先队列,其底层是用堆来实现的,在优先队列中,队首元素一定是当前队列中优先级最高的哪一个。

56.map/set/multiset/multimap的底层原理

map/set/multiset/multimap的底层实现都是红黑树,epoll模型的底层数据结构也是红黑树,linux系统中CFS进程调度算法,也用到红黑树。

红黑树特性:

①每个节点或是红色或黑色;

②根节点是黑色;

③每个叶节点是黑的;

④如果一个节点是红的,则他的两个儿子均是黑色;

⑤每个节点到其子孙节点的所有路径上包含相同数目的黑色节点。

57.为何map和set的插入删除效率比其他序列容器高:

因为不需要内存拷贝和内存移动。

58.为何map和set每次insert之后,以前保存的iterator不会失效?

因为插入操作只是节点指针换来换去,节点内存没有改变。而iterator就像指向节点的指针,内存没变,指向内存的指针也不会变。

59.当数据元素增多时(10000——20000),map的set查找速度会怎样变化?

RB_TREE用二分查找法,时间复杂度为log(n),所以从10000增到20000时,查找次数从log10000=14次到log20000=15次,多了一次。

60.map/set/multiset/multimap的特点:

①set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复、muliset可以重复。

②map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序,map中元素的key不允许重复,multimap可以重复;

③map和set的增删改查速度都是log(n),是比较高效的。

61.为何map和set的插入删除效率必其他序列容器高,而且每次insert之后,以前保存的iterator不会失效?

①存储的是节点,不需要内存拷贝和内存移动;

②插入操作只是节点指针换来换去,节点内存没有改变,而iterator就像指针节点的指针,内存没变,指向内存的指针也不会变。

62.为何map和set不能向vector一样有个reserve函数来预分配数据?

在map和set内部存储的已经不是元素本身了,而是包含元素的特点。也就是说map内部使用的Alloc>声明的时候从参数中传入的Alloc。

63.set 的底层实现为什么不适用哈希表而使用红黑树?

set中元素是经过排序的,红黑树也是有序的,哈希是无序的,如果只是单纯的吓着元素的话,肯定选哈希表,因为哈希表最好查找时间为O(1),并且如果用到set中是不允许有元素重复的,而红黑树的时间查找复杂度为O(log(n))。

64.hash_map与map的区别?什么时候用hash_map,什么时候用map?

①构造函数:hash_map需要hashfunction和等于函数。而map需要比较函数;

②存储结构:hash_map以hashtable为底层,而map以RB_TREE为底层;

③hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是logn级别,但不一定常熟就别log小,而且hash_map还要hashfunction耗时;

④如果考虑效率,特别元素数量较多时,用hash_map;

⑤考虑内存,或者元素较少时,用map。

65.迭代器失效问题:

插入操作:

①对于vector和string,如果容器内存被重新分配,iterators,poijters,references失效:如果没有重新分配,那么插入点之前的iterator有效,插入点之后的iterator失效;

②对于deque,如果插入点位于front和back的其他位置,iterators,poijters,references失效;当我们插入元素到front和back时,deque的迭代器失效,但references和poijters有效;

③对于list和forward_list.所有的iterators,poijters,references有效。

删除操作:

①对于vector和string,删除点之前的iterators,poijters,references有效;off_the_end迭代器总是失效的;

②对于deque,如果插入点位于front和back的其他位置,iterators,poijters,references失效;当我们插入元素到front和back时,off_the_end失效,但iterator、references和poijters有效;

③对于list和forword_list,所有的iterator、references和poijters有效;

④对于关联容器map来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用,否则会导致程序无定义的行为。

66.STL线程不安全的情况:

①在对同一个容器进行多线程的读写、写操作时;

②在每次调用容器的成员函数期间都要锁定该容器;

③在每个容器返回的迭代器的生存之内都要锁定该容器;

④在每个容器上调用的算法执行期间锁定该容器。

你可能感兴趣的:(笔记,c++,c语言)