27、必考 | C/C++ 最常见50道面试题

@[toc]

面试题 1:深入探讨变量的声明与定义的区别

在编程中,变量的声明指的是告知编译器变量的名称和类型,但不分配内存空间。声明可以多次,常见于头文件中,用于模块间的接口声明。使用extern关键字声明的变量,意味着其定义在别处,通常在另一个文件中。

相对地,定义则是创建一个具有存储空间的变量实例。定义只能有一次,通常在源文件中,确保为变量分配内存空间。例如,全局变量和局部变量的定义就是分配内存并初始化的过程。

面试题 2:编写比较“零值”的if语句

在JavaScript中,对基本数据类型与“零值”的比较可以通过以下if语句实现:

// 对于布尔型数据:
if (flag) {
    // A: 执行当flag为true时的操作
} else {
    // B: 执行当flag为false时的操作
}

// 对于整数型数据:
if (0 !== flag) {
    // A: 执行当flag非零时的操作
} else {
    // B: 执行当flag为零时的操作
}

// 对于指针型数据:
if (NULL === flag) {
    // A: 执行当flag为空指针时的操作
} else {
    // B: 执行当flag非空指针时的操作
}

// 对于浮点型数据:
if ((flag >= -NORM) && (flag <= NORM)) {
    // A: 执行当flag在正常范围内时的操作
} else {
    // B: 执行当flag超出正常范围时的操作
}

注意,为避免潜在的赋值错误,应将“零值”置于比较操作的左侧。

面试题 3:深入理解sizeofstrlen的差异

sizeof是一个编译时确定的运算符,可以用于获取变量或类型在内存中占用的字节数。它在编译阶段就已确定,不依赖于运行时数据。

相对地,strlen是一个运行时确定的库函数,专门用于计算以空字符\0结尾的字符串的实际字符数。由于它需要遍历字符串,因此其结果仅在运行时才可知。

面试题 4:解析C与C++中static关键字的不同用途

在C语言中,static用于修饰局部静态变量(延长生命周期至程序结束)、外部静态变量(限制链接至其他文件)和静态函数(限制函数的作用域至定义它的文件内)。

在C++中,static除了上述功能外,还用于类中定义静态成员变量和静态成员函数。静态成员属于整个类,而非单个对象,常用于计数器或共享数据的存储。

面试题 5:比较C语言的malloc与C++的new

mallocfree是C标准库函数,用于动态内存的分配与释放。malloc分配内存但不初始化,free仅释放内存。

newdelete是C++操作符,用于对象的动态创建与销毁。new分配并初始化内存,delete释放内存并调用析构函数。new返回具体类型的指针,而malloc返回void指针。

面试题 6:实现一个“标准”的MIN

#define MIN(a, b) ((a) <= (b) ? (a) : (b))

使用时应注意宏的副作用,特别是在复杂的表达式中,可能会因宏展开导致意外行为。

面试题 7:指针是否可以是volatile

是的,指针可以是volatile,这表明指针指向的值可能会在程序的控制之外改变,如在中断服务程序中。

面试题 8:探讨a&a的区别

#include <stdio.h>
int main() {
    int a[] = {1, 2, 3, 4, 5};
    int *ptr = (int *)(&a + 1);
    printf("%d, %d", *(a + 1), *(ptr - 1));
    return 0;
}

输出结果为2, 5a作为数组名,代表数组首地址;&a取地址操作后,再强制类型转换为int*,指向数组之后的内存位置。

面试题 9:详述C/C++程序编译时的内存分配

C/C++程序内存分配包括:

  1. 静态存储区:存储全局变量、静态变量、常量。
  2. 栈区:存储函数局部变量、函数参数。
  3. 堆区:通过malloc/new分配,由程序员管理。

面试题 10:区分strcpysprintfmemcpy

  • strcpy用于字符串复制。
  • sprintf用于格式化输出到字符串。
  • memcpy用于内存块复制,不仅限于字符串。

面试题 11:设置特定地址的整型变量值

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;

这个例子展示了如何通过强制类型转换将整型数据转换为指针,并设置其值。

面试题 12:面向对象的三大特征

  • 封装性:数据和方法的保护。
  • 继承性:代码重用和扩展。
  • 多态性:接口的统一和实现的多样性。

面试题 13:探讨C++中的空类及其成员函数

在C++中,一个空类默认包含以下成员函数:

  • 缺省构造函数:自动生成,用于创建类的新实例。
  • 缺省拷贝构造函数:在对象之间进行浅拷贝。
  • 缺省析构函数:在对象生命周期结束时自动调用。
  • 缺省赋值运算符:用于对象间的赋值操作。
  • 缺省取址运算符:允许获取对象的地址。
  • 缺省取址运算符 const:常量版本的取址运算符,保证对象不会被修改。

值得注意的是,这些成员函数仅在实际使用时才会由编译器定义。此外,深入理解这些函数的默认行为对于优化类设计至关重要。

面试题 14:拷贝构造函数与赋值运算符的深入分析

拷贝构造函数和赋值运算符在类的操作中扮演着不同角色:

  1. 拷贝构造函数:用于生成新的类对象实例,不需要检查源对象与目标对象是否相同,因为它总是创建新实例。
  2. 赋值运算符:用于将一个对象的状态复制到另一个已经存在的对象。在赋值前,需要检查自赋值,并妥善处理内存释放等问题。

特别地,当类包含指针成员时,为了管理内存,避免内存泄漏,通常需要重写这两个函数,而不是依赖编译器提供的默认实现。

面试题 15:设计一个不允许继承的C++类

以下是一个使用模板和友元声明来阻止类继承的C++类示例:

template <typename T> class A {
    friend T; // 允许T访问私有成员
private:
    A() {}
    ~A() {}
};

class B : virtual public A<B> {
public:
    B() {}
    ~B() {}
};

class C : virtual public B {
public:
    C() {}
    ~C() {}
};

int main() {
    B b; // C c; // 这将导致编译错误
    return 0;
}

通过将构造函数和析构函数声明为私有,可以阻止类被继承。这种设计模式在需要控制类使用场景时非常有用。

面试题 16:访问基类的私有虚函数

以下程序展示了如何通过特定技巧调用基类的私有虚函数:

#include <iostream>
class A {
public:
    virtual void g() {
        std::cout << "A::g" << std::endl;
    }
private:
    virtual void f() {
        std::cout << "A::f" << std::endl;
    }
};

class B : public A {
public:
    void g() {
        std::cout << "B::g" << std::endl;
    }
    virtual void h() {
        std::cout << "B::h" << std::endl;
    }
};

typedef void (*Fun)();
void main() {
    B b;
    Fun pFun;
    for (int i = 0; i < 3; i++) {
        pFun = (Fun)*((int*)*((int*)&b) + i);
        pFun();
    }
}

输出结果为:

B::g
A::f
B::h

这个示例展示了虚函数表的工作原理和多态性的重要性。

面试题 17:类成员函数的重写、重载和隐藏的区别

  • 重写:发生在派生类与基类之间,要求基类函数必须有virtual修饰符,参数列表必须一致。
  • 重载:发生在同一个类中,参数列表必须不同,与virtual修饰符无关。
  • 隐藏:发生在派生类与基类之间,参数列表可以相同也可以不同,但函数名必须相同。如果参数不同,即使基类函数有virtual修饰,也会发生隐藏而非重写。

重载和覆盖是实现多态性的基础,但它们的技术实现和目的完全不同。

面试题 18:多态实现的原理

多态的实现依赖于虚函数表(vtable)和虚函数指针(vptr)。当类中存在虚函数时,编译器会为此类生成vtable,并在构造函数中将vptr指向相应的vtable。这样,通过this指针就可以访问到正确的vtable,实现动态绑定和多态。

面试题 19:链表与数组的比较

链表和数组在数据结构中有以下区别:

  1. 存储形式:数组使用连续内存空间,链表使用非连续的动态内存空间。
  2. 数据查找:数组支持快速查找,链表需要顺序检索。
  3. 数据插入或删除:链表支持快速的插入和删除操作,数组可能需要大量数据移动。
  4. 越界问题:链表没有越界问题,数组存在越界风险。

选择合适的数据结构取决于具体需求。

面试题 20:单链表反序的实现

单链表反序可以通过以下两种方法实现:

  1. 循环算法
List reverse(List n) {
    if (!n) return n;
    List cur = n.next, pre = n, tmp;
    pre.next = null;
    while (cur != null) {
        tmp = cur;
        cur = cur.next;
        tmp.next = pre;
        pre = tmp;
    }
    return pre;
}
  1. 递归算法
List* reverse(List* oldList, List* newHead = NULL) {
    if (oldList == NULL) return newHead;
    List* next = oldList->next;
    oldList->next = newHead;
    newHead = oldList;
    return (next == NULL) ? newHead : reverse(next, newHead);
}

循环算法直观易懂,递归算法则需要对循环算法有深刻理解。

面试题 21:深入分析队列和栈的异同及其内存分配

队列和栈作为两种基本的线性数据结构,在数据处理流程中扮演着重要角色。它们的主要区别在于数据的存取原则:队列遵循“先进先出”(FIFO)原则,而栈则采用“后进先出”(LIFO)原则。这种差异导致它们在实际应用场景中的使用方式也不尽相同。

在内存管理方面,需要区分程序内存中的“栈区”和“堆区”。栈区由编译器自动管理,用于存储函数调用时的局部变量和参数,其存取方式与数据结构中的栈相似。相对地,堆区的内存分配和释放通常由程序员控制,如果程序员不释放,可能需要等到程序结束时由操作系统回收。堆的内存分配方式与链表类似,但与数据结构中的“堆”不同。

面试题 22:实现队列功能的经典栈操作

通过两个栈实现队列功能是一种经典的数据结构应用。以下是使用C语言实现的示例代码,展示了如何通过两个栈进行队列操作:

// 节点结构体定义
typedef struct node {
    int data;
    struct node *next;
} node, *LinkStack;

// 创建空栈
LinkStack CreateNULLStack(LinkStack *S) {
    *S = (LinkStack)malloc(sizeof(node));
    if (*S == NULL) {
        printf("Failed to malloc a new node.\n");
        return NULL;
    }
    (*S)->data = 0;
    (*S)->next = NULL;
    return *S;
}

// 栈的插入函数
LinkStack Push(LinkStack *S, int data) {
    if (*S == NULL) {
        printf("No node in stack!\n");
        return *S;
    }
    LinkStack p = (LinkStack)malloc(sizeof(node));
    if (p == NULL) {
        printf("Failed to malloc a new node.\n");
        return *S;
    }
    p->data = data;
    p->next = (*S)->next;
    (*S)->next = p;
    return *S;
}

// 出栈函数
node Pop(LinkStack *S) {
    node temp = {0, NULL};
    if (*S == NULL) {
        printf("No node in stack!\n");
        return temp;
    }
    LinkStack p = (*S)->next;
    node n = *p;
    (*S)->next = p->next;
    free(p);
    return n;
}

// 双栈实现队列的入队函数
void StackToQueuePush(LinkStack *S, int data) {
    LinkStack S1 = NULL;
    CreateNULLStack(&S1);
    node n;
    while ((*S)->next != NULL) {
        n = Pop(S);
        Push(&S1, n.data);
    }
    Push(&S1, data);
    while (S1->next != NULL) {
        n = Pop(&S1);
        Push(S, n.data);
    }
}

这段代码展示了如何使用两个栈实现队列的基本操作,包括入队和出队。

面试题 23:计算二叉树的深度

二叉树的深度是衡量树结构复杂度的重要指标。以下是一个使用递归方法计算二叉树深度的示例代码:

// 定义二叉树节点结构
typedef struct BiTNode {
    int data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

// 计算二叉树的深度
int depth(BiTree T) {
    if (T == NULL) return 0;
    int d1 = depth(T->lchild);
    int d2 = depth(T->rchild);
    return (d1 > d2 ? d1 : d2) + 1;
}

这段代码通过递归调用自身来计算左右子树的深度,并返回较大的深度值加一。

面试题 24:直接插入排序的实现

直接插入排序是一种简单直观的排序算法,它通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。以下是直接插入排序的实现代码:

#include <iostream>
using namespace std;

void InsertionSort(int ARRAY[], int length) {
    for (int i = 1; i < length; i++) {
        int key = ARRAY[i];
        int j = i - 1;
        while (j >= 0 && ARRAY[j] > key) {
            ARRAY[j + 1] = ARRAY[j];
           

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C/C++面试必考必会 文章被收录于专栏

【C/C++面试必考必会】专栏,直击面试核心,精选C/C++及相关技术栈中面试官最爱的必考点!从基础语法到高级特性,从内存管理到多线程编程,再到算法与数据结构深度剖析,一网打尽。助你快速构建知识体系,轻松应对技术挑战。希望专栏能让你在面试中脱颖而出,成为技术岗的抢手人才。

全部评论

相关推荐

牛客小菜鸡66:boss里面,招人的叫老板,找工作的叫牛人
点赞 评论 收藏
分享
emmm别问我为啥上一条帖子隔了两个月我才开始投简历和拿offer,因为我懒😰简单流程如下:周一凌晨改好的简历,然后到处乱投简历;周二接到了三维家的一面通知,临时抱佛脚的背了一些八股;周三上午一面下午通知第二天hr面;周四上午hr面下午拿offer,遂收手支线:在BOSS上顺手投了几个大厂,投字节的时候不小心投城客户端了,结果过了一天HR突然把我简历要走了,还问我能不能整客户端,我直接一口答应(脏面评警告😢)结果在周三下午的时候给我打电话,说前端有空缺实习岗,问我有没有兴趣,然后就跟我约了周四下午一面😰我都没咋准备啊,咩都不会啊😭结果周四下午面完,晚上打电话通知过一面了,赶紧把二面约在下周一下午,留点缓冲时间。逆大天了,我一半的问题都不会,他居然给我过了?运气未免有点好了😥现在正在恶补计网、网安、性能优化的东西(这三大板块我是几乎一点不会,一面几乎一点答不出来,加上我又没怎么背八股,这块被干烂了😵)心得体会与经验:1.&nbsp;我giao怎么这么快就结束了,我还以为要找好久😨2.&nbsp;大厂的面试问题真的和中厂小厂很大不同,比如在三维家我能自己吹水到vue的数据劫持、Proxy代理响应式之类的他们就觉得很不错了,但是在字节你但凡敢提到一下就会追问你细节了,一追问马脚就全漏出来了3.&nbsp;有信心真的很重要,我感觉我能拿中厂offer最重要的就是吹水吹出自信来了,以至于三维家面试反问面试官有哪里还需要改进的时候,他就说很不错了解的很多😦4.&nbsp;理解很重要,我从头到尾真没背过很多八股,不过有一些知识确实是敲过代码验证过,所以面试的时候能吹水吹得出来😇想了解面经啥的可以直接评论区问我,但我可能也说不全,因为我没有记录,而且今天摆了一天感觉记忆快清空了😵下面是故事时间:我暑假刚开始的时候才开始准备八股,印象很深那个时候连什么原型、事件循环、闭包这些名词都没听过,资料也不知道怎么找,就一直零零散散的准备,感觉也只有js稍微背了一下八股,其他很多时候都是靠完全理解和手写熟悉一些机制的,但这样做效率很低,反正准备了一个多星期半个月就开摆了😭结果一摆就摆到了开学,笔记是乱七八糟的,八股是忘光光的,简历是一直没改的,实习也是一直没投过的。直到上周日晚上偶然和师兄聊天,他突然问我“你怎么还不找实习”,那天晚上才幡然醒悟,是时候做点事情了😡然后就按照上面描述的来走了。其实我感觉我从头到尾都没背特别多八股,也没怎么找刷题资料啥的,早期就是翻尚硅谷或者黑马的入门视频从头学起,中期用面试鸭看了一点点题,主要是在学js机制和敲js代码,后期才发现了w3c的面经网站,然后在那里看着学(那个时候已经懒得敲了,因为有些问题与代码感觉不像是给找实习的看的,忒细了点😂)接下来继续准备字节二面吧,虽然几乎没啥可能可以通过,但是万一有奇迹呢?😍😍😍也祝大家能够早日拿到心仪的offer
我的offer呢😡:我已经预见10天后你会发,节孝子启动了
投递三维家等公司10个岗位
点赞 评论 收藏
分享
评论
2
11
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务