相信肯定有些人和我一样,没学过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;
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);
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;
}
}
}
while(*input != '\0'){
if(last_error == s_ok){
parse = (fn_parse)(*parse)(*input);
input++;
}else{
printf("ocurr an error:%d", last_error);
break;
}
}
}