20220222
C++ Primer Plus - 第五、六章
- 附录:
- 第1-3章
- 第4章 复合类型
- 第5章 循环和关系表达式
-
- 5.1 for循环
-
- 5.1.1 for循环的组成部分---表达式和语句的区别、输出true和false
- 5.1.2 回到for循环---const int SIZE = 10;为什么书上的程序敲出来会报错???
- 5.1.6 副作用和顺序点
- 5.1.7 前缀格式和后缀格式---i++ 和 ++i 的效率(内置类型和用户自定义类型)
- 5.1.8 递增/递减运算符和指针---优先级:后缀 > 前缀 == 解引用 pt++、++* pt 、*++pt、(*pt)++
- 5.1.10 复合语句(语句块)
- 5.1.11 其他语法技巧---逗号运算符
- 5.1.12 关系表达式---优先级:算术 > 关系运算符(>、<、=、>=、<=、!=、==)
- 5.1.13 赋值、比较和可能犯的错误---判断是否相等的时候用==,而不是=
- 5.2 while循环
- 5.2.2 类型别名(为已有类型建立一个新名称)---typedef 优于 #define
- 5.3 do-while循环
- 5.4 基于范围的for循环(C++11)
- 5.5 循环和文本输入
- 5.6 嵌套循环和二维数组
- 5.7 总结
- 5.8 复习题
- 5.9 编程练习
- 第6章 分支语句和逻辑运算符
- 第七章 函数---C++的编程模块
附录:
1.ASCII码字符对照表
2.C++ 运算符优先级
第1-3章
第4章 复合类型
第5章 循环和关系表达式
5.1 for循环
5.1.1 for循环的组成部分—表达式和语句的区别、输出true和false
①输出true和false;
②表达式age = 100
和 语句age = 100;
的区别;
5.1.2 回到for循环—const int SIZE = 10;为什么书上的程序敲出来会报错???
①
const int SIZE = 10;
代码:
#include<iostream>
using namespace std;
const int size = 10;
int main() {
//5.1.2
int arr1[size];//size不明确
for (int i = 0; i < size; i++) {
//size不明确
cout << i + 1 << " ";
}
system("pause");
return 0;
}
5.1.6 副作用和顺序点
①
while(guests++ < 10)
cout << guests << endl;
程序执行的顺序是:
先是guests < 10
;
然后guests++
;
最后cout << guests
。
②
y = (4 + x++) + (6 + x++);
C++没有规定是在计算每个子表达式之后将x的值递增,还是在整个表达式计算完毕后才将x的值递增,所以应避免使用上面的写法。
5.1.7 前缀格式和后缀格式—i++ 和 ++i 的效率(内置类型和用户自定义类型)
①
for(int i = 0; i < 10; i++)//后缀格式
和
for(int i = 0; i < 10; ++i)//前缀格式
对于内置类型来说,上面两种格式没有差别;
但对于用户自定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高,原因见下面:
这篇博客解释的很清楚:C++ 递增运算符:前置++和后置++的区别
首先说下结论:迭代器和其他模板对象应该使用前缀形式 (++i) 的自增,,自减运算符,因为前置自增 (++i) 通常要比后置自增 (i++) 效率更高。
前置++和后置++,有4点不同:返回类型不同、形参不同、代码不同、效率不同:
int main()
{
Age a;
(a++)++; //编译错误
++(a++); //编译错误
a++ = 1; //编译错误
(++a)++; //OK
++(++a); //OK
++a = 1; //OK
}
- 返回类型不同
前置++的返回类型是左值引用,后置++的返回类型const右值。而左值和右值,决定了前置++和后置++的用法。
问:++a的返回类型为什么是引用呢?
答:为了与内置类型的行为保持一致。前置++返回的总是被自增的对象本身。因此,++(++a)的效果就是a被自增两次。 - 形参的区别
前置++没有形参,而后置++有一个int
形参,但是该形参也没有被用到。很奇怪,难道有什么特殊的用意?
其实也没有特殊的用意,只是为了绕过语法的限制。
前置++与后置++的操作符重载函数,函数原型必须不同,否则就违反了“重载函数必须拥有不同的函数原型”的语法规定。
虽然前置++与后置++的返回类型不同,但是返回类型不属于函数原型。为了绕过语法限制,只好给后置++增加了一个int
形参。
原因就是这么简单,真的没其他特殊用意。其实,给前置++增加形参也可以;增加一个double
形参只要不是int
形参就可以。只是,当时就这么决定了。 - 代码实现的区别
前置++的实现比较简单,自增之后,将*this
返回即可。需要注意的是,一定要返回*this
。
后置++的实现稍微麻烦一些。因为要返回自增之前的对象,所以先将对象拷贝一份,再进行自增,最后返回那个拷贝。 - 效率的区别
如果不需要返回自增之前的值,那么前置++和后置++的计算效果都一样。但是,我们仍然应该优先使用前置++,尤其是对于用户自定义类型的自增操作。
前置++的效率更高,理由是:后置++会生成临时对象。
C++Primer中(P132)有这样简介的描述:
前置版本将对象本身作为左值返回,后置版本则将原始对象的副本作为右值返回,两种运算符必须作用于左值运算对象。后置版本需要拷贝副本,所以会影响程序的性能
在C++笔记10:运算符重载中有为自定义数据类型重载++运算符的内容 ,具体代码就是上面的第3点 代码实现的区别所说的:
用类成员函数实现的
//重载++运算符:
//前置++:
MyInt& operator++() {
//为什么要返回引用,是为了一直对一个数据(这里是自定义数据---类)进行递增操作
num++;//先++
return *this;//再返回对象本身this
}
//后置++:
MyInt operator++(int) {
//这里的int代表占位参数,可以用于区分前置和后置递增,
//141和134两行属于重载,重载的条件是参数的个数、顺序、类型不同,
//所以就加了个int,而且编译器只认int,其他的都不行,只有int可以区分开类++和++类
MyInt temp = *this;//先记录对象本身this
num++;//后++
return temp;//最后将记录的返回,注意这里返回的是值,不是引用&
}
5.1.8 递增/递减运算符和指针—优先级:后缀 > 前缀 == 解引用 pt++、++* pt 、*++pt、(*pt)++
①
//前缀递增、前缀递减、解引用运算符优先级相同;
//后缀递增、后缀递减优先级相同,且高于前缀和解引用运算符。
//5.1.8
//前缀递增、前缀递减、解引用运算符优先级相同:
//后缀递增、后缀递减优先级相同,且高于前缀和解引用运算符。
double arr[5] = {
11.1,22.2,33.3,44.4,55.5 };
double* pt = arr;
for (int i = 0; i < 5; i++) {
cout << *(pt + i) << ", ";
}
cout << "\n" << endl; //11.1, 22.2, 33.3, 44.4, 55.5
cout << "*pt = " << *pt << "\n" << endl;//11.1
//前缀递增 和 解引用运算符:
cout << "*++pt = " << *++pt << endl;//22.2 相当于*(++pt)
cout << "*pt = " << *pt << endl;//22.2
cout << "++*pt = " << ++*pt << endl;//23.2 相当于++(*pt)
cout << "*pt = " << *pt << endl;//23.2
cout << endl;
//后缀递增 和 解引用运算符:
cout << "(*pt)++ = " << (*pt)++ << endl;//23.2
cout << "*pt = " << *pt << endl;//24.2
cout << "*pt++ = " << *pt++ << endl;//24.2 相当于*(pt++)
cout << "*pt = " << *pt << endl;//33.3
cout << endl;
//前缀递增、后缀递增、后缀递减、前缀递减:
++pt;
cout << "*pt = " << *pt << endl;//44.4
pt++;
cout << "*pt = " << *pt << endl;//55.5
pt--;
cout << "*pt = " << *pt << endl;//44.4
--pt;
cout << "*pt = " << *pt << endl;//33.3
cout << endl;
for (int i = 0; i < 5; i++) {
cout << arr[i] << ", ";
}
cout << endl; //11.1, 24.2, 33.3, 44.4, 55.5
结果:
5.1.10 复合语句(语句块)
5.1.11 其他语法技巧—逗号运算符
5.1.12 关系表达式—优先级:算术 > 关系运算符(>、<、=、>=、<=、!=、==)
①关系运算符的优先级比算术运算符低;
②关系表达式对两个值进行比较,常被用作循环测试条件。
5.1.13 赋值、比较和可能犯的错误—判断是否相等的时候用==,而不是=
对于第二种情况,书中的解释:由于它将20赋给数组元素quizscores[i],因此表达式始终为非零,所以始终为true。
也可以看看5.1.1.的②:表达式 和 语句
。
5.2 while循环
5.2.2 编写延时循环—#include< ctime>
#include<iostream>
#include<ctime>
using namespace std;
int main() {
cout << "请输入您想要延迟的时长(单位:秒/s):";
float sec;
cin >> sec;
clock_t delay = sec * CLOCKS_PER_SEC;//5*1000
clock_t start = clock();
while (clock() - start < delay) {
//if ((clock() - start) > 0 && (clock() - start) % CLOCKS_PER_SEC == 0)//1000的整数倍:1000,2000,3000...
// cout << "第 " << (clock() - start) / CLOCKS_PER_SEC << " 秒" << endl;
}
cout << "clock() = "<< clock()<<",共计时 " << (clock() - start) / CLOCKS_PER_SEC << " 秒。" << endl;
system("pause");
return 0;
}
5.2.2 类型别名(为已有类型建立一个新名称)—typedef 优于 #define
①#define(没分号)
#define SIZE 100 //SIZE是100的别名
#define BYTE char //BYTE 是 char类型 的别名,预处理器将在编译程序时用char 替换所有的BYTE
注意:当需要声明一系列变量时,这种方法不适用:
②typedef (有分号)
typedef char byte; //byte 是 char类型 的别名
typedef char* byte_pointer; //byte_pointer 是 char指针 的别名
typedef int word; //word 作为 int 的别名,那么cout将把word类型的值视为int类型。
注意:
typedef不会创建新类型,而只是为已有的类型建立一个新名称。
5.3 do-while循环
do-while循环是出口条件(exit condition)循环:先执行循环体,然后再判定测试表达式,决定是否继续执行循环;
for循环和while循环是一种入口条件循环:如果测试条件为非零,则进入循环执行循环体;如果测试条件一开始就是false,则不会进入循环。
5.4 基于范围的for循环(C++11)
5.5 循环和文本输入
5.5.1 5.5.2 5.5.3 — 逐个字符char ch;
输入
5.5.1 使用原始的cin进行输入
①直接用cin << ch;
会忽略空格和换行符;
②为什么#后面还能输入字符?—见下图
5.5.2 使用cin.get(char)进行补救
①cin所属的istream类
的一个成员函数cin.get(ch)
可以读取输入的下一个字符(包括空格符),然后赋值给变量ch;
②#后面的内容依然会被缓冲,因此输入的字符个数仍可能比最终到达程序的要多。
思考:
//5.5.1 5.5.2
char ch;
int count = 0;
//cin >> ch;//5.5.1
cin.get(ch);//5.5.2
while (ch != '#') {
//#作为结束符
cout << ch;
count++;
//cin >> ch;//5.5.1
cin.get(ch);//5.5.2
}
cout << "共输入了 " << count << " 个字符。" << endl;
5.5.3 使用哪一个cin.get()
— 函数重载
到目前见到了三种cin.get();
:
- cin.get();//没有参数
- cin.get(ch);//一个参数char类型
- cin.get(name,20);//两个参数:字符数组名,一次接收的字符数上限(③见第四章的 4.13 编程练习 的 第1题 )
这就是函数重载,函数重载允许创建多个同名函数,条件是他们的参数列表不同。
☆☆☆函数重载的应用:(1和2适合读单个字符,3适合读一整行字符串)
char ch; ch = cin.get();
//没有参数,返回值为输入中的下一个字符char ch; cin.get(ch);
//一个参数char类型,参数是引用类型,直接将读到的字符赋给参数chchar name[20]; cin.get(name,20);
//两个参数:字符数组名,一次接收的字符数上限(③见第四章的 4.13 编程练习 的 第1题 )
5.5.4 5.5.5 —判断是否到达文件尾EOF
5.5.4和5.5.5分别介绍了2种和1种方法,几种方法都可行,最终推荐5.5.4的程序2。
5.5.4 cin.fail() == false 和 cin.eof() == false
①
二者都返回的是bool值:遇到EOF就返回true
,否则范围false
;
用cin.fail()
比 cin.eof()
多,因为前者可用于更多的实现中。
②
通过键盘模拟文件尾条件(EOF):Ctrl+Z
和 Enter
。
示例:
程序1:
//5.5.4 5.5.5
char ch;
int count = 0;
cin.get(ch);
while (cin.fail() == false) {
//判断是否到达文件尾 cin.eof() == false也可以
cout << ch;
count++;
cin.get(ch);
}
cout << "共输入了 " << count << " 个字符。" << endl;
程序语句进一步精简:
具体的解释:
结论:
所以说,判断是否到达文件尾,可以直接用while(cin)
,这比cin.fail() == false)
和 cin.eof() == false
更通用,因为它可以检测到其他失败原因,如磁盘故障。
程序2:
//程序2:
char ch;
int count = 0;
while (cin.get(ch)) {
//判断是否到达文件尾
cout << ch;
count++;
}
cout << "共输入了 " << count << " 个字符。" << endl;
5.5.5 (ch = cin.get()) != EOF
程序3:
//程序3:
int ch;
int count = 0;
while ((ch = cin.get()) != EOF) {
cout.put(char(ch));
count++;
}
cout << "共输入了 " << count << " 个字符。" << endl;
程序3和程序1、2的区别在于:①ch是int类型;②循环条件;③输出的语句
为什么是要用int ch;来接收cin.get()的返回值?
分析一下循环条件(ch = cin.get()) != EOF
:
最后比较下5.5.4和5.5.5的差别:
5.6 嵌套循环和二维数组
C++没有提供二维数组类型,但用户可以创建每个元素本身就是数组的数组。
区分 char数组/字符数组、字符串指针数组、string对象数组 ☆☆☆
//5.6
//字符串指针数组:
const char* cities[5] = {
"beijing","tianjin","xi'an","nanjing","xiamen" };
//字符数组:
char cities2[5][20]{