[Cherno C++ 笔记 P1~P10]安装、链接器、变量、函数、头文件
系列博客
[Cherno C++ 笔记 P1~P10]安装、链接器、变量、函数、头文件
[Cherno C++ 笔记 P11~P20]判断,循环,指针,引用,类
[Cherno C++ 笔记 P21~P30]static,枚举,构造函数,析构函数,继承,虚函数,接口,可见性
[Cherno C++ 笔记 P31~P40]数组、字符串、CONST、mutable、成员初始化列表、三元操作符、创建初始化对象、new关键字、隐式转换与explicit
[Cherno C++ 笔记 P41~P50]运算符重载、this、生存期、智能指针、复制与拷贝构造函数、箭头操作符、动态数组、std::vector、静态链接、动态库
前言
这个系列的视频需要一些基础,最好是学过C。
视频链接
P1 欢迎来到C++
P2 Windows 上安装C++
P3 Mac上安装C++
P4 Linux上安装C++
P5 C++是如何工作的
P6 C++编译器是如何工作的
P7 C++链接器是如何工作的
P8 C++变量
P9 C++函数
P10 C++头文件
P1~P4 安装部分
安装部分网上也有很多,安装也简单。
现在又很多工具可以用来写C++,这里推荐使用vs。
P5 C++是如何工作的
第一个C++程序
#include<iostream>
int main()
{
std::cout << "Hello world!" << std::endl;
std::cin.get();
}
#include<iostream>
叫做预处理语句编译器收到源文件后,会预先处理,找到<>中的文件,然后拷贝到这个位置。
main
是程序的入口,计算机从这个函数开始执行代码。在这里,main函数的返回类型是int,但是代码中没有返回任何东西,这是因为main函数是特殊的,它不一定需要返回值。没有写return进行返回的话,它会默认返回了0。
<<
符号叫做重载运算符,可以理解为一个函数。
视频有很大一段在讲vs的相关设置,感兴趣的话可以观看视频。
代码如何编译为二进制文件
项目中的每一个cpp文件都会被编译,但是头文件不会被编译,头文件的内容在预处理时包含到了cpp中。
每一个cpp文件都被编译为object file(目标文件)vs生成的文件后缀为.obj,我们需要将这些文件合并成一个执行文件,链接(Link)会将所有的obj文件黏合在一起,合并成一个.exe文件。
第一个函数
void Log(const char* message) {
std::cout << message << std::endl;
}
int main()
{
std::cout << "Hello world!" << std::endl;
Log("Hello World!");
std::cin.get();
}
将函数放在其他文件中
在源文件中新建一个Log.cpp
#include<iostream>
void Log(const char* message) {
std::cout << message << std::endl;
}
将Log函数放在其他地方后,main并不知道发生了这样的改变,它找不到叫做Log的函数,这时进行编译会报错。
我们需要在main所在的文件中声明Log函数存在
#include<iostream>
void Log(const char* message);
int main()
{
std::cout << "Hello world!" << std::endl;
Log("Hello World!");
std::cin.get();
}
声明:
声明表示这个符号、函数是存在的。void Log(const char* message);
就是一个声明,它没有函数体。
定义:
定义是说明这个函数到底是什么,是函数的函数体。
在构建整个项目时,所有.cpp文件都会被编译,链接器会找到正确的Log函数的定义在哪里,如果找不到定义,会报链接错误。
P6 C++编译器是如何工作的
C++编译器实际上负责什么?
C++编译器只负责一件事,将文本文件(我们写的代码)转换成称为目标文件的中间格式。
这些obj文件可以传递到链接,链接可以做它所有要链接的事情。
编译器在生成这些obj时:
- 预处理代码。这意味着所有的预处理器语句都会先处理。
- 记号化和解析。将文本转换为编译器真正能够理解和推理的格式。这创建了所谓的抽象语法树。语法树一旦被创建,编译器就可以开始实际生成代码
与java不同的是,c++并不关心文件,文件只是提供给编译器源代码的一种方式。 可以随便创建一个文件(如xxx.hello_world),只要告诉编译器这是一个c++文件,就可以进行编译。
我们提供给编译器的每个c++文件,编译器都将把文件变成翻译单元(一个cpp文件并不一定对应一个翻译单元),翻译单元生成一个obj文件。
obj文件中都是机器码,我们可以在vs中查看汇编语句。
- 首先,我们在这里打一个断点
- 进行调试
- 右键,转到反汇编
- 可以看到相应语句转换为的汇编语句
可以在属性中开启优化、关闭优化, 对比一下生成的汇编语句
P7 C++链接器是如何工作的
什么是链接
链接时一个过程,当我们编译好源文件,我们需要通过一个叫做链接的过程,现在链接的主要任务时找到每个符号和函数所在的地方,并把它们链接起来。
多个翻译单元之间并不互通,我们需要一种方法把这些文件连接起来成一个项目。即使只有一个翻译单元,也需要将main函数连接起来,这就是链接器的主要目的和要做的事情。
链接错误
未解决的外部符号
当链接器找不到确切定义时发生。
我们写一个死代码,它没有被任何函数调用,其中Log函数虽然被定义但并不存在。在这里依然会报错。
虽然我们没有使用Multiply函数,但是在技术上来讲,我们有可能会用到这个函数,所以链接器需要链接到它,所以会报错。
我们使用static将Multiply函数限制在该文件中使用,由于我们没有使用该函数,所以不再报错。
在这里,我们并没有真正导入我们所写的返回void的Log,这里返回为int的函数由于没有明确的定义,所以会引发报错。
有重复的符号
如果我们的函数或变量具有相同的名字或签名,链接器会不知道该链接哪一个。
在下面这个例子中,我们可以看到,即使没有申明,两个相同的函数放在不同的文件中,依然会报错。
P8 C++变量
略
P9 C++函数
略
P10 C++头文件
VS中的文件夹
VS中的文件夹并不是真正的文件夹,你可以在资源管理器中看到并没有这些文件夹。这些文件夹实质上是过滤器。
第一个头文件
Log.cpp
#include<iostream>
void Log(const char* message) {
std::cout << message << std::endl;
}
Log.h
#pragma once
void Log(const char* message);
Main.cpp
#include<iostream>
#include"Log.h"
int main()
{
Log("Hello world!");
std::cin.get();
}
通过#include"Log.h"
导入头文件,将头文件中的文本粘贴到该位置。
#pragma once
progma once: 只包括这个文件一次
#pragma once
监督这个头文件,阻止我们单个头文件被多次包含,并转换为单个翻译单元。
这里我想用集合来举例说明:
我们现在有两个集合A{a,b,c,d}和B{c,d,e,f}(a,b,c,d,e,f均为.h文件),当我们同时需要调取A和B时,#pragma once
的作用就是将A和B取并集为{a,b,c,d,e,f}而不是简单的合并成{a,b,c,d,c,d,e,f}。
可是使用另一种方法实现相同的功能
#ifndef _LOG_H
#define _LOG_H
void Log(const char* message);
#endif
#ifndef
检查看是否有一个叫做_LOG_H的符号被定义了,如果没有被定义,则继续,如果被定义,则禁止。
“” 和 <>
在编译程序时,<>和""表示两种不同含义。
“”用来包含相对于当前文件的文件
<>用来搜索包含路径中的文件
“”也具有<>的功能
[未完待续]