链接A引发的思考

本文探讨了HTML中链接(a)与按钮(button)的语义区别及应用场景,分析了不同写法的优劣,强调了正确使用的重要性,并提出了最佳实践。

转自:淘宝网UED 射雕

JavaEye看见这个话题,讨论挺有意思,忍不住也掺和一把。

语义思考

首先,链接a和按钮button是有语义的,不能因为使用上的方便而替换。a是anchor的缩写,是一个锚点,用来导航或定位。典型用法为:

<a href="http://www.w3c.org/">W3C Web Site</a>
<a name="anchor-one">This is the location of anchor one.</a>
<a href="#anchor-one">Link to anchor one</a>

还可以同时指定name和href属性,这是基础知识,若有疑问请参考HTML 4.01规范

再说按钮(包括 button 和 <input type=”button/submit” />)。从语义上讲,按钮是表单的一部分,触发的动作和表单是有关联的。如果根本就没有表单操作,就不应该使用按钮。举些例子:

上图是一些链接,虽然长得像button,但语义上是a.

上图中的显示和排序按钮,是操作表单。从语义上讲,用button或input更合适。(注意:淘宝搜索结果页目前采用的是a, 这是出于渐进增强的考虑,下面会提及)

总之链接和按钮有各自的语义和使用场景,不能随意替换使用。

写法分析

世界永远没那么简单,在当今JavaScript如日中天的Web世界里,链接a经常用来触发js事件:

<a href="" οnclick="something()">test 1</a>
<a href="#" οnclick="something();return false">>test 2</a>
<a href="javascript: void(0)" οnclick="something()">>test 3</a>
<a href="javascript: void something()">test 4</a>

首先,第一种写法在ie下是有问题的,原因是 ie下会自动补全href.

第二种写法直接在onclick事件中阻止掉默认事件,因此href="#"中的#实际上可以为任意值。用#,是考虑没有js时,点击后停留在本页(注意:当a在一屏以下时,这种写法会导致页面回滚到顶部)。

第三种写法,href值是一个javascript伪协议,void是javascript的一个一元操作符(比如!, typeof)。void操作符的作用是,只执行后面的表达式,不返回任何值。看起来好像是void(0)阻止了默认事件,实际上,下面这些写法都没问题:

<a href="javascript: void(1)" οnclick="something()">>test 3</a>
<a href="javascript:;" οnclick="something()">>test 3</a>
<a href="javascript:" οnclick="something()">>test 3</a>
<a href="javascript: return true" οnclick="something()">>test 3</a>

因为a的默认操作就是javascript伪协议的内容,里面加不加void都不会触发其它事件。(注意:Opera下,当伪协议里有返回值时,会改变href, 因此我们一般写void(0)或空语句)

理解了第三种写法,第四种写法也就明白了:href="javascript: void something()". 这种写法有一个“好处”是,鼠标悬浮时,用户可以通过状态栏看到将要执行的函数。对开发者来说,这或许是个好处,但对普通用户来说,这真的会增加信赖感吗?抑或是恐惧感?没有数据,无法下结论。

除了上面的写法,还有一种推荐的写法是,通过class或id给a增加一个hook,然后在js里通过hook来添加事件。

反思

我不想去讨论上面的各种写法中哪一个是最好的。让我们思考本源问题:为什么我们会用a来触发js事件?我能想到的理由有:

  1. 这样自动就拥有鼠标的悬浮样式了
  2. 大家都这么写
  3. 还真想不出啥理由,这不是很自然的事嘛
  4. 因为IE6只支持css样式的a:hover,而且href还不能为空

可以看出,除了悬浮样式,找不到啥实质性的理由了。我们暂时抛开样式问题,来看一个例子:

上面是Google Reader的操作栏,感兴趣的不妨firebug一下,采用的标记是:

鼠标的悬浮样式,也根本不是问题:

css里,加上cursor: pointer就行。

从上面的例子中,我们可以得到一个结论:如果仅仅是触发js动作,没有任何导航或定位的语义,采用span或其它合适的标签即可,没必要错误的使用a(用a反而惹麻烦:一是要去除默认事件,二是状态栏的信息会让普通用户迷惑甚至恐惧)。

当然,如果本身就是一个链接,仅仅想在导航前增加一些js逻辑,或者是表单排序等应用,从渐进增强角度考虑,最好的实践是将href值写全,以使得在不支持js的浏览器下,也能保证可用性。

最佳实践

不是总结,不是一锤子定音,“最佳实践”只是一系列原则,是写代码前要先想一想:

  1. 用链接a,还是用按钮button,根据具体使用场景来定,button是和表单相关的元素
  2. 不要滥用a,当你都想不明白href值是什么时(不要出现javascript伪协议,不要孤独寂寞的#),请选用其它标签,通过hook在js中添加事件
  3. 如果确实是一个链接,同时又有onclick事件,请想想渐进增强,保证href值的完整性

代码有生命,标签王国是一个动物园,熟悉它们,一切很可爱。

转载于:https://www.cnblogs.com/yuzhongwusan/archive/2009/04/20/1439437.html

<think>我们面对的是一个链接错误:找不到`Localpool`虚函数表的符号,却发现了另一个符号`QEventDispatcherGlib::registSocketNotifier(QSocketNotifier*)`。这个问题通常发生在C++程序的链接阶段,表明链接器在解析`Localpool`类的虚函数表(vtable)时出现了问题,而错误信息中出现的另一个符号可能是一个干扰项,也可能是问题的线索。下面我将逐步分析可能的原因并提供解决方案。 ### 1. 检查`Localpool`类的虚函数定义 虚函数表(vtable)是编译器为包含虚函数的类自动生成的一个表,其中存放了虚函数的地址。如果链接器找不到虚函数表的符号,通常是因为以下原因之一: - **类定义中声明了虚函数但没有实现**:特别是纯虚函数(`=0`)必须在派生类中实现,否则会导致链接错误。如果`Localpool`是一个基类,且包含纯虚函数,那么必须确保它有派生类并且实现了所有纯虚函数。如果`Localpool`不是抽象类,那么它的虚函数必须有定义(即实现体)。 例如,在`localpool.h`中声明: ```cpp class Localpool { public: virtual ~Localpool(); // 虚析构函数 virtual void someMethod(); // 虚函数 }; ``` 在`localpool.cpp`中必须实现: ```cpp Localpool::~Localpool() {} // 即使为空也必须定义 void Localpool::someMethod() { /* 实现 */ } ``` - **虚函数在不同编译单元中的可见性**:确保包含虚函数实现的源文件(如`localpool.cpp`)被正确编译并链接到最终的可执行文件或库中。检查构建脚本(如Makefile、CMakeLists.txt)是否包含了该源文件。 ### 2. 检查编译和链接过程 - **编译选项一致性**:确保所有涉及`Localpool`类的源文件在编译时使用了相同的编译选项(如`-std`、`-fPIC`等)。不同的编译选项可能导致符号生成不一致。 - **链接顺序问题**:链接器的符号解析是顺序敏感的。确保包含`Localpool`实现的库在依赖它的目标文件之后链接。例如: ```sh g++ -o myapp main.o -L. -lmylib # 其中mylib包含Localpool的实现 ``` 而不是: ```sh g++ -o myapp -L. -lmylib main.o # 错误的顺序,可能导致main.o中的符号未被解析 ``` - **库依赖关系**:如果`Localpool`类是在一个库中定义的,确保该库被正确链接。例如,使用`-l`选项指定库名,并确保库路径在`-L`指定的路径中。 ### 3. 检查符号冲突或干扰 错误信息中提到了另一个符号`QEventDispatcherGlib::registSocketNotifier(QSocketNotifier*)`,这可能是Qt框架中的一个函数。这暗示: - **多定义问题**:可能存在另一个类或函数与`Localpool`的虚函数表符号冲突。检查是否有多个定义(例如,同一个类在两个不同的源文件中定义)。 - **Qt相关干扰**:如果项目中使用了Qt,并且`Localpool`类与Qt的事件循环有关,那么可能是Qt的元对象系统(moc)生成的文件未正确链接。确保所有由moc生成的源文件(如`moc_localpool.cpp`)都被编译并链接。 对于Qt类,如果使用了Q_OBJECT宏,则必须由moc处理。检查: - 头文件中的类声明是否包含`Q_OBJECT`宏。 - moc生成的源文件是否被包含在构建中。 ### 4. 使用工具进一步定位 可以使用以下工具来定位问题: - **nm命令**:查看对象文件(.o)或库文件(.a/.so)中的符号。例如: ```sh nm -C myobject.o | grep Localpool ``` 查找`Localpool`的虚函数表符号(通常以`vtable for Localpool`形式出现)。如果找不到,说明实现未被编译或未被包含。 - **c++filt**:如果符号被修饰(mangled),可以使用c++filt来反修饰: ```sh nm myobject.o | grep Localpool | c++filt ``` - **检查链接命令的详细输出**:在链接命令中添加`-Wl,--verbose`或`-v`选项,以查看链接器搜索的库和符号解析过程。 ### 5. 其他可能原因 - **内联虚函数**:如果虚函数在头文件中定义且为内联(例如在类定义体内直接实现),则虚函数表可能会在每个包含该头文件的编译单元中生成,导致多重定义。解决方案是在类外定义虚函数(即在源文件中实现)。 - **动态链接库问题**:如果`Localpool`类在动态库中定义,确保动态库的版本正确,并且运行时路径(LD_LIBRARY_PATH)设置正确。 - **编译器版本兼容性**:不同版本的编译器可能有不同的ABI(应用程序二进制接口),特别是在C++标准库和虚函数表布局上。确保所有组件使用相同版本的编译器编译。 ### 解决步骤总结 1. **验证虚函数实现**:确保所有虚函数(包括析构函数)都有定义。 2. **检查构建系统**:确保包含`Localpool`实现的源文件被编译,且生成的目标文件被链接。 3. **检查Qt moc**:如果涉及Qt,确保moc文件被正确生成和链接。 4. **使用工具分析符号**:用nm等工具检查符号是否存在。 5. **调整链接顺序**:确保链接顺序正确。 6. **排查版本兼容性**:统一编译环境和库版本。 通过以上步骤,通常可以解决虚函数表链接错误的问题。如果问题仍然存在,可能需要进一步检查项目依赖和底层库的兼容性。 §§相关问题§§ 1. 如何在Linux环境下使用gdb调试链接器错误? 2. 为什么链接器错误有时会指向看似无关的符号? 3. 如何避免C++项目中的符号冲突问题? 4. 在Qt项目中,如何确保moc文件被正确处理? </think>### 深入分析并解决“找不到'Localpool'虚表符号”问题 该链接错误表明编译器在构建虚函数表(vtable)时遇到异常,本质是**编译器无法定位`Localpool`类的完整类型信息**,而错误引用了Qt框架中的无关符号。以下是系统性分析框架和解决方案: --- ### 一、核心问题诊断 1. **虚函数表生成机制** C++编译器在以下场景自动生成虚表: - 类包含虚函数(`virtual`关键字) - 类继承自含虚函数的基类 - **触发条件**:类定义的**完整声明**在编译单元可见 2. **错误本质分析** ```plaintext "找不到Localpool虚表" + "QEventDispatcherGlib符号干扰" ``` - ✅ `Localpool`类定义可能未在关键编译单元完全可见 - ✅ 存在头文件包含顺序问题或前向声明滥用 - ✅ Qt信号槽机制与虚表生成冲突(因`registSocketNotifier`是Qt内部函数) --- ### 二、关键解决步骤 #### 步骤1:验证类定义完整性(最常见原因) ```cpp // Localpool.h 必须包含完整虚函数声明 class Localpool : public QObject { // 继承QObject可能引发Qt元对象系统冲突 Q_OBJECT // 如果有此宏,必须由moc处理 public: virtual ~Localpool(); // 虚析构函数必须实现 virtual void criticalMethod(); // 纯虚函数必须实现(=0) }; ``` #### 步骤2:检查构建系统处理(Qt项目特有问题) 1. **moc预处理缺失** 若`Localpool`包含`Q_OBJECT`宏: ```cmake # CMakeLists.txt 必须显式声明 qt_wrap_cpp(MOC_FILES Localpool.h) # 生成moc_Localpool.cpp add_executable(app ${SRC} ${MOC_FILES}) ``` **验证方法**:检查构建目录是否生成`moc_Localpool.cpp` 2. **链接顺序修正** 调整库链接顺序,确保`Localpool`实现在Qt库之前: ```cmake target_link_libraries(app PRIVATE Localpool_lib # 先链接自定义类库 Qt6::Core # 后链接Qt库 ) ``` #### 步骤3:排除符号污染(解释无关符号) 当编译器无法定位`Localpool`虚表时,链接器可能: 1. 错误引用相邻内存的符号(此处为Qt内部函数`registSocketNotifier`) 2. 由以下原因触发: - 头文件中使用`class Localpool;`前向声明但未包含完整定义 - 不同编译单元(.cpp文件)中`Localpool`大小不一致 **诊断工具**: ```bash # 检查虚表符号是否存在 nm -C build/obj/Localpool.o | grep "vtable for Localpool" # 验证Qt符号干扰 nm -D libQtCore.so | grep registSocketNotifier # 确认符号来源 ``` --- ### 三、针对性解决方案 #### 场景1:因头文件包含缺失导致 ```cpp // 错误示例:仅前向声明(虚表无法生成) class Localpool; // ❌ 不完整类型 // 正确做法:包含完整定义 #include "Localpool.h" // ✅ 确保虚函数声明可见 ``` #### 场景2:Qt元对象系统冲突 ```diff // Localpool.h -class Localpool { +class Localpool : public QObject { Q_OBJECT // 启用Qt特性时必须 + Q_DISABLE_COPY(Localpool) // 禁用拷贝避免冲突 public: virtual void method(); }; ``` ```bash # 清理moc生成文件后重建 rm moc_*.cpp cmake --build . ``` #### 场景3:跨动态库边界问题 当`Localpool`定义在动态库中时: ```cpp // 显式导出虚表符号 class EXPORT_API Localpool { // EXPORT_API为平台特定导出宏 virtual ~Localpool(); // 必须导出析构函数 }; ``` --- ### 四、验证流程 1. 确保所有虚函数在.cpp文件中有实现体 2. 检查包含`Localpool`的每个.cpp文件是否都`#include "Localpool.h"` 3. 在构建日志中搜索`moc_Localpool.cpp`确认Qt元对象编译器生效 4. 使用链接器诊断选项: ```bash g++ -Wl,-trace-symbol='vtable for Localpool' ... # 跟踪符号解析 ``` > **关键结论**:该错误本质是编译器对类布局认知不完整,需确保: > 1. 类定义在引用处100%可见 > 2. Qt特殊机制(moc)被正确处理 > 3. 避免跨模块二进制兼容问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值