C语言

我的C语言

正式接触编程,大概是一年连六个月吧,这真是一个令人着迷的领域

0x01-C语言序言

倒是觉得写代码首先不是语法,而是格式,任何时候任何地点,要是自己的代码难以理解,要么你是故意的,要么你就是菜菜

一个难以被人理解的代码在我看来是没有太多的潜力的,但不排除故意为之的情况,也许很多人说这是强迫症,但是无论打开哪一个开源代码,你看到的都将是一个拥有规范的代码文件

也许有人说人不应该被限制,不应该拘泥于小节,但是当一个工程超过一千行,也许不用只需要不到五百行,就能完全暴露出代码规范的重要性,包括缩进,变量命名,接口存放,接口参数的规范之类,听起来似乎很虚谷歌代码规范(翻墙后查看)

在我看来C语言的内建语法真是无比简洁,几乎存在既有道理,简洁不代表着不强大,强大的某些地方在近来渐渐复苏的Lisp身上也有体现。
if, for, while, switch

组成了每个C程序的半壁江山
" + " " - " " * " " / " " % " " = "

组成了各式各样的算法计数
">>" "<<" "|" "&" "^" "~" "!"

让C语言有了更高效的算法以及更奇妙的思路
struct union #define return

而这些则让C语言在这乱世纷争中站稳了脚跟,并且一枝独秀
"{}" "()"

让代码不再无序混乱
"type * " "&" "()" "->"

让C语言在这个世界无处不在 " . " "[ ]" " < " " > " " == "

更是无处不在
还记得他们吗?我想这一辈子都忘不了了

0x02-编程带给我的

是快乐而不是痛苦,如果你觉得编程痛苦,请放下你手头的工作,找找自己真正想要的,无论从什么角度来看,你都应该放弃令你痛苦的事情,花上三杯茶的时间,看看自己的心到底喜欢什么。

C语言可谓是让一个程序员最难以感受到自己进步的编程语言了吧,一个窗口就让无数程序员再也走不出来。或者迷失,或者停滞不前,或者放弃,一个人最恐惧无助,甚至彷徨的时候,就是在努力之后却感受不到自己在进步,努力的白费是所有人不愿意看到的,在C语言的道路上,无外乎几个坎,学完所有语法,却不知所措接下去该怎么做?有心人在无尽的探索之后发觉,啊!标准库!啊算法!嗯对了,以后呢?啊!操作系统!然而自学的路上充满着坎坷,艰辛,无助,烦恼,那又如何?喜欢就好。

所谓师傅领进门,修行在个人,这句话在我看来有两个重要点,却是现在大学生几乎缺失的。师傅一词告诉我们,要不耻下问,要善于询问,而不是伸手即来思想,"提问的智慧"在我看来是一门很重要的课程,特别是在当今信息时代。而更重要的是,不要有先入为主的思想,在这两年的自学历程里,见过太多后来者居上的事迹,当你一直认为自己一定比后辈强时,你就注定输了,所以不耻下问才是最重要的。但是如果师傅是那么容易找到的,那就不会有学校了,个人指的并不是孤军奋战,而是要善于自己发现问题,解决问题,这个过程自然少不了请教他人

编程是一种信仰,至少在我认为是这样的,请把它当作信仰,它能给你快乐,给你充实,当然也不要忘了现实

0x03-C代码

  #include <stdio.h>
  int main(void)
  {
     printf("That is Right Style\n");
     return 0;
  }

在一个标准的C语言程序中,最重要的莫过于main函数了,而说到底它就是一个函数而已,仅仅因为它地位特殊拥有第一执行权力,换句话说,难道因为一个人是省长它就不是人类了?所以函数该有的它都应该有,那么函数还有什么呢?

函数大体上分为内联函数(C99)(内联函数并非C++专属,C语言亦有,具体见前方链接)和非内联的普通函数,它们之间有一个很明显的特点(一般情况下),那就是不写原型直接在main函数上方定义,即使不加'inline'关键字,也能被编译器默认为内联函数,但之后带来的某些并发问题就不是编译器考虑的了。

普通函数正确的定义应该是声明与定义分离,声明就是一个函数原型,函数原型应该有一个函数名字,一个参数列表,一个返回值类型和一个分号。定义就是函数的内在,花括号内的就是函数的定义:

  ...
  int function(int arg_1, float arg_2);
  ...
  int main(int argc, char* argv[])
  {
      int output = function(11, 22.0);
      printf("%d\n",output);
      return 0;
  }
  int function(int arg_1, float arg_2)
  {
      int    return_value  = arg_1;
      float  temp_float    = arg_2;
      return return_value;
  }

依上所述,当非必要时,在自己编写函数的时候请注意在开头(main函数之前)写上你的函数的原型,并且在末尾(main函数之后)写上你的函数定义,这是一个很好的习惯以及规范。所谓代码整洁之道,就是如此。

函数的另一种分类是,有返回值和无返回值,返回值的类型可以是内建(build-in)的也可以是自己定义的(struct, union之类),无返回值则是void。

1==>为什么我们十分谴责'void main()'这种写法?因为这完全是中国式教育延伸出来的谭式写法,main函数的返回值看似无用,实际上是由操作系统接收,在Windows下也许无甚"大碍"(实际上有),当你使用过Linux之后你就会发现一个C语言程序的main返回值关系到一个系统是否能运行,这里稍微提一句,0在Linux程序管道通信间代表着无错可行的意思。所以请扔掉void main这种写法。

2==>为什么我们对 'main()'这种省略返回值的写法置有微词?能发明这种写法的人,必定是了解了,在C语言中,如果一个函数不显式声明自己的返回值,那么会被缺省认为是int,但这一步是由编译器掌控,然而C语言设计之初便是让我们对一切尽可能的掌握,而一切不确定因子我们都不应该让它存在。其次有一个原则,能自己做的就不要让编译器做。

3==>为什么我们对参数放空置有不满(int main())?在C语言中,一个函数的参数列表有三种合法形态:

  int function();
  int function(void);
  int function(int arg_n);
  int function(int arg_n, ...);

第一种代表拥有未知个参数,第二种代表没有参数,第三种代表有一个参数,第四种代表拥有未知个参数,并且第一个参数类型为int,未知参数在C语言中有一个解决方案就是,可变长的参数列表,具体参考C标准库,在此我们解释的依据就是,我们要将一切都掌控在自己的手中,我们不在括号内填写参数,代表着我们认为一开始的意思是它为空,正因此我们就应该明确说明它为void,而不该让它成为一个未知参数长度的函数,如此在你不小心传入参数的时候,编译器也无法发现错误。

4==>' int main(int argc, char* argv[]) ' 和 ' int main(void) '才是我们该写的C语言标准形式

对于缩进,除了编译器提供的符号缩进之外,我们可以自己给自己一个规范(请少用或者不用Tab),比如每一块代码相教上一个代码块有4格的缩进。

对于学习C语言,请使用.c文件以及c语言编译器练习以及编写C程序,请不要再使用C++的文件编写C语言程序,并且自圆其说为了效率而使用C++的特性在C语言中,我们是祖国的下一代,是祖国的未来,请不要让自己毁在当下,珍爱编程,远离清华大学出版社
综上所述,在开始编写一个标准C语言程序的时候,请先把下面这些东西写上:

  #include <stdio.h>
  int main(void)
  {
     return 0;
  }

对于main的参数,有兴趣的可以查阅我的文章,或者自行谷歌,在此问题上百度也是可以的。

0x04-C语言变量

C语言在明面上将数的变量分为两类,整型变量以及浮点数,对应着现实世界的整数和小数。

  • 首先是整数,使用了这么多的C语言之后,每当在使用整数之时都会将其想象成二进制的存在,而不是十进制。原因在于,这是程序的本质所在,稍有研究编译器工作原理的都会发现,在编译器处理乘法乃至除法的时候,优秀的编译器总会想方设法的加快程序的速度,毫无疑问在所有运算中移位运算是最快速的"乘法"以及"除法":
    1<<2 == 4 ,8>>2 == 2
    而正常一个乘法相当于十数次的加法运算的时间消耗,移位则不用(除法的消耗更大,但是随着CPU的进步,这些差距正在逐渐缩小,就目前来看依旧是有着不小的差距但无论如何优化,乘法时间都会大于加法)。正如前面所说,C语言设计之初便是给了程序员所有的权利,而程序员要做的就是掌控所有能掌控的,即便是数的计算亦是如此,比如在优秀的编译器看来:
    2*7 ====> (2>>3) - 2
    5*31 ====> (5>>5) - 5
    毫无疑问经过编译器优化后的代码此前者要快许多。这就是为什么我们要将一个数看作二进制,这不仅仅是表面,而是要在深层次的认为它是二进制,总体来说C语言的整型是非常简洁明了的总体分为 有符号无符号,很好理解只需要注意不要让无符号数进行负数的运算,这里有一个原则,可以很好的规避这种无意之过,不把无符号类型变量和有符号类型变量放于同一运算中,时刻记得保持式子的类型一致是设计时的保障。
  • 浮点数,由于实数域可以看作稠密的,故除了整数以外,还有无数的小数,而小数在计算机中如何表示?一种无限的状态是无法在计算机中被精确表示,所以有了浮点法,关于浮点法可以参考书籍《深入理解计算机系统》
    这里介绍的是在C语言中我们应该如何正确使用浮点数?很多人(包括我)在初作之时总是想当然的以为计算机是无所不能的,连人类都无法完全表达出来的小数计算机一定可以,实际上并非如此,在这里我可以说,计算机只是近似表达,而最大的忌讳的便是将两个浮点数进行比较,此处介绍一种浮点数常用的比较方法,精确度法:

    
      #define DISTANCE 0.00000001
      ...
      float fx1 = 20.5;
      float fx2 = 19.5;
      if(fx1 - fx2 < DISTANCE)
            printf("They are Equal\n");
      else
            printf("Different\n");
    
    所以说,在很大程度上,当你在程序中使用了浮点数,又直接使用浮点数进行比较,却发现始终无法达到预期效果,那么你可以检查一下,是否是这个原因,在这一点上,不得不说是C语言的一个缺憾。

  • 指针变量,是一种比较特别的变量,以至于总是对它进行特别对待。这里有几个原则:

    • 两个不相关的指针进行加减操作是无意义的
    • 始终确保自己能够找到分配的内存
    • 无论何时何地何种情况,都要记住,不使用未初始化的指针,不让未使用的内存持续存在。
    • 指针在不同位的操作系统上的大小是不一样的,但是在同一个操作系统下,无论什么类型的指针都是相同大小,这涉及到指针的寻址问题 (题外话:C语言的寻址实际上使用了汇编语言的间接寻址,有兴趣的可以自行尝试,方法之一,使用gcc编译器的汇编选项,产生汇编代码,进行一一比对),对于寻址一个笼统一些的说法便是
      4Byte = 32bit
      2^32 = 4G
      所以32位的操作系统下C语言指针:
      
      ...
      sizet what = sizeof(void);
      printf("%d", what);
      ...
      
      输出:$root@mine: 4
      对于大部分使用者来说,指针主要用来降低内存消耗以及提高运算效率的,这里设计许多学问,我也无法一一展示,比较有意思也常用的两个东西便是递增以及语法糖:++, ->
      
      ...
      int dupli[10] = {0};也可以使用库函数memset()进行置0
      int *pointome = dupli;
      int me = 100;
      while(pointtome < (dupli + 10))
          *pointtome++ = me;
      
      其中*pointtome++ = me在C语言应用广泛它相当于是:
      
      *pointtome = me;
      pointtome++;
      
      的语法糖,对于++,在非必要的情况下,请使用前缀递增,而非后缀递增,原因是消耗问题,仔细想想这两种递增的区别在何处?
      前缀递增总是在原数上进行递增操作,然而后缀递增呢?它首先拷贝一份原数放于别处,并且递增这份拷贝,在原数进行的操作完毕后,将这份拷贝再拷贝进原数取代它,此中的操作涉及的更多,所以在非必要的情况下,请使用前缀递增而不是后缀递增(递减也是同样的道理)
      ->则是在结构体上使用的非常广泛:
      
      typedef struct data{
          int test;
          struct data next;
      }mystruct;
      ...
      mystruct temp;
      mystruct ptemp = &temp;
      ptemp->test = 100;
      ptemp->next = NULL;
      if(temp.test == 100)
           printf("Correctly!\n");
      else
          printf("That is impossible!\n");
      ...
      
      可以很清楚的看出其实ptemp->test便是`(ptemp).test`的语法糖

未完待续

Create By WuShengXin@2015,请勿用于商业.

Written on May 7, 2015