c++面试题总结1

Posted 兔老大RabbitMQ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++面试题总结1相关的知识,希望对你有一定的参考价值。

内存结构

堆:由程序员手动分配和释放,完全不同于数据结构中的堆,分配方式类似链表。由malloc(c语言)或new(c++)来分配,free(c语言)和delete(c++)释放。若程序员不释放,程序结束后由系统释放。堆由低向高生长。空间大 灵活性大 速度慢。

栈:由编译器自动分配和释放的,存放函数的参数值,局部变量的值。操作方式类似数据结构中的栈。由高向低生长。空间小,灵活性差

全局(静态)存储区:存放全局变量和静态变量。包括data段(全局初始化区)与BSS段(全局未初始化区)其中 初始化的全局变量和静态变量存放在data段,未初始化的全局变量和未初始化的静态变量存放在BSS段。程序结束后由系统释放。其中BSS段的特点是,在程序执行之前BSS段会自动清0.随意,未初始化的全局变量与静态变量在程序执行之前已经成0了。

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

程序代码区:存放函数体的二进制代码。

常量在C++里的定义就是一个top-level const加上对象类型,常量定义必须初始化。对于局部对象,常量存放在栈区,对于全局对象,常量存放在全局/静态存储区。对于字面值常量,常量存放在常量存储区。

Public和private的区别

public和private是类里的关键字,用于规定类内数据或者成员函数的访问权限。private类型的数据或者函数,只能在相应的类内被访问,而public类型的数据或者函数被访问的权限比较宽,还可以在其它类或者其它函数中被访问。

可以通过友元函数的方式在其他类中访问私有成员函数。

构造函数可以私有吗?

可以。

私有构造函数意味着只能在自身内部创建实例,加上static可以保证改类只有一个实例。

这种方式常用语单例模式。

什么是多态,什么是动态绑定。

多态是指同一个函数可以根据对象的不同而采用多种不同的行为方式.与之相对应是静态绑定,即在函数编译的时候决定要调用的函数。动态绑定,只有当程序运行的时候才能根据具体的对象来调用相应的函数。

多态存在的三个必要条件
一、要有继承;
二、要有重写;
三、父类引用指向子类对象。

回调函数和回调机制

⑴定义一个回调函数;

⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;

⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

被调用的函数。

Static 关键字的作用

1. 全局静态变量

在全局变量前加上关键字static,全局变量就定义成一个全局静态变量.

静态存储区,在整个程序运行期间一直存在。

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。

2.  局部静态变量

在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。

内存中的位置:静态存储区

初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化);

作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

3. 静态函数

在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突;

warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰;

4. 类的静态成员

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用

5. 类的静态函数

静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。

在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);

 说一下C++和C的区别

设计思想上:

C++是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

C++具有封装、继承和多态三种特性

C++相比C,增加多许多类型安全的功能,比如强制类型转换、

C++支持范式编程,比如模板类、函数模板等

 说一下C++中static关键字的作用

对于函数定义和代码块之外的变量声明,static修改标识符的链接属性,由默认的external变为internal,作用域和存储类型不改变,这些符号只能在声明它们的源文件中访问。

对于代码块内部的变量声明,static修改标识符的存储类型,由自动变量改为静态变量,作用域和链接属性不变。这种变量在程序执行之前就创建,在程序执行的整个周期都存在。

对于被static修饰的普通函数,其只能在定义它的源文件中使用,不能在其他源文件中被引用

对于被static修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象,所有对象共享一个静态成员。静态成员通过<类名>::<静态成员>来使用。

 说一说c++中四种cast转换

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

    static_cast<double>(a) / static_cast<double>(b);

1、const_cast

用于将const变量转为非const

2、static_cast

  (1)用于类层次结构中基类和派生类之间指针或引用的转换
      进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
      进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
    (2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
    (3)把空指针转换成目标类型的空指针
    (4)把任何类型的表达式转换为void类型

3、dynamic_cast

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

向上转换:指的是子类向基类的转换

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

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

4、reinterpret_cast

几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

5、为什么不使用C的强制转换?

C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

请说一下C/C++ 中指针和引用的区别?

1.指针有自己的一块空间,而引用只是一个别名;

2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;

4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;

5.可以有const指针,但是没有const引用;

6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;

7.指针可以有多级指针(**p),而引用至于一级;

8.指针和引用使用++运算符的意义不一样;

9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

1、引用:

C++是C语言的继承,它可进行过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。引用就是C++对C语言的重要扩充。引用就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名;引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。

2、指针:

指针利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。

请你说一下你理解的c++中的smart pointer四个智能指针shared_ptr,unique_ptr,weak_ptr,auto_ptr

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

1auto_ptrc++98的方案,cpp11已经抛弃)

采用所有权模式。

auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));

auto_ptr<string> p2;

p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

2unique_ptr(替换auto_ptr

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如new创建对象后因为发生异常而忘记调用delete”)特别有用。

采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string ("auto"));           //#4

unique_ptr<string> p4                           //#5

p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptrauto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world"));

unique_ptr<string> pu2;

pu2 = pu1;                                          // #1 not allowed

unique_ptr<string> pu3;

pu3 = unique_ptr<string>(new string ("You"));           // #2 allowed

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr

注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr<string> ps1, ps2;

ps1 = demo("hello");

ps2 = move(ps1);

ps1 = demo("alexia");

cout << *ps2 << *ps1 << endl;

3shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的. shared_ptr<int> sp(new int(1)); sp sp.get()是等价的

4weak_ptr

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,(避免成环)如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr

介绍一下智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

智能指针内存泄漏情况

  1. auto_ptr 的拥有权被另一个指针剥夺,再次访问原指针会导致崩溃(严格意义上不算内存泄漏)
  2. shared_ptr成环,形成死锁,内存永远无法释放

虚函数表

一个类只有一个虚函数表存放在只读数据段,。每个类的实例化对象有一个虚函数表指针指向虚函数表。

只要父类有虚函数表,不论子类有没有虚函数,都有虚函数表。基类的虚函数表和子类的虚函数表不是同一个表

单继承中父类一个虚函数表,子类一个虚函数表,如子类重写父类虚函数,子类虚函数表相应内容会被覆盖,指向子类函数。

多次单继承中 子类在父类的基础上进行修改。

多基继承:有多少个基类类就有多少个虚函数表

1.子类虚函数会覆盖每一个父类的每一个同名虚函数。

2.父类中没有的虚函数而子类有,填入第一个虚函数表中,且父类指针是不能调用。

3.父类中有的虚函数而子类没有,则不覆盖。仅子类和该父类指针能调用。

4.如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。

Explicit的作用是

C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。

Const关键字

int i = 0;

const int* p1 = &i;    指向的值不能改变,但是指针可以改变

int const* p2 = &i;       指向的值不能改变,但是指针可以改变

int* const p3 = &i;     指针不能改变  ,值可以改变

const int* const p4 = &i;  都不能变

 const 常量作为参数传入时,该常量一定需要是引用类型   

void setName(const string& name); 引用传递才能真正起到 const 的作用

void setName(const string name);  值传递 传进来是副本 不会造成更改

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。

任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误。

delete 和 delete []的真正区别

  1. delete 对应 new delete[]对应new[]
  2. 对于简单类型包括简单类型数组,delete 与delete[]没有区别。对于自定义类型数组,delete 只会删除一个元素,delete 则会删除所有元素。

野指针是什么

野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针

请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数 

将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。如果不设置基类指针只能访问非继承成员。

C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

请你来说一下C++中析构函数的作用

析构函数与构造函数对应,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统会自动执行析构函数。

析构函数名也应与类名相同,只是在函数名前面加一个位取反符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。

如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。

类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

请你来说一下静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销

以下四行代码的区别是什么? const char * arr = "123"; char * brr = "123"; const char crr[] = "123"; char drr[] = "123";

const char * arr = "123";

//字符串123保存在常量区,const以上是关于c++面试题总结1的主要内容,如果未能解决你的问题,请参考以下文章

c++面试题总结1

c++面试题总结

C++软件开发面试题总结

c++基础面试题总结

前端面试题总结——综合问题

面试题系列|前端面试题前端高频面试题总结(2021年最新版)