编译,链接。

news/2024/10/28 15:33:35/

 一、编译预处理

C++程序编译的过程:预处理 -> 编译(优化、汇编)-> 链接

预处理指令主要有以下三种:

  • 包含头文件:#include

  • 宏定义:#define(定义宏)、#undef(删除宏)。

  • 条件编译:#ifdef、#ifndef。

1)包含头文件

#include 包含头文件有两种方式:

  • #include <文件名>:直接从编译器自带的函数库目录中寻找文件。

  • #include "文件名":先从自定义的目录中寻找文件,如果找不到,再从编译器自带的函数库目录中寻找。

#include也包含其它的文件,如:.h、.cpp或其它的文件。

C++98标准后的头文件:

  • C的标准库:老版本的有.h后缀;新版本没有.h的后缀,增加了字符c的前缀。例如:老版本是<stdio.h>,新版本是<cstdio>,新老版本库中的内容是一样的。在程序中,不指定std命名空间也能使用库中的内容。

  • C++的标准库:老版本的有.h后缀;新版本没有.h的后缀。例如:老版本是<iostream.h>,新版本是<iostream>,老版本已弃用,只能用新版本。在程序中,必须指定std命名空间才能使用库中的内容。

注意:用户自定义的头文件还是用.h为后缀。

2)宏定义指令

无参数的宏:

#define 宏名 宏内容

有参数的宏:

#define MAX(x,y) ((x)>(y) ? (x) : (y))   MAX(3,5) ((3)>(5) ? (3) : (5))

编译的时候,编译器把程序中的宏名用宏内容替换,是为宏展开(宏替换)。

宏可以只有宏名,没有宏内容。

在C++中,内联函数可代替有参数的宏,效果更好。

C++中常用的宏:

  • 当前源代码文件名:

    __FILE__
  • 当前源代码函数名:

    __FUNCTION__
  • 当前源代码行号:

    __LINE__
  • 编译的日期:

    __DATE__
  • 编译的时间:

    __TIME__
  • 编译的时间戳:

    __TIMESTAMP__
  • 当用C++编译程序时,宏__cplusplus就会被定义。

3)条件编译

满足条件才会编译

最常用的两种:#ifdef、#ifndef if #define if not #define

#ifdef 宏名程序段一
#else程序段二
#endif

含义:如果#ifdef后面的宏名已存在,则使用程序段一,否则使用程序段二。

4)解决头文件中代码重复包含的问题

在C/C++中,在使用预编译指令#include的时候,为了防止头文件被重复包含,有两种方式。

第一种:用#ifndef指令。

#ifndef _GIRL_#define _GIRL_//代码内容。
#endif 

第二种:把#pragma once指令放在文件的开头。

#ifndef方式受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式有些编译器不支持。

#ifndef可以针对文件中的部分代码;而#pragma once只能针对整个文件。

#ifndef更加灵活,兼容性好;#pragma once操作简单,效率高。

二、编译和链接

1、源代码的组织

头文件(*.h):#include头文件、函数的声明、结构体的声明、类的声明、模板的声明、内联函数、#define和const定义的常量等。

源文件(*.cpp):函数的定义、类的定义、模板具体化的定义。

主程序(main函数所在的程序):主程序负责实现框架和核心流程,把需要用到的头文件用#include包含进来。

2、编译预处理

预处理的包括以下方面:

1)处理#include头文件包含指令。

2)处理#ifdef #else #endif、#ifndef #else #endif条件编译指令。

3)处理#define宏定义。

4)为代码添加行号、文件名和函数名。

5)删除注释。

6)保留部分#pragma编译指令(编译的时候会用到)。

3、编译

将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化和汇编后,编译成若干个目标文件(二进制文件)。

4、链接

将编译后的目标文件,以及它们所需要的库文件链接在一起,形成一个体整。

5、更多细节

1)分开编译的好处:每次只编译修改过的源文件,然后再链接,效率最高。

2)编译单个*.cpp文件的时候,必须要让编译器知道名称的存在,否则会出现找不到标识符的错误。(直接和间接包含头文件都可以)

3)编译单个*.cpp文件的时候,编译器只需要知道名称的存在,不会把它们的定义一起编译

4)如果函数和类的定义不存在,编译不会报错,但链接会出现无法解析的外部命令。(C++编译的时候,只检查名称是否合法,u检查名称的定义即实体是否存在,链接的时候才会寻找名称的定义,如果找不到就报错了)

5)链接的时候,变量、函数和类的定义只能有一个,否则会出现重定义的错误。(如果把变量、函数和类的定义放在.h文件中,.h会被多次包含,链接前可能存在多个副本;如果放在.cpp文件中,.cpp文件不会被包含,只会被编译一次,链接前只存在一个版本)

6)把变量、函数和类的定义放在.h中是不规范的做法,如果.h被多个*.cpp包含,会出现重定义。

7)用#include包含*.cpp也是不规范的做法,原理同上。

8)尽可能不使用全局变量,如果一定要用,要在.h文件中声明(需要加extern关键字),在.cpp文件中定义。

9)全局的const常量在头文件中定义(const常量仅在单个文件内有效)。

10).h文件重复包含的处理方法只对单个的.cpp文件有效,不是整个项目。

11)函数模板和类模板的声明和定义可以分开书写,但它们的定义并不是真实的定义,只能放在.h文件中;函数模板和类模板的具体化版本的代码是真实的定义,所以放在.cpp文件中。

12)Linux下C++编译和链接的原理与VS一样。

三、命名空间

在实际开发中,较大型的项目会使用大量的全局名字,如类、函数、模板、变量等,很容易出现名字冲突的情况。

命名空间分割了全局空间,每个命名空间是一个作用域,防止名字冲突。

1、语法

创建命名空间:

namespace 命名空间的名字
{// 类、函数、模板、变量的声明和定义。
}

创建命名空间的别名:

namespace 别名=原名;

2、使用命名空间

在同一命名空间内的名字可以直接访问,该命名空间之外的代码则必须明确指出命名空间。

1)运算符::

语法:命名空间::名字

简单明了,且不会造成任何冲突,但使用起来比较繁琐。

2)using声明

语法:using 命名空间::名字

用using声明名后,就可以进行直接使用名称。

如果该声明区域有相同的名字,则会报错。

3)using编译指令

语法:using namespace命名空间

using编译指令将使整个命名空间中的名字可用。如果声明区域有相同的名字,局部版本将隐藏命名空间中的名字,不过,可以使用域名解析符使用命名空间中的名称。

3、注意事项

1)命名空间是全局的,可以分布在多个文件中。

2)命名空间可以嵌套。

3)在命名空间中声明全局变量,源文件中定义,头文件中声明;而不是使用外部全局变量和静态变量。

4)对于using声明,首选将其作用域设置为局部而不是全局。

5)不要在头文件中使用using编译指令,如果非要使用,应将它放在所有的#include之后。

6)匿名的命名空间,从创建的位置到文件结束有效。

四、C++类型转换-static_cast

C风格的类型转换很容易理解:

语法:(目标类型)表达式或目标类型(表达式);

C++认为C风格的类型转换过于松散,可能会带来隐患,不够安全。

C++推出了新的类型转换来替代C风格的类型转换,采用更严格的语法检查,降低使用风险。

C++新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast,用于支持C++风格的类型转换。

C++的类型转换只是语法上的解释,本质上与C风格的类型转换没什么不同,C语言做不到事情的C++也做不到。

语法:

static_cast<目标类型>(表达式);         //用最多
const_cast<目标类型>(表达式);
reinterpret_cast<目标类型>(表达式);
dynamic_cast<目标类型>(表达式);

 


http://www.ppmy.cn/news/1542616.html

相关文章

设计模式讲解

设计原则 单一职责原则 > 一个对象应该只包含单一的职责&#xff0c;并且该职责被完整地封装在一个类中 > //一个人类 public class People {/*** 人类会编程*/public void coding(){System.out.println("int mian() {");System.out.println(" print…

代码随想录训练营Day10 | 二叉树的遍历

递归遍历 题目链接&#xff1a; 144.二叉树的前序遍历 void preRecur(vector<int>& ans, TreeNode* root) {if(root) {ans.push_back(root->val);preRecur(ans, root->left);preRecur(ans, root->right);}}145.二叉树的后序遍历 void sufRecur(vector<…

DICOM 基础知识:深入理解DICOM数据结构与标签说明

目录 DICOM 图像概念 DICOM 图像关键特性&#xff1a; DICOM 文件结构 常见数据元素&#xff1a; 数据元素示例详解 DICOM-VR 数据类型说明 DICOM 标准支持的数据集 结语 DICOM 图像概念 DICOM&#xff08;Digital Imaging and Communications in Medicine&…

JavaEE初阶---多线程(五)---定时器/线程池介绍

文章目录 1.定时器的介绍2.线程池2.1为什么需要使用线程池2.2如何进行线程池的创建2.3普通的构造方法的局限性2.4该种对象创建的方法的特点2.5线程池的模拟实现的逻辑 3.ThreadPoolExecutor类的介绍3.1构造方法3.2四种拒绝的策略 1.定时器的介绍 下面的这个就是我们的这个定时…

前端面试题-token的登录流程、JWT

这是我的前端面试题的合集的第一篇&#xff0c;后面也会更新一些笔试题目。秋招很难&#xff0c;也快要结束了。但是&#xff0c;不要放弃&#xff0c;一起加油^_^ 一、token的登录流程 1.客户端用账号密码请求登录 2.服务端收到请求&#xff0c;需要去验证账号密码 3.验证成…

SpringSecurity + Jwt权限校验,接口调用403 Forbidden问题排查与解决

问题背景&#xff1a;部分接口调用正常&#xff0c;部分接口调用报403Forbidden&#xff0c;postman不显示具体报错信息。 问题描述&#xff1a; 接口调用报错&#xff0c;经排查&#xff0c;权限校验认证通过&#xff0c;可以进入接口&#xff0c;但是在执行过程中&#xff0…

【MySQL】C语言连接MySQL数据库3——事务操作和错误处理API

目录 1.MySQL事务处理机制 1.1.autocommit 1.2.autocommit的设置与查看 1.3.使用示例 2.事务操作API 2.1.设置事务提交模式——mysql_autocommit() 2.2.提交事务——mysql_commit() 2.3.事务回滚——mysql_rollback() 3.错误处理的API 3.1.返回错误的描述——mysql_er…

R语言 | paletteer包:拥有2100多个调色板!

看到 PMID:39024031 文章的代码中&#xff0c;有颜色设置的语句&#xff1a; pal <- paletteer_d("ggsci::category20_d3")[c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18)]DimPlot(MM,reduction umap,group.by "sample",label F,pt.size 0.1,c…