浅谈C的应用与常见error

  开通博客很久了,一直想写一些自己的东西,由于所学知识有限,一直不能落笔,废话少说,进入正题。

  我下面所写的都是用“.c”后缀的。“.c”后缀是c源文件的后缀,“.cpp”后缀是c++源文件的后缀。c++继承了c语言的一些特性,所以有些bug在“.cpp”里是可以通过的。

  1、scanf()你真的了解了么?;

    scanf()是有返回值的,返回值类型是int,这个你知道么?它返回了成功读入输入信息的个数。

    那么我们可以利用它来做什么呢?

    非零即真,这句话你一定听说过,而且不止一次。那么我们看一下下面的代码。

      int a;
       while (scanf("%d", &a) == 0)
       {
            printf("请输入合法字符:");
       }

    这样写代码是不是好一点,再输入的同时判断了输入的是否正确。但这会使你进入一个死循环。为什么会这样呢?

      我们在输入时,我们的键值需要一块缓冲区在存放键值,如果输入的键值不被scanf()接受,那么它会一直在缓冲区中,那么再仔细看一看上面的代码,你是不是明白了呢。

    解决这个问题并不难,只要将输入的键值接受,他就不会在缓冲区中影响你的程序了。看看下面的代码,有没有豁然开朗,哇,scanf()还可以这么使用!

     int a;
       while (scanf("%d", &a) == 0)
       {
            getchar();
            printf("请输入合法字符:");
       }

    2、你还在纠结while(),for();

    初学者可能会纠结什么场合用while(),什么场合用for()。

    在我看来,这根本不存不值得我去纠结,我们应该想的是使用哪个可以使程序更简单,别人更容易看懂。

    while(),for()的本质都是循环,只是形式不一样而已。

    int i = 0;            int i;

    while (i<10)           for (i=0; i<10; i++)

    {                {

      i++;

    }                }

    上面的代码当然是for()更简明一写,但1、中的例子却是使用while()。for()比while()在语句要多一些,这是你需要的么?

    根据每个人的代码风格不同,不能统一要求使用for()或者while()。建议初学者可以两个都试一下,感觉他们之间的不同,写出漂亮的代码。

  3、switch()你会使用么?

    switch(n)

    {

    case 1:

      break;

    case 2:

      break;

    default:

    }

    你一直这么使用么?

    我们都知道case 后面不加break,会一直执行下面的代码,直到代码全不执行完或者遇到break。

    利用这个特性,我们是不是可以做一些其他的事,比如加法,一年有十二个月,我想算出4月6号是今年的第几天,你想到怎么使用了么?我们看一下下面的代码。

    switch (month-1)
       {
    case 11: sumMonth += 30;
    case 10: sumMonth += 31;
    case 9: sumMonth += 30;
    case 8: sumMonth += 31;
    case 7: sumMonth += 31;
    case 6: sumMonth += 30;
    case 5: sumMonth += 31;
    case 4: sumMonth += 30;
    case 3: sumMonth += 31;
    case 2:
           if ((i%4==0 && i%100!=0) || i%400==0)
            {
                   sumMonth += 29;
           }
            else
            {
               sumMonth += 28;
            }

    case 1: sumMonth += 31;
    }

    你看懂了么?只是提供一个思路,将我们思维打开,你会看见另一片天空。

    你在case中定义过变量么?

    case中为什么不能定义变量呢?

       int a;
       switch (a)
      {
      case 1:
          int b;
          break;
      case 2:
          b = 0;
          break;
      }
    你这么任性,编译器怎么办!

    那我们为什么不规定case 1定义的变量只能在case 1中使用呢?在6、变量作用域中你将会找到答案。

    当然,我们不应该只局限于switch(),if() else也一样可以完成多重选择的任务,不过switch()内置3-999个标签,使用switch()的程序运行速度可能稍快一些,代码也更简洁。

  

  4、if()的使用

    我们都知道if()用于判断。那么当你判断两个字符相等时,你会怎么写?

    int a;
       if (a == 5)

    如果是我,我会写成

    int a;
       if (5 == a)

    功能是一样的,何必这么麻烦?我们在做一个项目时,不可能十行代码调试半个小时,当我们思维在键盘上飞舞时,我才体会到一个程序员的快乐。在我们正在体验这种快乐时,手误在所难免,如果写成if(a=5),这个错误够你找两天的。但是if(5=a),编译器就可以帮你找到。

    当你想判断两个字符串是否相等时,你会不会这样写?

    char c_c1[10] = "acdefg";

    char c_c2[10] = "qwerty";

    if (c_c1 == c_c2)或者if (c_c1[10] == c_c2[10])

    总之都是不对的。字符串,数组,指针后面还会有所涉及,这里不做详谈。

    比较两个字符串我们用到了C函数库中提供的strcmp()函数,它包含在<string.h>头文件中。strcmp()返回了一个int类型的值,-1,0,1。0代表两个字符串相等,-1,1是由于字符之间的比较(ASCII)决定的。下面我看一个例子。

    char c_ch[10] = "qwertyui"  //最多可以存放9个字符,最后一位是”“

    char *p_ch = "adfgf"

    strcmp(c_ch, p_ch);  //strcmp()中的两个参数是两个字符串的地址

    strcmp("qwertyui", "adfgf");  //这样也是可以的,字符串存放的也是首字母的地址,后面会提到。

    

  5、你知道头文件的作用么?

  我们写代码都习惯把#include <stdio.h>最先写上,但你知道它的作用么?

  #include :文件包含

  当预处理器发现#include 指令后,就会寻找 <>中的文件名并把这个文件包含到你写的程序代码,替换源文件(就是你写的文件)中的#include。虽然你只写了一行代码,但是编译器却给你添加了很多代码。

  <stido.h>包含了输入输出函数,所以在程序的调试阶段,他是不可少的。

  #include "gaozy" 这类头文件你见过么?它与#include <stdio.h>又有哪些区别呢?<>包含的头文件,编译器会在系统目录中搜索。“”包含的头文件,编译器会先在当前工作目录中搜索,如果没有,再去系统目录中搜索。也可以简单的理解成C函数库里的函数在<>包含的头文件里,而我们自己写的函数则放在“”文件中。

  6、变量作用域

  int i;

  while (1)

  {

    int i = 0;

    printf("%d", i);

  }

  这样会报错么?当然不会,因为两个int i;并不在同一个作用域。

  那么什么是作用域,怎么分辨作用域呢?

  我们通常把一个{}内的代码看成在一个作用域。这也是为什么case里不可以定义变量的原因。

  只有{}才可以分辨作用域么?

  当然不是

  while (1)

    int i;

  printf("hellow world!");

  int i;自己单独在一个作用域中,printf()只会执行一次,当然这是一个死循环,不会执行到printf()。现在你有点略懂了么?跟while()相同的还有for(),if()

  还有个问题你想过么?第一次循环定义一个 i,第二次循环又定义一个 i,这不重复定义了么?

  当然不是,作用域内的变量是有生命周期的。也就是说,当第一次循环结束,你所定义的 i 的内存空间已经被释放。

  

  7、伤脑筋的bug

  你写过这样的程序么?

  int i;

  while (1)

  {

    int i = 0;

    printf("%d", i);

    int j;

  }

  编译器是这样报错的::c浅谈c.c(49) : error C2143: syntax error : missing ';' before 'type'

  我英文不好,不理解这句话是什么意思,但是这段代码里根本不存在缺少;的情况。

  我将代码粘到vs2010中,它是可以运行的。我一度怀疑是编译器出错了。但事实上编译器是很少出错的,纠结了很久,我突然想起来,c语言中规定,变量是必须在作用域的开始定义。vs210中,我用文件是后缀是.cpp的,就是是说c++允许在其他函数之后定义变量,而c对此则表示不支持。

  你写的代码有没有之前运行正常,你只是修改了一点,语法上别没有出错。但编译器却报错了呢?

  LINK : fatal error LNK1168: cannot open Debug/SMIS.exe for writing

  之前遇见这个问题也是纠结很久,为什么一会儿可以编译,一会儿又让我等待呢?

  这也不是编译器出错了。而是你执行了程序,没有关闭,对代码进行了修改,又要执行程序。

  编译器如果会说话:“小子,你拿我开心呢吧,还能不能一起玩耍了。”

  当你malloc时,要记得free啊,还有malloc之后跟上一个判断是有必要的。

  p_head = (P_STUDENT)malloc(sizeof(STUDENT));
     if (NULL == p_head)
     {
         printf("动态内存分配失败,程序结束!");
         exit(-1);
     }

  如果没有if()判断的话,在内存分配失败时,运行程序会出错,而且很难找到错误原因。

  8、字符串,数组,指针

  谈到字符串、数组、指针,那么我们就不得不谈内存了。我们可以把内存理解成一排很长很长的房子,它是一个线性结构。房子里住着不同的人家,每个房子有不同编号。

人家对应的是计算机里定义的变量,房子编号对应的是变量所在地址。所以,我们每定义一个变量,都会占用一套房子,当房子被全部占用时,你的电脑就崩溃了。当然,我们电脑的内存还是很大的。下面来看看字符串、数组、指针之间的关系。

  字符串以“/0”结束,所以字符串长度要在实际长度上+1

  数组名就是数组首元素的地址

  char ch[10] = "asdfg";  //ch[10]是定长数组

  char ch[] = "asdfg";  //ch[]没有规定长度,但是在定义时必须初始化

  char *p_ch = "asdfg";  //指针指向字符串的首地址

  这三种定义字符串的方法差不多。ch和p_ch都是地址。

  数组定义之后不可改变,指针还可以指向其他地址,但要注意内存泄露。

  

  9、指针与地址

  有些书上说指针就是地址,其实这种说法并不准确。

  那么指针是什么,地址又是什么?

  首先地址就是地址,物理内存的编号。

  指针是一个变量,这个变量存放的内容是内存的物理地址。就像int i; i 是一个变量,存放一个 i 值。指针变量也存放一个指针值,这个值是一块物理内存的地址。

  其实指针是地址还是变量并不影响我们对指针的使用。

  10、你用过宏么?

  #define MAX 60

  就是MAX = 60;的意思,之后的代码中MAX都等于60。宏使我们的程序维护更简单,也更容易理解。

  11、malloc与free

  有人说new跟delete更强大,但我并这么认为,new和delete是c++中的运算符,提供了对对象的操作。而malloc和free是c语言中用来进行动态内存分配的,不具备可比性。
C语言是面向过程的程序设计,根本没有对象的概念。

原文地址:https://www.cnblogs.com/ITgaozy/p/4395987.html