C++面试高频(一)

1.new和malloc的区别(使用和原理)

new的定义:

new是C++的关键字,用于动态分配内存并创建对象。它可以根据类型自动计算所需内存空间,并调用对象的构造函数进行初始化。在使用new分配内存后,需要使用delete来释放这些内存空间,以防止内存泄漏。

malloc的定义:

malloc是C语言的库函数,用于动态分配一块指定大小的内存块,并返回其地址。需要注意的是,使用malloc分配内存后,需要使用free来释放这些内存空间,以防止内存泄漏。

new与malloc的区别:(简单理解)

语法:new是C++的关键字,而malloc是C语言的库函数。

类型安全:new操作符会根据类型自动计算所需内存大小,并进行类型匹配,返回的是对象类型指针;而malloc需要手动计算内存大小,并使用强制类型转换,返回的是void指针。

构造函数与析构函数的调用:new会自动调用对象的构造函数进行初始化,而malloc不会调用构造函数,得到的内存空间内容是未初始化的。

内存泄漏的检测:new可以通过异常机制检测内存分配失败,而malloc在分配失败时返回NULL,需要手动检查。

重载和自定义类型:new操作符可以重载,并能够与自定义类型的构造函数和析构函数配合使用;而malloc是库函数,不会调用自定义类型的构造和析构函数

new与malloc使用区别

#include <iostream>
#include <cstdlib>

int main() {
    // 使用new进行动态内存分配和释放
    int* newPtr = new int(10);
    std::cout << "Value allocated with new: " << *newPtr << std::endl;
    delete newPtr;

    // 使用malloc进行内存分配和释放
    int* mallocPtr = (int*)malloc(sizeof(int));
    if (mallocPtr != nullptr) {
        *mallocPtr = 20;
        std::cout << "Value allocated with malloc: " << *mallocPtr << std::endl;
        free(mallocPtr);
    }

    return 0;
}

2.struct和class的区别

  1. 默认访问权限:struct中的成员默认为公共(public),而class中的成员默认为私有(private)。
  2. 默认继承方式:struct中的继承方式默认为公共(public),class中的继承方式默认为私有(private)。
  3. 使用习惯:struct适合用于简单的数据结构,class适合用于复杂的数据类型和实现面向对象编程。
  4. 成员变量和成员函数:struct中的成员变量和成员函数默认为公共,而class中的成员变量和成员函数默认为私有。
  5. 访问控制:struct中的成员在外部可直接访问,而class中的成员需要使用公共的成员函数来访问。
  6. 默认的构造函数和析构函数:class中会自动生成默认的构造函数和析构函数,而struct中不会。

3.char和int之间的转换

  1. 将char转换为int:可以直接将char类型的变量赋值给int类型的变量,将字符对应的ASCII码值赋给int变量。
char c = 'A';
int i = c;  // 将字符'A'的ASCII码值赋给i

2.将int转换为char:可以使用强制类型转换 (static_cast<char>) 将int类型的变量转换为char类型的变量,该方法只会截取int变量的低位字节作为字符。

int i = 65;
char c = static_cast<char>(i);  // 将整数65转换为对应的字符'A'

需要注意的是,对于转换为char的int值,如果超出了char类型的范围(-128至127),将会发生溢出,只保留最低位字节的值。

4.什么是野指针和悬挂指针

野指针(Dangling Pointer):指的是没有初始化过的指针,它指向的地址是未知的、不确定的、随机的。

产生野指针的原因主要是指针未初始化,防止的措施就是指针初始化(包括及时初始化或置空)。

举例:

int main() {
    int* ptr; // 未初始化的指针,成为野指针

    // 使用野指针会导致未定义的行为
    *ptr = 5; // 解引用野指针,可能导致程序崩溃

    return 0;
}

悬挂指针(Dangling Reference):指针最初指向的内存已经被释放了的一种指针。指针指向的内存已释放,但指针的值没有被清零,对悬空指针操作的结果不可预知

示例代码:

int* createInt() {
    int value = 5;
    int* ptr = &value;
    return ptr; // 返回指向局部变量的指针
}

int main() {
    int* danglingPtr = createInt(); // 指向已释放的内存

    // 对悬挂指针操作的结果不可预知
    int value = *danglingPtr; // 解引用悬挂指针,可能导致未定义的行为

    return 0;
}

5.NULL和nullptr区别

类型不同:NULL是宏定义或整数值0,而nullptr是C++11引入的关键字,表示空指针。

安全性不同:NULL可能导致函数调用二义性问题,nullptr更安全,不会被错误解释为整型。

上下文匹配不同:NULL可以用于整型类型的上下文,nullptr只能用于指针类型的上下文。

6.指针常量和常量指针有何区别

指针常量(Pointer to a constant):指针常量是指指针本身是不可更改的,即指针变量自身的值不能被修改,但指向的值可以修改。声明指针常量时,需要在指针类型前加上 const 修饰符。例如:

int* const ptr;  // ptr 是指向常量的指针,指向的值不能通过 ptr 修改,但可以通过其它方式修改

在这个例子中,ptr 是一个指向 int 类型常量的指针,它的值不能被修改,但可以通过其他途径修改指向的值。

常量指针(Constant pointer):常量指针是指指针指向的地址不能更改,即指针变量指向的地址不能被修改,但可以通过指针修改指向的值。常量指针在声明时需要使用 const 修饰符将指针指向的类型声明为常量。例如:

int x = 5;
 const int * ptr = &x;  // ptr 是常量指针,指向的地址不能通过 ptr 修改,但可以通过 *ptr 修改值


ptr是常量指针,不可以修改ptr指向地址的值,可以修改ptr 指向的地址。

总结:

  • 指针常量表示指针本身的值不能修改,但可以通过指针修改指向的值。
  • 常量指针表示指针指向的地址不能修改,但可以通过指针修改指向地址的值

7.虚拟内存和物理内存的区别

虚拟内存(Virtual Memory)和物理内存(Physical Memory)是计算机系统中存储和管理数据的两个概念:

  • 物理内存是计算机中的实际硬件内存,由RAM芯片组成。
  • 虚拟内存是对物理内存的扩展,使用磁盘空间来模拟更大的内存容量。

它们之间的区别包括:

  • 大小:物理内存的容量是固定的,而虚拟内存的大小可以超过物理内存的容量。
  • 访问速度:物理内存的访问速度较快,而虚拟内存的访问速度相对较慢,因为它需要与磁盘进行交互。
  • 地址空间:物理内存使用物理地址进行访问,而虚拟内存使用虚拟地址,通过内存管理单元(MMU)映射到物理内存。
  • 管理方式:物理内存的管理相对简单,而虚拟内存的管理涉及页表和页面置换等技术。
  • 可用空间:物理内存的可用空间有限,而虚拟内存可以提供更大的可用空间,因为它可以使用磁盘空间作为扩展。

总之,虚拟内存扩展了物理内存的容量,并提供了更灵活的内存管理机制,但付出的代价是访问速度较慢。

8.重载、重写和隐藏的区别⭐

重载(Overloading):

  • 重载是在同一个作用域内定义多个相同名称但参数列表不同的函数或方法。
  • 重载函数可以根据不同的参数数量或类型来执行不同的操作。
  • 重载通过函数名和参数列表来区分不同的函数。

举例代码:

#include <iostream>

void printNumber(int num) {
    std::cout << "Integer number: " << num << std::endl;
}

void printNumber(double num) {
    std::cout << "Floating-point number: " << num << std::endl;
}

int main() {
    printNumber(10);
    printNumber(3.14);
    return 0;
}

重写(Override):

  • 重写是指子类重新定义从父类继承的虚函数,使其具有不同的实现。
  • 重写的函数签名(函数名、参数列表和返回类型)必须与被重写函数相同。
  • 在运行时,根据具体的对象类型,调用的是子类重写的版本。

举例代码:

#include <iostream>

class Base {
public:
    virtual void sayHello() {
        std::cout << "Hello from Base class!" << std::endl;
    }
};

class Derived : public Base {
public:
    void sayHello() override {  // 使用 override 关键字表明重写了父类的函数
        std::cout << "Hello from Derived class!" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->sayHello();  // Output: "Hello from Derived class!"
    delete basePtr;
    return 0;
}

隐藏(Hiding):

  • 隐藏是指在派生类中定义与父类具有相同名称的成员函数,使其隐藏父类中的同名函数。
  • 隐藏函数与父类的函数没有多态性,只有通过对象的实际类型调用时才会调用相应的函数。

举例代码:

#include <iostream>

class Base {
public:
    void sayHello() {
        std::cout << "Hello from Base class!" << std::endl;
    }
};

class Derived : public Base {
public:
    void sayHello() {
        std::cout << "Hello from Derived class!" << std::endl;
    }
};

int main() {
    Base baseObj;
    Derived derivedObj;
    
    baseObj.sayHello();    // Output: "Hello from Base class!"
    derivedObj.sayHello(); // Output: "Hello from Derived class!"
    
    Base* basePtr = new Derived();
    basePtr->sayHello();   // Output: "Hello from Base class!"
    
    delete basePtr;
    return 0;
}

9.简述面向对象的三大特性

面向对象编程(OOP)的三大特性是封装、继承和多态。下面对每个特性进行简要说明:

封装(Encapsulation):

  • 封装是将数据和操作封装在一个单元(类)中的机制。
  • 通过封装,实现类的成员变量和成员函数作为一个整体进行管理和操作。
  • 封装隐藏了数据的具体实现细节,只暴露出必要的接口,提供了更好的安全性和可维护性。
  • 通过访问修饰符(公有、私有、保护),控制对类的成员的访问权限。

继承(Inheritance):

  • 继承是通过创建派生类来扩展和重用已有类的机制。
  • 基类(父类)是已经定义的类,派生类(子类)继承了基类的属性和方法。
  • 子类可以自定义新的属性和方法,也可以覆盖或扩展继承的父类的属性和方法。
  • 继承实现了类与类之间的关系,实现了代码的重用和扩展。

多态(Polymorphism):

  • 多态是指同一个接口可以由不同的对象以不同的方式进行实现和响应的能力。
  • 多态允许使用基类的指针或引用来引用派生类的对象,实现了多种形态的使用。
  • 编译时多态使用函数重载和运算符重载;运行时多态通过虚函数实现(动态绑定)。
  • 多态提高了代码的灵活性和可扩展性,使得程序更具有可读性和可维护性。

10.什么是多态?

利用虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。 基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。

代码举例:

#include <iostream>

class Base {
public:
    virtual void print() {
        std::cout << "This is the Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "This is the Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr;
    
    Base baseObj;
    Derived derivedObj;
    
    basePtr = &baseObj;
    basePtr->print();  // 此时使用基类的成员函数来打印消息
    
    basePtr = &derivedObj;
    basePtr->print();  // 此时使用派生类的成员函数来打印消息
    
    return 0;
}

#晒一晒我的offer##24届软开秋招面试经验大赏##我发现了面试通关密码##如何判断面试是否凉了#
c++/嵌入式面经专栏 文章被收录于专栏

本人2022年毕业于山东大学,目前就职intel。打算把之前校招时做的笔记通过专栏发出来,本专栏适合于C/C++、嵌入式方向就业的同学,本篇面经总结数千篇面经的知识集合,实时更新全网最新的嵌入式/C++最新内容,囊括了C语言、C++、操作系统、计算机网络、嵌入式、算法与数据结构、数据库等一系列知识点,在我看来这些是求职者在面试中必须掌握的知识点。最后呢祝各位能找到自己合适的工作。

全部评论
小伙伴们可以一键三连持续更新
9 回复 分享
发布于 2023-10-06 17:42 山东
指针常量和常量指针那里有点乱,有些反了
2 回复 分享
发布于 10-08 14:00 广东
大佬的代码解释感觉很直观,个人可能更喜欢从代码中理解知识点
1 回复 分享
发布于 2023-10-07 10:50 北京
野指针和悬空指针的概念是不是混淆了
1 回复 分享
发布于 2023-10-07 12:52 上海
真的不错
点赞 回复 分享
发布于 2023-10-06 18:01 湖北
非常感谢各位同学的订阅,有什么问题可以在评论区留言
点赞 回复 分享
发布于 2023-10-07 10:05 北京
大佬总结的很全
点赞 回复 分享
发布于 2023-10-07 10:22 北京
指针常量和常量指针 那个地方 代码给反了, const int * p 是常量指针
点赞 回复 分享
发布于 2023-10-20 10:18 辽宁
const int*p说错了吧,应该是*p的值不可改变,但是p的值可以改变
点赞 回复 分享
发布于 2023-10-23 10:22 安徽
问一下是在自己的编译器上写算法吗
点赞 回复 分享
发布于 2023-10-30 07:46 山东
函数的返回类型也算在函数签名里面吗
点赞 回复 分享
发布于 2023-10-30 18:59 广西
顶不住了 好累啊
点赞 回复 分享
发布于 01-04 22:35 江苏
打卡
点赞 回复 分享
发布于 01-05 11:18 江苏
c专家编程上写的,并没有指针常量的概念,指针常量就是地址常量像0x112233这种,对于程序员来说没有意义。常量指针、指向常量的指针才是作者你说的那两个概念吧......
点赞 回复 分享
发布于 03-20 20:21 江苏
C++primer上的常量指针是int *const,指向常量的指针是const int*,定义好像和网上的定义是反的,叫顶层const和底层const歧义好像小一些
点赞 回复 分享
发布于 10-21 12:07 四川

相关推荐

无情咸鱼王的秋招日记之薛定谔的Offer:好拒信,偷了,希望有机会用到
点赞 评论 收藏
分享
53 273 评论
分享
牛客网
牛客企业服务