一、背景
我是一个 C/C++ 都没实战经验的前端开发,C 在几年前有了解过一些语法知识,下面我按照《C++ Primer第五版》第一章节,跟着一起实现一个书店程序。
注:本文要求至少有一门编程语言的基础。
我的电脑环境:
- mac
- vscode 1.88.1
二、编写一个简单的C++程序
C++ 必须通过调用一个 main 函数来运行,下面是一个最简单的 main 函数:
int main() {
return 0;
}
1. vscode 编译、运行程序
- 检查是否有 g++、clang++ 编译器,没有请自行安装
g++ --version
clang++ --version
2. vscode 下载 c/c++ 插件和 c/c++ extension pack 插件、code runner 插件、vscode-icons-mac 插件 3. 创建后缀为 .cpp 的文件在 vscode 打开 4. vscode 调试运行,点击右侧的调试按钮点击运行和调试箭头,选择 run code
练习 1.1:在vscode运行图示c++程序。
练习 1.2:将练习1的返回值改成-1,看下c++如何处理错误返回
三、初识输入输出
c++ 使用 iostream 库提供标准输入输出对象有以下 4 个:
- cin (发音 see-in):标准输入
- cout (发音 see-out):标准输出
- cerr (发音 see-err):输出警告和错误日志
- clog (发音 see-log):输出日志
练习1.3:写一个输出 hello world 的程序:
std::cout << "Hello, World!" << std::endl;
这段代码是用于输出 “Hello, World!” 到标准输出(通常是屏幕),并在末尾添加换行符。让我来解释一下每个部分的含义:
std::cout
: 是 C++ 标准库中用于输出的流对象,cout
是 “character output” 的缩写。<<
: 是 C++ 中的输出操作符,用于将数据插入到输出流中。"Hello, World!"
: 是要输出的字符串常量,它将被插入到cout
流中。<< std::endl;
: 是另一个输出操作符,endl
是C++标准库提供的换行符和刷新缓冲区的方法。
因此,整个表达式 std::cout << "Hello, World!" << std::endl;
的作用是将 “Hello, World!” 这个字符串输出到屏幕,并换行,确保输出的内容立即显示在屏幕上。
练习1.4:使用加法运算符+将2个数相加
#include <iostream> // 使用 iostream 库
int main() {
std::cout << "Enter two numbers:" << std::endl; // 输出"Enter two numbers:"
int v1 = 0, v2 = 0; // 定义变量 v1、v2
std::cin >> v1 >> v2; // 输入变量 v1、v2
std::cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << std::endl; // 打印 v1+v2 的值
return 0; // 返回0
}
注意:如果 vscode 终端无法输入,请修改 vscode 配置:
练习1.5:重写练习 1.4,将每个运算对象的打印操作放在一个独立的语句中
#include <iostream> // 引入输入输出流库
int main() {
// 输出提示信息,要求用户输入两个数字
std::cout << "Enter two numbers:" << std::endl;
// 定义两个整数变量 v1 和 v2,并初始化为 0
int v1 = 0, v2 = 0;
// 从用户输入读取两个整数值
std::cin >> v1 >> v2;
// 输出提示信息
std::cout << "The product of ";
std::cout << v1;
std::cout << " and ";
std::cout << v2;
std::cout << " is ";
// 计算并输出两个数字的和(应该是乘积的计算错误)
std::cout << v1 + v2;
// 输出换行符
std::cout << std::endl;
return 0; // 返回 0 表示程序正常结束
}
四、注释简介
// 单行注释
/*
* 多行注释
* 多行注释
*/
练习1.6:解释下面程序片段是否合法。
std::cout << "The sum of " << v1;
<< " and " << v2;
<< " is " << v1 + v2 << std::endl;
解:程序不合法,第二行有多余的分号,修改如下:
std::cout << "The sum of " << v1
<< " and " << v2
<< " is " << v1 + v2 << std::endl;
练习1.7:编译一个包含不正确的嵌套注释的程序,观察编译器返回的错误信息。
/* 正常注释 /* 嵌套注释 */ 正常注释*/
输出:
1-7.cpp:1:17: warning: '/*' within block comment [-Wcomment]
/* 正常注释 /* 嵌套注释 */ 正常注释*/
^
1-7.cpp:1:36: error: unknown type name '正常注释'
/* 正常注释 /* 嵌套注释 */ 正常注释*/
^
1-7.cpp:1:49: error: expected unqualified-id
/* 正常注释 /* 嵌套注释 */ 正常注释*/
^
1 warning and 2 errors generated.
练习1.8:指出下列哪些输出语句是合法的(如果有的话):
std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
std::cout << /* "*/" /* "/*" */;
第三行不合法,应该改成 std::cout << /* "*/ " */"
;
五、控制流
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;
}
练习1.9:将 50-100 的整数相加
#include <iostream>
int main() {
int sum=0,val=50;
// 只要val的值小于等于10,while循环就会持续进行
while (val <= 100) {
sum += val; // 将sum + val赋予sum
++val; // 将val加1
}
std::cout << "Sum of 50 to 100 inclusive is " << sum << std::endl;
return 0;
}
练习1.10:使用递减运算符在循环中按递减顺序打印出 10 到 0 之间的整数。
#include <iostream>
int main(){
int val = 10;
while (val >= 0)
{
std::cout << val << " "; // 输出当前值和一个空格
--val;
}
std::cout << std::endl; // 输出一个换行符
}
练习1.11:提示用户输入两个整数,打印出这两个整数所指定的范围内的所有整数。
#include <iostream> // 引入C++标准库中的输入输出流
int main () {
// 输出提示信息,要求用户输入两个数字
std::cout << "Enter two numbers:" << std::endl;
// 定义两个整数变量v1和v2,并初始化为0
int v1 = 0, v2 = 0;
// 从标准输入读取用户输入的两个整数值
std::cin >> v1 >> v2;
// 当v1小于等于v2时,执行循环体内的语句
while(v1 <= v2) {
// 输出当前的v1值
std::cout << v1 << " ";
// 将v1的值增加1
++v1;
}
// 输出换行符,使输出结果更加整洁
std::cout << std::endl;
// 程序正常结束,返回0
return 0;
}
2. for 循环
练习1.12:下面的 for 循环完成了什么功能?sum 的终值是多少?
int sum = 0;
for (int i = -100; i <= 100; ++i)
sum += i;
解:从 -100 加到 100,sum 的终值是 0。
练习1.13:使用 for 循环重做练习 1.10
#include <iostream> // 引入C++标准库中的输入输出流
int main () {
// 提示用户输入两个整数
std::cout << "Please input two integers: ";
// 定义两个整数变量a和b
int a, b;
// 从标准输入读取用户输入的两个整数值
std::cin >> a >> b;
// 使用for循环从a开始迭代到b(包括b),每次循环输出一个整数
for (int i = a; i <= b; ++i) {
std::cout << i << " "; // 输出当前整数值,后跟一个空格
}
// 输出换行符,使输出结果更加整洁
std::cout << std::endl;
// 程序正常结束,返回0
return 0;
}
练习1.14:对比 for 循环和 while 循环,两种形式的优缺点各是什么?
while 循环适用于条件判断,for 循环更加通用。
练习1.15:编译器可以检查出的错误有哪些?
编译器可以检查出的错误有:
- 语法错误
- 类型错误
- 声明错误
3. 读取数量不定的输入数据
练习1.16:编写程序,从 cin 读取一组数,输出其和。
#include <iostream> // 引入C++标准库中的输入输出流
int main()
{
// 定义一个整数变量sum,用于存储输入数字的总和,初始化为0
int sum = 0;
// 使用for循环读取输入的整数值,并累加到sum中
// 循环的条件是std::cin >> value的结果,表示只要有有效输入,循环就会继续
for (int value = 0; std::cin >> value; )
sum += value; // 将读取的值累加到sum中
// 输出累加结果
std::cout << sum << std::endl;
// 程序正常结束,返回0
return 0;
}
4. if 语句
用 if 语句写 1 个程序,统计在输入中每个值连续出现了多少次。
#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 {
// 否则输出当前数的统计结果,然后更新 currVal 和 cnt
std::cout << currVal << " occurs " << cnt << " times" << std::endl;
currVal = val;
cnt = 1;
}
}
// 输出最后一个数的统计结果
std::cout << currVal << " occurs " << cnt << " times" << std::endl;
}
return 0;
}
练习1.17:如果输入的所有值都是相等的,本节的程序会输出什么?如果没有重复值,输出又会是怎样的?
解:
- 所有值相等:程序会输出一个行,显示所有输入的值及其总出现次数。
- 没有重复值:程序会为每个输入值输出一行,显示该值及其出现次数为 1。
练习1.18:编译并运行本节的程序,给它输入全都相等的值。再次运行程序,输入没有重复的值。
解:
全部重复:
1 1 1 1 1
1 occurs 5 times
没有重复:
1 2 3 4 5
1 occurs 1 times
2 occurs 1 times
3 occurs 1 times
4 occurs 1 times
5 occurs 1 times
练习1.19:修改练习 1.11 所编写的程序(打印一个范围内的数),使其能处理用户输入的第一个数比第二个数小的情况。
解:
#include <iostream> // 引入C++标准库中的输入输出流
int main()
{
// 定义两个整数变量 start 和 end,用于存储输入的范围
int start = 0, end = 0;
// 提示用户输入两个整数
std::cout << "Please input two num: ";
// 从标准输入读取两个整数,分别赋值给 start 和 end
std::cin >> start >> end;
// 检查 start 是否小于或等于 end
if (start <= end) {
// 如果 start 小于或等于 end,使用 while 循环输出从 start 到 end 的所有整数
while (start <= end) {
std::cout << start << " "; // 输出当前整数值,后跟一个空格
++start; // 将 start 递增 1
}
// 输出换行符,以确保输出结果整洁
std::cout << std::endl;
}
else {
// 如果 start 大于 end,输出错误信息
std::cout << "start should be smaller than end !!!";
}
return 0; // 程序正常结束,返回0
}
六、书店程序
在 C++ 中,通过定义一个类来定义自己的数据结构。 我们需要使用头文件来访问类,通常使用 .h 作为头文件的后缀。
书店程序就用 Sales_item 类实现,Sales_item 类表示一本书的总销售额、售出册数和平均售价。
Sales_item 类应该有以下功能:
- 调用一个名为 isbn 的函数从一个 sales_item 对象中提取 ISBN 书号
- 用输入运算符 (>>) 和输出还算符 (<<) 读、写 sales_item 类型的对象算符
- 将一个 Sales_item 对象的值赋子另一个 sales_item 对象。
- 用加法运算符 (+) 将两个 Sales_item 对象相加。两个对象必须表示同一本书(相 同的 ISBN)。加法结果是一个新的 sales_item 对象,其 ISBN 与两个运算对象相同,而其总销售额和售出册数则是两个运算对象的对应值之和。
- 使用复合赋值运算符 (+=) 将一个 Sales_item 对象加到另一个对象上
1. Sales_item 类
本例子已经提供了一个书店头文件 Sales_item.h
头文件,让 Sales_item 类有了以上功能:
#ifndef SALESITEM_H
// 我们在这里只有当 SALESITEM_H 尚未被定义时
#define SALESITEM_H
// Sales_item 类及相关函数的定义在这里
#include <iostream>
#include <string>
class Sales_item {
// 这些声明在第7.2.1节第270页和第14章第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:
// 构造函数在第7.1.4节第262 - 265页中解释
// 默认构造函数用于初始化内置类型的成员
Sales_item(): units_sold(0), revenue(0.0) { }
Sales_item(const std::string &book):
bookNo(book), units_sold(0), revenue(0.0) { }
Sales_item(std::istream &is) { is >> *this; }
public:
// 对 Sales_item 对象的操作
// 成员二元运算符:左操作数绑定到隐式 this 指针
Sales_item& operator+=(const Sales_item&);
// 对 Sales_item 对象的操作
std::string isbn() const { return bookNo; }
double avg_price() const; // 计算平均价格的函数
private:
std::string bookNo; // 隐式初始化为空字符串
unsigned units_sold;
double revenue;
};
// 用于第10章
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{ return lhs.isbn() == rhs.isbn(); }
// 非成员二元运算符:必须声明每个操作数的参数
Sales_item operator+(const Sales_item&, const Sales_item&);
inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
// 必须成为 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); // != 通过 == 定义
}
// 假设两个对象引用相同的 ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// 假设两个对象引用相同的 ISBN
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs); // 将 (lhs) 复制到一个本地对象中
ret += rhs; // 将 (rhs) 的内容添加到 (ret) 中
return ret; // 以值的形式返回 (ret)
}
std::istream&
operator>>(std::istream& in, Sales_item& s)
{
double price;
in >> s.bookNo >> s.units_sold >> price;
// 检查输入是否成功
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // 输入失败:将对象重置为默认状态
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
2. 读写类
一个简单的读写类的例子:
#include <iostream>
#include "Sales_item.h"
int main () {
// 定义一个Sales_item对象1
Sales_item book;
// 读取一个ISBN号、售出的册数以及销售价格
std::cin >> book;
// 输出ISBN号、售出的册数、总销售额和平均价格
std::cout << book << std::endl;
return 0;
}
练习1.20:读取一组书籍销售记录,将每条记录打印到标准输出上
#include <iostream>
#include "Sales_item.h"
int main()
{
for (Sales_item item; std::cin >> item; std::cout << item << std::endl);
return 0;
}
3. Sales_item 对象加法
练习1.21
编写程序,读取两个 ISBN 相同的 Sales_item 对象,输出他们的和。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item_1;
Sales_item item_2;
std::cin >> item_1; // 从标准输入读取第一个 Sales_item 对象的数据
std::cin >> item_2; // 从标准输入读取第二个 Sales_item 对象的数据
// 输出两个 Sales_item 对象的总销售情况
std::cout << "Sum of sale items: " << item_1 + item_2 << std::endl;
return 0; // 程序正常结束
}
练习1.22
编写程序,读取多个具有相同 ISBN 的销售记录,输出所有记录的和。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item sum_item; // 创建一个 Sales_item 对象,用于存储所有输入的销售项的总和
// 从标准输入读取一个 Sales_item 对象的数据,并存储在 sum_item 中
std::cin >> sum_item;
// 输出第一个 Sales_item 对象的数据
std::cout << "First sale item: " << sum_item << std::endl;
// 处理输入的其他 Sales_item 对象
// 使用循环来读取并累加销售项,直到输入流结束
for (Sales_item item; std::cin >> item; /* 空循环体 */)
{
// 输出当前读取的 Sales_item 对象的数据
std::cout << item << std::endl;
// 将当前 Sales_item 对象的值加到 sum_item 中
sum_item += item;
}
// 输出所有销售项的总和
std::cout << "Sum of sale items: " << sum_item << std::endl;
return 0; // 程序正常结束
}
练习1.23:编写程序,读取多条销售记录,并统计每个 ISBN(每本书)有几条销售记录。
练习1.24:输入表示多个 ISBN 的多条销售记录来测试上一个程序,每个 ISBN 的记录应该聚在一起。
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item total; // 创建一个 Sales_item 对象 `total` 用于存储累加后的销售项数据
// 尝试从标准输入读取第一个 Sales_item 对象的数据
if (std::cin >> total) {
Sales_item trans; // 创建一个 Sales_item 对象 `trans` 用于临时存储从输入中读取的销售项
// 使用 while 循环继续读取后续的 Sales_item 对象数据,直到输入流结束
while (std::cin >> trans) {
// 如果当前读取的销售项与 `total` 的 ISBN 相同,则累加 `trans` 到 `total`
if (total.isbn() == trans.isbn()) {
total += trans; // 累加当前销售项的数据到 `total`
}
else {
// 如果 ISBN 不同,则输出当前的 `total`,然后将 `total` 更新为新的销售项
std::cout << total << std::endl; // 输出当前累加结果
total = trans; // 将 `total` 更新为当前销售项 `trans`
}
}
// 循环结束后,输出最后一个累加的销售项数据
std::cout << total << std::endl;
}
else {
// 如果从输入流中无法读取数据,输出错误信息
std::cerr << "No data?!" << std::endl; // 使用 std::cerr 输出错误信息到标准错误流
return -1; // 返回 -1 表示程序执行失败
}
return 0; // 返回 0 表示程序正常结束
}
七、小结
这章内容足够让你掌握 C++ 的基础,能编译和运行一些简单的程序。
我们学会了怎么定义 main
函数,它就是操作系统运行你程序的入口。
接着,我们还了解了怎么定义变量,如何进行输入和输出操作,还学习了如何使用 if
、for
和 while
这些控制语句。
之后,我们还介绍了C++的一个核心特性——类。你会学到怎么创建和使用别人定义的类的对象。简单来说,就是怎么用这些现成的类来做各种事情。
接下来的章节,我们会深入讲解怎么自己定义类,让你能够根据自己的需求创建各种功能。