c++面经2

11.什么是inline内联函数,使用内联函数有什么优缺点,什么是宏,宏和inline函数有什么区别

内联函数是用inline关键字修饰的,定义在类体外的成员函数,内联函数会在函数编译阶段进行展开,少了函数调用的开销以提高代码运行的效率
内联函数的优点:
它通过避免函数调用开销来加速程序的运行
当函数调用发生时,它可以节省堆栈上变量push/pop的开销
它节省了函数调用返回的开销
内联函数的缺点:
由于代码扩展,它增加了可执行文件的大小
可能到时内存崩溃
宏的优缺点:宏包括宏常量和宏函数
宏常量优点:一改全改,比如在求圆的面积时,可以定义一个宏常量π
宏常量的缺点:在预处理阶段进行替换,因此不会参与到编译阶段的类型检测
宏函数优点:预处理阶段会用宏体替换宏–少了真正函数调用所存在的开销
宏函数缺点:不会参与编译,缺少参数的类型检测,不能调试

宏是根据一系列预定义的规则替换一定的文本模式,宏名之后带括号的宏被定义为宏函数 #define add(a,b) (a+b)
内联函数和宏的区别
宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈推栈的过程,提高了效率
内联函数本质上是一个函数,内联函数一般都是代码比较简单的函数
宏定义是在预处理时把所有的宏名用宏体替换,简单的说就是字符串替换
内联函数则是在编译时进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数展开,这样省去了函数调用的开销,提高了效率
宏定义是没有类型检测的,无论对还是错都是直接替换
内联函数在编译时会进行类型检查

12.构建一个main函数从执行到输出的过程

预处理–>编译–>汇编–>链接
预处理:主要处理那些源代码中以"#"开头的预处理命令,经过预处理的文件已经不包含任何注释以及宏定义,其中宏定义的删除只是进行了展开替换操作
编译:从直观角度来讲,编译器就是将高级语言翻译成机器语言的工具,编译过程一般分为6个步骤:扫描-词法分析-语法分析-语义分析-源代码优化-中间代码生成-目标代码优化
汇编:汇编器将汇编代码转变为机器可以执行的指令最终输出二进制格式的目标文件
链接:由汇编程序生成的目标文件并不能立即就被执行,其中可能还有很多没有解决的问题,连接器的主要工作就是将有关的目标文件彼此相连接,使得所有的目标文件成为一个可以被操作系统装入执行的统一整体 ,链接分为静态链接和动态链接
静态链接:就是把函数库中包含的目标代码静态添加到可执行文件中,一般静态链接生成的可执行程序比较大,可独立运行
动态链接:讲这些函数库的路径,函数名等信息添加到可执行文件当中,在执行过程中动态加载函数库,需要函数库的支持

13.什么是多态

概念:同一事物在不同情况下具有不同表现形态 例子:橘生淮南则为橘,生于淮北则为枳
分类:静态类型多肽(静态绑定/早绑定)和动态类型多肽(动态绑定/晚绑定);
静态多态:概念-在程序编译阶段来确定函数的行为----->确定机制:根据参数类型的推演来确定的 体现:函数重载
动态多态:概念-在程序运行阶段确定函数的行为------>确定机制: 体现:虚函数实现多态

动态多态的实现条件:一定要在继承体系中
a、基类中必须要有虚函数(被virtual关键字修饰的非静态成员函数),在子类中必须对基类的虚函数进行重写
b、虚函数调用–必须通过基类的指针或引用调用虚函数
多态的表现:在运行时,让基类的指针指向不同子类的对象,就可以调用到不同子类的函数来实现多态
在多肽实现条件中提到重写
1.基类的函数一定是虚函数 2.子类对基类的虚函数进行重写 3.子类重写时必须与基类的虚函数的原型完全相同
例外:协变–基类虚函数返回基类的指针或引用 子类虚函数返回子类的指针或引用---->基类和子类虚函数的返回值不同
析构函数:子类析构函数和基类虚构函数的名字不同
多肽的实现原理:
在这里插入图片描述

class Base
{
public:
virtual void f1();
virtual void f2();
virtual void f3();
private:
int b;
}
基类虚表构建规则:依照虚函数在基类中的声明次序依次将虚函数的入口地址放在虚表中
如果一个类中具有虚函数,该类的对象中就会多四个字节,该四个字节存储的是一个指针
class D:public B
{
public:
virtual void f1();
virtual void f3();
virtual void f4();
private:
int b;
}
派生类虚表构建规则:
1.将基类虚表中的虚函数地址拷贝一份放在派生类虚表中
2.如果派生类重写了某个基类中的虚函数,则用派生类自己同名的虚函数代替虚表中相同偏移量位置的虚函数
3.如果派生类增加新的虚函数,则将其增加在虚表的最后
构造函数不能为虚函数
原因:如果构造函数可以为虚函数,调用构造函数就必须通过虚表指针调用,而虚表指针是放在对象模型中的,对象只有通过调用构造函数才能完整的创建出来,所以这样就自相矛盾了
析构函数可以为虚函数

14 :c++中函数重载,重写以及重定义的区别

函数重载指的是在相同作用域内,两函数的函数名可以相同,但是参数不同完全相同(参数的类型或者参数的个数),与返回值类型无关
函数重载的调用原理:在编译阶段,编译器会对传递实参的类型进行推演,根据推演的实际类型选择类型匹配的函数来进行调用
注意:如果推演的实际类型结果没有找到完全匹配的函数,编译器会尝试进行隐式类型转化,如果隐式类型转化之后,有合适的函数可供调用,就调用该函数,如果没有合适的函数调用或产生二义性,则编译失败

函数重写指的是在继承体系中,1.基类的函数一定是虚函数 2.子类对基类的虚函数进行重写 3.子类重写时必须与基类的虚函数的原型完全相同

重定义也叫隐藏,指的是在继承体系中,子类实现了一个与父类名字一样的函数(只关心函数名,与参数和返回值类型无关),这样的话子类的函数就把父类的同名函数隐藏掉了

15.C语言和c++的区别
设计思想上:
C语言是面向过程的一门语言,面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
c++是基于面向对象而不是纯面向对象的一门语言。纯面向对象就是一切皆对象,而c++要兼容C语言,因此c++是既有面向过程也有面向对象。
面向对象就是把构成问题的事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为
面向过程的优点:性能比面向对象高,因为类在调用的时候需要实例化,开销比较大,比较消耗资源;
面向过程的缺点:没有面向对象易维护,易复用,易扩展
面向对象的优点:易维护,易复用,易扩展,由于面向对象有封装、继承、多态性的特点,使系统更加灵活,更加易于维护
面向对象的缺点:性能比面向对象比较低
结构上:
C语言和c++都有结构的概念,而C语言中结构只有成员变量,而c++不但有成员变量,也有成员函数
语法上:
c++具有封装、继承、多态三种特性
c++相比于C语言,增加了许多类型安全的功能,比如:强制类型转换
c++支持泛型编程,泛型编程是通过模板机制来构建一类操作类似但数据不同的程序,其中具有模板类、函数模板等。

16.c/c++中内存是如何分配的
在这里插入图片描述

数据段:数据段通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中 。
代码段:代码段通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中 。
栈区:由系统自动分配,当函数执行结束时由系统自动释放。存放局部变量。栈的缺点是:容量有限,当相应的区间被释放时,局部变量不可再使用。栈是一块连续的区域,向低地址扩展,栈顶和容量是事先约定好的。
堆区:在程序的执行过程中才能分配,由程序员决定,编译器在编译时无法为他们分配空间,只有在程序运行时分配,所以被称为动态分配。堆是不连续的区域,向高地址扩展。由于系统用链表来描述空闲的地址空间,链表的遍历是由地地址向高地址的,故堆区是不连续的动态的存储空间。

内存映射区: 将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件
17 strlen和sizeof的区别:
(1):strlen是函数,sizeof是运算符
(2):strlen测量的是字符的实际长度,以’\0’结束,sizeof计算的是字符的分配大小
(3):strlen只能用char*做参数,sizeof可以用类型做参数
(4):strlen的结果要在运行的时候才能计算出来,大部分编译程序,在编译的时候就把sizeof计算过了
(5):数组做sizeof的参数时不退化,做strlen的参数时退化为指针

18:malloc/realloc/calloc的区别
共同点:都是c语言中用来进行动态内存申请的库函数,都是在堆上申请空间,用完之后必须使用free进行释放
返回值类型都是void*,在接受返回的地址时必须进行强转
如果申请空间成功:返回的是空间的首地址,如果申请失败则返回空
不同点:
malloc函数。其原型void *malloc(unsigned int num_bytes);
num_byte为要申请的空间大小,需要我们手动的去计算
calloc函数,其原型void *calloc(size_t n, size_t size);
其比malloc函数多一个参数,并不需要人为的计算空间的大小
realloc函数和上面两个有本质的区别,其原型void realloc(void *ptr, size_t new_Size)
用于对动态内存进行扩容(即已申请的动态空间不够使用,需要进行空间扩容操作)

C语言中的动态内存管理方式在c++中仍然可以使用,但是使用起来比较麻烦,
(1) 需要用户手动计算字节数、需要对返回结果强转、需要判空、需要包含头文件
(2) malloc和free不会调用构造函数和析构函数 new/delete在进行空间申请和释放时,会调用构造函数和析构函数
c++中内存管理方式:new/delete----->申请单个类型的空间 new[]/delete[]----->申请一段连续的空间
注意:new/delete不是函数,是c++中的关键字;

19:c++的三大特性
一:封装
概念:所谓封装就是将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
封装特性:类和对象
抽象来说,类就是拥有相等功能和相同属性的对象的集合。而对象就是类的实例。从生活角度来看,如果把人当做类,那么你和我都属于对象,一个类就是一个作用域
类具有三个访问权限:public(公有),private(私有),protect(保护)
类对象大小的计算:只需将类对象中成员变量加起来就可以了(但是要注意内存对齐)
类具有六个默认成员函数:
(1):构造函数:是一个特殊的成员函数,函数名必须与类名相同,在创建对象时被调用,以完成对象的初始化操作,并在对象的整个生命周期内只被调用一次
构造函数特性:
*
函数名必须与类名相同
*
没有返回值
*
构造函数可以重载
*
如果用户没有显示定义,编译器会生成一个默认的构造函数(默认的构造函数是无参的)
*
将无参的构造函数和全缺省构造函数统一称为缺省构造函数,并且他们不能同时存在
*
构造函数不能使用const进行修饰
*
构造函数不能是静态的成员函数
*
构造函数不能被virtual修饰为虚函数

调用场景:在创建对象时,编译器会自定调用构造函数
(2):拷贝构造函数:拷贝构造函数是一个特殊的成员函数,它一定是单参的,并且该参数一定是类类型对象的引用
拷贝构造函数的特性:
*
单参的,参数为类类型对象的引用,否则会造成编译失败(可能会造成无限递归)
*
拷贝构造函数是构造函数的一个重载形式
*
如果用户没有显示定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数

调用场景:用类类型常见对象时:A a1;A a2(a1);
(3):赋值运算符重载:它是两个已有对象一个给另一个赋值的过程。它不同于拷贝构造函数,拷贝构造函数是用已有对象给新生成的对象赋值的过程
调用场景:a1 = a2
(4):析构函数:一个特殊的成员函数,在对象销毁时,由编译器自动进行调用,完成对象中资源的清理工作(只清理对象中的资源,并不会回收空间)
析构函数特性:
*
类名前加~
*
析构函数一定是无参的,没有返回值,不能重载,一个类最多有一个析构函数
*
如果用户没有显示定义析构函数,编译器会自动生成一个默认的析构函数

使用场景:在对象销毁时使用
(5):取地址运行算符重载:operator&()
(6):const取地址运算符重载:operator&()const
二:继承
继承可以提高代码复用,在保持原有类特性的基础上进行扩展,可以使子类具有父类的属性和方法或者重新定义,追加属性和方法
继承权限:public/protected/private 默认的继承权限:class–private struct–public
赋值兼容规则:
子类对象可以直接赋值给父类对象;
子类对象可以当做父类对象去使用;
子类对象可以初始化父类对象;
父类指针可以直接指向子类对象;
父类引用可以直接引用子类对象;
三:多态
概念:同一事物在不同情况下具有不同表现形态 例子:橘生淮南则为橘,生于淮北则为枳
分类:静态类型多肽(静态绑定/早绑定)和动态类型多肽(动态绑定/晚绑定);
静态多态:概念-在程序编译阶段来确定函数的行为----->确定机制:根据参数类型的推演来确定的 体现:函数重载
动态多态:概念-在程序运行阶段确定函数的行为------>确定机制: 体现:虚函数实现多态

动态多态的实现条件:一定要在继承体系中
a、基类中必须要有虚函数(被virtual关键字修饰的非静态成员函数),在子类中必须对基类的虚函数进行重写
b、虚函数调用–必须通过基类的指针或引用调用虚函数
多态的表现:在运行时,让基类的指针指向不同子类的对象,就可以调用到不同子类的函数来实现多态
在多肽实现条件中提到重写
1.基类的函数一定是虚函数 2.子类对基类的虚函数进行重写 3.子类重写时必须与基类的虚函数的原型完全相同
例外:协变–基类虚函数返回基类的指针或引用 子类虚函数返回子类的指针或引用---->基类和子类虚函数的返回值不同
析构函数:子类析构函数和基类虚构函数的名字不同
多肽的实现原理:
多态的实现原理可以通过创建对象模型体现出来
基类的对象模型:创建一个基类对象,如果基类里面有虚函数,它的对象模型中会增加4个字节,该4个字节存储的是一个指针,俗称虚表指针,虚表指针指向一个虚函数表,该虚函数表里存放的是基类中虚函数的入口地址,按照虚函数在基类中的声明顺序依次存放。
派生类的对象模型:和基类一样,创建一个派生类对象,如果基类里面有虚函数,它的对象模型中会增加4个字节,该4个字节存储的是一个指针,俗称虚表指针,虚表指针指向一个虚函数表
派生类虚函数表构建规则:
1.将基类虚表中的虚函数地址拷贝一份放在派生类虚表中
2. 如果派生类重写了某个基类中的虚函数,则用派生类自己同名的虚函数代替虚表中具有相同偏移量的虚函数
3.如果派生类增加新的虚函数,则将其增加在虚表的最后

20.const关键字和static关键字的作用

const关键字:
1.防止被修饰的成员的内容发生改变
2.修饰类的成员函数时,表示该函数为一个常函数,意味着该成员函数不能修改类成员变量的值
3.在函数声明时修饰参数,表示在函数访问时参数的值不会发生改变
4.对于指针而言,可以指定指针本身为const,也可以指定指针所指的数据位const, const int b = &a;或者int const b = &a;修饰的都是后面的值,分别代表*b和b不能改变 。
5.const可以替换C语言中的宏定义,该替换发生在编译时期,该常量参与类型检测,提高代码的安全性

static关键字:
static可以用来修饰全局变量,局部变量和类中的成员
当static修饰全局变量的时候,该变量只能你作用于当前文件,工程内的其他文件不能访问到该全局变量
当static修饰局部变量的时候,只会对该变量初始化一次,该变量在程序中只有一份内存,该变量的作用域不会被改变,生命周期会被延长,在程序结束时被销毁。
当static修饰类中的成员是,会形成类的静态成员,在类中,静态成员可以实现多个对象中的数据共享,静态成员是类中所有对象共享的成员,而不是某一个对象的成员

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值