c++常见基础面试题

本文详细比较C和C++在面向对象编程、内存管理、数据类型和概念上的差异,探讨了面向对象的特性、原则,以及变量、函数、内存分配、多态等核心概念,还涵盖了全局变量、局部变量、模板、继承、多态、内存泄漏和堆栈溢出的讲解。
摘要由CSDN通过智能技术生成

文章目录

c和c++的区别

第一,c是面向过程的语言,而c++是面向对象的语言
第二,c和c++动态管理内存的方法不一样,c是使用malloc和free函数,而c++一般使用new和delete关键字
第三,c中一般是struct来封装对象,c++不仅可以使用struct,还可以使用class
第四,c++支持函数重载,而c不支持函数重载
第五,c++有引用,c没有

面向对象

面向对象与面向过程的区别

面向过程是一种以过程为中心的编程思想,以算法进行驱动;面向对象是一种以对象为中心的编程思想,以消息进行驱动。面向过程编程编程语言的组成是程序=算法+数据;面向对象编程语言的组成是程序=对象+消息

面向对象的四大特性

分别是抽象、封装、继承、多态
抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象的哪些属性和行为,并不关注这些行为的细节
封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口,给对象提供了隐藏内部特性和行为的能力
继承是从已有类得到继承信息创建新类的过程,子类继承父类的属性和方法,同时可以重写父类方法实现扩展
多态是指允许相同或不同子类型的对象对同一消息作出不同响应,比如同一个动作作用在不同对象上拥有不同的表现

面向对象和面向过程相比有什么优点

面向对象易维护、易复用、易扩展,而且由于面向对象具有封装、继承、多态等特性,可以设计出低耦合度的系统,使系统更加灵活。其中低耦合度是说事物的相关性低,也就是一个事物的改变不会使得另外的事物发生很大的改变,独立性较高

面向对象的六大原则

分别是单一职责原则、开闭原则、迪米特原则、里氏替换原则、依赖倒转原则和接口隔离原则

变量与函数

++i 与 i++ 的区别

i++通俗的解释是先赋值再自增,这里的赋值其实是从操作数栈里取的值,也就是说先将i的值压入操作数栈中,而自增是局部变量表的值+1。
++i与此相反,是先自增后赋值,就是局部变量表的值自增,然后把局部变量表的值压入操作数栈中。

函数声明和函数定义的区别

函数声明只是说明了该函数应该如何使用,函数调用时应该给它传递哪些数据,函数调用的结果有应该如何使用。函数定义除了给出函数的使用信息外,还需要给出函数如何实现预期功能,即如何从输入得到输出的完整过程

值传递、引用传递和指针传递的区别

在值传递中,形式参数有自己的存储空间,实际参数是形式参数的初值。参数传递完成后,形式参数和实际参数再无任何关联。
在引用传递中,形式参数是实际参数的一个别名,形式参数并没有自己的空间,它操作的是实际参数的空间。

在指针传递中,形式参数是一个指针变量,在函数中通过间接访问调用函数中的某个变量。

内联函数

内联函数是一种能提升程序中函数调用效率的特殊函数,用inline修饰函数,程序在编译的时候,编译器将程序中出现内联函数的调用表达式用内联函数体进行替换,而对于其它的函数,都是在运行的时候才被替代,一般把执行语句少的函数设置为内联函数

static函数与普通函数的区别

static函数与普通函数的作用域不同。static函数的作用域只是当前文件,普通函数可以在当前源文件以外的文件使用

静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝程序的局部变量存在于堆栈中,全局变量存在于静态区中,动态申请数据存在于堆中

形参与实参的区别

分配内存单元、实参确定值、类型匹配、传送单向

形参变量只有在函数调用时才被分配内存单元,调用结束就释放分配的空间,相当于函数的一个局部变量,在函数外无法使用
实参一般是具有确定值的,而且把确定值赋给形参,形参会在函数调用时将实参拷贝一份
形参与实参在类型、数量、顺序上要保持一致性,否则会出现类型不匹配错误
一般是实参传递给形参,传送方向是单向的,不会把形参传递给实参,所以在值传递时,形参发生改变,一般不影响实参

static的作用

隐藏、保持变量持久、默认初始化为0、修饰c++类成员

第一个作用是隐藏,当同时编译多个文件时,所有未加static前缀的全局变量或全局函数都具有全局可见性,例如有两个文件A和B,A中的全局变量对B是可见的,但如果A中的变量加了static,B就看不到了,利用这个特性可以在不同文件的定义同名变量,不用担心命名冲突

第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化,它的生存期持续到程序结束,即使static局部变量在函数内使用,函数调用结束后,变量还是继续存在,只是不能使用

第三个作用是默认初始化变量为0。存储在静态数据区的变量都具备这个属性,利用这一特点可以减少程序员的工作量,不必每次初始化

第四个作用是修饰c++类的数据成员,可以在同类的多个对象之间实现数据共享,修饰的变量一般在类外初始化,修饰的类成员函数智能访问static成员

const的作用

第一个作用,阻止一个变量被改变。使用const修饰变量,先初始化,之后就无法改变变量值了

第二个作用,可以修饰指针,const位于*号的左侧时,能得到常量指针,不能通过指针来修改变量的值;const位于*号右侧时,得到指针常量,不能给指针赋予其它地址,也可以同时修饰指针和变量

第三个作用是修饰函数,const修饰函数参数时,可以让函数内部无法修改参数值;修饰类的成员函数时,表明是一个常函数,不能修改成员变量的值,只能常对象访问;还可以指定函数返回值类型为const,让其返回值不能作为左值

什么是函数模板?什么是模板函数?函数模板有什么用途?

函数模板就是函数中的某个参数或返回值的类型是不确定的,是可变的,这些不确定的类型称为模板参数。如果给函数模板的模板参数指定了一个具体的类型,就得到了一个可以执行的函数,这个函数称为模板函数。函数模板可以节省程序员的工作量,若干个被处理的类型不同,但处理流程完全一样的函数可以写成一个函数模板。

c++如何实现函数重载

编译器为每个函数重新取一个内部名字。当遇到调用重载函数时,编译器分析实际参数的个数和类型,确定一个形式参数与实际参数表一致的重载函数,将这个重载函数的内部名字替代函数调用时的函数名。

全局变量和局部变量的区别

局部变量在函数内部或语句块中定义,当函数调用时局部变量被生成,函数执行结束时局部变量被消亡。

全局变量是在所有函数外定义的变量,它可以一直生存道整个程序执行结束。所有定义该变量后面的函数都能使用该全局变量。

使用全局变量的优缺点

使用全局变量可以加强函数之间的联系,函数之间的信息交互不用通过参数传递。但全局变量也破坏了函数的独立性,用同样参数对同一个函数的多次调用可能因为执行时全局变量值不一样导致函数执行的结果不一样

变量的定义和声明的区别

变量定义和变量声明的主要区别在于有没有分配空间。变量定义要为所定义的变量分配空间,而变量声明仅指出本源文件中的程序用到了某个变量,该变量的类型是什么。而变量的定义可能出现在其它源文件,也可能出现在本源文件尚未编译到的地方。

指针与引用

数组和指针的区别

数组是在内存中连续存放的,开辟一块连续的内存空间,指针一般是指向某个变量的空间
用sizeof运算符可以计算数组的容量,sizeof指针得到的是指针本身的大小
向函数传递参数的时候,如果实参是一个数组,那用于接收的形参是对应的指针,而且是数组的首地址
在使用下标的时候,两者的用法相同,都是原地址加上下标值,不过数组的原地址就是数组首元素的地址,是固定的,指针的原地址不是固定的

指针数组和数组指针的区别

指针数组,首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定。
数组指针,首先它是一个指针,它指向一个数组,它占多少个字节取决于指针本身的大小。

引用和指针的区别

实体、初始化、多级、自增、sizeof、访问、底层
1)指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
2)引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。(注:不能有引用的值不能为NULL)
3)有多级指针,但是没有多级引用,只能有一级引用。
4)指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用是引用的变量值加1)
5)sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。
6)引用访问一个变量是直接访问,而指针访问一个变量是间接访问。
7)引用底层是通过指针实现的;

模板

模板类和模板函数的区别

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

类与对象

类和结构体的区别

1、结构体存储在栈中,类的实例化可以存储在栈中,也可以存储在堆中
2、结构体的执行效率比类要高
3、结构体没有析构函数,类有析构函数
4、结构体不可以继承,类可以继承

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

一般有三种,分别是public、private、protected
public修饰的成员表示公有成员,该成员不仅可以在类内被访问,在类外也可以被访问
private修饰的成员表示私有成员,该成员仅可以在类内被访问,在类外不可见
protected修饰的成员表示保护成员,保护成员在类外同样是不可见,但对于该类类内和它的派生类来说,是可以被访问的

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

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

从共享方式来说,静态成员变量是类的所有对象共享,而普通成员变量是每个对象单独享用

从定位位置看,普通成员变量存在栈或堆中,静态成员变量存在静态区

从初始化来看,普通成员变量在类内初始化,静态成员变量在类外初始化

构造函数和析构函数

构造函数是类的一种特殊的成员函数。它会在每次创建类的新对象时执行,可以用于为某些成员变量设置初始值,一个类可以有多个构造函数。
析构函数也是类的一种特殊的成员函数,但它是在每次删除所创建的对象时执行,主要用于回收资源,可以在跳出程序前释放资源,一个类只能有一个析构函数。

有多少种构造函数

c++中的构造函数可以分为4类
1、默认构造函数,没有参数,c++类自带
2、初始化构造函数,有参数,常用于初始化某些成员变量
3、复制构造函数,形参是本类对象的引用,用一个对象实例化另一个新对象
4、转换构造函数,用于其它类型的变量,隐式转换为本类对象。

深拷贝与浅拷贝

浅拷贝会拷贝基本类型的数据,对于引用类型,仅仅是引用地址,如果原地址中的对象被改变了,那浅拷贝出来的对象也会被改变,而且容易发生内存泄漏
深拷贝是在计算机中开辟了一块新的内存地址用于存放拷贝的对象

类成员的初始化方式

主要有两种,一是赋值初始化,在构造函数体内进行赋值;二是列表初始化,在构造函数使用冒号初始化列表进行初始化。这两种方式的主要区别在于:对于在函数体初始化,是在所有的数据成员被分配内存空间后进行的。列表初始化是给数据成员分配内存空间时就初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式,就可以在分配内存空间后进入函数体浅给数据成员赋值。另外初始化列表的效率比构造函数赋值要高,因为构造函数的赋值操作会产生临时对象

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

拷贝构造函数的作用就是用来复制对象的,使用这个对象的实例来初始化这个对象的一个新的实例

拷贝构造函数调用时,参数的传递过程是这样的,对于内置数据类型的传递,直接赋值拷贝给形参,对于类类型的值传递,首先调用该类的拷贝构造函数来初始化形参,所以会造成递归调用,陷入死循环,而引用传递,本质传递的是地址值,会按照简单类型赋值拷贝给形参,不会发生递归调用

什么情况下使用复制构造函数

1、当一个对象做显式的初始化操作,直接把另一个对象赋值给新对象
2、当对象被作为参数交给某个函数时
3、当函数传回一个类对象时
4、如果类的数据成员中含有指针,而指针指向的是一个动态变量,必须定义复制构造函数。

什么是this指针?为什么要有this指针

每个对象包含两部分内容:数据成员和成员函数。不同的对象有不同的数据成员值,因每个对象都拥有一块保存自己数据成员值得空间。但同一类得所有对象的成员函数的实现都是相同,因此所有的对象共享了一份成员函数的代码,难以把对象和成员对应上。而每个成员函数隐含的this指针就是用来确定当前调用该成员的对象。

构造函数为什么要有初始化列表

构造函数的初始化列表可以将数据成员的构造和赋值一起完成,提高对象构造的时间性能。除此之外,还有两种情况必须用初始化列表。第一种情况是数据成员函数中含有一些不能用赋值操作进行赋值的数据成员,例如常量数据成员或对象数据成员,这时必须在初始化列表中调用数据成员所属类型的构造函数来构造它们。第二种情况是在派生的方法定义一个类时,派生类对象中的基类部分必须在构造函数的初始化列表中调用基类的构造函数完成

一个派生类构造函数的执行顺序

1、先执行基类的构造函数
2、如果有类类型成员对象,执行类类型的成员对象的构造函数
3、最后执行派生类自己的构造函数

析构函数的作用

构造函数主要起初始化值得作用,实例化一个对象时,系统会调用一次构造函数
析构函数与构造函数得作用相反,用于撤销对象得一些特殊任务处理,可以是释放对象分配得内存空间。特点是析构函数与构造函数同名,在函数前加~。析构函数没有参数,也没有返回值,而且不能重载,一个类中只能有一个析构函数。

析构函数得顺序

1、调用派生类的析构函数
2、调用成员类对象的析构函数
3、调用基类的析构函数

虚析构函数的作用

在c++继承中,一般在基类使用虚析构函数,主要为了防止内存泄露。如果基类用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,只会调用基类的析构函数,而不会调用派生类的析构函数。在这种情况下,派生类中申请的空间得不到释放从而产生内存泄漏,用了虚析构函数就可以调用子类的析构函数来释放子类对象,防止内存泄漏

继承

所谓的继承就是一个类继承了另一个类的属性和方法,这个新的类包含了上一个类的属性和方法,成为子类或派生类,被继承的类称为父类或基类。一般来说子类对象可以当作父类对象使用,在c++中也能实现多继承,继承的优点是子类可以重写父类的方法实现对父类的扩展。

继承方式

1、public继承,基类成员在派生类中的访问权限保持不变,也就是说基类中的成员访问权限,在派生类中仍然保持原来的访问权限
2、private继承,基类所有成员在派生类中的访问权限都会变为私有
3、protected继承,基类的公有成员和保护成员在派生类中的访问权限都会变为保护权限,私有成员在派生类中的访问权限仍然是私有

多继承的

c++中允许为一个派生类指定多个基类,这样的继承结构被称做多重继承
优点就是对象可以调用多个基类中的接口。但缺点也有,如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性。可以使用虚继承或者用全局符号确定调用哪个类的接口来解决这个问题

基类指针可以指向派生类对象,为什么派生类指针不能指向基类对象

由于派生类对象中包含了一个基类对象,当基类指针指向派生类对象时,通过基类指针可访问派生类中的基类部分。但如果用一个派生类指针指向基类对象,通过派生类指针访问派生类新增加的成员时,将无法找到这些成员。

重载

c++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。函数重载一般是声明几个功能类似的同名函数,但这些函数的形式参数的个数、类型或顺序不同。运算符重载是重定义c++内置的运算符。

友元

友元及其作用

友元一般用freind修饰,友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。友元的作用是使全局函数或其它类的成员函数、甚至是某个类定义所有的成员函数可以直接访问当前类的私有成员。

多态

c++的多态

分为静态多态和动态多态。静态多态一般用函数重载,模板实现,是在编译的时候就确定调用函数的类型。运行时多态一般用虚函数实现,在运行的时候,才确定调用的是哪个函数,实现动态绑定。实例化基类指针指向派生类的对象,并调用派生类的函数。

抽象类

抽象类是指带有纯虚函数的类,主要作用是将有关操作接口组织在一起,为派生类提供一个公共的根,派生类具体实现在基类中定义的接口。比如派生类实现抽象类中的纯虚函数,如果派生类没有重新定义实现纯虚函数,而只是继承抽象类的纯虚函数,则这个派生类仍然是一个抽象类,不能实例化对象

虚函数

一般在类中那些被virtual关键字修饰的成员函数,就是虚函数,虚函数主要用于实现多态性,当把基类的某个成员函数声明为虚函数后,允许在它的派生类中对该函数重新定义,赋予新的功能,一般是定义一个指向基类对象的指针变量,让它指向同一类族中需要调用该函数的对象,通过指针变量调用此虚函数。

纯虚函数

纯虚函数定义是 virtual 函数=0。引入纯虚函数主要是为了更方便使用多态,我们经常需要在基类中定义虚拟函数。在很多情况下,基类本身生成对象是不合理的,例如动物可以作为一个基类派生出老虎、大象等子类,但动物本身应该不能生成对象,为了解决这种问题,引入纯虚函数,配合抽象类使用,抽象类不能生成对象,若要使派生类为非抽象类,需要对纯虚函数重载实现多态性。

虚函数和纯虚函数的区别

1、纯虚函数只有定义没有实现,虚函数既有定义又有实现
2、含有纯虚函数的类不能定义对象,含有虚函数的类能定义对象

内存管理

说一下c++的内存分配,有哪几个区

常用的有栈区、堆区、静态存储区
栈区 stack是由编译器自动分配、释放,存放函数的参数值,局部变量的值等等,它的操作方式类似于数据结构中的栈

堆区 heap一般手动分配、释放,程序结束时也可以由系统回收。它的分配方式类似于数据结构中的链表

静态存储区 static,一般存储和初始化全局变量和静态变量,程序结束后由系统释放。

堆区与栈区的区别

从管理方式来说,栈是由编译器自动管理,而在堆上的创建和释放对象需要手动控制
从空间大小来看,堆内存空间远大于栈内存空间,栈的空间有限,堆是很大的自由存储区
从分配方式来看,堆都是动态分配,通过new关键词。栈有两种分配方式,静态分配和动态分配。静态分配是由编译器完成,比如局部变量的分配,动态分配也是使用new关键字,但它的释放由编译器释放。

内存泄漏

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

堆栈溢出一般是由什么原因导致的?

1、函数调用层次太深。函数递归调用时,系统要在栈中不断保存函数调用时的现象和产生的变量,如果递归调用太深,就会造成栈溢出,这时候递归无法返回。
2、动态申请空间之后没有释放,C++没有垃圾资源自动回收机制,需要程序主动释放已经不再使用的动态地址空间,申请的动态空间使用的是堆空间,长此下去会造成堆溢出
3、因为数组访问越界、指针非法访问等问题造成内存访问错误,引发堆栈溢出问题

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值