Holden Blog

逐梦山海 / Hello World~

0%

C/C++面试题目整理(1)

整理一些C/C++程序员面试的题目,经常拿来看看吧~

1、malloc、free和new、delete的区别?

malloc和free:它们属于C语言标准库函数,在C++里依旧可以使用。本质上,malloc仅负责分配指定大小的内存块,返回一个void*类型的指针;free用于释放由malloc、calloc、realloc分配的内存。

new和delete:它们是C++的运算符,具备类型安全特性。new不仅会分配内存,还会调用对象的构造函数进行初始化;delete会先调用对象的析构函数,再释放内存。

malloc和free:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdlib>

int main() {
// 使用malloc分配内存
int* ptr = (int*)malloc(sizeof(int));
if (ptr != nullptr) {
*ptr = 10;
std::cout << *ptr << std::endl;
// 使用free释放内存
free(ptr);
}
return 0;
}

new和delete:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class MyClass {
public:
MyClass() { std::cout << "Constructor called" << std::endl; }
~MyClass() { std::cout << "Destructor called" << std::endl; }
};

int main() {
// 使用new分配内存并初始化对象
MyClass* obj = new MyClass();
// 使用delete释放内存并调用析构函数
delete obj;
return 0;
}

2、虚函数表和虚函数表指针的创建时机?

在C++里,虚函数表(Virtual Table)和虚函数表指针(Virtual Table Pointer,简称vptr)是实现多态性的关键机制。

(1)虚函数表的创建时机

虚函数表是一个存储类的虚函数地址的静态数组,每个包含虚函数的类都会有一个对应的虚函数表。虚函数表的创建时机如下:

编译时:编译器在编译阶段会为每个包含虚函数的类创建一个虚函数表。在这个过程中,编译器会遍历类中所有的虚函数,把它们的地址存于虚函数表中。如果类存在基类,且基类有虚函数,编译器还会处理继承关系,在派生类的虚函数表中更新或添加相应的虚函数地址。

链接时:链接器会把各个编译单元中生成的虚函数表合并起来,形成最终的可执行文件中的虚函数表。

(2)虚函数表指针的创建时机

虚函数表指针是一个指向虚函数表的指针,每个包含虚函数的类的对象都会有一个虚函数表指针。虚函数表指针的创建时机如下:

对象构造时:当创建一个包含虚函数的类的对象时,在对象的构造函数执行过程中,编译器会在对象的内存布局起始位置插入一个虚函数表指针,并将其初始化为指向该类的虚函数表。对于派生类对象,在基类构造函数执行时,虚函数表指针会指向基类的虚函数表;当派生类构造函数执行时,虚函数表指针会被更新为指向派生类的虚函数表。

对象析构时:在对象的析构函数执行过程中,虚函数表指针会在析构函数结束前被重置。对于派生类对象,在派生类析构函数执行时,虚函数表指针会指向派生类的虚函数表;当基类析构函数执行时,虚函数表指针会被更新为指向基类的虚函数表。

3、左值引用与右值引用的区别?右值引用的意义?

左值引用与右值引用的区别

(1)基本概念

左值:指的是那些可以取地址、有名字的表达式,通常是变量或者对象。例如,普通变量、数组元素、函数返回的左值引用等都是左值。

右值:指的是那些不能取地址、没有名字的临时对象或字面量。例如,字面常量、函数返回的临时对象等都是右值。

(2)语法形式

左值引用:使用一个&符号来声明,它只能绑定到左值。

1
2
int a = 10;
int& ref = a; // 左值引用绑定到左值

右值引用:使用两个&&符号来声明,它只能绑定到右值。

1
int&& rref = 20; // 右值引用绑定到右值

(3)绑定规则

左值引用:只能绑定到左值,不能直接绑定到右值。不过,const左值引用是个例外,它可以绑定到右值。

1
2
3
4
int a = 10;
int& ref1 = a; // 正确,绑定到左值
// int& ref2 = 20; // 错误,不能将左值引用绑定到右值
const int& ref3 = 20; // 正确,const左值引用可以绑定到右值

右值引用:只能绑定到右值,不能绑定到左值。但可以通过std::move将左值转换为右值后再绑定。

1
2
3
4
int&& rref1 = 20; // 正确,绑定到右值
int a = 10;
// int&& rref2 = a; // 错误,不能将右值引用绑定到左值
int&& rref3 = std::move(a); // 正确,通过std::move将左值转换为右值后绑定

(4)生命周期

左值引用:如果引用的对象是一个局部变量,其生命周期取决于该局部变量的生命周期。当局部变量离开作用域时,引用也会失效。

1
2
3
4
5
int getValue() {
int temp = 10;
// int& ref = temp; // 不推荐,temp 离开作用域后 ref 会失效
return temp;
}

右值引用:右值引用可以延长右值的生命周期,使其生命周期和右值引用对象的生命周期相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

class MyClass {
public:
MyClass() { std::cout << "Constructor" << std::endl; }
~MyClass() { std::cout << "Destructor" << std::endl; }
};

MyClass getTempObject() {
return MyClass();
}

int main() {
MyClass&& rref = getTempObject();
// 右值的生命周期被延长,直到 rref 离开作用域
return 0;
}

右值引用的意义

(1)实现移动语义

移动语义是C++11引入的重要特性,它允许资源(如动态分配的内存)从一个对象转移到另一个对象,而无需进行昂贵的深拷贝。通过定义移动构造函数和移动赋值运算符,可以使用右值引用实现移动语义。

(2)完美转发
完美转发是指在函数模板中,能够将参数以原有的值类别(左值或右值)传递给其他函数。通过使用右值引用和std::forward,可以实现完美转发。

综上所述,左值引用和右值引用在概念、语法、绑定规则和生命周期等方面存在明显区别,而右值引用主要用于实现移动语义和完美转发,提高程序的性能和灵活性。

4、C++智能指针有哪些,有什么区别?

  1. std::auto_ptr(C++98 引入,C++17 弃用)

std::auto_ptr是最早引入的智能指针,它采用独占所有权模型,即同一时间只能有一个std::auto_ptr指向同一个对象。当std::auto_ptr被销毁或赋值给另一个std::auto_ptr时,它会自动释放所管理的对象。不过,由于其在转移所有权时的一些问题,在 C++17 中已被弃用。

  1. std::unique_ptr(C++11 引入)

std::unique_ptr同样采用独占所有权模型,确保同一时间只有一个std::unique_ptr指向同一个对象。与std::auto_ptr不同的是,std::unique_ptr的所有权转移更加安全,它禁止隐式的拷贝和赋值操作,只能通过std::move进行显式的所有权转移。

  1. std::shared_ptr(C++11 引入)

std::shared_ptr采用共享所有权模型,多个std::shared_ptr可以指向同一个对象。它使用引用计数机制来跟踪有多少个std::shared_ptr共享同一个对象,当引用计数变为 0 时,对象会被自动释放。

  1. std::weak_ptr(C++11 引入)

std::weak_ptr是一种弱引用智能指针,它可以从std::shared_ptr或另一个std::weak_ptr创建,但不会增加所指向对象的引用计数。std::weak_ptr主要用于解决std::shared_ptr的循环引用问题。

区别总结

所有权模型:
std::auto_ptr和std::unique_ptr是独占所有权,同一时间只有一个智能指针可以指向同一个对象。
std::shared_ptr是共享所有权,多个智能指针可以同时指向同一个对象。
std::weak_ptr是弱引用,不拥有对象的所有权。

安全性:
std::auto_ptr在转移所有权时存在一些问题,容易导致悬空指针,已被弃用。
std::unique_ptr通过禁止隐式拷贝和赋值,提高了所有权转移的安全性。
std::shared_ptr和std::weak_ptr使用引用计数机制,避免了手动管理内存的复杂性。

引用计数:
std::shared_ptr使用引用计数来跟踪对象的引用次数,当引用计数为 0 时自动释放对象。
std::weak_ptr不影响对象的引用计数,主要用于解决循环引用问题。

综上所述,在实际开发中,应优先使用std::unique_ptr和std::shared_ptr,根据具体需求选择合适的智能指针。如果需要独占所有权,使用std::unique_ptr;如果需要共享所有权,使用std::shared_ptr;如果需要解决循环引用问题,使用std::weak_ptr。

5、sizeof与strlen的差异?

sizeof:它是一个运算符,并非函数。在编译阶段,编译器就会对sizeof进行计算,确定数据类型或者变量所占用的内存字节数。

它可以用于计算各种数据类型(如int、char、double等)、变量、数组、指针或者结构体所占用的内存大小。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main() {
int num = 10;
char str[] = "Hello";
printf("Size of int: %zu\n", sizeof(int));
printf("Size of num: %zu\n", sizeof(num));
printf("Size of str: %zu\n", sizeof(str));
return 0;
}

strlen:这是一个标准库函数,其定义在<string.h>(C)或者(C++)头文件中。它会在运行时计算字符串的实际长度。

它只能用于以’\0’结尾的字符串,计算从字符串起始位置到第一个’\0’字符之间的字符个数。

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello";
printf("Length of str: %zu\n", strlen(str));
return 0;
}

综上所述,sizeof和strlen在本质、使用方式、计算结果、时间复杂度和适用范围等方面都存在明显差异。在实际编程中,需要根据具体需求选择合适的工具。如果需要确定数据类型或变量所占用的内存大小,使用sizeof;如果需要计算字符串的实际长度,使用strlen。