C++ 学习记录(二)

C++ 学习过程中的程序及每章个人总结,此处梳理五到六章,重点在于几种主要结构的使用。

Posted by R on 2023-11-29

五六章的学习结束以后对于程序的各个部分结构就基本上清晰明确了,同时 c++ 的功能庞大性也在这两章陡然明显起来,此外对于文件 I/O 等的操作简单明确,对于输入输出具体函数的功能也越发详细起来。可以说,学到这两章结束,已经达到登堂标准,入室还需要后面两章关于函数的详细理解才可以达到。

对于 c++ 而言个人感觉这两章最重要的是掌握 cin 如何处理字符输入。

第五章:循环和关系表达式

for 循环

基本结构及步骤

for 循环所完成的基本步骤:初始值>>测试>>循环操作>>更新值

基本结构如下:

1
2
3
4
5
6
7
for (int i = 0; i < N; i++)
{
xxxxxx;
xxxxxx;
}
for (初始化;测试部分;更新操作)
循环体

其中,测试部分将结果强制转换为 bool 类型,更新操作在整个循环体完成后进行。

for 语句的控制部分由三个表达式构成,C++ 中表达式都有值,无论是赋值表达式还是运算表达式,诸如 int i = 0a + 3x = y = 0 这些都是表达式,并且只要表达式加上分号就是语句。但是并不是语句去掉分号就是表达式。如:int i 没有值,就不是表达式。

另外还有一点需要注意,在初始化表达式中声明的变量只存在于 for 语句中,循环结束就消失。(针对的是新式的 C++)

递增运算符和递减运算符

++i 表示先将 i 的值加 1 再计算表达式,而 i++ 表示先计算表达式再将 i 的值加 1,这是针对表达式而言。

顺序点是指程序执行过程中的一个点,再 C++ 中语句的分号就是一个顺序点,完整的表达式末尾也是一个顺序点。这意味着在执行下一个语句时,所有的运算符修改都会完成。

关于递增/减运算符和指针的问题,前缀和解除引用运算符优先级相同,而后缀优先级高于解除引用运算符,因此当递增/减运算符和指针搭配到一起时,结合方式上前缀右到左,后缀左到右。

逗号运算符

逗号运算符允许将两个表达式放到 C++ 句法只允许放一个的地方,也就是在 for 的更新操作部分同时让两个数更新十分方便。

逗号表达式的值是第二部分的值,同时逗号运算符的优先级最低。

算术运算符优先级 > 关系运算符 > 逗号运算符

C 风格字符串比较

1
word == "mate";

要想知道 word 中的字符串是不是数组,不能像上面一样直接进行比较,数组名是数组的地址,如上的比较也只是在确定它们是否存储在相同的地址上,应该使用 strcmp() 来比较,若字符串相同则返回零,第一个字符串排在第二个前返回负数,否则返回正数

虽然不能用关系运算符比较字符串,但是用来比较字符是可以的,因为字符属于整型。

while 循环

基本结构

while 循环的结构中只有测试条件和循环体:

1
2
while (测试条件)
循环体

因此循环体中的代码必须完成某种影响测试条件表达式的操作。

for 与 while

for 循环与 while 循环存在三个差别。首先,在 for 循环中省略了测试条件时,将认为条件为 true;其次,在 for 循环中,可使用初始化语句声明一个变量,但 while 不可以;最后,如果循环体中包括 continue 语句,continue 语句执行后,while 中后面可能存在的更新语句会被跳过,而 for 则一定会执行更新操作。

一般情况下,使用 for 为循环计数。在无法预先知道循环将执行的次数时,使用 while 循环。

在设计循环时,有几条指导原则:

  • 指定循环终止的条件
  • 在首次测试之前初始化条件
  • 在条件被再次测试之前更新条件

延时循环相关

clock()返回程序开始执行后所用的系统时间。

头文件 ctime 定义符号常量 CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。并且 ctimeclock_t 作为 clock()返回类型的别名,省去了无法明确 clock()返回类型的麻烦。

如下是使用 ctimeclock()创建延迟循环的例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#include<ctime>
int waiting()
{
using namespace std;
cout << "Enter the delay time,in seconds: ";
float secs;
cin >> secs;
clock_t delay = secs * CLOCKS_PER_SEC;
cout << "starting\a\n";
clock_t start = clock();
while (clock() - start < delay);
cout << "done \a\n";
return 0;
}
  • 类型别名:

    类型别名就是为类型建立别的名字,本质上还是这种类型。为 int 起一个 zheng 的类型别名,实际上还是在调用 int,但是这样可以优化一些潜在问题。

    一种方法是使用预处理器:

    1
    #define BYTE char

    预处理器将在编译程序时用 char 替换所有的 BYTE,就是说 BYTE 成为了 char 的另一个名字。

    另一种方法是使用关键字 typedef

    1
    typedef char BYTE;

    但是 typedef 可以处理更复杂的类型别名,这使得其成为一种更合适的选择。

do while 循环

该循环首先执行循环体,再判定测试表达式,决定是否应该继续执行循环:

1
2
3
do
循环体
while (测试条件);

循环和文本输入

cin 相关输入操作

读取文本操作,最关键的就是 cin 的部分,忽略空格与否,如何衔接、组合,这些都是问题。

cin >> ch(假设 ch 是 char 数组类型)在读取时会自动忽略空格和换行符,并且在换行符处停止。如果需要检查包括空格、制表符、换行符在内的所有字符cin.get(ch)可以出手,即使是空格它也会读取并且赋值给 ch。(此情况下 ch 是 char 类型)

针对 ch 是 char 数组的情况,cin.get()接受两个参数,数组名和 int 类型的长度,这一特征成为函数重载,也就是说同一个函数可能接受不同个参数,针对对应的参数有着不同的执行任务。cin.get()不接受任何参数,cin.get(ch)接受一个参数,cin.get(ch,Arsize)接受两个参数,它们各自的执行方式均不同。

相似的还有cin.getline()以及 cin.getline(ch,Arsize),对于以上种种有如下的一个简短总结:

在选择使用哪种输入方式时,要根据你的需求来决定。如果你需要读取整行文本并且可能包含空格,可以使用 getline()(getline 不需要指定最大字符数,会一直读取到换行符。)。如果你只需要读取一个特定类型的值,可以使用 cin >>。如果你需要逐字符读取,包括空格和换行符,可以使用 cin.get()cin.getline() 则是一种用于读取整行文本到字符数组的方式,通常在需要 C 风格字符串时使用。

文件尾条件

这里需要讲明 ch = cin.get()cin.get(ch) 的区别,前者是将 cin.get() 的值赋给了 ch,后者则是将 cin 对象的引用返回给了 ch。以例子为证,前者 cin.get()读取字符时会将对应字符的 ASCLL 值赋给 ch,也就是 int 类型;后者则是将字符存储在 ch 中。

EOF 表示一个文件的末尾,检测到 EOF 后,cin 将两位 eofbit 和 failbit 都设置为1,可以通过成员函数 eof() 来查看 eofbit 是否被设置;若检测到 EOF,cin.eof()将会返回 true,否则为 false。若 eofbit 或者 failbit 被设置为1,则 fail()返回 true。应该将 cin.eof()cin.fail()放在读取后。

cin.clear() 方法可以清除 EOF 标记,使输入继续进行。

嵌套循环和二维数组

二维数组声明格式:

1
int arr[4][5]

二维数组其实就相当于两层数组的嵌套,arr[0] 相当于 arr 数组的第一个元素,而 arr[0] 本身又是一个由 5 个 int 组成的数组,因此产生了行和列的概念,如下图所示:

1

初始化二维数组可以如下所示:

1
2
3
4
5
6
7
int arr[4][5] =
{
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5}
};

关于 char 二维数组和 char 指针数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char* cities[Cities] =
{
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
};
char cities[Cities][25] =
{
"Gribble City",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
};

指针数组只存储了5个字符串的地址,而二维数组将字符串复制了进来,内存上指针数组更具优势,但是如果考虑修改其中的字符串,二维数组是更好的选择。当然,string 对象数组是更方便的选择。

第六章:分支语句和逻辑运算符

if 语句

if 语句的基本结构就是测试条件加执行语句,如果有多个语句则用{}括起来,测试条件同样会被强制转换为 bool 值。

else if 语句只有在 if 或其他 else if 测试未通过的情况下才会执行,所以在某些情况下不用考虑会不会都执行的问题。

逻辑表达式

or —— ||,and —— &&,not —— !,两种表达式均可应用。

cctype 字符函数库

字符函数库简化了确定字符是否为大写字母、数字、标点符号等工作。

2

?: 运算符

该运算符被称为条件运算符,是 C++ 中唯一一个需要3个操作数的运算符,通用格式如下:

1
expression1 ? expression2 : expression3

如果分表达式1为 true,则整个表达式的值为分表达式2的值,反之则为分表达式3的值。

switch 语句

switch 语句的通用格式如下:

1
2
3
4
5
6
7
switch (integer-expression)
{
case label1 : statement(s)
case label2 : statement(s)
...
default : statement(s)
}

其中,integer-expression 必须是一个结果为整数值的表达式,int、char、枚举量均可。如果表达式不与任何标签匹配,则程序将跳转到标签为 default 的一行。此外,case 只是行标签,而不是选项之间的界线,也就是说执行完一个 case 后,程序不会在下一个 case 前自动停止,而是会顺序执行接下来的 case,如果想避免这种情况需要加入 break 语句。

语句中每个标签都是单独的值,涉及取值范围以及浮点测试等情况,应该使用 if else 语句。

break、continue 以及 错误处理

break 用于 switch 或循环中,使用 break 语句使得程序跳到 switch 或循环后面的语句处执行。

continue 用于循环中,让程序跳过剩下的语句,执行下次新的循环。(注意 for 和 while 在此处的区别)

程序发现用户输入错误内容时应该采取3个步骤:

  • 重置 cin 以接受新的输入
  • 删除错误输入
  • 提示用户再输入

重置 cin 时使用 clear() 语句,可以重置错误输入标记,同时也重置文件尾。错误处理例程如下:

1
2
3
4
5
6
7
while (!(cin >> golf[i]))
{
cin.clear();
while (cin.get() != '\n')
continue;
cout << "Please enter a number: ";
}

简单文件输入与输出

完成文件的输入输出需要包含头文件 fstream,使用其中的 ofstream 类和 ifstream 类,声明对象后使用 open() 方法将对象与实际文件关联起来。

文件输出主要步骤如下:

  • 包含头文件 fstream
  • 创建一个 ofstream 对象
  • 将该 ofstream 对象同一个文件关联起来
  • 就像使用 cout 一样使用 ofstream 对象

文件输入同理,但是文件输入的过程中有个步骤是检查文件是否被成功打开,使用的方法是 is_open()。