C++---常见秋招、春招问题汇总(持续更新)

重载、重写(覆盖)、重定义(隐藏)

重载 同一作用于下,函数名相同,参数不同两个函数构成重载,参数不同的意思包括参数类型、参数个数、参数顺序不同;
重定义(隐藏):不同作用域下(父类子类),函数名相同构成重定义;当子类调用该函数的时候只会调用自己的,要调用父类中的函数需要作用域限定符
重写 不同作用域下,函数名、参数、返回值都相同;并且两个函数必须是虚函数;重写的作用相当于类中的重载,主要实现不同对象调用同一种方式,实现不同的结果;
补充:
1、为什么C++可以实现重载而C语言不行
答:在C语言链接调用函数的过程中,函数名称就是函数名本身,不会做任何改变,所以如果函数重名就会不知道调用哪个函数;
而C++中链接函数的时候会对函数名进行修改,在函数名上加上参数的首字母,所以只要参数不同就会导致链接时候的函数名不同,所以不会出现函数调用混乱。

2、重定义就是继承么,是代码复用的一种方式,会对父类进行隐藏,如果想调用必须加作用域限定符。

指针和引用的区别

第一:指针是重新开辟一块空间用来存放某个对象的地址,而引用是给已经存在的变量起一个别名,它不会重开空间;
第二:引用必须初始化,而指针不用,意思就是没有空的引用,但是有空的指针;
第三:由于引用不需要重新开辟空间,所以相比于指针来说更加节省空间
其实引用的本质还是指针,他会在编译期间被处理(占内存

缺省参数

缺省参数相当于参数的备份,如果在传参的时候没有对其进行赋值,就会默认使用缺省参数,如果赋值了缺省参数相当于不存在;
缺省参数又分为全缺省和半缺省;
全缺省就是所有参数全部变成缺省参数,而半缺省则是一部分参数为缺省参数,这部分参数必须从右至左连续,中间不能断

函数编译链接的过程

主要分为预处理、编译、汇编、链接四个部分; 预处理主要进行:展开头文件、宏替换、去注释 编译主要进行:语法错误的检查、同时把代码转化成汇编代码; 汇编:主要把汇编代码转化成二进制代码; 链接:把多个目标文件链接起来形成一个可执行程序

内联函数

内联函数就是以inline修饰的函数,它不是强制性的,是对系统的一种建议,在编译的时候直接原地展开,没有函数压栈的开销;提高了性能,这是一种以空间换时间的方法,比较适合较短的函数;
inline函数不建议声明和定义分离,因为如果分开,在链接的时候会按照声明的函数名在源文件中寻找函数的实现,但是inline函数在编译的时候已经展开,所以用声明在源文件是找不到对应函数的;解决办法就是声明和定义写在一起。
补充:
1、inline函数不建议声明和定义分离
因为如果分开,在链接的时候头文件中会按照声明的格式在源文件中寻找,但是inline函数在编译的时候已经展开,所以用声明在源文件是找不到对应函数的;
所以办法就是声明和定义写在一起
2、解决频繁调用的方法
1、宏函数
缺点:不方便调试、代码的可读性和可维护性比较差
因此针对宏函数的缺点,可以用内联函数来代替宏;
3、宏替换和内联函数的区别
宏替换在预处理阶段完成;
内联函数在编译阶段完成;

struct和class的区别 && 空类为啥占一个字节

struct既可以做结构体的关键字又可以做类的关键字,struct做类的时候成员默认为public
class做类的时候成员默认为private


因为根据定义,空类也可以实例化,而类的实例化就是给对象分配一块空间,为了达到这一个目的,所以我们对于空的类也要开辟一字节的空间,否则空类无法进行实例化

为什么要内存对齐

原因有两个:
1、平台移植性原因:为了使代码在每个平台都具有可移植性所以规定的内存对齐;
2、提高性能:因为处理器访问没有对齐的内存需要访问两次,而访问对齐的内存只需要一次,提高了效率;

补充:为什么访问没有对齐的内存要访问两次?
例如:对于32位的CPU来说,一次可以访问4字节的内存,如果我们提前以4字节为一个空间做内存对齐,每个空间只会存在一个类型的变量,那么每次访问4字节就可以获取到内容,但如果步对齐,就有可能4个字节里面存了两个变量的数据,需要两次访问再拼接;
计算结构体大小(内存对齐)
第一个成员在与结构体变量偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
对齐数:
vs:8 Linux:4
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所
有最大对齐数(含嵌套结构体的对齐数)的整数倍。

//练习3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//d方0,占8.c方9,因为i=4,要是4倍数所以i放12,c后面空3个.
//因为最大对齐数是double(8),所以要8的倍数=16.

面向对象和面向过程 && 面向对象的特性

面向对象编程更加注重的是每个对象的功能,而面向过程变成更加注重对象实现功能的过程;就好比外卖体系;
面向对象在乎的是用户、骑手、餐厅之间的协作关系;
面向过程在乎的是餐厅怎么做饭,骑手怎么取餐等等


面向对象的三大特性:封装、继承、多态
封装:隐藏对象的实现细节,仅对外部提供调用的接口
继承:用于类中的代码复用,子类继承父类后,父类的成员变量和成员函数就会成为子类的一部分;
多态:用于实现不同子类调用同一函数,产生不同的行为;

this指针&&存的位置&&可以为空吗

定义:this指针是非静态成员函数中的一种隐式参数,它的作用就是保证每个对象拥有自己独有的成员,但共享处理这些成员的代码;


this指针一般村于栈中(堆人工开辟、静态区是全局变量、代码段存代码)


this指针可以为空;因为this指针说白了也是指针,单纯的空指针没有问题,但如果对空指针操作就不行

为什么拷贝函数必须引用传参

因为如果不引用传参就会出现无线递归调用拷贝构造的情况;
因为调用Date d2(d1)等价于Date d2 = d1;
Date d2(d1);先调用拷贝构造,然后d1传过去,调用Date(const Date d),传参相当于Date d = d1,这又是一个拷贝函数,所以会在这里无线的调用拷贝函数

什么是浅拷贝

浅拷贝是拷贝构造函数设计缺陷导致的一种报错;
它是由于自定义类型没有自己定义拷贝构造函数,而调用了系统自带的拷贝构造函数,默认的拷贝构造只是单纯的赋值,对于vector这种涉及空间的类型在拷贝的时候只是也只是单纯的把空间地址赋值过去,造成了两个对象指向同一块空间,在析构的的时候导致一块空间析构了两次。这就是浅拷贝
解决:
1、重新开辟空间,然后把值传过去

const对象和函数之间的调用

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
  4. 非const成员函数内可以调用其它的const成员函数吗?

对象调用函数,相当于把自己传给函数;所以const的传给非const不行;所以1不行
函数调用函数,A调用B则是把B拿来传给A,所以4不行
补充:

const Date* p1——指向对象
Date const* p2——指向对象
Date* const p3——指向指针
只要在*的左边就是指向对象,只要在右边就是指针;

初始化列表

就是const 和&还有 自定义类型类型初始化采用的一种方式;
(在构造函数的括号外,冒号后面跟着变量进行初始化,参数之间用逗号分开)
为什么?
因为正常类型定义和初始化可以分开,而这三个类型必须在定义的时候就初始化,所以用到了初始化列表;

空间内存分布&&栈堆区别

内存空间由上到下依次是栈、堆、数据段、代码段;
栈是由编译器自动分配和释放的空间,用来存放参数、变量等;堆一般是由程序员来分配和释放;数据段一般是用来存放全局变量和静态变量;代码段则一般是用来存放可执行代码的;


第一、他们的生长方向不同,栈向下生长堆向上生长;
第二、管理方式也不同,堆是手动开辟的,栈是系统分配与释放;
第三、同时就大小而言堆的空间更大(32位4G,堆一般几M);
第四、相比较而言堆更容易产生空间碎片,因为频繁的new和delete会造成内存空间的不连续,从而导致大量碎片,而栈空间不会存在这个问题,因为它是先进先出的一个队列,所以是连续的。

malloc、calloc、realloc

malloc只是单纯的开辟空间并不会初始化 calloc会把自己分配的空间都初始化为0 realloc则是给原来地址重新开辟空间
malloc(numElements *sizeOfElement)
calloc(numElements ,sizeOfElement)
void *realloc(void*ptr, size_t size);

new和delete相比于C语言malloc和free有什么区别?

对于内置类型没有区别,而对于自定义类型效果不同 对于自定义类型的malloc只是申请空间,而new申请空间+调用构造函数初始化 对于自定义类型的free只是释放空间,而delete析构函数+释放空间

模板

在这里插入图片描述模板的原理
他的原理其实就是替换,我们首先写好模板,在调用的时候会根据类型来自动转换成对应类型的模板(预处理),所以最后运行的还是转换后的代码
多模板参数
就是一个对象拥有多个类型的属性;比如人有名字、年龄

template<class T1,class T2>
class show
{
public:
    void show1(T1 &a){cout<<"show1:"<<a<<endl;}
    void show2(T2 &a){cout<<"show2:"<<a<<endl;}
};
int main()
{
    show<int,string> a;
    int temp1=5;
    string temp2="Hello,C++!";
    a.show1(temp1);
    a.show2(temp2);
    return 0;

模板的特化
模板的特化是为了针对特定类型进行特定处理的一种模板处理方式,它需要一个基础模板,然后在此基础上明确模板里的类型,当有同类型调用的时候优先走特化模板。
而模板的特化也分为全特化和偏特化;
全特化就是template中的所有参数都确定其类型;
偏特化是明确一部分;

模板为什么不支持分离编译
因为正常函数的声明和定义函数名是相同的,在最后的链接过程中可以通过声明找到函数的实现;
但模板声明的是模板类型,而在运行阶段,编译器会把模板转化成对应类型的函数,所以最后通过模板类型是找不到对应的函数实现,所以不支持分离编译;

vector&&list

vector和list的区别
从底层上来说,vector的底层是一个可动态增长的数组,是一块连续的空间;而list的底层是一个带头的双向循环链表,逻辑上连续空间上不连续。
因此也导致他们在很多方面有所区别:
vector的优点就是支持随机访问,所以很好的支持排序算法,缺点是头部插入和删除元素需要挪动大量数据效率比较低下;同时扩容代价大。
list的优点是在任意位置插入数据和删除数据的效率高,不需要挪动大量数据;但缺点是不支持随机访问。

其实除了这些比较基本的区别以外,我之前看他们两个的底层源码发现他们两个的迭代器虽然使用方式差不多,但底层实现有很大区别;由于vector的底层是一份连续的空间,所以指针可以作为天然的迭代器;而list的底层是逻辑连续但空间不连续,不能直接用list的指针作为迭代器,否则迭代器++,–就会到未知的空间;因此对于list的迭代器底层进行了一个封装,人为实现了迭代器的移动、引用等操作;
因此

  1. 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
  2. 如果你需要大量的插入和删除,而不关心随即存取,则应使用list

什么是迭代器
STL容器的下标,通过对迭代器的操作来实现容器内元素的增删改查;

迭代器失效
vector中迭代器失效有两种情况,一个是扩容后的迭代器失效,一个是删除后的迭代器失效;
扩容后的迭代器失效是——已经对迭代器进行了赋值,但赋值后又进行了扩容,由于vector的扩容是重新开辟空间再进行值拷贝,此时vector已经在新的空间,但迭代器还指向旧空间,因此迭代器失效了。
解决办法就是:在迭代器赋值后不要插入元素,防止出现扩容导致迭代器失效;
删除后的迭代器失效是——删除了一个元素,后面的元素移动到前面来,但迭代器同时又会向后移动一个位置,相当于跳过了一个元素,因此迭代器失效了。
解决办法就是:erase删除元素的时候用pos得到它的返回值,因为erase的返回的就是删除元素的下一个元素的位置;

vector的扩容机制
vector并不是有多少个元素就开多少个空间,他会预留一部分空间;因为vector扩容的时候还要进行元素的拷贝,代价比较大,预留的好处就是插入元素的时候不需要每次都扩容。这是一种一空间换事件的处理方法;
一般1.5倍或者两倍看个人选择。1.5倍空间利用率上更高,2倍数据拷贝的次数少;
vector的内存释放机制
vector 离开它的生存期的时候,它的析构函数会把 vector 中的元素销毁,并释放它们所占用的空间,所以用 vector 一般不用显式释放 —— 不过,如果你 vector 中存放的是指针,那么当 vector 销毁时,那些指针指向的对象不会被销毁,那些内存不会被释放。(clear只是清数据了未清内存,因为vector的capacity容量未变化,系统维护一个的默认值)

deque(stack和queue底层&&vector和list融合)

在这里插入图片描述
deque是由一段段连续的小空间组成,但它插入数据首先从deque的中间插入,如果该段的空间满了就会像两边扩容,而node指针就是标记数据在deque中的前后位置;每一段小空间里面又有指针控制内部的数据;
其实都可以实现,默认底层是deque
deque是一个完善vector和list缺点的容器,它是由一段段连续小空间拼接起来的一个双端队列,在两端都可以进行插入删除。
与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,在扩容的时候也不需要像vector那样拷贝数据。
与list比较,list的插入删除容易产生空间碎片,而deque的空间是连续的,不容易产生空间碎片;
缺点:虽然它支持随机访问的同时又支持头尾的插入删除;但是通过一个复杂的转换;效率比较低,因此不实用。
但作为stack和queue的底层,只是在数据两端进行操作,这个时候deque的效率还是可以的,

优先级队列

优先级队列它具有队列的特性,只是在内部添加一个排序规则;但是优先级队列和队列的底层区别非常大,队列的底层默认是deque,而优先级队列的底层是一个堆,默认是大顶堆。当有数据插入的时候,他就会根据大顶堆的规则对数据进行一个堆的调整。(插入元素向上调整、删除元素向下调整)

什么是继承&&继承方式&&继承与友元&&多继承菱形继承

什么是继承
继承是代码复用的一种方式,不同作用域下子类通过public、protected、private操作符继承父类的成员变量和成员函数、继承后父类的成员变量和成员函数都会变成子类的一部分;

继承方式:public、protected、private
在这里插入图片描述这三种操作符权限由高到低分别为pubilc、protect、private;子类的权限的大小是在关键字权限和父类权限中选小的。比如父类权限是public,但继承方式是private那么子类就是private;如果父类是private、继承方式为public那么还选小的,子类还是private;
除此以外,protected和private权限在在当前类中没有区别,但是在继承类中,private类中的成员函数不可见且无法访问(不过虽然不可见,但是在继承的时候还是会继承下来,sizeof的时候还是占空间的。)

继承与友元
子类是不能继承父类的友元关系,也就是说,父类的友元函数只能访问父类成员,不能访问子类中的成员

多继承与菱形继承
多继承就是一个子类可以同时继承两个父类,这也就导致菱形继承的问题;
菱形继承就是两个子类同时继承父类,再由一个孙类同时继承这两个子类,因为两个子类继承父类后会有两份相同的数据,这也就导致孙类在继承两个父类的时候 出现了代码冗余和二义性的问题

解决:
在两个子类继承父类的时候在继承操作符前面加上virtual关键字;这样子类再继承的时候就不再继承父类的内容,而是继承一个虚基类指针,子类通过指针+偏移量的方式找到父类中的成员,这样两个子类中的数据就是同一个数据,那么孙类再继承的时候就不会出现代码冗余和二义性的问题;

继承和组合

在这里插入图片描述组合
在这里插入图片描述
继承一般强调的是is-a的关系,就是子类是父类更加精细的划分,比如父类是人,子类可以是学生,子类也属于父类但它有自己更细化的特征;
组合强调的是has-a的关系,就是A是B的组合,那么A就是B的一部分;
两个相比较而言,更偏向与使用组合。因为继承是白箱复用,父类的所有成员函数子类都是可见的,因此失去了父类的封装性,而且耦合度较高;但组合是黑箱复用,类的内部双方都是不可见的,耦合度较低。而在代码的设计中强调低耦合。

多态&&多态的原理&&纯虚函数

什么是多态(什么是多态、静态/动态、原理)
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。简单来说就是不同对象去调用同一个函数产生的不同行为就叫多态;
实现多态要满足的条件有三个:第一必须存在继承关系 、第二子类对父类完成了重写、第三个父类的指针或引用指向子类

多态的原理
当父类为虚函数的时候,父类的内部会创建一个vfptr指针,而这个指针则指向这个类的虚表(表的内部记录着父类虚函数的地址);子类继承父类的时候也会继承这个虚表,如果子类没有完成重写,那么子类中的虚表中记录的地址就是父类的虚函数地址;如果子类完成重写,那么子类虚表内部就会替换成子类的虚函数地址;

(多态又分为静态多态和动态多态;
静态多态就是编译期间就已经确定了函数的地址 ——重载
动态多态其实就是刚才说的不同对象调用同一函数实现不同行为的那个多态,在编译期间还不知道调用哪个函数,是在运行的时候看虚表指针指向哪个表,才能知道调用哪个函数,因此也是地址的晚绑定,)

纯虚函数
实际写代码的时候,父类中的函数是不需要实现的,所以我们可以直接让它等于0,此时这个虚函数就变成了纯虚函数,此时类也称为抽象类。
纯虚函数的特点:1、无法实例化对象 2、子类必须重写抽象类中的纯虚函数,否则也无法实例化;
虚函数和虚表存在哪里
都存在代码段,虚函数存在代码段,虚函数表里存着虚函数的地址;

析构和构造能不能是虚函数

析构函数可以是虚函数而且它必须是虚函数,因为只有它是虚函数,才能满足多态,使得子类调用自己的析构函数;
因为在底层,为了满足子类可以调用自己的析构函数,编译器要实现析构函数的多态,它会把子类和父类的析构函数名称都改成destructor,而且一般析构函数子类都会用父类的指针指向。析构函数没有参数没有返回值所以再加上virtual就完成了重写;这样就实现了多态

构造函数不能是虚函数;因为每一个函数都有一个虚指针,所以如果构造函数是虚函数,就需要一个虚指针指向构造函数,但是虚函数指针都是在构造的时候初始化,因此产生矛盾,所以构造函数泵是虚函数;

智能指针 && 右值引用&&lamber && 完美转发

程序员在开辟空间后有可能忘了取释放,而只能指针是为防止内存泄露,采用RAII的思想将普通指针封装成一个栈对象,当栈对象的声明周期结束后,会在析构函数中释放申请的内存空间;
RAII:是一种托管的资源的思想,就是利用对象的声明周期来控制程序的资源;

1、auto_prt:最早的智能指针,它不支持拷贝构造和赋值;因为auto_prt在赋值的时候只是对指针地址进行的了简单的浅拷贝,因此在析构的时候就会导致一块空间被释放了两次,导致崩溃;
2、unique_prt:在auto_ptr的基础上做了改进,它通过std::move(移动语义)支持了只能指针的赋值操作(之前崩溃是因为赋值后一块空间被两个对象引用,而通过移动语义赋值后,它会把原指针置为空,因此析构的时候不会出现崩溃)
3、shared_ptr:是一种基于引用技术的智能指针,多个shared_ptr可以指向同一个对象并维护一个共享的引用计数器,当引用对象增加的时候计数器+1,销毁的时候-1.直到计数器为0的时候,shared_ptr才会释放资源;

智能指针会不会出现资源泄露(循环引用) (valgrind检测资源泄露情况)
在这里插入图片描述
现在最常用的智能指针就是shared_ptr,但是在使用的时候,如果两个对象相互使用shared_ptr指向对方,此时shared_ptr的计数为2,所以在释放A的时候-1以后还是1,不会释放空间。此时取释放B,同样还是计数器-1还剩1,这就是循环引用导致的资源泄露;(此时需要把其中一个引用改成weak_ptr)
注意:对象释放因为对象是栈上存储,而空间不释放是堆上的;

为了资源泄露(循环引用)问题,引出了weak_ptr指针;
不能直接用它来定义一个智能指针的对象,只能配合shared_ptr来使用,它是一种弱指针,可以将shared_ptr的对象赋值给weak_ptr,并且这样并不会改变引用计数的值,所以当一块内存同时被shared和wead指针同时引用的时候,只要weak_ptr的计数为0,就会释放该空间的内存。

右值引用

左值通常是变量 右值通常是常量,表达式或者函数返回值(临时对象)
而 左值引用和右值引用,他们都是给对象取别名,只不过对象不同;

注意:
左值引用不能引用右值, 但const左值引用可以(因为const 使左值引用具有常属性
int& e = 10;
int& f = x + y;
const int& e = 10;
const int& f = x + y;
右值引用不能引用左值,但是可以引用move后左值(move只是有类型转换的作用,和智能指针中的move不是一个东西)
int&& m = a;
int&& m = move(a);//使a变量具有常数属性

C++11又将右值分为纯右值和将亡值
纯右值:内置类型的常亮或者函数返回对象
将亡值:自定义类型的临时对象(赋值以后就要销毁了)
之所以区分左值和右值,就是因为右值引用中针对将亡值采取移动拷贝和移动赋值进行指针的交换就行,把原空间通过将亡值进行释放,减少了深拷贝的次数,提高了效率。
在这里插入图片描述
lambda表达式
lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
在这里插入图片描述
底层还是依靠仿函数来实现(定义了一个lamber表达式,实际上编译器会全局域生成一个叫lamber_uuid类,仿函数的operator()的参数和实现,就是我们写的labmber表达式的参数和实现)

int main(){
    不捕捉
    //auto add = [](int x1, int x2)->int{return  x1 + x2; };
    //add(1, 2);
    //cout << add(1, 2) << " ";
    
    捕捉(选择捕捉)
    //int a = 1, b = 2;
    //auto add2 = [a, b]()->int{return a + b; };
    //cout << add2() << " ";
    捕捉(全部捕捉)
    //int a = 1, b = 2;
    //auto add2 = [=]()->int{return a + b; };
    //cout << add2() << " ";
    int a = 1, b = 2;
    //auto swap = [](int& x1, int& x2){int tem =  x1; x1 = x2; x2 = tem; };
    swap(a, b);
    //捕捉(选择
    auto swap1 = [&a, &b]{int c = a; a = b; b =  c; };
    swap1();
    //(全捕捉
    auto swap2 = [&]{int c = a; a = b; b = c; };
    swap2();
    cout << a << b << endl;
    system("pause");
}

完美转发

一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值,并不是他原来的类型。系统之后会把他修改为左值,有可能造成不必要的拷贝(左值深拷贝,右值移动);所以必须保留右值的属性。
在这里插入图片描述

string的实现

移动拷贝和移动赋值:正常的拷贝都是开空间,然后拷贝数据;但是针对将亡值(自定义类型的临时对象,赋值以后就要销毁了)C++11就提出了移动拷贝,它其实是拷贝的一种重载方式,针对将亡值把它和要创建对象直接swap,避免了开空间拷数据的消耗,提高了拷贝的效率。

里面有两个版本,一个是给左值使用的,另外一个是给右值使用的(采用移动构造和赋值),较少了可深拷贝,提高了效率

public:
    String(const char* str = ""){
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    // s2(s1)
    String(const String& s){//这里加const是相当于左值引用接收右值,加了const既可以左也可以右;
        _str = new char[strlen(s._str) + 1];
        strcpy(_str, s._str);
    }
    // s3(右值-将亡值)
    String(String&& s)
        :_str(nullptr)
    {// 传过来的是一个将亡值,反正你都要亡了,我的目的是跟你有一样大的空间,一样的值
        swap(_str, s._str);
    }    
    // s3 = s4
    String& operator=(const String& s){        
        if (this != &s){
            char* newstr = new  char[strlen(s._str) + 1];
            strcpy(newstr, s._str);
            delete[] _str;
            _str = newstr;
        }
        return *this;
    }
    // s3 = 右值-将亡值
    String& operator=(String&& s){
        cout << "String& operator=(String&&  s)-移动赋值-效率高" << endl;
        swap(_str, s._str);
        return *this;
    }

C++的四种强制类型转化

C语言的强制类型转换是隐式的,所以不是很安全;在此基础上C++提出了四种强制类型转化,用来提醒程序员。
static_cast:对应C的隐式类型转换(类型相近的
reinterpret_cast:对应C的大部分强制类型转换(类型不相近
const_cast:去掉const属性的强制类型转换
dynamic_cast:正常父类的指针或引用是可以指向子类对象的(切片)这是向上转行,而dynamic_cast是安全的向下转型”,将父类指针转换为子类指针;(如果原来父类指针就指向父类则失败返回nullptr,如果原来父类指针指向子类则转换成功)

int i =1;
double d = 8.88;
int* p = nullptr;

i = d;//相近类型的转化(都是代表数字)
p = (int*)i;//一个数组一个指针,不相近类型的转换
d = static_cast<double>(i);//相近类型的
p = reinterpret_cast<int*>(i);//不相近的类型转换

reinterpret_cast:对应C的大部分强制类型转换(类型不相近

    class A { // ... };  
    class B { //... };  
    void f()  
    {  在不了解A、B内存布局的情况下,强行将其进行转换,很有可能出现访问越界或者切片。
      A* pa = new A;  
      B* pb = reinterpret_cast<B*>(pa);  
      // ...  
    } 

const_cast:去掉const属性的强制类型转换

class A  {  // …  };  
void Function()  
{  
    const A *pConstObj = new A;  
    A *pObj = pConstObj; //ERROR: 不能将const对象指针赋值给非const对象  
  pObj = const_cast<A*>( pConstObj); // OK  
}

dynamic_cast:

class B {  //...  };  
class D : public B {  //...  };  
void Function(D *pObjD)  
{  
     D *pObj = dynamic_cast<D*>( pObjD);  
     //...  
} 
如果pObjD指向一个D类型的对象,pObj则指向该对象,
所以对该指针执行D类型的任何操作都是安全的。
但是,如果pObjD指向的是一个B类型的对象,pObj将是一个空指针,

内存泄露 && 检测工具

C++的内存泄露一般是由于程序员开辟空间忘记释放导致的内存泄露,大致可以分为三种,分别是: 常发性、偶发性、和一次性泄露
1、常发性内存泄漏是指发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
   2. 偶发性内存泄漏是指发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。(对于特定的环境,偶发性的也许就变成了常发性的。所以 测试环境和测试方法对检测内存泄漏至关重要。)
  3. 一次性内存泄漏是指发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析 构函数中却没有释放该内存,但是因为这个类是一个单例,只会创建一次,所以内存泄漏只会发生一次。
  这里面其实最害怕的就是偶发性,因此其实一次的内存泄露可能没有关系, 但是比如服务器24小时运行造成的内存泄漏堆积则后果很严重,无论多少内存,迟早会被占完。最直接的后果就是导致运行程序卡顿严重的会导致程序运行崩溃
  解决办法
  1、多使用智能指针。它是采用RAII的思想将普通指针封装成一个栈对象,当栈对象的声明周期结束后,会在析构函数中释放申请的内存空间,防止内存泄露。
  2、内存检测工具Valgrind 、Rational Purify来定位;
  
问了再说
(第一堆内存泄露:就是new、malloc开辟空间但忘记释放,或者释放方式不对导致内存泄露;比如new了一个数组,但是在释放的时候只用了delete而没用delete方括号,这就会导致数组其他元素空间无法释放,从而导致一个内存泄露。
第二是系统资源泄露主要指程序使用系统分配的资源比如 Bitmap(位图),handle(句柄) ,SOCKET(套接字)等没有使用相应的函数释放掉,导致系统资源的浪费。)

危害:其实一次的内存泄露可能没有关系, 但是比如服务器24小时运行造成的内存泄漏堆积则后果很严重,无论多少内存,迟早会被占完。
最直接的后果就是导致运行程序卡顿:因为系统分配给每个应用的内存资源都是有限的,内存泄漏导致其他组件可用的内存变少后,用户越容易感应到卡顿。另一方面内存变少,可能使得系统额外分配给该对象一些内存,而影响整个系统的运行情况。 严重的会导致程序运行崩溃:因为一旦内存不足以为某些对象分配所需要的空间,将会导致程序崩溃,造成体验差。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2023年3月11日,美团春季招聘笔试中共包含五道编程题目。以下是对每道题目的简要说明: 1. 题目一:这道题目要求解决一个数字统计的问题。可能涉及到的知识点包括数据结构、循环和条件判断等。解决问题的思路可能是使用字典等数据结构来保存统计结果,并使用循环逐个读取输入数据并进行统计。 2. 题目二:这道题目可能是一个字符串处理的问题。需要使用字符串的方法进行操作,如提取、拼接、查找和替换等。可能的解决思路包括使用正则表达式、切片和遍历等。 3. 题目三:这道题目可能涉及到算法和数据结构的知识。可能是一道涉及到数组、链表、树等数据结构的问题。解决思路可能包括遍历、递归、搜索和排序等。 4. 题目四:这道题目可能是一个动态规划的问题。需要根据给定的条件和规则,通过动态规划的方式求解问题。解决思路包括定义状态和转移方程,使用递推或记忆化搜索进行求解。 5. 题目五:这道题目可能是一个图论或网络问题。需要根据给定的图或网络结构,解决一个相关的问题。可能涉及到广度优先搜索、深度优先搜索、最短路径等知识。解决思路可能包括使用图或网络的相关算法进行求解。 以上只是对这五道编程题目的一些可能情况进行的简要描述,具体的题目内容可能会有所不同。希望这些信息能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值