一、C++编程语言
1、C++内存基础
1.1、C++语言的特点
参考回答:
-
C++是在C语言的基础上引入了面向对象的机制,同时可以兼容C语言。
- C++有三大特征:封装、继承、多态
封装:封装是将类的实现细节隐藏起来,通过提供公共接口,使外部代码能够使用类的 功能,而无需关心其内部的具体实现细节。外部代码通过调用公共接口与类进行 交互,实现了对类的访问和使用。通过封装,可以将数据和操作封装在一个单元 内,提高了代码的模块性和可维护性。关键是通过类的成员访问控制来实现对成 员的封装,包括私有(private)、受保护(protected)和公有(public)等访问 权限。
继承:继承是一种通过建立一个类与另一个类之间的父子关系,使得子类能够继承父类 的属性和方法。通过继承,可以重用已有的代码,提高代码的复用性。C++支持 单继承和多继承。
单继承:一个派生类只能有一个直接的基类
多继承:一个派生类可以有多个直接的基类
多态:多态是指在不同的上下文中,同一操作符或函数可以具有不同的含义。在 C++中,多态通过虚函数(virtual function)和纯虚函数(pure virtual function) 实现。它使得在运行时能够动态地选择调用哪个函数,提高了代码的灵活性和可 扩展性。
- C++编写出的程序,结构清晰,易于扩展,程序的可读性好
- C++编写的代码运行效率高,原因如下:静态类型系统,直接访问内存,内联函数,低级别的硬件控制,高度优化的编译器,多线程支持,低级别的语言特性,STL的高效的实现。
静态类型系统: C++ 是一种静态类型语言,即在编译时就能确定变量的数据类型。这种 特性有助于编译器在优化代码时进行更有效的类型检查和优化,提高程 序的性能。
直接访问内存: C++ 允许直接访问内存和进行指针操作,这使得程序员能够更灵活地管 理内存,实现高效的数据结构和算法。
内联函数: C++ 支持内联函数,允许将函数的代码插入到调用点,减少了函数调 用开销,提高了程序的执行速度。
低级别的硬件控制: C++ 保留了对底层硬件的直接访问和操作的能力,使得程序员能 够更好地控制程序的执行方式,适应不同的硬件架构。
高度优化的编译器: C++ 编译器通常能够进行多种优化,包括但不限于内联、循环展 开、常量折叠等,以产生更高效的机器代码。
多线程支持: C++ 提供了对多线程编程的支持,允许程序员充分利用多核处理器, 提高程序的并发性和整体性能。
低级别的语言特性: C++ 保留了一些底层的语言特性,比如位运算、指针算术运算 等,这些特性允许程序员更细致地控制程序的执行,实现更高效 的算法。
STL 的高效实现: C++ 标准模板库(STL)提供了高度优化的数据结构和算法实现, 这些实现经过精心设计和测试,能够在大多数情况下提供高效的性 能。
-
C++更为安全,原因如下:静态类型系统,指针和引用的类型安全,异常的处理,RAll(资源获取即初始化),标准库和模板的安全性,面向对象编程的封装和抽象,编译器的优化和静态分析工具。
指针和引用的类型安全: C++ 中对指针和引用的使用相对谨慎,指针需要显式声 明,并且通过引用传递参数可以避免指针的误用。此外, C++ 引入了智能指针等机 制,减少了内存泄漏和悬挂指针 的风险。
异常处理: C++ 提供了异常处理机制(try—catch),允许程序员在 运 行时处理错误和异常情况。这有助于更加健壮地处理错 误,避免程序异常终止。
RAII(资源获取即初始化): C++ 鼓励使用 RAII 来管理资源,如内存、文件句柄 等。通过使用类对象的生命周期来管理资源的分配和 释放,可以减少资源泄漏的风险。
面向对象编程的封装和抽象: 面向对象编程的思想使得代码可以进行更好的封装和抽 象,隐藏内部实现细节,提高了代码的模块性,降低 了耦合度。
- C++增加了const常量、引用、四类cast转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)
const常量:可以用于修饰变量、指针、成员函数等,提供了一些在编译时进行类型 检查和防止意外修改的特性。
引用: 是一种别名机制,可以用于简化代码、提高效率、实现传递和返回引 用等场景。
四类cast转换:static_cast,dynamic_cast,const_cast,reinterpret_cast
static_cast:用于在编译时进行类型转换,通常用于相互关联的类 型之间的转换。静态转换在编译时进行,不提供运行 时的类型检查。
dynamic_cast:主要用于在继承层次结构中进行安全的向下转 型,它在运行时进行类型检查。在使用 dynamic_cast 进行转换时,目标类型必须包含虚 函数,否则 dynamic_cast 将无法工作。
const_cast:主要用于添加或移除变量的 const 修饰符。 const_cast 允许改变指针或引用的底层常量性,但不 会改变变量本身的值。
reinterpret_cast:用于低级别的重解释转换,通常用于不同类型 之间的二进制位模式的转换,如指针到整数、 整数到指针等。
- C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL
- C++是不断地发展的语言。C++11中引入了nullptr、auto变量、Lambda匿名函数、右值引用、智能指针。
nullptr: 在C++11 引入的空指针常量,用于替代传统的 NULL
。 具有更明确的语 义,并且能够避免一些与空指针相关的问题。
auto变量:是一种关键字,用于自动推导变量的类型,使得变量的声明更为简洁。 编译器会根据变量的初始化表达式推断出其类型。
Lambda匿名函数:Lambda 表达式是 C++11 引入的一种匿名函数定义方式,用于 创建临时的、一次性使用的函数。它可以捕获外部变量,具有 更灵活的语法。
右值引用:右值引用是 C++11 引入的一项特性,允许对右值进行引用。它主要用 于优化资源管理和实现移动语义,提高效率。
智能指针:智能指针是用于管理动态分配内存的智能对象,提供了自动内存管理和 避免内存泄漏的机制。常见的智能指针包括 std::unique_ptr。
1.2、说说C语言和C++的区别
参考回答:
- C语言是C++的子集,C++可以很好兼容C语言。
-
C++是面对对象的编程语言,C语言是面对过程的编程语言。
-
C语言有一些不安全的语言特性,如指针使用的潜在危险、强制转换的不确定性、内存泄露等。而C++对此增加了不少新特性来改善安全性,如const常量、引用、cast转换、智能指针、try—catch等等;
- C++可复用性高,C++引入了模板的概念,后面在此基础上,实现了方便开发的标准模板库STL。
1.3、C++中的struct和class有什么区别
参考回答:
- 在 C++ 中,
struct
和class
是两种用于定义类(class)的关键字,它们在语法上有一些区别,但在功能上基本相似。主要的区别在于成员的默认访问权限和继承方式。
默认的访问权限不同:
在 struct
中,成员的默认访问权限是公共(public)。这意味着在结构体中定 义的成员,默认情况下可以被外部访问。
在 class
中,成员的默认访问权限是私有(private)。这意味着在类中定义的 成员,默认情况下只能在类的内部访问。
继承方式:
在 struct
中,继承的默认访问权限是公共(public)。这意味着从结构体派生 的类中的成员默认是公共的。
在 class
中,继承的默认访问权限是私有(private)。这意味着从类派生的类 中的成员默认是私有的。
1.4、说说include头文件的顺序以及双引号""和尖括号<>的区别
参考回答:
- 区别:
(1)尖括号<>的头文件是系统文件,双引号""的头文件是自定义文件。
(2)编译器预处理阶段查找头文件的路径不一样。
- 查找路径:
(1)使用尖括号<>的头文件的查找路径:编译器设置的头文件路径-->系统变量。
(2)使用双引号""的头文件的查找路径:当前头文件目录-->编译器设置的头文件 路径-->系统变量。
1.5、 说说C++结构体和C结构体的区别
参考回答:
- C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。
- C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
- C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。
- C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名,而 C++ 中可以省略 struct 关键字直接使用。
1.6、导入C函数的关键字是什么,C++编译时和C有什么不同?
- 关键字:在C++中,导入C函数的关键字是extern,表达形式为extern “C”, extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
- 编译区别:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
1.7、简述C++从代码到可执行二进制文件的过程
参考回答:
- C++和C语言类似,一个C++程序从源码到执行文件,有四个过程,预处理、编译、汇编、链接。
预处理:
(1) 将所有的#define删除,并且展开所有的宏定义。
(2) 处理所有的条件预编译指令,如#if、#ifdef。
(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4) 过滤所有的注释
(5) 添加行号和文件名标识。
编译:
(1) 词法分析:从左到右一个字符一个字符的读入源程序,对构成源程序的字符进 行扫描和分解,从而识别出一个个单词序列。lex工具可实现词法扫 描。
(2) 语法分析:在词法分析的基础上将单词序列分解成各类语法短语。yacc工 具可实现语法分析(yacc: Yet Another Compiler Compiler)。
(3) 语义分析:审查源程序有无语义错误,为代码生成阶段收集类型信息。
(4) 中间代码生成:将源程序变成一种内部表现形式,是一种近似与三地址指 令的四元式。形式为:(运算符,运算对象1,运算对象2,结 果)
(5) 代码优化:对前一阶段的中间代码进行改造和优化,使目标代码更加高效,提 高时空效率。
(6) 目标代码的生成:将中间代码转化为特定机器上的绝对指令代码或可重定位的 指令代码或者汇编代码。
汇编:
(1) 将汇编代码翻译成机器码,这包括将汇编指令映射到相应的二进制表示,并 生成目标文件。目标文件包含了代码段、数据段、符号表等信息,以便后续 的链接器能够进行符号解析和地址解析。
链接:
(1) 将多个目标文件和库文件整合成一个完整的可执行文件,使得程序能够在计 算机上运行。链接器通过解析符号、调整地址、重定位引用等步骤,确保各 个模块之间正确地连接起来,形成一个独立可执行的程序。
1.8、static关键字的作用
参考回答:
- 定义全局的静态变量和局部的静态变量:在变量前加关键词static。初始化的静态变量会在数 据段分配内存,未初始化的静态变量会在BSS段分 配内存。直到程序结束,静态变量始终会维持前 值。只不过全局静态变量和局部静态变量的作用域 不同;
- 定义静态函数: 在函数返回类型前加上static关键字,函数即被定义为静态函数。 静态函数只能在本源文件中使用;
- 定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又 被隐藏在类的内部。类中的static静态数据成员拥有一块单独的存 储区,而不管创建了多少个该类的对象。所有这些对象的静态数 据成员都共享这一块静态存储空间。
- 定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需 要在函数前加上关键字static即可。如静态成员函数也是类的一部 分,而不是对象的一部分。所有这些对象的静态数据成员都共享 这一块静态存储空间。
解析:
当调用一个对象的非静态成员时,系统会把该对象的起始地址赋 给成员函数的this指针。而静态成员不属于任一个对象,因而 C++规定静态成员函数没有this指针。静态成员函数是和类绑定 的,不是和对象绑定的。静态成员函数不能直接访问类的非静态 成员函数和非静态成员变量。
1.9、数组和指针的区别
参考回答:
- 概念:
数组:数组是用于存储多个相同数据类型的集合。数组名是首元素的地址。
指针:指针相当于一个变量,但与变量不同的是,指针存放的是其他变量在内存中 的地址。指针名指向了内存的首地址。
- 区别:
(1) 赋值:同类型的指针变量可以相互赋值;但数组只能一个变量一个 变量的进行赋值或拷贝。
(2) 存储方式:数组在内存中是连续存放的,需要开辟一块连续的内存空 间。数组的访问是根据数组的下标进行的,数组的存储空 间,不是在静态区就是在栈上;
指针存储的是指向某个内存地址的值,而不是一块连续的 内存。指针本身占用的空间大小是固定的(通常由系统的 位数决定),而指针所指向的内存可以是任意位置。
(3) 所占存储空间的内存大小:在数组中,需要使用sizeof(数组 名)/sizeof(数据类型);但是指针所占 空间的大小只和平台有关,32位系统占 4B,64位系统占8B。
(4) 初始化: 数组的初始化,可以在声明时进行,也可以在后续的代码 中进行。在声明时,可以直接给出元素的初始值,也可以 省略,由编译器自动初始化为默认值。
指针的初始化,可以将指针直接指向一个变量或数组的地 址,也可以使用 new
运算符动态分配内存,并将指针指向 该动态分配的内存;或者直接初始化为,nullptr
(C++11 及以上)或 NULL
(较早的 C++ 版本)表示空指 针。
(5)什么是函数指针,如何定义函数指针,有什么使用场景
(6)