C++ Chapter 1: Getting Started

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

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.cppmul.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 预处理与宏定义

在前面的部分简要介绍了编译过程。在编译器真正编译源代码之前,有几个步骤要经历。编译前的前一个步骤是预处理。

预处理指令以 # 字符开始,每个指令占据一行。最常用的预处理指令包括:includedefineundefififdefifndefelseelifendiflineerror and pragma. include is 刚刚介绍过,在几乎所有的源文件中都会出现。

define 是另一个常用的指令,可用于定义一些宏。在以下源代码中,通过使用#define PI 3.14PI定义为 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风格不同。可以使用重载运算符 <<。这里的 coutstd 命名空间中的 ostream 类的对象。它在头文件 iostream 中声明,所以在使用之前应该先包含 iostreamstd::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

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦