第一章 开始
1.1 编写一个简单的C++程序
每个C++程序都包含一个或多个函数,其中有一个必须是main函数。
//最简单的main函数
int main()
{
return 0;
}
一个函数的定义包括四部分:
- 返回类型
- 函数名
- 形参列表
- 函数体
注意:
-
main函数的返回类型必须是 int 型(int类型是一种内置类型,即语言自身定义的类型)。
-
当return语句包含一个值时,此返回值的类型必须与函数的返回类型相容。
在大多数系统中,main的返回值被用来指示状态。返回值0表明成功,非0的返回值含义由系统定义,通常用来指出错误类型
1.1 编译、运行程序
程序源文件命名约定
-
程序文件通常被称为源文件
-
源文件的名字以一个后缀为结尾,后缀是一个句点后接一个或多个字符组成的。
后缀告诉系统这个文件是一个C++程序,不同编译器使用不同的后缀命名约定,最常见的包括.cc、.cxx、.cpp、.cp 及 .C
从命名行运行编译器
见P3~P4
再探编译
编译器的一部分工作是寻找程序文本中的错误。编译器没有能力检查一个程序是否按照其作者的意图工作,但可以检查形式上的错误。下面是一些最常见的编译器可以检查出来的错误:
-
语法错误
//错误:main的参数列表漏掉了 int main( { //错误:endl后使用了冒号而非分号 std::cout << "Read each file" << std::endl: //错误:字符串字面值常量的两侧漏掉了分号 std::cout << Update master. <<std::endl; //错误:漏掉了第二个输出运算符 std::cout << "Write new master." std::endl; //错误:return语句漏掉了分号 return 0 }
-
类型错误:C++中每个数据项都有其类型。例如:10的类型是int;单词“hello”,包括两边的双引号标记,则是一个字符串字面值常量。一个类型错误的例子:向期望参数为int的函数传递了一个字符串字面值常量。
-
声明错误:C++程序中每个名字都要先声明后使用。名字声明失败通常会导致一条错误信息。两种常见的错误声明:
- 对来自标准库的名字忘记使用 std::
- 标识符名字拼写错误
按照编译器提示的报告顺序逐个修正错误是一种好习惯。因为一个单个错误常常会具有传递效应,导致编译器在其后报告比实际数量多得多的错误信息。
另一个好习惯是在每修正一个错误后就立即重新编译代码,或者最多是修正了一小部分明显的错误后就重新编译。这就是所谓的 “编辑-编译-调试” 周期。
1.2 初始输入输出
C++语言未定义任何输入输出IO语句,取而代之的是包含了一个全面的标准库来提供IO机制(以及很多其他设施)。
iostream 库包含两个基础类型 istream(输入流) 和 ostream(输出流)。
一个流就是一个字符系列,是从IO设备读出或写入IO设备的。术语“流”想表达的是:随着时间的推移,字符是顺序生成或消耗的。
标准输入输出对象
标准库定义了4个IO对象。
处理输入:
-
cin(istream类型的对象),该对象也被称为
标准输入
处理输出:
-
cout(ostream类型的对象),该对象也被称为
标准输出
-
cerr(ostream类型的对象),通常用 cerr 来输出警告和错误消息,因此也被称为
标准错误
- clog(ostream类型的对象),用来输出程序运行时的一般性信息
一个使用IO库的程序
示例:
一个提示用户输出两个数,然后输出它们的和程序:
#include<iostream>
int main()
{
std::cout << "Enter two number:" << 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;
}
头文件
- 第一行告诉编译器我们想使用iostream库。尖括号中的名字指出了一个头文件
- 每个使用标准库设施的程序都必须包含相关的头文件
- 对于来自标准库的头文件应用尖括号(< >)包围头文件名,对于不属于标准库的头文件,则用双引号(“ ”)包围
-
#include
指令和头文件必须写在同一行 - 通常,#include指令必须出现在所有函数之外,一般将一个程序的所有#include指令放在源文件的开始
向流写入数据
在C++中,一个表达式产生一个计算结果,它由一个或多个运算对象和(通常是)一个运算符组成。
<< (输出运算符)接受两个运算对象:左侧的运算对象必须是一个ostream对象,右侧的运算对象是要打印的值。此运算符将给定的值写到给定的ostream对象中。输出运算符的计算结果就是其左侧运算对象。
对于语句std::cout << "Enter two number:" << std::endl;
,使用了两次 << 运算符,因为此运算符返回其左侧的运算对象,因此第一个运算符的结果成为了第二个运算符。这样,就可以将输出请求连接起来。
链中每个运算符的左侧运算对象都是相同的(本例中是:std::cout)。因此可以用两条语句生成相同的输出。
- std::cout << "Enter two number:" << std::endl;
- <=> (std::cout << "Enter two number:") << std::endl;
- <=> std::cout << "Enter two number:"; std::cout << std::endl;
操纵符 endl
endl是一个被称为操纵符的特殊值。写入endl的效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中。缓冲刷新操作可以保证到目前为止的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。
在调试试我们经常添加打印语句。这类语句应保证“一直”刷新流。否则程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。
从流读取数据
初始化一个变量,就是在变量创建的同时为它赋予一个值。
>>(输入运算符)与输出运算符类似,接受一个istream作为其左侧运算对象,接受一个对象作为其右侧运算对象。他从给定的istream读入数据,并存入给定对象中。与输出运算符类似,输出运算符返回其左侧运算对象作为其计算结果。
同理有:
- std::cin >> v1 >> v2;
- <=> (std::cin >> v1) >> v2;
- <=> std::cin >> v1; std::cin >> v2;
使用标准库中的名字
- 前缀std::指出名字 cout 和 endl 是定义在名为 std 的命名空间(namespace)中的。命名空间可以帮助我们避免不经意的名字定义冲突,以及使用库中相同名字导致的冲突。
- 标准库定义的所有名字都在命名空间 std 中。
- 通过命名空间使用标准库的副作用:当使用标准库中的一个名字时,必须显式说明我们想使用来自命名空间 std 中的名字。
1.3 注释简介
注意:当修改代码时,不要忘记同时更新注释!
C++中注释种类
C++中有两种注释:单行注释和界定符对注释。
- 单行注释:以双斜线(//)开始,以换行符结束。这种注释可以包含任何文本。
- 注释界定符:继承自C语言的两个界定符(/* 和 */)。可以包含除 */ 以外的任何内容。
当注释界定符跨越多行时,最好能显式指出其内部的程序都属于多行注释的一部分。因此我们采用的风格是,注释内的每一行都以一个星号开头,从而指出整个范围都是多行注释的一部分。
/*
* 注释对/* */不能嵌套。
* “不能嵌套”几个字会被认为是源码,
* 像剩余程序一样处理。
*/
1.4 控制流
语句块:用花括号包围的零条或多余语句的序列。
1.4.1 while语句
while语句反复执行一段代码,直至给定条件为假为止。
while语句形式:
while(condition)
statement
1.4.2 for语句
由于在循环条件中检测变量、在循环体中递增变量的模式使用非常频繁,以至于C++语言专门定义了第二种循环语句——for语句
每个for语句都包括两部分:循环头和循环体。循环头控制循环体的执行次数,它由三部分组成:一个初始化语句(init-statement)、一个循环条件(condition)以及一个表达式(expression)。
- 初始化语句只在for循环入口处执行一次
- 循环体内次执行前先检查循环条件
- 表达式在for循环体之后执行
1.4.3 读取数量不定的输入数据
如 当我们预先不知道要对多少个数求和时,就需要不断读取数据直至没有新的输入为止:
#include<iostream>
int main()
{
int sum = 0, value = 0;
//读取数据直至遇到文件尾,计算所有读入的值的和
while(std::cin >> value) {
sum += value;
}
std::cout << "Sum is : " << sum << std::endl;
return 0;
}
当使用一个istream对象作为条件时,其效果是检测流的状态。
- 如果流是有效的,即流未遇到错误,那么检测成功。
- 当遇到
文件结束符(end-of-file)
,或遇到一个无效输入时(如读入的值不是一个整数),istream对象的状态会变为无效。处于无效状态的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;
else {
std::cout << currVal << "occurs"
<< cnt << "times" << std::endl;
currVal = val; //记住新值
cnt = 1; //重置计数器
}
}//while循环在这结束
//记住打印文件中最后一个值的个数
std::cout << currVal << "occurs"
<< cnt << "times" << std::endl;
}
return 0;
}
1.5 类简介
-
一个类定义了一个类型,以及与其关联的一组操作。
-
习惯上,头文件根据其中定义的类的名字来命名。通常使用
.h
作为头文件的后缀,也有一些程序员习惯用.H、.hpp或.hxx
。 -
标准库头文件通常不带后缀。
-
编译器一般不关心头文件名的形式,但有的IDE对此有特定要求。
1.5.1 Sales_item类
每个类都定义了一个新的类型,其类型名就是类名。因此,Sales_item 类定义了一个名为Sales_item 的类型。
语句:Sales_item item;
想表达的item是一个Sales_item类型的对象。通常将“一个Sales_item类型的对象” 简单说成”一个Sales_item对象“ 或更简单的”一个Sales_item“。
//Sales_item类
class Sales_item {
// these declarations are explained section 7.2.1, p. 270
// and in chapter 14, pages 557, 558, 561
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool
operator==(const Sales_item&, const Sales_item&);
public:
// constructors are explained in section 7.1.4, pages 262 - 265
// default constructor needed to initialize members of built-in type
#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)
Sales_item() = default;
#else
Sales_item() : units_sold(0), revenue(0.0) { }
#endif
Sales_item(const std::string &book) :
bookNo(book), units_sold(0), revenue(0.0) { }
Sales_item(std::istream &is) { is >> *this; }
public:
// operations on Sales_item objects
// member binary operator: left-hand operand bound to implicit this pointer
Sales_item& operator+=(const Sales_item&);
// operations on Sales_item objects
std::string isbn() const { return bookNo; }
double avg_price() const;
// private members as before
private:
std::string bookNo; // implicitly initialized to the empty string
#ifdef IN_CLASS_INITS
unsigned units_sold = 0; // explicitly initialized
double revenue = 0.0;
#else
unsigned units_sold;
double revenue;
#endif
};
类定义了行为:
一般而言,类的作者决定了类类型对象上可以使用的所有操作(如Sales_item的作者定义了类对象可以执行的所有动作)。
1.5.2 初始成员函数
代码:
#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语句检测条件item1.isbn() == item2.isbn(),调用名为 isbn 的成员函数。
-
成员函数是定义为类的一部分的函数,有时也被称为
方法
。 -
使用点运算符(.)来表达 某个对象 的 成员 。点运算符只能用于类类型的对象。其左侧运算对象必须是一个类类型的对象,右侧运算对象必须是该类型的一个成员名,运算结果为右侧运算对象指定的成员。
-
使用调用运算符( () )来调用一个函数。
1.6 书店程序
见P21~ P22