C++ 基础知识 面试题(一)

news2024/9/25 21:21:54

1.变量的声明与定义

声明:int x; //告诉编译器这个变量的类型和名称

定义:int x = 0; //告诉编译器这个变量的类型和名称,为该变量分配内存空间,并初始化该变量

主要区别在于是否为变量分配内存空间

2.extern关键字

用法一:在声明时修饰函数或变量,表明该函数或变量定义其他文件中,告诉编译器去其他文件中寻找函数或变量的定义。

extern int a;

extern void func();

当一个变量要在多个文件中使用时,只需在一个文件中定义该变量,然后再其他文件中使用extern声明这个变量。

用法二:extern "C" 

C++会对函数名进行名称修饰(Name Mangling),以便支持函数重载和命名空间等特性。

也就是说,编译器会在编译函数的过程中将函数的参数类型也加到函数名后面。

但是C语言不支持函数重载,编译时不会加上函数的参数类型,一般只有函数名。

所以当我们要在C++中调用C语言的代码时,不能直接按照C++的规则进行名称修饰。

所以可以在前面加上extern "C" ,表明这一段是C语言代码,按照C语言的规则编译。

Example:

C++中直接使用C代码:

// myfunc.cpp
#include <stdio.h>

extern "C" {
    void myFunc() {
        printf("Hello from myFunc!\n");
    }
}

C++中调用C语言头文件:

// main.cpp
#include <iostream>

extern "C" {
    #include "mylib.h"
}

int main() {
    myFunc();
    std::cout << "myVar = " << myVar << std::endl;
    return 0;
}

3.内存类型

栈内存(Stack Memory):栈内存是由编译器自动分配和释放的,用于存储局部变量和函数参数等数据。栈内存的大小是固定的,通常比堆内存小,且分配和释放速度较快。当函数执行完毕时,栈内存中的数据会自动被销毁。

堆内存(Heap Memory):堆内存是由程序员手动分配和释放的,用于存储动态分配的数据。堆内存的大小是不固定的,可以根据需要进行动态调整,但分配和释放速度较慢。需要注意的是,堆内存中的数据需要手动释放,否则会导致内存泄漏。

全局内存(Global Memory):全局内存是由编译器自动分配和释放的,用于存储全局变量和静态变量等数据。全局内存的大小是固定的,程序运行期间始终存在,直到程序结束才会被释放。

常量内存(Constant Memory):常量内存是用于存储常量数据的内存区域,通常包括字符串常量、枚举常量和const修饰的变量等。常量内存的大小是固定的,程序运行期间始终存在,直到程序结束才会被释放。

关于内存类型,还会涉及到自由存储区

自由存储区是一个抽象的概念,指的是C++中通过new()和delete()动态分配和释放的内存区域。

C++编译器默认使用堆来实现自由存储区,但是自由存储区不等价于堆。

堆是操作系统维护的一块动态分配内存,通过malloc()和free()来分配和释放。

4.C++程序编译的过程:

  • 预处理:C++编译器会先对源代码进行预处理,将所有的#include指令替换为对应的头文件内容,宏定义替换为对应的文本等。
  • 编译:编译器将预处理后的源代码翻译成汇编代码,这个过程包括词法分析、语法分析、语义分析等步骤。
  • 汇编:汇编器将汇编代码转换为机器码,这个过程包括生成目标文件、符号解析、重定位等步骤。
  • 链接:连接器将目标文件和库文件链接在一起,生成可执行文件。这个过程包括符号解析、重定位等步骤。

编译过程中生成的文件流程图:

5. malloc和new的区别

语法:C语言中使用malloc函数动态分配内存,而C++中使用new运算符动态分配内存

参数:malloc函数只接受一个参数,即要分配的内存大小,而new运算符可以接受一个参数(分配单个对象内存)或多个参数(分配数组内存)

        eg: Person* arr = new Person[size] { {"Tom", 20}, {"Jerry", 18}, {"Alice", 22} };

返回值:malloc函数返回void*类型的指针,需要进行强制类型转换才能使用;new运算符返回分配的对象类型的指针,不需要进行强制类型转换

内存初始化:malloc函数分配的内存不会被初始化,而new运算符分配的内存会被初始化为默认值(0或空指针)或用户指定的值

异常处理:malloc函数在分配内存失败时返回空指针,new运算符在分配内存失败时会抛出std::bad_alloc异常

6.计算机内部存储负数的方式

最常用的方式是二进制补码表示法。

在二进制补码表示法中,最高位(即最左边的位)为符号位,0表示正数,1表示负数。

例如,十进制数-42的8位二进制表示为11010110。

其计算方法为,先用原码表示-42,即将42的二进制(00101010)表示取反,得到11010101,然后将结果加1,得到11010110,这就是-42的二进制补码表示。

7.计算机内部存储浮点数的方式

计算机内部存储浮点数的方式是采用IEEE 754标准。

在IEEE 754标准中,一个浮点数由三个部分组成:符号位、指数和尾数。

IEEE 754标准定义了两种浮点数表示方式:单精度浮点数(4个字节)和双精度浮点数(8个字节)。

在单精度浮点数中,第1位为符号位,接下来的8位为指数部分,最后的23位为尾数部分。

在双精度浮点数中,第1位为符号位,接下来的11位为指数部分,最后的52位为尾数部分。

对于指数部分,IEEE 754标准采用了偏移值的表示方法。具体来说,指数部分的实际值等于指数字段的值减去一个偏移值。

在单精度浮点数中,偏移值为127,在双精度浮点数中,偏移值为1023。

对于尾数部分,IEEE 754标准采用了规格化和非规格化的表示方法。

规格化的尾数部分表示的是一个范围在1到2之间的实数,非规格化的尾数部分表示的是一个范围在0到1之间的实数。

举例:单精度浮点数

例如,我们要存储十进制数-3.25,其二进制表示为-11.01(整数部分除2取余再翻转,小数部分乘2取整)

根据IEEE 754标准,我们需要将其转换为科学计数法的形式,即-1.101 x 2^1。

其中,符号位为1,指数部分为1+127=128(偏移值为127),尾数部分为10100000000000000000000。

将这三部分按照规定的格式组合起来,就可以得到单精度浮点数的二进制表示:1 10000000 10100000000000000000000。其中,第1位为符号位,接下来的8位为指数部分,最后的23位为尾数部分。

8.内存泄漏

内存泄漏是指程序在运行过程中申请的内存空间没有被及时释放,导致系统中的可用内存不断减少,最终可能导致系统崩溃或性能下降的问题。

以下是一些常用的方法:

  • 使用内存分配和释放函数时要小心,确保申请的内存空间在不需要时及时释放。
  • 避免使用全局变量,因为全局变量的生命周期很长,容易导致内存泄漏。
  • 使用智能指针和垃圾回收机制来管理内存,这样可以自动释放不再使用的内存空间。
  • 使用静态分析工具来检测潜在的内存泄漏问题。
  • 对于C++等语言,可以使用RAII技术,即在对象的构造函数中申请内存,在析构函数中释放内存,从而确保内存的正确释放。
  • 在对指针赋值前,要考虑是否释放指针原先指向的内存。

9.volatil关键字

用于告诉编译器一个变量可能会被意外地修改。

它的作用是防止编译器对变量的优化,保证程序的正确性。

当一个变量被声明为volatile时,编译器将不会对该变量进行优化,例如,不会将该变量的值缓存到CPU寄存器中。这是因为该变量的值可能会被其他线程或硬件设备修改,如果编译器对该变量进行了优化,就可能导致程序出错。

10. final关键字

用于修饰类、成员函数和虚函数。它的作用是禁止继承、重载和覆盖。

当一个类被声明为final时,它不能被其他类继承。

当一个成员函数被声明为final时,它不能被子类重载或覆盖。

当一个虚函数被声明为final时,它不能被子类覆盖。

当一个变量被声明为final时,它只能赋一次值。

当一个引用变量被声明为final时,它只能赋一次值,并且它只能永远指向该对象,无法指向其他对象。虽然final的引用指向对象A后,不能再重新指向对象B,但是对象A内部的数据可以被修改。

final修饰的实例变量一般添加 static修饰。static final联合修饰的变量称为常量。
 

11.多态

静态多态和动态多态都是面向对象编程中的概念,它们都涉及到多态性的实现。

静态多态是指在编译时就能够确定调用的函数,也称为编译时多态。它是通过函数重载和运算符重载实现的,编译器会根据参数的类型和数量来决定调用哪个函数或运算符。静态多态的优点是效率高,因为编译器能够在编译时确定函数调用,不需要在运行时进行类型检查。

动态多态是指在运行时才能够确定调用的函数,也称为运行时多态。

它允许同一个函数名在不同的对象中具有不同的行为。多态的实现原理是通过虚函数来实现的。

在C++中,通过在函数声明前面添加关键字virtual来将函数声明为虚函数。当一个类中的函数被声明为虚函数时,它可以被子类重写。

使用虚函数可以使程序更加灵活,可以根据不同的对象类型调用不同的函数实现。

编译器会在基类中为虚函数生成一个虚函数表,派生类会继承这个虚函数表并重写其中的虚函数。虚函数的实现依赖于虚函数表,每个对象都有一个指向虚函数表的指针,通过该指针可以在运行时确定应该调用哪个函数。

通过基类指针或引用调用虚函数(普通变量不会调用虚函数)。在运行时,虚函数会根据对象的实际类型来调用相应的函数,从而实现多态。

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void speak() {
        cout << "This is an animal speaking." << endl;
    }
};

class Cat : public Animal {
public:
    void speak() {
        cout << "Meow!" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        cout << "Woof!" << endl;
    }
};

int main() {
    Animal* animal1 = new Cat();
    Animal* animal2 = new Dog();
    animal1->speak();// it will print "Meow!"
    animal2->speak();// it will print "Woof!"
    return 0;
}

12.引用和指针的区别

  • 指针可以为空,而引用不能。指针可以指向空值,即nullptr,而引用必须引用一个有效的对象。
  • 指针可以进行算术运算,而引用不能。指针可以进行加、减等运算,而引用是引用的变量值加一。
  • 指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
  • 引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且可以被重新赋值。
  • 有多级指针,但是没有多级引用,只能有一级引用。
  • sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。
  • 引用在内部实现上是一个指针,但是它在使用时,像一个变量一样使用。这使得使用引用更加方便和直观。
  • 作为参数时也不同,传指针的实质是传值,传递的值是指针的地址;传引用的实质是传地址,传递的是变量的地址。

13.函数重写和重载的区别

函数重写(Overriding):在继承关系中,子类可以对父类的某个方法进行重写,以满足自己的需求。子类重写父类的方法时,方法名、参数列表和返回值类型必须与父类的方法相同,但是方法体可以不同。在运行时,如果调用的是子类对象的该方法,那么就会执行子类中的方法体,而不是父类中的方法体。

在C++中,如果想要在派生类中重写基类的函数,通常需要在基类的函数声明中添加virtual关键字,这样才能实现运行时多态。如果基类函数没有使用virtual关键字,那么在派生类中重写该函数只会隐藏基类的同名函数,而不会实现多态

函数重载(Overloading):在同一个类中,可以定义多个同名的方法,但是它们的参数列表必须不同。参数列表可以包括参数的类型、个数、顺序等。在调用该方法时,编译器会根据传入的参数类型和数量来选择合适的方法进行调用。

总的来说,函数重写是子类对父类某个方法的重新实现,而函数重载是同一个类中对同名方法的多次定义。

14.面向对象编程(Object-Oriented Programming,简称 OOP)具有以下三大特征:

1) 封装(Encapsulation):
   - 封装是将数据和操作数据的方法(即类的成员变量和方法)组合在一起,形成一个称为类的实体。
   - 封装隐藏了数据的具体实现细节,只提供公共接口供外部访问和操作数据,也增加了对数据和方法的访问控制(private, protected, public)
   - 通过封装,可以实现数据的安全性和灵活性,使得代码更加模块化和可维护。

2) 继承(Inheritance):
   - 继承是指一个类(称为子类或派生类)可以从另一个类(称为父类或基类)继承属性和方法。
   - 继承使得子类可以重用父类的代码,避免了重复编写相同的代码。
   - 子类可以扩展或修改从父类继承的属性和方法,也可以添加自己特有的属性和方法。
   - 继承提供了代码的层次化组织,使得代码更加可扩展和可维护。

3) 多态(Polymorphism):
   - 多态是指同一个方法名可以在不同的对象上具有不同的实现方式。
   - 多态通过方法的重写和方法的重载实现。
   - 方法的重写(Override)指子类重写父类的方法,使得子类对象调用该方法时执行子类的实现逻辑。
   - 方法的重载(Overload)指在同一个类中定义多个方法,它们具有相同的方法名但不同的参数列表。
   - 多态提高了代码的灵活性和可扩展性,使得同一段代码可以适用于不同类型的对象。

15.空类

在 C++ 中,空类(Empty Class)指的是没有显式声明任何成员变量或成员函数的类。它是一种最简单的类定义形式,没有任何数据成员或成员函数的定义。空类可以用于一些特定的编程场景,例如作为基类或占位符

下面是一个示例,展示了一个空类的定义:

```cpp
class EmptyClass {
    // Empty class with no member variables or member functions
};
```

在这个示例中,`EmptyClass` 是一个空类,没有任何成员变量或成员函数的定义。

空类的主要用途之一是作为基类,用于派生其他类。通过继承空类,子类可以继承空类的特性,并添加自己的成员变量和成员函数。这种用法在实现多态和组织代码结构时很常见。

另外,空类也可以用作占位符,作为一种标记或占位的作用。例如,在某些设计模式中,可以使用空类作为标记类来表示某个特定的概念或行为。

需要注意的是,即使是空类,编译器在没有显式定义成员函数时,仍会为其生一些默认的成员函数。这些默认生成的成员函数包括:

1. 默认构造函数(Default Constructor):
   - 默认构造函数是没有参数的构造函数。
   - 如果你没有显式定义任何构造函数,编译器会自动生成一个默认构造函数。
   - 默认构造函数用于创建类的对象,并初始化其成员变量。

2. 默认析构函数(Default Destructor):
   - 默认析构函数没有参数。
   - 如果你没有显式定义任何析构函数,编译器会自动生成一个默认析构函数。
   - 默认析构函数用于在对象被销毁时清理资源。

3. 默认拷贝构造函数(Default Copy Constructor):
   - 默认拷贝构造函数用于创建一个新对象,并将其初始化为另一个同类型对象的副本。
   - 如果你没有显式定义任何拷贝构造函数,编译器会自动生成一个默认拷贝构造函数。
   - 默认拷贝构造函数执行逐个成员变量的复制。

4. 默认赋值运算符(Default Assignment Operator):
   - 默认赋值运算符用于将一个对象的值赋给另一个同类型的对象。
   - 如果你没有显式定义任何赋值运算符,编译器会自动生成一个默认赋值运算符。
   - 默认赋值运算符执行逐个成员变量的赋值。

需要注意的是,只有当实际使用这些函数的时候,编译器才会去定义它们。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/709884.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache IoTDB 论文入选数据库领域顶级学术会议 ACM SIGMOD

6 月 18-23 日&#xff0c;ACM SIGMOD 会议在美国西雅图举办。Apache IoTDB 的研究成果论文《Apache IoTDB: A Time Series Database for IoT Applications》在大会做了报告&#xff0c;并进行了 Poster 展示。 01 关于 SIGMOD SIGMOD 数据管理国际会议&#xff08;Special Int…

嵌入式系统BSP开发(二)

快递拿到R16的开发板后&#xff0c;通过官方拿到SOCHIP的相关资料&#xff0c;压缩包的名称是lichee.tar.gz 一&#xff0c;解压相关的资料 tar xzvf r16_lichee.tar.gz 解压后得到的资料如下&#xff1a; yveyve:/data/home/yve/Linux/lichee$ ls brandy buildroot build…

计算物理专题:傅里叶变换与快速傅里叶变换

计算物理专题&#xff1a;傅里叶变换与快速傅里叶变换 傅里叶变换提供一个全新的角度去观察和描述问题&#xff0c;如在量子力学中&#xff0c;动量与坐标表象之间的变换就是傅里叶变换。傅里叶变换同意可以用在数据处理等领域。1965年&#xff0c;Cooley 和 Tukey 提出了快速傅…

redis之主从复制、哨兵、集群

文章目录 一、redis的高可用1.1 redis高可用的概念1.2 Redis的高可用技术 二、redis 主从复制2.1主从复制的原理2.2搭建Redis 主从复制 三、Redis 哨兵模式3.1搭建Redis 哨兵模式3.2启动哨兵模式3.3查看哨兵信息3.4故障模拟 四、Redis 群集模式4.1搭建Redis 群集模式 一、redis…

数据结构--串的定义和基本操作

数据结构–串的定义和基本操作 注:数据结构三要素――逻辑结构、数据的运算、存储结构&#xff08;物理结构) 存储结构不同&#xff0c;运算的实现方式不同 \color{pink}存储结构不同&#xff0c;运算的实现方式不同 存储结构不同&#xff0c;运算的实现方式不同 串的定义 串 …

用Java制作简单的记事本

目录 前言 主界面设计 功能实现 打开 另存为 保存 查找 替换 成员变量 其他方法 警告弹窗 不移动光标更新文本框内容 源代码 总结 转载请注明出处&#xff0c;尊重作者劳动成果。 前言 考完试想写敲一下代码就写了一下这个程序&#xff0c;整个也是写了怎么久…

JavaEE语法第二章之多线程(初级一)

一、认识线程 1.1线程的概念 一个线程就是一个 "执行流"。每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时"执行着多份代码。 一家公司要去银行办理业务&#xff0c;既要进行财务转账&#xff0c;又要进行福利发放&#xff0c;还得进行缴…

Docker常见问题集合

一、Docker安装 1、yum 安装 1&#xff09;更新yum包到最新 yum update2&#xff09;安装软件需要的软件&#xff0c;yum-util&#xff08;提供 yum-config-manager 功能&#xff09;&#xff0c;device-mapper-persistent-data、lvm2&#xff08;devicemapper 驱动依赖&…

mmdetection踩坑记录

1.mmcv-full和mmdetection的版本匹配问题 Readme里应该会给可复现的版本&#xff0c;一定要按照readme里的&#xff0c;这里是一些版本对应关系&#xff0c;像我的mmdet是2.3.0&#xff0c;我就只能装1.0.5的mmcv-full 表格来源&#xff1a;https://blog.csdn.net/qq_55957975/…

高频-测试岗面试题,软件测试面试常问面试题(付答案)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试流程&#xf…

【Zynq】Xilinx SDK设置编码方式

举例&#xff1a;将Xilinx SDK设置为UTF-8编码 工具栏->Window->Preferences

基于Tensorflow和Keras实现卷积神经网络CNN并进行猫狗识别

文章目录 一、环境配置1、安装Anaconda2、配置TensorFlow、Keras 二、猫狗数据集分类建模3.1 猫狗图像预处理3.2 猫狗分类的实例——基准模型3.1 构建神经网络3.2 配置优化器3.3 图片格式转化3.4 训练模型3.5 保存模型3.6 可视化 三、数据增强四、dropout 层五、参考资料 一、环…

Openresty原理概念篇(十五)Lua 规则和 NGINX 配置文件产生冲突怎么办?

一 Lua 规则和 NGINX 配置文件产生冲突怎么办? ① OpenResty 的名字和语言 说明&#xff1a; 了解openresty的发展史 ② 配置文件的规则优先级 1) 如何各司其职2) 都能满足功能,该如何取舍 理解&#xff1a; 1) rewrite ... break 到POST_WRITE阶段2) 而rewrite_by_lua*…

JAVA的DIFF算法

首先看一下我的文件结构 1.EnumType 类 public enum EnumType {ADD("ADD"),MODIFIED("MODIFIED"), DELETED("DELETED");//创建私有变量private String type;EnumType(String type) {this.type type;} }2.OperationType类 public class Operati…

vue封装svg组件来修改svg图片颜色

文章目录 1、引入依赖2、根目录的vue.config.js配置3、在组件文件夹(compontents)中创建svgIcon.vue4、在src目录下创建icons文件5、处理svg格式的图片6、在main.js文件中引入icons文件中的index.js文件7、使用8、效果图1、项目成功运行后的样子2、直接在html上添加样式&#x…

DEBUG系列三:使用 F9 和 watch point

首先是我随便找了个报错。 报销消息号信息&#xff1a; No pricing procedure could be determined Message No. V1212 1&#xff09;首先可以直接SE91 来追溯这个消息号哪儿报出来的 可以看到下面两个地方可能会报这个消息&#xff0c;可以直接在这两个地方打断点&#xff0c;…

开发一个RISC-V上的操作系统(一)—— 环境搭建

在前面我们使用Verilog实现了一个简易的RISC-V处理器&#xff0c;并且能烧录到板子上跑一些简单C程序&#xff0c;传送门&#xff1a; RISC-V处理器的设计与实现&#xff08;一&#xff09;—— 基本指令集_risc_v处理器_Patarw_Li的博客-CSDN博客 RISC-V处理器的设计与实现&…

电子器件系列41:扁平高压电阻

这种电阻和其他的高压电阻不同&#xff0c;不是绕线电阻而是陶瓷电阻 找到一个大神&#xff0c;他的专栏也得很详细了&#xff0c;贴在这里 https://blog.csdn.net/wkezheng/category_12059870.html 阻容感基础03&#xff1a;电阻器分类&#xff08;1&#xff09;-片式电阻器…

如何快速判断是否在容器环境

在渗透测试过程中&#xff0c;我们的起始攻击点可能在一台虚拟机里或是一个Docker环境里&#xff0c;甚至可能是在K8s集群环境的一个pod里&#xff0c;我们应该如何快速判断当前是否在容器环境中运行呢&#xff1f; 当拿到shell权限&#xff0c;看到数字和字母随机生成的主机名…