相信肯定有些人和我一样,没学过C语言,是从做网页开始学习编程的,直接从ASP学ASP.NET,从VBScript学到C#的。现在c#比较熟悉了,我们可以回过头来做一下C语言的入门,补一课。 

介绍

学一门语言,最好的办法就先系统的看一遍教程,然后多多动手实践,我们拿老赵出的字符串提取信息的题目开始学习,题目要求如下:
趣味编程:从字符串中提取信息
http://www.cnblogs.com/JeffreyZhao/archive/2009/10/12/code-for-fun-tokenizer.html
老赵后来给出了c#的参考答案,用状态机实现的,如下
趣味编程:从字符串中提取信息(参考答案 - 上)
http://www.cnblogs.com/JeffreyZhao/archive/2009/10/21/code-for-fun-tokenizer-answer-1.html
后来有给出了一个f#的版本,用了f#的模式匹配,如下
趣味编程:从字符串中提取信息(参考答案 - 下)
http://www.cnblogs.com/JeffreyZhao/archive/2009/10/22/code-for-fun-tokenizer-answer-2-fsharp.html
现在我们参考c#的实现,再用C语言来实现一个,因为C语言和C#有很多区别,而我们比较熟悉c#语言,所以我们可以对比着来学习。首先要说的是c和c++是两门不同的语言,虽然c++编译器能编译c代码。下面的介绍中也会提到一些c和c++的区别。精通C语言不是特别难,如果要精通c++很难,C++的细节太多了。
C语言入门教程
http://zhuanti.club.it.sohu.com/user_webpage/webpages/commuser/web1_13497.html
初学者,你应当如何学习C++以及编程
http://club.it.sohu.com/read_elite.php?b=program&a=3473490

引入命名空间

c#有using语句用来引入一个命名空间,本文件就可以使用该命名空间下的类了,c语言里没有命名空间的概念,如果要使用C函数库里的函数的话,使用#include语句来把那个头文件包含进来就可以使用了,如下
#include  < stdio.h >
但c++里就可以使用using来引入命名空间,比如
using   namespace  std;
类库
.net开发有.net framework,c没有这么强大的类库,但c也有一些标准的函数库,比如stdio.h里定义了输入输出的一些函数,stdlib.h定义了一些内存分配,类型转换等函数,math.h里定义了一下数学相关的函数。
注意不要把c标准函数库和c++标准程序库混淆了,c++标准程序库比c标准函数库强大多了,增强了字符串相关的类,还有一些数据结构,异常处理方面的类。另外还有个c++标准模板库,就是STL,这个大概相当于.NET里System.Collections.Generic下的类,主要是有一些通用的容器的数据结构和用于查询数据的定位器,c语言只能用第一个,c标准函数库,另外两个都不能用。
c标准函数库
http://zh.wikipedia.org/zh-cn/C%E6%A8%99%E6%BA%96%E5%87%BD%E5%BC%8F%E5%BA%AB
c++标准程序库
http://zh.wikipedia.org/zh-cn/C%2B%2B%E6%A8%99%E6%BA%96%E7%A8%8B%E5%BC%8F%E5%BA%AB
c++标准模板库
http://zh.wikipedia.org/zh-cn/%E6%A0%87%E5%87%86%E6%A8%A1%E6%9D%BF%E5%BA%93

宏,枚举和常量

c语言里可以用#define宏和enum类型来定义常量,在程序中没有地方存它的值,在编译的时候就分配好内存了;而用const修饰的变量叫只读变量,需要在内存里开辟一个地方保存它的值,由编译器来标识它不可以修改。在c#里const和readonly关键字定义了编译时常量和运行时常量,这两种常量在使用上也有一些不同,比如a程序集定义了一个const常量,b程序集引用a程序集,a的const常量修改了后,b程序集要重新编译才能使修改生效,而如果a程序集里是用readonly定义的常量,b就不用重新编译。如下,定义了一个常量和3个只读变量
#define  MAX_TOKEN_LEN  20

const  e_arg_invalid  =   - 1 ;
const  e_token_overfllow  =   - 2 ;
const  s_ok  =   0 ;

注意#define语句后面没有分号,关于常量和只读变量的更多的细节上的区别,见下面的链接
C语言程序设计之正确使用const
http://tech.sina.com.cn/s/2004-09-17/0735426801.shtml
CLR Via C# 学习笔记- 常量和字段(const readonly)
http://www.cnblogs.com/oec2003/archive/2009/06/22/1508051.html

函数原型定义

在c#里,一个文件里一个函数无论定义在哪里,另一个函数都可以直接使用,但C语言里,一个函数的定义必须在它的使用之前,如果一个函数的定义在较靠后的地方,而上面有个函数要调用这个函数,就需要把被调用的这个函数在文件比较靠上的部分进行原型定义。原型定义就是把这个函数的签名写出来,表示下面会有这么一个函数,一般函数定义写在头文件里。如下就是对字符串状态解析用的5个函数的原型定义。
void *  p1( char  ch);
void *  p2( char  ch);
void *  p3( char  ch);
void *  p4( char  ch);
void *  p5( char  ch);
如何写出专业的C头文件
http://www.yuanma.org/data/2007/0523/article_2618.htm

函数指针

c#里的委托咱们都十分的熟悉,它指向一个方法链,执行它的话,这一串儿方法都会执行。在c里没有委托的概念,但有函数指针的概念,函数指针可以指向一个函数,调用这个函数指针,真正的方法也会调用(但会访问两次内存,效率比直接调用低),可如下c#代码如何用c来表示呢?
delegate  StateParser StateParser( char  ch);
StateParser这个委托的参数是char类型,但返回值又是它本身的类型,c语言虽然支持返回函数指针的函数,却不支持这种循环定义,所以我们需要一个无类型的指针void*来定义这个函数指针的返回值,然后再必要的时候进行类型转换,如下。
typedef  void *  ( * fn_parse)( char  c);
该语句定义了名为fn_parse的函数指针,它需要一个char类型的参数,并返回void*类型。
本篇帖子的目的是为了学习c语言,其实这个题目用状态机来解,直接用几个状态处理函数之间相互调用就行,不定义StateParser这个委托就行,但现在我们来完全模仿老赵的c#实现。

LINQ

呵呵,C语言当然不会有LINQ了,更不会有string的Aggregate方法了,所以我们要自己实现一个aggregate方法,该方法需要两个参数,第一个参数是fn_parse类型的函数指针,第二个参数是一个字符串,遍历这个字符串,分别用每个字符作为参数去调用parse函数指针,并把其返回值也转换成fn_parse类型,然后用它去处理下一个参数。
代码

void  aggregate(fn_parse parse,  const   char *  input){
      while ( * input  !=   ' /0 ' ){
         if (last_error  ==  s_ok){
            parse  =  (fn_parse)( * parse)( * input);
            input ++ ;
        } else {
            printf( " ocurr an error:%d " , last_error);
             break ;
        }
     }
}
看下面这句
parse  =  (fn_parse)( * parse)( * input);
*用在一个指针的前面的时候是表示取值,*parse是取出函数指针parse所指向的函数,前面的(fn_parse)把返回的函数的地址void*转换成fn_parse类型,*input是取出input指针所指向的字符。
LinQ方法之---------Aggregate
http://www.cnblogs.com/srymbud/archive/2008/10/26/1320004.html

异常处理

c#里异常处理可以用try语句来实现结构化异常处理,在c里一般用返回码来做异常处理,我们先声明了一个全局变量last_error表示最后一次出现异常的错误码,然后定义了几个只读变量来表示可能的错误码,如下。
const  e_arg_invalid  =   - 1 ;  /*  错误的参数 */
const  e_token_overfllow  =   - 2 ; /*  数组溢出  */
const  s_ok  =   0 ;
int  last_error  =   0 ;
c里只支持/**/括住的注释,c++里支持//格式的注释,我们在每调用一次函数之后,要判断last_error是否为s_ok,如果不是,说明函数执行出错了,上面的aggregate里就用了这样的异常处理机制。在函数的定义里,应该在函数的开始把last_error设置为s_ok,出错的时候设置为相应的错误码。

字符串

c#的字符串很强大,String类型有很多方法,而且.net 3.5又加了一些扩展方法,使用起来非常方面,c里就没这么方便了,c里的字符串是char的数组,并以/0结束。可以用char[]来表示,因为数组可以用指针表示,所以也可以用char*来表示。然后有一些库函数可以操作字符串,但都比较简单。c++里定义了string类型,以及支持多字节编码的wchar_t等类型,c/c++里的字符串由于历史原因有很多细节的问题,具体看下面的两个链接。
C++字符串完全指引之一 
http://www.vckbase.com/document/viewdoc/?id=1082
C++字符串完全指引之二
http://www.vckbase.com/document/viewdoc/?id=1096

输入输出

c#里有Console.Write和Console.Read两个方法进行输入输出,在c里有printf和scanf,这两个函数可以进行格式化输出和输入,常用的%d表示int,%c表示字符,%s表示字符串,更详细的查看以下链接吧。
printf() 和 scanf() 使用
http://fxl.blogbus.com/logs/2123615.html

结构,类,面向对象

c语言是结构化语言,没有类,接口等面向对象的特性,但c里有struct,但struct不是一种类型,只是一种标识,不能用来声明变量,要声明变量前面还要加前缀struct,如下定义了一个token的结构,注意,结尾处有分号
struct  token{
        char *  inner_token;
        int  index;
};
要声明一个这个结构类型的指针变量,用如下语句
struct token* current_token;
c语言的结构不能定义成员函数,只能定义数据成员,但c++里可以。c语言里只能定义函数指针类型的成员,比如
struct  token{
        char *  inner_token;
        int  index;
        void  ( * append)( struct  token *  t,  char  ch);
};
但这个函数指针指向的函数里不能用this,所以参数里还需要传入一个struct token*类型的参数,我觉得这种方式还不如定义一个纯粹的函数来的直接呢,如下
void  append( struct  token *  t,  char  ch){
      if (t  ->  index ++   >  MAX_TOKEN_LEN){
         last_error  =  e_token_overfllow;
          return ;
     }
     t  ->  inner_token[ t  ->  index ]  =  ch;
}
我们定义这个函数是往token里添加一个字符,在c#里用+操作符就可以把两个字符串拼接成一个新字符串,在c里我们要这么的大费周折,定义结构,定义函数啥的,其实在C语言里特意的去模拟面向对象也没什么好处,主要是把代码组织好就行了。其它的token_group,parse_result也以类似的方式定义了。有一个链接可以推荐给大家,如下:
c的面向对象思想
http://blog.csdn.net/liyuming1978

内存管理

如果在一个函数里声明一个变量,在执行这个函数的时候它自动会在栈上分配内存,等函数执行完毕,这些内存会自动释放。如果是全局变量或者static变量的话,在编译时已经分配了好了内存,程序运行中会一直存在。如果在程序的逻辑里需要动态的创建一些对象,需要用malloc或calloc来动态分配内存,用free来释放动态分配的内存,如下reset函数用来创建一个token的新实例并赋值给current_token,用malloc来给自身分配内存,用calloc给其成员inner_token分配内存。
void  reset(){
   current_token  =  ( struct  token * )malloc( sizeof ( struct  token));
   current_token  ->  inner_token  =  ( char * )calloc(MAX_TOKEN_LEN,  sizeof ( char ));
   current_token  ->  index  =   - 1 ;
}
如下的函数用来释放一个动态创建的struct token对象的内存,先释放它的成员所分配的内存,然后释放它自己所分配的内存。
void  dispose( struct  token *  t)
{
   free(t  ->  inner_token);
   free(t);   
}
C语言:malloc()函数与alloc()函数
http://webservices.ctocio.com.cn/net/437/9244937.shtml
c#里有自动垃圾回收功能,在C里没有,用C语言实现垃圾回收的帖子不太多,我找了几个,还没细看,如下
浅议C 中的垃圾回收方法
http://www.abc188.com/info/html/chengxusheji/C-C--/20080224/9601.html
主题:我也研究下云风的垃圾回收库
http://www.javaeye.com/topic/352733
我写了一个C语言的GC库.用法是这样的:
http://bbs.pfan.cn/post-220504.html
关于内存管理这块儿,还需要说一下static关键字,如果static修饰一个全局变量,它则改变了这个变量的作用域,表示这个全局变量只能在本文件里定义的函数里使用;如果static修饰一个函数内的局部变量,它则改变了这个变量的存储位置,默认函数内的局部变量是在栈上分配的,如果用static修饰,则它变成在全局存储区存储,在函数调用执行完后不会释放,如果一个函数要返回一个指针的话,这个返回的指针必须指向一个static修饰的局部变量,否则就会返回错误的指针。
难点分析——C语言之static变量声明的辨析
http://www.ccidedu.com/art/1925/20040927/159537_1.html
c语言中static变量
http://doc.linuxpk.com/6049.html

函数

前面讲到过函数的原型是用来先声明有这么一个函数,然后函数就可以在下面定义了,处理本题目的5种解析状态的函数分别定义成p1到p5,和c#的差不多,比如p1如下
代码

void *  p1( char  ch){
    if  (ch  ==   ' - ' ){
      last_error  =  e_arg_invalid;
       return  NULL;
   }
    if  (ch  ==   ' / '' ){
       return  p5;
   }
    else {
      append(current_token,  ch );
       return  p4;
   }
}

主方法

以上定义好了用来解决这个问题的数据及操作这些数据的函数,那么再定义一个总的函数来对这些数据和函数进行单元测试吧,如下
代码

void  test_parse(){
    int  i, j;
    struct  token_group *  group;
    struct  token *  t;
   
   reset();
   reset2();
   reset3();
   
    char *  str  =   " cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s' " ;
   aggregate( & p1, str);
   append2(current_group, current_token);
   append3(result, current_group );

for (i  =   0 ; i  <=  result  ->  index; i ++ ){
      group  =  result  ->  groups[i];
      printf( " group:%d/r/n " , i);
       for (j  =   0 ; j  <=  group  ->  index; j ++ ){
         t  =  group  ->  tokens[j];
         printf( " /ttoken:%d-%s/r/n " , j, t  ->  inner_token);
      }      
   }
   
    for (i  =   0 ; i  <=  result  ->  index; i ++ ){
      group  =  result  ->  groups[i];
       for (j  =   0 ; j  <=  group  ->  index; j ++ ){
         t  =  group  ->  tokens[j];
         dispose(t);
      }
      dispose2(group);
   }
   dispose3(result);
}

c函数里局部变量要在函数开始声明,c++不用。

完整的代码见如下链接
http://www.cnblogs.com/onlytiancai/archive/2009/12/12/1622734.html

小节

从c#到c,看起来好像在倒退,却更像是返璞归真,把基础补一补,换一种思路来考虑编程。新语言层出不穷,c#4.0,f#,python,erlang,都很有特点,但C何尝不是呢。

勘误:

23:25 :去掉了函数指针强转的一处错误描述。

蛙蛙推荐:从C#到C语言相关推荐

  1. 蛙蛙推荐:微软网络讲座系列教程视频下载

    蛙蛙推荐:微软网络讲座系列教程视频下载(2004年1月到2005年4越) 好多都是很经典的问题解答和技巧应用,推荐大家有空看看 总体浏览地址 http://www.microsoft.com/chin ...

  2. 蛙蛙推荐:蛙蛙牌软件注册码算法

    蛙蛙推荐:蛙蛙牌软件注册码算法 摘要:辛辛苦苦写个共享软件,又怕被人破解,所以就会想到用注册码的方式来激活软件.本蛙给大家一个简单的思路来实现软件注册码算法,当然.net做的东西很容易被人破解,反编译 ...

  3. 蛙蛙推荐:在c#使用IOCP(完成端口)的简单示例

    蛙蛙推荐:在c#使用IOCP(完成端口)的简单示例 上次给大家发了利用winsock原生的api来做一个同步的socket服务器的例子,大致上只是贴了一些代码,相信大家这么冰雪聪明,已经研究的差不多了 ...

  4. 蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园

    蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园 蛙蛙推荐:蛙蛙教你文本聚类 - 蛙蛙王子 - 博客园 蛙蛙推荐:蛙蛙教你文本聚类 摘要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一 ...

  5. 蛙蛙推荐:利用WMI脚本批量恢复SQLSERVER数据库

    转载原文: 蛙蛙推荐:利用WMI脚本批量恢复SQLSERVER数据库 蛙蛙推荐:利用WMI脚本批量恢复SQLSERVER数据库 问题提出 蛙蛙求助:以编程的方式还原sqlserver数据库问题 我有一 ...

  6. 蛙蛙推荐:如何编写高质量的python程序

    蛙蛙推荐:如何编写高质量的python程序 蛙蛙推荐:如何编写高质量的python程序 - 蛙蛙王子 - 博客园 蛙蛙推荐:如何编写高质量的python程序 如何编写高质量的python程序 目录 代 ...

  7. 蛙蛙推荐:蛙蛙牌网页捕捉器

    蛙蛙推荐:蛙蛙牌网页捕捉器 摘要:你有没有看到一篇好文章想保存到本地,有没有想过只保存网页选中的部分而不要那些不必要的导航和广告,本贴告诉你达到这个目的的思路及主要代码. 思路:首先我们要获取到所有I ...

  8. 蛙蛙推荐:蛙蛙教你文本聚类

    蛙蛙推荐:蛙蛙教你文本聚类 摘要:文本聚类是搜索引擎和语义web的基本技术,这次本蛙和大家一起学习一下简单的文本聚类算法,可能不能直接用于实际应用中,但对于想学搜索技术的初学者还是有一定入门作用的.这 ...

  9. 蛙蛙推荐:蛙蛙浏览器

    蛙蛙推荐:蛙蛙浏览器 摘要:google推出了自己的网页浏览器,现在web浏览器的竞争更激烈了,各有各的用户群.其实有另一个领域没有多少竞争,那就是应用程序浏览器,今天给大家演示的蛙蛙浏览器,不仅可以 ...

  10. 蛙蛙推荐:一个程序员2012年技术学习总结 - 蛙蛙王子 - 博客园

    蛙蛙推荐:一个程序员2012年技术学习总结 - 蛙蛙王子 - 博客园 蛙蛙推荐:一个程序员2012年技术学习总结 - 蛙蛙王子 - 博客园 俗一吧,也总结一下,程序员,代码说话. posted on ...

最新文章

  1. 用seaborn 画出唯美的论文专用图片,自己定制python画图的数据集
  2. android异常(2)
  3. CopyOnWriteArrayList源码分析
  4. 卷积神经网络图像卷积池化尺寸计算器
  5. 接入路由器做NAT,做限速的一些想法
  6. textmate开发一个blog
  7. 【C language】typedef的使用:结构体、基本数据类型、数组
  8. 月薪15k起,想进入这个行业有哪些书值得读?
  9. Spring : @Repository 注解
  10. [导入]新网络流行语 打酱油 三个俯卧撑
  11. FreeSWITCH权威指南-基础篇-1.4-信令
  12. yyuc php,yyuc框架介绍
  13. postman并发测试_PostMan接口压力测试
  14. 18个免费视频素材网站,超高清、不限速、无版权、可商用,1秒解决你90%的视频剪辑难题!
  15. 变转速数据集 -- 渥太华轴承数据集描述及下载链接
  16. 关于微信小程序的wx.request执行后sucess和fail的问题
  17. BP神经网络实现实例1曲线拟合
  18. BMS(Battery Management System)是什么?
  19. python 实现布谷鸟算法(CS)
  20. tar命令(linux解压缩命令)

热门文章

  1. Tomcat AJP协议文件读取漏洞
  2. Android开发完全讲义(第三版)已出版
  3. [#Linux] CentOS 7 安装微信详细过程
  4. 不懂建站技术如何快速搭建一个网站
  5. 定义Boat与car两个类,二者都有weight属性,定义二者的一个友元函数gettotalweight(),计算二者的重量和。
  6. 在postfix中实现基于cyrus-sasl的SMTP认证(转)
  7. Azure Kinect DK 首次配置流程
  8. 【自监督论文阅读笔记】Self-Supervised Learning from Images with a Joint-Embedding Predictive Architecture
  9. Code - Windows Overlapped
  10. pig4444tiger4444用友金蝶软件中勒索病毒解密ox4444后缀勒索病毒解密成功