面向零基础初学者的现代-C---教程-一-

92 阅读10分钟

面向零基础初学者的现代 C++ 教程(一)

原文:Modern C++ for Absolute Beginners

协议:CC BY-NC-SA 4.0

一、介绍

亲爱的读者:

恭喜你选择学习 C++ 编程语言,感谢你拿起这本书。我叫 Slobodan Dmitrovi,是一名软件开发人员和技术作家,我将尽我所能向您介绍一个 C++ 的美丽世界。

这本书致力于以一种结构化的、简单的和友好的方式向读者介绍 C++ 编程语言。我们将尽可能使用“足够的理论和大量的例子”的方法。

对我来说,C++ 是人类智慧的奇妙产物。这些年来,我确实认为它是一件美丽而优雅的事情。C++ 是一种与众不同的语言,它的复杂性令人惊讶,但在许多方面都非常圆滑和优雅。它也是一种不能通过猜测来学习的语言,一种容易出错并且很难正确掌握的语言。

在本书中,我们将首先熟悉语言基础。然后,我们将转向标准库。一旦我们了解了这些,我们将更详细地描述现代 C++ 标准。

在每一节之后,都有源代码练习来帮助我们更有效地采用所学的材料。让我们开始吧!

二、什么是 C++?

C++ 是一种编程语言。一种标准化的、通用的、面向对象的编译语言。C++ 附带了一组称为 C++ 标准库的函数和容器。比雅尼·斯特劳斯特鲁普创造了 C++ 作为 C 编程语言的扩展。尽管如此,C++ 已经发展成为一种完全不同的编程语言。

让我们强调一下:C 和 C++ 是两种不同的语言。C++ 开始是“带类的 C”,但现在它是一种完全不同的语言。所以,C++ 不是 C;C++ 不是有类的 C;它只是 C++。而且也没有 C/C++ 编程语言这种东西。

C++ 广泛用于所谓的系统编程和应用编程。C++ 是一种语言,它允许我们深入到底层,如果需要的话,我们可以执行底层的例程,或者通过模板和类等抽象机制高飞。

2.1 C++ 标准

C++ 由 ISO C++ 标准管理。这里按时间顺序列出了多个 ISO C++ 标准:C++03、C++11、C++14、C++17,以及即将推出的 C++20 标准。

从 C++11 开始的每一个 C++ 标准都被称为“现代 C++”。现代 C++ 是我们将在本书中教授的内容。

三、C++ 编译器

C++ 程序通常是分布在一个或多个源文件中的 C++ 代码的集合。C++ 编译器编译这些文件,并将它们转换成目标文件。目标文件通过链接器链接在一起,以创建可执行文件或库。在撰写本文时,一些比较流行的 C++ 编译器是:

  • g++ 前端(作为 GCC 的一部分)

  • Visual C++(作为 Visual Studio IDE 的一部分)

  • Clang(作为 LLVM 的一部分)

3.1 安装 C++ 编译器

以下部分解释了如何在 Linux 和 Windows 上安装 C++ 编译器,以及如何编译和运行我们的 C++ 程序。

Linux 上的 3.1.1

要在 Linux 上安装 C++ 编译器,请在终端中键入以下命令:

sudo apt-get install build-essential

为了编译 C++ 源文件 source.cpp ,我们输入:

g++ source.cpp

该命令将生成一个默认名称为 a.out 的可执行文件。要运行可执行文件,请键入:

./a.out

为了针对 C++11 标准进行编译,我们添加了-std=c++11 标志:

g++ -std=c++11 source.cpp

为了启用警告,我们添加了 -Wall 标志:

g++ -std=c++11 -Wall source.cpp

为了生成自定义的可执行文件名称,我们添加了 -o 标志,后跟一个可执行文件名称:

g++ -std=c++11 -Wall source.cpp -o myexe

同样的规则也适用于 Clang 编译器。用 clang++ 替换 g++

3.1.2 在 Windows 上

在 Windows 上,我们可以安装一个免费的 Visual Studio。

选择创建新项目,确保选择了 C++ 语言选项,选择- 空项目-点击下一步,点击创建。转到解决方案资源管理器面板,右击项目名称,选择添加新项目c++ 文件(。cpp) ,输入文件名( source.cpp ),点击添加。按 F5 运行程序。

我们也可以这样做:选择创建一个新项目,确保选择了 C++ 语言选项,选择—控制台 App—点击下一步,点击创建

如果创建新项目按钮不可见,选择文件-新建-项目,重复其余步骤。

四、我们的第一个项目

让我们使用我们选择的文本编辑器或 C++ IDE 创建一个空白文本文件,并将其命名为 source.cpp 。首先,让我们创建一个空的 C++ 程序,它什么也不做。 source.cpp 文件的内容是:

int main(){}

函数main是主程序入口点,我们程序的开始。当我们运行我们的可执行文件时,main函数体内的代码被执行。一个函数的类型是int(并向系统返回一个结果,但是我们先不要担心这个)。保留名main是一个函数名。它后面是圆括号内的参数列表(),后面是用大括号标记的函数体{}。标记函数体开始和结束的大括号也可以在不同的行上:

int main()
{

}

这个简单的程序什么也不做,圆括号中没有列出任何参数,函数体中也没有任何语句。理解这是主程序签名是很重要的。

还有另一个main函数签名接受两个不同的参数,用于操作命令行参数。现在,我们将只使用第一种形式。

4.1 评论

C++ 中的单行注释以双斜线//开头,编译器会忽略它们。我们使用它们来注释或记录代码,或者将它们用作注释:

int main()
{
    // this is a comment
}

我们可以有多个单行注释:

int main()
{
    // this is a comment
    // this is another comment
}

多行注释以/*开始,以*/结束。它们也被称为 C 风格的注释。示例:

int main()
{
    /* This is a
    multi-line comment */
}

4.2 Hello World 示例

现在我们已经准备好第一次看到我们的“Hello World”示例。下面的程序是最简单的“Hello World”例子。它打印出 Hello World。在控制台窗口中:

#include <iostream>

int main()
{
    std::cout << "Hello World.";
}

信不信由你,这个例子的详细分析和解释长达 15 页。我们现在可以深入研究它,但是在这一点上我们不会更明智,因为我们首先需要知道什么是头、流、对象、操作符和字符串文字。别担心。我们会到达那里。

简短的解释

#include <iostream>语句通过#include指令将iostream头包含到我们的源文件中。iostream标题是标准库的一部分。我们需要包含它来使用std::cout对象,也称为标准输出流。<<操作符将 Hello World 字符串文字插入到输出流中。字符串文字用双引号括起来"";标志着语句的结束。语句是 c++ 程序中被执行的部分。在 C++ 中,语句以分号;结尾。std是标准库名称空间,而::是范围解析操作符。对象coutstd名称空间内,为了访问它,我们需要在调用前加上std::。我们将在本书的后面部分更加熟悉所有这些,尤其是std::部分。

简要说明

简而言之,std::cout <<是 C++ 中向标准输出/控制台窗口输出数据的自然方式。

我们可以通过用多个<<操作符分隔它们来输出多个字符串文字:

#include <iostream>

int main()
{
    std::cout << "Some string." << " Another string.";
}

为了在新行上输出,我们需要输出一个新行字符\n文本。字符用单引号括起来'\n'

示例:

#include <iostream>

int main()
{
    std::cout << "First line" << '\n' << "Second line.";
}

\代表一个转义序列,一种输出某些特殊字符的机制,比如换行字符'\n'、单引号字符'\''或双引号字符'\"'

字符也可以是单个字符串文字的一部分:

#include <iostream>

int main()
{
    std::cout << "First line\nSecond line.";
}

不使用 using 命名空间 std

web 上的许多例子通过using namespace std;语句将整个 std 名称空间引入当前范围,只是为了能够键入cout而不是std::cout。虽然这可能会让我们少打五个额外的字符,但由于许多原因,这是错误的。我们不希望将整个 std 名称空间引入当前范围,因为我们希望避免名称冲突和歧义。记住:不要通过using namespace std;语句将整个std名称空间引入当前范围。所以,与其用这种错误的方法:

#include <iostream>

using namespace std; // do not use this

int main()
{
    cout << "A bad example.";
}

使用以下内容:

#include <iostream>

int main()
{
    std::cout << "A good example.";
}

对于驻留在std名称空间内的对象和函数的调用,在需要的地方添加std::前缀。

五、类型

每个实体都有一个类型。什么是类型?类型是一组可能的值和操作。类型的实例称为对象。对象是内存中具有特定类型值的某个区域(不要与也称为对象的类的实例相混淆)。

5.1 基本类型

C++ 有一些内置类型。我们经常称它们为基本类型。声明是将名称引入当前范围的语句。

布尔型

让我们声明一个类型为bool的变量b。这种类型保存truefalse.的值

int main()
{
    bool b;
}

这个例子声明了一个类型为bool的变量b。就是这样。变量没有初始化,在构造时没有给它赋值。为了初始化一个变量,我们使用一个赋值操作符=,后跟一个初始化器:

int main()
{
    bool b = true;
}

我们也可以使用大括号{}进行初始化:

int main()
{
    bool b{ true };
}

这些例子声明了一个bool类型的(局部)变量b,并将其初始化为值true。我们的变量现在保存了一个值true。所有局部变量都应该初始化。访问未初始化的变量会导致未定义的行为,缩写为 UB。在接下来的章节中会有更多的介绍。

字符类型

类型char,简称字符类型,用于表示单个字符。该类型可存储'a''Z'等字符。字符类型的大小正好是一个字节。在 C++ 中,字符文字用单引号''括起来。为了声明和初始化一个char类型的变量,我们写:

int main()
{
    char c = 'a';
}

现在我们可以打印出 char 变量的值:

#include <iostream>

int main()
{
    char c = 'a';
    std::cout << "The value of variable c is: " << c;
}

声明并初始化后,我们可以访问变量并更改其值:

#include <iostream>

int main()
{
    char c = 'a';
    std::cout << "The value of variable c is: " << c;
    c = 'Z';
    std::cout << " The new value of variable c is: " << c;
}

char类型在内存中的大小通常是一个字节。我们通过一个sizeof操作符获得类型的大小:

#include <iostream>

int main()
{
    std::cout << "The size of type char is: " << sizeof(char) << " byte(s)";
}

还有其他字符类型,比如用于保存 Unicode 字符集字符的wchar_t,用于保存 UTF-16 字符集的char16_t,但是现在,让我们坚持使用类型char

字符文字是用单引号括起来的字符。例如:'a''A''z''X''0'等。

字符集中的每个字符都由一个整数表示。这就是为什么我们可以给我们的char变量分配数字文字(直到某个数字)和字符文字:

int main()
{
    char c = 'a';
    // is the same as if we had
    // char c = 97;
}

我们可以写成:char c = 'a';也可以写成char c = 97;(大概)是一样的,因为 ASCII 表中的'a'字符是用97的数字来表示的。在大多数情况下,我们将使用字符来表示 char 对象的值。

整数类型

另一种基本类型是int称为整型。我们用它来存储整数值(整数),包括负数和正数:

#include <iostream>

int main()
{
    int x = 123;
    int y = -256;
    std::cout << "The value of x is: " << x << ", the value of y is: " << y;
}

这里我们声明并初始化了两个类型为int的变量。int的大小通常是 4 个字节。我们也可以用另一个变量来初始化这个变量。它将收到其值的副本。我们在内存中仍然有两个独立的对象:

#include <iostream>

int main()
{
    int x = 123;
    int y = x;
    std::cout << "The value of x is: " << x << " ,the value of y is: " << y;
    // x is 123
    // y is 123
    x = 456;
    std::cout << "The value of x is: " << x << " ,the value of y is: " << y;
    // x is now 456
    // y is still 123
}

一旦我们声明了一个变量,我们就只通过变量名来访问和操作变量名,而不用类型名。

整数可以是十进制、八进制和十六进制。八进制文字以前缀0,开始,十六进制文字以前缀0x开始。

int main()
{
    int x = 10;     // decimal literal
    int y = 012;    // octal literal
    int z = 0xA;    // hexadecimal literal
}

所有这些变量都被初始化为由不同的整数文字表示的值10。在大多数情况下,我们将使用十进制文字。

还有其他整数类型如int64_t等,但我们暂时坚持使用int

浮点类型

C++ 中有三种浮点类型:floatdouble, long double,,但我们将坚持使用类型double(双精度)。我们用它来存储浮点值/实数:

#include <iostream>

int main()
{
    double d = 3.14;
    std::cout << "The value of d is: " << d;
}

一些浮点文字可以是:

int main()
{
    double x = 213.456;
    double y = 1.;
    double z = 0.15;
    double w = .15;
    double d = 3.14e10;
}

类型空隙

类型void是没有值的类型。如果我们不能拥有那种类型的对象,那么这种类型的目的是什么?问得好。虽然我们不能拥有类型为void的对象,但是我们可以拥有类型为void的函数。不返回值的函数。我们也可以有一个标有void*void指针类型。在后面的章节中,当我们讨论指针和函数时,会有更多的介绍。

5.2 类型修饰符

类型可以有修饰符。一些修饰符是signedunsignedsigned(如果省略,则为默认值)意味着该类型可以保存正值和负值,而unsigned意味着该类型具有无符号表示。其他的修饰符是关于大小的:short - type 的宽度至少是 16 位,long - type 的宽度至少是 32 位。此外,我们现在可以组合这些修饰符:

#include <iostream>

int main()
{
    unsigned long int x = 4294967295;
    std::cout << "The value of an unsigned long integer variable is: " << x;
}

类型int默认为signed

5.3 变量声明、定义和初始化

在当前作用域中引入一个名字叫做声明。从现在开始,在当前范围内,我们让世界知道有一个某种类型的名称(例如,一个变量)。在声明中,我们在变量名前面加上一个类型名。声明示例:

int main()
{
    char c;
    int x;
    double d;
}

我们可以在同一行声明多个名称:

int main()
{
    int x, y, z;
}

如果存在一个对象的初始化器,那么我们称之为初始化。我们将一个对象声明并初始化为一个特定的值。我们可以用多种方式初始化一个对象:

int main()
{
    int x = 123;
    int y{ 123 };
    int z = { 123 };
}

变量定义是在内存中为一个名字设置一个值。这个定义是确保我们可以在我们的程序中访问和使用这个名字。粗略地说,它是一个声明,后跟一个初始化(对于变量)和一个分号。定义也是一种声明。定义示例:

int main()
{
    char c = 'a';
    int x = 123;
    double d = 456.78;
}

六、练习

6.1 Hello World 和评论

写一个有注释的程序,输出“Hello World”在一行上写着“C++ 太棒了!”在新的一行。

#include <iostream>

int main()
{
    // this is a comment
    std::cout << "Hello World." << '\n';
    std::cout << "C++ rocks!";
}

6.2 声明

编写一个程序,在main函数中声明三个变量。变量有charintdouble.类型,变量的名字是任意的。因为我们不使用任何输入或输出,所以我们不需要包含头。

int main()
{
    char mychar;
    int myint;
    double mydouble;
}

6.3 定义

编写一个在main函数中定义三个变量的程序。变量有charintdouble.类型,变量的名字是任意的。初始化器是任意的。

int main()
{
    char mychar = 'a';
    int myint = 123;
    double mydouble = 456.78;
}

6.4 初始化

编写一个在main函数中定义三个变量的程序。变量有charintdouble.类型,变量的名字是任意的。初始化器是任意的。使用初始化列表执行初始化。之后打印这些值。

#include <iostream>

int main()
{
    char mychar{ 'a' };
    int myint{ 123 };
    double mydouble{ 456.78 };
    std::cout << "The value of a char variable is: " << mychar << '\n';
    std::cout << "The value of an int variable is: " << myint << '\n';
    std::cout << "The value of a double variable is: " << mydouble << '\n';
}

七、运算符

7.1 赋值运算符

赋值运算符=为变量/对象赋值:

int main()
{
    char mychar = 'c';    // define a char variable mychar
    mychar = 'd';         // assign a new value to mychar

    int x = 123;          // define an integer variable x
    x = 456;              // assign a new value to x
    int y = 789;          // define a new integer variable y
    y = x;                // assing a value of x to it
}

7.2 算术运算符

我们可以使用算术运算符进行算术运算。其中一些是:

+ // addition
- // subtraction
* // multiplication
/ // division
% // modulo

示例:

#include <iostream>

int main()
{
    int x = 123;
    int y = 456;
    int z = x + y; // addition
    z = x - y; // subtraction
    z = x * y; // multiplication
    z = x / y; // division
    std::cout << "The value of z is: " << z << '\n';
}

在我们的例子中,整数除法产生值0.,这是因为两个操作数都是整数的整数除法的结果被截断为零。在表达式x / y中,xy是操作数,/是运算符。

如果我们想要一个浮点结果,我们需要使用类型double并确保至少有一个除法操作数也是类型double:

#include <iostream>

int main()
{
    int x = 123;
    double y = 456;
    double z = x / y;
    std::cout << "The value of z is: " << z << '\n';
}

同样,我们可以有:

#include <iostream>

int main()
{
    double z = 123 / 456.0;
    std::cout << "The value of z is: " << z << '\n';
}

结果是一样的。

7.3 复合赋值运算符

复合赋值操作符允许我们执行算术运算,并用一个操作符赋值一个结果:

+= // compound addition
-= // compound subtraction
*= // compound multiplication
/= // compound division
%= // compound modulo

示例:

#include <iostream>

int main()
{
    int x = 123;
    x += 10;    // the same as x = x + 10
    x -= 10;    // the same as x = x - 10
    x *= 2;     // the same as x = x * 2
    x /= 3;     // the same as x = x / 3
    std::cout << "The value of x is: " << x;
}

7.4 递增/递减运算符

递增/递减运算符递增/递减对象的值。这些操作符是:

++x // pre-increment operator
x++ // post-increment operator
--x // pre-decrement operator
x-- // post-decrement operator

一个简单的例子:

#include <iostream>

int main()
{
    int x = 123;
    x++;    // add 1 to the value of x
    ++x;    // add 1 to the value of x
    --x;    // decrement the value of x by 1
    x--;    // decrement the value of x by 1
    std::cout << "The value of x is: " << x;
}

前递增和后递增操作符都将1加到我们对象的值上,前递减和后递减操作符都将我们对象的值减一。除了实现机制(非常宽泛地说)之外,两者的区别在于,使用预递增运算符时,首先添加一个值1。然后在表达式中计算/访问该对象。使用后增量,首先对对象进行求值/访问,然后添加1的值。对于接下来的下一个陈述来说,这并没有什么不同。无论使用什么版本的运算符,对象的值都是相同的。唯一的区别是表达式中使用它的时间。

八、标准输入

C++ 提供了接受用户输入的工具。我们可以把标准输入想象成我们的键盘。接受一个整数并将其打印出来的简单例子是:

#include <iostream>

int main()
{
    std::cout << "Please enter a number and press enter: ";
    int x = 0;
    std::cin >> x;
    std::cout << "You entered: " << x;
}

std::cin是标准的输入流,它使用>>操作符提取已经读入变量的内容。std::cin >> x;语句的意思是:从一个标准输入读入一个 x 变量cin对象驻留在std名称空间中。因此,std::cout <<用于输出数据(到屏幕),而std::cin >>用于输入数据(从键盘)。

我们可以从标准输入中接受多个值,用多个>>操作符将它们分开:

#include <iostream>

int main()
{
    std::cout << "Please enter two numbers separated by a space and press enter: ";
    int x = 0;
    int y = 0;
    std::cin >> x >> y;
    std::cout << "You entered: " << x << " and " << y;
}

我们可以接受不同类型的值:

#include <iostream>

int main()
{
    std::cout << "Please enter a character, an integer and a double: ";
    char c = 0;
    int x = 0;
    double d = 0.0;
    std::cin >> c >> x >> d;
    std::cout << "You entered: " << c << ", " << x << " and " << d;
}

九、练习

9.1 标准输入

编写一个程序,从标准输入中接受一个整数,然后打印该数字。

#include <iostream>

int main()
{
    std::cout << "Please enter a number: ";
    int x;
    std::cin >> x;
    std::cout << "You entered: " << x;
}

9.2 两个输入

编写一个程序,从标准输入中接受两个整数,然后打印出来。

#include <iostream>

int main()
{
    std::cout << "Please enter two integer numbers: ";
    int x;
    int y;
    std::cin >> x >> y;
    std::cout << "You entered: " << x << " and " << y;
}

9.3 多输入

编写一个程序,分别从标准输入中接受类型为charintdouble的三个值。之后打印出这些值。

#include <iostream>

int main()
{
    std::cout << "Please enter a char, an int and a double: ";
    char c;
    int x;
    double d;
    std::cin >> c >> x >> d;
    std::cout << "You entered: " << c << ", " << x << ", and " << d;
}

9.4 输入和算术运算

编写一个程序,接受两个int数,将它们相加,并将结果赋给第三个整数。随后打印出结果。

#include <iostream>

int main()
{
    std::cout << "Please enter two integer numbers: ";
    int x;
    int y;
    std::cin >> x >> y;
    int z = x + y;
    std::cout << "The result is: " << z;
}

9.5 后加薪和复合派任

编写一个程序,定义一个名为xint变量,其值为123,在下一条语句中后递增该值,并在下面的语句中使用复合赋值运算符添加20的值。随后打印出该值。

#include <iostream>

int main()
{
    int x = 123;
    x++;
    x += 20;
    std::cout << "The result is: " << x;
}

9.6 整数和浮点除法

编写一个程序,将数字 9 和2相除,并将结果赋给一个int和一个double变量。然后修改其中一个操作数,使其为double类型,并观察浮点除法的不同结果,其中至少有一个操作数为double类型。之后打印出这些值。

#include <iostream>

int main()
{
    int x = 9 / 2;
    std::cout << "The result is: " << x << '\n';
    double d = 9 / 2;
    std::cout << "The result is: " << d << '\n';
    d = 9.0 / 2;
    std::cout << "The result is: " << d;
}

十、数组

数组是相同类型的对象序列。我们可以如下声明一个类型为char的数组:

int main()
{
    char arr[5];
}

此示例声明了一个包含 5 个字符的数组。要声明一个包含五个元素的类型为int的数组,我们可以使用:

int main()
{
    int arr[5];
}

要初始化一个数组,我们可以使用初始化列表{}:

int main()
{
    int arr[5] = { 10, 20, 30, 40, 50 };
}

我们示例中的初始化列表{ 10, 20, 30, 40, 50 }用大括号和逗号分隔的元素标记。这个初始化列表用列表中的值初始化我们的数组。第一个数组元素现在有一个值10;第二个数组元素现在的值为20等。最后一个(第五个)数组元素现在的值为50

我们可以通过下标操作符和索引来访问单个数组元素。第一个数组元素的索引为 0,我们通过以下方式访问它:

int main()
{
    int arr[5] = { 10, 20, 30, 40, 50 };
    arr[0] = 100; // change the value of the first array element
}

由于索引从 0 而不是 1 开始,最后一个数组元素的索引为 4:

int main()
{
    int arr[5] = { 10, 20, 30, 40, 50 };
    arr[4] = 500; // change the value of the last array element
}

所以,在声明一个数组的时候,我们写下想要声明多少个元素,但是在访问数组元素的时候,我们需要记住索引是从 0 开始,以元素数–1结束。也就是说,在现代 C++ 中,我们应该更喜欢std::arraystd::vector容器而不是原始数组。在后面的章节中会有更多的介绍。

十一、指针

对象驻留在内存中。到目前为止,我们已经学会了如何通过变量来访问和操作对象。另一种访问内存中对象的方法是通过指针。内存中的每个对象都有自己的类型和地址。这允许我们通过指针访问对象。因此,指针是可以保存特定对象地址的类型。仅出于说明的目的,我们将声明一个未使用的指针,它可以指向一个int对象:

int main()
{
    int* p;
}

我们说p属于类型int*

为了声明一个指向char(对象)的指针,我们声明一个类型为char *的指针:

int main()
{
    char* p;
}

在我们的第一个例子中,我们声明了一个类型为int*.的指针,让它指向内存中一个现有的int对象,我们使用了地址操作符&。我们说p 指向 x

int main()
{
    int x = 123;
    int* p = &x;
}

在我们的第二个例子中,我们声明了一个类型为char*的指针,类似地,我们有:

int main()
{
    char c = 'a';
    char* p = &c;
}

要初始化一个不指向任何对象的指针,我们可以使用nullptr文字:

int main()
{
    char* p = nullptr;
}

据说 p 现在是一个空指针

指针是变量/对象,就像任何其他类型的对象一样。它们的值是对象的地址,即存储对象的内存位置。要访问指针指向的对象中存储的值,我们需要解引用指针。通过在指针(变量)名称前添加一个解引用操作符*:来完成解引用

int main()
{
    char c = 'a';
    char* p = &c;
    char d = *p;
}

要打印出解引用指针的值,我们可以使用:

#include <iostream>

int main()
{
    char c = 'a';
    char* p = &c;
    std::cout << "The value of the dereferenced pointer is: " << *p;
}

现在,解引用指针*p的值就是'a'

类似地,对于一个整数指针,我们有:

#include <iostream>

int main()
{
    int x = 123;
    int* p = &x;
    std::cout << "The value of the dereferenced pointer is: " << *p;
}

在这种情况下,解引用指针的值是123

我们可以通过一个解引用的指针来改变被指向对象的值:

#include <iostream>

int main()
{
    int x = 123;
    int* p = &x;
    *p = 456; // change the value of pointed-to object
    std::cout << "The value of x is: " << x;
}

我们将讨论指针,尤其是在讨论动态内存分配和对象生存期等概念时,我们将讨论智能指针。

十二、引用

另一个(有点)类似的概念是引用类型。引用类型是内存中现有对象的别名。必须初始化引用。我们将引用类型描述为type_name后面跟一个&符号&。示例:

int main()
{
    int x = 123;
    int& y = x;
}

现在我们有了两个不同的名字来指代内存中的同一个int对象。如果我们给它们中的任何一个赋予不同的值,它们都会改变,因为我们在内存中有一个对象,但是我们使用了两个不同的名称:

int main()
{
    int x = 123;
    int& y = x;
    x = 456;
    // both x and y now hold the value of 456
    y = 789;
    // both x and y now hold the value of 789
}

另一个概念是const-引用,它是某个对象的只读别名。示例:

int main()
{
    int x = 123;
    const int& y = x; // const reference
    x = 456;
    // both x and y now hold the value of 456
}

我们将在学习函数和函数参数时更详细地讨论引用和const -reference。现在,让我们假设它们是一个别名,一个现有对象的不同名称。

重要的是不要混淆在指针类型声明(如int* p;)中使用*和在解引用指针(如*p = 456.)时使用*。虽然是相同的星形字符,但在两种不同的上下文中使用。

重要的是不要混淆引用类型声明中的&符号&的用法,比如int& y = x;和作为地址操作符int* p = &x.s的&符号的用法。同一个文字符号用于两种不同的东西。

十三、字符串简介

前面,我们提到了通过以下方式将一个字符串文字如"Hello World ."打印到标准输出:

std::cout << "Hello World.";

我们可以将这些文字存储在std::string类型中。C++ 标准库提供了一个名为string或者更确切地说是std::string的复合类型,因为它是std名称空间的一部分。我们用它来存储和操作字符串。

13.1 定义字符串

要使用std::string类型,我们需要在程序中包含<string>头:

#include <string>

int main()
{
    std::string s = "Hello World.";
}

要在标准输出中打印出这个字符串,我们使用:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello World.";
    std::cout << s;
}

13.2 连接字符串

我们可以使用复合运算符+=,将字符串文字添加到字符串中:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello ";
    s += "World.";
    std::cout << s;
}

我们可以使用+=运算符向字符串中添加一个字符:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello";
    char c = '!';
    s += c;
    std::cout << s;
}

我们可以使用+运算符将另一个字符串添加到我们的字符串中。我们说我们连接字符串:

#include <iostream>
#include <string>

int main()
{
    std::string s1 = "Hello ";
    std::string s2 = "World.";
    std::string s3 = s1 + s2;
    std::cout << s3;
}

类型string就是所谓的类——模板。它是使用模板实现的,我们将在后面讨论。现在,我们将只提到这个字符串类提供了一些处理字符串的功能(成员函数)。

13.3 访问字符

字符串中的单个字符可以通过下标操作符[]或成员函数来访问。 指数】。索引从0开始。示例:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello World.";
    char c1 = s[0];        // 'H'
    char c2 = s.at(0);     // 'H';

    char c3 = s[6];        // 'W'
    char c4 = s.at(6);     // 'W';

    std::cout << "First character: " << c1 << ", sixth character: " << c3;
}

13.4 比较字符串

使用等号==操作符,可以将一个字符串与字符串文字和其他字符串进行比较。比较字符串和字符串文字:

#include <iostream>
#include <string>

int main()
{
    std::string s1 = "Hello";
    if (s1 == "Hello")
    {
        std::cout << "The string is equal to \"Hello\"";
    }
}

使用相等运算符==将一个字符串与另一个字符串进行比较:

#include <iostream>
#include <string>

int main()
{
    std::string s1 = "Hello";
    std::string s2 = "World.";
    if (s1 == s2)
    {
        std::cout << "The strings are equal.";
    }
    else
    {
        std::cout << "The strings are not equal.";
    }
}

13.5 字符串输入

接受来自标准输入的字符串的首选方式是通过将std::cin和我们的字符串作为参数的std::getline函数:

#include <iostream>
#include <string>

int main()
{
    std::string s;
    std::cout << "Please enter a string: ";
    std::getline(std::cin, s);
    std::cout << "You entered: " << s;
}

我们使用std::getline是因为我们的字符串可以包含空格。如果我们单独使用std::cin函数,它将只接受字符串的一部分。

std::getline函数有如下签名:std::getline(read_from, into);函数将标准输入(std::cin)中的一行文本读入一个字符串(s)变量。

一个经验法则:如果我们需要使用std::string类型,就明确地包含<string>头。

13.6 指向字符串的指针

字符串有一个成员函数。c_str(),返回指向第一个元素的指针。据说它返回一个指向空字符数组的指针,我们的字符串是由这个数组组成的:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello World.";
    std::cout << s.c_str();
}

这个成员函数的类型是const char*,当我们想把我们的std::string变量传递给一个接受const char*参数的函数时,这个函数很有用。

13.7 子字符串

为了从一个字符串创建一个子串,我们使用了*。substr()* 成员函数。该函数返回一个从主字符串中的某个位置开始并具有一定长度的子字符串。函数的签名是:。子串(起始位置,长度)。示例:

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello World.";
    std::string mysubstring = s.substr(6, 5);
    std::cout << "The substring value is: " << mysubstring;
}

在本例中,我们有保存“Hello World”值的主字符串然后我们创建一个只有“World”值的子串。子串从主串的第六个字符开始,长度为五个字符。

13.8 查找子字符串

为了在字符串中找到子串,我们使用了*。find()* 成员函数。它在字符串中搜索子字符串。如果找到子字符串,函数将返回第一个找到的子字符串的位置。这个位置是主字符串中子字符串开始的字符位置。如果没有找到子串,该函数将返回一个值 std::string::npos 。函数本身的类型是 std::string::size_type

为了在“这是一个 Hello World 字符串”字符串中找到子字符串“Hello ”,我们编写:

#include <iostream>
#include <string>

int main()
{
    std::string s = "This is a Hello World string.";
    std::string stringtofind = "Hello";
    std::string::size_type found = s.find(stringtofind);
    if (found != std::string::npos)
    {
        std::cout << "Substring found at position: " << found;
    }
    else
    {
        std::cout << "The substring is not found.";
    }
}

这里我们有一个主字符串和一个要查找的子字符串。我们将子字符串提供给。find()函数作为参数。我们将函数的返回值存储到变量 found 中。然后我们检查这个变量的值。如果值不等于 std::string::npos ,则找到子串。我们打印消息和主字符串中某个字符的位置,也就是找到子字符串的位置

十四、自动类型推导

我们可以使用auto描述符自动推断对象的类型。自动描述符根据对象的初始值设定项类型推断对象的类型。

示例:

auto c = 'a';    // char type

这个例子推断出c的类型为char,因为初始化器'a'的类型为char

同样,我们可以有:

auto x = 123;    // int type

这里,编译器将x推断为int类型,因为整数文字123int类型。

该类型也可以基于表达式的类型来推导:

auto d = 123.456 / 789.10;    // double

这个例子将d推断为double类型,因为整个123.456 / 789.10表达式的类型是double

我们可以使用auto作为引用类型的一部分:

int main()
{
    int x = 123;
    auto& y = x; // y is of int& type
}

或者作为常量类型的一部分:

int main()
{
    const auto x = 123; // x is of const int type
}

当类型(名称)很难手动推导或者由于长度原因键入起来很麻烦时,我们使用 auto 描述符。

十五、练习

15.1 数组定义

编写一个程序,定义并初始化一个由五个双精度值组成的数组。更改并打印第一个和最后一个数组元素的值。

#include <iostream>

int main()
{
    double arr[5] = { 1.23, 2.45, 8.52, 6.3, 10.15 };
    arr[0] = 2.56;
    arr[4] = 3.14;
    std::cout << "The first array element is: " << arr[0] << '\n';
    std::cout << "The last array element is: " << arr[4] << '\n';
}

15.2 指向对象的指针

编写一个定义 double 类型对象的程序。定义一个指向该对象的指针。通过取消对指针的引用来打印所指向对象的值。

#include <iostream>

int main()
{
    double d = 3.14;
    double* p = &d;
    std::cout << "The value of the pointed-to object is: " << *p;
}

15.3 参考类型

编写一个程序,定义一个名为mydouble的 double 类型的对象。定义一个名为myreference的引用类型的对象,并用mydouble初始化它。改变myreference的值。使用引用和原始变量打印对象值。改变mydouble的值。打印两个对象的值。

#include <iostream>

int main()
{
    double mydouble = 3.14;
    double& myreference = mydouble;

    myreference = 6.28;
    std::cout << "The values are: " << mydouble << " and " << myreference << '\n';

    mydouble = 9.45;
    std::cout << "The values are: " << mydouble << " and " << myreference << '\n';
}

15.4 弦

写一个定义两个字符串的程序。将它们连接在一起,并将结果赋给第三个字符串。打印出结果字符串的值。

#include <iostream>
#include <string>

int main()
{
    std::string s1 = "Hello";
    std::string s2 = " World!";
    std::string s3 = s1 + s2;
    std::cout << "The resulting string is: " << s3;
}

标准输入的 15.5 个字符串

编写一个程序,使用std::getline函数从标准输入中接受名字和姓氏。将输入存储在一个名为fullname的字符串中。打印出字符串。

#include <iostream>
#include <string>

int main()
{
    std::string fullname;
    std::cout << "Please enter the first and the last name: ";
    std::getline(std::cin, fullname);
    std::cout << "Your name is: " << fullname;
}

15.6 创建子字符串

编写一个程序,从主字符串创建两个子字符串。主字符串由名和姓组成,等于“John Doe”第一个子字符串是名字。第二个子串是姓氏。之后打印主字符串和两个子字符串。

#include <iostream>
#include <iostream>

int main()
{
    std::string fullname = "John Doe";
    std::string firstname = fullname.substr(0, 4);
    std::string lastname = fullname.substr(5, 3);
    std::cout << "The full name is: " << fullname << '\n';
    std::cout << "The first name is: " << firstname << '\n';
    std::cout << "The last name is: " << lastname << '\n';
}

15.7 查找单个字符

编写一个程序,用值“Hello C++ World”定义主字符串。并检查是否在主字符串中找到单个字符“C”。

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello C++ World.";
    char c = 'C';
    auto characterfound = s.find(c);
    if (characterfound != std::string::npos)
    {
        std::cout << "Character found at position: " << characterfound << '\n';
    }
    else
    {
        std::cout << "Character was not found." << '\n';
    }
}

15.8 查找子字符串

编写一个程序,用值“Hello C++ World”定义主字符串。并检查在主字符串中是否找到子字符串“C++”。

#include <iostream>
#include <string>

int main()
{
    std::string s = "Hello C++ World.";
    std::string mysubstring = "C++";
    auto mysubstringfound = s.find(mysubstring);
    if (mysubstringfound != std::string::npos)
    {
        std::cout << "Substring found at position: " << mysubstringfound << '\n';
    }
    else
    {
        std::cout << "Substring was not found." << '\n';
    }
}

“C”字符和“C++”子字符串在主字符串中的相同位置开始。这就是为什么两个示例都产生值 6。

我们没有为我们的 characterfoundmysubstringfound 变量键入冗长的 std::string::size_type 类型,而是使用 auto 描述符为我们自动推导出类型。

15.9 自动类型扣除

编写一个程序,根据使用的初始化器自动推导出charintdouble对象的类型。之后打印出这些值。

#include <iostream>

int main()
{
    auto c = 'a';
    auto x = 123;
    auto d = 3.14;

    std::cout << "The type of c is deduced as char, the value is: " << c << '\n';
    std::cout << "The type of x is deduced as int, the value is: " << x << '\n';
    std::cout << "The type of d is deduced as double, the value is: " << d << '\n';
}

十六、语句

之前,我们将语句描述为命令,即按某种顺序执行的代码片段。以分号结尾的表达式是语句。C++ 语言自带一些内置语句。我们将从选择语句开始。

16.1 选择声明

选择语句允许我们检查使用条件,并基于该条件执行适当的语句。

if 语句

当我们想基于某种条件执行一条或多条语句时,我们使用if-语句。if-声明的格式为:

if (condition) statement

仅当条件为true时,statement才会执行。示例:

#include <iostream>

int main()
{
    bool b = true;
    if (b) std::cout << "The condition is true.";
}

如果条件是true,为了执行多个语句,我们使用块范围{}:

#include <iostream>

int main()
{
    bool b = true;
    if (b)
    {
        std::cout << "This is a first statement.";
        std::cout << "\nThis is a second statement.";
    }
}

另一种形式是if-else语句:

if (condition) statement else statement

如果条件为true,则执行第一条语句,否则执行else关键字后的第二条语句。示例:

#include <iostream>

int m.ain()
{
    bool b = false;
    if (b) std::cout << "The condition is true.";
    else std::cout << "The condition is false.";
}

为了在ifelse分支中执行多条语句,我们使用括号括起来的块{}:

#include <iostream>

int main()
{
    bool b = false;
    if (b)
    {
        std::cout << "The condition is true.";
        std::cout << "\nThis is the second statement.";
    }
    else
    {
        std::cout << "The condition is false.";
        std::cout << "\nThis is the second statement.";
    }
}

条件表达式

简单的 if 语句也可以写成条件表达式。下面是一个简单的 if 语句:

#include <iostream>

int main()
{
    bool mycondition = true;
    int x = 0;

    if (mycondition)
    {
        x = 1;
    }
    else
    {
        x = 0;
    }
    std::cout << "The value of x is: " << x << '\n';
}

为了使用一个条件表达式重写前面的例子,我们写:

#include <iostream>

int main()
{
    bool mycondition = true;
    int x = 0;
    x = (mycondition) ? 1 : 0;
    std::cout << "The value of x is: " << x << '\n';
}

条件表达式的语法如下:

(condition) ? expression_1 : expression_2

条件表达式使用一元*?运算符,检查条件的值。如果条件为真,则返回表达式 _1* 。如果条件为假,则返回 expression_2 。它可以被认为是一种用一行程序代替简单的 if-else 语句的方法。

逻辑运算符

逻辑运算符对其操作数执行逻辑与、【or】、运算。第一个是&&运算符,这是一个逻辑 AND 运算符。如果两个操作数都是true,则带有两个操作数的逻辑与条件的结果是true。示例:

#include <iostream>

int main()
{
    bool a = true;
    bool b = true;
    if (a && b)
    {
        std::cout << "The entire condition is true.";
    }
    else
    {
        std::cout << "The entire condition is false.";
    }
}

下一个操作符是||,,它是一个逻辑 or 操作符。逻辑 OR 表达式的结果总是true,除非两个操作数都是false。示例:

#include <iostream>

int main()
{
    bool a = false;
    bool b = false;
    if (a || b)
    {
        std::cout << "The entire condition is true.";
    }
    else
    {
        std::cout << "The entire condition is false.";
    }
}

下一个逻辑运算符是由!表示的否定运算符。它对其唯一右侧操作数的值求反。它将true的值转换为false,反之亦然。示例:

#include <iostream>

int main()
{
    bool a = true;
    if (!a)
    {
        std::cout << "The condition is true.";
    }
    else
    {
        std::cout << "The condition is false.";
    }
}

16.1.3.1 比较运算符

比较运算符允许我们比较操作数的值。比较运算符小于,大于等于> =,等于==,不等于!=.

我们可以使用相等运算符==来检查操作数的值是否相等:

#include <iostream>

int main()
{
    int x = 5;
    if (x == 5)
    {
        std::cout << "The value of x is equal to 5.";
    }
}

其他比较运算符的用例:

#include <iostream>

int main()
{
    int x = 10;
    if (x > 5)
    {
        std::cout << "The value of x is greater than 5.";
    }
    if (x >= 10)
    {
        std::cout << "\nThe value of x is greater than or equal to 10.";
    }
    if (x != 20)
    {
        std::cout << "\nThe value of x is not equal to 20.";
    }
    if (x == 20)
    {
        std::cout << "\nThe value of x is equal to 20.";
    }
}

现在,我们可以在相同的条件下使用逻辑运算符和比较运算符:

#include <iostream>

int main()
{
    int x = 10;
    if (x > 5 && x < 15)
    {
        std::cout << "The value of x is greater than 5 and less than 15.";
    }
    bool b = true;
    if (x >5 && b)
    {
        std::cout << "\nThe value of x is greater than 5 and b is true.";
    }
}

任何可隐式转换为truefalse的文字、对象或表达式都可以用作条件:

#include <iostream>

int main()
{
    if (1) // literal 1 is convertible to true
    {
        std::cout << "The condition is true.";
    }
}

如果我们使用一个整数变量,其值不是0,那么结果将是true:

#include <iostream>

int main()
{
    int x = 10; // if x was 0, the condition would be false
    if (x)
    {
        std::cout << "The condition is true.";
    }
    else
    {
        std::cout << "The condition is false.";
    }
}

if-语句分支内使用代码块{}是一个很好的实践,即使只有一条语句要执行。

开关声明

switch 语句类似于拥有多个 if 语句。它检查条件的值(必须是整数或枚举值),并根据该值执行一组给定事例标签中的一个标签内的代码。如果没有一个 case 语句符合条件,则执行默认标签中的代码。常规语法:

switch (condition)
{
case value1:
    statement(s);
    break;
case value2etc:
    statement(s);
    break;
default:
    statement(s);
    break;
}

一个简单的示例,它检查整数 x 的值并执行适当的 case 标签:

#include <iostream>

int main()
{
    int x = 3;
    switch (x)
    {
    case 1:
        std::cout << "The value of x is 1.";
        break;
    case 2:
        std::cout << "The value of x is 2.";
        break;
    case 3:
        std::cout << "The value of x is 3."; // this statement will be // executed
        break;
    default:
        std::cout << "The value is none of the above.";
        break;
    }
}

break语句退出 switch 语句。如果没有 break 语句,代码将跳转到下一个 case 语句,并在那里执行代码,而不考虑 x 值。我们需要在所有的*情况:默认:*开关中放置断点。

16.2 迭代语句

如果我们需要一些代码执行多次,我们使用迭代语句。迭代语句是在循环中执行某些代码的语句。循环中的代码执行 0 次、1 次或多次,具体取决于语句和条件。

16.2.1 声明

for-语句循环执行代码。执行取决于条件。for-语句的一般语法是:for (init_statement; condition; iteration_expression) { // execute some code }.一个简单的例子:

#include <iostream>

int ma.in()
{
    for (int i = 0; i < 10; i++)
    {
        std::cout << "The counter is: " << i << '\n';
    }
}

这个例子执行了十次for循环中的代码。init_statementint i = 0;,我们将计数器初始化为0condition是:i < 10; and the iteration_expression is i++;

简单解释:

将计数器初始化为0,检查计数器是否小于 10,执行代码块中的std::cout << "The counter is: " << i << '\n';语句,并将计数器i加 1。因此,只要i < 10条件为true,代码块中的代码就会继续执行。一旦计数器变为10,条件不再是true,,并且for循环终止。

如果我们希望某个东西执行 20 次,我们将设置一个不同的条件:

#include <iostream>

int main()
{
    for (int i = 0; i < 20; i++)
    {
        std::cout << "The counter is: " << i << '\n';
    }
}

while 语句

while-语句执行代码,直到条件变为falsewhile循环的语法是:

while (condition) { // execute some code }

只要条件是truewhile-循环就会继续执行代码。当condition变成false,时,while循环终止。示例:

#include <iostream>

int main()
{
    int x = 0;
    while (x < 10)
    {
        std::cout << "The value of x is: " << x << '\n';
        x++;
    }
}

本例中的代码执行十次。每次迭代后,对条件x < 10进行求值,只要等于true,代码块中的代码就会一直执行。一旦条件变为false,则while循环终止。在这个例子中,我们在每次迭代中增加x的值。而一旦变成10,循环终止。

做陈述

do-语句类似于while-语句,但是条件在主体之后。do语句中的代码保证至少执行一次。语法是:

do { // execute some code } while (condition);

如果我们使用前面的例子,代码将是:

#include <iostream>

int main()
{
    int x = 0;
    do
    {
        std::cout << "The value of x is: " << x << '\n';
        x++;
    } while (x < 10);
}

很少使用并且最好避免使用do-语句。

请注意,还有一个名为的迭代语句,即语句的范围。我们稍后讨论容器时会谈到它。