《C++ Primer》个人学习笔记,主要对书中内容进行删节提炼,同时记录可供复制使用程序源码。如有错误欢迎指出。
一、编写一个简单的C++程序
每个C++程序都包含了一个或多个函数,其中一个必须命名为main。下面展示了一个最简单的C++程序:
int main()
/*
* int 代表了函数的返回类型(整数型)
* main 代表了函数名
* () 代表了形参列表(为空)
*/
{
return 0;
}
// {}所包含的即为函数体
尽管上面main函数什么也没有做,但是完整包含了C++程序定义的四个部分,即返回类型(return type)、函数名(function name)、形参列表(parameter list)和函数体(function body)。
return 0是该函数中的唯一一条语句,代表结束函数的执行。当return语句返回值时,返回类型必须与函数定义中的返回类型一致,在本例中,返回值0具有int类型。一般来说,返回值0则代表函数执行成功,若为-1或其他非0定义值,则用来指出函数运行错误类型。
此外,在上述代码实例中还涉及到C++中的注释写法。C++的注释包含两种一是以“//”开始的单行注释,二是用“/* */”所包围的多行注释。在多行注释中,每一行前使用*可以更加清楚直观地显示出注释内容,但这并不是强制要求的。此外,值得注意的是在使用“/* */”界定符进行注释时,不可在其中再次嵌套其他注释,避免引起歧义。以下为错误实例:
/*
* 注释对/* */不能嵌套
* “不能嵌套”几个字会被当做源码
*/
二、输入与输出(IO)
C++本身并没有自带的输入与输出语句,而需要使用标准库(standard library)来实现IO操作,其中最广泛使用的为iostream标准库。下面,我们在进一步丰富上一章节所提到的main函数,实现简单的加和功能。
#include <iostream>
use namespace std;
/*
*这里首先调用了iostream这个函数库
*并使用标准命名空间
*/
int main()
/*
*实现两个数的加和需要用到输入和输出两个操作
*cin为标准输入,>>为输入运算符
*cout为标准输出,<<为输出运算符
*/
{
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;
}
程序运行之后,例如用户键入3 7,回车之后,程序则会产生如下输出:
The sum of 3 and 7 is 10
程序的第一行指出了一个头文件,告诉编译器我们想要使用iostream库,我们一般将一个程序所有的#include语句统一写在源文件的开始位置。C++标准库中定义了4个IO对象,分别为标准输入cin(读作see-in),标准输出cout(读作see-out),标准错误cerr(读作see-err)和一般信息输出clog(读作see-log)。
同时,我们注意到在输入与输出语句中,还出现了两种方向的尖角括号,分别为输出运算符(<<)和输入运算符(>>)。两种运算符逻辑较为类似,这里以输出运算符为例进行解释。输出运算符接受两个运算对象:左侧对象必须是一个ostream对象,右侧对象是要打印的值,由此将给定的值写到给定的ostream对象中。原函数中第一行的语句其实等价于:
(std::cout << "Enter two numbers:") << std::endl;
或者:
std::cout << "Enter two numbers:";
std::cout << std::endl;
在这里还发现endl这个特殊值,即操纵符(manipulator),效果为结束当前执行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中。缓冲区刷新操作可以保证到目前为止程序所产生的所有输出都真实写入输出流中,而不是仅停留在内存中等待写入流。
“程序员常常在调试时添加打印语句。这类语句应该保证一直刷新流。否则,如果程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。”
三、控制流
默认情况下,程序是按照语句先后顺序逐条执行的。但是在解决实际问题时,往往需要写出更加复杂的执行路径,C++语言为我们提供了多种不同的控制流语句。
1.while语句
while语句反复执行一段代码,直至给定条件为假为止。例如:
#include <iostream>
int main()
{
int sum = 0, val =1;
while (val <= 10) //只要val小于等于10为真,则循环体一直运行
{
sum += val; //将sum + val 赋给 sum,等价于sum = sum + val
++val; // 将val + 1 赋给 val,等价于 val = val + 1
}
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0 ;
}
我们执行这个程序,它会打印出:
Sum of 1 to 10 inclusive is 55
与前面程序不同的点在于,该程序使用了while语句,其一般语句形式为:
while (condition)
{
statement
}
while语句的运行过程为交替监测condition和执行statement,直到condition为假。需要注意的是,在每一轮while循环中,是先监测,再执行。
同时,本例中的statement是一个由两个语句组成的语句块(block),即用花括号所包围的部分。语句块也是语句的一种,在任何要求使用语句的地方都可以使用语句块,便于读者理解程序。
2.for语句
由于while语句在条件语句中检测变量,在循环体中递增变量的操作较为频繁,因此C++提供了一种简化方式,即for语句。下面我们使用for语句对刚刚的程序进行改写:
#include <iostream>
int main()
{
int sum = 0;
for (int val = 1; val <= 10; ++val) //循环条件
sum += val; //将sum + val 赋给sum
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
我们运行这个程序,最终的打印结果与前例相同。
for语句的一般形式为:
for (init-statement; condition; expression)
{
statement
}
for的循环头中包含三个部分,即初始化语句(init-statement)、循环条件(condition)和表达式(expression)。以该程序为例,for语句的执行过程为:
1. 创建变量val,初始化为1;
2. 检测val是否小于等于10。若为真,执行for循环体;若为假,跳出for循环,执行return 0;
3. 将val+1赋给val;
3. 重复第二步过程。
3.读取数据量不定的输入数据
前面提到的while循环与for循环解决了数据累加的问题,但是实际情况中,我们经常会遇到需要由用户来输入数据进行求和或计算,程序在编写时并不知道有多少组输入数据,这就需要程序对于输入操作进行判断。例如:
#include <iostream>
int main()
{
int sum = 0, value = 0;
while (std::cin >> value) //读取数据直到遇到文件尾
sum += value:
std::cout << "Sum is:" << sum << std::endl;
return 0;
}
例如我们执行程序并输入
3 4 5 6
则程序会输出:
Sum is: 18
在该例中,我们使用value保存用户输入的每个数,数据的读取操作是在while的循环条件中实现的。因此该循环的检测对象即为std::cin,若用户输入的数据符合定义的int类型则监测结果一直为真。此外,当程序遇到文件结束符(end-of-file)时,也会结束循环。
在Windows系统中,输入文件结束符的方法是Ctrl+Z;
在UNIX/Mac系统中,输入文件结束符的方式是Ctrl+D。
4.if语句
与大多数语言一样,C++也提供了if语句支持条件执行,以下为书中给出的统计输入词连续出现个数的程序代码:
#include <iostream>
int main()
{
int currVal = 0, val = 0; //currVal是我们正在统计的数,将读入的新值存入val
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;
} //最外层if语句结束
return 0;
}
如果我们输入:
42 42 42 42 42 55 55 62 100 100
则程序会输出:
42 occures 5 times
55 occures 2 times
62 occures 1 times
100 occures 3 times
尽管书中所给出的统计程序直观地展示了if语句的用法,但是从程序功能上来看并不完善。例如,当用户输入一组相同的数时,则while语句以及嵌套的if语句的条件始终为真,程序既不会输出统计结果,也不会自动跳出循环,需要用户手动键入文件结束符。
四、类简介
类机制是C++最终要的特性之一,一个类定义了一个类型,以及与其关联的一组操作。C++最初的一个设计焦点就是能定义使用上项内置类型一样自然的类类型(class type)。
书中许多代码中均涉及到了一个名为Sales_item的类,其具体代码并未在书中给出,笔者在这里列出网络资源中提供的相关代码。在初始阶段,并不用关心其如何实现,只需要了解其支持哪些操作。
#ifndef SALESITEM_H
// we're here only if SALESITEM_H has not yet been defined
#define SALESITEM_H
// Definition of Sales_item class and related functions goes here
#include <iostream>
#include <string>
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
Sales_item() = default;
Sales_item(const std::string &book): bookNo(book) { }
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
unsigned units_sold = 0; // explicitly initialized
double revenue = 0.0;
};
// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{ return lhs.isbn() == rhs.isbn(); }
// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);
inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
// must be made a friend of Sales_item
return lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue &&
lhs.isbn() == rhs.isbn();
}
inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
return !(lhs == rhs); // != defined in terms of operator==
}
// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// assumes that both objects refer to the same ISBN
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs); // copy (|lhs|) into a local object that we'll return
ret += rhs; // add in the contents of (|rhs|)
return ret; // return (|ret|) by value
}
std::istream&
operator>>(std::istream& in, Sales_item& s)
{
double price;
in >> s.bookNo >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}
std::ostream&
operator<<(std::ostream& out, const Sales_item& s)
{
out << s.isbn() << " " << s.units_sold << " "
<< s.revenue << " " << s.avg_price();
return out;
}
double Sales_item::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}
#endif
如前所见,为了使用标准库设施,我们必须包含相关的头文件。类似的,我们也需要通过头文件来访问为自己的应用程序所定义的类。一般使用.h作为文件名后缀。
简单来说,Sales_item类支持以下操作:
1. 通过Sales_item XX 定义一个Sales_item类型的对象;
2. 使用isbn函数从Sales_item对象中提取ISBN号;
3. 使用输入运算符(>>)和输出运算符(<<)读、写Sales_item对象;
4. 用赋值运算符(=)将一个Sales_item类型的对象对象赋值给另一个;
5. 用加法运算符(+)将两个相同的Sales_item类型的对象相加,得到的新的Sales_item对象,其ISBN号与两者相同,总销售额和售出册数则是两者对应值之和;
6. 使用复合赋值运算符(+=)将一个Sales_item类型对象加到另一个上。
下面我们使用Sales_item类进行一些简单的操作:
#include <iostream>
#include "Sales_item.h" //需要提前建立好文件
int main()
{
Sales_item book;
std::cin >> book;
std::cout << book << std::endl;
return 0;
}
当我们输入如下内容,即以每本24.99元价格出售了4本isbn号为0-201-70353-x的书:
0-201-70353-x 4 24.99
则输出结果如下,即总售出isbn号为0-201-70353-x书册数为4,总售价99.96元,每册数平均价格为24.99元:
0-201-70353-x 4 99.96 24.99
反复键入销售记录的过程非常乏味,我们可以使用文件重定向机制将标准输入和标准输出与命名文件关联起来:
$ SalesItems <infile >outfile
我们将上例程序编译名为SalesItem.exe的可执行文件,则上述命令会从一个名为infile的文件读取销售记录,并将输出结果写入到一个名为outfile的文件中,两个文件都位于当前目录。
五、成员函数简介
成员函数(member function)是定义为类的一部分的函数,有时被称为方法(method)。仍然以Sales_item类为例,给出一个调用成员函数的例子。
#include<iostream>
#include "Sales_item.h"
using namespace std;
int main()
{
Sales_item item1, item2;
std::cin >> item1 >> item2;
if (item1.isbn() == item2.isbn()) // 通过isbn号检查是否为同一本书
{
std::cout << item1 + item2 << std::endl;
return 0; //成功
}
else
{
std::cerr << "Data must refer to the same ISBN" << std::endl;
return -1; //失败
}
}
上例调用了名为isbn的成员函数。我们通常以一个类对象的名义来调用成员函数,使用点运算符来表达我们需要“名为item1的对象的isbn成员”,点运算符左侧的运算对象必须是一个类类型的对象,右侧必须是该类型的一个成员名。而括号则为调用运算符,用于放置实参(可为空)。
总结
第一章节主要讲解了C++中基础的程序结构,通过本章学习可以基本进行简单C++程序的编译。