C++ day6 循环,关系表达式(一)

每个语言都是如此,讲了最基本的输入输出和所有内置数据类型以后,就开始讲循环,循环就三种,for,while,do while,但是讲循环一定会涉及递增递减运算符,关系表达式,所以这些知识点也就串起来了。

for循环(entry-condition loop)

在这里插入图片描述
测试表达式可以不是关系表达式,总之程序会检查测试表达式是真还是假,也就是说,程序会自动把测试表达式转换为bool类型。

在这里插入图片描述
在这里插入图片描述

#include <iostream>
int main()
{
    int i;//计数器
    //初始化(initialization);测试表达式(loop test);更新表达式(loop update)
    for(i = 0; i < 5; i++)
        std::cout << "C++ knows loops.\n";
    std::cout << "C++ knows when to stop.\n";
    return 0;
}

示例可见,for语句的控制部分要使用三个表达式。

测试表达式可以不是关系表达式,总之程序会检查测试表达式是真还是假,也就是说,程序会自动把测试表达式转换为bool类型。举个栗子

#include <iostream>
int main()
{
    std::cout << "Enter the starting countdown limit(a positive integer):\n";
    int limit;
    std::cin >> limit;
    int i;
    for(i = limit; i; i--)
        std::cout << "i = " << i << std::endl;//不写namespace就会发现,
        //cin cout的前缀还好,但endl的前缀老忘!!
    std::cout << "Now i = " << i << std::endl;
    return 0;
}
Enter the starting countdown limit(a positive integer):
5
i = 5
i = 4
i = 3
i = 2
i = 1
Now i = 0

由于for循环,while,if等关键字后面跟着圆括号,看起来像函数调用,所以C++为了明确表示他们和函数调用的区别,于是常常在for,while,if和圆括号之间留一个空格;并且把他们后面的循环体啥的缩进。

表达式(值 或 值与运算符的组合)

任何值都是一个表达式,比如10,就是一个值为10的表达式

任何有效的值和运算符的组合就是一个表达式,比如29*10是一个值为290的表达式。

C++的每个表达式都有值。

x = 20是一个值为20的表达式,和C一样,C++赋值表达式的值被定义为左侧成员的值。实际上C和C++支持的多重赋值就是用了这个特性,当然也用到了赋值运算符从右往左的结合性,所以会把右边的值依次给左边,0先给z,(z = 0)的值再给y

x = y = z = 0;//z = 0的值是0,所以y = z的值是0,依次···

示例 表达式的值(如何打印true和false而非0和1)

#include <iostream>
int main()
{
    using namespace std;
    int x;

    cout << (x = 100) << endl;//不打括号报错,因为插入运算符的优先级比赋值运算符更高
    cout << x << endl;
    cout << (x < 3) << endl;//cout显示bool之前会转换为int
    cout << (x > 3) << endl;
    cout.setf(ios_base::boolalpha);//新的C++特征,设置一个标记,命令cout显示true和false而不是int的0和1
    cout << (x < 3) << endl;
    cout << (x > 3) << endl;
    return 0;
}
100
100
0
1
false
true

表达式的副作用side effect

并不是所有表达式都有副作用,x+15就没有副作用,因为没改变x的值。但是++x+15就有副作用,因为x的值加了1。

所以,表达式的副作用指的是:求出表达式的值的同时把表达式中一个或多个变量的值改了。比如所有赋值表达式都是有副作用的,因为实际上赋值表达式对于程序来说只需要判定表达式的值,而变量被赋值反而是副作用,当然对于我们来说赋值才是主要作用。

这个副作用不是很重要,就是说一说,知道一下

表达式语句:所有表达式加上分号就是表达式语句

语句种类还有其他的,这里只说表达式语句,表达式加上分号就是有效的C++语句,但是不一定有编程意义

x + 15;//是有效的表达式语句,但是得到的结果没存起来,后面没法用,比较智能的编译器做优化会直接忽略这种语句

示例 求阶乘

这个示例在for循环的初始化中声明计数器变量,这样做的好处是这个变量的作用域只限于for循环内部,一旦出来循环,变量就从栈中释放了

#include <iostream>
const int ArraySize = 16;//用const值定义数组的元素个数是个好习惯;声明外部变量,这使得ArraySize在程序运行期间一直存在,且文件内所有函数均可共用,其他问价也可以用
int main()
{
    long long factorials[ArraySize] = {};//阶乘很大,增长很快,因此选择long long
    factorials[0] = factorials[1] = 1;
    for (int i = 2; i < ArraySize; i++)//i<ArraySize和i<=ArraySize-1效果一样,却更简洁
        factorials[i] = i * factorials[i - 1];
    for (int i = 0; i < ArraySize; i++)
        std::cout << i << "! = " << factorials[i] << std::endl;
    return 0;
}
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000

示例 步长不为1,使用using声明

其实写这个程序只是想练习使用using声明,而不是using编译指令,毕竟直接写using namespace std是个偷懒的办法,但绝对不是个好办法

#include <iostream>
int main()
{
    using std::cout;//using声明
    using std::cin;
    using std::endl;

    cout << "Enter an integer:\n";
    int by;
    cin >> by;
    for (int i = 0; i < 100; i = i + by)
        cout << "i = " << i << endl;
    return 0;
}
Enter an integer:
10
i = 0
i = 10
i = 20
i = 30
i = 40
i = 50
i = 60
i = 70
i = 80
i = 90

示例 反序输出字符串

#include <iostream>
int main()
{
    using std::cin;
    using std::cout;
    using std::string;

    cout << "Enter a word:";
    string word;
    cin >> word;
    for (int i = word.size() - 1; i>=0; i--)//反序计数
        cout << word[i];//使用数组表示法访问string对象的每一个字符
    return 0;
}
Enter a word:animal
lamina

如果你输入的是回文,比如rotator,stats, 有可能会看不出程序的功能

Enter a word:redder
redder

示例 ++ – prefix postfix

#include <iostream>
int main()
{
    using std::cout;
    using std::endl;

    int a, b, c, d;
    a = b = 6;
    cout << "a = " << a << ", b = " << b << endl;
    cout << "a++ = " << a++ << ", ++b = " << ++b << endl;
    cout << "a = " << a << ", b = " << b << endl;

    c = ++a;
    d = b++;
    cout << "c = " << c << endl;
    cout << "d = " << d << endl;
    return 0;
}

递增递减运算符都长得很好看,很简洁,而且++还是C++名称的来历。

从结果看出,a++表达式的值是递增之前的值,++b表达式的值是递增后的值,后面的赋值表达式就是把表达式的值给左边,很好理解

a = 6, b = 6
a++ = 6, ++b = 7
a = 7, b = 7
c = 8
d = 7

在这里插入图片描述

其实上图的那种表达式的结果未知是因为“顺序点”的概念。这个概念已经被淘汰了,因为不适用于多线程执行。总之,一个完整的表达式末尾就是一个顺序点,在进入顺序点后面的代码前,所有的赋值,递增等等等等的操作和副作用必须完成,不可以影响后面的代码。所以上图的2*x++不是一个完整表达式,所以递增行为可能还没发生,整个赋值表达式的右边是一个完整的表达式,可是有两次递增,C++只能保证会做两次递增,但是和其他运算的先后顺序是没有定义的,所以结果是未知的。

前缀版本效率比后缀高!

下面这两种情况,前缀和后缀在行为上没有区别,因为表达式的值根本没被使用,只利用副作用(赋值),副作用又是一样的

i++;
++i;
for (int i = 0; i < n; i++)
for (int i = 0; i < n; ++i)

但是也不是完全没区别,你想,你如果要定义递增运算符,那么前缀形式,你只需要对变量加1,然后返回变量的值;但是后缀形式,你需要先创建变量的副本(就是表达式的值了),对副本加1,再返回副本加1后的值,总之要多做一步。虽然现在的编译器使得前缀和后缀的运算速度区别并不大,但是毕竟是不一样的。

而且如果是自己定义,那这之间的区别不可忽视,所以对于类方法来说,还是尽量使用前缀形式

*++pt, ++*pt, *pt++, (*pt)++, *(pt++)

当递增递减运算符遇上解引用运算符,就需要扎实的优先级和结合性知识了,否则就晕了

首先,看清楚优先级,后缀高于前缀,前缀和解引用优先级一样(同一组则优先级相同)

但是后缀的结合性是从左到右(下图中看不到,截图没截到),前缀和解引用的结合性是从右到左
在这里插入图片描述

int a[5] = {1, 4, 6, 10, 5};
int *pt = a;

∗ * ++pt:
优先级一样,结核性从右到左,所以pt先和++结合,所以解引用作用于递增后的pt,相当于 ∗ * (++pt),等于4

++ ∗ * pt:
pt先和解引用结合,所以递增的是1,结果是2

∗ * pt++:
后缀优先级更高所以pt和++结合,但是后缀,使得先解引用pt再递增pt(不是递增解引用后的值!),表达式相当于

*pt;
pt++;

结果等于 ∗ * pt,即1,但是pt指向4了

∗ * (pt++),这个和上一个的区别就是解引用的是pt递增后的值,最终结果是4,pt也指向4

∗ * pt)++:这个就简单了,就是解引用以后的值递增,等于2

逗号运算符(优先级最低的运算符),把多个表达式合并为一个

逗号不一定就是逗号运算符哈,很多时候逗号只是列表的分隔符号,比如声明语句中多个同类型的变量

大括号可以把多条语句合并为一条复合语句,(其实是因为C++只允许放一条语句的地方可以放多条了);逗号运算符对表达式起一样的作用,可以再C++只允许放一个表达式的地方放多个表达式

逗号运算符最常用的地方就是for循环里面,像下面这个示例一样

逗号运算符是一个顺序点,即它保证左边的表达式先被计算,且逗号表达式的值是第二部分的值

i = 20, j = 2 * i;//j等于40;整个表达式的值是40
cats = (17, 23);//cats = 23
cats = 17, 23;//cats = 17
(cats = 17), 23;//cats = 17,和上一句等价,因为逗号运算符优先级是最低的

示例 字符串反转(反序输出)

这里面涉及到一个算法呢,不断交换两边对称的位置,直到相遇或者走过了

不可以用逗号运算符组合两个声明!!

#include <iostream>
int main()
{
    using std::cout;
    using std::cin;
    using std::string;

    cout << "Enter a word:\n";
    string word;
    cin >> word;

    char temp;
    int i, j;//必须在循环外部声明
    for (i = word.size() - 1, j = 0; j < i; ++j, --i)
    //用了两次逗号运算符,这里需要注意的是:
    //绝对不可以用逗号运算符组合两个声明,因为那不是表达式!!
    {//交换第一个和最后一个字符;第二个和倒数第二个字符···直到相遇
        temp = word[j];
        word[j] = word[i];
        word[i] = temp;
    }
    cout << word << "\nDone!\n";
    return 0;
}
Enter a word:
lobster
retsbol
Done!

关系表达式(优先级低于算术运算符)

关系表达式是程序语言做决策的基础。

C++有6种关系运算符。
在这里插入图片描述

x + 3 > y - 2;//等价于(x + 3) > (y -  2)
x + (3 > y) - 2;//也是有效表达式,因为bool会被提升为int

关系运算符不能比较字符串,用strcmp()

由于字符是整型,当然可以用关系运算符比较,但是字符串可就不行了,字符串本身代表的实际是存储它的地址的嘛,你比较实际上是在比较地址是否一样

示例 比较字符

#include <iostream>
int main()
{
    using std::cout;
    for (char ch = 'a'; ch < 'f'; ++ch)
        cout << ch << '\n';
    return 0;
}
a
b
c
d
e

C里面说过,strcmp()函数接收两个地址,所以可以是char数组名,指针,字符串字面量。

如果两个字符串一样,返回0;
如果第一个字符串排在第二个字符串前面,返回负数;
否则,返回正数

注意,ASCII中, 所有大写字母都在小写字母前面,所以Zoo在apple前面哦;并且FOO和foo不是一样的哦

示例 比较字符串 strcmp()的返回值作为for循环测试条件(因吹斯听)(非计数循环)

在这里插入图片描述

#include <iostream>
#include <cstring>
int main()
{
    using namespace std;
    char word[5] = "?ate";

    for (char ch = 'a'; strcmp(word, "mate"); ++ch)//测试条件等价于strcmp(word, "mate")!=0
    {
        cout << word << endl;
        word[0] = ch;
    }
    cout << "After loops, word = " << word << endl;
    return 0;
}
?ate
aate
bate
cate
date
eate
fate
gate
hate
iate
jate
kate
late
After loops, word = mate

示例 用关系运算符比较string类字符串对象(运算符重载)(非计数循环)

string类的对象之所以又可以用关系运算符比较字符串,是因为string类重载了关系运算符,即重新定义了这些关系运算符

#include <iostream>
#include <string>
int main()
{
    using namespace std;
    string word = "?ate";

    for (char ch = 'a'; word != "mate"; ++ch)
    {
        cout << word << endl;
        word[0] = ch;
    }
    cout << "After loops, word = " << word << endl;
    return 0;
}

执行结果和上面一样的

基于范围的for循环(C++11新增,主用于模板容器类:如vector类,array类)

普通for循环想对一个数组的所有元素做点什么操作,必须一个一个遍历来,很麻烦,这个基于范围的for循环就是针对这一痛点的

#include <iostream>
int main()
{
    using std::cout;
    using std::endl;

    double prices[5] = {1.2, 2.1, 5.6, 4.56, 8.74};

    for(double x : prices)
        cout << x << endl;

    for(double &x: prices)
    {
        x = x * .5;
        cout << x << endl;
    }


    for(int x : {1, 2, 3, 4, 5})
        cout << x << " ";

    return 0;
}

一定要用支持C++11标准的编译器,否则程序中的循环不能停止,因为他不懂停止条件

1.2
2.1
5.6
4.56
8.74
0.6
1.05
2.8
2.28
4.37
1 2 3 4 5

while循环(entry-condition loop,类似于只有测试表达式的for循环)

上面的最后两个示例都是非计数循环,即循环次数不定,而是根据条件判断,其实这种情况更加常用while循环

while是特殊的for循环,他是没有初始化和更新表达式的for循环,即只有测试条件的for循环
在这里插入图片描述在这里插入图片描述在这里插入图片描述

示例1 遍历C风格字符串

#include <iostream>
const int Arsize = 20;
int main()
{
    using namespace std;
    char name[Arsize];
    cout << "Enter your name:\n";
    cin >> name;

    int i = 0;
    while(name[i])//等价于while(name[i] != '\0'),但前者更简洁也更常用
    {
        cout << name[i] << ": " << int (name[i]) << endl;//打印ASCII码必须强制转换
        ++i;
    }
    return 0;
}
Enter your name:
Manica
M: 77
a: 97
n: 110
i: 105
c: 99
a: 97

示例2 用clock()库函数创建延时循环

有时候需要程序等一会儿,我现在还没遇到过这种需求,但是它确实很有用

普通的延时,这么写三行代码就可以了,但是他的问题是延迟的时间不确定(和运行速度有关),而且如果需要更改时间也不太方便

long wait = 0;
while(wait < 10000)
	wait++;

更好的方法是使用C和C++库提供的clock()函数,它的返回类型是clock_t,可能是long,也可能是long long,系统不同就不同,这个别名当然是typedef定义的了,而且返回的不是秒数,而是系统时间单位。

ctime或者time.h中定义了一个符号常量:CLOCKS_PER_SEC,clock()返回的系统时间单位除以这个符号常量就得到了秒数。

#include <iostream>
#include <ctime>
int main()
{
    using namespace std;
    cout << "Enter the delay time, in seconds:";
    float secs;
    cin >> secs;
    clock_t delay = secs * CLOCKS_PER_SEC;
    clock_t start = clock();//获取当前系统时间
    cout << "Starting...\a\n";
    while(clock() - start < delay)
        ;
    cout << "Done!\n";
    return 0;
}
Enter the delay time, in seconds:5
Starting...
Done!

两种方式创建类型别名(#define typedef)

先说结论:typedef更好,因为可以处理更复杂的类型,比如指针,而且有时候是唯一选择

注意两种方法都只是创建别名,都不会创建新类型

这个例子两种方式都没问题

#define BYTE char//文件中所有BYTE会在编译前的预处理阶段背替换为char,BYTE是宏
typedef char BYTE;

这种指针类型宏就相形见绌了

#define byte_pointer char *
byte_pointer b1, b2;//预处理解文本替换后是char * b1, b2; 所以只有b1是char指针,b2只是char
typedef char * byte_pointer;//可以声明一系列变量
byte_pointer b1, b2;//二者都是char指针

for和while循环本质上是相同的!但当循环次数不一定时常使用while

在这里插入图片描述在这里插入图片描述

一个奇葩示例

for

int i = 0;
for (;;i++)
{
	if (30 <= i)
		break;
}

while

int i = 0;
while(i < 30)
	i++;

两端代码作用一样,但是还是while好理解点

另外,要说一下,if或者while里面,把值写在关系运算符的左边,变量写在右边是有好处的,比如判断相等==,和赋值运算符,如果 v a l u e = = v a r i a b l e value==variable value==variable不小心被写为value=variable,编译器就会报错,你给常量赋值。但是 v a r i a b l e = = v a l u e variable==value variable==value要是不小心写为variable=value,就不会报错哦

do while循环(exit-condition loop:即先执行循环体再判断测试条件)

通常都用入口条件循环,第一次执行循环体之前就要判断条件,但有的时候,用do while更合适,比如要涉及到输入的时候,你必须先获得一次输入再去判断这个输入是否符合测试条件,然后不符合的话就继续输入
在这里插入图片描述

示例1 猜数字

#include <iostream>
int main()
{
    using std::cin;
    using std::cout;

    cout << "Enter an integer in the range 1-10 "
         << "to find my favorite number:\n";
    int number;
    do
    {
        cin >> number;
    }while(number != 6);
    cout << "Yes! 6 is my favorite number.\n";
    return 0;
}
Enter an integer in the range 1-10 to find my favorite number:
2
8
3
9
10
4
6
Yes! 6 is my favorite number.

do while比for循环更合理的例子

for

int i = 0;
for(;;) // a forever loop死循环
{
	i++;
	if(30 <= i)
		break;
}

do while

int i = 0;
do
{
	i++;
}while(i < 30);

两种写法一样的,i=30时都退出了循环,或者说退出循环时i都是30,但是第一个写法毕竟有点奇特非主流,实际上最好还是不要写这样的代码去炫技,还是写清晰好理解的代码,走大路

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页