今天面试了一家100人以上的小公司,做QT上位机开发,个人感觉凉凉。以下是笔试题和我能回忆起的面试题
目录
一、笔试题
1. 什么是C++中的指针
官方解释:
C++中的指针是一个变量,其值为另一个变量的地址,即内存位置的地址。指针变量简称指针,它专用于存放变量在内存中的起始地址。指针是C/C++中的精髓所在,其特殊性在于它存放的是内存地址。
在C++中,每定义一个变量,系统就会给变量分配一块内存,且内存是有地址的。指针变量通过存储这些地址,使得我们可以间接地访问和操作这些内存中的数据。因此,指针是实现动态内存分配、函数调用、数据结构等高级功能的重要工具。
声明指针的语法:数据类型 *变量名。例如,int *p; 就声明了一个指向整数的指针p。
自己的理解:
你有一个好朋友住在你家附近。你知道他家的地址,所以你可以根据这个地址找到他家,然后和他一起玩。
在这个例子里,地址就像是C++中的指针,而你的朋友家则是那个地址所指向的变量或者数据。
2.什么是引用,它与指针有什么不同
官方解释:
引用是C++中的一个概念,它是变量的一个别名,与原来的变量实质上指向同一块内存地址。当我们给一个变量起一个引用时,我们可以使用这个引用来代替原来的变量名,对引用的操作就是对原变量的操作。例如,如果我们有int a = 5;
,然后定义int& ref = a;
,那么ref
就是a
的一个引用,它们实际上都是指向同一个内存地址的。
自己的理解:
引用:你有一个很喜欢的玩具熊,你给这个玩具熊起了一个特别的名字,叫做“熊熊”。这个名字就是玩具熊的一个引用,每次你叫“熊熊”的时候,其实就是在说那个玩具熊。
指针:指针就像是一个小纸条,上面写着你朋友的家的地址。有了这个小纸条,你就可以找到你朋友的家,和他一起玩。
引用 | 指针 | |
---|---|---|
定义与性质 | 引用则是变量的别名,它和原变量实际上是同一个变量。 | 指针是一个变量,它存储的是另一个变量的地址 |
存储空间 | 引用则不占用额外的存储空间,它只是原变量的一个别名。 | 指针有自己的存储空间,它的大小通常是固定的 |
初始化 | 引用必须在定义时初始化,且一旦初始化后就不能再绑定到另一个变量上 | 指针可以不初始化 |
可变性 | 而引用的值(即它所代表的变量)在初始化后则不能改变。 | 指针的值(即它所指向的地址)可以在初始化后改变,使其指向另一个对象 |
空值 | 不能为空 | 空 |
多级引用与指针 | 引用只能是一级 | 可以有多级指针(例如int** p; ) |
安全性 | 好(因为引用在初始化后不能改变所指向的对象,这减少了出错的可能性。) | 差 |
内存管理 | 内存泄漏 | 返回动态内存分配的对象或内存,必须使用指针 |
3.什么是多态?
简单来说,就是“一个接口,多种方法”。多态的实现方式主要依赖于继承和虚函数。
举几个代码例子
1).运行时多态(通过虚函数)
#include <iostream>
#include <string>
// 基类 Animal
class Animal {
public:
virtual void speak() const {
std::cout << "The animal speaks." << std::endl;
}
virtual ~Animal() {} // 虚析构函数确保正确释放派生类资源
};
// 派生类 Dog
class Dog : public Animal {
public:
void speak() const override {
std::cout << "The dog barks." << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void speak() const override {
std::cout << "The cat meows." << std::endl;
}
};
// 使用多态的函数
void letAnimalSpeak(const Animal& animal) {
animal.speak(); // 在运行时确定调用哪个 speak()
}
int main() {
Dog dog;
Cat cat;
Animal* animalPtr;
animalPtr = &dog;
letAnimalSpeak(*animalPtr); // 输出: The dog barks.
animalPtr = &cat;
letAnimalSpeak(*animalPtr); // 输出: The cat meows.
return 0;
}
在上面的例子中,Animal
类有一个虚函数 speak()
,Dog
和 Cat
类分别重写了这个函数。在 letAnimalSpeak
函数中,我们传入了一个 Animal
类型的引用,但实际上可以传入任何 Animal
的派生类对象。在运行时,程序会根据实际对象的类型调用相应的 speak()
函数。
2).编译时多态(通过函数重载)
#include <iostream>
// 函数重载示例
void print(int value) {
std::cout << "Integer: " << value << std::endl;
}
void print(double value) {
std::cout << "Double: " << value << std::endl;
}
void print(const std::string& text) {
std::cout << "String: " << text << std::endl;
}
int main() {
print(42); // 输出: Integer: 42
print(3.14); // 输出: Double: 3.14
print("Hello, World!"); // 输出: String: Hello, World!
return 0;
}
在这个例子中,我们重载了 print
函数,使其可以接受 int
、double
和 std::string
类型的参数。编译器根据传递给 print
函数的参数类型在编译时决定调用哪个版本的函数。这就是编译时多态的一个例子。
4.什么是异常处理?如何在C++中实现?
异常处理:是我们在生活中遇到问题时,找到合适的方法来解决它。在C++编程中,异常处理可以帮助我们找到并修复代码中的错误。
在C++中,这个过程可以这样实现:
1). throw:相当于你发现锅里的油着火了,你决定要采取行动,于是你“抛出”了一个问题,即火需要被扑灭
if (oil_is_on_fire) {
throw "Fire!"; // 抛出异常,表示发生了火灾
}
2).try:你在厨房里做饭的时候(try
块里的代码),一直在注意火的情况
try {
// 在这里做饭,可能会遇到油着火的情况
}
3).catch:当你发现火情(捕获到异常)时,你立即采取行动(执行catch
块里的代码)来灭火。
catch (const char* message) {
// 灭火操作,比如用锅盖盖住锅
std::cout << "Caught an exception: " << message << ". Using the lid to put out the fire." << std::endl;
}
catch (...) {
// 对于未知或无法处理的异常,我们可以做进一步的处理,比如发出警报
std::cout << "Unknown exception caught! Calling the firefighters!" << std::endl;
}
5.请解释C++的const关键字,它在哪些情况下使用
在C++中,const
关键字用于声明一个常量,即一个值在程序执行期间不能被修改的对象。
- 保护数据不被意外修改:当你有一个不应该在程序执行期间改变的值时,使用
const
可以确保这个值不会被意外修改。 - 提高代码可读性:
const
变量提供了关于其用途的额外信息,使得代码更易读。 - 优化:编译器可能会对
const
变量进行额外的优化,因为它们不会在运行时改变。 - 作为函数参数和返回值的保证:
const
可以用于函数参数和返回值,以表明参数或返回值在函数内部不会被修改。
下面是一些使用const
的常见情况:
1. 声明常量值
const int MAX_SIZE = 100;
2. 指针和const
指向常量的指针:该指针不能用来修改它所指向的值。
const int* ptr = &some_integer; // ptr指向一个常量,不能通过ptr修改some_integer的值
常量指针:该指针自身不能被修改(即不能指向其他地址),但它所指向的值可以被修改(如果它不是指向一个常量的话)。
int value = 10;
int* const ptr = &value; // ptr是一个常量指针,不能改变ptr指向的地址,但可以修改value的值
指向常量的常量指针:该指针自身不能被修改,同时它也不能用来修改它所指向的值。
const int value = 10;
const int* const ptr = &value; // ptr是常量,value也是常量,两者都不能通过ptr被修改
3. 函数参数
void func(const int& param) {
// 在func内部,param不能被修改
}
4. 函数返回值
const int* getPointer() {
static int value = 42;
return &value;
}
// 调用者不能通过返回的指针修改value的值
5. 类成员
class MyClass {
public:
const int constantMember;
MyClass(int value) : constantMember(value) {}
};
// MyClass的实例一旦初始化后,constantMember的值就不能再被修改
6. 类成员函数
class MyClass {
public:
int getValue() const {
// 这个函数不会修改类的任何成员变量
return someMember;
}
private:
int someMember;
};
// const成员函数保证不会修改类的状态
7. 在数组中使用
const int arraySize = 5;
int const arr[arraySize]; // 或者 int arr[arraySize] const;
// arr的内容在初始化后不能被修改
6.请解释C++中的static关键字,它在哪些情况下使用
在C++中,static
是一个多功能的关键字,它可以用于多种上下文,包括变量、函数、类成员等。
以下是static
关键字的主要用途和它们的使用场景:
1. 静态局部变量
当在函数内部声明一个静态局部变量时,这个变量只在程序执行到该变量的声明代码时初始化一次,并且在程序的整个生命周期内保持其值。即使函数返回后,静态局部变量的值也不会丢失。
自己的理解:想象你在家里有一个存钱罐,每次你得到零花钱,你都会放进这个存钱罐里。这个存钱罐就像是一个静态局部变量,因为它只在家里(函数内部)存在,并且每次你往存钱罐里放钱(函数调用),里面的钱(变量的值)都会累积起来,不会消失。即使你离开房间去做其他事情(函数返回),存钱罐里的钱还是会在那里
使用场景:当你想在函数调用之间保持某些状态或数据时,可以使用静态局部变量。
void addMoneyToPiggyBank() {
static int moneyInPiggyBank = 0; // 静态局部变量,初始化为0
int moneyToAdd = 5; // 假设每次添加5块钱
moneyInPiggyBank += moneyToAdd; // 往存钱罐里加钱
std::cout << "存钱罐里现在有 " << moneyInPiggyBank << " 块钱。\n";
}
2. 静态全局变量
静态全局变量只在其定义的文件中可见,而在其他文件中不可见。这提供了封装性,允许程序员隐藏不应在其他文件中使用的变量。
自己的理解:静态全局变量就像是你家里的一个秘密宝藏,只有你和你的家人知道它的存在。其他来你家做客的小朋友是看不到这个宝藏的。在C++中,如果一个全局变量被声明为static
,那么它就只能在其所在的源文件中被访问,其他文件是看不到的。
使用场景:当你想限制全局变量的作用域到单个源文件时。
// 在你的房间的文件(file1.h)里
static int secretTreasure = 10; // 静态全局变量,只有这个文件能看到
//另外一个文件(file1.cpp)
void showTreasure() {
std::cout << "秘密宝藏里有 " << secretTreasure << " 颗糖果!\n";
}
其他小朋友的文件(file2.cpp)里是不能访问secretTreasure
的。
3. 静态类成员
静态类成员是类的所有对象共享的成员。它们不属于类的任何特定对象实例。静态成员变量必须在类外部定义和初始化。
自己的理解:静态类成员就像是全班同学共享的一个东西,比如班级的图书角。每个同学都可以从图书角借书,也可以放回书。图书角里的书不属于任何一个同学,而是属于整个班级。
使用场景:当你想存储与类本身相关而不是与类的任何特定对象实例相关的信息时。
class Classroom {
public:
static int booksInLibrary; // 静态类成员,表示图书角里的书的数量
// ... 其他成员 ...
};
// 在类外部定义和初始化静态成员变量
int Classroom::booksInLibrary = 50; // 图书角初始有50本书
每个同学都可以改变booksInLibrary
的值,来表示借书或还书的行为。
4. 静态函数
在函数外部(通常在命名空间或全局范围内),static
用于声明函数的作用域为文件内部。这与静态全局变量的效果相似,它使得函数只在其定义的文件中可见。
自己的理解:你有一个秘密的食谱,只有你和你的家人知道怎么做。这个食谱就像static
函数一样,只有在其定义的文件中才能使用。
使用场景:
- 当你需要保持某个变量的值在函数调用之间时,可以使用静态局部变量。
- 当你需要在多个文件中共享某个变量,但又不想让它被其他所有文件访问时,可以使用静态全局变量。
- 当你需要在不创建类实例的情况下访问某个变量或函数时,可以使用静态类成员。
- 当你想要限制函数或变量的可见性,使它们仅在其定义的文件中可见时,可以使用静态函数或静态全局变量。
// file1.cpp
static void myFunction() { // 静态函数
// ...
}
// file2.cpp
extern void myFunction(); // 错误!file2中不能访问file1中的静态函数
7.请解释C++中的sizeof运算符,它返回什么?
在 C++ 中,sizeof
是一个运算符,用于获取一个对象或类型的字节大小。它可以用于获取各种数据类型的大小,包括内置类型、类、结构体等。
自己的理解:sizeof
是一个运算符,用于获取一个对象或类型的字节大小。就像你在生活中会问“这个苹果有多大?”来了解苹果的大小一样,sizeof
可以帮你了解一个对象或类型在计算机内存中占用了多少空间。
使用场景:
1.
用于数据类型:你可以直接使用sizeof
运算符获取基本数据类型(如int
、char
、double
等)或复合数据类型(如数组、结构体、类等)的大小。例如:
std::cout << sizeof(int) << std::endl; // 输出int类型在内存中的大小(以字节为单位)
2.用于变量:你也可以对变量使用sizeof
运算符,这将返回该变量类型的大小,而不是变量本身所占用的内存空间大小。例如:
int x;
std::cout << sizeof(x) << std::endl; // 输出int类型在内存中的大小(以字节为单位)
3.用于数组:当用于数组时,sizeof
返回的是整个数组所占用的内存空间大小,而不是数组中单个元素的大小。例如:
int arr[10];
std::cout << sizeof(arr) << std::endl; // 输出10个int类型在内存中的总大小(以字节为单位)
4.用于指针:当用于指针时,sizeof
返回的是指针本身的大小,而不是指针所指向的内存空间大小。例如:
int *ptr;
std::cout << sizeof(ptr) << std::endl; // 输出指针类型在内存中的大小(以字节为单位)