IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++的编译模型 -> 正文阅读

[C++知识库]C++的编译模型

C++的编译模型

单遍编译

在单遍编译时,编译器只能根据目前看到的代码做出决策,读到后面的代码也不会影响前面做出的决定。这很影响名字查找和函数重载决议

对于名字查找,C++中的名字包括类型名,函数名,变量名,typedef名,template名。

对于下面这行代码:

Foo<T> a;	//Foo,T,a这三个名字都不是macro

根据之前出现的代码不同,上面这行语句至少有三种可能性。

  1. Foo是个template<typename X> class Foo;Ttype,那么这句话就是以T为模板类型参数具体化了Foo<T>类型,并定义了变量a
  2. Foo是个template<int X> class FooTconst int变量,这句话以T为非类型模板参数具体化了Foo<T>类型,并定义了变量a
  3. Foo、T、a都是int,没啥用的表达式。

C++只能通过解析源代码来了解名字的含义,不能像其他语言通过直接读取目标代码中的元数据来获得所需要的信息如果要想准确理解一行C++代码的含义,我们需要通读这行代码之前的所有代码,并理解每个符号的定义。然而,由于头文件的存在,可能出现某人不经意间改变了头文件,这样就会破坏了代码的功能。

C++编译器的符号表至少要保存目前已看到的每个名字的含义,包括class的成员定义,已声明的变量,已知的函数原型等,才能正确解析源代码。对于编译模板,难度十分大,除此之外,编译器还要正确处理作用域嵌套引发的名字的含义变化:内层作用域中的名字有可能遮住外层作用域中的名字。

对于函数重载决议当C++编译器读到一个函数调用语句时,必须从目前已看到的同名函数中选出最佳函数。哪怕后面的代码中出现了更合适的匹配,也不能影响当前的决定

对于下面一段代码

void foo(int)
{
	printf("foo(int);\n");
}
void bar()
{
	foo('a');	//调用foo(int)
}
void foo(char)
{
	printf("foo(char);\n");
}
int main()
{
    bar();
}

如果在重构的时候把void bar()的定义挪到void foo(char)之后,程序的输出就不一样了。

前向声明

C++规范建议尽量使用前向声明来减少编译期依赖

如果在代码中调用函数foo,C++编译器解析到此处函数调用时,需要生成函数调用的目标代码。为了完成语法检查并生成调用函数的目标代码,编译器需要知道函数的参数个数和函数的返回值类型,它并不需要知道函数体的实现(除非使用inline展开)。因此通常把函数原型放到头文件中,这样每个包含了此头文件的源文件都可以使用这个函数。

光有函数原型不够,程序其中某一个源文件应该定义这个函数,否则会造成链接错误。定义foo()函数的源文件通常也会包括foo()的头文件。但是,假设在定义foo()函数时把参数类型写错了,会出现什么情况。

//in foo.h
void foo(int);	//原型声明

//in foo.cc
#include "foo.h"

void foo(int, bool)		//可能会抄错
{
    //do something	
}

这块编译foo.cc不会出错,因为编译器认为foo有两个重载。但是链接整个程序会报错:因为找不到void foo(int)的定义。

这是C++的一种典型缺陷,即一样东西区分声明和定义,代码放到不同的文件中,就有可能出现不一致的可能性。

对于函数的原型声明和函数体定义而言,这种不一致表现在参数列表和返回类型上,编译器通常能查出参数列表不同,但不一定能查出返回类型不同。也可能是参数类型相同,但是顺序调换了。例如原型声明为draw(int height, int width),定义的时候写成draw(int width, int height),编译器无法查看此类错误,因为原型声明中的变量名是没用的。

如果要写一个库给别人用,通常要把接口函数的原型声明放到头文件中.但是在写库的内部实现的时候,如果没有出现函数相互调用的情况,可以适当组织函数定义的顺序,让基础函数出现在代码的前面,这样就不必前向声明函数原型了。

函数原型声明可以看作对函数的前向声明,还有对类的前向声明。

有时候类的前向声明是必须的。有时候类的完整定义是必须的,例如要访问类的成员,或者要知道类的大小以便分配空间。其他时候,有类的前向声明就够了,编译器只需要知道有这么个名字的类

对于class Foo,以下几种使用不需要看到其完整定义:

  • 定义或声明Foo*Foo&,包括用于函数参数,返回类型,局部变量,类成员变量等等。
  • 声明一个以Foo为参数或返回类型的函数,如Foo bar()void bar(Foo f),但是,如果代码里调用这个函数就需要知道Foo的定义,因为编译器要使用Foo的拷贝构造函数和析构函数,因此至少要看到它们的声明。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-14 13:08:38  更:2021-09-14 13:09:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 22:40:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码