C++面试高频考点整理--基础

1、const、define的联系与区别

const :在堆栈分配了空间,在程序中可以被调用、传递,存在于程序的数据段
define:仅将具体数值传递到目标变量,生命周期止步于编译期,在程序中仅是一个常数,没有实际存在,存在于数据的代码段

2、指针和引用的区别

指针:

是一个变量,指向内存中的一个存储单元,存储的是一个地址
可以有const指针
可以有多级,即指针的指针
可以为空 nullptr
初始化后指针指向可以改变
sizeof(指针) 获取指针本身的大小

引用:

是原变量的一个别名,在内存中占用同一个存储单元
没有const引用
只能是一级
不能为空,且在定义时必须初始化
初始化后不能改变
sizeof(引用) 获取指向对象的大小

3、堆和栈的区别

程序的内存分配:

栈区:由编译器自动分配释放,存放函数的参数值,局部变量值
堆区:一般由程序员分配释放,若程序员不释放,程序结束后可能由OS回收
全局区(静态区)static:全局变量和静态变量的存储是放在一块的。初始化的放在一块,未初始话的放到另一块,程序结束后由系统释放
文字常量区:存放常量字符串,程序结束由系统释放
程序代码区:存放函数体的二进制代码

int a = 0; 全局初始区
char* p1; 全局未初始区
main(){
	int b;char s[] = "abc";char* p2;char* p3="123456"; 123456在常量区,p3在栈上
	static int c=0; 全局(静态)初始区
	p1 = (char*)malloc(10);
	p2 = (char*)malloc(20); 分配得来的1020字节的空间在堆区
	strcpy(p1,"123456"); 123456在常量区,编译器可能会将它与p3所指向的123456优化成一个空间
}

1、申请方式:

栈资源由编译器自动管理;
堆资源由程序员申请释放(malloc/free、new/delete,易产生memory leak)

2、申请后系统响应:

对于栈,只要栈的剩余空间大于所申请空间,系统就会为程序分配内存,否则报异常提示栈空间溢出错误;
对于堆:系统存在一个记录空闲内存地址的链表,当系统收到程序申请时,遍历链表,寻找第一个大于所申请空间的堆节点,删除链表中该空闲节点,并将该节点空间分配给程序
(大多数系统会在这块内存空间首地址记录本次分配的大小,这样才能正确释放本内存空间;,
另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的部分重新放入空闲链表中)

3、申请大小限制:

栈:在windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域,大小是OS预定好的(windows下栈大小是2M,也有1M,在编译时确定,VC中可设置);
堆:是向高地址扩展的数据结构,是不连续的内存区域
( 由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存(32位机器上理论上是4G大小),所以堆的空间比较灵活,比较大)

4、申请效率问题:

栈是由系统系统分配,速度较快,但程序员无法控制
堆是由程序员动态分配空间,一般速度较慢,而且容易产生内存碎片

4、构造函数(初始化列表)、析构函数

构造函数 :

在类被实例化的时候调用,可用于为某些成员变量设置初始值;
不能是虚函数;
调用顺序是先是派生类然后是基类;
名字必须是类的名字,不能带返回值;
当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数(无任何操作)

析构函数:

在类对象被销毁的时候调用;
可以是虚构函数,也可以是纯虚函数,但纯虚函数必须有定义体;
调用顺序是先是基类然后是派生类
不能带返回值,并且析构函数没有参数
默认建立析构函数,但是默认也是什么都不做的

5、new、delete 和 malloc、free 的区别

1、属性

malloc / free 为 C 的标准库函数
new / delete 为 C++ 的操作运算符

2、使用

malloc开辟空间大小需手动计算 ; new是由编译器自己计算;
malloc返回类型为void*,必须强制类型转换对应类型指针 ; new则直接返回对应类型指针
malloc分配内存失败时返回NULL;new内存分配失败时,会抛出bac_alloc异常

3、内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存

☆6、深拷贝和浅拷贝

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址;
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误

7、友元函数、友元类—friend

友元函数:
在定义类时,将一些函数(全局函数 或其他类的成员函数等)声明为友元,则这些函数就称为该类的友元函数,在友元函数内部就可以访问该类对象的私有成员。但不能把其他类的私有成员函数声明为友元

// 将全局函数声明为友元:
friend  返回值类型  函数名(参数表);
// 将其他类的成员函数声明为友元:
friend 返回值类型  其他类的类名::成员函数名(参数表);

友元类:
一个类A可以将另一个类B声明为自己的友元,则类B的所有成员函数就都可以访问类A对象的私有成员

friend  class  类名;

关于友元类的注意事项:

(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。

8、static的用法与意义

在 C++ 中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用
静态成员的定义或声明要加个关键 static。静态成员可以通过双冒号来使用即 <类名>::<静态成员名>

用法:

1、用来修饰成员变量,将其变为类的成员,从而实现所有对象对于该成员的共享;
2、用来修饰成员方法,将其变为类方法,可以直接使用“类名.方法名”的方式调用,常用于工具类;
3、静态块用法,将多个静态类成员放在一起初始化,使得程序更加规整,其中理解对象的初始化过程非常关键

作用:

1、扩展生存期:针对普通局部变量
声明为static的局部变量的生存期不再是当前作用域,而是整个程序的生存期。
2、限制作用域:针对普通全局变量
普通的全局变量和函数作用域为整个程序或项目,如果要在多个cpp文件间共享数据,应该将数据声明为extern 类型。
static 全局变量和函数,其作用域为当前cpp文件,其它的cpp文件不能访问该变量和函数。如果有两个cpp文件声明了同名的全局静态变量,那么他们实际上是独立的两个变量,在其他的文件中即使用 extern 声明也不能使用
3、唯一性:针对类成员
表示属于一个类而不是属于此类的任何特定对象的变量和函数

9、内联函数 – inline

内联函数是指用 inline 关键字修饰的函数。
内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名
只有当函数只有 10 行甚至更少时才将其定义为内联函数

引入内联函数的目的是为了解决程序中函数调用的效率问题

注意点:
1、内联函数的定义必须出现在内联函数第一次调用之前;
2、函数体内不能含有循环、条件、选择等复杂的结构;
3、类结构中所在的类说明内部定义的函数是内联函数;

10、继承、虚继承、钻石继承

多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径
假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义(可以指定作用域),编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径,同时会造成资源浪费(两份相同的数据--虚继承)
使用 虚继承,使得在派生类中只保留一份间接基类的成员
在这里插入图片描述

菱形继承问题:

#include <iostream>
using namespace std;
//基类
class D
{
public:
    D(){cout<<"D()"<<' ';}
    ~D(){cout<<"~D()"<<' ';}
	
protected:
    int d;
};

class B:virtual public D
{
public:
    B(){cout<<"B()"<<' ';}
    ~B(){cout<<"~B()"<<' ';}
protected:
    int b;
};

class A:virtual public D
{
public:
    A(){cout<<"A()"<<' ';}
    ~A(){cout<<"~A()"<<' ';}
protected:
    int a;
};

class C:public B, public A
{
public:
    C(){cout<<"C()"<<' ';}
    ~C(){cout<<"~C()"<<' ';}
protected:
    int c;
};

int main()
{
    C c;
	cout<<"*********************************"<<endl;
    cout<<"D:"<<sizeof(D)<<endl; 
    cout<<"B:"<<sizeof(B)<<endl;
    cout<<"A:"<<sizeof(A)<<endl;
    cout<<"C:"<<sizeof(C)<<endl;
	cout<<"*********************************"<<endl;
    return 0;
}

在这里插入图片描述

未使用虚继承:D创建了两次,存在数据冗余
在这里插入图片描述
采用虚继承可解决此问题;

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。
在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员
在这里插入图片描述

11、同名覆盖

1、子类对象可以当做父类对象使用(赋值兼容)
2、子类对象可以直接赋值(或初始化)给父类对象
3、父类指针可以直接指向子类对象
4、父类引用可以直接引用子类对象

当使用父类指针( 引用 ) 指向子类对象时:
子类对象退化为父类对象,只能访问父类中定义的成员

#include <iostream>
using namespace std;

class Parent
{
public:
    int mi;
    
    void add(int i){
        mi += i;
    }
    void add(int a, int b){
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z){
        mv += (x + y + z);
    }
};

int main()
{
    Parent p;
    Child  c;
    
    p = c;                  // 子类对象赋值父类对象
    Parent p1(c);           // 子类对象初始化父类对象   
    Parent& rp = c;         // 父类引用子类对象
    Parent* pp = &c;        // 父类指针指向子类对象
    
    rp.mi = 100;
    rp.add(5);              // 注意这里!
    rp.add(10, 10);
	
    //  子类对象退化为父类对象,只能访问父类中定义的成员
    // pp->mv = 1000;        // Error
    // pp->add(1, 10, 100);  // Error
    return 0;
}

12、虚函数(实现原理:虚函数表、虚指针)、纯虚函数

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数,核心理念就是通过基类访问派生类定义的函数
在派生类中对基类定义的虚函数进行重写时,需要在派生类中声明该方法为虚方法
使用一个基类类型的指针或者引用,来指向子类对象,进而调用由子类复写的个性化的虚函数,这是C++实现多态性的一个最经典的场景

当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的实际类型决定

编译器处理虚函数的方法是:为每个类(包含虚函数)对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组称为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针

如果派生类重写了基类的虚方法,该派生类虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。
如果基类中的虚方法没有在派生类中重写,那么派生类将继承基类中的虚方法,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址

注意,如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。

虚函数,在类成员方法的声明(不是定义)语句前加“virtual”, 如 virtual void func()
纯虚函数,在虚函数后加“=0”,如 virtual void func()=0

何时使用纯虚函数?
(1)当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;
(2)这个方法必须在派生类(derived class)中被实现;

子类如果不提供虚函数的实现,将会自动调用基类的缺省虚函数实现,作为备选方案;
子类如果不提供纯虚函数的实现,编译将会失败。尽管在基类中可以给出纯虚函数的实现,但无法通过指向子类对象的基类类型指针来调用该纯虚函数,也即不能作为子类相应纯虚函数的备选方案

在这里插入图片描述

13、接口- - - Interface(实现原理)- - - 理解不清

接口描述了类的行为和功能,而不需要完成类的特定实现。

C++ 接口是使用抽象类来实现的,如类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类
设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类
抽象类不能被用于实例化对象,它只能作为接口使用

面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。

14、重载(Overload)、重写(Override)

面试题:重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
答:方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常
重载对返回类型没有特殊的要求,不能根据返回类型进行区分

重载:是指同一个类中的多个方法具有相同的名字,但这些方法具有不同的参数列表,即参数的数量或参数类型不能完全相同

重载——有不同的参数列表(静态多态性)

重写:是存在与子父类之间的,子类定义的方法与 父类中的方法具有相同的方法名字,相同的参数列表和相同的返回类型

在子类中重写父类非私有方法—— 相同参数,不同实现(动态多态性
(1)子类中不能重写父类中的 final方法
(2)子类中必须重写父类中的 abstract方法

15、函数重载、运算符重载

函数重载:
在同一作用域内,可以声明几个功能类似的同名函数,但这些同名函数的形参(参数个数,类型或顺序)必须不同,不能仅通过修改返回类型来重载函数

运算符重载:
重载的运算符是带有特殊名称的函数,函数名是由关键字operator 和 其后需重载的运算符符号构成。重载运算符也有返回类型和参数列表 ------ 返回类型 operator 运算符 (形参列表)

可通过成员函数进行重载 或 通过全局函数进行重载
运算符重载 也可发生函数重载
运算符重载函数作为全局函数时,一般都需要在类中将该函数声明为友元函数。因为,该函数大部分情况下都需要使用类的 private 成员

16、流类库和文件

流是一种抽象的概念,负责在数据的生产者和消费者之间建立联系,并管理数据的流动
一般意义的读操作在流数据抽象中被称为(从流中)提取写操作被称为(从流中)插入

整个流类体系是一个派生类体系,ios 是抽象类,作为所有基本流类的基类

ios 类公有派生的 istream和ostream 两个类分别提供对流进行提取操作和插入操作的成员函数
iostream类 通过 组合istream类和ostream类 来支持对一个流进行双向(也就是输入和输出)操作,它并没有提供新的成员函数

cout 继承 ostream ,ostream 继承于ios
cin 继承 istream , istream 继承于 ios

C++流类库预定义了4个流,它们是cin、cout、cerr、clog
事实上,可以将cin视为类istream的一个对象,而将cout视为类ostream的对象

cin是标准输入流对象,cout是标准输出流对象, cerr和clog 是标准错误输出流
其中cin,cout和clog是带缓冲区,有streambuf类对象管理,cerr不带缓冲区,发生错误立即显示

在C++里,文件操作是通过流来完成的
C++总共有输入文件流输出文件流输入输出文件流3种
要打开一个输入文件流,需要定义一个ifstream类型的对象;要打开一个输出文件流,需要定义一个ofstream类型的对象;如果要打开输入输出文件流,则要定义一个fstream类型的对象。
这3种类型都定义在头文件<fstream>

17 、为什么C++没有实现垃圾回收

1、系统开销:垃圾回收所带来的系统开销,违反了C++的设计哲学,“不为不必要的功能支付代价”,不符合C++高效的特性,使得不适合做底层工作
2、替代方法:C++有析构函数、智能指针、引用计数去管理资源的释放,对垃圾回收(GC)的需求不迫切

传送门

github

const与define的区别
C++ 常量类型 const 详解

浅谈C++中指针和引用的区别
#图解 轻松看懂「指针的引用*&」
C++ 指针的引用和指向引用的指针

堆和栈的区别(转过无数次的文章)
C++:堆和栈的区别

构造函数和析构函数
c++中的初始化列表详解
C++ 类构造函数初始化列表

浅谈new/delete和malloc/free的用法与区别
指针常量和常量指针的区别,看看你们的理解跟我的一样嘛??
常量指针与指针常量的区别(转帖)
为何new出的对象数组必须要用delete[]删除,而普通数组delete和delete[]都一样
new/delete与malloc/free的区别与联系详解!

深拷贝和浅拷贝的区别
面试题:深拷贝和浅拷贝

友元(友元函数、友元类和友元成员函数) C++
C++友元函数和友元类(C++ friend)详解

static关键字的用法与作用
c/c++ static 用法总结(三版本合一)
C/C++ 中 static 的用法全局变量与局部变量

C++ 内联函数
内联函数与普通函数的区别
内联函数和普通函数的区别在哪里

C++黑马 - - -类和对象-继承-菱形继承问题以及解决方法
虚继承类内存大小计算
(虚)继承类的内存占用大小
C++类内存分布 - - - VS
C++ 继承
C++虚继承和虚基类详解
菱形继承问题和虚继承

C++继承中的同名覆盖
【C++】 48_同名覆盖引发的问题
【C++】 49_多态的概念和意义
什么是多态?实现多态的机制是什么?
黑马视频 - - - 类和对象-继承-同名成员处理

C++中的虚函数
C++ 虚函数、纯虚函数
C/C++杂记:虚函数的实现的基本原理
C++ 多态的实现及原理(虚函数与纯虚函数)

C++ 接口(抽象类)

重载与重写的区别
C++ override和final关键字(详解版)
final关键字的用法

运算符重载和函数重载
黑马 – -C++运算符重载-左移运算符重载

C++流类库(11)
C++笔记------流类库和文件的输入输出
C++ IO流的概念及流类库、文件操作
C++(笔记)文件操作练习(流类库与输入输出)

c++没有垃圾回收机制的原因

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值