从 for while 开始的C语言学习(复习)之路 第一部分PART1

从 for while 开始的C语言学习(复习)之路PART 1 基础篇

PART 2 近期出解。/笑哭

总体来说 for 循环 和 while 循环 都是循环,可是就是些循环 却被我玩坏了。[玩坏过的勿喷]

第一个: for + scanf :

例如:    

 
 
for(int i;scanf("%d",&i),i;){ }
int i;
while(scanf("%d",&i),i){ }
while(scanf("%d",&i)!=EOF){ }
while(~scanf("%d",&i)) { }

一般会在各种书里会见到他们的影子。这里只是做出一点直观的解释。

1.它们都有一个共同的先决条件 -- 变量必须要先定义,然后才能使用。

for(int i;scanf("%d",&i),i;){ }

就像这个样例的for循环 在第一个;之前定义了一个变量,然后输入这个变量,判断这个变量是不是等于 0 ,如果等于 0 就会退出。 而有些新手可能就会写成下面这样,然后问为什么输入 0 后 循环不会退出??

for(int i;scanf("%d",&i);){ }

这就要讨论一下 scanf 它的返回值了 scanf到底是返回什么的 我们 可以使用 一个整型的变量来接收它看看。

扫描二维码关注公众号,回复: 1018386 查看本文章
int main(){

    int i,j;
    int c =scanf("%d%d",&i,&j);
    cout<<c;
    return 0;
}

当发生正常输入,那么输出结果是 2 。为什么呢?那是因为 scanf返回的是 你输入的元素个数 因为 scanf里面有两个%d 【什么,和你想的不一样,你是不是想因为有两个变量在scanf里 所以是 2??】那好,我们再做一个实验

int main(){

    int i,j;
    //int c =scanf("%d%d",&i,&j);
    int c =scanf("%d",&i,&j);

当发生正常输入的时候 输出 1. 然后我们接着做下面这个实验

                

int main(){

    int i,j;
    //int c =scanf("%d%d",&i,&j);
    int c =scanf("%d%d%d",&i,&j);
    cout<<c;
    return 0;
}

这回正常输入后  c 的值 是 3.  所以真正控制 scanf返回值的 我觉得应该是里边的%d . 

这就会让人想到一个符号 EOF 它的值是-1 它的用处是判断文件是不是输入结束。一般都是这两种写法

scanf("%d",&i)!=EOF
~scanf("%d",&i);

这两种写法,那么想玩的同学可能要问 这个要怎么才能使用啊!我好想让 scanf 返回值是-1啊! 在这里,因为笔者使用的是windows 系统 所以 仅仅介绍一下 windows 的操作。

在运行程序scanf等待用户输入的时候 按 ctrl + z 两个键,此时你会看见 程序框显示   ^z 这时按回车就会发现 scanf的返回值是-1了。

[又跑题了] 回归正题  for( 1 ; 2 ; 3)  1 只在循环一开始执行  2 只在下一循环开始之前执行  3 每次循环结束以后执行。为了方便理解 我写了三个程序。

    for(;int i=1;){
        scanf("%d",&i);
        cout<<" ";
        break;
    }
    for(int i;;){
        scanf("%d",&i);
        cout<<i<<" ";
        break;
    }

    // 以下编译错误
    for(;;int i=0){
        scanf("%d",&i);
        cout<<i<<" ";break;
    }
知道为什么会编译错误了吗?这就是我之前提到的 for( 1 ; 2 ; 3)  1 只在循环一开始执行  2 只在下一循环开始之前执行  3 每次循环结束以后执行。所以 定义在 使用的后面 所以编译出错。而第一段代码必须给i赋初值,否则同样编译不通过,原因应该是中间那个是判断循环是否继续执行的条件,所以只接受 0 和 非0 这种数据吧!  (bool值也可以使用 %d 的方法进行输出,也会发现是 0 和 1.)


所以 我有一种奇怪的写法 来看一看

   while(int i=1){
        scanf("%d",&i);
   }

这样的写法正是等同于 for(;int i;){} 这种写法。 所以 我们得出了一个什么样子的结论呢??

任意的while()可以使用for() 进行替换。那 while(scanf("%d",&i)!=EOF) 这个能不能进行替换呢?? 试一下就知道了

   for(int i;scanf("%d",&i)!=EOF;){
        ;
   }

或者是这样

   for(int i;~scanf("%d",&i);){
        ;
   }

我发现 这两条也是可以通过编译器进行编译的。所以可以暂说for 可以 替代 while . 

那它们之间 到底是谁效率高呢???

网上都有讨论 据说 九十年代有过测试 证明过 while 的效率优于 for 不过 更多的,我们现在的计算机配置,似乎已经看不到差别了吧!!!


所以 那么冒泡排序的 while 的写法 是不是 可以试试看呢~~~  我们要善于观察 for 和 while 的 差异性

这个是用 for 写的 冒泡排序

int main(){

    int a[10]={1,3,2,5,4,7,6,9,8,0};
    for(int i=1;i<10;i++)
        for(int j=0;j<10-i;j++)
            if(a[j]>a[j+1])
                swap(a[j],a[j+1]);
    for(int i=0;i<10;i++)
        cout<<a[i]<<" ";
}

这个是用 while 写的 冒泡排序

int main(){

    int a[10]={1,3,2,5,4,7,6,9,8,0};
    int i=1,j;
    while(i<10){
        j = 0;
        while(j<10-i){
            if(a[j]>a[j+1])
                swap(a[j],a[j+1]);
            j++;
        }
        i++;
    }
    for(int i=0;i<10;i++)
        cout<<a[i]<<" ";
}

就发现了for 和 while 的 残留问题  for 循环 可以轻松定义局部的变量 避免了 空间的浪费 而while 循环却会残余变量,那是不是就无解了呢?? 不是的 !! 还记得  { } 花括号的用法吗??我们把 排序所需要的代码用 花括号括起来 

int main(){

    int a[10]={1,3,2,5,4,7,6,9,8,0};
{  // 我是一个可爱的花括号

    int i=1,j;
    while(i<10){
        j = 0;
        while(j<10-i){
            if(a[j]>a[j+1])
                swap(a[j],a[j+1]);
            j++;
        }
        i++;
    }
} // 我也是一个可爱的花括号,你看见我了吗== 喂
  //  cout<<i<<endl; // 你们猜 这条语句 可以编译的过去吗?? 答案:是不能编译过去的。所以局部变量的概念掌握了吗??
    for(int i=0;i<10;i++)
        cout<<a[i]<<" ";
}

所以 并不是像我之前所说的那样 会有这种 多余的 东西存在。所以仁者见仁,智者见智,使用方略其实是非常宽广的嘛!!

根据我们的学习我们会发现 其实并不像老师所说 for 循环是有循环次数才使用,while是未知循环次数才使用的。

下面讲一下  循环 for 的指针和字符串的 使用方法。

再讲这个之前 我们讲一下 指针的 使用方法。

指针 分为 很多种 什么 int* char* string* (c++) 迭代器指针啊等等等。 第一节课的时候老师说 指针保存的是指向元素的地址。随着学习的逐渐深入 发现 并不是完全是这样的。为什么呢??我们举一个例子 

    char i = 'c';
    int t = (int)i;

这种写法大家都是认为可以这样的吧!!因此无论怎么变 指针变量 可以理解是一种指向它本身基类型的类型,总之就是类型,是类型就可以用强制类型转换进行赋值。因此我们不难说明以下代码是正确的

int* i = (int*)1;

接着我们 还知道了 已知数组中间元素的地址 ,那么用它来减去首地址就会得出这个元素在数组的位置。这个就说明了指针可以进行加减运算 ,然后我们可以猜想它能不能进行乘除运算呢?好!上代码

    int* i = (int*)1;
    int* j = (int*)2;
    cout<<i*j<<endl; // 我是错误的代码 你发现我了吗??

所以是不能进行乘除运算的!!!!

那么指针是 怎么进行加减运算的呢??它又拿什么进行运算的呢??例如: j - i

    int* i = (int*)1;
    int* j = (int*)2;
    cout<<j - i<<endl;

答案是多少呢??反正不是 1 而是 0 !!!!为什么是 0 呢?因为 int 类型是 占用 4 个字节的空间 即 [1,4] 闭区间哦!!

所以两个相同类型的指针相减 减得是 所占用的那个空间 1 和 2 都是第一个空间 所以 1-1==0 正确!

我们又来看下下面这个程序

int main(){

    int* p =NULL;
    int k = (int)p;
    cout<<k;
}

k的值是多少 ?答案是 0 。说明了什么 int* p =NULL 就等价于 int *p = (int *)0; 呀!!不信?

我们看看下面这个程序

    int* p =NULL;
    int* k = (int *)0;

    if(p == k)
        cout<<"我对了"; // 是输出的我 看见我了吗??喂!
    else
        cout<<"继续努力";

我们还知道 '\0'代表的是 0 那么我们可以演示一下这个程序:  (逐渐进入今天的正题

    char* p =NULL;

    if(p == '\0') // 或者 if(p == 0)
        cout<<"我对了"; // 是我是我!!
    else
        cout<<"继续努力"; // ………………

现在 我们 看 正题 ,哎对了 正题是什么来着??

循环 for 的指针和字符串的 使用方法。(肯定我掌握的不如那些牛叉的大佬了,所以想要学更多的东西还得靠手 不是靠喷!)

我们都知道 字符串 是由若干个字符在和'\0'在一段连续的空间中组成的。间断的空间是无法构建字符串的。比如用链表来存。

我们来看下面一个代码:

 char *s = "nihao ma";

现在问一下 一共有多少个有效字符 一共有8 个有效字符。

再问一下 一共有多少个字符 因为是直接赋值 所以我们有三种方法 知道这个结果:

1. 使用strlen函数的返回值+1  即 int i = sizeof(s);  返回值是 9、

2.一眼看出来,你不会不懂数数吧??/笑哭

3.因为这个是一个直接赋值的字符串 所以 可以直接调用 sizeof() 来查 ,所以 int i = sizeof(s); 返回值是 9

但是注意 sizeof 用于查看这个数组剩余的空间的 我们再扯一下 sizeof()这个函数:

char a[6];
cout<<sizeof(p);

那都不用想啊 肯定返回的是 6 啊??为什么??因为char 类型一个占用1 个空间 一共有 6 个 因此 1*6 == 6 就是这么来的。

那也就是可以拓展为

int a[6];
cout<<sizeof(p);

返回的是 4*6 == 24  答案正确

那 给定一个字符串 我们怎样遍历呢??

是这样??

for(int i=0,j=strlen(str);i<j;i++)

;

还是这样??

for(int i=0;str[i]!='\0';i++)

;

还是 一种特殊的方法

for(char* s=str;*s!='\0';s++)

;

推荐使用第三种 方法 只是推荐  ,那有人会问我怎么知道我正在处理的元素是第几个元素呢??其实这个非常好处理,只需要

当前指针 - 数组的首部指针 。无论是 malloc定义的 还是 int 直接定义的  都可以通过减法计算出当前处理的元素的位置。

即 s - str == 当前元素的个数。

下回再见。哈哈哈 别怪我下回放难的呀??基础不牢地动山摇./笑哭

猜你喜欢

转载自blog.csdn.net/oshuaifeng/article/details/80158436