# C++ 复习笔记
# C++ 复习笔记(详细讲解)
# 1. 类与对象
类的定义:
class Rectangle {
public:
double length; // 长度
double width; // 宽度
double area() { // 计算面积的成员函数
return length * width;
}
};
对象的创建与使用:
int main() {
Rectangle rect; // 创建对象
rect.length = 5; // 访问成员变量
rect.width = 10;
double area = rect.area(); // 调用成员函数
std::cout << "Area: " << area << std::endl;
return 0;
}
访问控制:
class Circle { | |
private: | |
double radius; // 私有成员变量,只能在类内部访问 | |
public: | |
void setRadius(double r) { // 公有成员函数 | |
radius = r; | |
} | |
double getRadius() { // 公有成员函数 | |
return radius; | |
} | |
}; | |
int main() { | |
Circle c; | |
c.setRadius(5.0); | |
std::cout << "Radius: " << c.getRadius() << std::endl; | |
return 0; | |
} |
# 2. 构造函数与析构函数
构造函数:
class Person { | |
public: | |
std::string name; | |
int age; | |
// 构造函数 | |
Person(std::string n, int a) : name(n), age(a) { | |
std::cout << "Constructor called" << std::endl; | |
} | |
// 析构函数 | |
~Person() { | |
std::cout << "Destructor called" << std::endl; | |
} | |
}; | |
int main() { | |
Person p("John", 30); | |
return 0; | |
} |
默认构造函数与带参构造函数:
class Car { | |
public: | |
std::string brand; | |
int year; | |
// 默认构造函数 | |
Car() { | |
brand = "Unknown"; | |
year = 0; | |
} | |
// 带参构造函数 | |
Car(std::string b, int y) : brand(b), year(y) {} | |
}; | |
int main() { | |
Car car1; // 调用默认构造函数 | |
Car car2("Toyota", 2020); // 调用带参构造函数 | |
return 0; | |
} |
# 3. 对象数组和对象指针
对象数组:
class Student { | |
public: | |
std::string name; | |
int age; | |
Student(std::string n, int a) : name(n), age(a) {} | |
}; | |
int main() { | |
Student students[2] = { {"Alice", 20}, {"Bob", 22} }; // 对象数组 | |
for(int i = 0; i < 2; i++) { | |
std::cout << students[i].name << " is " << students[i].age << " years old." << std::endl; | |
} | |
return 0; | |
} |
对象指针:
class Book { | |
public: | |
std::string title; | |
Book(std::string t) : title(t) {} | |
}; | |
int main() { | |
Book* bookPtr = new Book("C++ Programming"); // 使用 new 创建对象 | |
std::cout << "Book title: " << bookPtr->title << std::endl; | |
delete bookPtr; // 释放内存 | |
return 0; | |
} |
# 4. 向函数传递对象
值传递:
class Box { | |
public: | |
double length; | |
Box(double l) : length(l) {} | |
}; | |
void printLength(Box b) { | |
std::cout << "Length: " << b.length << std::endl; | |
} | |
int main() { | |
Box box(10); | |
printLength(box); // 值传递 | |
return 0; | |
} |
引用传递:
class Box { | |
public: | |
double length; | |
Box(double l) : length(l) {} | |
}; | |
void printLength(const Box& b) { // 引用传递 | |
std::cout << "Length: " << b.length << std::endl; | |
} | |
int main() { | |
Box box(10); | |
printLength(box); // 引用传递 | |
return 0; | |
} |
指针传递:
class Box { | |
public: | |
double length; | |
Box(double l) : length(l) {} | |
}; | |
void printLength(Box* b) { // 指针传递 | |
std::cout << "Length: " << b->length << std::endl; | |
} | |
int main() { | |
Box box(10); | |
printLength(&box); // 指针传递 | |
return 0; | |
} |
# 5. 静态成员与友元
静态成员变量和静态成员函数:
class Account { | |
private: | |
double balance; | |
static double interestRate; // 静态成员变量 | |
public: | |
Account(double b) : balance(b) {} | |
static void setInterestRate(double rate) { // 静态成员函数 | |
interestRate = rate; | |
} | |
double getBalanceWithInterest() const { | |
return balance * (1 + interestRate); | |
} | |
}; | |
double Account::interestRate = 0.05; // 静态成员变量初始化 | |
int main() { | |
Account a1(1000); | |
Account::setInterestRate(0.03); | |
std::cout << "Balance with interest: " << a1.getBalanceWithInterest() << std::endl; | |
return 0; | |
} |
友元函数和友元类:
class Box { | |
private: | |
double length; | |
public: | |
Box(double l) : length(l) {} | |
friend void printLength(const Box& b); // 友元函数声明 | |
}; | |
void printLength(const Box& b) { // 友元函数定义 | |
std::cout << "Length: " << b.length << std::endl; | |
} | |
int main() { | |
Box box(10); | |
printLength(box); // 调用友元函数 | |
return 0; | |
} |
# 6. 类的继承与派生
继承的定义和实现:
class Shape { | |
public: | |
double area; | |
virtual double calculateArea() = 0; // 纯虚函数 | |
}; | |
class Circle : public Shape { | |
private: | |
double radius; | |
public: | |
Circle(double r) : radius(r) {} | |
double calculateArea() override { // 重写基类的纯虚函数 | |
area = 3.14 * radius * radius; | |
return area; | |
} | |
}; | |
int main() { | |
Circle c(5); | |
std::cout << "Circle area: " << c.calculateArea() << std::endl; | |
return 0; | |
} |
# 7. 多重继承
多重继承派生类 调用顺序是:
①调用基类构造函数,各个基类按定义时的次序先后调用;
②调用子对象构造函数,各个子对象按声明时的次序先后调用;
③执行派生类初始化列表;
④执行派生类初始化函数体;
多重继承的实现:
class A { // 声明为基类 A | |
public: // 外部接口 | |
A(int n){ nv=n; cout<<"Member of A"<<< "fun of A" << endl; } | |
void fun(){cout << "fun of A" <<endl;} | |
private: int nv; // 私有成员 | |
}; | |
class B1:virtual public A{ // 声明 A 为虚基类 | |
public: | |
B1(int a):A(a){ cout<<"Member of B1"<<endl; } //B1 类的构造函数 | |
private: int nv1; | |
}; | |
class B2 :virtual public A // 声明 A 为虚基类 | |
{ | |
public: | |
B2(int a) :A(a){ cout << "Member of B2" << endl; } //B2 类的构造函数 | |
private:int nv2; | |
}; | |
class C :public B1, public B2 | |
{ | |
public: | |
// 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用 | |
C(int a):A(a),B1(a),B2(a){cout << "Member of C"<<endl;} | |
void fund(){ cout << "fun of C" << endl; } | |
private: int nvd; | |
}; | |
int main() { | |
C c1(1); | |
c1.fund(); | |
c1.fun(); // 不会产生二义性 | |
return 0; | |
} | |
// 运行结果 | |
Member of A | |
Member of B1 | |
Member of B2 | |
Member of C | |
fun of C | |
fun of A |
# 8. 多态性
多态性的实现:
class Animal { | |
public: | |
virtual void speak() const { // 虚函数 | |
std::cout << "Animal sound" << std::endl; | |
} | |
}; | |
class Dog : public Animal { | |
public: | |
void speak() const override { // 重写虚函数 | |
std::cout << "Woof" << std::endl; | |
} | |
}; | |
class Cat : public Animal { | |
public: | |
void speak() const override { // 重写虚函数 | |
std::cout << "Meow" << std::endl; | |
} | |
}; | |
int main() { | |
Animal* a1 = new Dog(); | |
Animal* a2 = new Cat(); | |
a1->speak(); // 调用 Dog 类的 speak 方法 | |
a2->speak(); // 调用 Cat 类的 speak 方法 | |
delete a1; | |
delete a2; | |
return 0; | |
} |
# C++ 复习题目
# 1. 类与对象
重点知识:
- 类的定义:class 关键字、类成员、成员函数
- 对象的创建与使用
- 访问控制:public, private, protected
例题:
- 简述类与对象的区别。
- 编写一个类
Rectangle,包含成员变量length和width,以及计算面积的成员函数。
参考解答:
- 类是一个数据类型,是对一类对象的描述;对象是类的实例。
class Rectangle { | |
public: | |
double length; | |
double width; | |
double area() { | |
return length * width; | |
} | |
}; | |
int main() { | |
Rectangle rect; | |
rect.length = 5; | |
rect.width = 10; | |
std::cout << "Area: " << rect.area() << std::endl; | |
return 0; | |
} |
# 2. 构造函数与析构函数
重点知识:
- 构造函数:定义、重载、默认构造函数、带参构造函数
- 析构函数:定义、作用
例题:
- 什么是构造函数和析构函数?它们的作用是什么?
- 编写一个类
Person,包含姓名和年龄,定义一个带参数的构造函数和析构函数。
参考解答:
- 构造函数用于初始化对象,析构函数用于清理资源。
class Person { | |
public: | |
std::string name; | |
int age; | |
Person(std::string n, int a) : name(n), age(a) { | |
std::cout << "Constructor called" << std::endl; | |
} | |
~Person() { | |
std::cout << "Destructor called" << std::endl; | |
} | |
}; | |
int main() { | |
Person p("John", 30); | |
return 0; | |
} |
# 3. 对象数组和对象指针
重点知识:
- 对象数组:定义、初始化、访问
- 对象指针:定义、访问成员
例题:
- 如何定义一个包含 10 个对象的数组?
- 编写一个类
Circle,使用指针创建对象并访问成员变量。
参考解答:
Person people[10];
class Circle { | |
public: | |
double radius; | |
Circle(double r) : radius(r) {} | |
double area() { | |
return 3.14 * radius * radius; | |
} | |
}; | |
int main() { | |
Circle* c = new Circle(5); | |
std::cout << "Area: " << c->area() << std::endl; | |
delete c; | |
return 0; | |
} |
# 4. 向函数传递对象
重点知识:
- 值传递
- 引用传递
- 指针传递
例题:
- 简述值传递和引用传递的区别。
- 编写一个函数,计算矩形对象的周长,使用引用传递。
参考解答:
- 值传递会复制对象,引用传递传递的是对象的引用,不会复制对象。
class Rectangle { | |
public: | |
double length; | |
double width; | |
}; | |
double perimeter(Rectangle& rect) { | |
return 2 * (rect.length + rect.width); | |
} | |
int main() { | |
Rectangle rect = {5, 10}; | |
std::cout << "Perimeter: " << perimeter(rect) << std::endl; | |
return 0; | |
} |
# 5. 静态成员与友元
重点知识:
- 静态成员变量和静态成员函数
- 友元函数和友元类
例题:
- 解释静态成员变量的用途。
- 编写一个类
Account,定义静态成员变量interestRate和友元函数setInterestRate。
参考解答:
- 静态成员变量在所有对象间共享,适合表示类级别的属性。
class Account { | |
private: | |
double balance; | |
static double interestRate; | |
public: | |
Account(double b) : balance(b) {} | |
static void setInterestRate(double rate) { | |
interestRate = rate; | |
} | |
double getBalanceWithInterest() const { | |
return balance * (1 + interestRate); | |
} | |
}; | |
double Account::interestRate = 0.05; | |
int main() { | |
Account a1(1000); | |
Account::setInterestRate(0.03); | |
std::cout << "Balance with interest: " << a1.getBalanceWithInterest() << std::endl; | |
return 0; | |
} |
# 6. 类的继承与派生
重点知识:
- 基类和派生类
- 继承的方式:public, protected, private
- 派生类构造函数和析构函数
例题:
- 简述继承的作用。
- 编写一个基类
Shape和派生类Square,派生类中计算正方形的面积。
参考解答:
- 继承用于实现代码重用和建立类之间的层次关系。
class Shape { | |
public: | |
virtual double area() const = 0; // 纯虚函数 | |
}; | |
class Square : public Shape { | |
private: | |
double side; | |
public: | |
Square(double s) : side(s) {} | |
double area() const override { | |
return side * side; | |
} | |
}; | |
int main() { | |
Square sq(5); | |
std::cout << "Area of square: " << sq.area() << std::endl; | |
return 0; | |
} |
# 7. 多重继承
重点知识:
- 多重继承的定义
- 菱形继承问题及虚继承
例题:
- 什么是多重继承?它带来了什么问题?
- 编写一个类
A和两个派生类B和C,再创建一个类D,同时继承B和C。
参考解答:
- 多重继承指一个类可以有多个基类,它带来的问题是菱形继承导致的二义性。
class A { | |
public: | |
void show() { | |
std::cout << "Class A" << std::endl; | |
} | |
}; | |
class B : virtual public A {}; | |
class C : virtual public A {}; | |
class D : public B, public C {}; | |
int main() { | |
D d; | |
d.show(); // 调用 A 类的 show | |
return 0; | |
} |
# 8. 多态性
重点知识:
- 多态的定义和作用
- 虚函数和纯虚函数
- 动态绑定
例题:
- 什么是多态?如何实现多态?
- 编写一个基类
Animal和派生类Dog和Cat,基类中有一个虚函数speak,派生类分别重写该函数。
参考解答:
- 多态性允许在基类指针指向派生类对象时调用派生类的方法。通过虚函数实现。
class Animal { | |
public: | |
virtual void speak() const { | |
std::cout << "Animal sound" << std::endl; | |
} | |
}; | |
class Dog : public Animal { | |
public: | |
void speak() const override { | |
std::cout << "Woof" << std::endl; | |
} | |
}; | |
class Cat : public Animal { | |
public: | |
void speak() const override { | |
std::cout << "Meow" << std::endl; | |
} | |
}; | |
int main() { | |
Animal* a1 = new Dog(); | |
Animal* a2 = new Cat(); | |
a1->speak(); // Woof | |
a2->speak(); // Meow | |
delete a1; | |
delete a2; | |
return 0; | |
} | |
//---- 迭代器 iterators---- | |
Iterator begin(); // 返回向量第 1 个元素为迭代器起始 | |
Iterator end(); // 返回向量末尾元素为迭代器结束 | |
reverse_iterator rbegin(); // 返回向量末尾元素为逆向迭代器起始 | |
reverse_iterator rend(); // 返回向量第 1 个元素为逆向迭代器结束 | |
//---- 容量 capacity---- | |
size_type size(); // 返回向量元素数目 | |
size_type max_size(); // 返回向量能容纳的最大元素数目(长度) | |
void resize(size_type sz,T c=T()); // 重置向量长度为 sz,c 填充到扩充元素中 | |
size_type capacity(); // 返回向量容器存储空间大小 | |
bool empty(); // 测试向量是否为空 | |
void reserve(size_type n); // 为向量申请能容纳 n 个元素的空间 | |
int main() { | |
vector V1, V2; // 定义向量 | |
int A[]={1949,10,1} ,i; | |
vector::iterator It; | |
V1.assign(A,A+3); //V1: 1949 10 1 | |
V2.assign(3,10); //V2: 10 10 10 | |
//---- 元素存取 element access---- | |
operator[](size_type n); // 返回向量第 n 个位置元素的运算符,n 从 0 起 | |
at(size_type n); // 返回向量第 n 个位置元素,n 从 0 起 | |
front(); // 返回向量第 1 个元素 back (); // 返回向量末尾元素 | |
//---- 向量调节器 modifiers---- | |
void assign(size_type n,const T& u); // 向量赋 n 个 u 值 | |
void push_back(const T& x); // 增加一个元素到向量末尾 | |
void pop_back(); // 删除向量末尾元素 | |
void insert(iterator pos,size_type n,const T& x); // 在向量 pos 处插入 n 个元素值 x,pos 从 1 起 | |
iterator erase(iterator pos); // 删除向量指定位置的元素,pos 从 1 起 | |
void swap(vector& vec); // 与向量 vec 互换元素 | |
void clear(); // 清空向量 | |
for (i=1; i<=5; i++) V1.push_back(i); //V1: 1949 10 1 1 2 3 4 5 | |
V1.pop_back(); //V1: 1949 10 1 1 2 3 4 | |
V1.front() -= V1.back(); //V1: 1945 10 1 1 2 3 4 | |
for(It=V1.begin(); It<V1.end(); It++) V2.push_back(*It); // 遍历 V1 向量 | |
//V2: 10 10 10 1945 10 1 1 2 3 4 | |
V2.insert(V2.begin(),2,300); //V2: 300 300 10 10 10 1945 10 1 1 2 3 4 | |
V2.erase(V2.begin()+5); //V2: 300 300 10 10 10 10 1 1 2 3 4 | |
for(i=0;i<V2.size();i++) cout<V2[i]<<" "; // 输出 V2 向量元素 | |
return 0; | |
} | |
//---- 容量 capacity---- | |
bool empty(); // 测试是否为空队列 | |
size_type size(); // 返回队列长度 | |
//---- 元素存取 element access---- | |
front(); // 返回队头元素 | |
back(); // 返回队尾元素 | |
//---- 队列运算 operations---- | |
void push(const T& x); // 插入一个元素到队尾 | |
void pop(); // 删除队列下一个元素 | |
#include < iostream > | |
#include <queue> // 使用队列 | |
using namespace std; // 队列定义在 std 命名空间 | |
// 队列示例 | |
int main() { | |
queue Q; | |
for (int i=1;i<=6;i++) Q.push(i); // 进队 Q: 1 2 3 4 5 6 | |
Q.front() -= Q.back(); //Q: -5 2 3 4 5 6 | |
while (!Q.empty()) {cout<<Q.front()<<" "; Q.pop();} // 出队 | |
return 0; | |
} | |
//---- 容量 capacity---- | |
bool empty(); // 测试是否为空栈 | |
size_type size(); // 返回栈长度 | |
//---- 元素存取 element access---- | |
top(); // 返回栈顶元素 | |
//---- 栈运算 operations---- | |
void push(const T& x); // 进栈 | |
void pop(); // 出栈 | |
#include < iostream > | |
#include < stack >// 使用栈 | |
using namespace std; // 栈定义在 std 命名空间 | |
// 栈示例 | |
int main() { | |
stack S; | |
for (int i=1;i<=6;i++) S.push(i); // 进栈 S: 1 2 3 4 5 6 | |
while (!S.empty()) {cout<<S.top()<<" "; S.pop(); // 出栈} | |
return 0; | |
} | |
//---- 迭代器 iterators---- | |
iterator begin(); // 返回表头元素为迭代器起始 | |
iterator end(); // 返回表尾元素为迭代器结束 | |
reverse_iterator rbegin(); // 返回表尾元素为逆向迭代器起始 | |
reverse_iterator rend(); // 返回表头元素为逆向迭代器结束 | |
//---- 容量 capacity---- | |
bool empty(); // 测试是否为空表 | |
size_type size(); // 返回列表长度 | |
size_type max_size(); // 返回列表能容纳的最大长度 | |
void resize(size_type sz,T c=T()); // 重置列表长度为 sz,c 填充到扩充元素中 | |
//---- 元素存取 element access---- | |
front(); // 返回表头元素 | |
back(); // 返回表尾元素 | |
//---- 列表调节器 modifiers---- | |
void assign(size_type n,const T& u); // 列表赋 n 个 u 值 | |
void push_front(const T& x); // 插入一个元素到表头 | |
void pop_front(); // 删除表头元素 | |
void push_back(const T& x); // 增加一个元素到表尾 | |
void pop_back(); // 删除表尾元素 | |
// 在列表 pos 处插入 n 个元素值 x,pos 从 1 起 | |
void insert(iterator pos,size_type n,const T& x); | |
iterator erase(iterator pos); // 删除列表指定位置的元素,pos 从 1 起 | |
void swap(list& lst); // 与列表 lst 互换元素 | |
void clear(); // 清空列表 | |
//---- 列表运算 operations---- | |
void remove(const T& value); // 删除列表中值与 value 相同的所有元素 | |
void remove_if(Predicate pred); // 删除列表满足条件的元素 | |
void unique(); // 删除列表重复值 | |
void merge(list& x); // 合并列表 x,列表必须有序 | |
void sort(); // 列表排序 | |
void sort(Compare comp); // 列表按 comp 关系比较排序 | |
void reverse(); // 列表逆序 | |
void splice(iterator pos, list& y); // 列表拼接函数 | |
int main() { | |
int i, A[]={15,36,7,17}; | |
list::iterator It; | |
list L1, L2, L3(A,A+4); //L3: 15 36 7 17 | |
for(i=1; i<=6;i++) L1.push_back(i); //L1: 1 2 3 4 5 6 | |
for(i=1; i<=3; i++) L2.push_back(i*10); //L2: 10 20 30 | |
It=L1.begin(); advance(It,2); //It 指向 3 (第 3 个元素) | |
L1.splice(It,L2); //L1: 1 2 10 20 30 3 4 5 6 L2(empty) | |
//It 仍然指向 3 (第 6 个元素) | |
L2.splice(L2.begin(),L1,It); //L1:1 2 10 20 30 4 5 6 L2:3 | |
L1.remove(20); //L1: 1 2 10 30 4 5 6 | |
L1.sort(); //L1: 1 2 4 5 6 10 30 | |
L1.merge(L3); //L1: 1 2 4 5 6 10 15 30 36 7 17 | |
L1.push_front(L2.front()); //L1: 3 1 2 4 5 6 10 15 30 36 7 17 | |
L1.reverse(); //L1: 17 7 36 30 15 10 6 5 4 2 1 3 | |
for (It=L1.begin(); It!=L1.end(); ++It) cout<<*It<<" "; | |
return 0; } |
# Qt 对象模型复习笔记
# 1. 理解 3 个问题
# (1)对象树的原理和实现、创建和析构顺序
对象树是 Qt 提供自动、有效的组织和管理继承自 QObject 对象的一种机制。父对象与子对象相互指向,整体结构关系相当于一个森林。
对象树主要解决了两个问题:
- 内存管理:在 Qt 中,如果一个 QObject 对象(或任何继承自 QObject 的对象)有一个父对象,那么当父对象被删除时,所有的子对象也会被自动删除。这种机制允许开发者在不需要手动删除每一个对象的情况下,进行有效的内存管理。
- 事件传播:Qt 的事件系统使用对象树来决定如何传播事件。例如,如果一个点击事件发生在一个按钮上,那么这个事件首先会被发送到按钮对象。如果按钮对象没有处理这个事件,那么事件会被发送到按钮的父对象,然后是父对象的父对象,以此类推,直到事件被处理或者到达对象树的根。
原理和实现:
- 对象树是通过将一个对象指定为另一个对象的父对象来形成的。所有继承自
QObject的类都可以利用这种机制。 - 子对象在父对象的析构函数中自动删除,这样可以确保内存管理的简化和防止内存泄漏。
创建顺序:
父对象先创建,然后依次创建子对象。
例如:
QObject *parent = new QObject();
QObject *child1 = new QObject(parent);
QObject *child2 = new QObject(parent);
QObject *child1_1 = new QObject(child1);
QObject *child1_2 = new QObject(child1);
析构顺序:
父对象在析构时会自动删除所有的子对象。
例如:
delete parent;
这会删除
child1,child2,child1_1和child1_2,因为它们都是parent的子对象。
# (2)动态属性的原理与实现
属性系统提供开发者可以向 Qt 对象中添加自定义的属性,以便存储和访问特定的数据。
原理:
属性定义:基于元对象系统(Meta-Class System),利用宏 Q_PROPORTY (…) 标记相关属性信息,将其注册到 QMetaObject 中。
属性的使用:允许在运行时向 QObject 实例添加和删除动态属性,动态属性不需要在编译时声明,使用 API 操作(property () 和 setProperty () )实现获取和设置属性。
动态属性:函数 QObject::setProperty () 设置属性值时,如果属性名称不存在,就会为对象定义一个新的属性并设置属性值,这时定义的属性称为动态属性。
动态属性是针对类的实例定义的,所以只能使用函数 QObject::property () 读取动态属性的属性值。
动态属性允许在运行时向对象添加属性,而不需要在编译时定义这些属性。
实现:
使用
setProperty和property方法。QObject obj;obj.setProperty("dynamicProperty", 123);
int value = obj.property("dynamicProperty").toInt();
动态属性存储在一个内部的哈希表中,这样可以在不修改类定义的情况下扩展对象的属性。
# (3)信号与槽的实现原理、工作机制
1. 基本概念:基于观察者模式的设计模式,在 Qt 中采用信号 (Signals) 和槽 (Slots)机制用于 QObject 及其派生类对象之间的通信,所有继承自 QObject 或者它的一个子类(例如 QWidget)都可以包含信号和槽。
- 信号是一种特殊的函数,用于通知其他对象发生了某个特定的事件。信号可以在任何时候被发射,且可以带有参数。
- 槽是一种普通的成员函数,用于接收信号并执行相应的操作。当信号被发射时,与之相连接的槽函数会被自动调用。
- 信号槽函数本质上也是一个回调函数,用于不同对象之间的通信,当某个对象内部的状态发生变化时,其他对象如果需要对其状态变化做出相应,只需要让这个类发出对应状态改变的信号即可,其他对象接收到这个信号以后,调用对应的槽函数进行相应的动作处理。
- 将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,称为回调函数。
原理:
- 信号是类中定义的事件,槽是处理这些事件的函数。通过信号与槽的连接,可以实现对象之间的通信。
- 可以将任意多个信号连接到一个槽,也可以将一个信号连接到任意多个插槽,还可以将一个信号直接连接到另一个信号,这将在第一个信号发出时立即发出第二个信号。
工作机制:
使用
connect函数连接信号和槽。例如:
connect(senderObject, SIGNAL(signal()), receiverObject, SLOT(slot()));
当
signal发射时,slot就会被调用。Qt 使用元对象编译器(MOC)来处理信号与槽的连接,MOC 生成的代码管理这些连接和调用。在进行信号和槽的连接时,Qt 首先在元对象结构中找出要连接的信号和槽的索引,然后创建一个 QMetaObject::Connection 对象(下文简称 Connection)并添加到内部的链表中。由于存在一个信号连接多个槽的情况,因此 Connection 对象需要为每个 信号保存接收对象和槽的链表 QObjectPrivate::ConnectionList
所有连接到某个信号上的连接以单向链表的方式组织起来。
所有的信号则构成了一个链表向量类型 QObjectConnectionListVector 类型的变量 QObjectPrivate::connectionLists。
解除与一个发射者所有信号的连接:
QObject::disconnect(sender); |
在这个例子中, sender 是发射信号的对象。这个调用会解除 sender 发射的所有信号与其连接的所有槽的连接。
- 解除与一个特定信号的所有连接:
QObject::disconnect(sender, SIGNAL(someSignal())); |
在这个例子中, sender 是发射信号的对象, someSignal() 是要解除连接的信号。这个调用会解除 sender 发射的 someSignal 信号与其连接的所有槽的连接。
- 解除与一个特定接收者的所有连接:
QObject::disconnect(sender, 0, receiver, 0); |
在这个例子中, sender 是发射信号的对象, receiver 是接收信号的对象。这个调用会解除 sender 发射的所有信号与 receiver 的所有槽的连接。
- 解除特定的一个信号与槽的连接:
QObject::disconnect(sender, SIGNAL(someSignal()), receiver, SLOT(someSlot())); |
在这个例子中, sender 是发射信号的对象, someSignal() 是要解除连接的信号, receiver 是接收信号的对象, someSlot() 是要解除连接的槽。这个调用会解除 sender 发射的 someSignal 信号与 receiver 的 someSlot 槽的连接。
# 2. 理解 “Qt 元对象系统” 内容
# (1)元对象
QMetaObject 类的主要接口函数:
- 类的信息:如类名和基类名。
- 附加信息:如类的元数据、信号与槽信息等。
- 构造函数元数据:包含构造函数的信息。
- 方法元数据:包含类中所有方法的信息。
- 枚举类型元数据:包含类中所有枚举类型的信息。
- 属性元数据:包含类中所有属性的信息。
- 信号与槽:定义信号与槽的元数据和连接机制。
- 静态函数:提供类的一些静态信息和方法【6:5†source】。
运行时类型信息:
使用
QMetaObject::className获取类名。使用
QMetaObject::inherits判断一个对象是否继承自某个类。例如:
QPushButton *btn = new QPushButton();
bool result = btn->inherits("QPushButton");
属性系统:
- 定义属性并使用
setProperty和property方法进行访问和修改。 - 使用
Q_PROPERTY宏在类定义中声明属性。
- 定义属性并使用
信号与槽:
使用
connect()连接信号与槽,使用disconnect()断开连接。自定义信号槽规则:
class MyClass : public QObject {
Q_OBJECT
public:
signals:void mySignal();
public slots:
void mySlot();
};
# (2)对象树示例
示例代码:
QObject *parent = new QObject(); | |
QObject *child1 = new QObject(parent); | |
QObject *child2 = new QObject(parent); | |
QObject *child1_1 = new QObject(child1); | |
QObject *child1_2 = new QObject(child1); | |
delete parent; |
上述代码展示了对象树的创建和析构顺序,当 parent 被删除时,其子对象 child1 和 child2 以及 child1 的子对象 child1_1 和 child1_2 都会被自动删除。
# 例题及参考解答
例题:简述对象树的创建和析构顺序。
参考解答: 对象树的创建顺序是:先创建父对象,然后依次创建子对象。例如,创建一个父对象 parent 后,再创建两个子对象 child1 和 child2 ,它们都以 parent 为父对象。子对象可以继续拥有自己的子对象,例如 child1_1 和 child1_2 ,它们都是 child1 的子对象。当父对象 parent 被删除时,所有的子对象 child1 和 child2 以及它们的子对象 child1_1 和 child1_2 都会被自动删除。
# 事件系统复习笔记
# 1. Qt 事件系统的基本原理和处理流程
事件机制的基本原理是消息循环。应用程序通常在启动时会传递给操作系统一个回调函数指针(Qt 中是 qt_internal_proc),在有消息需要发送给应用程序时,操作系统会调用此回调函数用以处理消息。应用程序也会在启动后专门开辟一个线程和设定一个消息队列用于消息循环,不断把消息队列中的消息转发给操作系统。
Qt 使用 QAbstractEventDispatcher 类来管理消息分发与 Qt 事件队列,因此需要在线程内部创建一个事件分发器对象。在 currentThread () 函数内部,创建 QThreadData 类对象 threadData,并创建一个 QAdoptedThread 类实例 threadData->thread,并把这个 thread 赋予 QCoreApplicationPrivate::theMainThread,作为 Qt 应用程序的主线程。QCoreApplication 类中的线程,是运行程序时由操作系统系统创建的,它由 QAdoptedThread 类对象管理。QAdoptedThread 类是 QCoreApplication 类中的线程的线程包装器,它不需要创建线程,而是在其 init () 函数中通过 GetCurrentThread () 函数直接获取应用程序主线程所在的程序。Qt 程序运行后,在 QCoreApplication::exec () 中调用 QEventLoop::exec () 进入消息循环,等待和处理应用程序的消息。
- 原理:
- GUI 应用程序是事件驱动的,Qt 事件系统将事件(如鼠标点击、键盘按下等)转化为 QEvent 对象进行处理。
- 处理流程:
- 应用程序进入事件循环,通过
exec()启动。 - 事件循环从操作系统接收事件,分发给合适的对象处理。
- 应用程序进入事件循环,通过
# 2. 事件的产生和派发
- 事件类型:
- 自生事件(由操作系统产生,如 QKeyEvent、QMouseEvent)
- 发布事件(由 Qt 或应用程序产生,如 QTimerEvent)
- 发送事件 (sentevent): 是由 Qt 或应用程序定向发送给某个对象的事件。应用程序使用态函数 QCoreApplication::sendEvent () 产生发送事件,由对象的 event () 函数直接处理。
- 事件队列:
- 自生事件进入系统队列,发布事件进入 Qt 事件队列。
- 派发机制:
postEvent()发布事件。sendEvent()发送事件。
解决响应迟滞问题方法: ①多线程 例如一般的涉及网络大量数据传输的程序都会使用多线程,将界面更新与网络数据传输分别用两个线程去处理,这样就不会出现界面无响应的情况。
②调用静态函数 QCoreApplication::processEvents ()
在长时间占用 CPU 的代码段中,偶尔调用静态函数 processEvents (),将事件队列里未处理的事件派发出去,让事件接收对象及时处理,这样就不至于出现停滞的现象。
# 3. 事件过滤器
工作原理:
一个界面组件如果要对事件进行处理,需要从父类继承定义一个新类,在新类里编写程序直接处理事件,或者将事件转换为信号。
如果不想定义一个新的类,可以用事件过滤器 (eventflter) 对界面组件的事件进行处理。事件过滤器是 QObject 提供的一种处理事件的方法,它可以将一个对象的事件委托给另一个对象(称为事件过滤器)来监视并处理。
例如:一个窗口可以作为其界面上的 QLabel 组件的事件过滤器,派发给 QLabel 组件的事件由窗口去处理,不需要为了处理某种事件而新定义一个标签类。
实现:
- 使用
installEventFilter()为对象安装事件过滤器。 - 事件过滤器对象需要重实现
eventFilter()方法。
- 使用
例题: 简述事件过滤器的工作原理及实现方法。
参考解答: 事件过滤器用于拦截对象的事件,进行预处理。通过 installEventFilter() 方法为对象安装事件过滤器,事件过滤器对象需要重实现 eventFilter() 方法来处理或过滤事件。
# 事件系统示例程序
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
ui->labHover->installEventFilter(this);
ui->labDBClick->installEventFilter(this); // 安装事件过滤器
}
bool Widget::eventFilter(QObject *watched, QEvent *event) {
// 上面的 QLabel 组件的事件处理
if (watched == ui->labHover) {
if (event->type() == QEvent::Enter) // 鼠标光标移入
ui->labHover->setStyleSheet("background-color: rgb(170, 255, 255);");
else if (event->type() == QEvent::Leave) { // 鼠标光标离开
ui->labHover->setStyleSheet("");
ui->labHover->setText("靠近我,点击我");
}
else if (event->type() == QEvent::MouseButtonPress) // 鼠标键按下
ui->labHover->setText("button pressed");
else if (event->type() == QEvent::MouseButtonRelease)
ui->labHover->setText("button released");
}
// 下面的 QLabel 组件的事件处理
if (watched == ui->labDBClick) {
if (event->type() == QEvent::Enter) // 鼠标光标移入
ui->labDBClick->setStyleSheet("background-color: rgb(85, 255, 127);");
else if (event->type() == QEvent::Leave) { // 鼠标光标离开
ui->labDBClick->setStyleSheet("");
ui->labDBClick->setText("可双击的标签");
}
else if (event->type() == QEvent::MouseButtonDblClick) // 鼠标双击
ui->labDBClick->setText("double clicked");
}
return QWidget::eventFilter(watched, event); // 运行父类的 eventFilter() 函数
}
# 1. 基本事件处理
- 实现方法:
- 重写
event()方法处理事件。 - 常见事件处理函数如
mousePressEvent()、keyPressEvent()等。
- 重写
# 2. 事件与信号
- 关系:
- 某些事件(如按钮点击)可以触发信号,信号槽机制可用于响应这些事件。
例题: 如何使用信号槽机制处理按钮点击事件?
参考解答: 通过 connect() 方法将按钮的 clicked() 信号连接到槽函数。例如:
connect(button, &QPushButton::clicked, this, &MyClass::onButtonClicked); |
# 3. 事件过滤器示例
- 实现方法:
- 创建一个继承自 QObject 的类,重写
eventFilter()方法。 - 使用
installEventFilter()方法为目标对象安装该事件过滤器。
- 创建一个继承自 QObject 的类,重写
例题: 编写一个简单的事件过滤器,拦截并处理键盘按下事件。
参考解答:
class KeyPressFilter : public QObject | |
{ | |
Q_OBJECT | |
protected: | |
bool eventFilter(QObject *obj, QEvent *event) override | |
{ | |
if (event->type() == QEvent::KeyPress) { | |
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); | |
qDebug() << "Key pressed:" << keyEvent->key(); | |
return true; | |
} | |
return QObject::eventFilter(obj, event); | |
} | |
}; | |
// 使用示例 | |
KeyPressFilter *filter = new KeyPressFilter(this); | |
targetObject->installEventFilter(filter); |
# 线程同步复习笔记
# 1.QThread
# 1.1 基本概念
QThread类提供不依赖平台的管理线程的方法。- 设计多线程程序时,需要从
QThread继承定义线程类,并重定义QThread的虚函数run(),在函数run()里处理线程的事件循环。 ![image-20240608171244065]()
# 1.2 成员函数
isFinished(): 判断线程是否已结束。isRunning(): 判断线程是否正在运行。priority(): 返回线程的优先级。setPriority(QThread::Priority priority): 设置线程的优先级。wait(unsigned long time): 阻塞线程运行,直到线程结束或等待时间超过time毫秒。exit(int returnCode = 0): 退出线程的事件循环。quit(): 退出线程的事件循环。start(QThread::Priority priority=InheritPriority): 设置线程优先级并启动线程。terminate(): 终止线程运行。
# 1.3 基于 QThread 多线程编程示例
class TDiceThread : public QThread { | |
Q_OBJECT | |
private: | |
int m_seq = 0; | |
int m_diceValue; | |
bool m_paused = true; | |
bool m_stop = false; | |
protected: | |
void run(); | |
public: | |
explicit TDiceThread(QObject *parent = nullptr); | |
void diceBegin(); | |
void dicePause(); | |
void stopThread(); | |
signals: | |
void newValue(int seq, int diceValue); | |
}; | |
void TDiceThread::run() { | |
m_stop = false; | |
m_paused = true; | |
m_seq = 0; | |
while (!m_stop) { | |
if (!m_paused) { | |
m_diceValue = QRandomGenerator::global()->bounded(1, 7); | |
m_seq++; | |
emit newValue(m_seq, m_diceValue); | |
} | |
msleep(500); | |
} | |
quit(); | |
} |
# 2. 线程同步
# 2.1 基本概念
- 线程之间可能需要访问同一个变量,或者一个线程需要等待另一个线程完成某个操作后才产生相应的动作。
# 2.2 互斥量(QMutex)
QMutex用于锁定和解锁线程访问的共享资源。- 函数:
void lock(): 锁定互斥量。void unlock(): 解锁互斥量。bool tryLock(int timeout = 0): 尝试锁定互斥量。
class TDiceThread : public QThread { | |
Q_OBJECT | |
private: | |
QMutex mutex; | |
int m_seq = 0; | |
int m_diceValue; | |
bool m_paused = true; | |
bool m_stop = false; | |
protected: | |
void run(); | |
public: | |
explicit TDiceThread(QObject *parent = nullptr); | |
void diceBegin(); | |
void dicePause(); | |
void stopThread(); | |
bool readValue(int *seq, int *diceValue); | |
}; | |
void TDiceThread::run() { | |
m_stop = false; | |
m_paused = true; | |
m_seq = 0; | |
while (!m_stop) { | |
if (!m_paused) { | |
mutex.lock(); | |
m_diceValue = QRandomGenerator::global()->bounded(1, 7); | |
m_seq++; | |
mutex.unlock(); | |
emit newValue(m_seq, m_diceValue); | |
} | |
msleep(500); | |
} | |
quit(); | |
} | |
bool TDiceThread::readValue(int *seq, int *diceValue) { | |
if (mutex.tryLock(100)) { | |
*seq = m_seq; | |
*diceValue = m_diceValue; | |
mutex.unlock(); | |
return true; | |
} | |
return false; | |
} |
# 2.3 互斥锁定器(QMutexLocker)
QMutexLocker自动管理互斥量的锁定和解锁。- 在 QMutexLocker 实例的生命周期内,互斥量是锁定的,超出范围后自动解锁。
void TDiceThread::run() { | |
m_stop = false; | |
m_paused = true; | |
m_seq = 0; | |
while (!m_stop) { | |
if (!m_paused) { | |
QMutexLocker locker(&mutex); | |
m_diceValue = QRandomGenerator::global()->bounded(1, 7); | |
m_seq++; | |
emit newValue(m_seq, m_diceValue); | |
} | |
msleep(500); | |
} | |
quit(); | |
} |
# 2.4 信号量(QSemaphore)
QSemaphore用于限制对资源的访问数量。- 常用函数:
void acquire(int n = 1): 请求资源。void release(int n = 1): 释放资源。
# 例题与参考解答
# 例题 1
问题:实现一个简单的掷骰子程序,其中包含一个主线程和一个工作线程。主线程负责显示结果,工作线程负责生成随机数。
解答:
- 定义工作线程类
TDiceThread,继承自QThread,并重写run()函数。 - 在主线程中,创建
TDiceThread的实例,并启动和停止线程。 - 使用信号和槽机制,将工作线程的结果传递到主线程。
#include <QThread> | |
#include <QRandomGenerator> | |
#include <QMutex> | |
#include <QMainWindow> | |
#include <QApplication> | |
class TDiceThread : public QThread { | |
Q_OBJECT | |
private: | |
QMutex mutex; | |
int m_seq = 0; | |
int m_diceValue; | |
bool m_paused = true; | |
bool m_stop = false; | |
protected: | |
void run() override; | |
public: | |
explicit TDiceThread(QObject *parent = nullptr); | |
void diceBegin(); | |
void dicePause(); | |
void stopThread(); | |
signals: | |
void newValue(int seq, int diceValue); | |
}; | |
void TDiceThread::run() { | |
m_stop = false; | |
m_paused = true; | |
m_seq = 0; | |
while (!m_stop) { | |
if (!m_paused) { | |
QMutexLocker locker(&mutex); | |
m_diceValue = QRandomGenerator::global()->bounded(1, 7); | |
m_seq++; | |
emit newValue(m_seq, m_diceValue); | |
} | |
msleep(500); | |
} | |
quit(); | |
} | |
TDiceThread::TDiceThread(QObject *parent) : QThread(parent) {} | |
void TDiceThread::diceBegin() { m_paused = false; } | |
void TDiceThread::dicePause() { m_paused = true; } | |
void TDiceThread::stopThread() { m_stop = true; } | |
class MainWindow : public QMainWindow { | |
Q_OBJECT | |
private: | |
TDiceThread *threadA; | |
public: | |
MainWindow(QWidget *parent = nullptr); | |
~MainWindow(); | |
private slots: | |
void onNewValue(int seq, int diceValue); | |
}; | |
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), threadA(new TDiceThread(this)) { | |
connect(threadA, &TDiceThread::newValue, this, &MainWindow::onNewValue); | |
threadA->start(); | |
} | |
MainWindow::~MainWindow() { | |
threadA->stopThread(); | |
threadA->wait(); | |
} | |
void MainWindow::onNewValue(int seq, int diceValue) { | |
qDebug("第 %d 次掷骰子,点数为:%d", seq, diceValue); | |
} | |
int main(int argc, char *argv[]) { | |
QApplication a(argc, argv); | |
MainWindow w; | |
w.show(); | |
return a.exec(); | |
} |
这段代码展示了如何创建一个多线程应用,其中主线程和工作线程通过信号和槽机制进行通信,实现了线程间的数据同步。
# 线程同步的各种实现方式及其特点
# 1. 互斥量(QMutex)
特点:
- 用于在线程之间保护共享资源,以防止多个线程同时访问同一资源。
- 提供两种锁定模式:普通模式(非递归)和递归模式(可以多次锁定)。
- 使用简单,效率高,适用于短时间的资源锁定。
常用函数:
lock(): 锁定互斥量。如果其他线程已经锁定了互斥量,则调用线程将阻塞,直到互斥量变得可用。tryLock(int timeout = 0): 尝试锁定互斥量。如果锁定成功则返回true,否则返回false。可以设置超时时间。unlock(): 解锁互斥量。
示例代码:
QMutex mutex; | |
void someFunction() { | |
mutex.lock(); | |
// 访问共享资源 | |
mutex.unlock(); | |
} |
# 2. 互斥锁定器(QMutexLocker)
特点:
QMutexLocker是一个帮助类,用于简化互斥量的使用。- 自动管理互斥量的锁定和解锁,防止因异常或函数返回导致的死锁问题。
- 使用
QMutexLocker时,只需在需要同步的代码块前创建一个QMutexLocker对象即可。
常用函数:
QMutexLocker(QMutex *mutex): 构造函数,自动锁定传入的互斥量。~QMutexLocker(): 析构函数,自动解锁互斥量。
示例代码:
QMutex mutex; | |
void someFunction() { | |
QMutexLocker locker(&mutex); | |
// 访问共享资源 | |
} // 离开作用域时,自动解锁 |
# 3. 读写锁(QReadWriteLock)
特点:
- 允许多个线程同时读取数据,但写入时只能有一个线程。
- 提供了比互斥量更高的并发性,适用于读多写少的场景。
- 支持递归读锁和递归写锁。
常用函数:
lockForRead(): 锁定读锁,允许多个线程同时读取。lockForWrite(): 锁定写锁,只允许一个线程写入。unlock(): 解锁读写锁。
示例代码:
QReadWriteLock rwLock; | |
void readFunction() { | |
rwLock.lockForRead(); | |
// 读取共享资源 | |
rwLock.unlock(); | |
} | |
void writeFunction() { | |
rwLock.lockForWrite(); | |
// 写入共享资源 | |
rwLock.unlock(); | |
} |
# 4. 等待条件(QWaitCondition)
特点:
- 用于在线程之间进行条件同步。
- 允许线程在特定条件下等待,并在条件满足时继续执行。
- 通常与
QMutex一起使用。
常用函数:
wait(QMutex *mutex, unsigned long time = ULONG_MAX): 释放互斥量并等待条件满足。wakeOne(): 唤醒一个等待的线程。wakeAll(): 唤醒所有等待的线程。
示例代码:
QMutex mutex; | |
QWaitCondition condition; | |
void producer() { | |
mutex.lock(); | |
// 生产数据 | |
condition.wakeOne(); | |
mutex.unlock(); | |
} | |
void consumer() { | |
mutex.lock(); | |
condition.wait(&mutex); | |
// 消费数据 | |
mutex.unlock(); | |
} |
# 5. 信号量(QSemaphore)
特点:
- 用于控制线程对资源的访问量。
- 可以控制同时访问某资源的线程数量。
- 在资源有限的情况下非常有用,比如限制对某个连接池的访问。
常用函数:
acquire(int n = 1): 请求n个资源,如果资源不足则等待。release(int n = 1): 释放n个资源,增加可用资源的数量。
示例代码:
QSemaphore semaphore(5); // 最大允许 5 个线程同时访问资源 | |
void worker() { | |
semaphore.acquire(); | |
// 访问资源 | |
semaphore.release(); | |
} |
# 线程同步总结
- 互斥量 用于保护共享资源,防止多个线程同时访问,适用于短时间锁定。
- 互斥锁定器 自动管理互斥量锁定和解锁,防止死锁。
- 读写锁 允许多个线程同时读取,但写入时只能有一个线程,适用于读多写少的场景。
- 等待条件 通过条件变量实现线程之间的同步等待,通常与互斥量一起使用。
- 信号量 控制线程对资源的访问量,适用于限制资源访问的场景。
通过理解和应用这些线程同步机制,可以编写出高效且安全的多线程程序。
# 3. 线程池
线程池类 QThreadPool 是可重用的 QThread 的集合,线程池的作用是避免频繁创建和销毁线程所带来的系统开销。
在一个 QThreadPool 的线程中运行代码,一般可以继承 QRunnable 类并重写其 run () 方法,然后通过 QThreadPool::start () 将 QRunnable 子类对象放到 QThreadPool 的运行队列中。
每个 Qt 应用程序都有一个全局线程池,可以通过 QThreadPool::globalInstance () 访问,Qt 也允许开发人员创建和管理自己的 QThreadPool 类对象
# Qt 绘图复习笔记
# 重点掌握内容:坐标系统与坐标变换、图形视图架构的含义
# 目录
- QPainter 绘图
- QPainterPath 的使用
- 坐标系统与坐标变换
- 坐标变换:平移、旋转、缩放、状态保存与恢复
- 视口和窗口:视口和窗口定义、物理坐标和逻辑坐标
- 图形视图架构
- 场景、视图与图形项定义
- 图形 / 视图架构的坐标系
- 图像处理
- 颜色数据的格式
- 图像格式
- 访问颜色数据
# 1. QPainter 绘图
# 1.1 基本概念
QPainter类用于在小部件、图片和其他绘图设备上进行绘图操作。QPainter支持高级的 2D 绘图特性,包括反走样、路径、渐变和透明度。
# 1.2 QPainterPath 的使用
QPainterPath类用于构建和操作复杂的路径。- 支持多个子路径,包括直线、曲线和闭合路径。
示例代码:
void Widget::paintEvent(QPaintEvent *event) | |
{ | |
QPainter painter(this); | |
painter.setRenderHint(QPainter::Antialiasing); | |
painter.setRenderHint(QPainter::TextAntialiasing); | |
// 生成五角星的 5 个顶点,假设原点在五角星中心 | |
qreal R=100; // 半径 | |
const qreal Pi=3.14159;372 | |
qreal deg=Pi*72/180; | |
QPoint points[5]={ QPoint(R,0), | |
QPoint(R*qCos(deg), -R*qSin(deg)), | |
QPoint(R*qCos(2*deg), -R*qSin(2*deg)), | |
QPoint(R*qCos(3*deg), -R*qSin(3*deg)), | |
QPoint(R*qCos(4*deg), -R*qSin(4*deg)) }; | |
// 设置字体 | |
QFont font; | |
font.setPointSize(14); | |
painter.setFont(font); | |
// 设置画笔 | |
QPen penLine; | |
penLine.setWidth(2); | |
penLine.setColor(Qt::blue); | |
penLine.setStyle(Qt::SolidLine); | |
penLine.setCapStyle(Qt::FlatCap); | |
penLine.setJoinStyle(Qt::BevelJoin); | |
painter.setPen(penLine); | |
// 设置画刷 | |
QBrush brush; | |
brush.setColor(Qt::yellow); | |
brush.setStyle(Qt::SolidPattern); // 画刷填充样式 | |
painter.setBrush(brush); | |
// 设计绘制五角星的 PainterPath,以便重复使用 | |
QPainterPath starPath; | |
starPath.moveTo(points[0]); | |
starPath.lineTo(points[2]); | |
starPath.lineTo(points[4]); | |
starPath.lineTo(points[1]); | |
starPath.lineTo(points[3]); | |
starPath.closeSubpath(); // 闭合路径,最后一个点与第一个点相连 | |
starPath.addText(points[0],font,"1"); // 显示顶点编号 | |
starPath.addText(points[1],font,"2"); | |
starPath.addText(points[2],font,"3"); | |
starPath.addText(points[3],font,"4"); | |
starPath.addText(points[4],font,"5"); | |
// 绘图,第一个五角星 | |
painter.save(); // 保存坐标状态 | |
painter.translate(100,120); | |
painter.drawPath(starPath); // 画五角星 | |
painter.drawText(0,0,"S1"); | |
painter.restore(); // 恢复坐标状态 | |
// 第二个五角星 | |
painter.translate(300,120); // 平移 | |
painter.scale(0.8,0.8); // 缩放 | |
painter.rotate(90); // 顺时针旋转 | |
painter.drawPath(starPath); // 画五角星 | |
painter.drawText(0,0,"S2"); | |
// 第三个五角星 | |
painter.resetTransform(); // 复位所有坐标变换 | |
painter.translate(500,120); // 平移 | |
painter.rotate(-145); // 逆时针旋转 | |
painter.drawPath(starPath); // 画五角星 10.2 坐标系统和坐标变换 373 | |
painter.drawText(0,0,"S3"); | |
event->accept(); | |
} |
# 2. 坐标系统与坐标变换
# 2.1 坐标变换
# 平移(Translation)
示例:
QPainter painter(this); | |
painter.translate(50, 50); | |
painter.drawRect(0, 0, 100, 100); |
# 旋转(Rotation)
示例:
QPainter painter(this); | |
painter.rotate(45); | |
painter.drawRect(0, 0, 100, 100); |
# 缩放(Scaling)
示例:
QPainter painter(this); | |
painter.scale(2, 2); | |
painter.drawRect(0, 0, 100, 100); |
# 状态保存与恢复
示例:
QPainter painter(this); | |
painter.save(); | |
painter.rotate(45); | |
painter.drawRect(0, 0, 100, 100); | |
painter.restore(); | |
painter.drawRect(50, 50, 100, 100); |
# 2.2 视口和窗口
# 定义
- 视口(Viewport):是显示在设备上的区域。
- 窗口(Window):是逻辑坐标系中的区域。
- 视口是指绘图设备的任意一个矩形区域,它使用物理坐标系。可以只选取物理坐标系中的一个矩形区域来绘图,默认情况下,视口等于绘图设备的整个矩形区域。窗口与视口是同一个矩形区域, 但是窗口是用逻辑坐标系定义的,窗口可以直接定义矩形区域的逻辑坐标范围。
# 示例代码
QPainter painter(this); | |
painter.setViewport(10, 10, 200, 200);// 定义视口 | |
painter.setWindow(-50, -50, 100, 100); | |
painter.drawRect(0, 0, 50, 50); |
# 3. 图形视图架构
# 3.1 场景、视图与图形项定义
场景(Scene):
QGraphicsScene用于管理和操作图形项。场景是一个抽象的管理图形项的容器,可以向场景添加图形项,可以获取场景中的某个图形项。场景不是界面组件,它是不可见的。
QGraphicsScene 类提供绘图场景 (scene), 它的父类是 QObject。
视图(View):
QGraphicsView用于显示场景中的内容。视图用于显示场景中的内容。QGraphicsView 是图形 / 视图架构中的视图组件。QGraphicsView 的间接父类是 QWidget,它是一个界面组件。
一个场景可以设置多个视图,用于提供不同的显示界面。视图接收键盘和鼠标输入并转换为场景的事件,进行坐标变换后这些事件被传送给可视场景。
图形项(Item):
QGraphicsItem是场景中的基本绘图元素。图形项是一些基本图形组件,相当于模型中的数据。一个图形项存储了绘制这个图形项的各种参数,场景管理所有图形项,而视图组件则负责绘制图形项。
一个图形项可以包含子图形项,图形项还支持碰撞检测,即检测是否与其他图形项碰撞。
# 3.2 图形 / 视图架构的坐标系
# 坐标原点关系
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent), ui(new Ui::MainWindow) | |
{ | |
ui->setupUi(this); | |
labViewCord= new QLabel("View 坐标: ", this); | |
labViewCord->setMinimumWidth(150); | |
ui->statusBar->addWidget(labViewCord); | |
labSceneCord= new QLabel("Scene 坐标: ", this); | |
labSceneCord->setMinimumWidth(150); | |
ui->statusBar->addWidget(labSceneCord); | |
labItemCord= new QLabel("Item 坐标: ", this); | |
labItemCord->setMinimumWidth(150); | |
ui->statusBar->addWidget(labItemCord); | |
//TGraphicsView 组件 view 的设置 | |
ui->view->setCursor(Qt::CrossCursor); // 十字形光标 | |
ui->view->setMouseTracking(true); // 开启鼠标跟踪 | |
ui->view->setDragMode(QGraphicsView::RubberBandDrag); // 矩形选择框 | |
connect(ui->view,SIGNAL(mouseMovePoint(QPoint)), | |
this, SLOT(do_mouseMovePoint(QPoint))); | |
connect(ui->view,SIGNAL(mouseClicked(QPoint)), | |
this, SLOT(do_mouseClicked(QPoint))); | |
iniGraphicsSystem(); // 图形 / 视图架构初始化 | |
} | |
void MainWindow::iniGraphicsSystem() | |
{ | |
QRectF rect(-200,-100,400,200); | |
scene= new QGraphicsScene(rect,this); // 场景坐标系定义 | |
ui->view->setScene(scene); // 为视图设置场景 | |
// 画一个矩形框,其大小等于场景的大小 | |
QGraphicsRectItem *item= new QGraphicsRectItem(rect); | |
// 矩形框的大小正好等于场景的大小 | |
// 可选择,可以获得焦点,但是不能移动 | |
item->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable); | |
QPen pen; | |
pen.setWidth(2);10.3 图形/视图架构 385 | |
item->setPen(pen); | |
scene->addItem(item); | |
// 一个位于场景中心的椭圆,测试局部坐标 | |
QGraphicsEllipseItem *item2=new QGraphicsEllipseItem(-100,-50,200,100); | |
// 矩形框内创建椭圆,图形项的局部坐标,左上角坐标为 (-100,-50),宽 200,高 100 | |
item2->setPos(0,0);// 图形项在场景中的坐标 | |
item2->setBrush(QBrush(Qt::blue)); | |
item2->setFlag(QGraphicsItem::ItemIsMovable); // 能移动 | |
item2->setFlag(QGraphicsItem::ItemIsSelectable); // 可选择 | |
item2->setFlag(QGraphicsItem::ItemIsFocusable); // 可以获得焦点 | |
scene->addItem(item2); | |
// 一个圆,中心位于场景的边缘 | |
QGraphicsEllipseItem *item3=new QGraphicsEllipseItem(-50,-50,100,100); | |
// 矩形框内创建圆,图形项的局部坐标,左上角坐标为 (-100,-50),宽 200,高 100 | |
item3->setPos(rect.right(),rect.bottom()); // 图形项在场景中的坐标 | |
item3->setBrush(QBrush(Qt::red)); | |
item3->setFlag(QGraphicsItem::ItemIsMovable); // 能移动 | |
item3->setFlag(QGraphicsItem::ItemIsSelectable); // 可选择 | |
item3->setFlag(QGraphicsItem::ItemIsFocusable); // 可以获得焦点 | |
scene->addItem(item3); | |
scene->clearSelection(); | |
} | |
void MainWindow::do_mouseMovePoint(QPoint point) | |
{ | |
labViewCord->setText(QString::asprintf("View 坐标: %d,%d",point.x(),point.y())); | |
QPointF pointScene= ui->view->mapToScene(point); // 变换为场景坐标 | |
labSceneCord->setText(QString::asprintf("Scene 坐标: %.0f,%.0f",386 第 10 章 绘图 | |
pointScene.x(),pointScene.y())); | |
} | |
void MainWindow::do_mouseClicked(QPoint point) | |
{ | |
QPointF pointScene= ui->view->mapToScene(point); // 变换为场景坐标 | |
QGraphicsItem *item= NULL; | |
item= scene->itemAt(pointScene,ui->view->transform()); // 获取光标处的图形项 | |
if (item != NULL) | |
{ | |
QPointF pointItem= item->mapFromScene(pointScene); // 变换为图形项的局部坐标 | |
labItemCord->setText(QString::asprintf("Item 坐标: %.0f,%.0f", | |
pointItem.x(),pointItem.y())); | |
} | |
} | |
void MainWindow::resizeEvent(QResizeEvent *event) | |
{ | |
QString str= QString::asprintf("Graphics View 坐标, 左上角总是(0,0),宽度=%d, 高度=%d", | |
ui->view->width(),ui->view->height()); | |
ui->labViewSize->setText(str); | |
QRectF rectF= ui->view->sceneRect(); // 场景的矩形区 | |
QString str2= QString::asprintf("QGraphicsView::sceneRect=(Left,Top,Width,Height)" | |
"=%.0f,%.0f,%.0f,%.0f", rectF.left(), rectF.top(), rectF.width(), rectF.height()); | |
ui->labSceneRect->setText(str2); | |
event->accept(); | |
} |
graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);graphicsView->setAlignment(Qt::AlignCenter);// 缺省值graphicsSence->setSceneRect(-100, -100, 100, 100);
示例代码:
QGraphicsScene *scene = new QGraphicsScene(); | |
scene->setSceneRect(-100, -100, 200, 200); | |
QGraphicsView *view = new QGraphicsView(scene); | |
view->setAlignment(Qt::AlignLeft | Qt::AlignTop); | |
QGraphicsRectItem *item = new QGraphicsRectItem(-50, -50, 100, 100); | |
scene->addItem(item); |
# 4. 图像处理
# 4.1 颜色数据的格式
图像 (image) 是在各种绘图设备上显示的二维的图,图像的数据可以看作二维数组,数组的每个元素就是 1 个像素的颜色数据,在绘图设备上显示图像就是设置每个像素的颜色。任何颜色都是红色、绿色、蓝色三原色的组合。1 个像素的颜色数据有多种表示格式,常见的有以下几种:
- RGB:红、绿、蓝三通道颜色模型。
- ARGB:带有透明度通道的 RGB 模型。
# 4.2 图像格式
- 常见的图像格式包括 BMP、JPEG、PNG、GIF 等。
# 4.3 访问颜色数据
QImage类用于图像的表示和处理。- 提供多种访问颜色数据的方法。
示例代码:
QImage image("example.png"); | |
// 访问单个像素的颜色值 | |
QRgb color = image.pixel(10, 10); | |
int red = qRed(color); | |
int green = qGreen(color); | |
int blue = qBlue(color); | |
int alpha = qAlpha(color); | |
// 修改单个像素的颜色值 | |
image.setPixel(10, 10, qRgb(255, 0, 0)); | |
void MainWindow::on_actFile_Open_triggered() | |
{//“打开” 按钮 | |
QString curPath= QDir::currentPath(); // 应用程序当前目录 | |
QString filter= "图片文件(*.bmp *.jpg *.png);;" | |
"BMP 文件(*.bmp);;JPG 文件(*.jpg);;PNG 文件(*.png)"; | |
QString fileName= QFileDialog::getOpenFileName(this,"选择图片文件",curPath,filter); | |
if (fileName.isEmpty()) | |
return; | |
ui->statusbar->showMessage(fileName); | |
m_filename= fileName; // 保存当前图片文件名 | |
QFileInfo fileInfo(fileName); | |
QDir::setCurrent(fileInfo.absolutePath()); | |
m_image.load(fileName); // 加载图片文件 | |
QPixmap pixmap= QPixmap::fromImage(m_image); // 创建 QPixmap 对象用于界面显示 | |
ui->labPic->setPixmap(pixmap); | |
ui->tabWidget->setCurrentIndex(0); | |
showImageFeatures(); // 显示图片属性 | |
} | |
(1)图像缩放。图像缩放可以使用函数 scaled()来实现,该函数原型定义如下: | |
QImage QImage::scaled(int width, int height, | |
Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio,Qt::TransformationMode transformMode = Qt::FastTransformation) | |
其中,参数 width 和 height 分别是缩放后的新图像的宽度和高度,单位是像素;参数 aspectRatioMode 控制是否保持图像的长宽比,默认值 Qt::IgnoreAspectRatio 表示忽略长宽比,也可以设置为保持长宽比,也就是设置值为 Qt::KeepAspectRatio; 参数 transformMode 表示变换模式,默认值 Qt::FastTransformation表示快速转换,不做平滑处理,也可以设置值为 Qt::SmoothTransformation,表示进行平滑处理。 | |
函数 scaled()返回缩放后的图像副本,原图像不变。 | |
示例程序中将缩放后的图像又保存到原图像,所以会改变图像的物理尺寸,需要调用函数 showImageFeatures(false)显示图像尺寸信息。 | |
图像缩放还可以使用函数 scaledToHeight()和 scaledToWidth()来实现,它们分别用于指定高度或宽度进行缩放,函数原型定义如下: | |
QImage QImage::scaledToHeight(int height,Qt::TransformationMode mode = Qt::FastTransformation) | |
QImage QImage::scaledToWidth(int width,Qt::TransformationMode mode = Qt::FastTransformation) | |
(2)图像旋转。函数 transformed()可以通过一个变换矩阵对图像进行任意的变换,其函数原型定义如下: | |
QImage QImage::transformed(const QTransform &matrix,Qt::TransformationMode mode = Qt::FastTransformation)10.4 图像处理 399 | |
参数 matrix 是 QTransform 类型的变换矩阵。变换矩阵是一个 3×3 的矩阵,可以表示坐标系的平移、缩放、旋转等坐标变换运算关系。变换矩阵的具体原理和用法可以查看 QTransform 的帮助文档。 | |
(3)图像镜像。函数 mirror()可以对图像进行镜像处理,该函数定义如下: | |
void QImage::mirror(bool horizontal = false, bool vertical = true) | |
其中,参数 horizontal 表示是否进行水平镜像,参数 vertical 表示是否进行垂直镜像。函数没有返回值,直接修改原图像。还有一个函数 mirrored()可以对图像进行镜像处理,它返回处理后的图像副本,不修改原图像。 | |
QImage QImage::mirrored(bool horizontal = false, bool vertical = true) |
# 例题与参考解答
# 例题 1:QPainter 绘图
问题:使用 QPainter 在窗口上绘制一个路径,该路径包含一条直线和一条贝塞尔曲线。
解答:
void paintEvent(QPaintEvent *event) { | |
QPainter painter(this); | |
QPainterPath path; | |
path.moveTo(20, 20); | |
path.lineTo(100, 100); | |
path.cubicTo(150, 0, 250, 200, 300, 100); | |
painter.drawPath(path); | |
} |
# 例题 2:坐标变换
问题:在窗口上绘制一个经过平移、旋转和缩放的矩形,并在操作前后保存和恢复状态。
解答:
void paintEvent(QPaintEvent *event) { | |
QPainter painter(this); | |
// 保存状态 | |
painter.save(); | |
painter.translate(50, 50); | |
painter.rotate(45); | |
painter.scale(2, 2); | |
painter.drawRect(0, 0, 100, 100); | |
// 恢复状态 | |
painter.restore(); | |
painter.drawRect(150, 150, 100, 100); | |
} |
# 例题 3:图形视图架构
问题:创建一个场景,并在其中添加一个矩形项,通过视图显示该场景,并将视图对齐到左上角。
解答:
QGraphicsScene *scene = new QGraphicsScene(); | |
scene->setSceneRect(-100, -100, 200, 200); | |
QGraphicsView *view = new QGraphicsView(scene); | |
view->setAlignment(Qt::AlignLeft | Qt::AlignTop); | |
QGraphicsRectItem *item = new QGraphicsRectItem(-50, -50, 100, 100); | |
scene->addItem(item); |

平台集成 QPlatformIntegration 在 Windows、Linux 等操作系统上都有各自的具体实现。在 Windows 平台上还有 GDI 和 Direct2D 两种方式,平台集成对象保存在 QGuiApplicationPrivate 类的成员 platform_integration 中。QPlatformScreen 描述监视器,一台计算机可能连接多台监视器。当发现监视器时,Qt 会根据监视器信息生成 QPlatformScreen 类对象。在 Windows 平台上 QPlatformScreen 对象保存在 QWindowsIntegration 中的上下文 QWindowsContext 类对象 m_context 里面监视器管理器 QWindowsScreenManager 类的对象 m_screenManager,在监视器管理器中保存 QPlatformScreen 类型的监视器列表 .m_screens。同时根据得到的 QPlatformScreen 对象创建 Qscreen 实例,并存放在到 QWindowsScreenManager::m_screens 列表中。最后把 QSreen 对象存放到 QGuiApplicationPrivate::screen_list 列表中。
# 界面组件复习笔记
# 重点掌握内容:日期时间数据、QTimer、QComboBox、QMainWindow、QAction、QListWidget、QTreeWidget、QTableWidget 的使用
# 目录
- 界面组件概述
- QString 字符串操作
- QSpinBox
- 按钮组件
- QSlider
- 日期时间数据
- QTimer
- QComboBox
- QMainWindow 和 QAction
- QListWidget
- QTreeWidget
- QTableWidget
# 1. 界面组件概述
- Qt 提供了丰富的界面组件,用于构建 GUI 应用程序。
- 每个组件都有其特定的属性和方法,可以通过信号和槽机制进行交互。
# 2. QString 字符串操作
QString是 Qt 中用于处理字符串的类,提供了丰富的字符串操作函数。
常用方法:
arg(): 格式化字符串。toUpper()/toLower(): 转换大小写。trimmed(): 去除两端空白字符。
示例代码:
QString str = "Hello, %1!"; | |
str = str.arg("World"); | |
qDebug() << str; // 输出: Hello, World! |
# 3. QSpinBox
QSpinBox是一个用于输入和显示整数值的组件,带有上下按钮用于增加和减少值。
常用方法:
setRange(int min, int max): 设置范围。value(): 获取当前值。setValue(int value): 设置当前值。
示例代码:
QSpinBox *spinBox = new QSpinBox(this); | |
spinBox->setRange(0, 100); | |
spinBox->setValue(50); |
# 4. 按钮组件
- 常用的按钮组件包括
QPushButton、QRadioButton和QCheckBox。
示例代码:
QPushButton *button = new QPushButton("Click Me", this); | |
connect(button, &QPushButton::clicked, this, &MainWindow::handleButtonClick); |
# 5. QSlider
QSlider是一个滑动条组件,用于选择一个范围内的值。
常用方法:
setOrientation(Qt::Orientation): 设置方向(水平或垂直)。setValue(int value): 设置当前值。value(): 获取当前值。
示例代码:
QSlider *slider = new QSlider(Qt::Horizontal, this); | |
slider->setRange(0, 100); | |
slider->setValue(50); |
# 6. 日期时间数据
- Qt 提供了
QDate、QTime和QDateTime类用于处理日期和时间。
常用方法:
currentDate(): 获取当前日期。currentTime(): 获取当前时间。toString(): 将日期 / 时间转换为字符串。
示例代码:
QDate date = QDate::currentDate(); | |
QTime time = QTime::currentTime(); | |
qDebug() << date.toString() << time.toString(); |
# 7. QTimer
QTimer类用于触发定时事件。
常用方法:
start(int msec): 启动定时器,间隔时间为毫秒。stop(): 停止定时器。timeout(): 超时信号。
示例代码:
QTimer *timer = new QTimer(this); | |
connect(timer, &QTimer::timeout, this, &MainWindow::update); | |
timer->start(1000); // 每秒更新一次 |
# 8. QComboBox
QComboBox是一个组合框组件,提供下拉列表供用户选择。
常用方法:
addItem(const QString &text): 添加项。currentIndex(): 获取当前索引。currentText(): 获取当前文本。
示例代码:
QComboBox *comboBox = new QComboBox(this); | |
comboBox->addItem("Option 1"); | |
comboBox->addItem("Option 2"); | |
connect(comboBox, &QComboBox::currentIndexChanged, this, &MainWindow::handleSelectionChanged); |
# 9. QMainWindow 和 QAction
# 9.1 QMainWindow
QMainWindow是一个主窗口类,提供了一个主界面框架,包括菜单栏、工具栏、状态栏和中央小部件。
常用方法:
setCentralWidget(QWidget *widget): 设置中央小部件。statusBar(): 获取状态栏。
示例代码:
QMainWindow *mainWindow = new QMainWindow(this); | |
QWidget *centralWidget = new QWidget(mainWindow); | |
mainWindow->setCentralWidget(centralWidget); | |
mainWindow->statusBar()->showMessage("Ready"); |
# 9.2 QAction
QAction类用于表示菜单项、工具栏按钮等的动作。
常用方法:
setText(const QString &text): 设置文本。setShortcut(const QKeySequence &shortcut): 设置快捷键。triggered(): 触发信号。
示例代码:
QAction *action = new QAction("Open", this); | |
action->setShortcut(QKeySequence::Open); | |
connect(action, &QAction::triggered, this, &MainWindow::openFile); | |
QMenu *fileMenu = menuBar()->addMenu("File"); | |
fileMenu->addAction(action); |
# 10. QListWidget
QListWidget是一个用于显示列表项的组件,继承自QListView。
常用方法:
addItem(const QString &text): 添加项。currentItem(): 获取当前项。
示例代码:
QListWidget *listWidget = new QListWidget(this); | |
listWidget->addItem("Item 1"); | |
listWidget->addItem("Item 2"); | |
connect(listWidget, &QListWidget::itemClicked, this, &MainWindow::handleItemClick); |
# 11. QTreeWidget
QTreeWidget是一个用于显示树状结构数据的组件,继承自QTreeView。
常用方法:
addTopLevelItem(QTreeWidgetItem *item): 添加顶级项。currentItem(): 获取当前项。
示例代码:
QTreeWidget *treeWidget = new QTreeWidget(this); | |
QTreeWidgetItem *item1 = new QTreeWidgetItem(treeWidget, QStringList("Item 1")); | |
QTreeWidgetItem *item2 = new QTreeWidgetItem(item1, QStringList("Child Item 1")); | |
treeWidget->addTopLevelItem(item1); |
# 12. QTableWidget
QTableWidget是一个用于显示表格数据的组件,继承自QTableView。
常用方法:
setRowCount(int rows): 设置行数。setColumnCount(int columns): 设置列数。setItem(int row, int column, QTableWidgetItem *item): 设置项。
示例代码:
QTableWidget *tableWidget = new QTableWidget(3, 3, this); | |
tableWidget->setItem(0, 0, new QTableWidgetItem("Cell 0,0")); | |
tableWidget->setItem(0, 1, new QTableWidgetItem("Cell 0,1")); | |
tableWidget->setItem(0, 2, new QTableWidgetItem("Cell 0,2")); |
# 编程示例程序
# 示例 1:QTimer 使用
问题:创建一个窗口,包含一个定时器,每秒更新一次显示的文本。
解答:
#include <QApplication> | |
#include <QWidget> | |
#include <QLabel> | |
#include <QTimer> | |
class TimerWidget : public QWidget { | |
Q_OBJECT | |
public: | |
TimerWidget(QWidget *parent = nullptr) : QWidget(parent) { | |
label = new QLabel("Time: 0", this); | |
label->setAlignment(Qt::AlignCenter); | |
timer = new QTimer(this); | |
connect(timer, &QTimer::timeout, this, &TimerWidget::updateTime); | |
timer->start(1000); | |
} | |
private slots: | |
void updateTime() { | |
static int time = 0; | |
time++; | |
label->setText(QString("Time: %1").arg(time)); | |
} | |
private: | |
QLabel *label; | |
QTimer *timer; | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
TimerWidget widget; | |
widget.show(); | |
return app.exec(); | |
} |
# 示例 2:QComboBox 使用
问题:创建一个窗口,包含一个组合框和一个标签,根据组合框选择项更新标签内容。
解答:
#include <QApplication> | |
#include <QWidget> | |
#include <QComboBox> | |
#include <QLabel> | |
#include <QVBoxLayout> | |
class ComboBoxWidget : public QWidget { | |
Q_OBJECT | |
public: | |
ComboBoxWidget(QWidget *parent = nullptr) : QWidget(parent) { | |
QVBoxLayout *layout = new QVBoxLayout(this); | |
comboBox = new QComboBox(this); | |
comboBox->addItem("Option 1"); | |
comboBox->addItem("Option 2"); | |
comboBox->addItem("Option 3"); | |
label = new QLabel("Please select an option", this); | |
label->setAlignment(Qt::AlignCenter); | |
layout->addWidget(comboBox); | |
layout->addWidget(label); | |
connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), | |
this, &ComboBoxWidget::updateLabel); | |
} | |
private slots: | |
void updateLabel(int index) { | |
label->setText(QString("Selected: %1").arg(comboBox->itemText(index))); | |
} | |
private: | |
QComboBox *comboBox; | |
QLabel *label; | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
ComboBoxWidget widget; | |
widget.show(); | |
return app.exec(); | |
} |
# 示例 3:QMainWindow 和 QAction 使用
问题:创建一个主窗口,包含一个菜单项 “Open”,点击该菜单项时显示消息框。
解答:
#include <QApplication> | |
#include <QMainWindow> | |
#include <QAction> | |
#include <QMenuBar> | |
#include <QMessageBox> | |
class MainWindow : public QMainWindow { | |
Q_OBJECT | |
public: | |
MainWindow(QWidget *parent = nullptr) : QMainWindow(parent) { | |
QAction *openAction = new QAction("Open", this); | |
openAction->setShortcut(QKeySequence::Open); | |
connect(openAction, &QAction::triggered, this, &MainWindow::openFile); | |
QMenu *fileMenu = menuBar()->addMenu("File"); | |
fileMenu->addAction(openAction); | |
} | |
private slots: | |
void openFile() { | |
QMessageBox::information(this, "Open", "Open action triggered"); | |
} | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
MainWindow mainWindow; | |
mainWindow.show(); | |
return app.exec(); | |
} |
# 示例 4:QListWidget 使用
问题:创建一个窗口,包含一个列表框和一个标签,点击列表项时更新标签内容。
解答:
#include <QApplication> | |
#include <QWidget> | |
#include <QListWidget> | |
#include <QLabel> | |
#include <QVBoxLayout> | |
class ListWidget : public QWidget { | |
Q_OBJECT | |
public: | |
ListWidget(QWidget *parent = nullptr) : QWidget(parent) { | |
QVBoxLayout *layout = new QVBoxLayout(this); | |
listWidget = new QListWidget(this); | |
listWidget->addItem("Item 1"); | |
listWidget->addItem("Item 2"); | |
listWidget->addItem("Item 3"); | |
label = new QLabel("Please select an item", this); | |
label->setAlignment(Qt::AlignCenter); | |
layout->addWidget(listWidget); | |
layout->addWidget(label); | |
connect(listWidget, &QListWidget::itemClicked, | |
this, &ListWidget::updateLabel); | |
} | |
private slots: | |
void updateLabel(QListWidgetItem *item) { | |
label->setText(QString("Selected: %1").arg(item->text())); | |
} | |
private: | |
QListWidget *listWidget; | |
QLabel *label; | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
ListWidget widget; | |
widget.show(); | |
return app.exec(); | |
} |
# 示例 5:QTreeWidget 使用
问题:创建一个窗口,包含一个树控件和一个标签,点击树项时更新标签内容。
解答:
#include <QApplication> | |
#include <QWidget> | |
#include <QTreeWidget> | |
#include <QLabel> | |
#include <QVBoxLayout> | |
class TreeWidget : public QWidget { | |
Q_OBJECT | |
public: | |
TreeWidget(QWidget *parent = nullptr) : QWidget(parent) { | |
QVBoxLayout *layout = new QVBoxLayout(this); | |
treeWidget = new QTreeWidget(this); | |
treeWidget->setHeaderLabels(QStringList() << "Tree Items"); | |
QTreeWidgetItem *item1 = new QTreeWidgetItem(treeWidget, QStringList("Item 1")); | |
new QTreeWidgetItem(item1, QStringList("Child Item 1.1")); | |
new QTreeWidgetItem(item1, QStringList("Child Item 1.2")); | |
QTreeWidgetItem *item2 = new QTreeWidgetItem(treeWidget, QStringList("Item 2")); | |
new QTreeWidgetItem(item2, QStringList("Child Item 2.1")); | |
label = new QLabel("Please select an item", this); | |
label->setAlignment(Qt::AlignCenter); | |
layout->addWidget(treeWidget); | |
layout->addWidget(label); | |
connect(treeWidget, &QTreeWidget::itemClicked, | |
this, &TreeWidget::updateLabel); | |
} | |
private slots: | |
void updateLabel(QTreeWidgetItem *item, int column) { | |
label->setText(QString("Selected: %1").arg(item->text(column))); | |
} | |
private: | |
QTreeWidget *treeWidget; | |
QLabel *label; | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
TreeWidget widget; | |
widget.show(); | |
return app.exec(); | |
} |
# 示例 6:QTableWidget 使用
问题:创建一个窗口,包含一个表格控件,显示 3 行 3 列的内容,并点击单元格时更新标签内容。
解答:
#include <QApplication> | |
#include <QWidget> | |
#include <QTableWidget> | |
#include <QLabel> | |
#include <QVBoxLayout> | |
class TableWidget : public QWidget { | |
Q_OBJECT | |
public: | |
TableWidget(QWidget *parent = nullptr) : QWidget(parent) { | |
QVBoxLayout *layout = new QVBoxLayout(this); | |
tableWidget = new QTableWidget(3, 3, this); | |
tableWidget->setItem(0, 0, new QTableWidgetItem("Cell 0,0")); | |
tableWidget->setItem(0, 1, new QTableWidgetItem("Cell 0,1")); | |
tableWidget->setItem(0, 2, new QTableWidgetItem("Cell 0,2")); | |
tableWidget->setItem(1, 0, new QTableWidgetItem("Cell 1,0")); | |
tableWidget->setItem(1, 1, new QTableWidgetItem("Cell 1,1")); | |
tableWidget->setItem(1, 2, new QTableWidgetItem("Cell 1,2")); | |
tableWidget->setItem(2, 0, new QTableWidgetItem("Cell 2,0")); | |
tableWidget->setItem(2, 1, new QTableWidgetItem("Cell 2,1")); | |
tableWidget->setItem(2, 2, new QTableWidgetItem("Cell 2,2")); | |
label = new QLabel("Please select a cell", this); | |
label->setAlignment(Qt::AlignCenter); | |
layout->addWidget(tableWidget); | |
layout->addWidget(label); | |
connect(tableWidget, &QTableWidget::cellClicked, | |
this, &TableWidget::updateLabel); | |
} | |
private slots: | |
void updateLabel(int row, int column) { | |
QTableWidgetItem *item = tableWidget->item(row, column); | |
label->setText(QString("Selected: %1").arg(item->text())); | |
} | |
private: | |
QTableWidget *tableWidget; | |
QLabel *label; | |
}; | |
int main(int argc, char *argv[]) { | |
QApplication app(argc, argv); | |
TableWidget widget; | |
widget.show(); | |
return app.exec(); | |
} |
通过以上复习笔记和编程示例,您应能掌握 Qt 界面组件的基本使用方法,并能运用这些知识开发实际的应用程序。
# 网络编程复习笔记
# 重点掌握内容:QHostInfo、QTcpServer、QTcpSocket、QUdpSocket 的使用、HTTP 应用程序编写
# 目录
- 主机信息查询
- TCP 通信
- UDP 通信
- 基于 HTTP 的网络应用程序
# 1. 主机信息查询
# 1.1 QHostInfo
QHostInfo类用于查询主机名和 IP 地址。公共函数 Qlist addresses() 返回与 hostName () 对应主机关联的 IP 地址列表 HostInfoError error() 如果主机查找失败,返回失败类型 QString errorString() 如果主机查找失败,返回错误描述字符串 QString hostName() 返回通过 IP 地址查找到的主机名公共函数 int lookupId() 返回本次查找到的 ID 静态函数 void abortHostLookup(int id) 中断主机查找 QHostInfo fromName(QString &name) 返回指定主机名的 IP 地址 QString localDomainName() 返回本机域名系统(domain name system,DNS)域名 QString localHostName() 返回本机主机名 int lookupHost(QString &name, QObject *receiver, char *member) 以异步方式查找主机的 IP 地址,返回一个表示本次查找的 ID,可用作 abortHostLookup () 函数的参数
示例代码:
#include <QCoreApplication> | |
#include <QHostInfo> | |
#include <QDebug> | |
int main(int argc, char *argv[]) { | |
QCoreApplication a(argc, argv); | |
QHostInfo info = QHostInfo::fromName("www.example.com"); | |
foreach (const QHostAddress &address, info.addresses()) { | |
qDebug() << address.toString(); | |
} | |
return a.exec(); | |
} |
# 2. TCP 通信
# 2.1 QTcpServer
QTcpServer类用于创建 TCP 服务器。
| 函数 | 功能 |
|---|---|
| 公共函数 | |
| void close() | 关闭服务器,停止网络监听 |
| bool listen() | 在设置的 IP 地址和端口上开始监听,若成功就返回 true |
| bool isListening() | 返回 true 表示服务器处于监听状态 |
| QTcpSocket *nextPendingConnection() | 返回下一个等待接入的连接 |
| QHostAddress serverAddress() | 如果服务器处于监听状态,就返回服务器地址 |
| quint16 serverPort() | 如果服务器处于监听状态,就返回服务器监听端口 |
| bool waitForNewConnection() | 以阻塞方式等待新的连接 |
| 函数 | 功能 |
| ---------------------------- | ------------------------------------------------------------ |
| 信号 | |
| void acceptError() | 当接收一个新的连接发生错误时,此信号被发射 |
| void newConnection() | 当有新的连接时,此信号被发射 |
| 保护函数 | |
| void incomingConnection() | 当有一个新的连接可用时,QTcpServer 内部调用此函数,创建一个 QTcpSocket 对象,将其添加到内部可用新连接列表,然后发射 newConnection () 信号 |
| void addPendingConnection() | 由 incomingConnection () 调用,将创建的 QTcpSocket 添加到内部可用新连接列表 |
示例代码:
#include <QTcpServer> | |
#include <QTcpSocket> | |
#include <QCoreApplication> | |
#include <QDebug> | |
class MyServer : public QTcpServer { | |
Q_OBJECT | |
public: | |
MyServer(QObject *parent = nullptr) : QTcpServer(parent) { | |
listen(QHostAddress::Any, 1234); | |
} | |
protected: | |
void incomingConnection(qintptr socketDescriptor) override { | |
QTcpSocket *socket = new QTcpSocket(this); | |
socket->setSocketDescriptor(socketDescriptor); | |
connect(socket, &QTcpSocket::readyRead, this, &MyServer::readData); | |
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); | |
} | |
private slots: | |
void readData() { | |
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender()); | |
if (socket) { | |
qDebug() << socket->readAll(); | |
socket->write("Hello Client"); | |
} | |
} | |
}; | |
int main(int argc, char *argv[]) { | |
QCoreApplication a(argc, argv); | |
MyServer server; | |
return a.exec(); | |
} |
# 2.2 QTcpSocket
QTcpSocket类用于创建 TCP 客户端或处理 TCP 连接。
常用方法:
connectToHost(const QString &hostName, quint16 port): 连接到指定主机和端口。write(const QByteArray &data): 发送数据。
示例代码:
#include <QTcpSocket> | |
#include <QCoreApplication> | |
#include <QDebug> | |
class MyClient : public QObject { | |
Q_OBJECT | |
public: | |
MyClient(QObject *parent = nullptr) : QObject(parent) { | |
socket.connectToHost("127.0.0.1", 1234); | |
connect(&socket, &QTcpSocket::readyRead, this, &MyClient::readData); | |
} | |
private slots: | |
void readData() { | |
qDebug() << socket.readAll(); | |
} | |
private: | |
QTcpSocket socket; | |
}; | |
int main(int argc, char *argv[]) { | |
QCoreApplication a(argc, argv); | |
MyClient client; | |
return a.exec(); | |
} |
# 3. UDP 通信
# 3.1 QUdpSocket
QUdpSocket类用于创建 UDP 套接字。
| 函数 | 功能 |
|---|---|
| bool bind( ) | 为 UDP 通信绑定一个端口 |
| qint64 writeDatagram() | 向目标地址和端口的 UDP 客户端发送数据报,返回成功发送的字节数 |
| bool hasPendingDatagrams() | 当至少有一个数据报需要读取时,返回 true |
| qint64 pendingDatagramSize() | 返回第一个待读取的数据报的大小 |
| qint64 readDatagram() | 读取一个数据报,返回成功读取的数据报的字节数 |
| bool joinMulticastGroup() | 加入一个多播组 |
| bool leaveMulticastGroup() | 离开一个多播组 |
bind(const QHostAddress &address, quint16 port): 绑定到指定地址和端口。writeDatagram(const QByteArray &data, const QHostAddress &address, quint16 port): 发送数据报。
示例代码:
#include <QUdpSocket> | |
#include <QCoreApplication> | |
#include <QDebug> | |
class MyUdp : public QObject { | |
Q_OBJECT | |
public: | |
MyUdp(QObject *parent = nullptr) : QObject(parent) { | |
udpSocket.bind(1234); | |
connect(&udpSocket, &QUdpSocket::readyRead, this, &MyUdp::readData); | |
} | |
void sendData(const QByteArray &data, const QHostAddress &address, quint16 port) { | |
udpSocket.writeDatagram(data, address, port); | |
} | |
private slots: | |
void readData() { | |
while (udpSocket.hasPendingDatagrams()) { | |
QByteArray datagram; | |
datagram.resize(udpSocket.pendingDatagramSize()); | |
QHostAddress sender; | |
quint16 senderPort; | |
udpSocket.readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); | |
qDebug() << "Received:" << datagram; | |
} | |
} | |
private: | |
QUdpSocket udpSocket; | |
}; | |
int main(int argc, char *argv[]) { | |
QCoreApplication a(argc, argv); | |
MyUdp udp; | |
udp.sendData("Hello UDP", QHostAddress("127.0.0.1"), 1234); | |
return a.exec(); | |
} |
# 4. 基于 HTTP 的网络应用程序
# 4.1 QNetworkAccessManager
QNetworkAccessManager类用于发送网络请求和接收响应。
常用方法:
get(const QNetworkRequest &request): 发送 GET 请求。post(const QNetworkRequest &request, const QByteArray &data): 发送 POST 请求。
示例代码:
//“下载” 按钮,开始下载 | |
void MainWindow::on_btnDownload_clicked() { | |
QString urlSpec= ui->editURL->text().trimmed(); | |
if (urlSpec.isEmpty()) { | |
QMessageBox::information(this, "错误","请指定需要下载的 URL"); | |
return; | |
} | |
QUrl newUrl= QUrl::fromUserInput(urlSpec); //URL | |
if (!newUrl.isValid()) { | |
QString info= "无效 URL:"+urlSpec+"\n 错误信息:"+newUrl.errorString(); | |
QMessageBox::information(this, "错误",info); | |
return; | |
} | |
QString tempDir= ui->editPath->text().trimmed(); // 临时目录 | |
if (tempDir.isEmpty()) { | |
QMessageBox::information(this, "错误", "请指定保存下载文件的目录"); | |
return; | |
} | |
QString fullFileName= tempDir+newUrl.fileName(); // 文件名 | |
if (QFile::exists(fullFileName)) | |
QFile::remove(fullFileName); | |
downloadedFile= new QFile(fullFileName); // 创建临时文件 | |
if (!downloadedFile->open(QIODevice::WriteOnly)) { | |
QMessageBox::information(this, "错误","临时文件打开错误"); | |
return; | |
} | |
ui->btnDownload->setEnabled(false); | |
// 发送网络请求,创建网络响应 | |
reply= networkManager.get(QNetworkRequest(newUrl)); | |
connect(reply, SIGNAL(readyRead()), this, SLOT(do_readyRead())); | |
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(do_downloadProgress(qint64,qint64))); | |
connect(reply, SIGNAL(finished()), this, SLOT(do_finished())); | |
} | |
void MainWindow::do_readyRead() {// 读取下载的数据 | |
downloadedFile->write(reply->readAll()); | |
} | |
// 下载进度 | |
void MainWindow::do_downloadProgress(qint64 bytesRead, qint64 totalBytes) { | |
ui->progressBar->setMaximum(totalBytes); | |
ui->progressBar->setValue(bytesRead); | |
} | |
void MainWindow::do_finished() { // 网络响应结束 | |
QFileInfo fileInfo(downloadedFile->fileName()); // 获取下载的文件的文件名 | |
downloadedFile->close(); | |
delete downloadedFile; // 删除临时文件对象 | |
reply->deleteLater(); // 由主事件循环删除此对象 | |
if (ui->chkBoxOpen->isChecked()) // 打开下载的文件 QDesktopServices::openUrl (QUrl::fromLocalFile (fileInfo.absoluteFilePath ())); | |
ui->btnDownload->setEnabled(true); | |
} | |
// 在缓冲区有新下载的数据等待读取时,QNetworkReply 会发射 readyRead () 信号,其关联的槽函数 do_readyRead () 里读取下载缓冲区中的数据并将其写入临时文件。 | |
// 信号 downloadProgress () 表示网络操作进度,传递 bytesRead 和 totalBytes 两个参数,表示已读取字节数和总的字节数,其关联的槽函数 do_downloadProgress () 里用这两个参数显示下载进度。 | |
// 信号 finished () 在下载结束后被发射,槽函数 do_finished () 的功能是关闭临时文件,删除文件对象 downloadedFile 和网络响应对象 reply。然后用静态函数 QDesktopServices::openUrl () 调用默认的应用软件打开下载的文件,例如下载一个 PDF 文件,会自动用系统的默认软件打开此文件。 |
# 例题
# 一、C++ 面向对象程序设计
# 简述题
- 简述对象树的创建和析构顺序。
- 创建顺序:对象树的创建是从父对象开始的,先创建根对象,然后递归创建子对象。
- 析构顺序:对象树的析构顺序是从子对象开始的,先递归销毁子对象,最后销毁根对象。
- 描述构造函数和析构函数的作用及其使用场景。
- 构造函数:用于初始化对象,分配资源等。使用场景包括对象创建时的初始化操作。
- 析构函数:用于清理资源,释放内存等。使用场景包括对象销毁时的清理操作。
- 解释静态成员与普通成员的区别。
- 静态成员:属于类本身,而不是某个对象,所有对象共享同一个静态成员。使用
className::memberName访问。 - 普通成员:属于对象,每个对象都有自己独立的普通成员。
- 静态成员:属于类本身,而不是某个对象,所有对象共享同一个静态成员。使用
- 说明类的继承与派生的基本概念。
- 继承:一个类(子类)从另一个类(父类)获取属性和行为的机制。子类继承父类的所有公有和保护成员。
- 派生:从已有类(基类)创建新类(派生类)的过程。
- 简述多态性的实现方式及其优点。
- 实现方式:通过虚函数和继承实现。基类声明虚函数,子类重写虚函数,使用基类指针或引用调用时,调用的是子类的实现。
- 优点:提高代码的灵活性和可扩展性,允许在不同类中实现相同接口的不同功能。
# 分析题
给出下列程序的对象的析构顺序,写出
main()函数运行后的输出结果:class myInt {
int *p;
public:
myInt(int x=0) { p=new int; *p=x; }
virtual ~myInt(){ cout<<"~myInt:"<<*p<<endl; delete p; }
friend ostream& operator<<(ostream &o,myInt &i){ return o<<*(i.p); }
};
int main() {
myInt xs[3]={myInt(1),myInt(2),myInt(3)};
for(int i=0; i<3; i++)
cout<<xs[i]<<" ";
cout<<endl;
return 0;
}参考答案:
1 2 3 ~myInt:3 ~myInt:2 ~myInt:1
# 编程题
补充
myMatrix类的拷贝构造函数和运算符()重载函数的定义,使之能满足以下main()函数的功能需求:class myMatrix {
int rows, cols;
int **data;
public:
myMatrix(int r, int c);
~myMatrix();
myMatrix(const myMatrix &m);
int& operator()(int r, int c);
// 其他成员函数};
int main() {
myMatrix mat1(2, 2);
mat1(0, 0) = 1;
mat1(0, 1) = 2;
mat1(1, 0) = 3;
mat1(1, 1) = 4;
myMatrix mat2 = mat1;
cout << mat2(0, 0) << " " << mat2(0, 1) << " " << mat2(1, 0) << " " << mat2(1, 1) << endl;
return 0;
}
# 二、对象模型
# 简述题
- 简述对象树的原理和实现、创建和析构顺序。
- 解释动态属性的原理与实现。
- 描述信号与槽的实现原理和工作机制。
# 分析题
- 分析并解释
QMetaObject类的主要接口函数的功能。 - 解释运行时类型信息的作用及其实现方式。
# 编程题
- 编写一个包含自定义信号与槽的
QObject派生类,并在main()函数中演示其使用。
# 三、事件系统
# 简述题
简述 Qt 中的事件系统的工作原理。
- Qt 事件系统通过事件对象和事件循环处理事件。事件对象继承自
QEvent,事件循环由QCoreApplication管理。对象接收事件并调用其event()方法进行处理。
解释事件与信号的区别和联系。
- 区别:事件是低级别的机制,用于直接处理用户输入、窗口系统事件等。信号是高级别的机制,用于对象之间的通信。
- 联系:信号与槽可以间接处理事件,通过发出信号通知其他对象事件的发生。
描述事件过滤器的工作原理和使用方法。
- 工作原理:事件过滤器是一个对象,可以拦截和处理其他对象的事件。通过
installEventFilter安装事件过滤器,拦截的事件传递给eventFilter方法处理。 - 使用方法:创建一个继承自
QObject的类,重写eventFilter方法,然后使用installEventFilter安装事件过滤器。
- Qt 事件系统通过事件对象和事件循环处理事件。事件对象继承自
# 分析题
分析以下代码的功能,解释事件过滤器的作用:
class MyWidget : public QWidget {
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
int main() {
QApplication app(argc, argv);
MyWidget widget;widget.installEventFilter(&widget);
widget.show();
return app.exec();
}
作用:事件过滤器用于拦截并处理特定对象的事件。在上例中,MyWidget 对象的 eventFilter 方法拦截并处理键盘事件,并输出按键值。
# 编程题
实现一个自定义事件过滤器,用于在特定条件下拦截和处理键盘事件。
class KeyPressFilter : public QObject { Q_OBJECT protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_Escape) { qDebug() << "Escape key pressed!"; return true; // 事件已处理 } } return QObject::eventFilter(obj, event); // 传递给基类处理 } }; int main() { QApplication app(argc, argv); QWidget widget; KeyPressFilter filter; widget.installEventFilter(&filter); widget.show(); return app.exec(); }
# 四、多线程
# 简述题
简述 Qt 中线程同步的各种实现方式及其特点。
- 互斥量(QMutex):用于保护临界区,避免多个线程同时访问共享资源。
- 互斥锁(QMutexLocker):自动加锁和解锁互斥量,简化代码。
- 读写锁(QReadWriteLock):允许多个线程同时读,但写操作是独占的。
- 等待条件(QWaitCondition):线程可以等待特定条件的发生,再继续执行。
- 信号量(QSemaphore):用于控制访问资源的线程数量。
解释
QThread的使用方法和注意事项。- 使用方法:创建一个继承自
QThread的类,重写run方法,然后调用start方法启动线程。 - 注意事项:不要直接操作 GUI 元素,线程安全的资源共享,避免死锁和竞争条件。
# 分析题
分析以下代码段,解释互斥量和条件变量的使用方式:
class Worker : public QObject {
Q_OBJECT
private:
QMutex mutex;QWaitCondition condition;bool ready;
public slots:
void process() {
QMutexLocker locker(&mutex);
while (!ready) {
condition.wait(&mutex);
}// 执行任务}void setReady(bool value) {
QMutexLocker locker(&mutex);
ready = value;
condition.wakeOne();
}};
解释: process 方法使用 QMutexLocker 锁定互斥量,如果 ready 为假,线程进入等待状态。 setReady 方法修改 ready 状态并唤醒一个等待的线程。此方法确保了线程安全和同步。
# 编程题
实现一个多线程程序,其中主线程创建多个子线程,并通过
QSemaphore控制子线程的执行顺序。class Worker : public QObject { Q_OBJECT public slots: void doWork() { qDebug() << "Thread" << QThread::currentThread() << "is running."; } }; int main() { QCoreApplication app(argc, argv); QSemaphore semaphore(1); // 初始值为1 Worker worker; QThread thread1, thread2, thread3; QObject::connect(&thread1, &QThread::started, [&]() { semaphore.acquire(); worker.doWork(); semaphore.release(); thread1.quit(); }); QObject::connect(&thread2, &QThread::started, [&]() { semaphore.acquire(); worker.doWork(); semaphore.release(); thread2.quit(); }); QObject::connect(&thread3, &QThread::started, [&]() { semaphore.acquire(); worker.doWork(); semaphore.release(); thread3.quit(); }); worker.moveToThread(&thread1); worker.moveToThread(&thread2); worker.moveToThread(&thread3); thread1.start(); thread2.start(); thread3.start(); thread1.wait(); thread2.wait(); thread3.wait(); return app.exec(); }
# 五、绘图
# 简述题
简述
QPainter的基本使用方法。- 创建
QPainter对象,指定绘图设备(如QWidget或QPixmap)。 - 使用绘图命令(如
drawRect、drawLine)绘制图形。 - 调用
end方法结束绘图。
解释坐标系统与坐标变换的概念和实现方式。
- 坐标系统:用于定义绘图的坐标范围。Qt 中使用物理坐标(窗口坐标)和逻辑坐标(场景坐标)。
- 坐标变换:改变绘图的坐标系,包括平移、旋转和缩放。使用
translate、rotate、scale方法实现。
描述图形视图架构的组成和作用。
- 组成:图形视图架构由
QGraphicsScene、QGraphicsView和QGraphicsItem组成。 - 作用:用于管理和显示大量的图形项,提供丰富的交互和变换功能。
- 创建
# 分析题
分析以下代码段,解释坐标变换的作用:
QPainter painter(this);
painter.translate(50, 50);
painter.rotate(45);
painter.scale(2, 2);
painter.drawRect(0, 0, 100, 100);
解释:此代码先将坐标系平移 50 像素,然后旋转 45 度,再按比例 2 缩放。绘制的矩形最终会在变换后的坐标系中显示,位置和大小都被调整。
# 编程题
编写一个程序,使用
QPainterPath绘制复杂图形,并演示平移、旋转和缩放的效果。void MyWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); QPainterPath path; path.moveTo(50, 50); path.lineTo(150, 50); path.cubicTo(100, 0, 200, 100, 150, 150); path.lineTo(50, 150); path.closeSubpath(); painter.fillPath(path, Qt::blue); }# 六、界面组件
# 简述题
简述日期时间数据在 Qt 中的使用方法。
在 Qt 中,日期和时间数据可以通过
QDate,QTime, 和QDateTime类来处理。QDate:用于表示和处理日期。
QDate date = QDate::currentDate();
qDebug() << date.toString();
QTime:用于表示和处理时间。
QTime time = QTime::currentTime();
qDebug() << time.toString();
QDateTime:用于表示和处理日期和时间的组合。
QDateTime dateTime = QDateTime::currentDateTime();
qDebug() << dateTime.toString();
QDateTimeEdit组件可以在界面中方便地输入和显示日期时间。QDateTimeEdit *dateTimeEdit = new QDateTimeEdit(QDateTime::currentDateTime(), this);
解释
QTimer的基本用法。QTimer用于在指定的时间间隔后发出信号。它可以是单次触发或者周期触发。基本用法:
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MyClass::onTimeout);
timer->start(1000); // 每隔 1000 毫秒触发一次
停止定时器:
timer->stop();
描述
QComboBox、QMainWindow、QAction的使用场景。QComboBox:用于显示一个下拉列表,让用户从多个选项中选择一个。
QComboBox *comboBox = new QComboBox(this);
comboBox->addItem("Option 1");
comboBox->addItem("Option 2");
QMainWindow:用于创建一个带有菜单栏、工具栏和状态栏的主窗口。
QMainWindow *mainWindow = new QMainWindow();
mainWindow->show();
QAction:表示一个可以在菜单、工具栏和快捷键中使用的操作。
QAction *action = new QAction(tr("Open"), this);
connect(action, &QAction::triggered, this, &MyClass::openFile);
mainWindow->menuBar()->addAction(action);
# 分析题
分析以下代码段,解释信号与槽的连接方式和作用:
QComboBox *comboBox = new QComboBox;
connect(comboBox, &QComboBox::currentIndexChanged, [](int index) {
qDebug() << "Current index changed:" << index;
});
- 信号与槽的连接方式:
- 使用
connect函数将comboBox的currentIndexChanged信号连接到一个 lambda 表达式。
- 使用
- 作用:
- 当
comboBox的当前索引改变时,currentIndexChanged信号被发射,连接的 lambda 表达式被调用,输出当前索引。
- 当
- 信号与槽的连接方式:
# 编程题
实现一个包含
QListWidget和QTreeWidget的应用程序,并演示如何动态添加和删除项。#include <QApplication>#include <QWidget>#include <QVBoxLayout>#include <QListWidget>#include <QTreeWidget>#include <QPushButton>#include <QInputDialog>class MyWidget : public QWidget {
Q_OBJECT
public:
MyWidget(QWidget *parent = nullptr) : QWidget(parent) {
QVBoxLayout *layout = new QVBoxLayout(this);
listWidget = new QListWidget(this);
treeWidget = new QTreeWidget(this);
treeWidget->setHeaderLabels({"Column 1", "Column 2"});
QPushButton *addButton = new QPushButton("Add Item", this);
QPushButton *removeButton = new QPushButton("Remove Item", this);
connect(addButton, &QPushButton::clicked, this, &MyWidget::addItem);
connect(removeButton, &QPushButton::clicked, this, &MyWidget::removeItem);
layout->addWidget(listWidget);
layout->addWidget(treeWidget);
layout->addWidget(addButton);
layout->addWidget(removeButton);
}private slots:
void addItem() {
bool ok;
QString text = QInputDialog::getText(this, "Add Item", "Enter item text:", QLineEdit::Normal, "", &ok);
if (ok && !text.isEmpty()) {
listWidget->addItem(text);
QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget);
item->setText(0, text);
item->setText(1, "Sub Item");
treeWidget->addTopLevelItem(item);
}}void removeItem() {
QListWidgetItem *listItem = listWidget->currentItem();
if (listItem) {
delete listItem;
}QTreeWidgetItem *treeItem = treeWidget->currentItem();
if (treeItem) {
delete treeItem;
}}private:
QListWidget *listWidget;
QTreeWidget *treeWidget;
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MyWidget widget;widget.show();
return app.exec();
}
# 七、网络
# 简述题
简述
QHostInfo的作用和使用方法。作用:
QHostInfo提供主机名与 IP 地址的查找功能。使用方法:
QHostInfo::lookupHost("example.com", this, [](const QHostInfo &host) {
if (host.error() == QHostInfo::NoError) {
foreach (const QHostAddress &address, host.addresses()) {
qDebug() << "Found address:" << address.toString();
}} else {
qDebug() << "Lookup failed:" << host.errorString();
}});
解释
QTcpServer和QTcpSocket的区别和联系。- 区别:
QTcpServer用于监听传入的连接请求。QTcpSocket用于客户端和服务器之间的数据传输。
- 联系:
QTcpServer接收到连接后,会创建一个新的QTcpSocket实例用于与客户端通信。QTcpSocket用于在两端之间进行读写操作。
- 区别:
描述
QUdpSocket的基本使用场景。- 用于发送和接收 UDP 数据报。
- 适用于实时性要求高的场景,如视频流、游戏网络通信、简单的网络协议等。
# 分析题
分析以下代码段,解释 TCP 服务器的工作原理:
QTcpServer *server = new QTcpServer;
connect(server, &QTcpServer::newConnection, []() {
QTcpSocket *clientSocket = server->nextPendingConnection();
connect(clientSocket, &QTcpSocket::readyRead, [clientSocket]() {
QByteArray data = clientSocket->readAll();
qDebug() << "Received:" << data;
});
});
server->listen(QHostAddress::Any, 1234);
- 工作原理:
QTcpServer对象server监听来自任意地址 (QHostAddress::Any) 端口1234的连接请求。- 当有新连接到来时,触发
newConnection信号,创建新的QTcpSocket实例clientSocket处理这个连接。 - 通过连接
clientSocket的readyRead信号,确保在有数据可读时,读取并打印收到的数据。
- 工作原理:
# 编程题
实现一个简单的 HTTP 客户端,发送 GET 请求并处理响应。
#include <QCoreApplication>#include <QTcpSocket>#include <QTextStream>#include <QDebug>class HttpClient : public QObject {
Q_OBJECT
public:
HttpClient(const QString &host, int port, const QString &path, QObject *parent = nullptr)
: QObject(parent), host(host), port(port), path(path) {
connect(&socket, &QTcpSocket::connected, this, &HttpClient::sendRequest);
connect(&socket, &QTcpSocket::readyRead, this, &HttpClient::readResponse);
}void start() {
socket.connectToHost(host, port);
}private slots:
void sendRequest() {
QTextStream stream(&socket);
stream << "GET " << path << " HTTP/1.1\r\n";
stream << "Host: " << host << "\
