IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【现代C++】第零部分 开始:初始输入输出、注释、控制流和类简介 -> 正文阅读

[C++知识库]【现代C++】第零部分 开始:初始输入输出、注释、控制流和类简介

本文属于「现代C++学习实践」系列文章之一,这一系列正式开始于2021/09/04,着重于现代C++(即C++11、14、17、20、23等新标准)和Linux C++服务端开发的学习与实践。众所周知,「C++难就难在:在C++中你找不到任何一件简单的事」。因此,本系列将至少持续到作者本人「精通C++」为止(笑)。由于文章内容随时可能发生更新变动,欢迎关注和收藏现代C++系列文章汇总目录一文以作备忘。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:[https://github.com/memcpy0/]。在这一仓库中,你可以看到本人学习C++的全过程,包括C++书籍源码、练习实现、小型项目等。

需要特别说明的是,为了透彻理解和全面掌握现代C++,本系列文章中参考了诸多博客、教程、文档、书籍等资料,限于时间精力有限,这里无法一一列出。部分重要资料的不完全参考目录如下所示,在后续学习整理中还会逐渐补充:

  • C++ Primer 中文版(第5版),Stanley B. Lippman、Barbara E. Moo等著,王刚、杨巨峰译,叶劲峰、李云、刘未鹏等审校,电子工业出版社;
  • 侯捷老师的公开课;
    • C++面向对象高级开发上、下:正确理解面向对象的精神和实现手法,涵盖对象模型、关键机制、编程风格、动态分配;
    • STL标准库与范型编程:深入剖析STL标准库之六大部件、及其之间的体系结构,并分析其源码,引导高阶泛型编程。
    • C++新标准C++11/14:在短时间内深刻理解C++2.0的诸多新特性,涵盖语言和标准库两层
    • C++内存管理机制:学习由语言基本构件到高级分配器的设计与实作,并探及最低阶malloc 的内部实现。
    • C++ Startup揭密:C++程序的生前和死后。认识Windows平台下的Startup Code(启动码),完全通透C++程序的整个运行过程。

本章中将编写一个小的C++程序,来解决简单的书店问题——书店保存所有销售记录的档案,每条记录都保存了某本书的一次销售信息:ISBN 售出册数 书的单价 ,此外老板还需要查询此档案,计算每本书的销售量、销售额、平均售价。


1.1 编写一个简单的C++程序

每个C++程序都包含许多函数,其中一个必须命名为 main,操作系统通过调用 main 来运行C++程序1。最简单的 main 函数如下,它只返回给操作系统一个值:

int main() 
{
	return 0;
}

每个函数2的定义都包括四部分,即使是特殊的 main 函数也不例外:返回类型 return type 、函数名 function name 、一个括号包围的形参列表 parameter list(允许为空)、函数体 function body ——一个以左花括号 curly brace 开始、以右花括号结束的语句块 block of statements

6.2.5节讨论 main 函数的形参列表和其他形参类型。

main 函数的返回类型必须为整数类型 int ,这是一种内置类型 built-in type ,即语言自身定义的类型。类型是程序设计的基本概念之一,不仅定义了数据元素的内容,还定义了在这类数据上可以进行的运算程序处理的数据都保存在变量中,每个变量都有自己的类型。

此处 main 函数体语句块中只有一条 return 语句,它结束函数的执行,还会向调用者返回一个值——注意,return 语句返回值的类型必须与函数返回类型相容。大多数系统中,main 函数的返回值被用来指示状态,零表示成功执行,非零返回值的含义由系统定义,用来指出错误类型。

1.1.1 编译、运行程序

编写程序后,就要编译它,如何编译程序取决于使用的操作系统编译器。在【VS Code】Windows10下VS Code配置C/C++语言环境这篇文章中,为了配置语言学习环境,我稍微介绍了一下GCC、Clang、MSCV三个编译器:

常见的C/C++编译工具有GCC(GNU Compiler Collection ,即GNU编译器套件,GCC过去代表 GNU C Compiler ,但是由于编译器支持除C之外的其他几种语言,它现在代表 GNU Compiler CollectionsGCC官网在此处)、Clang(C language family frontend for LLVM ,提供兼容GCC的编译器驱动程序 clang.exe 和兼容MSVC的编译器驱动程序 clang-cl.exeClang官网在此)、MSVC(Microsoft C++ 编译器工具集)三巨头,个人觉得Clang的架构更优雅、优化更惊艳,然而姜还是老的辣……在对C++ 20的语核支持“比赛”中,GCC首先接近了终点——虽然MSVC一个月前宣布自己冲过了终点。

编译器包含许多选项,其中有些参数能对有问题的程序结构发出警告,总是打开这些选项是一个好习惯,如在GNU编译器中使用 -Wall 选项,在MSCV中使用 /W4更多参数可以查阅编译器的参考手册

此外,还需要为编译器配备编辑器,比如我正在学习的VS CodeVim/Emacs,或者使用集成开发环境,比如Jetbrains全家桶的CLion(跨平台的C/C++ IDE,支持现代C++、libc++以及Boost):
在这里插入图片描述

程序源文件命名约定

多数编译器要求程序源码存储在一个或多个文本文件中——程序文件常被称为源文件 source file ;源文件的名字以一个后缀名(一个句点接一个或多个字符组成)结尾,后缀名告知系统这个文件是一个C++源文件;不同的编译器使用不同的后缀命名约定,包括 .cc, .cxx, .cpp, .cp.c

从命令行运行编译器、编译运行程序、查看 main 返回值

命令行如Unix/Linux的Shell、Windows的命令提示符或Powershell中,可以编译程序。假设 $ 是系统提示符,CC 是编译器程序的名字,prog1.cc 保存了我们的 main 程序:

$ CC prog1.cc

执行命令后,编译器生成一个可执行文件——Windows中将其命名为 prog1.exe ,Unix/Linux中的编译器如GCC通常将其命名为 a.out(个人尝试,我在Windows中使用GCC编译,默认生成的可执行文件是 a.exe)。

要运行可执行文件,需要提供可执行文件的文件名:

  • Windows中可以忽略其扩展名 .exe ,执行 prog1(我执行的是 a ),但是加上扩展名也无妨。
  • 某些系统中,即使文件就在当前工作目录中,也必须显式指出文件的位置,此时在 $ 提示符后键入 .\prog1 即可(Windows CMD中,键入 .\prog1 也可以执行程序,但是不可使用 ./prog1 ),. 后跟着反斜线 \ 指出该文件在当前目录中。
  • Unix系统中运行一个可执行文件,需要使用全文件名,包括文件扩展名,如 $ a.out(书上说的,没有试过)。
  • Linux系统中运行一个可执行文件,要显式指出文件位置,用一个 . 后跟着斜线 / 来指出可执行文件位于当前目录中,如 ./a.out

访问 main 函数返回值的方法依赖于系统。Unix和Windows系统中,执行完一个程序后,都可以通过 echo 命令获取其返回值:

  • Unix中,通过 $ echo $? 获取状态;
    在这里插入图片描述
  • Windows中,查看状态需要键入 $ echo %ERRORLEVEL%
    在这里插入图片描述

1.1节练习*

练习1.1:查阅你使用的编译器的文档,确定它使用的文件命名约定。编译并运行第2页的 main 程序。
答:文中已经编译运行。我使用的是GCC 8.1.0:
在这里插入图片描述点击查看GCC官方文档
在这里插入图片描述
练习1.2:改写程序,让它返回-1。返回值-1通常被当作程序错误的标识。重新编译并运行你的程序,观察系统如何处理 main 返回的错误标识。
答:在CMD中编译运行,系统没有处理返回的错误标识:
在这里插入图片描述
在DEV C++中编译运行:
在这里插入图片描述


1.2 初识输入输出

C++语言没有定义任何输入输出语句,替代的是用一个全面的?标准库 standard library 来提供IO机制(和很多其他设施)。不过,我们只需了解IO库中一部分基本概念和操作。

其中之一是 iostream 库,它包含两个基础类型 istream, ostream ,分别表示输入流和输出流——所谓的 stream ,实际就是一个从IO设备中读出或写入IO设备的字符序列,随着时间的推移,字符顺序生成或消耗。

1.2.1 标准输入输出对象

标准库定义了4个IO对象:

  • 处理输入,使用名为 cinistream 类型对象,此对象也被称为标准输入 standard input
  • 处理一般输出,使用名为 coutostream 类型对象,此对象也被称为标准输出 standard output
  • 处理警告和错误消息的输出,使用名为 cerrostream 类型对象,此对象也被称为标准错误 standard error
  • 处理程序运行时的一般性消息(即日志)的输出,使用名为 clogostream 类型对象。

系统会将程序运行的窗口与这些对象关联起来——读取 cin 时,数据从程序正在运行的窗口读入;向 cout, cerr, clog 写入数据时,将写到同一个窗口。

1.2.2 使用IO库的程序

如下是一个使用IO库的简单程序,提示用户输入两个数,然后输出它们的和。我们将具体分析这一程序:

#include <iostream>
int main() 
{
	std::cout << "Enter two numbers:" << std::endl;
	int v1 = 0, v2 = 0;
	std::cin >> v1 >> v2;
	std::cout << "The sum of " << v1 << " and " << v2
			  << " is " << v1 + v2 << std::endl;
	return 0;
}

程序的第一行 #include <iostream> 告诉编译器,程序要使用 iostream 库。#include 是一个预处理 preprocess 指令,<> 中则指出了一个头文件 header ,每个使用标准库设施的程序都必须包含相关的头文件。且有以下注意事项:

  • #include 指令和头文件名字必须写在同一行中
  • #include 指令必须出现在所有函数之外;
  • 一个程序的所有 #include 指令,一般都放在源文件的开始位置。

1.2.3 向流写入数据

main 的函数体中,第一条语句执行了一个表达式 expression ——它由一个或多个运算对象和一个或多个运算符组成。C++中,一个表达式产生一个计算结果。这条语句中的表达式使用了输出运算符 << 在标准输出上打印消息 "Enter two numbers:"

std::cout << "Enter two numbers:" << std::endl;

<< 运算符接受两个运算对象:左侧必须是一个 ostream 对象,右侧运算对象是要打印的值对象。<< 将给定的值写到给定的 ostream 对象中,计算结果就是其左侧运算对象,即写入给定值的那个 ostream 对象。

这一条语句使用了两次 << 运算符。由于 << 返回左侧运算对象,因此第一个 << 的结果成为了第二个 << 的左侧运算对象,第二个 << 的结果还是那个左侧运算对象。这条链中每个 << 运算符的左侧运算对象计算结果都是同一个对象,本例中是 std::cout(当然,对象的内部状态发生了改变),这样就可以将一系列输出请求连接起来,少写几条语句。

原表达式等价于:

(std::cout << "Enter two numbers:") << std::endl;

或者等价于以下形式。其中第一个 << 打印一条消息,消息是一个字符串字面值常量 string literal ,即用一对双引号包围的字符序列。双引号间的文本被打印到标准输出。第二个 << 打印 std::endl ,这是一个称为操纵符 manipulator 的特殊值,写入它的效果是结束当前行、并将与设备关联的缓冲区 buffer 中的内容刷到设备中。缓冲刷新操作保证,到目前为止程序产生的所有输出都真正写入输出流中、而非停留在内存中等待写入流。

std::cout << "Enter two numbers:");
std::cout << std::endl;

调试时经常要添加打印语句,这类语句应该保证一直刷新流;否则如果程序崩溃,输出可能还留在缓冲区中,导致错误推断程序崩溃的位置。

1.2.4 从流读取数据

提示输入数据后,要读入用户的输入。这里定义了两个名为 v1, v2变量 variable 来保存输入,将它们定义为内置的 int 类型以表示整数,还将它们初始化 initialize 为0。初始化一个变量,就是在变量创建的同时为它赋予一个值。

int v1 = 0, v2 = 0;

接着读入输入的数据:

std::cin >> v1 >> v2;

输入运算符 >><< 类似,接受两个运算对象:左侧运算对象必须是一个 istream 对象,右侧运算对象用来存储读入的数据。>> 从给定的 istream 对象读入数据并存入给定对象中,计算结果就是其左侧运算对象。

这一条语句使用了两次 >> 运算符。由于 >> 返回左侧运算对象,因此第一个 >> 的结果成为了第二个 >> 的左侧运算对象,第二个 >> 的结果还是那个左侧运算对象。这条链中每个 >> 运算符的左侧运算对象计算结果都是同一个对象,本例中是 std::cin(当然,对象的内部状态发生了改变),这样就可以将一系列输入请求连接起来,少写几条语句。

因此,这一表达式等价于:

(std::cin >> v1) >> v2;

或者等价于以下形式。它们的执行结果是一样的。

std::cin >> v1;
std::cin >> v2;

1.2.5 使用标准库中的名字、命名空间的作用

不可不提的是,程序使用了 std::cin, std::cout 而非 cin, cout 。前缀 std:: 指出名字 cin, cout 定义在名为 std命名空间 namespace 中。命名空间能避免不经意的名字定义冲突、使用库中相同名字导致的冲突。

标准库定义的所有名字都在命名空间 std 中,只是通过命名空间使用标准库有点不便——每次使用其中的一个名字时,必须用作用域运算符 :: 来显式指出,想使用来自命名空间 std 中的名字,即写出 std::

3.1节有一个更简单的访问标准库中名字的方法。

1.2.6 完成程序

最后就是打印计算结果。有意思的是,<< 的右侧运算对象可以不是相同类型的值(>> 也类似),如字符串字面值常量,或者 int 值。原因在于,标准库定义了不同版本的输入和输出运算符,以处理不同类型的运算对象。

std::cout << "The sum of " << v1 << " and " << v2
		  << " is " << v1 + v2 << std::endl;

1.2节练习

练习1.3:编写程序,在标准输出上打印 Hello, World

练习1.4:我们的程序使用加法运算符+来将两个数相加。编写程序使用乘法运算符*,来打印两个数的积。

练习1.5:我们将所有输出操作放在一条很长的语句中。重写程序,将每个运算对象的打印操作放在一条独立的语句中。

练习1.6:解释下面程序片段是否合法:

std::cout << "The sum of " << v1;
			   << " and " << v2;
			   << " is " << v1 + v2 << std::endl;

答:不合法。原因在于,前两个分号使得第三个及以后的 << 运算符失去了左侧运算对象,无法将右侧对象的值输出到 ostream 对象中。应该去掉多余的分号,修改为:

std::cout << "The sum of " << v1
			   << " and " << v2
			   << " is " << v1 + v2 << std::endl;

1.3 注释简介

这里简单介绍C++如何处理注释 comments ——注释用于概述算法、确定变量用途、解释代码段,帮助读者理解程序。编译器会忽略注释,注释对程序的行为或性能不会有任何影响。

错误的注释比完全没有注释更糟糕,因为它会误导读者!因此当你修改代码时,不要忘记同时更新注释!

1.3.1 C++中注释的种类

C++中有两种注释,单行注释(用于半行和单行附注)和界定符对注释(用于多行解释):

  • 前者以 // 开始、以换行符结束,当前行双斜线右侧的所有内容都会被编译器忽略。这种注释可以包含除换行符外的任何文本,哪怕是额外的双斜线
  • 后者使用继承自C的两个界定符 /**/ ,以 /* 开始、以 */ 结束,中间的所有内容都被当作注释。界定符对注释可以放置于任何允许放置制表符、空格符和换行符的地方。这种注释可以包含除 */ 外的任意内容,包括换行符,因此可以跨越程序中的多行(并不是必须的)。
    当界定符对注释跨越多行时,最好显示指出其内部的程序行都属于多行注释的一部分。具体做法是,注释内的每行都以一个星号开头。

一个同时包含两种注释的程序:

#include <iostream>
/*
 * 简单主函数:
 * 读取两个数,求它们的和
 */
int main() {
	// 提示用户输入两个数
	std::cout << "Enter two numbers:" << std::endl;
	int v1 = 0, v2 = 0;   // 保存我们读入的输入数据的变量
	std::cin >> v1 >> v2; // 读取输入数据
	std::cout << "The sum of " << v1 << " and " << v2
			  << " is " << v1 + v2 << std::endl;
	return 0;
}

注意,界定符对注释不能嵌套!因为前面说过,界定符对注释可以包含除 */ 外的任意内容,也包括一个或多个 /* ,只要不包括 */——这个 */ 会提前结束界定符对,导致后面的内容不被看做注释。

/*
 * 注释对/* */不能嵌套
 * “不能嵌套”几个字会被认为是源码,
 * 像剩余程序一样处理
 */
int main() {
    return -1;
}

在调试期间可能要注释掉一些代码,由于其中可能包含界定符对注释,就可能导致注释嵌套错误。最好的方法是使用单行注释方式,注释掉代码段的每一行:

// /*
// * 单行注释中的任何内容都会被忽略
// * 包括嵌套的注释对也一样会被忽略
// */

1.3节练习

练习1.7:编译一个包含不正确的嵌套注释的程序,观察编译器返回的错误信息。

练习1.8:指出下列哪些输出语句是合法的(如果有的话):

std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
std::cout << /* "*/" /* "/*" */;

预测编译这些语句会产生什么样的结果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。


1.4 控制流

前面的语句都是顺序执行的:语句块的第一句首先执行,然后是第二条语句……然而我们可以写出更加复杂的执行路径。

1.4.1 while语句

while语句反复执行一段代码,直到给定条件为假为止。其形式如下,执行过程是交替地检测 condition 条件和执行关联的语句 statement ,直到 condition 为假时停止。所谓条件 condition 就是一个或真或假的表达式,只要 condition 为真,statement 就会被执行。执行完 statement ,会再次检测 condition 。只要 condition 仍为真,statemetn 再次被执行。如此交替检测 condition 和执行 statement ,直到 condition 为假为止。

while (condition)
	statement

下面用while语句编写一段程序,求 110 这十个数之和。不难发现,while语句的条件中使用了小于等于运算符 <= 来比较 val 的当前值和 10 ,只要 val <= 10 ,条件就为真,就执行while循环体。如此循环,交替检测条件、执行循环体,直至 val > 10 为止。

本例中的循环体是由两条语句组成的语句块 block ——用花括号包围的零条或多条语句的序列。语句块也是语句的一种,在任何要求使用语句的地方都可以使用语句块。本例语句块的第一条语句使用了复合赋值运算符 += ,此运算符将右侧运算对象加到左侧运算对象上,将结果保存到左侧运算对象中,本质上与一个加法结合一个赋值 assignment 相同;下一条语句使用前缀递增运算符 ++ ,递增运算符将运算对象的值增加 1++val 等价于 val = val + 1

//C++ version
#include <iostream>
int main() 
{
	int sum = 0, val = 1;
	//只要val的值小于等于10,while循环就会持续执行
	while (val <= 10) {
		sum += val; 	//将sum+val赋予给sum,用于保存和
		++val;		    //将val加1,表示从1到10的每个数
	}
	std::cout << "Sum of 1 to 10 inclusive is "
	   		  << sum << std::endl;
	return 0;
}	

跳出循环后,继续执行之后的语句,本例中继续执行打印输出语句,然后执行 return 语句完成 main 程序。编译并执行这个程序,它会打印出:

Sum of 1 to 10 inclusive is 55

1.4.1节练习

练习1.9:编写程序,使用 while 循环将 50100 的整数相加。

练习1.10:除了 ++ 运算符将运算对象的值增加 1 之外,还有一个递减运算符 -- 实现将值减少 1 。编写程序,使用递减运算符在循环中按递减顺序打印出 100 之间的整数。

练习1.11:编写程序,提示用户输入两个整数,打印出这两个整数指定范围内的所有整数。

1.4.2 for语句

上述示例中,在循环条件中检测变量、在循环体中递增变量的模式使用非常频繁,因此C++专门定义了第二种循环语句——for语句,来简化符合这种模式的语句。每个for语句都包含两个部分:循环头和循环体。循环头控制循环体的执行次数,由三个部分组成:一个初始化语句 init-statement只在for循环入口处执行一次)、一个循环条件 condition循环体每次执行前都要先检查循环条件)以及一个表达式 expression表达式在for循环体之后执行,执行后重新检测循环条件)。循环体同while循环一样,是一个语句 statement

重写上例程序,int val = 1 是初始化语句,定义了一个 int 型对象 val 、并赋初值为 1 ,变量 val 仅在for循环内部存在,在循环结束之后不能使用;val <= 10 是循环条件,每次先检测循环条件,为真时再执行循环体;表达式是 ++val ,在for循环体之后执行,其后for语句重新检测循环条件,仍为真就再次执行for循环体。如此循环持续这一过程,直至循环条件为假。

#include <iostream>
int main()
{
	int sum = 0;
	for (int val = 1; val <= 10; ++val) //从1加到10
		sum += val;	//等价于sum = sum + val
	std::cout << "Sum of 1 to 10 inclusive is "
			  << sum << std::endl;
	return 0;
}

上述for循环的总体执行流程如下:

  1. 创建变量 val ,初始化为 1
  2. 检测 val 是否小于等于 10 。若检测成功则执行 for 循环体。若失败则退出循环,继续执行for循环体之后的第一条语句;
  3. val 的值增加 1
  4. 重复第二步的条件检测,只要条件为真就继续执行剩余步骤。

1.4.2节练习

练习1.12:下面的for循环完成了什么功能?sum 的终值是多少?

int sum = 0;
for (int i = -100; i <= 100; ++i)
	sum += i;

答:for循环将 -100100 之间的整数相加,sum 的终值为 0

练习1.13:使用for循环重做1.4.1节的所有练习。

练习1.14:对比for循环和while循环,两种形式的优缺点各是什么?

练习1.15:编写程序,包含第14页“再谈编译”中讨论的常见错误,熟悉编译器生成的错误信息。

1.4.3 读取数量不定的输入数据

如果我们预先不知道要对多少个数求和,就需要不断读取数据直至没有新的输入为止。

#include <iostream>
int main()
{
	int sum = 0, value = 0;
	while (std::cin >> value) //读取数据直到遇到文件尾,计算所有读入的值的和
		sum += value; //等价于sum = sum + value
	std::cout << "Sum is: " << sum << std::endl;
	return 0;
}

显然,数据读取操作是在while的循环条件中完成的。对while循环条件进行求值,就是执行表达式 std:: cin >> value ,这一表达式从标准输入读取一个数,保存在 value 中——输入运算符 >> 的左侧运算对象必须是一个 istream 对象,右侧运算对象用来存储读入的数据。此处的 >> 从给定的 istream 对象 std::cin 读入数据并存入给定对象 value 中,计算结果就是其左侧运算对象 std::cin 。 因此,此循环条件实际上检测的是 std::cin

特别地,当我们使用一个 istream 对象作为条件时,效果是检测流的状态。如果流是有效的,即流未遇到错误,则检测成功;遇到文件结束符 end-of-file 或一个无效输入(如要求整数,读入的值却是一个字符串),istream 对象的状态会变为无效。处于无效状态的 istream 对象会使条件变为假

因此,本例的while循环将一直执行,直到遇到文件结束符或无效输入。一旦条件失败,while循环将结束,并执行下一条语句,打印 sum 的值和一个 std::endl 。如果输入 3 4 5 6 +文件结束符,则程序会输出 Sum is: 18

我们可以从键盘键入文件结束符以指出文件结束,只是不同的操作系统有不同的约定。Windows系统中是输入 Ctrl+Z ,然后按下 Enter 键;Unix系统(包括Mac OS X系统)中,输入文件结束符是用 Ctrl+D

再探编译

1.4.3节练习

练习1.16:编写程序,从 cin 读取一组数,输出其和。

1.4.4 if语句

类似其他语言,C++也提供了if语句来支持条件执行。比如说写一个程序,统计在不定长度的输入中每个值连续出现了多少次——程序以 val, currVal 两个变量的定义开始,currVal 记录正在统计出现次数的那个数,val 则保存从输入读取的每个数;最外层的if语句保证输入不为空,它读取一个数值存入 currVal 中,如果读取成功,则条件求值为真,继续执行条件之后的语句块;接着定义 cnt ,用于统计每个数值连续出现的次数;然后用一个while循环反复从标准输入读取整数;while循环中是第二条if语句,它使用相等运算符 == 检测 val 是否等于 currVal ,等于则执行紧跟条件之后的语句,将 cnt 增加 1 ,表示再次看到了 currVal ,否则执行 else 之后的语句块,输出语句打印我们刚刚统计完的值和值出现的次数,赋值语句将 cnt 重置为 1 、将 currVal 重置为刚刚读入的值 val

#include <iostream>
int main() 
{
	// currVal是我们正在统计的数;我们将读入的新值存入val
	int currVal = 0, val = 0;
	// 读取第一个数,并确保确实有数据可以处理
	if (std::cin >> currVal) {		
		int cnt = 1;				// 保存我们正在处理的当前值的个数
		while (std::cin >> val) {	// 读取剩余的数
			if (val == currVal) 	// 如果值相同
				++cnt;				// 将cnt加1
			else {
				std::cout << currVal << " occurs "
						  << cnt << " times" << std::endl;
				currVal = val;	    // 记住新值
				cnt = 1;			// 重置计数器
			} 
		} // while循环在这里结束
		// 记住打印文件中最后一个值的个数
		std::cout << currVal << " occurs "
				  << cnt << " times" << std::endl;
	} // 最外层的if语句在这里结束
	return 0;
}

对应的输入和输出是:

Input: 42 42 42 42 42 55 55 62 100 100 100
Output:
42 occurs 5 times
55 occurs 2 times
62 occurs 1 times
100 occurs 3 times

1.4.4节练习

练习1.17:如果输入的所有值都是相等的,本节的程序会输出什么?如果没有重复值,输出又会是怎样的?

练习1.18:编译并运行本节的程序,给它输入全都相等的值。再次运行程序,输入没有重复的值。

练习1.19:修改你为1.4.1节练习1.10所编写的程序(打印一个范围内的数),使其能处理用户输入的第一个数比第二个数小的情况。

关键概念:C++程序的缩进和格式

很大程度上,C++程序是格式自由的,何处放置花括号、缩进、注释、换行符通常不会影响程序的语义。如表示 main 函数体开始的左花括号,可以放在 main 的同一行中,或者放在下一行的起始位置,或者放在我们喜欢的其他任何位置——唯一的要求是左花括号必须是 main 形参列表后的第一个非空、非注释的字符

我们很大程度上可以按照自己的意愿自由地设定程序的格式,但是所做的选择会影响程序的可读性,所以必须谨慎行事——把整个 main 函数写在很长的单行内,虽然是合乎语法的,但会非常难读。

C/C++正确格式的辩论是无休无止的。以DevC++菜单栏的AStyle为例,可以设置格式化选项、并依此格式化当前文件,如下所示可以看到括号风格(很多种如 Allman, Java, K&R, Stroustrup, Whitesmith, Banner, GNU, ...)、缩进风格Spaces, Tabs, Force Tab, Force Tab X)、Tab宽度、最大行数(搞错了吧,应该是列数)、缩进以下类型的代码、格式化命令Artistic Style 3.1 是一个免费、快速、小型、自动化的格式化工具,用于C/C++, C++/CLI, Objective-C, C#, Java):
在这里插入图片描述
作者认为,不存在唯一正确的风格,但保持一致性是非常重要的……其他可能的程序格式总是存在的,当你要选择一种格式风格时,思考一下它会对程序的可读性和易理解性有什么影响,而一旦选择了一种风格,就要坚持使用


1.5 类简介

为了解决书店程序,需要了解的唯一一个C++特性:如何定义一个数据结构 data structure ,以表示销售数据。事实上,C++中我们通过定义一个 class 来定义自己的数据结构——一个类定义了一个新的类型与其关联的一组操作,这一新类型的类型名就是类名。类机制是C++最重要的特性之一,也是C++最初的设计焦点——能定义在使用上像内置类型一样自然的类类型 class type

使用类需要了解三件事情:类名是什么?类是在哪里定义的?类支持什么操作?和使用标准库设施一样,使用自定义的类也要包含相关的头文件,来访问为自己的应用程序所定义的类。注意:头文件习惯上按照其中定义的类的名字来命名,且通常使用 .h 作为头文件的后缀(有些程序员习惯 .H, .hpp, .hxx 等)。标准库头文件通常不带后缀。不过,编译器一般不关心头文件名的形式,只是有的IDE对此有特定要求

本节编写一个简单的类,用于书店程序,类名为 Sales_item ,在头文件 Sales_item.h 中定义。后续章节中学习了更多关于类型、表达式、语句和函数的知识后,才会真正实现这个类

1.5.1 Sales_item

Sales_item 类的作用是表示一本书的总销售额、售出册数和平均售价。由于类名就是类型名,Sales_item 类定义了一个名为 Sales_item 的类型。与内在类型一样,我们可以定义类类型的变量,以下语句表示 item一个 Sales_item 类型的对象,或称为一个 Sales_item 对象

Sales_item item;

为了使用一个类,我们不必关心它是如何实现的,只需知道类对象可以执行什么操作。当前,我们所知的可以在 Sales_item 对象上执行的全部操作,就是列出的这些操作:

  • 调用一个名为 isbn 的函数从一个 Sales_item 对象中提取ISBN书号;
  • 用输入运算符 >> 和输出运算符 << 读、写 Sales_item 类型的对象;
  • 用赋值运算符 = 将一个 Sales_item 对象的值赋给另一个 Sales_item 对象;
  • 用加法运算符 + 将两个 Sales_item 对象相加。注意,两个对象必须表示同一本书即相同的ISBN(对调用者的要求),加法结果是一个新的 Sales_item 对象,其ISBN与两个运算对象相同,其总销售额和售出册数则是两个运算对象的对应值之和。
  • 使用复合运算符 += 将一个 Sales_item 对象加到另一个 Sales_item 对象上。

要牢记的是,Sales_item 的作者定义了类类型对象上可以执行的所有动作,即 Sales_item 类定义了创建一个 Sales_item 对象时会发生什么事情,以及对 Sales_item 进行赋值、加法或输入输出运算时会发生什么事情。

读写 Sales_item

下面的程序从标准输入读入数据,存入一个 Sales_item 对象中,然后将 Sales_item 的内容写到标准输出。注意,该程序在包含来自标准库的头文件时,使用 <> 包围头文件名,对不属于标准库的头文件,则用双引号 "" 包围

#include <iostream>
#include "Sales_item.h"
int main() 
{
	Sales_item book;
	std::cin >> book; 				// 输入ISBN号、售出的册数和销售价格
	std::cout << book << std::endl; // 写入ISBN号、售出的册数、总销售额和平均价格
	return 0;
}

该程序的输入和输出如下所示。输入告诉我们以每本24.99美元的价格售出了4册书,输出告诉我们总售出册数为4、总销售额为99.96美元、平均售价为24.99美元:

Input:
0-201-70353-X 4 24.99
Output:
0-201-70353-X 4 99.96 24.99

Sales_item 对象的加法

下面的程序从标准输入读入数据,存入两个 Sales_item 对象之中,输出表达式完成加法运算并打印结果。这一程序与1.2.2 使用IO库的程序非常相似,只是把运算对象从两个整数换成了两个 Sales_item 而已,读取和打印和的运算方式没有发生任何变化。当然,由于运算符重载,“和”的概念完全不同——我们对 int 对象用加法运算符 + ,计算的是传统意义上的算术加法和;对 Sales_item 用加法运算符 + ,得到的是两个 Sales_item 对象的成员对应相加的结果。

#include <iostream>
#include "Sales_item.h"
int main()
{
	Sales_item item1, item2;
	std::cin >> item1 >> item2;	// 读入一对交易记录
	std::cout << item1 + item2 << std::endl; // 打印它们的和
	return 0;
}

该程序的输入和输出如下所示。

Input:
0-201-78345-X 3 20.00
0-201-78345-X 2 25.00
Output:
0-201-78345-X 5 110 22

使用文件重定向

测试程序时,反复从键盘敲入这些销售记录作为程序的输入,非常乏味费时。幸好多数操作系统支持文件重定向机制,允许我们将标准输入标准输出命名文件关联起来:

$ addItems < infile > outfile

假设 $ 是操作系统提示符,加法程序已经编译为 addItems.exe 的可执行文件(在Unix中是 addItems ),则上述命令从一个名为 infile 的文件读取销售记录,并将输出结果写入到一个名为 outfile 的文件中,两个文件都位于当前目录中

除此以外,使用文件重定向机制,可以进一步优化对拍技巧

1.5.1节练习

练习1.20:在网站http://www.informit.com/title/0321714113上,第一章的代码目录中包含了头文件 Sales_item.h 。将它拷贝到你自己的工作目录中,用它编写一个程序,读取一组书籍销售记录,将每条记录打印到标准输出中。

练习1.21:编写程序,读取两个ISBN相同的 Sales_item 对象,输出它们的和。

练习1.22:编写程序,读取多个具有相同ISBN的销售记录,输出所有记录的和。

1.5.2 初识成员函数(item.isbn()

将两个 Sales_item 对象相加的程序,首先应检查两个对象是否具有相同的ISBN。如果相等则程序打印计算结果,并返回0表示成功;如果条件失败则打印错误消息,并返回-1表示错误标识。

#include <iostream>
#include "Sales_item.h"
int main() 
{
	Sales_item item1, item2;
	std::cin >> item1 >> item2;
	// 首先检查item1和item2是否表示相同的书
	if (item1.isbn() == item2.isbn()) {
		std::cout << item1 + item2 << std::endl;
		return 0;  // 表示成功
	} else {
		std::cerr << "Data must refer to same ISBN" << std::endl; 
		return -1; // 表示失败
	}
}

需要注意的是if语句的检测条件,调用了名为 isbn()成员函数 member function ,成员函数是定义为类的一部分的函数,有时也称为方法 method 。我们常以一个类对象的名义来调用成员函数,如 item1.isbn() 中使用点运算符 . 表达需要“名为 item1 的对象的 isbn 成员”。

要注意的是,点运算符只能用于类类型的对象,其左侧运算对象必须是一个类类型的对象,右侧运算对象必须是该类型的一个成员名,运算结果为右侧运算对象指定的成员。

1.5.2节练习

练习1.23:编写程序,读取多条销售记录,并统计每个ISBN(每本书)有几条销售记录。

练习1.24:输入表示多个ISBN的多条销售记录来测试上一个程序,每个ISBN的记录应该聚在一起。


1.6 书店程序

现在万全具备,我们已经准备好完成书店程序了。先从一个文件中读取销售记录,生成每本书的销售报告,显示售出册数、总销售额和平均售价。此处假定每个ISBN书号的所有销售记录在文件中是聚在一起保存的

程序将每个ISBN的所有数据合并起来,存入名为 total 的变量中,同时使用另一个名为 trans 的变量保存读取的每条销售记录。如果 totaltrans 指向相同的ISBN,我们更新 total 的值;否则打印 total 的值,并重置为刚刚读取的数据 trans 。如果在第一条if语句就读取失败,则意味着没有任何销售记录,于是直接跳到最外层的else分支,打印一条警告消息,告诉用户没有输入:

#include <iostream>
#include "Sales_item.h"
int main()
{
	Sales_item total;			// 保存下一条交易记录的变量
	// 读入第一条交易记录,并确保有数据可以处理
	if (std::cin >> total) {
		Sales_item trans;		// 保存和的变量
		// 读入并处理剩余交易记录
		while (std::cin >> trans) {
			// 如果我们仍在处理相同的书
			if (total.isbn() == trans.isbn())
				total += trans; // 更新总销售额
			else {
				std::cout << total << std::endl;
				total = trans;  // total现在表示下一本书的销售额
			}
		}
		std::cout << total << std::endl; // 打印最后一本书的结果
	} else {
		// 没有输入!警告读者
		std::cerr << "No data?!" << std::endl;
		return -1; //表示失败
	}
	return 0;
}

1.6节练习

练习1.25 借助网站上的 Sales_item.h 头文件,编译并运行本节给出的书店程序。



  1. Main function ??

  2. Functions ??

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-12 23:15:23  更:2021-10-12 23:17:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 19:42:00-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计