《C和指针》读书笔记(第四章 语句)

简介: 《C和指针》读书笔记(第四章 语句)

这章的内容比较简单,但是非常重要。高中时候,我们就学过顺序结构条件结构以及循环结构。然后在这章,又在原来的基础上进行了深化补充

先来看看这章的知识体系。

1 内容概览

2.1 空语句

空语句非常简单,仅仅只有一个分号,类似于Python中的pass。空语句并不是完全没有作用,常见以下两种情况。

  • 不想有任何表达,但是客观上必须要有语句。比方说 ,在嵌入式开发中,最开始的延时就是通过while循环,循环体内一个空语句实现的。
  • 暂时不想写东西,为了内容的完整性,补充一个空语句。以后可以补充。比方说,有的人喜欢先写一个基本的逻辑框架,再填写具体的语句。

2.2 表达式语句

2.3 代码块

代码块在此前的代码块作用域已经讲过了。再来举个例子。

int main()
{
  int res = 10;
  printf("%d",res);
  {
    int res = 5;
    printf("%d",res);
  }
  system("pause");
}

比方说,在同一个函数中,有时候想用相同的变量名称,这个时候就可以采用代码块。这个与命名空间有异曲同工之妙。

2.4 if语句

if语句是最常见的条件判断语句,但是C语言中独特的switch语句,使得在某些特殊场合switch语句比传统的if语句更加美观(虽然时间复杂度基本一致)。if语句唯一需要注意的就是if和else的匹配问题:

在C语言中,当出现if和else得匹配出现歧义的时候,else子句从属于靠近它的不完整的if语句。

比方说:

int main()
{
  int score = 66;
  if (score > 60)
    if (score > 70)
      printf("良好");
  else
    printf("不及格");
  system("pause");
}

在上面的例子中,就出现了else和if的匹配问题,所以按照所谓的就近原则,会输出不及格,而不是无输出。

2.5 循环语句

2.5.1 while语句

while语句与do语句非常类似,不过while语句是先判断循环条件,再执行循环体,而do语句恰好相反,是先执行循环体,然后再判断循环条件。

while语句相比for语句,当循环的判断条件或者执行情况比较复杂的时候,往往while语句比较占优势。但是时间复杂度基本一致。比方下面这个问题(出自leetcode455.分发饼干):

  1. 分发饼干
    假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

如果采用while循环:

int cmp(int* a, int* b) {
    return *a - *b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
    qsort(g, gSize, sizeof(int), cmp);
    qsort(s, sSize, sizeof(int), cmp);
    int res = 0;
    int g_left = 0;
    int s_left = 0;
    while(g_left < gSize && s_left < sSize)
    {
        if(g[g_left] <= s[s_left])
        {
            g_left++;
            s_left++;
            res++;
        }
        else
        {
            s_left++;
        }
    }
    return res;
}

for循环的具体代码如下:

int cmp(int* a, int* b) {
    return *a - *b;
}
int findContentChildren(int* g, int gSize, int* s, int sSize){
    qsort(g, gSize, sizeof(int), cmp);
    qsort(s, sSize, sizeof(int), cmp);
    int res = 0;
    int g_left = 0;
    int s_left = 0;
    for(;g_left < gSize && s_left < sSize;s_left++)
    {
        if(g[g_left] <= s[s_left])
        {
            g_left++;
            res++;
        }        
    }
    return res;
}

其中的qsort快速排序语句,用来给两个数组排序

从上面的例子可以看出,循环语句中while和for并没有根本上的优劣之分,只是个人使用习惯上的区别,当循环的情况比较复杂的时候,优先考虑while语句。

2.5.2 for语句

for语句一般是在循环步骤比较明确的时候,比较常用,比如需要求一个数组各个元素的和:

int main()
{
  int a[10];
  int res = 0;
  //数组初始化
  for (int i = 0; i < 10; i++)
  {
    a[i] = i+1;
  }
  //求和
  for (int j= 0; j < 10; j++)
  {
    res += a[j];
  }
  printf("a数组各元素之和为%d\n",res);
  system("pause");
}

而此时如果用while语句实现,将会是这样:

int main()
{
  int a[10];
  int res = 0;
  int i = 0, j = 0;
  //数组初始化
  while (i < 10)
  {
    a[i] = i + 1;
    i++;
  }
  //求和
  while (j < 10)
  {
    res += a[j];
    j++;
  }
  printf("a数组各元素之和为%d\n",res);
  system("pause");
}

从这个例子就可以看出,for循环可以直接将循环体放在花括号里,增加了代码的可读性。

2.5.3 do语句

do语句一般适用于先执行循环体,再进行条件判断,比如输入一个数,判断是奇数还是偶数:

int main()
{
  int a;
  do {
    int b;      /*要继续吗?*/
    printf("请输入一个整数:");
    scanf("%d", &b);
    if (b % 2)
      puts("这个整数是奇数。");
    else
      puts("这个整数是偶数。");
    printf("要重复一次吗?[yes……0/No……9]:");
    scanf("%d", &a);
  } while (a == 0);
  return(0);
}

可以看出,在这个情况下,我们必须先输入数据,执行一次,由用户决定是否继续执行数据处理,这个时候do语句就有了得天独厚的优势。

最后就是break和continue语句,这两条语句在以上的三种循环语句中都适用,break用于跳出本层循环,而continue用于跳过此次循环体的后续语句,直接进行下一轮循环。

三种循环语句运行的流程图如下所示(Y表示满足循环条件,N表示不满足循环条件):

2.6 switch语句

在有的教材中,switch语句又叫开关语句,这样的称呼也行,应该是属于直译,***从逻辑上说,Switch语句和多个if语句并无二致。***当然,在很多同行中称,多个if语句更好维护,因为以后若是有人在此基础上进行开发,增加条件的时候只需要增加if语句就行,在Switch中,不仅仅是增加case这么简单,因为要求判断的表达式必须为整型值。

当然Switch有其独特的优势,就是形式上更加美观整齐。比方说在我们以前的教材中,成绩的评级问题,就可以用Switch语句很好地解决。

int score = 88;
  switch (score / 10)
  {
  case 10:
  case 9: 
  case 8: printf("优秀\n");  break;
  case 7: printf("良好\n");  break;
  case 6: printf("及格\n");  break;
  default:
    printf("不及格\n");    break;
  }

如果我们用if语句,就写成了下面这样:

int score = 88;
  int stage = score / 10;
  if (stage == 10 || stage == 9 || stage == 8)
  {
    printf("优秀\n");
  }
  else if (stage == 7)
  {
    printf("良好\n");
  }
  else if (stage == 6)
  {
    printf("及格\n");
  }
  else
  {
    printf("不及格\n");
  }

显然在这个时候,Switch显得十分优雅,但是Switch使用的时候有个问题,就是要谨慎地使用break语句。如果不使用,就会在执行完该case之后,直接顺延到下一个case的语句中,直到运行完所有语句或者中途遇到break。

  1. 还有一点,就是如果所以的情况都不符合,则会运行default子句,所以一般情况下,都需要默认加上default子句,类似于if语句中的else。

2.7 goto语句

应该说,goto语句是最不常用的语句,所以在其他很多语言中,是没有goto语句的,原因有二:

3. goto语句破坏了原本程序的空间连续性。

4. 无论在什么情况下,goto语句总是可以找到替代方案。尽管有时候替代方案并不完美,但是无伤大雅。

书中提到了一种看起来需要goto语句的情况,就是在多层的循环中,需要break的时候,并不是非常方便。例如这样的情况,我需要检查一个数组中的数是否有序,可以采用冒泡排序的思想,只要前面的数大于后面的数,直接跳出,但是在冒泡算法中,有两层循环,程序是这样的:

int main()
{
  int rank[10] = {4,0,5,3,6,2,5,1,15,17};
  bool res = true;
  for (int i = 0; i < 10; i++)
  {
    for (int j = i + 1; j < 10; j++)
    {
      if (rank[i] > rank[j])
      {
        res = false;
        goto print_res;
      }
    }
  }
print_res:
  if (res == true)
  {
    printf("该数组是有序的!");
  }
  else
  {
    printf("该数组是无序的!");
  }
  system("pause");
}

替代方案有两种:

  1. 在每层循环中都加上条件判断。
  2. 将所有循环放在一个函数中,借用return有语句终止循环。

方案一实现的代码如下:

int main()
{
  int rank[10] = {4,0,5,3,6,2,5,1,15,17};
  bool res = true;
  for (int i = 0; i < 10 && res == true; i++)
  {
    for (int j = i + 1; j < 10 && res == true; j++)
    {
      if (rank[i] > rank[j])
      {
        res = false;
        break;
      }
    }
  }
  if (res == true)
  {
    printf("该数组是有序的!");
  }
  else
  {
    printf("该数组是无序的!");
  }
  system("pause");
}

显然会打印输出:该数组是无序的!

当然,在上面的例子中,break语句不是必须的,因为当res = false的时候,已经不可能继续执行下次循环,所以循环会终止,将继续执行后面的语句。

方案二实现的代码如下:

bool rank_fun(int *num, int numsize)
{
  for (int i = 0; i < 10; i++)
  {
    for (int j = i + 1; j < 10; j++)
    {
      if (num[i] > num[j])
      {
        return false;
      }
    }
  }
  return true;
}
int main()
{
  int rank[10] = {4,0,5,3,6,2,5,1,15,17};
  bool res = rank_fun(rank,10);
  if (res == true)
  {
    printf("该数组是有序的!");
  }
  else
  {
    printf("该数组是无序的!");
  }
  system("pause");
}

同样会打印输出:该数组是无序的!

本章的内容到这里就结束了,一起加油,有什么想法可以在下方留言,大家一起讨论!

------------------------------------------------------------END----------------------------------------------------------------

相关文章
|
存储 搜索推荐 算法
《C和指针》读书笔记(第十一章 动态内存分配)
《C和指针》读书笔记(第十一章 动态内存分配)
|
存储 C语言
《C和指针》读书笔记(第十章 结构和联合)(下)
《C和指针》读书笔记(第十章 结构和联合)(下)
|
存储 C语言 C++
《C和指针》读书笔记(第十章 结构和联合)(上)
《C和指针》读书笔记(第十章 结构和联合)(上)
《C和指针》读书笔记(第九章 字符串、字符和字节)(下)
《C和指针》读书笔记(第九章 字符串、字符和字节)(下)
|
程序员 C语言
《C和指针》读书笔记(第九章 字符串、字符和字节)(中)
《C和指针》读书笔记(第九章 字符串、字符和字节)(中)
|
存储 C语言
《C和指针》读书笔记(第九章 字符串、字符和字节)(上)
《C和指针》读书笔记(第九章 字符串、字符和字节)(上)
|
存储 编译器 C语言
《C和指针》读书笔记(第八章 数组)(下)
《C和指针》读书笔记(第八章 数组)
|
存储 安全 编译器
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
[笔记]读书笔记 C++设计新思维《一》基于策略的类设计(下)
|
存储 关系型数据库 编译器
C++ Primer Plus 第6版 读书笔记(9)第 9章 函数——内存模型和名称空间
C++ Primer Plus 第6版 读书笔记(9)第 9章 函数——内存模型和名称空间
110 1
|
存储 算法 编译器
C++ Primer Plus 第6版 读书笔记(8)第 8章 函数探幽(二)
C++ Primer Plus 第6版 读书笔记(8)第 8章 函数探幽(二)
70 1