2.1 正式学习 C++
最早的计算机所需执行的任务便是由人们输入一些指令,并将运算所得的再输出出来,这里我们也将介绍一个最简单的可以执行输入并输出的 C++ 程序。程序代码2-1实现了用户输入什么内容,程序边输出什么内容。
程序代码2-1先使用 std::cin
输入,再使用 std::cout
输出。代码中包含了一些提供程序员或任何读到这段源码的读者所阅读的文字内容,这称作注释,这些注释都以 //
开头,在实际代码编译过程中,编译器会忽略它们。
#include <iostream> // 引入 iostream 头文件
using namespace std; // 使用标准命名空间 std
int main() // 声明 main 函数
{ // 函数体开始
string strA; // 声明 strA 变量
cin >> strA; // 从标准输入流读取后赋值给 strA 变量
cout << strA; // 向标准输出流写入 strA 变量的值
return 0; // 通常用于表示程序成功运行并正常结束。
} // 函数体结束
C++ 对大小写敏感,也就是说在开发过程中,需要严格区分大小写字符,比如将 cin
替换为 CIN
或 Cin
程序将无法通过编译(编译器提示:使用了未知的标识符)。同样编译器也是对拼写敏感的,因此如果你将 cin
替换为 in
或 cing
程序也将无法通过编译。这也意味着,你的程序中拼写与大小写必须与示例中相同。
C++ 中,通常将代码分为头文件、源文件和可能的其他文件(如实现文件)。头文件通常用 .h
或 .hpp
后缀;源文件通常用 .cpp
后缀;实现文件通常用 .cxx
或 .cc
后缀。这是一种我们建议的组织代码的方式。
如果你使用过 C 语言,在看到 cout 函数(而不是 printf()
函数)时可能会略显惊讶。事实上,C++ 完全可以使用原有 C 语言的所有功能,例如 scanf()
、 printf()
,你只需要包含常规 C 语言的 stdio.h
头文件即可。但本书讲解的是 C++ 语言,所以将使用 C++ 的特性和功能,他们在 C 版本的基础上进行了很多改进。从某些方面来讲,这将极大的提高你的开发效率。
使用函数来创建 C++ 程序。通常先将程序组织为主要任务,然后设计独立的函数来处理这些任务(通常在程序设计的部分我们要大体遵循一个原则:“高内聚,低耦合”)。
提示
高内聚 (High Cohesion): 意味着一个模块(类、函数等)应该关注于单一责任,执行一个明确定义的任务。高内聚的模块内部各部分应该彼此关联,共同服务于同一目标。高内聚的设计使得模块更容易理解、修改和维护。例如,一个处理用户输入的类应该专注于处理用户输入,而不涉及过多与其他模块无关的任务。如果一个模块承担了过多的任务,它的内聚性就较低。
低耦合 (Low Coupling): 表示模块之间的相互依赖应该尽量减少。模块之间的关系应该是松散的,一个模块的修改不应该对其他模块造成过多的影响。低耦合的设计使得系统更容易维护、测试和重构。例如,如果一个模块修改了其内部实现,不应该导致其他模块的大规模修改。模块之间的接口应该清晰明了,避免不必要的依赖。
综合来说,高内聚低耦合的设计原则旨在创建结构清晰、易于理解和可维护的代码,从而提高软件系统的质量和可扩展性。
程序代码2-1中的示例非常简单,只包含一个名为 main()
的函数。程序源代码参见 GitHub 代码仓库 2-1.cpp。2-1.cpp 中包含下述元素。
- 预处理指令
#include
; - 编译指令
using namespace
; - 函数头
int main()
; - 函数体 {和} 包含;
- 使用 C++ 的
cin
、cout
工具输入输出; - 代表程序结束并结束
main()
函数的return
语句; - 注释语句,由前缀
//
进行标识。
下面将详细介绍这些语句。我们将优先介绍 main()
函数,当你了解 main()
的作用后,其他将更容易理解。
2.1.1 预处理指令和 iostream 文件
C++ 和 C 一样,也使用一种预处理器,该程序在进行主编译之前对源文件进行处理。不必执行任何特殊的操作来调用该预处理器,它会在编译程序时自动运行。这些指令以 #
符号开始,并在每条指令的后面没有分号。我们在程序代码2-1中就使用了一次预处理指令 #include <iostream>
,用于包含头文件,将 iostream 文件的内容插入到程序代码2-1中,原始文件没有被修改,而是将源代码文件和 iostream 组合成了复合文件。
这提出一个问题:为什么要将 iostream 文件的内容添加到程序中呢?
C++ 中,iostream 中的前两个字母 io 指的是输入和输出(即I/O,Input/Output)。C++ 的输入/输出方案涉及到 iostream 文件中的多个定义。为了使用 cin 输入、cout 来输出消息,程序代码2-1需要这些定义才可以顺利完成任务。#include 编译指令相当于一个拷贝指令,可以将 isotream 与引用它的源代码一起被编译器编译。更通俗的来讲,iostream 文件的内容将取代程序中的代码行 #include <iostream>
。
注意:使用 cin
、cout
进行输入和输出的程序必须包含 iostream 文件。
2.1.2 头文件
像 iostream 这样的文件叫做包含文件,因为经常出现在源代码的头部也常被称为头文件。C++ 编译器自带了很多头文件,每个头文件都支持一组特定的工具。C 语言的传统是,头文件使用扩展名 .h ,将其作为一种通过名称标识文件类型的简单方式。例如,math.h 头文件支持各种 C 语言数学函数。最初,C++ 也是这样做的。例如,支持输入和输出的头文件叫做 iostream.h 。自 C++ 98 标准发布以后,C++ 的用法发生了变化。现在,对老式 C 的头文件保留了扩展名 .h ,C++ 仍可以使用这种文件,而标准 C++ 头文件则没有扩展名。有些 C 头文件被转换为 C++ 头文件,这些文件被重新命名,去掉了扩展名 .h ,并在文件名称前面加上前缀 c(表明来自 C 语言)。例如,C 版本的 math.h 改为 C++ 版本的 cmath 。有时 C 头文件的 C 版本和 C++ 版本相同,而有时候新版本做了一些修改。对于纯粹的 C++ 头文件(如 iostream )来说,去掉 h 不只是形式上的变化,没有 .h 的头文件也可以包含名称空间--2.1.3将向你说明。
2.1.3 名称空间
如果使用 iostream ,则应使用 using namespace std;
。
这叫 using 编译指令,你大可不必现在就明白为什么要用到它,你只需要明白,我们用了 iostream 中的标准输入 cin
和标准输出 cout
。如果你偏执的不使用它,也可以用 std::cin
和 std::cout
去代替它,但可能你也发现了,如果一个程序有几十次甚至几百次输入输出操作,你便要将 std:: 重复输入几十次甚至几百次。
你可能很疑惑,为了避免你一头雾水,我将在这里向你简单介绍。名称空间是 C++ 中一项较新的特性,它是为了使多个作者、厂商的源代码能够进行更简单的组合且不出现冲突和错误而设计的。例如:helloCpp 公司设计了一个叫做 test()
的函数,而 Microsoft(微软) 公司也早已有了一个叫做 test()
的函数,当其他用户在使用 test()
函数进行开发时,编译器将不知道你使用的是那一个版本。而名称空间可以让厂商对自己所开发的函数进行封装操作,例如:helloCpp 公司可以将其封装在 helloCpp 的名称空间中,Microsoft(微软) 公司可以将其封装在 Microsoft 的名称空间中。这样,test()
函数便成了两个独立的版本,编译器就可以使用名称空间来区分它们了。
helloCpp::test("A warning appears!");
Microsoft::test("Correct execution.");
依据这个原理,cin
和 cout
被封装在标准模板库1中,它们都被放置在名称空间 std 中。如之前所说,在初期学习过程中我们需要大量使用 std 中的标准组件,我们使用 using 编译指令可以大量减少重复使用 std:: 前缀的工作。这个 using 编译指令使得 std 名称空间中定义的所有名称都可用,这是一种偷懒手段。为了在更大的程序中不出现难以察觉的错误,建议的方法是只使用所需的名称即可,这也可以通过 using 声明实现:
using std::cin;
using std::cout;
2.1.4 main()
函数
将 main()
单独提出后,程序代码2-1的示例基本结构如下:
int main()
{
//功能语句
return 0;
}
请注意!
在 C++ 中,程序的执行通常从 main()
函数开始。main()
函数是程序的入口点,操作系统在运行程序时会从 main()
函数开始执行。因此,你需要记住,你的程序应该有且只有一个 main()
函数。如果你尝试不使用 main()
函数,编译器可能会报错或者警告,因为这种操作违反了 C++ 的执行模型。
这些语句表明有一个名为 main()
的函数,函数头为 int main()
,函数体为花括号({和})中的内容。函数头和函数体的所有内容共同描述了该函数的功能。函数头是声明类型并且大概表明实现了什么功能,在后期我们学习到函数的参数后,它便有了与程序其他部分之间进行数据交换和接口的作用。函数体主要是指出该函数应该实现什么指令和功能。在 C++ 中,每条完整的指令都称为语句,所有的语句都是以分号结束,若忘记输入分号,编译器会认为该语句没有结束。所以,请在代码编写完毕后认真检查代码的完整性。
提示
在 C++ 中,所有的函数都是由名称+圆括号组成的,例如:sin()
、cos()
、tan()
等。你也可以自己定义并命名一个函数,如:test()
、abc()
、LearnCpp()
等。
main()
函数中最后一条语句叫做返回语句,它返回一个 0 ,表明程序正常结束并退出程序。在其他非 main()
的函数中,return
表明返回这个函数运算后的内容(结果)。后续还会详细介绍。
2.1.5 使用 cout 进行 C++ 输出
在程序代码2-1中,我们使用了 cout << strA;
进行输出操作,但如果我们想要打印 hello,world 这个语句应该怎么办呢?很经简单,我们只需要使用之前了解过的 cout 语句即可:
#include <iostream>
using namespace std;
int main()
{
cout << "hello,world";
return 0;
}
在这个示例中 cout << "hello,world";
双引号括起的部分是要打印输出的消息。在 C++ 中,用双引号括起的一系列字符叫做字符串,因为它们是由若干字符组合而成的一串内容,程序员形象的称为字符串。<<
符号表示该语句将把这个字符串发送给 cout ,该符号指出了信息流动的方向。而 cout 则是一个预定义的对象,知道如何显示字符串、数字和单个字符等(第1章介绍过,对象是类的特定实例,而类定义了数据的存储和使用方式)。
因为后面才会介绍面向对象,所以很明显现在就使用面向对象可能有很大的困难。实际上,这演示了对象的其中一个优点——不用了解对象的内部情况,就可以使用它。只需要知道它的接口,即可开箱即用。事实上在程序代码2-1中我便使用了 cout 的一个简单接口,输出变量 cout << strA;
,strA 本质上是一个字符串变量(我们会在本节2.2.2中讲解),通过这个变量,我们就实现了一个功能——输入什么,得到(输出)什么。大部分书可能会在第一个示例代码中只是输出一个特定字符串,例如 cout << "hello,world";
但这对于大多数人是无趣的,好比当你学会驾驶电瓶车,自然不会畏惧自行车一样,当你学会输出变量,自然会知道如何输出字符串。
对于显示字符串而言,只需知道这些即可。不过,现在我们要看一看 C++ 从概念上如何解释这个过程。从概念上看,输出是一个流,即从程序流出的一系列字符。cout 对象表示这种流,其属性是在 iostream 文件中定义的。cout 的对象属性包括一个插入操作符(<<),它可以将其右侧的信息插入到流中。因此 cout << "hello,world";
将把 “hello,world” 字符串插入到输出流中。
初识操作符重载
如果熟悉C后才开始学习 C++,则可能注意到了,插入操作符(<<)看上去就像按位左移操作符(<<)。这是一个操作符重载的例子,通过重载,同一个操作待将有不同的含义。编译器通过上下文来确定操作符的含义 C 本身也有一些操作符重载的情况,例如,& 符号既表示地址操作符,又表示按位 AND 操作符;* 符号既表示乘法,又表示对指针解除引用。这里重要的不是这些操作符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义 C++ 扩展了操作符重栽的概念,允许为用户定义的类型(类)重新定义操作符的含义。
-
控制符
endl
endl 是一个特殊的 C++ 符号,重起一行。在输出流中插入 endl 将导致屏幕光标移到下一行的开头。诸如 endl 等,对于 cout 来说有特殊函数的特殊符号被称为控制符。和 cout 一样,endl 也是在头文件 iostream 中定义的,且位于名称空间 std 中。打印字符串时,cout 不会自动移到下一行,如需从下一行继续输出,可以用 endl 控制符。
cout << "Hello,"; cout << "world!";
该代码输出后得到如下结果:
Hello,world!cout << "Hello," << endl << "world!";
而这行代码输出后却得到如下结果:
Hello,
world!注意: 要尝试上述输出范例,必须将代码放到完整的程序中,该程序应该包含一个
main()
函数以及起始和结束花括号。 -
换行符
\n
1C++ 还提供了另一种在输出中指示换行的旧方法:C 语言符号
\n
:cout << "Hello,world!\n";
\n
被视为一个字符,名为换行符。 显示字符串时,在字符串中包含换行符,而不是在末尾加上 endl ,可减少输入量。但如果要生成一个空行,则两种方法输入量相同,但对大多数人而言,输入 endl 更为方便:cout << "\n"; cout << endl;
2.1.6 注释
C++ 注释以双斜杠 //
打头。注释是程序员为读者提供的说明,通常标识程序的一部分或解释代码的某个方面。编译器忽略注释,你可以将它想象为只有人类可读的语言,对编译器并不起效,在任何情况下编译器都不能理解注释。
C++ 也能识别 C 的注释,C 注释包括在 /*
和 */
之间。你可以在程序中使用 C 或 C++ 风格的注释,也可以同时使用这两种注释。
#include <iostream> /* A C-style comment. */
提示
如果你只需要在一行中使用注释,那么推荐你使用 //
,如果你需要在多行中使用,你可以使用 /**/
,区间内的所有文本包括换行将都被识别成注释。
/*
2-1.cpp
Hello-Cpp (www.hi-cpp.com)
实现从标准输入读取字符串并输出
Copyright (c) 2024 Hello-Cpp
*/
#include <iostream> // 引入 iostream 头文件
using namespace std; // 使用标准命名空间 std
int main() // 声明 main 函数
{ // 函数体开始
string strA; // 声明 strA 变量
cin >> strA; // 从标准输入流读取后赋值给 strA 变量
cout << strA; // 向标准输出流写入 strA 变量的值
return 0; // 通常用于表示程序成功运行并正常结束。
} // 函数体结束
特别建议:程序中应常用注释来说明程序,程序越复杂,注释的价值体现越大。注释不仅有助于他人理解代码,也有助于程序员自己理解代码,特别是隔了一段时间没有接触该代码的情况下。
2.1.7 C++ 源代码的格式化
有些语言是面向行的语言,即每条语句占一行。对于这些语言来说,回车的作用是将语句分开。在 C++ 中,分号标示了语句的结尾。因此,在 C++ 中,回车的作用就和空格或制表符相同。通常可以在能够使用回车的地方使用空格,反之亦然。这说明既可以把一条语句放在几行上,也可以把几条语句放在同一行上。例如,可以将程序代码2-1重新格式化为如下所示:
#include <iostream>
using namespace std;
int
main(){
string strA;
cin >>
strA;
cout
<<
strA
;return 0;}
这样虽然不太好看,但仍是合法的代码。如果你的同事给你这样的代码,且它实际上确实完成了一些任务,让你来维护,你可能会气到爆粗口。为了避免这种情况发生,通常我们会对代码的格式做出一些共同约定。
- 每行一条语句;
- 每个函数体都有一对花括号({}),这两个花括号通常各占一行;
- 函数体中的语句都相对于花括号进行缩进(一个缩进通常是4个空格,但不要使用空格缩进)2。
在涉及其他指导原则时,我们将特别提醒你。