2023QT面试题总会
1、Qt信号槽机制的优势
(1)类型安全。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数必须是信号参数的最后一个或几个参数。如果信号和槽的签名不符,编译器就会报错。
(2)松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是哪个对象的哪个槽需要接收它发出的信号,它只需在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道是哪个对象的哪个槽收到了信号。同样的,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。即使关联的对象在运行时被删除,应用程序也不会崩溃。
(3)信号和槽机制增强了对象间通信的灵活性。一个信号可以关联多个槽,也可以多个信号关联一个槽。
2、Qt信号槽机制的不足
同回调函数相比,信号和槽机制运行速度有些慢。通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。原因如下:
(1)需要定位接收信号的对象;
(2)安全地遍历所有的关联(如一个信号关联多个槽的情况);
(3)编组/解组传递的参数;
(4)多线程的时候,信号可能需要排队等待。
然而,与创建对象的new操作及删除对象的delete操作相比,信号和槽的运行代价只是他们很少的一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的。
3、请描述过程, 如何实现一个自定义按钮, 使其在光标进入,按下,离开三种状态下显示不同的图片.
创建一个类, 让其从QPushButton类派生, 重写该类中的事件处理器函数
方法一:
1>. enterEvent() – 光标进入
2>. leaveEvent() – 光标离开
3>. mousePressEvent() – 鼠标按下
4>. paintEvent() – 刷新背景图
方法二:
通过setstylesheet设置
4. Qt信号和槽的本质是什么
回调函数
5、描述QT中的文件流(QTextStream)和数据流(QDataStream)的区别, 他们都能帮助我们完成一些什么事情.
QTextStream – 文本流, 操作轻量级数据(int, double, QString), 数据写入文件中之后以文本的方式呈现。
QDataStream – 数据流, 通过数据流可以操作各种数据类型, 包括类对象, 存储到文件中数据可以还原到内存(二进制)。
QTextStream, QDataStream可以操作磁盘文件, 也可以操作内存数据, 通过流对象可以将数据打包到内存, 进行数据的传输.
6、描述Qt下Tcp通信的整个流程
服务器端:
- 创建用于监听的套接字
- 给套接字设置监听
- 如果有连接到来, 监听的套接字会发出信号newConnected
- 接收连接, 通过nextPendingConnection()函数, 返回一个QTcpSocket类型的套接字对象(用于通信)
- 使用用于通信的套接字对象通信
1>. 发送数据: write
2>. 接收数据: readAll/read
客户端: - 创建用于通信的套接字
- 连接服务器: connectToHost
- 连接成功与服务器通信
1>. 发送数据: write
2>. 接收数据: readAll/read
7、 描述QT下udp通信的整个流程
QT下udp通信服务器端和客户端的关系是对等的, 做的处理也是一样的.
- 创建套接字对象
- 如果需要接收数据, 必须绑定端口
- 发送数据: writeDatagram
- 接收数据: readDatagram
8. 描述QT下多线程的两种使用方法, 以及注意事项
方法一:
-
- 创建一个类从QThread类派生
-
- 在子线程类中重写 run 函数, 将处理操作写入该函数中
-
- 在主线程中创建子线程对象, 启动子线程, 调用start()函数
方法二:
-
- 将业务处理抽象成一个业务类, 在该类中创建一个业务处理函数
-
- 在主线程中创建一QThread类对象
-
- 在主线程中创建一个业务类对象
-
- 将业务类对象移动到子线程中
-
- 在主线程中启动子线程
-
- 通过信号槽的方式, 执行业务类中的业务处理函数
方法三:
QFuture fut1 = QtConcurrent::run(processFun, command);
processFun为线程回调函数
多线程使用注意事项:
-
- 业务对象, 构造的时候不能指定父对象
-
- 子线程中不能处理ui窗口(ui相关的类)
-
- 子线程中只能处理一些数据相关的操作, 不能涉及窗口
9、多线程下,信号槽分别在什么线程中执行,如何控制
可以通过connect的第五个参数进行控制信号槽执行时所在的线程
connect有几种连接方式,直接连接和队列连接、自动连接
直接连接:信号槽在信号发出者所在的线程中执行
队列连接:信号在信号发出者所在的线程中执行,槽函数在信号接收者所在的线程中执行
自动连接:多线程时为队列连接函数,单线程时为直接连接函数。
10. 如何使用C++模拟Qt信号和槽
Qt的信号和槽原理就是回调函数。所以,我们需要保存对象绑定的回调函数
1. 创建槽类Slot,该类的功能是保存对象和对象绑定的回调函数
template<class T>
class SlotBase
{
public:virtual void Exec(T param1) = 0; //纯虚函数virtual ~SlotBase(){}
};/*
* func: 槽函数
* parm:
* return:
*/
template<class T, class T1>
class Slot : public SlotBase<T1>
{
public:/* 定义Slot的时候,获取槽函数信息 */Slot(T* pObj, void (T::*func)(T1)){m_pSlotBase = pObj;m_Func = func;}/* signal触发时,调用 */void Exec(T1 param1){(m_pSlotBase->*m_Func)(param1);}private:/* 槽函数信息 暂存 */T* m_pSlotBase;void (T::*m_Func)(T1);
};
2. 创建signal类
重要阐述:
-
1.创建一个Signal 类,该类保主要是保存多个Slot对象,当一个信号发送时,会遍历这个表,对每一个slot绑定的回调函数进行调用。
-
2.重载运算符(), 遍历这个表,调用回调函数,即signal触发机制
-
3.写一个绑定函数Bind,用于将Slot对象添加到槽表中
template<class T1>
class Signal
{
public:/* 模板函数 -> Bind时获取槽函数指针 */template<class T>void Bind(T* pObj, void (T::*func)(T1)){m_pSlotSet.push_back(new Slot<T,T1>(pObj,func));}/* 重载操作符 -> signal触发机制 */void operator()(T1 param1){for(int i=0;i<(int)m_pSlotSet.size();i++){m_pSlotSet[i]->Exec(param1);}}~Signal(){for(int i=0;i<(int)m_pSlotSet.size();i++){delete m_pSlotSet[i];}}private:vector<SlotBase<T1>*> m_pSlotSet; //这一句很重要,靠基类的指针来存储 信号槽指针
};
3.测试类
测试类包含多个signal 当调用接口就将调用signal的()函数,从而调用slot
class TestSignal
{
public:TestSignal(){}void setValue(int value){emit ValueChanged(value);}void setfValue(int value){emit ValueChanged_f(value);}public slots:void FuncOfA(int parm){printf("enter FuncOfA parm = %d\n", parm);}void FuncOfB(int parm){printf("enter FuncOfB parm = %d\n", parm);}signals:Signal<int> ValueChanged;Signal<float> ValueChanged_f;
};
4.定义一个链接函数
#define Connect(sender, signal, receiver, method) ((sender)->signal.Bind(receiver, method))
11.QVariant使用
-
1、用户自定义需要先注册一个类型,即使用qRegisterMetaType,注册到QT的一个Vector中
-
2、QVariant里面会new一个用户自定义类型的内存,并调用拷贝构造函数,QVariant自身的赋值会使用共享内存管理
所以用户可以传入一个临时变量地址,如果用户传入的是一个指针,这个指针需要用户自己析构,改变这个指针的值,并不会改变QVariant,因为是两个不同的空间了
而如果QVariant a1=b1(b1是QVariant),改变b1的值会改变a1的。因为这样用的是shared指针
初看2以为是对的,验证发现不准确,改变b1并没有改变a1的值,细看发现这里面有QT使用了个小技巧,要取b1的值然后改变时,会调用data函数
CVariantHelp* pBTemp = reinterpret_cast<CVariantHelp*>(b1.data());
pBTemp->j_ = 99;
而data的实现会调用detach将shared分离
void* QVariant::data()
{
detach();
return const_cast<void *>(constData());
}
void QVariant::detach()
{
if (!d.is_shared || d.data.shared->ref == 1)
return;
Private dd;
dd.type = d.type;
handler->construct(&dd, constData());
if (!d.data.shared->ref.deref())handler->clear(&d);
d.data.shared = dd.data.shared;
}
12.Qt中的2指针
- QPointer
特点:当其指向的对象(T必须是QObject及其派生类)被销毁时,它会被自动置NULL.
注意:它本身析构时不会自动销毁所guarded的对象
用途:当你需要保存其他人所拥有的QObject对象的指针时,这点非常有
2.QScopedPointer QScopedArraytPointer与 std::unique_ptr/scoped_ptr
这是一个很类似auto_ptr的智能指针,它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除。但它的所有权更加严格,不能转让,一旦获取了对象的管理权,你就无法再从它那里取回来。
无论是QScopedPointer 还是 std::unique_ptr 都拥有一个很好的名字,它向代码的阅读者传递了明确的信息:这个智能指针只能在本作用域里使用,不希望被转让。因为它的拷贝构造和赋值操作都是私有的,这点我们可以对比QObject及其派生类的对象哈。
- QSharedPointer QSharedArrayPointer 与 std::shared_ptr
QSharedPointer 与 std::shared_ptr 行为最接近原始指针,是最像指针的"智能指针",应用范围比前面的提到的更广。
QSharedPointer 与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了std::auto_ptr 和 QScopedPointer 因为转移语义而不能把指针作为容器元素的缺陷。
- QWeakPointer 与 std::weak_ptr
强引用类型的QSharedPointer已经非常好用,为什么还要有弱引用的 QWeakPointer?
QWeakPointer 是为配合 QSharedPointer 而引入的一种智能指针,它更像是 QSharedPointer 的一个助手(因为它不具有普通指针的行为,没有重载operator*和->)。它的最大作用在于协助 QSharedPointer 工作,像一个旁观者一样来观测资源的使用情况。
weak_ptr 主要是为了避免强引用形成环状。摘自msdn中一段话:
A cycle occurs when two or more resources controlled by shared_ptr objects hold mutually referencing shared_ptr objects. For example, a circular linked list with three elements has a head node N0; that node holds a shared_ptr object that owns the next node, N1; that node holds a shared_ptr object that owns the next node, N2; that node, in turn, holds a shared_ptr object that owns the head node, N0, closing the cycle. In this situation, none of the reference counts will ever become zero, and the nodes in the cycle will not be freed. To eliminate the cycle, the last node N2 should hold a weak_ptr object pointing to N0 instead of a shared_ptr object. Since the weak_ptr object does not own N0 it doesn’t affect N0’s reference count, and when the program’s last reference to the head node is destroyed the nodes in the list will also be destroyed.
在Qt中,对于QObject及其派生类对象,QWeakPointer有特殊处理。它可以作为QPointer的替代品
这种情况下,不需要QSharedPointer的存在
5. QSharedDataPointer
这是为配合 QSharedData 实现隐式共享(写时复制 copy-on-write))而提供的便利工具。
Qt中众多的类都使用了隐式共享技术,比如QPixmap、QByteArray、QString、…。而我们为自己的类实现隐式共享也很简单,比如要实现一个 Employee类:
定义一个只含有一个数据成员(QSharedDataPointer) 的 Employee 类
我们需要的所有数据成员放置于 派生自QSharedData的 EmployeeData类中。
- QExplicitlySharedDataPointe
这是为配合 QSharedData 实现显式共享而提供的便利工具。
QExplicitlySharedDataPointer 和 QSharedDataPointer 非常类似,但是它禁用了写时复制功能。这使得我们创建的对象更像一个指针。
13. QT的d和p指针
保持一个库中的所有公有类的大小恒定的问题可以通过单独的私有指针给予解决。这个指针指向一个包含所有数据的私有数据结构体。这个结构体的大小可以随意改变而不会产生副作用,应用程序只使用相关的公有类,所使用的对象大小永远不会改变,它就是该指针的大小。这个指针就被称作D指针。
D指针的其他好处
1.隐藏实现细节——我们可以不提供widget.cpp文件而只提供WidgetLib和相应的头文件和二进制文件。
2.头文件中没有任何实现细节,可以作为API使用。
3.由于原本在头文件的实现部分转移到了源文件,所以编译速度有所提高。
4.二进制兼容
其实以上的点都很细微,自己跟过源代码的人都会了解,qt是隐藏了d指针的管理和核心源的实现。像是在_p.h中部分函数的声明,qt也宣布在以后版本中将会删除。( This file is not part of the Qt API. It exists purely as an implementation detail. This header file may change from version to version without notice, or even be removed.)
q_ptr指针指向父类,使用如下宏定义辅助函数和声明友元类
#ifndef D_PTR_H
#define D_PTR_H#include <QObject>template <typename T> static inline T *GetPtrHelper(T *ptr) { return ptr; }#define DECLARE_PRIVATE(Class) \inline Class##Private* d_func() { return reinterpret_cast<Class##Private*>(GetPtrHelper(d_ptr)); } \inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private*>(GetPtrHelper(d_ptr)); }\friend class Class##Private;#define DPTR(Class) Class##Private * const d = d_func()class MyClassPrivate;class MyClass : public QObject {Q_OBJECT
public:explicit MyClass(QObject *parent = 0);virtual ~MyClass();void testFunc();protected:MyClass(MyClassPrivate &d);private:MyClassPrivate * const d_ptr;DECLARE_PRIVATE(MyClass);MyClass(const MyClass&);MyClass& operator= (const MyClass&);
};#endif #ifndef Q_PTR_H
#define Q_PTR_H#include <QObject>
#include "d_ptr.h"#define DECLARE_PUBLIC(Class) \inline Class* q_func() { return static_cast<Class *>(q_ptr); } \inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \friend class Class;#define QPTR(Class) Class * const q = q_func()class MyClassPrivate : public QObject
{
Q_OBJECTpublic:MyClassPrivate(MyClass *q, QObject *parent = 0);virtual ~MyClassPrivate() {}signals:void testSgnl();private slots:void testSlt();public:void fool();private:MyClass * const q_ptr;DECLARE_PUBLIC(MyClass);
};#endif
相关文章:

2023QT面试题总会
1、Qt信号槽机制的优势 (1)类型安全。需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数同接收该信号的槽的参数类型和参数个数相同。不过,一个槽的参数个数是可以少于信号的参数个数的,但缺少的参数…...

【微信小程序】-- npm包总结 --- 基础篇完结(四十七)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...

Leetcode刷题之经典双指针问题
光是话不行,要紧的是做。 ——鲁迅 目录 一.什么是双指针问题? 二.最接近的三数之和 第一种暴力法: 第二种双指针: 三.移除元素 第一种暴力法: 第二种双指针: 四.盛最…...

C语言学习之路--指针篇
目录一、前言二、指针一、指针是什么1、指针的重要理解2、指针变量3、其他问题二、指针和指针类型1、指针—整数2、指针的解引用三、野指针1、野指针成因2、如何规避野指针四、指针的运算1、指针—指针2、指针的关系运算五、指针和数组六、二级指针七、指针数组一、前言 本人是…...

吃透Java面试题,建议收藏
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~ Github地址:https://github.com/…...

华为OD机试题,用 Java 解【最差产品奖】问题 | 含解题说明
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:最差产品奖 题目 A 公司准备对…...

Redis缓存优化
数据库在用户数量多,系统访问量大的时候,系统性能会下降,用户体验差。1.缓存优化作用:1.降低数据库的访问压力2.提高系统的访问性能3.从而提高用户体验实现思路:1.先查询缓存2.如果缓存有数据,直接返回3.如…...

少儿Python每日一题(23):楼梯问题
原题解答 本次的题目如下所示: 楼梯有n阶台阶,上楼可以一步上1阶,也可以一步上2阶,走完n阶台阶共有多少种不同的走法? 输入格式: 输入楼梯的阶梯数n 输出格式: 输出不同走法的个数 输入样例: 10 输出样例: 89 这是一道非常经典的题目,我们可以先寻找一下上楼梯的规律…...

【Leetcode】队列实现栈和栈实现队列
目录 一.【Leetcode225】队列实现栈 1.链接 2.题目再现 3.解法 二.【Leetcode232】栈实现队列 1.链接 2.题目再现 3.解法 一.【Leetcode225】队列实现栈 1.链接 队列实现栈 2.题目再现 3.解法 这道题给了我们两个队列,要求去实现栈; 首先&…...

(一)Tomcat源码阅读:查看官网,厘清大概轮廓
一、进入官网 点击以下链接进入官网:Apache Tomcat - Welcome!,点击介绍进入介绍,查看tomcat的项目结构。 二、查看项目结构 进入介绍后,我们可以看到下面的这些东西,这些对于tomcat是比较重要的,我们要一一对其进行解读。 这段…...

刷题记录(2023.3.14 - 2023.3.18)
[第五空间 2021]EasyCleanup 临时文件包含考点 分析源码,两个特殊的点,一个是 eval,另一个是 include eval 经过了 strlen filter checkNums 三个函数 include 经过了 strlen filter 两个函数 filter 检测是否包含特定的关键字或字符 fun…...

http协议 - 笔记
1 http协议 -- post,get,delete 如何使用http协议post - /api/v1/User/1 要使用 HTTP 协议 POST 方法向 /api/v1/User/1 发送请求,您可以使用一个 HTTP 客户端(例如 Postman、cURL 或浏览器扩展程序)并按照以下步骤操作: 打开您的 HTTP 客户端。在 URL 地址栏中输入 /a…...

第十八天 Vue-前端工程化总结
目录 Vue-前端工程化 1. 前后端分离开发 1.1 介绍 1.2 Yapi 2. 前端工程化 2.1 环境准备 2.2 Vue项目简介 2.3 Vue项目开发流程 3. Vue组件库Element 3.1 快速入门 3.2 常用组件 3.3 案例 Vue-前端工程化 前面我们已经讲解了HTML、CSS、JavaScript以及Vue等知识。已…...

python网上选课系统django-PyCharm
学生选课信息管理系统,可以有效的对学生选课信息、学生个人信息、教师个人信息等等进行管理。 开发语言:Python 框架:django Python版本:python3.7.7 数据库:mysql 数据库工具:Navicat11 开发软件&#x…...

Java序列化与反序列化
优秀博文:IT-BLOG-CN 序列化:把对象转换为字节序列存储于磁盘或者进行网络传输的过程称为对象的序列化。 反序列化:把磁盘或网络节点上的字节序列恢复到对象的过程称为对象的反序列化。 一、序列化对象 【1】必须实现序列化接口Serializabl…...

【网络】网络层协议——IP
目录网络层IP协议IP基础知识IP地址IP报头格式网段划分CIDR特殊的IP地址IP地址的数量限制私有IP地址和公有IP地址路由IP总结网络层 在复杂的网络环境中确定一个合法的路径。 IP协议 IP协议作为整个TCP/IP中至关重要的协议,主要负责将数据包发送给最终的目标计算机…...

安装kubernetes
master110.10.10.10docker、kubelet、kubeadm、kubectlmaster210.10.10.11docker、kubelet、kubeadm、kubectlnode110.10.10.12docker、kubelet、kubeadm、kubectlnode210.10.10.13docker、kubelet、kubeadm、kubectl 1.关闭防火墙(所有节点执行) syste…...

三维点云转深度图
文章目录 目录 一、算法原理 算法流程 二、代码实现 1.Python代码 2....

Qt音视频开发27-ffmpeg视频旋转显示
一、前言 用手机或者平板拍摄的视频文件,很可能是旋转的,比如分辨率是1280x720,确是垂直的,相当于分辨率变成了720x1280,如果不做旋转处理的话,那脑袋必须歪着看才行,这样看起来太难受…...

python例程:《彩图版飞机大战》程序
目录开发环境要求运行方法《彩图版飞机大战》程序使用说明源码示例源码及说明文档下载路径开发环境要求 本系统的软件开发及运行环境具体如下。 操作系统:Windows 7、Windows 10。 Python版本:Python 3.7.1。 开发工具:PyCharm 2018。…...

【前端八股文】JavaScript系列:Set、Map、String常用属性方法
文章目录Set概念与arr的比较属性和方法并集、交集、差集Map概念属性和方法String用索引值和charAt()的区别charAt()和charCodeAt()方法的区别5个查找方法的区别如何把字符串分割为数组3个截取方法的区别大小写转换3个模式匹配方法(正则表达式)3个移除字符…...

跳跃-动态规划问题
跳跃-动态规划问题1、题目描述2、解题思路2.1 解法一:动态规划2.2 解法二:DFS深度优先搜索最大权值1、题目描述 小蓝在一个 n 行 m 列的方格图中玩一个游戏。 开始时,小蓝站在方格图的左上角,即第 11 行第 11 列。 小蓝可以在方格…...

Django笔记三十九之settings配置介绍
这一篇笔记介绍 Django 里 settings.py 里一些常用的配置项,这些配置有一些是在之前的笔记中有过介绍的,比如 logging 的日志配置,session 的会话配置等,这里就只做一下简单的回顾,有一些是之前没有介绍过的就着重介绍…...

【JavaSE】类和对象(中)
类和对象(中)4. this引用4.1 为什么要有this引用4.2 什么是this引用4.3 this引用的特性5. 对象的构造及初始化5.1 如何初始化对象5.2 构造方法(构造器)5.2.1 概念5.2.2 特性5.3 默认初始化5.4 就地初始化6. 封装6.1 封装的概念6.2…...

C语言例程:学生成绩管理程序
学生成绩管理程序 实例说明 编制一个统计存储在文件中的学生考试分数的管理程序。设学生成绩以一个学生一条记录的 形式存储在文件中,每个学生记录包含的信息有姓名、学号和各门功课的成绩。要求编制具有以 下几项功能的程序:求出各门课程的总分&#…...

完美日记母公司再度携手中国妇基会,以“创美人生”助力女性成长
撰稿 | 多客 来源 | 贝多财经 当春时节,梦想花开。和煦的三月暖阳,唤醒的不止是满城春意,更有逸仙电商“创美人生”公益项目播撒的一份希望。 3月8日“国际妇女节”当日,为积极响应我国促进共同富裕的政策倡导,助力相…...

【JaveEE】线程的创建及常见方法解析(Tread类)
目录 1.Tread类介绍 2线程的构造方法——创建线程 1.继承Thread类,重写run()方法 2.使用Runnbable接口创建线程 3.继承 Thread, 重写 run, 使用匿名内部类 4.实现 Runnable, 重写 run, 使用匿名内部类 5.使用 lambda 表达式(重点掌握)…...

Linux的诞生过程
个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。座右铭:海不辞水,故能成其大;山不辞石,故能成其高。个人主页:小李会科技的…...

面部表情识别1:表情识别数据集(含下载链接)
面部表情识别1:表情识别数据集(含下载链接) 目录 面部表情识别1:表情识别数据集(含下载链接) 1.前言 2.表情识别数据集介绍 1.JAFFE数据集 2.KDEF(Karolinska Directed Emotional Faces)数据集 3.GENKI数据集 4.RaFD数据集…...

CSS实现文字凹凸效果
使用两个div分别用来实现凹凸效果;text-shadow语法 text-shadow: h-shadow v-shadow blur color; h-shadow:必需。水平阴影的位置。允许负值。 v-shadow :必需。垂直阴影的位置。允许负值。 blur:可选,模糊的距离。 co…...