【C++八股-第五期】C++基础 ① - 24年春招特

👉 八股:

1、请简单描述一下C语言和C++的区别

2、简单概述一下C++的特点(三大特性)

3、C++从源码到可执行文件有哪几步?

4、说一说动态链接和静态链接的区别。

5、include中双引号“”和尖括号<>的区别

6、类构造函数调用问题

7、怎么在C++中调用已经被C编译器编译后的函数?

8、介绍一下什么是C++的宏

9、C++为什么提倡尽量避免使用宏定义

10、C++中应该怎么办才能替代 #define 的一些功能

11、简单介绍一下 const 和 # define 的区别

12、简单介绍一下const的各种应用

13、简单介绍一下# define 和 inline 的区别

👉 代码:

八股内容:C++基础 ①

1、请简单描述一下C语言和C++的区别

  • C++可以很好的兼容C语言,但同时也有很多新的特性(C++11新特性)

  • 首先要说的区别:

    • C语言是面向过程的编程语言。

    • C++是面向对象的编程语言,C++引入了,因此产生了三大特性:封装、继承、多态。

  • C语言和C++中都有结构这一概念。

    • 但是在 C语言 中,结构只包含成员变量,没有成员方法,且成员变量是公共的,不管什么人都可以去访问;

    • C++ 的结构则可以同时拥有成员函数和成员变量,且默认访问权限是私有;

      • C++中struct 的默认访问权限也是public
  • 设计思路区别:

    • C语言是一门结构化语言,在设计的时候我们首先要考虑的是怎样通过一个过程对输入进行处理的到输出,C语言广泛用于底层开发。

    • 而进行C++编程,我们首先要考虑的是如何构造一个对象模型,让这个模型能够契合对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

  • 图像处理方面的区别:

    • C++中的图形处理和C语言的图形处理有很大的区别。C语言中的图形处理函数基本上是不能用在中C++中的。C语言标准中不包括图形处理。
  • 安全方面:

    • C语言指针使用的潜在危险性、强制转换的不确定性、内存泄漏问题等,都会带来安全方面的问题。

    • 对此,C++引入了 const常量、引用、cast转换机制、智能指针、try-catch语法等。

  • 可复用性:

    • C++引入了模板的概念并再次基础上实现了STL(标准模板库)。STL的重要特点之一就是以泛型化编程的思想实现了算法与数据结构的分离,

2、简单概述一下C++的特点(三大特性)

  • C++在C语言的基础上引入了面向对象的机制,C++支持数据封装支持数据封装就是支持数据抽象。在C++中,类是支持数据封装的工具,对象则是数据封装的实现。面向过程的程序设计方法与面向对象的程序设计方法在对待数据和函数关系上是不同的。C++同时兼容C语言。

  • C++有三大特性,分别是封装、继承、多态。(面试官就等着你说这个呢);

    • 多态:C++允许函数名和运算符重载;C++支持多态性,C++允许一个相同的标识符或运算符代表多个不同实现的函数,这就称标识符或运算符的重载,用户可以根据需要定义标识符重载或运算符重载。

    • C++中可以允许单继承和多继承。一个类可以根据需要生成派生类。派生类继承了基类的所有方法,另外派生类自身还可以定义所需要的不包含在父类中的新方法。一个子类的每个对象包含有从父类那里继承来的数据成员以及自己所特有的数据成员。

  • C++因为其结构清醒的特点,可读性好,也便于扩充;

  • C++可以定义不同的访问控制权限,类内分 私有、公有、保护;

  • C++生成的代码质量高,运行效率高,仅比汇编语言慢10%~20%;

  • C++更加安全,增加了const常量、引用、四类cast转换(static_cast、dynamic_cast、const_cast、reinterpret_cast)、智能指针、try—catch等等;

  • 可复用性高:C++引入了模板的概念并再次基础上实现了STL(标准模板库)。STL的重要特点之一就是以泛型化编程的思想实现了算法与数据结构的分离,

3、C++从源码到可执行文件有哪几步?

  • 一共有四个过程预编译编译汇编链接

alt

  • 预处理: 产生.ii文件;主要的处理操作如下:

    • 对全部的#define进行宏展开。

    • 处理全部的条件编译指令,比方#if#ifdef#elif#else#endif;

    • 处理 #include 指令,这个过程是递归的,也就是说被包括的文件可能还包括其它文件;

    • 删除全部的注释 ///**/

    • 加入行号和文件标识

    • 保留全部的 #pragma 编译器指令

ps:经过预处理后的.ii文件不包括不论什么宏定义,由于全部的宏已经被展开。而且包括的文件也已经被插入到.ii文件里。

  • 编译:产生汇编文件(.s文件)
    • 词法分析:将源代码的字符序列分割成一系列的记号。

    • 语法分析:对记号进行语法分析,产生语法树。

    • 语义分析:判断表达式是否有意义。

    • 代码优化:

    • 目标代码生成:生成汇编代码。

    • 目标代码优化:

编译会将源代码由文本形式转换成机器语言,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。

编译后的.s是ASCII码文件。

  • 汇编:产生目标文件(.o或.obj文件)

    • 汇编过程调用汇编器AS来完成,是用于将汇编代码转换成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。

汇编后的.o文件是纯二进制文件。

  • 链接:产生可运行文件(.out或.exe文件)

    • 链接是将所有的.o文件和库(动态库、静态库)链接在一起,得到可以运行的可执行文件(Windows的.exe文件或Linux的.out文件)等。它的工作就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了地址和空间分配、符号决议和重定向。

最基本的链接叫做静态链接,就是将每个模块的源代码文件编译、汇编成目标文件(Linux:.o 文件;Windows:.obj文件),然后将目标文件和库一起链接形成最后的可执行文件(.exe或.out等)。库其实就是一组目标文件的包,就是一些最常用的代码变异成目标文件后打包存放。最常见的库就是运行时库,它是支持程序运行的基本函数的集合。

4、说一说动态链接和静态链接的区别。

  • 静态链接: 把目标程序运行时需要调用的函数代码直接链接到了生成的可执行文件中,程序在运行的时候不需要其他额外的库文件,且就算你去静态库把程序执行需要的库删掉也不会影响程序的运行,因为所需要的所有东西已经被链接到了链接阶段生成的可执行文件中。

    • Windows下以.lib为后缀,Linux下以.a为后缀。
  • 动态链接: 动态 “动” 在了程序在执行阶段需要去寻找相应的函数代码,即在程序运行时才会将程序安装模块链接在一起

    • 具体来说,动态链接就是把调⽤的函数所在⽂件模块(DLL )和调⽤函数在⽂件中的位置等信息链接进目标程序,程序运⾏的时候再从 DLL 中寻找相应函数代码,因此需要相应 DLL ⽂件的⽀持 。(Windows)

    • 包含函数重定位信息的文件,在Windows下以.dll为后缀,Linux下以.so为后缀。

了解了定义之后让我们来看一下他们的区别

  • 链接使用工具不同:

    • 静态链接由称为“链接器”的工具完成;
    • 动态链接由操作系统在程序运行时完成链接;
  • 库包含限制:

    • 静态链接库中不能再包含其他的动态链接库或者静态库;
    • 动态链接库中还可以再包含其他的动态或静态链接库。
  • 静态链接是 将各个模块的obj和库链接成一个完整的可执行程序;而动态链接是程序在运行的时候寻找动态库的函数符号(重定位),即DLL不必被包含在最终的exe文件中

  • 运行速度:

    • 静态链接运行速度快(因为执行过程中不用重定位),可独立运行

    • 动态链接运行速度慢、不可独立运行(其实这就是为了答题答题,其实现实中影响不大,你看你电脑里一堆dll就说明大家都在用,都说好~)

空间占用:

  • 静态链接浪费空间,存在多个副本,同一个函数的多次调用会被多次链接进可执行程序,当库和模块修改时,main也需要重编译;

  • 动态链接节省空间(未写入要链接的内容),相同的函数只有一份,当库和模块修改时,main不需要重编译。

5、include中双引号“”和尖括号<>的区别

  • 双引号 “”

    • 引用文件类型: 系统自定义文件,也可以引用系统文件;

    • 头文件查找路径: 当前头文件目录 → 编译器设置的头文件路径 → 系统变量。

  • 尖括号 <>:

    • 引用文件类型: 系统文件,不可以引用系统自定义文件;

    • 头文件查找路径: 编译器设置的头文件路径 → 系统变量。

6、类构造函数调用问题

题目: 假定Car为一个类,则执行 Car a(2), b[3], *p[4]; 语句时调用该类构造函数的次数为:

首先分析这几个变量的意义:

  • a(2):意思是定义了一个变量叫a,并使用还有一个参数的构造函数完成构造;

  • b[3]:定义了一个数组,数组中有3个元素,每一个元素都是AB的实例,为确保实例在使用前已经构造,编译器会自动调用没有参数的构造器3次,一次这里就调用了3次无参数构造器;

  • AB* p[4]: 是指声明了一个指针数组,数组名字叫做p,数组大小为4,数组中的每一个元素都是AB类的指针,这里只是声明了指针,并未给每个指针安排指向的对象,所以并没有调用构造函数。

7、怎么在C++中调用已经被C编译器编译后的函数?

  • 使用 extern C

  • 在C++中编译器会按照C语言的标准编译加了 extern C 的代码,而不是使用C++的标准

// 例子
  extern “C”
  {
  void foo(int x, int y);
  // 其它函数
  }
  
// 或者写成
  extern “C”
  {
  #include “myheader.h”
  // 其它C 头文件
  }

助记:

C++标准 与 C标准的编译区别

在学C++时我们都学过:函数的返回值类型不能作为函数重载的判别标准;

C++支持函数重载的原理是,编译器在编译函数的过程中会将函数的 参数类型+函数名加到编译后的代码中;

而 C只加函数名;

两者都不加返回值。

8、介绍一下什么是C++的宏

  • #define :宏是一种语言命令,它可以将一个标识符定义为一个字符串,然后整个源程序所有的标识符都会以这个字符串来代替。

  • 该标识符被称为宏名,被定义的字符串称为替换文本。

#define 标识符 字符串
  • 该命令有两种格式:一种是不带参数的宏定义,另一种是带参数的宏定义。
//举例
#include<cstdio> 
#include<climits> 
using namespace std; 
 
#define PI  3.1415926
 
int main() 
{
    int i;
    i  = 44;
    i2 = 55;
    double k1 = i * PI;
    //printf("(%m.nf) 打印至少m个字符宽度(包括小数点和小数部分的位数),打印n位小数");//
    
    double k2 = i2 * PI;
 
    printf("%11.7/n", i2);
    printf("%11.7/n", i2);   
    return 0; 
} 

9、C++为什么提倡尽量避免使用宏定义

  • 宏会导致错误替换:

    • 由程序编译的四个过程,知道宏是在预编译阶段被展开的。在预编译阶段是不会进行语法检查、语义分析的,宏被暴力替换,正是因为如此,如果不注意细节,宏的使用很容易出现问题。

    // 例子
    
    #define PI 3.14159 // This is a constant.
    
    double radius = 2.0;
    double area = PI * radius * radius;
    
    cout << area << endl;
    
    

    某些编译器的结果会输出 3.14159;但是有的编译器会报错。因为在输出错误结果的编译器下,上面使用了 PI 的那行被替换成了:

    double area = 3.14159 // This is a constant. * radius * radius
    

    其实这种情况可以用下面的方法解决:

    #define PI 3.14159 /* This is a constant */
    
  • 用 #define 定义字面常量可能会浪费很多空间

    • 比如在代码中使用 #define 定义了一个比较长的常量字符串,如果这个宏被使用了很多次,那么这个字面常量将会遍地开花,如果编译器没有那么聪明的话,可能会耗费很多不需要耗费的空间。
  • 用 #define 定义常量对象可能会执行多次构造函数而降低时间效率

    #define WELCOME_MESSAGE string("Welcome!")
    
    • 如果多次使用 WELCOME_MESSAGE 宏的话,将有可能每次遇到它们的时候都调用 class string 的 string(const char *) 构造函数。这样的话,不仅空间会被浪费,而且也会影响执行效率。
  • #define 定义的“函数”“参数”也只是简单替换

// 示例

#define max(a,b) (a > b ? a : b)

/* 伪代码 */

int x = 5, y = 6;
int n = max(++x, ++y);

// 运行后,n的结果不为 7. 因为那个调用 max 宏的那行被替换成了:

int n = (++x > ++y ? ++x : ++y);

// 所以 n 的值其实为 8。


10、C++中应该怎么办才能替代 #define 的一些功能

  • 对于常数定义:(不带参数的定义)

    • 不带参数的宏命令我们可以用常量 const 来替代,比如 const int PI = 3.1415;这种操作比宏使用更安全,且可以达到同样的效果,因为使用const会让语句在编译阶段时进行语法检查。
  • 对于简单函数定义:(带参数的定义)

    • 可以使用 C++ 的 inline 修饰符或模板来代替,使用这个修饰符,效果上会和一般的函数一样,但是实际上编译器会把函数中的代码根据语义替换到调用函数的地方,所以运行效率不会受到太大影响。

    • 内联函数与宏命令功能相似,是在调用函数的地方,用函数体直接替换。但是内联函数比宏命令安全,因为内联函数的替换发生在编译阶段,同样会进行语法检查、语义分析等,而宏命令发生在预编译阶段,属于暴力替换,并不安全。

11、简单介绍一下 const 和 # define 的区别

  • define可以用于定义常量,也可以用于定义宏 而const只能用于定义常量。

  • 因此区别将围绕着对常量的定义展开:

    • const生效于编译阶段;define生效于预处理阶段。

    • const定义的常量,在C语言中是存储在内存中、需要额外的内存空间的;

    • define定义的常量,并不会存放在内存中。

    • const定义的常量是带类型的;define定义的常量不带类型,因此使用define定义的常量不利于类型检查。

四个阶段:预编译、编译、汇编、链接

12、简单介绍一下const的各种应用

  • const修饰普通类型的变量,告诉编译器某值是保持不变的。const int a

  • const 修饰指针变量,根据const出现的位置和出现的次数分为三种

    • 指向常量的指针:const char* name

      • const指针指向一个常量对象,目的是防止使用该指针来修改指向的值
    • 常指针:char* const name

      • const将指针本身声明为常量,这样可以防止改变指针指向的位置
    • 指向常量的常指针:const char * const name

      • const一个常量指针指向一个常量对象
  • const修饰参数传递,可以分为三种情况。

    • 值传递的 const 修饰传递,一般这种情况不需要 const 修饰 func(const int a)

      • 如果输入参数采用“值传递”,不要加const修饰。因为对于值传递函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护。
    • 引用传递:void test(const int& y)

      • 因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。
    • 当 const 参数为指针时,可以防止指针被意外篡改。func(const int* a)

    • 自定义类型的参数传递:需要临时对象复制参数时,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。

  • const修饰函数返回值,分三种情况。

    • const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。

    • const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用(不能在 等号左边),既不能被赋值,也不能被修改。

    • const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。

  • const修饰成员函数 int func(void) const;

    • const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。

13、简单介绍一下# define 和 inline 的区别

  • 有无类型检测:

    • define是 预编译时处理的宏; 只进行简单的字符替换,无类型检测

    • inline是编译时的内联函数,有类型检查,且编译器有权拒绝内联。

  • inline有传参,这点和普通函数一致,define只是简单的文本替换,这点对于处理变量++的时候区别明显。例如:

#define SQUARE(x) ((x)(x));
y = SQUARE(i++); // y = ((i++) * (i++)); 
  • 处理完后i相当于自加2,实际要的效果是i自加1
inline int Square(x) {return xx}; 
y= Square(i++); //y = i*i; i++; i自加1

代码推荐:剑指Offer(说在**中的题号)

第一天:

  • 剑指 Offer 46

  • 剑指 Offer 48

  • 剑指 Offer 21

第二天:

  • 剑指 Offer 18

  • 剑指 Offer 22

  • 剑指 Offer 57

第三天:

  • 剑指 Offer 25

  • 剑指 Offer 52

  • 剑指 Offer 58-I

相关阅读:

全部评论
表扬了
点赞 回复
分享
发布于 02-05 20:41 浙江

相关推荐

7 46 评论
分享
牛客网
牛客企业服务