笔记不定期更新,读到哪里更到哪里:D
5.1 for循环
使用方法:
int i;//举例
for(i=0;i<5;i++) //控制部分,注意用分号分隔每个部分,结尾不需要分号
cout<<"哼哼啊啊啊啊啊"; //循环体注意缩进
格式:
for(初始化,循环测试,循环更新)
循环测试部分的参数会被强制转换为bool类型,非0转为true,0转为false(如果通过这种方式转换为ture将会导致死循环)
也就是说,循环测试部分的参数就像汇编语言中的cmp和je,先比较,比较完得出参数(个人理解,这部分可以划掉)。
循环将在循环测试部分变为0后停止。
1.表达式和语句
表达式的概念:任何值或任何有效的值和运算符的组合都是表达式
在C++中,每个表达式都有值(由此可判断是不是表达式),如:
22+22 是值为44的表达式。
像x<y这样的关系表达式将被判定为bool值,如:
int x;
x=100;
cout<<(x<3); //结果是0,因为cout在显示bool值前会将其转换为int类型
为了判定:表达式x=100,C++将会把100赋给x,
当判定一个表达式的值这种操作改变了内存中的数据的值时,我们说表达式有副作用。
我们在使用这种表达式时把赋值当成主要目的,但从C++的角度来看,判定才是主要目的。
判定x+15并没有副作用,因为它将计算出一个新的值,因此,一般有副作用的表达式都是赋值表达式。
将表达式转换成语句的方式:加上分号
如:
x=10 //是一个表达式
x=10; //是一个语句(表达式语句)
编译器允许无意义的语句,但可能会直接跳过执行。
2.非表达式和语句
任何表达式加上分号都能成为语句,但语句去掉分号不一定是表达式,如:
int toad; //它没有值,因此去掉分号也不是表达式
也并非所有语句都满足语句=表达式+分号,如for语句。
不是表达式就不能赋值和被赋值(因为不是表达式就没有值)
3.修改规则:
可以这么做:
for(int i=0;i<5;i++)
不要被书中的格式误导,不可以直接省略分号!!!(编程得来的经验)
在循环初始化部分声明的变量在循环结束后就会消失(但某些老式实现会保留变量)
5.1.2
阶乘的方法:
x*(x-1)! //零阶乘(0!)被定义为1
程序5.4:
由于前两个阶乘的结果都是1,所以在循环开始前直接为数组进行赋值。
表达式i<ArSize导致循环在数组索引到ArSize-1的地方停止
5.1.3
可以通过修改循环更新表达式来改变步长。
如:
int by=2;
for(int i=0,i<5,i=i+by) //注意赋值表达式的使用,i+by是无意义的
重点:循环更新表达式可以是任何有效的表达式
5.1.4
程序5.6:
循环初始化中int i=word.size() - 1 //这里的字符串是使用cin得来的,结尾包含空字符,所以最后-1
该程序使用了关系运算符大于或等于(>=)
5.1.5
粗略地讲,a++意味着使用a的当前值进行计算,然后再将a的值+1
++a则是先把a+1,再计算。
注意:不要在同一个语句内对同一个值递增或递减多次
5.1.6
副作用指的是在计算表达式时对某些东西进行了修改,
顺序点是程序执行过程中的一个点,在这里确保所有副作用都已完成。
语句中的每个分号都是顺序点,任何完整的表达式末尾也是顺序点。
y=(4+x++)+(6+x++);//分号是顺序点,因此这条语句在结束时才完成修改
5.1.7
如果递增运算符仅进行计算,没有被使用,则使用前缀格式和后缀格式没有任何区别,
但在用户定义的类中,前缀版本的效率比后缀版本的高。
5.1.8
将*和++同时用于指针时:
前缀递增递减和解除引用运算符的优先级相同,从右到左结合。
后缀递增递减的优先级比前缀递增递减高,从左到右结合。
5.1.9 组合赋值运算符
格式:
+=、-=、*=、/=、%=
含义:先将两个操作数进行计算,然后将得到的结果赋给左边的操作数
5.1.10
可以在循环体内包含任意条语句,方法是用花括号构造一个代码块(代码块被视为一条语句)
如果在代码块中声明变量,在代码块执行完毕后会释放该变量。
如果内外部语句块中有两个名称相同的变量,则在内部语句块结束之前,新变量将代替旧变量。
5.1.11 逗号运算符
逗号运算符允许两个表达式放到只允许放一个表达式的地方。
在同一语句内,逗号只能有一种含义(列表分隔符或运算符),例如:
for(int x=0,y=10; ......)不被允许,因为逗号已经是列表分割符了。
逗号运算符还有另外两个特性:
1.逗号运算符是一个顺序点
2.逗号表达式的值是逗号右边的值
编程测试结果:逗号表达式的值为最右边的值,无论有多少个逗号都是如此。
在所有运算符中,逗号运算符的优先级是最低的,例如:
x=7,240; //结果为7,240不起作用
但这样的语句:
x=(7,240); //结果为240,原因为特性2
5.1.12 关系表达式
关系运算符可以对数字进行比较,for语句中就是靠关系运算符得出ture和false,
可以将它们用于:
数字、字符、string类型。不能用于C风格字符串。
编程测试结果:对C风格字符串直接使用关系运算符不会报错,但无法得到想要的结果。
5.1.14
因为C++将C风格字符串视为第一个字符的地址,因此无法使用==进行比较
如果想要比较C风格字符串,需要使用strcmp()函数,该函数接受两个字符串地址作为函数,
如果相同则返回0,如果第一个字符串的ASCII码比第二个字符串小,则返回负数,大则返回正数。
(ASCII码中,大写字母的编码比小写字母小)
这里顺便提一下之前学过的知识:C风格字符串的结尾是通过空字符而非数组长度确定的。
可以使用strcmp()函数在字符串相等时返回0(false)的特性用它来进行循环比较。
strcmp()的用法和汇编语言cmp有点像,如:
int str1=1,str2=0;
strcmp(str1,str2)>0; //将返回true
编程测试结果:strcmp仅能用来比较字符串,可以是数组、C风格字符串,但不能是string,因为
strcmp的参数标准格式为const char *,无法从string转换为char *,
而对数组名或字符串使用时,将会比较整个字符串,而非其元素首地址。
5.1.15
可以将关系运算符(如!=)用于string对象。例如:
word !="mate"
同时,可以使用数组表示法表示string对象的内容(老知识)
5.2 while循环
while是只有测试条件和循环体的for循环。
while(word[i] !='\0')
它的循环体必须修改i的值,否则将陷入死循环。
可以改成这样:
while(word[i])
它的效果不变,因为当i为空字符时,其编码为0,也就是false。
重点:不同于C风格字符串,string类不使用空字符来标记字符串末尾。
5.2.1
省略for循环的测试表达式将会导致死循环。
通常,程序员使用for循环,因为它能将所有相关的信息放到一个地方,
而选择在无法预先知道循环的次数时使用while循环。
记住,不要往循环的括号后面加分号,因为分号用来表示语句结束。
5.2.2
为了实现延时,可以使用循环,但是更好的方式是使用系统时间完成这个任务。
函数clock()可以完成这个任务,它返回开始执行后的系统时间,
为了控制其返回的时间单位和类型,需要使用头文件<ctime>中的定义。
它定义了一个符号常量:CLOCKS_PER_SEC,该常量为每秒钟包含的系统时间数,
将系统时间除以它将得到秒数,将秒数乘以它也能得到系统时间。
在ctime中定义了clock的返回类型(系统时间的类型)的别名clock_t,
使用clock_t创建变量将会被转换为头文件定义的系统时间的类型。
clock_t 变量名=输入的秒数*CLOCKS_PER_SEC;//可以将输入的秒数转换成系统时间
程序5.14解读:
clock_t delay =secs * CLOCKS+PER_SEC;//同上
clock_t start=clock(); //获得循环开始前的时间
while(clock() - start<delay); //用循环开始后的时间减去开始前的时间,算出已经循环了多久
类型别名:
C++为类型建立别名的方式有两种:
第一种:使用预处理器:#define 替换类型名 被替换的类型名
第二种:使用关键字:typedef 被替换的类型名 替换类型名
第二种方式同样可用于某种类型的指针,如:
typedef char * byte_pointer; //用byte_pointer代替char类型指针
使用tapedef进行置换可以在声明指针变量时无视必须在每个指针前都加上*的规则,如:
typedef type * char;
type pa,pb;
5.3 do while循环
do while循环不同于前面的两种循环是入口条件循环,它是出口条件循环,两者的差别是:
入口条件循环有可能一次都不会执行循环体内语句,出口条件循环则至少会执行一次循环体内语句。
do while循环多用于比较用户的输入。
5.4 基于范围的for循环(C++11)
这种循环简化了对数组或容器(模板)类反复执行同样的操作的循环,如下所示:
double prices[5]={4.99,10.99,6,87,7,99,8,49};
for(double x : prices) //冒号后面应为元素(使用{})或可代表元素的对象名(如数组名和指针)
cout<<x; //在这里,x代表prices的第一个元素
如果要用这种循环修改数组的内容,则需要使用引用变量(第8章再谈具体的):
for(double &x : prices)
5.5 循环和文本输入
5.5.1 cin进行输入
cin将会忽略换行符和空格,而且更为复杂的是,发送给cin的输入将会被缓冲,
也就是说,只有用户按下回车后,内容才会被发给程序。
(这个地方建议自己去编程理解)
5.5.2
这个地方就不多说了,使用cin.get()虽然能读入空格,但是输入依旧会被缓冲。
引用是C++新增的一种类型,头文件iostream将cin.get(ch)的参数声明为引用类型(这段不重要)。
5.5.3
C++中通过函数重载,可以声明多参数列表不同的同名函数(不同的函数版本)。
剩下的依旧是在讲C++和C语言的不同之处,就不多说了。
5.5.4 文件尾条件
前面的程序表明,使用某个符号表示输入结束很难令人满意(因为输入将被缓冲)
如果输入来自文件,则可以使用一种技术:检测文件尾(EOF)
很多操作系统都允许用文件替换键盘输入(重定向),
例如有一个名为 gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以:
gofish<fishtale //在命令提示符中输入这行命令,<是命令提示符模式的重定向运算符
重点:有些C++实现允许通过键盘来模拟文件尾条件,大多为使用Ctrl+Z(我使用的是Microsoft Visual)
因此应在行首按下Ctrl+Z随后按下回车键。
检测到EOF后,cin将两位(eobit和failbit)都设置为1,因此有两种方法可检测EOF是否被检测到:
如果检测到EOF,则函数cin.eof()返回ture,否则将返回false
如果eobit或failbit位被设置为1,则函数cin.fail()返回ture,否则返回false
注意,cin.eof和cin.fail在事后报告,因此应在读取后再使用它们。
fail()相较于eof()可用于更多的实现中,因此程序5.18中使用了fail(),
它使用cin.fail()检测EOF是否被检测到,如果结果为ture则循环检测部分为false(0),循环 体不执行。
通过重定向,也可以该程序显示文本文件(暂时没那么重要)
1.EOF结束输入
前面说过,检测到EOF后,cin将会设置eofbit,设置eofbit后,cin将不读取输入
然而在程序中可能会出现使用模拟EOF结束循环,随后继续输入的例子,
这种情况可以使用cin.clear来恢复输入(但是有些系统中按下Ctrl+Z无法使用cin.clear恢复)
(我是用的是Microsoft Visual,可以使用cin.clear恢复输入)
2.常见的字符输入做法
程序5.18中使用了这种设计,但还可以简化:
while(!cin.fail()) //取反(!)是第6章内容,可以将ture切换成false或将false切换成ture
一,cin.get(ch)的返回值是一个cin对象。
二,当cin出现在需要bool值的地方(如循环测试部分),cin将会被转换为bool值。
三,如果循环终止前的最后一次读取成功了,则cin转换得来的bool值为ture,否则为false。
因为以上三点,可以将上面的while循环测试部分改写成:
while (cin) //这比上面的两种格式更通用,因为它能检测到其他失败原因
这里可能对新手而言不太好理解,我们复习一下函数的返回值:
循环体中的cin.get(ch)将会把返回值返回给cin对象,然后再回到循环测试部分。
也可以使用这种格式:while(cin.get(ch)),因为cin.get(ch)同样也会返回值给cin,
这种情况下cin.get(ch)只会在循环开始时被调用一次,
而非上面几种方式的开始前调用一次,循环开始时仅仅检测不调用,循环时调用一次(共两次)
这里我把我测试时用的程序附上,可以根据本节内容进行修改,测试不同的循环测试方式。
#include<iostream>
using namespace std;
int main()
{
cout << "请输入字符:" << endl;
char ch;
int count = 0;
while (cin.get(ch))
{
++count;
cout << ch;
}
cout << "字符数为:" << count << endl;
cin.clear();
cout << "请继续输入以测试输入是否恢复:";
char b;
cin >> b;
cout << b;
return 0;
}
5.5.5
C语言中的输入/输出函数getchar()和putchar()在C++中依旧适用,但是要包含头文件<cstdio>
不接受任何函参数的cin.get()仅接受一个字符,工作方式与getchar()类似:
ch=cin.get();
这样的函数将读取的字符(编码)以int类型值返回,
而有参数的cin.get()和cin>>ch返回的是一个cin对象而非读取的字符
重点:cin是C++的标准输入流,其本身是一个对象而非函数,并不存在返回值,
应该关注的是重载运算符(如>>),是它们决定返回了什么,
一般情况下,返回值为cin本身。
同样,也可以使用cout.put函数,工作方式类似于putchar,输出一个字符,如:
cout.put(ch);
注意:如果cout.put()的参数为int类型而非char,需要显式地使用强制类型转换,否则将发生错误。
使用cin.get()到达EOF时,将会返回一个用符号常量EOF表示的值,无需关心这个常量的实际值,
只需知道如何使用这个常量便可。
使用方法例如:
ch=cin.get(); //这里使用无参数的cin.get()
while(ch!=EOF) //这里使用了符号常量EOF
{
ch=cin.get(); //循环体中
}
同样,也可以使用在循环开始时进行输入的格式
while(ch=cin.get()!=EOF) //值最终将会被赋给ch
总结:有字符参数的格式更适合对象方式,因为其返回值是istream类对象cin,这意味着可以拼接成员函数
cin.get(ch1).get(ch2);
重点:函数调用cin.get(ch1)返回一个cin对象,然后便可利用该对象调用get(ch2),
这就是为什么可以使用拼接成员函数的根本原因。
而不带参数的get()和put()的主要用处是替换getchar()和putchar(),
我个人认为暂时用处不大。
5.6 嵌套循环与二位数组
二维数组简介:一维数组可以看作一行数据,而二维数组可以看作一个既有行也有列的表格
创建方法:
int maxtemps[4][5]; //该数组每一个元素都是一个由5个整数组成的数组
可以认为,第一个下标表示行,第二个下标表示列
假设想要打印二维数组中的所有内容,可以用嵌套循环(一个循环内嵌一个循环),
一个改变行,一个改变列(王爽的8086汇编里也用到过原理类似的循环)
5.6.1 初始化二维数组
初始化二位十足的步骤和初始化一维数组原理相同,但在细节上有些不同:
二维数组需要用到多个列表初始化,因此需要花括号括起来,每个列表之间用逗号分隔。
5.6.2 使用二维数组
程序5.20
这里的程序就不细讲了,把主要部分说一下:
1,这个程序的循环部分真的和王爽8086汇编语言中的列表循环很像,
循环初始化部分声明的变量city就像寄存器BX,用来指明要打印的元素。
2,这里用了一个指针数组来指出字符串的地址,
这个指针数组是一个包含了5个指向不同字符串的指针的数组,
循环用到的指针数组名cities 代表的是指针数组中第一个元素的地址,
打印时将连续打印直至空字符或数组结束。
3,这个指针数组可用二维数组替换,但是指针数组存储的是5个指向字符串的指针的地址,
而二位数组要存储全部字符串,因此使用指针数组更为经济(节省空间)
4,使用二维字符数组存储字符串时,不必给每个字符串都套上花括号。
5,可使用string对象代替以上的所有数组,初始化列表与用于显示字符串的代码与数组相同
(显示时使用数组名方式进行显示)
在希望可以调整字符串长度的情况下,这种方式更为方便 。