当前位置: 首页 > news >正文

盘点近几年腾讯的精选面试题(c/c++研发岗)

map插入方式有几种?

1)用insert函数插入pair数据, mapStudent.insert(pair<int, string>(1, “student_one”));

2)用insert函数插入value_type数据 mapStudent.insert(map<int, string>::value_type (1, “student_one”));

3)在insert函数中使用make_pair()函数 mapStudent.insert(make_pair(1, “student_one”));

4)用数组方式插入数据 mapStudent[1] = “student_one”;

STL中unordered_map(hash_map)和map的区别,hash_map如何解决冲突以及扩容

1)unordered_map和map类似,都是存储的key-value的值,可以通过key快速索引到value。不同的是unordered_map不会根据key的大小进行排序。

2)存储时是根据key的hash值判断元素是否相同,即unordered_map内部元素是无序的,而map中的元素是按照二叉搜索树存储,进行中序遍历会得到有序遍历。

3)所以使用时map的key需要定义operator<。而unordered_map需要定义hash_value函数并且重载operator==。但是很多系统内置的数据类型都自带这些,

4)那么如果是自定义类型,那么就需要自己重载operator<或者hash_value()了。

5)如果需要内部元素自动排序,使用map,不需要排序使用unordered_map

6)unordered_map的底层实现是hash_table;

7)hash_map底层使用的是hash_table,而hash_table使用的开链法进行冲突避免,所有hash_map采用开链法进行冲突解决。

8)什么时候扩容:当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容啦。

9)扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。

vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?

1)通过下标访问vector中的元素时不会做边界检查,即便下标越界。也就是说,下标与first迭代器相加的结果超过了finish迭代器的位置,程序也不会报错,而是返回这个地址中存储的值。如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。

2)map的下标运算符[]的作用是:将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。

3)erase()函数,只能删除内容,不能改变容量大小; erase成员函数,它删除了itVect迭代器指向的元素,并且返回要被删除的itVect之后的迭代器,迭代器相当于一个智能指针;clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么你可以选择deque容器。

map[]与find的区别?

1)map的下标运算符[]的作用是:将关键码作为下标去执行查找,并返回对应的值;如果不存在这个关键码,就将一个具有该关键码和值类型的默认值的项插入这个map。

2)map的find函数:用关键码执行查找,找到了返回该位置的迭代器;如果不存在这个关键码,就返回尾迭代器。

STL中list与queue之间的区别

1)list不再能够像vector一样以普通指针作为迭代器,因为其节点不保证在存储空间中连续存在;

2)list插入操作和结合才做都不会造成原有的list迭代器失效;

3)list不仅是一个双向链表,而且还是一个环状双向链表,所以它只需要一个指针;

4)list不像vector那样有可能在空间不足时做重新配置、数据移动的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;

5)deque是一种双向开口的连续线性空间,所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作;可以在头尾两端分别做元素的插入和删除操作;

6)deque和vector最大的差异,一在于deque允许常数时间内对起头端进行元素的插入或移除操作,二在于deque没有所谓容量概念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,deque没有所谓的空间保留功能。

STL中的allocator,deallocator

1)第一级配置器直接使用malloc()、free()和relloc(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,便调用第一级配置器;当配置器区块小于128bytes时,为了降低额外负担,使用复杂的内存池整理方式,而不再用一级配置器;

2)第二级配置器主动将任何小额区块的内存需求量上调至8的倍数,并维护16个free-list,各自管理大小为8~128bytes的小额区块;

3)空间配置函数allocate(),首先判断区块大小,大于128就直接调用第一级配置器,小于128时就检查对应的free-list。如果free-list之内有可用区块,就直接拿来用,如果没有可用区块,就将区块大小调整至8的倍数,然后调用refill(),为free-list重新分配空间;

4)空间释放函数deallocate(),该函数首先判断区块大小,大于128bytes时,直接调用一级配置器,小于128bytes就找到对应的free-list然后释放内存。

STL中hash_map扩容发生什么?

1)hash table表格内的元素称为桶(bucket),而由桶所链接的元素称为节点(node),其中存入桶元素的容器为stl本身很重要的一种序列式容器——vector容器。之所以选择vector为存放桶元素的基础容器,主要是因为vector容器本身具有动态扩容能力,无需人工干预。

2)向前操作:首先尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易完成前进操作,如果目前正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。

vector的增加删除都是怎么做的?为什么是1.5倍?

1)新增元素:vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;

2)对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ;

3)初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;

4)不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。

1)考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2二倍的方式扩容,或者以1.5倍的方式扩容。

2)以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间:

3)向量容器vector的成员函数pop_back()可以删除最后一个元素.

4)而函数erase()可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素。

5)还可以采用通用算法remove()来删除vector容器中的元素.

6)不同的是:采用remove一般情况下不会改变容器的大小,而pop_back()与erase()等成员函数会改变容器的大小。

函数指针?

1)什么是函数指针? 函数指针指向的是特殊的数据类型,函数的类型是由其返回的数据类型和其参数列表共同决定的,而函数的名称则不是其类型的一部分。 一个具体函数的名字,如果后面不跟调用符号(即括号),则该名字就是该函数的指针(注意:大部分情况下,可以这么认为,但这种说法并不很严格)。

2)函数指针的声明方法 int (pf)(const int&, const int&); (1) 上面的pf就是一个函数指针,指向所有返回类型为int,并带有两个const int&参数的函数。注意pf两边的括号是必须的,否则上面的定义就变成了: int *pf(const int&, const int&); (2) 而这声明了一个函数pf,其返回类型为int *, 带有两个const int&参数。

3)为什么有函数指针 函数与数据项相似,函数也有地址。我们希望在同一个函数中通过使用相同的形参在不同的时间使用产生不同的效果。

4)一个函数名就是一个指针,它指向函数的代码。一个函数地址是该函数的进入点,也就是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为变元传递给其他函数;

5)两种方法赋值: 指针名 = 函数名; 指针名 = &函数名

说说你对c和c++的看法,c和c++的区别?

1)第一点就应该想到C是面向过程的语言,而C++是面向对象的语言,一般简历上第一条都是熟悉C/C++基本语法,了解C++面向对象思想,那么,请问什么是面向对象?

2)C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字;(关于malooc/free与new/delete的不同又可以说一大堆,最后的扩展_1部分列出十大区别);

3)接下来就不得不谈到C中的struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;

4)C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是 _fun,一般是这样的,所以C++才会支持不同的参数调用不同的函数;

5)C++中有引用,而C没有;这样就不得不提一下引用和指针的区别(文后扩展_2);

6)当然还有C++全部变量的默认链接属性是外链接,而C是内连接;

7)C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);

8)当然还有局部变量的声明规则不同,多态,C++特有输入输出流之类的,很多,下面就不再列出来了;

c/c++的内存分配,详细说一下栈、堆、静态存储区?

1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等 其操作方式类似于数据结构中的栈。

2、堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

4、文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放。

5、程序代码区 —存放函数体的二进制代码。

堆与栈的区别?

1)管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

2)空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

3)碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。

4)生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

5)分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。

6)分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

野指针是什么?如何检测内存泄漏?

1)野指针:指向内存被释放的内存或者没有访问权限的内存的指针。

2)“野指针”的成因主要有3种:

①指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如 char *p = NULL; char *str = new char(100);

②指针p被free或者delete之后,没有置为NULL;

③指针操作超越了变量的作用范围。

3)如何避免野指针:

①对指针进行初始化

①将指针初始化为NULL。 char * p = NULL;

②用malloc分配内存 char * p = (char * )malloc(sizeof(char));

③用已有合法的可访问的内存地址对指针初始化 char num[ 30] = {0}; char *p = num;

②指针用完后释放内存,将指针赋NULL。 delete§; p = NULL;

悬空指针和野指针有什么区别?

1)野指针:野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界

2)悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针。野指针是那些未初始化的指针。

今天分享的只是腾讯面试题当中的冰山一角,小编整理了1000道c++研发岗位的面试题,需要的朋友可以加baby602878196(备注123 免费领取)

内存泄漏

1)内存泄漏 内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制;

2)后果 只发生一次小的内存泄漏可能不被注意,但泄漏大量内存的程序将会出现各种证照:性能下降到内存逐渐用完,导致另一个程序失败;

3)如何排除 使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误; 调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。

4)解决方法 智能指针。

5)检查、定位内存泄漏 检查方法:在main函数最后面一行,加上一句_CrtDumpMemoryLeaks()。

调试程序,自然关闭程序让其退出,查看输出: 输出这样的格式{453}normal block at 0x02432CA8,868 bytes long 被{}包围的453就是我们需要的内存泄漏定位值,868 bytes long就是说这个地方有868比特内存没有释放。

定位代码位置 在main函数第一行加上_CrtSetBreakAlloc(453);意思就是在申请453这块内存的位置中断。

然后调试程序,程序中断了,查看调用堆栈。加上头文件#include <crtdbg.h>

new和malloc的区别?

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

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

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

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

5、new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

delete p;与delete[]p,allocator

1、动态数组管理new一个数组时,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数;

2、new动态数组返回的并不是数组类型,而是一个元素类型的指针;

3、delete[]时,数组中的元素按逆序的顺序进行销毁;

4、new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起,同样的,delete也是将对象析构和内存释放组合在一起的。allocator将这两部分分开进行,allocator申请一部分内存,不进行初始化对象,只有当需要的时候才进行初始化操作。

new和delete的实现原理, delete是如何知道释放内存的大小的额?

1、new简单类型直接调用operator new分配内存;而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小后调用operator new;对于复杂数据结构,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;

①new表达式调用一个名为operator new(operator new[])函数,分配一块足够大的、原始的、未命名的内存空间;

②编译器运行相应的构造函数以构造这些对象,并为其传入初始值;

③对象被分配了空间并构造完成,返回一个指向该对象的指针。

2、delete简单数据类型默认只是调用free函数;复杂数据类型先调用析构函数再调用operator delete;针对简单类型,delete和delete[]等同。假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址。delete[]实际释放的就是p-4指向的内存。而delete会直接释放p指向的内存,这个内存根本没有被系统记录,所以会崩溃。

3、需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

malloc申请的存储空间能用delete释放吗

不能,malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作对象都是必须明确大小的。而且不能用在动态类上。new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

malloc与free的实现原理?

1、在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk、mmap、,munmap这些系统调用实现的;

2、brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系;

3、malloc小于128k的内存,使用brk分配内存,将_edata往高地址推;malloc大于128k的内存,使用mmap分配内存,在堆和栈之间找一块空闲内存分配;brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单独释放。当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲内存超过128K,于是内存紧缩。

4、malloc是从堆里面申请内存,也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

malloc、realloc、calloc的区别

1)malloc函数 void* malloc(unsigned int num_size); int p = malloc(20sizeof(int));申请20个int类型的空间;

2)calloc函数 void* calloc(size_t n,size_t size); int *p = calloc(20, sizeof(int)); 省去了人为空间计算;malloc申请的空间的值是随机初始化的,calloc申请的空间的值是初始化为0的;

3)realloc函数 void realloc(void *p, size_t new_size); 给动态分配的空间分配额外的空间,用于扩充容量。

__stdcall和__cdecl的区别?

1)__stdcall __stdcall是函数恢复堆栈,只有在函数代码的结尾出现一次恢复堆栈的代码;在编译时就规定了参数个数,无法实现不定个数的参数调用;

2)__cdecl __cdecl是调用者恢复堆栈,假设有100个函数调用函数a,那么内存中就有100端恢复堆栈的代码;可以不定参数个数;每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用__stacall函数大。

使用智能指针管理内存资源,RAII

1)RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。

2)智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。

手写实现智能指针类

1)智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer<T*>对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1

2)一个构造函数、拷贝构造函数、复制构造函数、析构函数、移走函数;

内存对齐?位域?

1、 分配内存的顺序是按照声明的顺序。

2、 每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止。

3、 最后整个结构体的大小必须是里面变量类型最大值的整数倍。

添加了#pragma pack(n)后规则就变成了下面这样:

1、 偏移量要是n和当前变量大小中较小值的整数倍

2、 整体大小要是n和最大变量大小中较小值的整数倍

3、 n值必须为1,2,4,8…,为其他值时就按照默认的分配规则

结构体变量比较是否相等

1)重载了 “” 操作符

struct foo {
int a;
int b;
bool operator(const foo& rhs) // 操作运算符重载
{
return( a == rhs.a) && (b == rhs.b);}
1
};

2)元素的话,一个个比; 3)指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真;

位运算

若一个数m满足 m = 2^n;那么k%m=k&(m-1)

为什么内存对齐

1、平台原因(移植原因)

1)不是所有的硬件平台都能访问任意地址上的任意数据的;

2)某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异

2、性能原因:

1)数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

2)原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

函数调用过程栈的变化,返回值和参数变量哪个先入栈?

1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈;

2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作隐含在call指令中);

3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov ebp,esp);

4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;

怎样判断两个浮点数是否相等?

对两个浮点数判断大小和是否相等不能直接用==来判断,会出错!明明相等的两个数比较反而是不相等!对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值!浮点数与0的比较也应该注意。与浮点数的表示方式有关。

define、const、typedef、inline使用方法?

一、const与#define的区别:

1)const定义的常量是变量带类型,而#define定义的只是个常数不带类型;

2)define只在预处理阶段起作用,简单的文本替换,而const在编译、链接过程中起作用;

3)define只是简单的字符串替换没有类型检查。而const是有数据类型的,是要进行判断的,可以避免一些低级错误;

4)define预处理后,占用代码段空间,const占用数据段空间;

5)const不能重定义,而define可以通过#undef取消某个符号的定义,进行重定义;

6)define独特功能,比如可以用来防止文件重复引用。

二、#define和别名typedef的区别

1)执行时间不同,typedef在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;

2)功能差异,typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

3)作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。

三、define与inline的区别

1)#define是关键字,inline是函数;

2)宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换;

3)inline函数有类型检查,相比宏定义比较安全;

printf实现原理?

在C/C++中,对函数参数的扫描是从后向前的。C/C++的函数参数是通过压入堆栈的方式来给函数传参数的(堆栈是一种先进后出的数据结构),最先压入的参数最后出来,在计算机的内存中,数据有2块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针了,最先压入的参数是在最上面,就是说在所有参数的最后面,最后压入的参数在最下面,结构上看起来是第一个,所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方。printf的第一个被找到的参数就是那个字符指针,就是被双引号括起来的那一部分,函数通过判断字符串里控制参数的个数来判断参数个数及数据类型,通过这些就可算出数据需要的堆栈指针的偏移量了,下面给出printf(“%d,%d”,a,b);(其中a、b都是int型的)的汇编代码.

#include 的顺序以及尖叫括号和双引号的区别

表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件; 表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。

lambda函数

1)利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;

2)每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。

3)lambda表达式的语法定义如下: [capture] (parameters) mutable ->return-type {statement}; 4)lambda必须使用尾置返回来指定返回类型,可以忽略参数列表和返回值,但必须永远包含捕获列表和函数体;

hello world 程序开始到打印到屏幕上的全过程?

1.用户告诉操作系统执行HelloWorld程序(通过键盘输入等)

2.操作系统:找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。

3.操作系统:创建一个新进程,将HelloWorld可执行文件映射到该进程结构,表示由该进程执行helloworld程序。

4.操作系统:为helloworld程序设置cpu上下文环境,并跳到程序开始处。

5.执行helloworld程序的第一条指令,发生缺页异常

6.操作系统:分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序

7.helloword程序执行puts函数(系统调用),在显示器上写一字符串

8.操作系统:找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程

9.操作系统:控制设备的进程告诉设备的窗口系统,它要显示该字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区

10.视频硬件将像素转换成显示器可接收和一组控制数据信号

11.显示器解释信号,激发液晶屏

12.OK,我们在屏幕上看到了HelloWorld

模板类和模板函数的区别是什么?

函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。在使用时类模板必须加,而函数模板不必

为什么模板类一般都是放在一个h文件中

1)模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

2)在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。

C++中类成员的访问权限和继承权限问题。

1)三种访问权限

①public:用该关键字修饰的成员表示公有成员,该成员不仅可以在类内可以被 访问,在类外也是可以被访问的,是类对外提供的可访问接口;

② private:用该关键字修饰的成员表示私有成员,该成员仅在类内可以被访问,在类体外是隐藏状态;

③ protected:用该关键字修饰的成员表示保护成员,保护成员在类体外同样是隐藏状态,但是对于该类的派生类来说,相当于公有成员,在派生类中可以被访问。

2)三种继承方式

①若继承方式是public,基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;

② 若继承方式是private,基类所有成员在派生类中的访问权限都会变为私有(private)权限;

③若继承方式是protected,基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,私有成员在派生类中的访问权限仍然是私有(private)权限。

cout和printf有什么区别?

cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。 cout是有缓冲输出: cout < < "abc " < <endl; 或cout < < "abc\n ";cout < <flush; 这两个才是一样的. endl相当于输出回车后,再强迫缓冲输出。 flush立即强迫缓冲输出。 printf是无缓冲输出。有输出时立即输出

重载运算符?

1、我们只能重载已有的运算符,而无权发明新的运算符;对于一个重载的运算符,其优先级和结合律与内置类型一致才可以;不能改变运算符操作数个数;

2、. :: ?: sizeof typeid **不能重载;

3、两种重载方式,成员运算符和非成员运算符,成员运算符比非成员运算符少一个参数;下标运算符、箭头运算符必须是成员运算符;

4、引入运算符重载,是为了实现类的多态性;

5、当重载的运算符是成员函数时,this绑定到左侧运算符对象。成员运算符函数的参数数量比运算符对象的数量少一个;至少含有一个类类型的参数;

6、从参数的个数推断到底定义的是哪种运算符,当运算符既是一元运算符又是二元运算符(+,-,*,&);

7、下标运算符必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量版本和非常量版本;

8、箭头运算符必须是类的成员,解引用通常也是类的成员;重载的箭头运算符必须返回类的指针;

函数重载函数匹配原则

1)名字查找 2)确定候选函数 3)寻找最佳匹配

定义和声明的区别

1.如果是指变量的声明和定义 从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。而定义就是分配了内存。

2.如果是指函数的声明和定义 声明:一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。 定义:一般在源文件里,具体就是函数的实现过程 写明函数体。

C++类型转换有四种

1)static_cast能进行基础类型之间的转换,也是最常看到的类型转换。它主要有如下几种用法:

1 . 用于类层次结构中父类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成父类表示)是安全的;

2 . 进行下行转换(把父类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的;

3 . 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

4 . 把void指针转换成目标类型的指针(不安全!!)

5 . 把任何类型的表达式转换成void类型。

2)const_cast运算符用来修改类型的const或volatile属性。除了去掉const 或volatile修饰之外, type_id和expression得到的类型是一样的。但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。

3)reinterpret_cast它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

4)dynamic_cast 主要用在继承体系中的安全向下转型。它能安全地将指向基类的指针转型为指向子类的指针或引用,并获知转型动作成功是否。转型失败会返回null(转型对象为指针时)或抛出异常bad_cast(转型对象为引用时)。 dynamic_cast 会动用运行时信息(RTTI)来进行类型安全检查,因此 dynamic_cast 存在一定的效率损失。当使用dynamic_cast时,该类型必须含有虚函数,这是因为dynamic_cast使用了存储在VTABLE中的信息来判断实际的类型,RTTI运行时类型识别用于判断类型。typeid表达式的形式是typeid(e),typeid操作的结果是一个常量对象的引用,该对象的类型是type_info或type_info的派生。

全局变量和static变量的区别

1、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。static全局变量与普通的全局变量的区别是static全局变量只初始化一次,防止在其他文件单元被引用。

2.static函数与普通函数有什么区别?   static函数与普通的函数作用域不同。尽在本文件中。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。 static函数与普通函数最主要区别是static函数在内存中只有一份,普通静态函数在每个被调用中维持一份拷贝程序的局部变量存在于(堆栈)中,全局变量存在于(静态区)中,动态申请数据存在于(堆)

静态成员与普通成员的区别

1)生命周期 静态成员变量从类被加载开始到类被卸载,一直存在; 普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;

2)共享方式 静态成员变量是全类共享;普通成员变量是每个对象单独享用的;

3)定义位置 普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;

4)初始化位置 普通成员变量在类中初始化;静态成员变量在类外初始化;

5)默认实参 可以使用静态成员变量作为默认实参,

说一下理解 ifdef endif

1)一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

2)条件编译命令最常见的形式为: #ifdef 标识符 程序段1 #else 程序段2 #endif 它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。 其中#else部分也可以没有,即: #ifdef 程序段1 #denif

3)在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量“重定义”错误。在头文件中使用#define、#ifndef、#ifdef、#endif能避免头文件重定义。

隐式转换,如何消除隐式转换?

1.C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换

2.C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。在比如,数值和布尔类型的转换,整数和浮点数的转换等。某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。

3.基本数据类型 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。自定义对象 子类对象可以隐式的转换为父类对象。

4.C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

5.如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。

多继承的优缺点,作为一个开发者怎么看待多继承

1)C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。

2)多重继承的优点很明显,就是对象可以调用多个基类中的接口;

3)如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性

4)加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。

5)使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。

迭代器++it,it++哪个好,为什么

1)前置返回一个引用,后置返回一个对象 // ++i实现代码为: int& operator++() { *this += 1; return *this; }

2)前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低 //i++实现代码为: int operator++(int) { int temp = *this; ++*this; return temp; }

C++如何处理多个异常的?

1)C++中的异常情况: 语法错误(编译错误):比如变量未定义、括号不匹配、关键字拼写错误等等编译器在编译时能发现的错误,这类错误可以及时被编译器发现,而且可以及时知道出错的位置及原因,方便改正。 运行时错误:比如数组下标越界、系统内存不足等等。这类错误不易被程序员发现,它能通过编译且能进入运行,但运行时会出错,导致程序崩溃。为了有效处理程序运行时错误,C++中引入异常处理机制来解决此问题。

2)C++异常处理机制: 异常处理基本思想:执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理, 而是抛出该异常,让函数的调用者直接或间接处理这个问题。 C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获) 抛出异常的语句格式为:throw 表达式;如果try块中程序段发现了异常则抛出异常。 try { 可能抛出异常的语句;(检查) } catch(类型名[形参名])//捕获特定类型的异常 { //处理1; } catch(类型名[形参名])//捕获特定类型的异常 { //处理2; } catch(…)//捕获所有类型的异常 { }

模板和实现可不可以不写在一个文件里面?为什么?

因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。 《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

在成员函数中调用delete this会出现什么问题?对象还可以使用吗?

1.在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。

2.为什么是不可预期的问题? delete this之后不是释放了类对象的内存空间了么,那么这段内存应该已经还给系统,不再属于这个进程。照这个逻辑来看,应该发生指针错误,无访问权限之类的令系统崩溃的问题才对啊?这个问题牵涉到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。此时这段内存是可以访问的,你可以加上100,加上200,但是其中的值却是不确定的。当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。

3.如果在类的析构函数中调用delete this,会发生什么? 会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。

智能指针的作用

1)C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

2)智能指针在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr。shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

3)初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr p4 = new int(1);的写法是错误的 拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象

4)unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

5)智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

6)weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.

auto_ptr作用

1)auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题;抛出异常,将导致指针p所指向的空间得不到释放而导致内存泄漏;

2)auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。我们实际上是创建一个auto_ptr类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放,所以不会有内存泄漏;

3)auto_ptr的构造函数是explicit,阻止了一般指针隐式转换为 auto_ptr的构造,所以不能直接将一般类型的指针赋值给auto_ptr类型的对象,必须用auto_ptr的构造函数创建对象;

4)由于auto_ptr对象析构时会删除它所拥有的指针,所以使用时避免多个auto_ptr对象管理同一个指针;

5)Auto_ptr内部实现,析构函数中删除对象用的是delete而不是delete[],所以auto_ptr不能管理数组;

6)auto_ptr支持所拥有的指针类型之间的隐式类型转换。

7)可以通过和->运算符对auto_ptr所有用的指针进行提领操作; 8)T get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回。

class、union、struct的区别

1)C语言中,struct只是一个聚合数据类型,没有权限设置,无法添加成员函数,无法实现面向对象编程,且如果没有typedef结构名,声明结构变量必须添加关键字struct。

2)C++中,struct功能大大扩展,可以有权限设置(默认权限为public),可以像class一样有成员函数,继承(默认public继承),可以实现面对对象编程,允许在声明结构变量时省略关键字struct。

3)C与C++中的union:一种数据格式,能够存储不同的数据类型,但只能同时存储其中的一种类型。C++ union结构式一种特殊的类。它能够包含访问权限、成员变量、成员函数(可以包含构造函数和析构函数)。它不能包含虚函数和静态数据变量。它也不能被用作其他类的基类,它本身也不能有从某个基类派生而来。Union中得默认访问权限是public。union类型是共享内存的,以size最大的结构作为自己的大小。每个数据成员在内存中的起始地址是相同的。

4)在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。在某一时刻,一个union中只能有一个值是有效的。union的一个用法就是可以用来测试CPU是大端模式还是小端模式:

动态联编与静态联编

1)在C++中,联编是指一个计算机程序的不同部分彼此关联的过程。按照联编所进行的阶段不同,可以分为静态联编和动态联编;

2)静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。

3)动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)

4)实现动态联编三个条件: 必须把动态联编的行为定义为类的虚函数; 类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来; 必须先使用基类指针指向子类型的对象,然后直接或间接使用基类指针调用虚函数;

动态编译与静态编译

1)静态编译,编译器在编译可执行文件时,把需要用到的对应动态链接库中的部分提取出来,连接到可执行文件中去,使可执行文件在运行时不需要依赖于动态链接库;

2)动态编译的可执行文件需要附带一个动态链接库,在执行时,需要调用其对应动态链接库的命令。所以其优点一方面是缩小了执行文件本身的体积,另一方面是加快了编译速度,节省了系统资源。缺点是哪怕是很简单的程序,只用到了链接库的一两条命令,也需要附带一个相对庞大的链接库;二是如果其他计算机上没有安装对应的运行库,则用动态编译的可执行文件就不能运行。

动态链接和静态链接区别

1)静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。

2)静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。

3)动态库就是在需要调用其中的函数时,根据函数映射表找到该函数然后调入堆栈执行。如果在当前工程中有多处对dll文件中同一个函数的调用,那么执行时,这个函数只会留下一份拷贝。但是如果有多处对lib文件中同一个函数的调用,那么执行时,该函数将在当前程序的执行空间里留下多份拷贝,而且是一处调用就产生一份拷贝。

在不使用额外空间的情况下,交换两个数?

1)算术 x = x + y; y = x - y; x = x - y;

2)异或 x = x^y;// 只能对int,char… y = x^y; x = x^y; x ^= y ^= x;

strcpy和memcpy的区别

1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

执行int main(int argc, char *argv[])时的内存结构

参数的含义是程序在命令行下运行的时候,需要输入argc 个参数,每个参数是以char 类型输入的,依次存在数组里面,数组是 argv[],所有的参数在指针 char * 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称。

volatile关键字的作用?

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 volatile用在如下的几个地方:

中断服务程序中修改的供其它程序检测的变量需要加volatile; 多任务环境下各任务间共享的标志应该加volatile; 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

讲讲大端小端,如何检测(三种方法)

大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址端。 小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。

1)直接读取存放在内存中的十六进制数值,取低位进行值判断 int a = 0x12345678; int *c = &a; c[0] == 0x12 大端模式 c[0] == 0x78 小段模式

2)用共同体来进行判断 union共同体所有数据成员是共享一段内存的,后写入的成员数据将覆盖之前的成员数据,成员数据都有相同的首地址。Union的大小为最大数据成员的大小。 union的成员数据共用内存,并且首地址都是低地址首字节。Int i= 1时:大端存储1放在最高位,小端存储1放在最低位。当读取char ch时,是最低地址首字节,大小端会显示不同的值。 union w w p; { p.i = 1; int i; if(ch == 1) char ch; };

查看内存的方法

1.首先打开vs编译器,创建好项目,并且将代码写进去,这里就不贴代码了,你可以随便的写个做个测试;

2.调试的时候做好相应的断点,然后点击开始调试;

3.程序调试之后会在你设置断点的地方暂停,然后选择调试->窗口->内存,就打开了内存数据查看的窗口了。

空类会默认添加哪些东西?怎么写?

1)Empty(); // 缺省构造函数//

2)Empty( const Empty& ); // 拷贝构造函数//

3)~Empty(); // 析构函数//

4)Empty& operator=( const Empty& ); // 赋值运算符//

为什么拷贝构造函数必须传引用不能传值?

拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。 参数传递过程到底发生了什么? 将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!

1)值传递: 对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量); 对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj); 首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用 ii)引用传递: 无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型). 上述1)

2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用,内存溢出。 拷贝构造函数用来初始化一个非引用类类型对象,如果用传值的方式进行传参数,那么构造实参需要调用拷贝构造函数,而拷贝构造函数需要传递实参,所以会一直递归。

空类的大小是多少?为什么?

1)C++空类的大小不为0,不同编译器设置不一样,vs设置为1;

2)C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址;

3)带有虚函数的C++类大小不为1,因为每一个对象会有一个vptr指向虚函数表,具体大小根据指针大小确定;

4)C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。

你什么情况用指针当参数,什么时候用引用,为什么?

1)使用引用参数的主要原因有两个: 程序员能修改调用函数中的数据对象 通过传递引用而不是整个数据–对象,可以提高程序的运行速度

2)一般的原则: 对于使用引用的值而不做修改的函数: 如果数据对象很小,如内置数据类型或者小型结构,则按照值传递; 如果数据对象是数组,则使用指针(唯一的选择),并且指针声明为指向const的指针; 如果数据对象是较大的结构,则使用const指针或者引用,已提高程序的效率。这样可以节省结构所需的时间和空间; 如果数据对象是类对象,则使用const引用(传递类对象参数的标准方式是按照引用传递);

3)对于修改函数中数据的函数: 如果数据是内置数据类型,则使用指针 如果数据对象是数组,则只能使用指针 如果数据对象是结构,则使用引用或者指针 如果数据是类对象,则使用引用

大内存申请时候选用哪种?C++变量存在哪?变量的大小存在哪?符号表存在哪?

1.大内存申请时,采用堆申请空间,用new申请;

2.不同的变量存储在不同的地方,局部变量、全局变量、静态变量;

3.C++对变量名不作存储,在汇编以后不会出现变量名,变量名作用只是用于方便编译成汇编代码,是给编译器看的,是方便人阅读的

相关文章:

盘点近几年腾讯的精选面试题(c/c++研发岗)

map插入方式有几种&#xff1f; 1)用insert函数插入pair数据&#xff0c; mapStudent.insert(pair<int, string>(1, “student_one”)); 2)用insert函数插入value_type数据 mapStudent.insert(map<int, string>::value_type (1, “student_one”)); 3)在insert函…...

C++详细笔记(四)

1.类和对象 1.1拷贝构造函数&#xff08;补充&#xff09; class Date{ public:int _year;int _month;int _day; // 全缺省的构造函数Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;} } int main() {Date d1(2024, 4, 14);Date d2(d1…...

架构师备考-背诵精华(系统架构评估)

系统架构评估是在对架构分析、评估的基础上&#xff0c;对架构策略的选取进行决策。它利用数学或逻辑分析技术&#xff0c;针对系统的一致性、正确性、质量属性、规划结果等不同方面&#xff0c;提供描述性、预测性和指令性的分析结果。 重要概念 敏感点&#xff1a;敏感点是…...

QT 实现自定义水波进度条

1.界面实现效果 以下是具体的项目需要用到的效果展示。 2.简介 原理:随着进度的改变,在我们的绘制图像void paintEvent(QPaintEvent *) override;事件中绘制图形。 使用QPainter来绘制正弦波,通过定时器,不断的更新我们绘制的图形,动态改变正弦波的参数来创建动画效果…...

厨房老鼠数据集:掀起餐饮卫生监测的科技浪潮

厨房老鼠数据集&#xff1a;掀起餐饮卫生监测的科技浪潮 摘要&#xff1a;本文深入探讨了厨房老鼠数据集在餐饮行业卫生管理中的重要性及其相关技术应用。厨房老鼠数据集通过收集夜间厨房图像、老鼠标注信息以及环境数据&#xff0c;为深度学习模型提供了丰富的训练样本。基于…...

Linux系统:more命令

1、命令详解&#xff1a; more命令是Linux系统中的一个文本查看器&#xff0c;它可以一次显示一页内容&#xff0c;并提供了一些方便的浏览操作&#xff0c;比如向下滚动、向上滚动、搜索等。当需要查看大型文件或长文本时&#xff0c;通过分页显示可以很好地避免一次性将整个文…...

ESP32-S3学习笔记:分区表(Partition Table)的二进制分析

一、参考资料 用于研究的官方示例代码&#xff1a;esp-idf-v5.3\examples\storage\partition_api\partition_find参考的官方文档&#xff1a;ESP-IDF编程指南&#xff1a;分区表 二、准备工作 用VS Code打开示例代码&#xff0c;打开示例代码的CSV自定义分区表&#xff0c;如…...

04,perl

1 &#xff0c;作用 &#xff1a; 2 &#xff0c;原理 &#xff1a; 3 &#xff0c;使用场景 &#xff1a;...

Js 更加优雅地实现Form表单重置

文章目录 前言代码实现代码优化 前言 最近在做一个后台项目 不免大量使用表单 表单查询 编辑 还原 导入导出 不免要经常实现 记录下表单重置的一些方法 代码实现 <div class"Query"><el-form :model"ruleForm" ref"ruleFormref" labe…...

基于微信小程序的驾校预约系统设计与实现

文章目录 前言项目介绍技术介绍功能介绍核心代码数据库参考 系统效果图文章目录 前言 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 当今社会已经步入了科学技术进步和经济社会快速发展的新时期&#xff0c;国际信息和学术交流也不…...

利用python进行数据处理,得到对自己有利的内容

一&#xff0c;介绍 在2025年的公务员录取信息发布后&#xff0c;许多考生和求职者可能会感到困惑&#xff0c;尤其是在面对庞大的职位列表时&#xff0c;如何快速找到适合自己的职位成为了一个亟待解决的问题。面对这样的问题&#xff0c;以下几点建议或许能够帮助大家更有效地…...

Java面试题五

一、谈谈你对Java内存模型&#xff08;JVM Memory Model&#xff09;的理解。 Java内存模型&#xff08;Java Memory Model&#xff0c;简称JMM&#xff09;是Java虚拟机&#xff08;JVM&#xff09;规范中定义的一种关于内存访问、共享变量在多线程之间的可见性、以及原子性、…...

2013 lost connection to MySQL server during query

1.问题 使用navicat连接doris&#xff0c;会有这个错误。 2.解决 换低版本的navicat比如navicat11。...

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-1

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…...

Android便携式WLAN热点分析

Android便携式WLAN热点分析 Platform: RK3368 OS: Android 6.0 Kernel: 3.10.0 文章目录 Android便携式WLAN热点分析分析Android开启WLAN热点的路由策略和防火墙便携式WLAN热点开启流程路由表路由策略iptables(防火墙) 用hostapd命令手动开启WLAN热点1.加载驱动2.配置wlan0 …...

从经济学原理看团队分工合作

最近我接触到一个经济学概念叫"比较优势"。学过经济学的同学应该很熟悉&#xff0c;但为了让不了解经济学的朋友们也能明白&#xff0c;我们先来解释下什么是比较优势&#xff0c;然后再谈谈为什么我认为这个原理应当被用在团队合作的分工当中。 比较优势是19世纪初…...

企业一级流程架构规划方法

在之前关于企业业务流程规划的系列文章中&#xff0c;我们已经分别对企业业务流程规划的价值和原则、企业的业务流程架构的应用、两种常见的企业总体业务流程架构模式等进行了比较深入的分析和阐述&#xff0c;相信大多数企业同仁&#xff0c;已经对企业的业务流程规划&#xf…...

用ESP32驱动stt7735 TFT屏幕

操作流程&#xff1a; 1 在esp32项目中搜索TFT库 2 找到对应TFT_eSPI库&#xff0c;添加进所在项目中&#xff08;这里可能下载很久&#xff09; 3 找到对应屏幕文件下的User_Setup.h 修改&#xff0c;将驱动换成ST7735_DRIVER&#xff0c;并修改对应屏幕引脚和屏幕尺寸&…...

Yolo目标检测:Yolo v1简介

一、Yolo系列介绍 YOLO&#xff08;You Only Look Once&#xff09;是一种基于深度学习的目标检测算法&#xff0c;由Joseph Redmon等人于2016年提出。它的核心思想是将目标检测问题转化为一个回归问题&#xff0c;通过一个神经网络直接预测目标的类别和位置。YOLO算法将输入图…...

Java動態轉發代理IP詳解

Java中實現動態轉發代理IP 在Java中實現動態轉發代理IP並不複雜&#xff0c;通常可以通過一些開源庫和框架來實現。下麵是一個簡單的實現思路&#xff1a; 設置HTTP請求&#xff1a;在Java中&#xff0c;可以使用HttpURLConnection或Apache HttpClient來發送HTTP請求。在發送…...

Android15之解决gdb:Remote register badly formatted问题(二百三十六)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…...

【Router】路由器中NAT、NAPT、NPT是什么?

参考链接 NAT vs. NAPT: What’s the Difference? IPv6 Network Prefix Translation (NPt) | pfSense Documentation (netgate.com) 趣谈NAT/NAPT的原理&#xff0c;这篇不可不读&#xff01; - 知乎 (zhihu.com) NAT (Network Address Translation) NAT说明 NAT&#x…...

代码随想录算法训练营第三十九天|背包问题,416. 分割等和子集

背包问题&#xff0c;416. 分割等和子集 背包问题416. 分割等和子集 背包问题 有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 卡玛网的…...

可调用对象和Lambda

可调用对象&#xff1a; 函数 函数指针 函数对象 Lambda表达式(匿名函数) 01 函数对象 如果一个类实现了"函数调用运算符()"的重载&#xff0c;那么这个类的对象称为函数对象(仿函数) 函数对象的行为&#xff0c;类似于函数&#xff0c;可以被调用 #include …...

华为认证HCIE存储考啥?未来发展方向在哪?一个月就能轻松拿下?

说起HCIE&#xff0c;很多人第一反应都是路由交换、网络安全那些“热门”方向&#xff0c;而存储方向反而成了小众的存在。 其实&#xff0c;存储的江湖地位一点不低&#xff0c;尤其在数据爆炸的时代。 今天咱们就聊聊HCIE存储考什么、为什么要学&#xff0c;以及未来的可能…...

如何让自己的网站,被更多的人搜索到(免费方案)

文章目录 一、要做时间的朋友二、需要独立IP的服务器三、SEO信息如何设置设置网站TDK生成网站地图设置搜索引擎自动提交部署SSL证书加分项&#xff1a;定期更新文章 引言&#xff1a; 许多人都有这样一个问题&#xff1a;做好自己的网站&#xff0c;如何让这个网站被更多的人浏…...

Modbus 协议:工业自动化领域的通信脊梁

一、引言 在当今工业自动化的舞台上&#xff0c;数据的准确传输和设备间的有效通信是实现高效生产、精准控制的关键。Modbus 协议作为一种应用广泛、历史悠久的通信协议&#xff0c;在工业领域发挥着举足轻重的作用。从工厂的生产线到智能建筑的控制系统&#xff0c;从能源管理…...

函数的力量:掌握C语言的基石

目录 前言 标准库&#xff1a;C语言的百宝箱 头文件&#xff1a;库函数的藏宝图 实例分析&#xff1a;计算平方根的sqrt函数 功能描述 头文件包含的重要性 库函数文档的一般格式 自定义函数&#xff1a;释放你的编程创造力 函数的语法形式 函数的比喻 函数的举例 简化…...

U-Boot的移植流程

U-Boot的简化版启动流程&#xff1a; 1、设置状态寄存器 cpsr &#xff0c;使CPU进入 SVC 特权模式&#xff0c;并且禁止 FIQ 和 IRQ&#xff1b; 2、关闭看门狗、中断、MMU、Cache&#xff1b; 3、初始化部分寄存器和外设&#xff08;时钟、串口、Flash、内存&#xff09;&…...

xRDP – 在 Ubuntu 18.04、20.04、22.04、22.10、23.04(脚本版本 1.4.7)上轻松安装 xRDP

最新脚本Repository | c-nergy.be 概述 到目前为止&#xff0c;您应该知道 xrdp-installer 脚本旨在简化 xRDP 在 Ubuntu 操作系统上的安装和配置后操作。xRDP 是一款在 Linux 上启用远程桌面服务的软件。这意味着 Windows 用户可以使用他们的远程桌面客户端 &#xff08;mst…...