1.1 编写一个简单的C++程序
每个C++程序都包含一个或多个函数,其中一个必须命名为main。操作系统通过调用main来运行C++程序。
一个函数的定义包括四部分:返回类型、函数名、形参列表(允许为空)、函数体。
main函数的返回类型必须为int,即整数类型。int类型是一种内置类型,即语言自身定义的类型。
函数定义的最后一部分为是函数体,它是一个以左花括号开始,以右花括号结束的语句块。
在大多数系统中,main的返回值被用来指示状态。返回值0表明成功,非0的返回值的含义由系统定义,通常用来指出错误类型。
类型是程序设计最基本的概念之一,一种类型不仅定义了数据元素的内容,还定义了这类数据上可以进行的运算。
程序所处理的数据都保存在变量中,而每个变量都有自己的类型。如果一个名为V的变量的类型为T,我们通常说“V具有类型T”或“V是一个T类型的变量”。
1.1.1 编译、运行程序
程序源文件命名约定
程序文件通常被称为源文件。
不同编译器使用不同的后缀命名约定,最常见的包括.cc、.cxx、.cpp、.cp及.c。
从命令行运行编译器(略)
1.2 初识输入输出
C++语言并未定义任何输入输出(IO)语句,取而代之,包含了一体个全面的标准库来提供IO机制。
iostream库包含两个基础类型istream和ostream,分别表示输入流和输出流。术语“流”想要表达的是,随着时间的推移,字符是顺序生成或消耗的。
标准输入输出对象
标准库定义了4个IO对象。
为处理输入,使用一个名为cin的istream类型对象,也被称为标准输入。
对于输出,使用一个名为cout的ostream类型对象,也被称为标准输出。
标准库还定义了其他两个ostream对象cerr(输出警告和错误信息,又称为标准错误)和clog(输出程序运行时的一般性消息)。
系统通常将程序所运行的窗口与这些对象关联起来。因此,当我们读取cin,数据将从程序正在运行的窗口读入,当我们向cout、cerr和clog写入数据时,将会写到同一个窗口。
一个使用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库。尖括号中的名字(本例中的iostream)指出了一个头文件。每个使用标准库设施的程序都必须包含相关的头文件。#include指令和头文件的名字必须写在同一行中。
向流写入数据
main的函数体的第一条语句执行了一个表达式。这条语句中的表达式使用了输出运算符(<<)在标准输出上打印消息。
<<运算符接受两个运算对象:左侧的运算对象必须是一个ostream对象,右侧的运算对象是要打印的值。此运算符将那个给定的值写到给定的ostream对象中,计算结果就是我们写入给定值的那个ostream对象。
输出语句使用了两次<<运算符。因为此运算符返回其左侧的运算对象,因此第一个运算符的结果成为了第二个运算符的左侧运算对象,这样就能连接输出请求。
链中每个运算符的左侧运算对象都是相同的,在本例中是std::cout。以下三种形式可以生成相同时输出。
//方法1
std::cout<<"Enter two numbers:"<<std::endl;
//方法2
(std::cout<<"Enter two numbers:")<<std::endl;
//方法3
std::cout<<"Enter two numbers:";
std::cout<<std::endl;
方法3第一行输出运算符打印一条消息,是字符串字面值常量, 是一对用双引号包围的字符序列,其中文本被打印到标准输出;第二行打印endl,被称为操纵符的特殊值,效果是结束当前行并将与设备关联的缓冲区中的内容刷到设备中,保证目前为止程序所产生的所有输出都真正写入到输出流中,而不是停留在内存等待写入流。
使用标准库中的名字
前缀std::指出名字cout和endl是定义在名为std的命名空间中的,命名空间可以帮助我们避免不经意的名字定义冲突,以及使用库中相同的名字导致的冲突。标准库定义的所有名字都在命名空间std中。使用库中的一个名字时必须通过使用作用域运算符(::)来指出想使用的定义在命名空间的名字。
从流读取数据
定义两个名为v1和v2的变量来保存输入:
int v1=0,v2=0;
将这两个变量定义为int类型(int是一种内置类型,用来表示整数),还将它们初始化为0。
初始化一个变量,就是在变量创建的同时为它赋予一个值。
//方式1
std::cin>>v1>>v2;
//方式2
(std::cin>>v1)>>v2;
//方法3
std::cin>>v1;
std::cin>>v2;
输入运算符(>>)接受一个istream作为其左侧运算对象,接受一个对象作为其右侧运算对象。它从给定的istream读入数据,并存入给定对象中。输入运算符返回其左侧运算对象作为其计算结果。
完成程序
打印结果。
std::cout<<"The sum of" <<v1<< "and" <<v2<< "is" <<v1+v2 <<std::endl;
1.2节练习
练习1.3
编写程序,在标准输出上打印Hello, World。
#include <iostream>
int main()
{
std::cout<<"Hello, World"<<std::endl;
return 0;
}
练习1.4
我们的程序使用加法运算符+来将两个数相加。编写程序使用乘法运算符*,来打印两个数的积。
#include <iostream>
int main()
{
std::cout<<"请输入两个数:"<<std::endl;
int v1=0,v2=0;
std::cin>>v1>>v2;
std::cout<<"数字" <<v1<< "和" <<v2<< "的积是" <<v1*v2<< std::endl;
return 0;
}
练习1.5
我们将所有输出操作放在一条很长的语句中。重写程序,将每个运算对象的打印操作放在一条独立的语句中。
#include <iostream>
int main()
{
std::cout<<"请输入两个数:";
std::cout<<std::endl;
int v1=0,v2=0;
std::cin>>v1>>v2;
std::cout<<"数字" <<v1<< "和" <<v2<< "的积是" <<v1*v2;
std::cout<<std::endl;
return 0;
}
练习1.6
解释下面程序片段是否合法。
std::cout<<"The sum of" <<v1;
<<"and" <<v2;
<<"is" <<v1+v2 <<std::endl;
这个程序片段不合法。前两行已有分号表示语句结束,后两行没有执行操作命令。应当修改如下:
//方法1
std::cout <<"The sum of" <<v1 "and" <<v2 "is" <<v1+v2 <<std::endl;
//方法2
std::cout <<"The sum of" <<v1;
std::cout <<"and" <<v2;
std::cout <<"is" <<v1+v2 <<std::endl;
1.3 注释简介
C++中注释的种类
单行界定符:一行出现//后的所有内容都当做注释。常用于半行和单行附注。
界定符对:以/*开始,*/结束,可以包含换行符,除*/以外的任意内容都可以包含,界定符对之间的额内容都当做注释。通常用于多行解释。
注释界定符不能嵌套(略)
1.3节练习
练习1.8
指出下列哪些输出语句是合法的(如果有的话):
std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
std::cout << /* "*/" /* "/*" */;
预测编译这些语句会产生什么样的效果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。
第一行:合法,输出“/*”。
第二行:合法,输出“*/”。
第三行:不合法。第一个引号属于注释范围,引号对不完整。
第四行:合法。第一个注释:/* "*/,之后输出"/*",之后出现第二个注释:/*" */。即:这条语句最终会输出"/*"。
1.4 控制流
1.4.1 while语句
while语句反复执行一段代码,直至给定条件为假为止。
一个求1至10这10个数之和的代码如下:
#include <iostream>
int main()
{
int sum = 0, val = 1;
//只要val的值小于等于10,while循环就会持续地执行
while (val <= 10) {
sum += val; //将 sum + val 赋予sum
++val //将 val 加1
}
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
while语句的形式为
while (condition)
statement
while语句的执行过程是交替地检测condition条件和执行关联的语句statement,直至condition为假时停止。条件是一个产生真或假的结果的表达式。只要condition为真,statement就会被执行,执行完后再次检测condition,仍为真则再次执行,直至condition为假则跳出循环。
例子中的while语句条件中使用了小于等于运算符(<=)来比较val的当前值和10.循环体由两条语句块组成,所谓语句块就是用花括号包围的零条或多条语句的序列。
本例中语句块的第一条语句使用了复合赋值运算符(+=),此运算符将其右侧的运算对象加到左侧运算对象上,将结果保存到左侧运算对象中。它本质上与一个加法结合的赋值是相同的。
++val使用前缀递增运算符(++),使得运算对象的值+1。
1.4.1节练习
练习1.9
编写程序,使while循环将50道100的整数相加。
#include <iostream>
int main()
{
int sum = 0, val = 50;
while (val <= 100){
sum += val;
++val;
}
std::cout << "50到100的整数相加的结果是:"
<< sum << std::endl;
return 0;
}
练习1.10
除了++运算符将运算对象的值增加1之外,还有一个递减运算符(--)实现将值减少1。编写程序,使用递减运算符再循环中按递减顺序打印出10到0之间的整数。
#include <iostream>
int main()
{
int val=10;
while(val>=0)
{
std::cout<<val<<std::endl;
--val;
}
}
练习1.11
编写程序,提示用户输入两个整数,打印出这两个整数所指定的范围内的所有整数。
//我自己的做法
#include <iostream>
int main()
{
int i,j,k;
std::cin >> i >> j;
if(i > j)
{
k = i;
i = j;
j = k;
}
while (i <= j)
{
std::cout << i << " ";
i++;
}
}
//习题集解答
#include <iostream>
int main()
{
std::cout<<"请输入两个数:"<<std::endl;
int v1,v2;
std::cin>>v1>>v2;
if(v1>v2) //由大至小打印
while(v1>=v2) {
std::cout<<v1<<std::endl;
v1--;
}
else //由小至大打印
while(v1<=v2) {
std::cout<<v1<<std::endl;
v1++;
}
std::cout << std::endl;
return 0;
}
1.4.2 for语句
使用for语句重写1加到10的程序:
#include <iostream>
int main()
{
int sum=0;
//从1加到10
for(int val = 1; val <= 10; ++val)
sum += val; //等价于 sum = sum + val
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
每个for语句都包含两部分:循环头和循环体。循环头控制循环体的执行次数,由三个部分组成:初始化语句(int val=1,此处变量val只能在for循环内部存在,循环结束后不能使用,只在for循环入口处执行一次)、循环条件(val<=10,每次执行前检查循环条件)和表达式(++val)。执执行完表达式后重新检测循环条件,符合则继续循环,直到循环条件为假。
1.4.2节练习
练习1.12
下面的for循环完成了什么功能?sum的终值是多少?
int sum = 0;
for (int i = -100; i <= 100; ++i)
sum += i;
这个for循环求-100到100之间所有整数的和,因此sum终值为0。
练习1.13
使用for循环重做1.4.1节中的所有练习。
练习1.9
#include <iostream>
int main()
{
int sum = 0;
for(int i=50; i<=100; i++)
{
sum += i;
}
std::cout << "50到100的整数相加之和为:" << sum << std::endl;
}
练习1.10
#include <iostream>
int main()
{
int sum = 0;
for(int i=10; i>=0; i--)
{
std::cout << i << std::endl;
}
}
练习1.11
//我的做法
#include <iostream>
int main()
{
int i,j,k;
std::cin >> i >> j;
if(i > j)
{
k = i;
i = j;
j = k;
}
for (;i <= j;i++)
{
std::cout << i << " ";
}
}
//参考答案
#include <iostream>
int main()
{
std::cout<<"请输入两个数";
std::cout<<std::endl;
int v1,v2;
std::cin>>v1>>v2;
if(v1>v2) //由大至小打印
for(;v1>=v2;v1--)
std::cout<<v1<<" ";
else
for(;v1<=v2;v1++)
std::cout<<v1<<" ";
std::cout<<std::endl;
return 0;
}
练习1.14
对比for循环和while循环,两种形式的优缺点各是什么?
【解答】
在循环次数已知的情况下,for循环的形式显然更为简洁。
而循环次数无法预知时,用while循环实现更适合。用特定条件控制循环是否执行,循环体中执行的语句可能导致循环判定条件发生变化。
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循环条件的求值就是执行表达式,其中的条件表达式从标准输入读取下一个数,保存在value中。输入运算符返回其左侧运算对象,在本例中是std::cin。因此,次循环条件实际上检测的是std::cin。
使用一个istream对象作为条件的效果是检测流的状态,流有效(未遇到错误)则检测成功,遇到文件结束符(EOF)或无效输入则istream对象状态变为无效使得条件变为假。
1.4.4 if语句
用if语句写一个程序来统计在输入中每个值连续出现了多少次:
#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;
}
第一条if语句保证输入不为空。该语句块以左花括号开始,以return语句之前的右花括号结束。
如果需要统计出现次数的值,我们就定义cnt,用来统计每个数值连续出现的次数。
while的循环体是一个语句块,包含了第二条if语句,其中条件使用了相等运算符(==)来检测val是否等于currVal。
1.5 类简介
在C++中,我们通过定义一个类来定义自己的数据结构。一个类定义了一个类型,以及与其关联的一组操作。C++最初的一个设计焦点就是能定义使用上像内置类型一样自然的类类型(class type)。
本节以书店程序会用到的简单的类作为介绍,后续章节学习更多内容后再真正实现。假定类名为Sales_item,头文件Sales_item.h已包含这个类。
习惯上,头文件根据其中定义的类的名字来命名,通常使用.h作为头文件。标准库头文件通常不带后缀。
1.5.1 Sales_item类
Sales_item类的作用是表示一本书的总销售额、售出册数和平均售价。写出这个语句
Sales_item item;
是想表达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;
//读入ISBN号、售出的册数以及销售价格
std::cin >> book;
//写入ISBN、售出的册数、总销售额和平均价格
std::cout << book << std::endl;
return 0;
}
此程序以两个#include指令开始,其中一个使用了新的形式。包括来自标准库的头文件时,也应该使用尖括号(<>)包围头文件名。对于不属于标准库的头文件,则使用双引号(" ")包围。
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;
}
此程序包含了Sales_item和iostream两个头文件。然后定义了两个Sales_item对象来保存销售记录。我们从标准输入读取数据,存入两个对象之中。输出表达式完成加法运算并打印结果。
这和1.2节的程序非常相似,因为只是将对象从两个整数变为两个Sales_item对象,但读取和打印运算方式不变。其中不同之处是“和”的概念完全不同。对于int计算传统意义上的和,而对于Sales_item对象则采用“两个Sales_item对象的成员的对应相加的结果”的全新概念。
1.5.1节练习
练习1.20
在网站https://www.informit.com/title/0321714113上,第1章的代码目录中包含了头文件Sales_item.h。将它拷贝到你自己的工作目录中。用它编写一个程序,读取一组书籍销售记录,将每条记录打印到标准输出上。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item book;
std::cout << "请输入销售记录" std::endl;
while (std::cin >> book)
{
std::cout << "ISBN、售出本数、销售额和平均售价为" << book << std::endl;
}
return 0;
}
练习1.21
编写程序,读取两个ISBN相同的Sales_item对象,输出他们的和。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item trans1, trans2;
std::cout << "请输入两条ISBN相同的销售记录:" << std::endl;
std::cin >> trans1 >> trans2;
if (compareIsbn(trans1, trans2))
std::cout << "汇总信息:ISBN、售出本数、销售额和平均售价为 " << trans1 + trans2 << std::endl;
else
std::cout << "两条销售记录的ISBN不同" << std::endl;
return 0;
}
练习1.22
编写程序,读取多个具有相同ISBN的销售记录,输出所有记录的和。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item total, trans;
std::cout << "请输入几条ISBN相同的销售记录:" << std::endl;
if (std::cin >> total) {
while (std::cin >> trans)
if (compareIsbn (total, trans)) //ISBN相同
total = total + trans;
else { //ISBN不同
std::cout << "ISBN不同" << std::endl;
return -1;
}
std::cout << "汇总信息:ISBN、售出本数、销售额和平均售价为 " << total << std::endl;
}
else {
std::cout << "没有数据" << std::endl;
return -1;
}
return 0;
}
1.5.2 初识成员函数
将两个Sales_item对象的程序首先要检查两对象中的ISBN是否相同,方法如下:
#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语句及其else分支。
什么是成员函数?
程序中的if语句的检测条件调用名为isbn的成员函数。成员函数是定义为类的一部分的函数,有时也称为方法。
通常以一个类对象的名义来调用成员函数,如上面表达式中“item1.isbn()”的点运算符表达需要“名为item1的对象的isbn成员”。点运算符只能用于类类型的对象,左侧运算对象必须是一个类类型的对象,右侧运算对象必须是该类型的一个成员名,运算结果为右侧运算对象指定的成员。后面的一对括号称为调用运算符,作用是调用一个函数,其中的内容是实参(可能为空)。
1.5.2节练习
练习1.23
编写程序,读取多条销售记录,并统计每个ISBN(每本书)有几条销售记录。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item trams1, trans2;
int num = 1;
std::cout << "请输入若干销售记录" << std::endl;
if (std::cin >> trans1) {
while (std::cin >> trans2)
if (compareIsbn(trans1, trans2)) //ISBN相同
num++;
else { //ISBN不同
std::cout << trans1.isbn() << "共有" << num << "条销售记录" << std::endl;
trans1 = trans2;
num = 1;
}
std::cout << trans1.isbn() << "共有" << num << "条销售记录" << std::endl;
}
else {
std::cout << "没有数据" << std::endl;
return -1;
}
return 0;
}
1.6 书店程序
#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;
}