Chapter 1: Getting Started
The first example 第一个例子
//hello.cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> msg {"Hello", "C++", "World", "!"};
for (const string& word : msg) //C++11 standard
{
cout << word << " ";
}
cout << endl;
}
- 编译hello.cpp
g++ hello.cpp
- 在初始化msg变量时,使用了C++11的标准。因此在对其编译时可能(不同编译器情况有些许差异)需要使用如下命令:
g++ hello.cpp --std=c++11
- 经上述命令后,会生成a.out可执行文件。可以通过 [-o filename] 操作改变输出的文件名:
g++ hello.cpp --std=c++11 -o hello
- 执行
./hello
Compile and Link 编译和链接
Part1
在这个案例中, main()
函数调用 mul()
, 并且 mul()
可以返回变量 a
和变量 b
的乘积.
//mainmul.cpp
#include <iostream>
using namespace std;
int mul(int a, int b)
{
return a * b;
}
int main()
{
int a, b;
int result;
cout << "Pick two integers:";
cin >> a;
cin >> b;
result = mul(a, b);
cout << "The result is " << result << endl;
return 0;
}
- 函数声明 (function prototypes)一般放在头文件中(*.h, *.hpp)
int mul(int a, int b);
其中变量a和b的名字可以省略,即写成int mul(int, int); 的形式;
- 函数定义 (function definitiones) 一般放在源文件中(*.c, *.cpp)
int mul(int a, int b)
{
return a * b;
}
Part2
按照上面的描述,应该有main.cpp、mul.hpp、以及mul.cpp
//main.cpp
#include <iostream>
#include "mul.hpp"
using namespace std;
int main()
{
int a, b;
int result;
cout << "Pick two integers:";
cin >> a;
cin >> b;
result = mul(a, b);
cout << "The result is " << result << endl;
return 0;
}
//mul.hpp
#pragma once // 保证头文件只被编译一次。
int mul(int a, int b);
//mul.cpp
#include "mul.hpp"
int mul(int a, int b)
{
return a * b;
}
源文件的组织形式如下图所示:
编译和链接的过程,如下图所示:
其中
-c : 编译,不链接,生成.obj文件
-o: 链接,即从object文件生成可执行程序的过程。
当然,也可以采用懒一点的办法,一次性编译完成:
g++ main.cpp mul.cpp -o mul
Different Errors 不同的错误
Compilation Errors 编译错误
编译器在无法understand (理解)
源代码时会出现编译错误。比如,在以下示例中,n1
乘以 n2
。但它们是什么呢?编译器找不到它们的定义。当然,编译器将不知道如何将它们相乘,只能弹出一个错误消息,一个编译错误消息。此外,以下源代码中也缺少了 ;
,这也会导致另一个编译错误。
int mul(int a, int b)
{
return n1 * n2
}
前面源代码生成的编译错误信息如下所示
$ g++ -c mul.cpp
mul.cpp:5:12: error: use of undeclared identifier 'n1'
return n1 * n2
^
mul.cpp:5:17: error: use of undeclared identifier 'n2'
return n1 * n2
^
mul.cpp:5:19: error: expected ';' after return statement
return n1 * n2
^
;
3 errors generated.
Link Errors 链接错误
链接错误发生在链接阶段。最常见的链接错误是undefined symbol (未定义符号)
。如果链接器在目标文件中找不到函数的定义,它就会出现。在前面的例子中,如果将函数名从 mul
拼写错误为 Mul
,则会出现这种错误。
//mul.cpp
#include "mul.hpp"
int Mul(int a, int b) //mul() is callled in main() but here it is Mul()
{
return a * b;
}
即使可以分别编译两个文件 main.cpp
和 mul.cpp
,但最终仍会出现以下链接错误。
$ g++ main.o mul.o -o mul
Undefined symbols for architecture arm64:
"mul(int, int)", referenced from:
_main in main.o
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
如果出现链接错误,请首先检查链接选项。还应该检查被报错的函数名。
Runtime Errors 运行错误
#include <iostream>
int main()
{
int * p = nullptr;
p[0] = 5;
std::cout << p[0] << std::endl;
return 0;
}
如果运行程序会出现以下错误。通常情况下,段错误(Segmentation fault)是由于读取或写入一些不允许的内存区域引起的,它是大多数初学者的噩梦。如果出现这种情况,请检查指针访问。还有一些其他类型的运行时错误,如除数为0等。
$ ./a.out
segmentation fault ./a.out
Preprocessor and Macros 预处理与宏定义
在前面的部分简要介绍了编译过程。在编译器真正编译源代码之前,有几个步骤要经历。编译前的前一个步骤是预处理。
预处理指令以 #
字符开始,每个指令占据一行。最常用的预处理指令包括:include
, define
, undef
, if
, ifdef
, ifndef
, else
, elif
, endif
, line
, error
and pragma
. include
is 刚刚介绍过,在几乎所有的源文件中都会出现。
define
是另一个常用的指令,可用于定义一些宏。在以下源代码中,通过使用#define PI 3.14
将 PI
定义为 3.14
#define PI 3.14
double len(double r)
{
return 2.0 * PI * r;
}
经过预处理后,源代码会变成以下样子。PI
已经被替换成了 3.14
。然后这段代码会被发送给编译器。 PI
不是一个变量。对于宏的预处理就像文本替换一样。
double len(double r)
{
return 2.0 * 3.14 * r;
}
宏的行为类似于文本替换,有时可能会导致错误。如果我们将 PI
定义为 2.14+1.0
,这个语句在语法上是正确的。
#define PI 2.14+1.0
double len(double r)
{
return 2.0 * PI * r;
}
是的,经过预处理后,函数的返回值将是 4.28 + r
,而不是你预期的 2.0 * 3.14 * r
。尽管源代码会成功编译,编译器不会报任何警告或错误,但在使用宏时需要非常小心。宏的文本替换机制可能导致意外的结果,这需要开发者格外注意。
double len(double r)
{
return 2.0 * 2.14+1.0 * r; //= 4.28 + r
}
宏可以定义得像函数一样。有时宏的效率甚至可以比函数更高,因为宏没有函数调用的开销。
#define MAX(a,b) (((a)>(b))?(a):(b))
int main()
{
//...
float a = 2.0f;
float b = 3.0f;
float m;
m = MAX(a, b);
//...
}
Simple Input and Output 简单的输入输出
下面是C风格的输出源代码。函数 printf()
可以接受多个参数。第一个参数是一个字符串,用于指定如何解释数据。%d
让函数将 v
解释为一个整数。另一个类似的函数是 fprintf()
。
int v = 100;
printf("Hello, value = %d\n", v);
C++风格的输出源代码与C风格不同。可以使用重载运算符 <<
。这里的 cout
是 std
命名空间中的 ostream
类的对象。它在头文件 iostream
中声明,所以在使用之前应该先包含 iostream
。std::endl
是一个仅用于输出的 I/O 操纵器。它会输出一个换行符并刷新缓冲区。
int v = 100;
std::cout << "Hello, value = " << v << std::endl;
要从标准输入流中获取数据,可以调用 C 函数 scanf()
,如下所示。要注意,应该使用 &
取变量 v
的地址。否则,程序会尝试写入 v
的值所对应的地址。这可能是一个随机数,在这里可能会遇到 segmentation fault
运行时错误。
int v;
int ret = scanf("%d", &v);
C++风格的输入更安全。可以使用重载的运算符 >>
,如下所示。std::cin
对象会自动将输入数据转换为 v
的格式。
int v;
std::cin >> v;
Command Line Arguments 命令行参数
命令行参数可以通过 main()
函数的参数来处理。argc
参数表示命令行参数的数量,而 argv[]
是一个数组,其中的每个元素指向一个 char
类型的数组字符串。
//argument.cpp
#include <iostream>
using namespace std;
int main(int argc, char * argv[])
{
for (int i = 0; i < argc; i++)
cout << i << ": " << argv[i] << endl;
}
$ ./argument hello.cpp -o hello
0: ./argument
1: hello.cpp
2: -o
3: hello
Reference:
[1] 《C/C++从基础语法到优化策略》https://www.bilibili.com/video/BV1Vf4y1P7pq/?spm_id_from=333.999.0.0&vd_source=eb2aff91d0c138676172d1f9746b9f1e
[2] https://github.com/ShiqiYu/CPP