Qt笔试题
选择题
1.使用值传递方式将实参传给形参,下列说法正确的是(A)
A.形参是实参的备份
B.实参是形参的备份
C.形参和实参是同一对象
D.形参和实参无联系
void function(int a)
{}int main()
{function((int)3);return 0;
}
这里实参是int类型的变量(假定给它命名为x)值为3.
我们把实参x传给形参a,这里使用值传递,所以形参a的值和实参x的值开始是一致的,所以可以说形参是实参的备份。A正确
x和a的值是相同的,但它们的地址是不同的,所以肯定不是同一对象。C错误
2.int Func(int,int)不可与下列哪个函数构成重载(B)
A.int Func(int,int,int)
B.double Func(int,int)
C.double Func(double,double)
D.double Func(int,double)
是否重载看的是函数参数列表是否完全一致
3.假定一个类的构造函数为A(int aa,int bb){a = aa--;b = a * bb;},则执行A x(4,5);语句后x.a 和x.b的值分别为(4,20)
a = aa--;
aa先赋值,再减一
则a = 4; aa = 3;
b = 4 * 5 = 20;
4.假设Class Y : public X,则创建Y类的对象和销毁Y类对象时,调用构造函数和析构函数的次序分别为(XY,YX)
调用构造函数时,先构造基类对象,再构造子类对象。
调用析构函数时,先析构子类对象,再析构基类对象。
5.RAII是(资源获取即初始化)
Resource Acquisition is initialization.
填空题
1.简述类成员函数的重写,重载和隐藏的区别
重写:在继承中,子类重写基类的虚函数。函数签名相同,用于运行时多态。
函数签名相同 : 函数名 函数参数 返回类型 都完全相同
重载 : 同一个类中,函数名相同,参数列表必须不同。
隐藏 :子类定义同名函数后,基类同名的所有函数都会被隐藏(即使参数不同)。
条件 :
(1)子类函数与基类函数名相同,但参数列表不同。
(2)子类函数与基类函数名相同,但基类函数非虚。
2.C中的malloc和C++中的new有什么区别
malloc仅分配内存,不调用构造函数;
new分配内存并调用构造函数,处理资源初始化和类型安全。
主要区别包括内存管理方式,异常处理和类型安全。
malloc :
不执行初始化
如 : int * p = malloc(sizeof(int));分配内存但内容未初始化(内容是随机值)
malloc返回的是void * 指针,需显示类型转换,如int * p = (int *)malloc(sizeof(int));
new :
在C++中作为运算符使用,用于分配内存并调用构造函数。
自动初始化 : 例如,int * p = new int(10);
分配内存并将值初始化为10,或调用对象的构造函数。
返回类型指针:无需转换,类型安全
如 MyClass * obj = new MyClass();
2.核心区别详解
构造函数/析构函数调用:
malloc只分配内存字节,不调用任何构造函数。对象成员可能处于未定义状态。
new在分配后自动调用构造函数(初始化对象),delete调用后析构对象(释放资源)。
异常处理:
malloc分配失败后返回NULL,需手动检查(如if(p == NULL) )。
new失败时抛出std::bad_alloc异常,可与C++异常机制结合(如try-catch)。
内存大小计算:
malloc需显示计算大小(如sizeof(int) * n),易出错。
new自动根据类型计算大小(如new int[n] ) ,更安全。
资源管理兼容性:
malloc / free 不与RAII集成,可能泄漏。
new / delete 是 C++ RAII的一部分,更适合智能指针(如 std::unique_ptr)。
实际应用建议:
在C++中优先使用new (避免原始指针,推荐智能指针)。
在C或C++与C混编时使用malloc,但需要手动管理。
map与unordered_map的区别
map
基于红黑树实现,元素按键有序存储(排序);unordered_map
基于哈希表实现,元素无序。区别主要在于数据结构、时间复杂度和使用场景。
解题思路和详细解析:
底层实现差异:
map
:关键特性:按键顺序排序(默认升序,可由比较函数自定义),迭代时按顺序访问。
map
:
实现:红黑树(自平衡二叉搜索树)。
关键特性:按键顺序排序(默认升序,可由比较函数自定义),迭代时按顺序访问。
unordered_map
:
实现:哈希表(散列表)。
关键特性:元素无序存储,键的分布依赖哈希函数。
性能对比(时间复杂度):
操作 | map (时间复杂度) | unordered_map (时间复杂度) |
---|---|---|
插入、删除 | O(log n) | 平均 O(1),最坏 O(n) |
查找 | O(log n) | 平均 O(1),最坏 O(n) |
迭代 | 有序,O(n) | 无序,O(n) 但顺序不可预测 |
解释:
map
的树结构保证稳定对数时间,适用于需要有序访问的场景。
unordered_map
哈希表在平均情况下为常数时间,但哈希冲突时退化为线性时间。
3.
使用场景推荐:
选择 map
当:
需要元素按键排序(如输出数据时按键遍历)。
键类型无高效哈希函数,或哈希冲突严重(例如字符串键不频繁插入)。
代码示例:
#include <map>
std::map<std::string, int> myMap = {{"apple", 5}, {"banana", 3}}; // 按键升序存储
for (auto& pair : myMap) { cout << pair.first << ": " << pair.second << endl; // 输出 apple:5, banana:3(有序)
}
选择 unordered_map
当:
需要高速查找/插入,且不关心顺序。
键类型有高质量哈希函数(如整数、标准库字符串)。
代码示例:
#include <unordered_map>
std::unordered_map<std::string, int> myUnorderedMap = {{"apple", 5}, {"banana", 3}}; // 无序存储
cout << myUnorderedMap["apple"]; // 平均 O(1) 访问
4.
其他区别:
内存开销:unordered_map
因哈希表需额外存储桶(bucket),内存占用可能更高。
稳定性:map
插入/删除不使迭代器失效(除当前元素);unordered_map
插入可能导致rehash,使所有迭代器失效。
综合建议:
优先 unordered_map
用于高性能查询(如缓存)。
当顺序重要的适合使用 map
(如数据库索引仿真)。
编程题
1.将两个降序链表合并为一个新的降序链表并返回,合并后的链表必须通过拼接给定两个链表的所有节点组成。
示例:
链表1:5 → 2 → 1
链表2:6 → 3 → 1
输出:6 → 5 → 3 → 2 → 1 → 1
合并后的链表节点全部来源于链表1和链表2
#include <vector>
#include <iostream>
using namespace std;/* 链表1: 5->2->1链表2: 6->3->1合并后为:6->5->3->2->1->1*/struct node
{int value;struct node * next;node(int x) : value(x),next(nullptr) {}
};node * getNode(std::vector<int> v)
{node * last = nullptr;node * header = nullptr;for(auto it = v.begin();it != v.end();++it){//当前节点node * n = new node(*it);//上一个节点if(last != nullptr)last->next = n;else header = n;last = n;}return header;
}struct node * merge(struct node * l1,struct node * l2)
{node * p = new node(0);node * head = p;while(l1 && l2){if(l1->value > l2->value){p->next = l1;l1 = l1->next;}else{p->next = l2;l2 = l2->next;}p = p->next;}if(l1){p->next = l1;}else{p->next = l2;}return head->next;
}
void printNodes(node * list)
{while(list != nullptr){cout << list->value << " ";list = list->next;}cout << endl;
}int main(int argc, char *argv[])
{std::vector<int> v1 = {5,2,1};std::vector<int> v2 = {6,5,3,2,1,1};node * l1 = getNode(v1);node * l2 = getNode(v2);node * l3 = merge(l1,l2);printNodes(l3);return 0;
}
给定一个只包含'{'和'}'的字符串,找出最长有效(格式正确且连续)花括号字串的长度。
示例:
输入:str="}}{}}{}{}"
输出:4
解释:最长子串是"{}{}"(对应"{}"有效括号对)
#include <iostream>
#include <stack>
#include <algorithm>
using namespace std;class Solution {
public:int longestValidParentheses(string s) {stack<int> stk;stk.push(-1); // 哨兵索引,表示有效序列的起始边界int max_len = 0;for (int i = 0; i < s.length(); i++) {char c = s[i];if (c == '{') {stk.push(i); // 左括号入栈}else {stk.pop(); // 遇到右括号,弹出匹配的左括号if (stk.empty()) {stk.push(i); // 无效位置设为新边界}else {max_len = max(max_len, i - stk.top()); // 更新有效长度}}}return max_len;}
};int main() {Solution sol;// 测试string test1 = "}}{}}{}{}{}";cout << "input: \"" << test1 << "\" => output: "<< sol.longestValidParentheses(test1) << " (expect: 6)" << endl;return 0;
}
QT部分
1.简述QT信号槽机制的优缺点
优点:
1.松耦合设计
信号发送者与槽接收者无需相互引用
对象间通过信号关联而非直接调用(解耦程度高)
示例:connect(button, &QPushButton::clicked, processor, &DataProcessor::start)
2.类型安全
编译时检查信号/槽签名匹配(避免运行时崩溃)
支持所有元对象系统兼容的数据类型(包括自定义类型)
3.跨线程通信
示例:后台线程完成计算后通过信号更新UI
自动处理线程间通信(通过Qt::QueuedConnection
)
无需手动加锁,简化多线程编程
4.灵活的连接方式
支持一对多、多对一、多对多连接
Lambda表达式作为槽函数(C++11特性)
connect(timer, &QTimer::timeout, []{qDebug() << "Timer triggered without dedicated slot";
});
缺点:
1.性能开销
比直接函数调用慢5-10倍(元对象系统查找成本)
不适合超高频率调用(如实时音频处理)
2.内存占用
每个QObject携带元对象信息(增加~500字节/对象)
连接关系需额外存储(大型项目可能占用数MB)
3.调试复杂性
信号槽调用栈不连续(调试时调用链断裂)
难以追踪间接连接(如通过中间对象转发)
4.继承限制
必须继承QObject类(无法用于纯C++类)
需要手动运行moc预处理(增加构建复杂度)
典型应用场景对比表
场景 | 适合信号槽 | 更适合传统回调 |
---|---|---|
GUI事件处理 | ✓ | |
高频数据流处理 | ✓ | |
跨线程状态通知 | ✓ | |
性能关键型算法 | ✓ | |
插件系统通信 | ✓ |
总结:QT信号槽是GUI应用开发的核心优势,但需避免在性能敏感模块过度使用。在实际项目中,通常将信号槽用于架构设计(约80%场景),关键算法采用直接调用。