C++常见面试问题汇总1——基本语法

参考:

https://www.jianshu.com/p/9aae0e5dc21a 

https://www.cnblogs.com/hit-ycy/p/11208052.html

  • 指针和引用的区别

  1. 指针有自己的一块空间,而引用只是一个别名。
  2. 不存在空引用。引用必须连接到一块合法的内存。
  3. 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  4. 引用必须在创建时被初始化。指针可以在任何时间被初始化。
  • 堆和栈的区别

  1. C++ 程序中的内存分为两个部分:
  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
  • new和delete是如何实现的,new 与 malloc的异同处

new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数

  • C和C++的区别

设计思想上:

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

语法上:

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

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

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

  • C++、Java的联系与区别,包括语言特性、垃圾回收、应用场景等(java的垃圾回收机制)

语言特性的话:c++ 有指针多重继承强制类型转换

Java 有垃圾回收机制能够自动释放掉申请的内存,而C++是以能够操作内存著称,但是很容易带来一些错误

Java是完全面向对象的语言,所有函数和变量部必须是类的一部分。除了基本数据类型之外,其余的都作为类对象,包括数组。对象将数据和方法结合起来,把它们封装在类中,这样每个对象都可实现自己的特点和行为。而c++允许将函数和变量定义为全局的。此外,Java中取消了c/c++中的结构和联合,消除了不必要的麻烦。 

应用场景:c++用于底层和中间件,java用于高层

  • Struct和class的区别

在C++中,可以用struct和class定义类,都可以继承。

区别在于:struct的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。

另外,class还可以定义模板类形参,比如template <class T, int i>。

  • 内联函数(inline)和宏定义(#define)的区别

(1)内联函数在编译时展开,宏在预编译时展开;

(2)内联函数直接嵌入到目标代码中,宏是简单的做文本替换;

(3)内联函数有类型检测、语法判断等功能,而宏没有;

(4)inline函数是函数,宏不是;

(5)宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义;

  • define 和const的区别(编译阶段、安全性、内存占用等)

区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。 
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。

const优点
(1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2)有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
(3)const可节省空间,避免不必要的内存分配,提高效率

  • 在C++中const和static的用法(定义,用途)
  • const和static在类中使用的注意事项(定义、初始化和使用)

static成员不属于任何对象,而是被他们共享。

静态数据成员应该在类的外部定义。

因为没有this指针,static成员函数不能用const修饰,也不能访问非静态成员。

  • C++中的const类成员函数(用法和意义),以及和非const成员函数的区别

非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用);唯一的例外是对于 mutable修饰的成员。加了const的成员函数可以被非const对象和const对象调用,但不加const的成员函数只能被非const对象调用

  • C++的顶层const和底层const

顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。

Int *const p1 = &i;  不能改变P1的值,这是一个顶层const

Const int *p2 = &i;  允许改变P2的值,这是一个底层const

用实参初始化形参时会忽略到顶层const。换句话说,形参的顶层const被忽略掉了

  • C++中的重载和重写的区别

  • 重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中
    重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写

  • final和override关键字

final限定某个类不能被继承或某个虚函数不能被重写

override关键字保证了派生类中声明重写的函数与基类虚函数有相同的签名,可避免一些拼写错误,如加了此关键字但基类中并不存在相同的函数就会报错,也可以防止把本来想重写的虚函数声明成了重载。同时在阅读代码时如果看到函数声明后加了此关键字就能立马知道此函数是重写了基类虚函数。保证重写虚函数的正确性的同时也提高了代码可读性。

  • 拷贝初始化和直接初始化,初始化和赋值的区别

当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象

  • extern "C"的用法

C++调用C函数需要extern C,因为C语言没有函数重载

  • 模板函数和模板类的特例化
  • C++内存管理,内存池技术(热门问题),与csapp中几种内存分配方式对比学习加深理解

c++的内存管理延续c语言的内存管理,但是也增加了其他的,例如智能指针,除了常见的堆栈的内存管理之外,c++支持智能指针,智能指针的对象进行赋值拷贝等操作的时候,每个智能指针都有一个关联的计数器,该计数器记录共享该对象的指针个数,当最后一个指针被销毁的时候,计数器为0,会自动调用析构函数来销毁函数。

  • 介绍面向对象的三大特性,并且举例说明每一个

继承,多态,封装。

继承:本质是代码复制。

多态:多态和虚函数

封装:将数据和和对操作封装到放在一起。

  • C++多态的实现

多态通过覆盖和重载来完成。

  • C++虚函数相关(虚函数表,虚函数指针),虚函数的实现原理(包括单一继承,多重继承等)(拓展问题:为什么基类指针指向派生类对象时可以调用派生类成员函数,基类的虚函数存放在内存的什么区,虚函数表指针vptr的初始化时间)

虚函数分为两种,纯虚函数虚函数,纯虚函数适用于抽象基类,不需要定义,类似一种接口,是多态的典型处理方式。

一个类如果定义了虚函数,那么编译器会自动为它加上一个虚函数表,并提供一个指向虚函数表的指针,子类通过继承,可以覆盖父类的虚函数,当用户调用虚函数的时候,会调用指针,去虚函数表中找匹配的虚函数,如果当前对象有覆盖的虚函数,则去执行覆盖的虚函数,否则执行父类的虚函数。

  • C++中类的数据成员和成员函数内存分布情况《深度探索 C++对象模型》

  1. 空类 一个字节填充
  2. 数据成员
  • this指针

每个对象都有一个特殊的指针 this,它指向对象本身。

  • 析构函数一般写成虚函数的原因

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

delete父类指针时,可以调用对象真正的析构函数。否则可能会出错,如子类中有指针类型,并且申请了内存,这时就会造成内存泄漏。

  • 构造函数、拷贝构造函数和赋值操作符的区别

构造函数:对象不存在,没用别的对象初始化

拷贝构造函数:对象不存在,用别的对象初始化

赋值运算符:对象存在,用别的对象给它赋值

  • 构造函数声明为explicit
  • 构造函数为什么一般不定义为虚函数虚函数表

  • 虚函数表在构造函数调用后才建立,因而构造函数不可能为虚函数

  • 构造函数的几种关键字(default delete 0)

= default:将拷贝控制成员定义为=default显式要求编译器生成合成的版本

= delete:将拷贝构造函数和拷贝赋值运算符定义删除的函数,阻止拷贝(析构函数不能是删除的函数 C++Primer P450)

= 0:将虚函数定义为纯虚函数(纯虚函数无需定义,= 0只能出现在类内部虚函数的声明语句处;当然,也可以为纯虚函数提供定义,不过函数体必须定义在类的外部)

  • 构造函数或者析构函数中调用虚函数会怎样

1. 从语法上讲,调用完全没有问题。
2. 但是从效果上看,往往不能达到需要的目的。
Effective 的解释是:
派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。
同样,进入基类析构函数时,对象也是基类类型。

  • 纯虚函数

纯虚函数的格式   virtual  f()=0;

代表我不实现,留给子类去实现,带有纯虚函数的类也叫抽象类,不能够实例对象出来,子类必须实现,主要解决的是多态问题,面向对象的思想

动物基类不能实现,老虎,狮子等派生类可以给出对象,所以嘛出现了纯虚函数抽象类这个东西

  • 静态类型和动态类型,静态绑定和动态绑定的介绍

1、对象的静态类型(static type):就是它在程序中被声明时所采用的类型(或理解为类型指针或引用的字面类型),在编译期确定;

  2、对象的动态类型(dynamic type):是指“目前所指对象的类型”(或理解为类型指针或引用的实际类型),在运行期确定;

 这两个概念一般发生在基类和派生类之间。

 

 

  3、静态绑定(statically bound):又名前期绑定(eraly binding),绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;

  4、动态绑定(dynamically bound):又名后期绑定(late binding),绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;

  比如常见的,virtual函数是动态绑定,non-virtual函数是静态绑定,缺省参数值也是静态绑定。当缺省参数和虚函数一起出现的时候情况有点复杂,极易出错。我们知道,虚函数是动态绑定的,但是为了执行效率,缺省参数是静态绑定的  

https://blog.csdn.net/chgaowei/article/details/6427731

  • 引用是否能实现动态绑定,为什么引用可以实现

可以实现,因为动态绑定是发生在程序运行阶段的,c++中动态绑定是通过对基类的引用或者指针调用虚函数时发生。

引用和指针的静态类型和动态类型可以不一样。

 

静态类型:变量声明时的类型或表达式生成的类型。编译时已经知道。

动态类型:变量或表达式表示的内存的对象的类型。

  • 深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)

在有指针成员的情况下,浅拷贝只是将指针指向已存在的内存。即两个对象的指针成员指向的是同一内存区域。

深拷贝的做法是申请一个内存复制一份,并将新对象指针指向备份区。

安全性:浅拷贝如果修改了指针指向的内容,将对两个对象都有影响。

  • 对象复用的了解,零拷贝的了解

1.对象池:对象池通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用。

适用性:类的实例可重用。类的实例化过程开销较大。类的实例化的频率较高。

 

2.对象复用指得是设计模式,对象可以采用不同的设计模式达到复用的目的,最常见的就是继承和组合模式了

零拷贝:零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。

  • 介绍C++所有的构造函数

  1. 默认构造函数,默认无参
  2. 普通构造函数,默认有参
  3. 拷贝(复制)构造函数:复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中
  • 什么情况下会调用拷贝构造函数(三种情况)

(1)用类的一个对象去初始化另一个对象时

(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用

(3)当函数的返回值是类的对象或引用时

  • 结构体内存对齐方式和为什么要进行内存对齐?

保证计算机可以一次读出

结构体不像数组,结构体中可以存放不同类型的数据,它的大小也不是简单的各个数据成员大小之和,限于读取内存的要求,而是每个成员在内存中的存储都要按照一定偏移量来存储,根据类型的不同,每个成员都要按照一定的对齐数进行对齐存储,最后整个结构体的大小也要按照一定的对齐数进行对齐。

同样的结构体,里面的数据排放顺序不一样,最后占用的内存也不一样大

  • 内存泄露的定义,如何检测与避免?

内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete。为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。

解决内存泄漏最有效的办法就是使用智能指针

  • 手写智能指针的实现(shared_ptr和weak_ptr实现的区别)

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

  • 智能指针的循环引用
  • 遇到coredump要怎么调试

core dump又叫核心转储。当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump。

  • 内存检查工具的了解

linux可以使用开源的Valgrind工具包,包含多个工具:Memcheck常用语检测malloc和new这类的问题,callgrind用来检查函数调用,cachegrind缓存使用,helgrind多线程程序中的竞争。除了valgrind还可以用mtrace这些工具

  • 模板的用法与适用场景

模板是C++泛型编程的基础。

使用:template  //关键字+<模板参数列表>

应用场景:除了参数类型不一样外,其他的内容全部一样(函数体),用模板,而不是每一个类型都写一个函数。

特化:模板的一个独立的定义,其中一个或多个参数被指定为特定的类型。通用模板不能适应所有情况。(特殊情况特殊处理)

代码可重用,泛型编程,在不知道参数类型下,函数模板和类模板

  • 成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?

使用初始化列表可以直接调用成员的构造函数,不用去赋值产生临时变量,所以更快。如果不用,可能会去调用成员的构造函数和赋值构造函数(多出来的)。

  • 用过C++ 11吗,知道C++ 11哪些新特性?

Lambda表达式, 智能指针 移动,auto,范围for,decltype,array,forward_list,tuple(元组),正则表达式库,随机数库,bitset运算

c++11提供的<random>实现了随机数库,它通过随机数引擎(random_number_engines)产生随机数序列,随机数分布类(random-number distribution)使用随机数引擎生成服从特定概率分布的随机数。

https://www.cnblogs.com/byhj/p/4149467.html

tuple是一个固定大小的不同类型值的集合,是泛化的std::pair

https://blog.csdn.net/fjb2080/article/details/15809097

tuple<const char*, int>tp = make_tuple(sendPack,nSendSize); //构造一个tuple

这个tuple等价于一个结构体

struct A

{

char* p;

int len;

};

用tuple<const char*, int>tp就可以不用创建这个结构体了,而作用是一样的,是不是更简洁直观了。还有一种方法也可以创建元组,用std::tie,它会创建一个元组的左值引用。

auto tp = return std::tie(1, "aa", 2);

//tp的类型实际是:std::tuple<int&,string&, int&>

再看看如何获取它的值:

const char* data = tp.get<0>(); //获取第一个值

int len = tp.get<1>(); //获取第二个值

引入了emplace_front、emplace、emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应于push_front、insert、push_back。Emplace成员使用这些参数在容器管理的内存空间中直接构造函数。例如,在c的末尾构造一个Sales_data

   c. emplace_back( “666-556695” , 25 , 52.36);///直接使用Sales_data的三个参数就可以了

   c.push_back(“666-556695” , 25 , 52.36); //错误,.push_back不接受3个参数

c.push_back( Sales_data(“666-556695” , 25 , 52.36) ); //正确,创建一个临时对象

  • C++的调用惯例(简单一点C++函数调用的压栈过程)

函数调用大家都不陌生,调用者向被调用者传递一些参数,然后执行被调用者的代码,最后被调用者向调用者返回结果。

对于程序,编译器会对其分配一段内存,在逻辑上可以分为代码段,数据段,堆,栈

代码段:保存程序文本,指令指针EIP就是指向代码段,可读可执行不可写

数据段:保存初始化的全局变量和静态变量,可读可写不可执行

BSS(静态内存分配):未初始化的全局变量和静态变量

堆(Heap):动态分配内存,向地址增大的方向增长,可读可写可执行

栈(Stack):存放局部变量,函数参数,当前状态,函数调用信息等,向地址减小的方向增长,非常非常重要,可读可写可执行

程序开始,从main开始,首先将参数压入栈,然后压入函数返回地址,进行函数调用,通过跳转指定进入函数,将函数内部的变量去堆栈上开辟空间,执行函数功能,执行完成,取回函数返回地址,进行下一个函数。

  • C++的四种强制转换

  1. static_cast  可以实现C++中内置基本数据类型之间的相互转换。
  2. dynamic_cast
  3. const_cast 用于将const变量转为非const
  4.  reinterpret_cast
  • C++中将临时变量作为返回值的时候的处理过程(栈上的内存分配、拷贝过程)
  • C++的异常处理
  • volatile关键字

volatile关键字是一种限定符用来声明一个对象在程序中可以被语句外的东西修改,比如操作系统、硬件或并发执行线程。

遇到该关键字,编译器不再对该变量的代码进行优化,不再从寄存器中读取变量的值,而是直接从它所在的内存中读取值,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

一般说来,volatile用在如下的几个地方:

(1)、中断服务程序中修改的供其它程序检测的变量需要加volatile;

(2)、多任务环境下各任务间共享的标志应该加volatile;

(3)、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同

  • 优化程序的几种方法

1、 选择合适的算法和数据结构

2、 使用尽量小的数据类型

3、 减少运算的强度

4、 结构体成员的布局

5、 循环优化

6、 提高CPU的并行性(使用并行代码,把长的代码分解成短的)

7、 循环不变计算(不使用的循环变量,放到外面)

8、 函数优化(内联函数)

  • public,protected和private访问权限和继承
  • decltype()和auto

decltype类型指示符

Const int c = 0,&b = c;

Decltype(c) x =3;//x是const int 类型

Decltype(b) y = x;//y是const int&类型;所以定义y时必须初始化

auto

使用auto也能在一条语句中声明多个变量,因为一条语句只能有一个基本的数据类型。

Auto定义的变量必须有初值,这样才能推导出类型是什么

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