第5章 基础——5.3. C++项目组成

2023-11-08

[回到目录]

白话C++

 

5.3. C++项目组成

首先我们知道了,写一个C++程序,可能需要多个源文件,比如a.cpp、b.cpp。

有没有可能只用一个源文件呢?似乎是可以的,比如我们之前写的“Hello world”经典版等项目,不就只有一个main.cpp吗。

其实,就算是“Hello world”经典版这样一个小程序,我们也要支付链接器同志的出场费。因为,我们在代码中使用了std::cout,std::cout来自于C++标准库,而C++标准库,又可能调用了C的标准库——“标准库”文件,其实也是一种“目标文件”,通常它就是多个“目标文件”的打包——结论是,就算我们只写了一个“main.cpp”,编译器只编译出一个“main.o”,但是,仍然需要链接器将“main.o”和一些必要的标准库文件进行链接。

5.3.1.项目文件

项目文件并不是必须的,直接使用g++编译器写命令行,就可以完成C++源文件的编译或链接,但我们刚提过,C++的编译器是单个单个源文件编译的,非常不方便,所以,需要项目文件。

在Unix下,最流行的C++项目文件,称作“Makefile/制作文件”。那相当于是一种“批量处理文件”,并不需要任何IDE。在Windows下,通常每个不同的IDE都会制定自己的项目文件格式。Code::Blocks可以使用Makefile,但默认的,也是更方便的方式是使用Code::Blocks自定义的项目文件,扩展名为:“.cbp”(Code::Blocks Project)。

hint〖小提示〗:工作空间:管理多个项目

除了提供项目文件以外,Code::Blocks还提供了“工作空间文件”,用于同时管理多个相关的项目,其扩展名为:“.workspace”。

 

5.3.2.源文件、头文件

作为一种笼统的说法,我们往往将程序员所写的一切代码文件,都称为“源文件”,不过如果具体到“编译器是单个单个源文件进行编译”时,这里的“源文件”就仅限于扩展名为“.cpp”或“.c”,或“.cxx”等文件,而“.hpp”、“.h”、“.hxx”等文件被称为“头文件”。

从扩展名来区分有些本末倒置。我们刚刚说过,如果要代码的某处需要用到某个函数,而该函数在代码当前位置之前还未有定义,我们可以通过“声明”这个函数长什么样子,来骗过编译器(链接器会帮我们寻找那个函数的真实位置);那么,假设我们“a.cpp”里实现了100个函数,而在“b.cpp”和“c.cpp”里都要全部用上,是否意味着我们要写上200次“函数声明”呢?

当然不必,再往前的课程,我们提过:“函数声明”就像函数的名片,而“头文件”就是含有多个声明的名片夹,因为,我们可以将“a.cpp”中的100个函数声明,全都写到一个头文件中(通常就叫“a.hpp”),以后在任意需要用到相关函数的源文件里,通过:“#include a.hpp”导入所有全部声明。

由于“头文件”不是编译单元,所以类似像C++标准库这样主要以“头文件”形式提供的库,我们只需要让编译可以找到这些头文件即可;而对于另外一些,直接以“源文件”形式的扩展库,如果我们要在某个项目中使用它,就必须将它所提供“源文件”,最好先复制一份,然后加入项目文件,参与编译。

在《准备》章节中,我们安装了很多扩展库。我们注意到,几乎所有扩展库,都提供一个include子目录,通常这个目录之下(可能还会有子目录),会存在该扩展库的头文件。比如MySQL++库的include目录为:E:/cpp_ex_libs/MySQL++/3.0.6/include。

xczy〖课堂作业〗:查找MySQL的头文件

请进入MySQL++中的include目录,然后查找“mysql++.h”文件。

 

现在,假设以我们要用到MySQL++库,于是要包含它的一头文件,名为“mysql++”,源代码中是否写成如下?

#include “E:/cpp_ex_libs/MySQL++/3.0.6/include/mysql++.h”

用绝对路径不仅麻烦,而且让源代码变得很不通用。如果不写绝对路径,该如何让编译器找到mysql++.h这个文件呢?

原来,编译允许我们通过在命令行中,指定参数来告诉它,如果某个头文件找不到了,可以上哪里寻找。对于g++,这个参数是“-I头文件路径”,本例解决方法写出来类似:

g++.exe –I”E:/cpp_ex_libs/MySQL++/3.0.6/include/” ……

不过,我们并不直接写命令行,而是通过Code::Blocks调用编译器,因此,Code::Blocks要求我在项目文件中配置该项目所需要用到的扩展库的路径,具体的配置方法,我们将在IDE章节详谈。

5.3.3.使用头文件

头文件就像“名片”或“名片夹”,通常包含了一些数据声明、函数声明、类型定义(典型的如:struct/class)等内容。一个人的名片可以分发给很多人,一个头文件通常也要被多个源文件包含;再者,头文件之间还可以相互包含,这就带来了一种不太好的可能性:相同头文件往往会在同一个项目中被重复包含。

  • 唯一包含

生活中我们不会喜欢拥有同一个人的多张重复的名片。对C++来说,重复的包含一个头文件,不仅造成编译速度降低,还会带来编译错误:数据、函数声明允许重复,但一个类型不允许重复定义。

#define可以定义一个“宏符号”,并且可以使用 #ifndef来判断一个“符号”是否已经定义:

#define ABCD //定义一个宏符号:名为ABCD

#ifdef ABCD  //判断ABCD是否“已定义”
  /*
     这里的代码,仅当ABCD有定义
     才会接受编译,否则被直接略过
  */

#endif 

和本例中的 #ifdef指示符正好相反,#ifndef多出来的字母‘n’为not,它用来判断指定的符号是否“未定义”。我们可以方便地使用以下方法,来保证一个头文件只被实际包含一次。假设当前头文件名为:“my_header.h”

001 #ifndef  _MY_HEADER_H_
002 #define _MY_HEADER_H_
003

// 所要声明或定义的内容,放在此处


xxx #endif //_MY_HEADER_H_

我们首先假设这是一个项目中第一次包含到“my_eader.h”这个文件:

预编译器在处理“myHeader.h”时,首先就碰上下面这行预处理指令(通常是第一行):

#ifndef _MY_HEADER_H_

它判断宏符号“_MY_HEADER_H_”是否“未定义”,因为现在是第一次包含本文件,所以确实还没有定义过它,于是002行立即定义这个符号。然后才是本头文件的实质内容(从003行,一直到xxx前一行)。

接着,我们假设某一处代码,再次包含了这个头文件,但这回预编译器发现符号_MY_HEADER_H_ 已经定义过了,于它直接跳到xxx行(#endif)之后。

窍门显然在于:我们应该为每一个文件都取一个唯一的宏符号——通常也称为:“保护符”。 要让每一个头文件都“挂着”一个唯一的保护符,方法是让这个符号的名字和头文件名字有一个映射关系,习惯上是:将所有字母都改成大写,再把扩展名之前的‘.’改成‘_’,如果还想再酷一点,可以在前后分别再加一个下划线。

〖小提示〗:同名头文件怎么办?

有时候,位于不同目录下的两个头文件,确实有可能同名,这时我们需要使用更长一些的保护符名称。

 

  • 使用自定义的头文件

#include 接受两种形式的头文件指示

#include <library_header.h>
   #include "my_header.h"

通常我们对标准库头文件,使用尖括号(<>)形式,对当前项目我们自己所写的头文件,使用双引号("")形式。至于第三方库,如果我们已经在IDE中配置它的全局路径,也可以使用尖括号的形式,典型的如wxWidgets或boost库。

Code::Blocks提供了方便的“文件向导”用于生成头文件或源文件,但本节我们将学习如何“纯手工”地项目添加头文件及源文件。

先用向导创建一个控制台项目(命名为IncludeDemo1),一开始它只有一个文件:main.cpp。

点击主菜单“文件”-> “新建” ->“空白文件”(或热键:Ctrl + Shift + N),出现提问框:

“是否将新文件加入到当前项目(加入项目前,须先保存)?”

选择“是”,然后将文件存为:“my_file.hpp”。由于一个项目默认会有两个构建目标:Debug和Release版,所以接下来IDE会询问新建的文件要加入到哪些构建目标,请选择全部目标。

再新建一个文件,保存为“my_file.cpp”,同样加入全部目标。现在,项目文件树如下:

图 5-4 添加了my_file.cpp/.hpp之后的项目树

然后,我们将——

  • 在my_file.hpp中,定义MyStruct类,声明my_function函数;
  • 在my_file.cpp中,实现MyStruct类,实现my_function函数;
  • 在main.cpp中,使用MyStruct类,使用my_function函数。

 

请分别完成以下代码,为了方便排除输入代码不小心造成的错误,请每完成一个文件的内容之后,就按Ctrl + F9 进行编译,确实编译无误后,再进行下一步。

第一、my_file.hpp中的代码——声明、定义:

#ifndef _MY_FILE_HPP_
#define _MY_FILE_HPP_

//定义一个类
struct MyStruct
{
    MyStruct();
    ~MyStruct();
};


//声明一个函数:
void my_function(int year, int month, int day);

#endif //my_file.hpp

第二、my_file.cpp中的代码——实现

#include "my_file.hpp" //包含自定义的头文件

#include <iostream> //包含标准库文件

using namespace std;

MyStruct::MyStruct()
{
    cout << "MyStruct Construct." << endl;
}

MyStruct::~MyStruct()
{
    cout << "MyStruct Destruct." << endl;
}

void my_function(int year, int month, int day)
{
    cout << year << '-' << month << '-' << day << endl;
}

第三、main.cpp中的代码——使用

#include <iostream>

#include "my_file.hpp" //引入MyStruct和my_function 

using namespace std;

int main()
{
    MyStruct myStruct;
    
    my_function(1974, 4, 20);
    
    return 0;
}

5.3.4.库文件

C++扩展库的提代方式,可以是普通源文件,也可以是已经编译成目标文件的“库文件”。对于前者,使用起来和我们自己写的源文件没有两样,需要加入项目,参加编译;对于后者,只需要参加链接。链接形式上,又分成两种方式:静态链接库、动态链接库。之前我们在《准备》章节已经介绍过二者的区别。

  • 静态链接库

在“构建期”(也经常笼统地称为“编译期”)完成链接。即,当源文件编译完成之后,库文件和其它中间文件统一参加链接。因此,库文件也被合并到可执行文件(程序)。

静态链接库的文件扩展名,通常是“.lib”或“.a”。

  • 动态链接库

动态链接库的文件扩展名,通常是“.dll”或“.so”、“.o”。

这类库采用特定技术,允许在程序运行时,才将库与程序在内存中实现合并。合并所完成的主要任务,是“定位”。比如在主程序k.exe中,需要用到动态库m.dll中的一个签名为“void foo()”的函数,自然的,k.exe就需要知道“foo()”函数在m.dll中“地址”。由于程序(包括动态库)运行时,需要加载到内存中,因此这个地址,是一个“内存地址”。

如何定位函数(或其它内存对象,下面仅以函数为例)在动态库中的地址,又分为两种方法:

第一、自动导入

对于C++编程,当我们写一个复杂的程序,往往会将程序分成一个主项目(用于生成可执行文件)和好些子项目(用于生成动态库)。此时常用的方法是由可执行程序在启动时自动加载所需要的动态库,并完成定址。

程序如何知道需要加载哪些函数?又如何知道这些函数的地址?这就需要第三种库出现:“导入库/import library”(全称符号导入库)。“导入库”保存了某一动态库中全部(需要导出的)函数等符号的偏移地址。

偏移地址不是“内存地址”,它是一种各个函数在DLL文件中的地址,而当动态库被加载到内存时,整个动态库有一个起始地址。函数内存地址=DLL起始地址+函数偏移地址。

通常我们在编译一个动态库项目时,除了生成动态库文件以外,还会同时产生“导入库”。而当我们需要在一个执行文件使用这个动态库,并且想采用“自动导入”的方法,则需要将“导入库”以“静态链接”的方式,加入项目。对于g++,导入库通常以“.a”为扩展名。

C++对动态库自动导入实现没有统一标准,因此,这项技术通常无法在不同编译器之间使用。比如Borland C++ 或Visual C++编译出来的动态库,则g++编译出来的可执行文件无法以自动导入的方式调用。

第二、手工导入

手工导入动态库中函数等数据,其实是C语言的一项标准。C++因为兼容而获得了这项功能。要使用这项技术,要求必须以C语言的相关标准来导出一个函数或数据,通常被称为“C 语言接口”。C语言接口是操作系统暴露其编程接口时的事实标准。

程序在运行时,仅当在需要时,才通过一些特定的语句,将指定动态库加载到内存,再查找到所需的函数,然后调用这个函数。用完之后,还可以从内存中卸载掉这个动态库。这就是手工导入动态库这项技术最大的特色。

 

C++库的形式及用法有哪些,是时候重新看一眼了:

图 5-5 C++库形式及使用方法

 

不管采用什么形式,提供“头文件”,以免使用者自己去写声明,这是最基本的要求了。对于C++标准库,及boost中的很多子库,由于使用了“泛型”技术,所以采用的是“纯头文件”的形式。

最后,我们一直没有提到的,但对于库的使用非常重要的内容是:库的说明文档。通常开源的库都可以在共官方网站上找到说明文档的链接

 

[回到目录]

白话C++
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第5章 基础——5.3. C++项目组成 的相关文章

  • 在 Web 浏览器中禁用 F5 [重复]

    这个问题在这里已经有答案了 可能的重复 禁用浏览器的后退按钮 https stackoverflow com questions 961188 disable browsers back button 如何禁用浏览器上的 F5 刷新 htt
  • 通过另一个列表更新列表(linq)

    我有类 Data 的对象列表 如下所示 class Data int code string name DateTime date update 我还有另一个课程列表 例如 class RefCodes int old code int n
  • OpenGL缓冲区更新[重复]

    这个问题在这里已经有答案了 目前我正在编写一个模拟水的程序 以下是我所做的步骤 创建水面 平面 创建VAO 创建顶点缓冲区对象 在其中存储法线和顶点 将指针绑定到此 VBO 创建索引缓冲区对象 然后我使用 glDrawElements 渲染
  • .pdbs 会减慢发布应用程序的速度吗?

    如果 dll 中包含 pdb 程序调试 文件 则行号将出现在引发的任何异常的堆栈跟踪中 这会影响应用程序的性能吗 这个问题与发布与调试 即优化 无关 这是关于拥有 pdb 文件的性能影响 每次抛出异常时都会读取 pdb 文件吗 加载程序集时
  • libtool 在 Ubuntu 13.04 上构建 thrift 0.9.1 时出错

    在 Ubuntu 13 04 上构建 thrift 0 9 1 支持 C C java C perl python 时出现此错误 configure 不带任何选项运行 make 不带任何选项运行 Making all in test mak
  • 具有多个谓词的 C++11 算法

    功能如std find if来自algorithmheader 确实很有用 但对我来说 一个严重的限制是我只能为每次调用使用 1 个谓词count if 例如给定一个像这样的容器std vector我想同时应用相同的迭代find if 多个
  • DataGridView 列中的数字文本框

    我有一个DataGridView 我想要它的第一列或任何所需的列 其中有textboxes在其中 成为NUMERIC ONLY 我目前正在使用这段代码 private void dataGridViewItems EditingContro
  • 类中是否可以有虚拟类声明?

    我正在为个人项目中框架的各个组件设置一个接口 我突然想到了一些我认为可能对接口有用的东西 我的问题是这是否可能 class a public virtual class test 0 class b public a public clas
  • 虚拟并行端口模拟器

    在我的计算机网络课程中 我们应该通过使用本机寄存器 例如使用 outportb 等命令 来学习并行端口编程 我没有并行端口 因为我住在 2011 年 但想练习这些程序 我使用 dosbox 安装了旧的 Turboc 3 IDE 有没有一个程
  • Nhibernate:连接表并从其他表获取单列

    我有以下表格 create table Users Id uniqueidentifier primary key InfoId uniqueidentifier not null unique Password nvarchar 255
  • 关闭整数的最右边设置位

    我只需要关闭最右边的设置位即可 我的方法是找到最右边位的位置 然后离开该位 我编写这段代码是为了这样做 int POS int n int p 0 while n if n 2 0 p else break n n 2 return p i
  • 如何设置消息队列的所有者?

    System Messaging MessageQueue 类不提供设置队列所有权的方法 如何以编程方式设置 MSMQ 消息队列的所有者 简短的答案是 p invoke 对 windows api 函数的调用MQSetQueueSecuri
  • 编写具有多种类型的泛型扩展方法时的类型推断问题

    我正在为 IEnumerable 编写一个通用扩展方法 用于将对象列表映射到另一个映射对象列表 这就是我希望该方法的工作方式 IList
  • 如何使用 C# 查询远程 MS ACCESS .mdb 数据库

    我正在尝试使用 C 查询 mote MS ACCESS 数据库 mdb 文件 将文件复制到本地计算机时可以成功查询它 我只想远程放置文件 所以我的客户端程序不包含原始数据 static string m path http www xyz
  • 在 C++ 代码 gdb 中回溯指针

    我在运行 C 应用程序时遇到段错误 在 gdb 中 它显示我的一个指针位置已损坏 但我在应用程序期间创建了 10 万个这样的对象指针 我怎样才能看到导致崩溃的一个 我可以在 bt 命令中执行任何操作来查看该指针的生命周期吗 谢谢 鲁奇 据我
  • 选择 asp.net CheckBoxList 中的所有项目

    ASP NET 和 C 我想要一个带有 全选 项目的复选框列表 当这个特定项目是 已选择 所有其他都将被选择 也 当选择被删除时 这个项目 也将来自所有人 其他物品 选中 取消选中 任何其他项目只会有一个 对特定项目的影响 无论选择状态如何
  • 时间:2019-03-17 标签:c#TimerStopConfusion

    我想通过单击按钮时更改文本颜色来将文本框文本设置为 闪烁 我可以让文本按照我想要的方式闪烁 但我希望它在闪烁几次后停止 我不知道如何在计时器触发几次后让它停止 这是我的代码 public Form1 InitializeComponent
  • 解释这段代码的工作原理;子进程如何返回值以及在哪里返回值?

    我不明白子进程如何返回该值以及返回给谁 输出为 6 7 问题来源 http www cs utexas edu mwalfish classes s11 cs372h hw sol1 html http www cs utexas edu
  • 值和类型的简洁双向静态 1:1 映射

    我将从我想象如何使用我想要创建的代码开始 它不必完全像这样 但它是我在标题中所说的 简洁 的一个很好的例子 就我而言 它是将类型映射到相关的枚举值 struct bar foo
  • Emacs C++,打开相应的头文件

    我是 emacs 新手 我想知道 是否有在头文件 源文件和相应的源文件 头文件之间切换的快捷方式 是否有像通用 emacs 参考卡那样的参考卡 Thanks There s ff find other file 您可以使用以下方法将其绑定到

随机推荐

  • 1071 小赌怡情(15 分) java

    1071 小赌怡情 15 分 常言道 小赌怡情 这是一个很简单的小游戏 首先由计算机给出第一个整数 然后玩家下注赌第二个整数将会比第一个数大还是小 玩家下注 t 个筹码后 计算机给出第二个数 若玩家猜对了 则系统奖励玩家 t 个筹码 否则扣
  • MySQL 对字符串使用 STR_TO_DATE() 函数

    Ptw cwl 前面我们利用 date formate 函数 按照自己希望的格式来输出日期时间 我们从用户界面接收到的信息都是以字符串的形式在进行传递 如何把字符串转换为日期类型进行存储呢 可使用 str to date 函数 把字符串转换
  • 【Ardunio小车】智能小车组装

    一 材料和工具 原智能小车底盘 电机 电池槽 5号电池 残缺一角的2WD V15智能小车底板 电机驱动板 ardunio板 剪钳 二 过程 1 用剪钳把残缺的底板剪规整 2 将长脚螺丝固定在原底盘上 3 在长脚螺丝上加上螺帽 4 找到合适的
  • 罗技鼠标 MM Mx Master 2 掉帧的一种解决方法

    入手MM已经月余了 不得不说这是MAC下的相当有力的助手 但是这几天发现掉帧严重 要不就卡 要不就飞 网上说需要调整蓝牙和wifi 的服务顺序 我也调整了 无效 忽然发现蓝牙设备里面有两个 MM 链接 因为MM可以同时接三个设备 而我不知道
  • TS实现原生数组方法之slice()

    function slice description 返回一个新的数组对象 这一对象是一个由 begin 和 end 决定的原数组的浅拷贝 不改变原数组 param begin 可选参数 提取起始处的索引 省略则默认为0 param end
  • sharepoint 2013 部署步骤“添加解决方案”中出现错误: 已在此服务器场中安装 ID 为{guid}的功能。请使用强制属性显式地重新安装此功能。

    在部署sharepoint 2013解决方案wsp包时 出现一个错误 部署步骤 添加解决方案 中出现错误 已在此服务器场中安装 ID 为 735efe4e 8b50 4310 b588 c6ae2ba0759f 的功能 请使用强制属性显式地
  • 线性动归1---数字三角形及动归模型

    学习目标 1 了解动归 2 能用动归解决的条件 3 使用动归解题的一般步骤 学习内容 一 数字三角形1 0 题目描述 观察下面的数字金字塔 写一个程序查找从最高点到底部任意处结束的路径 使路径经过数字的和最大 每一步可以从当前点走到左下方的
  • Python数据分析复习整理(综合应用)

    数据分析 指使用适当的统计分析方法对搜集来的大量数据进行分析 提取有用信息并形成结论 从而对数据进行更加详细的研究和概括总结的过程 数组转置 数据转置是数组重塑的一种特殊形式 哑变量 又称虚拟变量 是用以反映质的属性的一种人工变量 是量化了
  • Tomact使用startup.bat启动闪退解决办法

    Tomact使用startup bat启动闪退解决办法 tomact启动 闪退 原因可能 是JRE环境变量未配置 或配置错误 尝试启动 Tom act
  • Crashlytics工具的接入

    最近应公司上级的指示 要接入Crashlytics来进行bug统计工具 根据官网的指示安装出现了好多坑 费了点劲接入了 但是本人感觉没有啥卵用 具体的看下面这篇文章的介绍 移动平台奔溃收集 http blog csdn net zhuoba
  • WEB前端后端简单区别,通俗理解

    前端开发和后台开发是有区别的 工作的内容和负责的东西是完全的不同的 以下以网站的开发为例 1 前端开发 前端开发现在一般指的就是web前端开发工程师 其负责是网站前端页面也就是网页的页面开发 简单的说网站前端负责是东西是网站用户可见的东西
  • 解决 Agent admitted failure to sign using the key 问题 with ssh

    配置ssh 之前要在本机上装上ssh 可以通过sudo apt get install ssh来安装 如果没有进行配置的话 登录到本机或者远程主机需要该主机的密码才行 下面进行无密码登录的配置 很简单 执行ssh keygen t rsa命
  • VM虚拟机中如何设置ip地址

    当我们在windows环境下 在cmd命令行中输入ipconfig可以看到我们的主机ip地址 但是我们创建了一台虚拟机 并且装好系统时 输入ifconfig 这里和windows下命令不一样 不要搞混了 时 会发现得不到ip地址 下面就说一
  • 删除数组中小于平均值的数

    利用指向一维数组的指针 将一个含有m m lt 10 个整数的一维数组中小于平均值的所有元素顺次删除掉 例如 原数组为3 5 7 4 1 删除后的数组应为5 7 4 提示 先输入数组元素个数 再依次输入数组元素的值 include
  • 吊打面试官:2023最新安全渗透面试题。

    安全渗透面试题 1 引言 2 安全渗透面试题 2 1 什么是渗透测试 2 2 你能提供一些常见的渗透测试工具和技术吗 2 3 在渗透测试中 如何利用SQL注入攻击 2 4 在渗透测试中 如何利用XSS攻击 2 5 在渗透测试中 如何利用代码
  • 编译busybox报错:scripts/Makefile.build:192: recipe for target 'loginutils/passwd.o' failed

    ubuntu18 04上编译busybox 提示上图中的错误 如何解决 修改busybox中的源码 include libbb h 中 增加一行 include
  • 已解决:H5移动端网页实现录音功能,js实现录音功能,包括安卓webview接口也可以使用

    遇到一个需求 需要做一个手机网页录音的功能 嵌入到webview中去 用安卓原生录音倒是可以 但是想着尽量去安卓化开发 就想着用纯的js前端代码去实现录音功能 在 Web 应用程序中 JavaScript 是运行在浏览器中的客户端脚本语言
  • Android自定义蒙层

    在开发过程中有时候会遇到特定情况下显示蒙层的需求 比如在点击某个Edittext搜索框时 部分界面出现浅透明蒙层 自定义蒙层 class MongolianView context Context attrs AttributeSet Li
  • 华为p20nfc怎么复制门禁卡_华为手机怎么绑定门禁卡

    绑定门禁卡的功能在华为手机的 钱包 应用内 点击 门钥匙 的选项 选择 添加 就可以将门禁卡贴近NFC功能进行自动读取 添加需要验证华为账号 使用的时候在钱包中选择门禁卡验证指纹之后 靠近读卡机即可 以下是详细介绍 1 打开华为 钱包 应用
  • 第5章 基础——5.3. C++项目组成

    回到目录 白话C 5 3 C 项目组成 首先我们知道了 写一个C 程序 可能需要多个源文件 比如a cpp b cpp 有没有可能只用一个源文件呢 似乎是可以的 比如我们之前写的 Hello world 经典版等项目 不就只有一个main