语言只是一种工具,任何语言之间都是相通的,一通则百通,关键是要理解语言背后的思想,理解其思想,任何语言,拿来用就好了。语言没有好坏之分,任何语言既然存在天然有它存在的价值。nginx
在一个处处是OOP的年代,为什么面向过程的C语言依然能够如此活跃?这主要得益于C语言自己的语言特性。C语言小巧灵活,并且还有一个直接与硬件打交道的指针的存在,因此它是嵌入式开发惟有的高级语言;正由于他的小巧灵活,咱们能够用它来开发一系列的小工具,Unix/Linux就是由这些小工具组成的操做系统;同时用C语言能够开发高性能的应用程序。正则表达式
一、数据类型。C是一门面向过程的语言,但它依旧能够实现大多数面向对象所能完成的工做。好比面向对象的三大特性:封装、继承、多态。apache
封装:C中有一种复杂的数据结构叫作struct。struct是C里面的结构体。编程
假如咱们要对person进行封装,person可能包括姓名、性别、年龄、身高、体重等信息。咱们就能够对它封装以下:数组
struct Person{ char name[20];//姓名 char gender; //性别 int age; //年龄 int height; //身高 int weight; //体重 };
当咱们要像OOP那样新建一个对象时,咱们就能够:数据结构
struct Person p;
咱们就能够直接对p进行赋值:并发
p.name = "whc"; p.gender = 'b'; //'b' = boy; 'g' = girl p.age = 25; p.height = 175; p.weight = 65;
继承:一样利用struct,咱们来建立一个学生结构,同时继承结构体Person,以下:编程语言
struct Student{ struct Person p; char number[20]; //学号 int score; //成绩 };
对Student进行建立对象,并赋值:函数
struct Student s; s.p.name = "whc"; s.p.gender = 'b'; s.p.age = 25; s.p.height = 175; s.p.weight = 65; s.number = "20150618"; s.score = 90;
多态:C中对于多态的实现能够借助函数指针来实现。为了简单起见,咱们假设Person这个结构体中,只有一个函数指针。工具
struct Person{ void (*print)(void *p); }; struct Student{ struct Person p; };
而Person和Student这两个结构体的print函数实现以下:
void printPerson(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; printf("run in the person!!\n"); } void printStudent(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; printf("run in the student!!\n"); }
咱们写一个函数来调用他们:
void print(void *person){ if(NULL == person) return ; struct Person *p = (struct Person *)person; p->print(person); } int main(){ struct Person person; struct Student student; person.print = printPerson; student.p.print = printStudent; print(&person); //实参为Person的对象 print(&student); //实参为Student的对象 return 0; }
他们的输出为:
其实这个也不难理解,不管是Person仍是Student,他们在内存中只有一个变量,就是那个函数指针,而void*表示任何类型的指针,当咱们将它强制转换成struct Person*类型时,p->print指向的天然就是传入实参的print地址。
二、 指针和内存管理
不管问哪个C工程狮:C语言中最容易出错的地方在哪?咱们基本上会获得同一个答案,那就是指针和内存溢出。那么指针是什么,指针其实就是一个地址,这个地址能够是一个变量的地址,也能够是一个函数的地址,不论是什么,反正都是内存中的一个地址。
例若有一个变量a,咱们定义一个指针来保存变量a的地址:
int a = 0; int *p = &a;
若是是一个函数呢?咱们定义一个函数,而后用一个函数指针来保存这个函数地址:
int min(int a,int b){ return a<b?a:b; } int (*f)(int,int); f = min;
可能咱们有时候会想,难道咱们只能先定义一个变量或者函数,而后把它的地址给指针么?不能直接使用指针,或者直接给指针赋一个常量么?首先,咱们不知道内存中哪些是可用的地址,哪些是不可用的,每当咱们定义一个指针时,这个指针指向的是一个未定义的内存,这个就是传说中的野指针。若是咱们给这个指针所指向的内存赋值,就有可能覆盖了一些很重要的数据,因此每当咱们定义一个指针时,最好给它赋一个初始地址或者NULL;若是咱们给一个指针赋常量,一样的道理。
指针的类型要与变量的类型一致(若是咱们不是故意要他们不一致),所谓类型,只是变量的一直表现形式,其实在内存中,他们不过是0101的二进制,当咱们用32bits的原码表示时,它就是unsigned;当咱们用32bits补码表示时,就是signed;当用浮点表示时就是float;当用更复杂的自定义表示时就是struct;用union能够很好的理解这些。
如今咱们来说一下内存,这里咱们只讨论用户内存区域:
通常分为5个区域:
(1)程序代码区:存放代码指令的地方
(2)全局(静态)变量区:包括初始化、未初始化的全局变量和静态变量
(3)字符常量区:存放一些字符串常量,在C语言里面,这个很容易与栈中定义的字符数组搞混,当咱们定义以下:
int main(){ char *str0 = "Hello World!"; //字符常量区 char str1[] = "Hello World!"; //栈区 return 0; }
str0所指向的字符串就是在字符常量区,可是str0自己的这个指针变量是在栈区的,这个变量存放的是字符常量区中"Hello World!"的首地址。
str1是字符数组,因此str1中所存放的字符串是在栈区,这里利用的不过是字符数组初始化的一种形式,其实它能够写成以下形式:
char str1[] = {'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};
(4)栈区:局部变量,形参,函数返回地址等,由系统来管理,在内存里面是由高地址往低地址生长,因此栈空间大小是有限的,当在栈中定义一个很大的数组或者使用很深的递归调用时,就有可能栈溢出。
(5)堆区:由malloc、calloc、realloc函数分配的空间,由咱们本身来管理,每次用完以后,必须用free释放内存,不然,就会产生内存泄漏,每次释放内存后,虽然再也不占用着这块内存中,可是对应的指针依然指向这块区域,这个指针就是野指针,因此释放内存后,建议给指针赋NULL。以下:
int main(){ int *p = (int*)malloc(100*sizeof(int)); /* 执行语句 */ free(p);//这时p依然指向那块内存,成了野指针 p = NULL; //对p赋值NULL return 0; }
三、C语言的I/O输入输出
C语言自己并不带有输入输出的特性,因此它的全部I/O操做都是经过系统调用来实现。幸运的是C标准库,已经给咱们封装好了一系列的I/O操做的函数。
putchar ():把变量中的一个字符常量输出到显示器屏幕上;
getchar ();从键盘上输入一个字符常量,此常量就是该函数的值;
printf ();把键盘中的各种数据,加以格式控制输出到显示器屏幕上;
scanf ();从键盘上输入各种数据,并存放到程序变量中;
puts ():把数组变量中的一个字符串常量输出到显示器屏幕上
gets ():从键盘上输入一个字符串常量并放到程序的数组中
一些为对文件的操做,因为一切皆可看做是文件,标准输入,输出也能够看成文件来操做,文件描述符:标准输入(0)、标准输出(1)、标准错误(2)
fputs();输出到文件
fgets();从文件输入
fscanf();格式化文件输入
fprintf();格式化文件输出
另外两个很重要的函数,固然还有他们的派生函数也是相似的
sscanf(); 从一个字符串中提取各种数据。
sprintf(); 把格式化的数据写入某个字符串
这里不对每一个函数进行详解,主要对格式化函数进行分析:
(1)当咱们要把一个字符串转换成一个整数或者把一个整数转换成一个字符串时,咱们通常会想到atoi()或者itoa()(非标准函数),可是咱们能够经过流来实现:
int main(){ int num = 10; char str[10] = {0}; sprintf(str,"%d",num); //把int转换成char[] num = 0; sscanf(str,"%d",&num);//把字符串转换成int printf("num:%d str:%s\n",num,str); return 0; }
输出结果以下:
把字符串转与其它类型之间的转换:好比float,16进制,unsigned等均可以用流实现。
(2)格式化函数中的正则表达式
全部的格式化函数均可以定制本身的扫描集 %[abc]、%[a-z]、%[^abc]、%[^a-z],其中[]内是匹配的字符,^表示求反集。
当咱们要从标准输入输入一个可能带空格的字符串时,直接用scanf("%s",str);当读到空格时就返回,此时就可使用正则表达式:
char str[100] = {0}; scanf("%[^\n]",str);//直到遇到回车才写入
从标准输入中只要读小写字母a-z,遇到其它字符则返回:
char str[100] = {0}; scanf("%[a-z]",str);
其余格式化函数的用法相同,不一一举例。
四、总结
从大一开始学习C语言也有四五年了,我的认为:C语言中最大的成功在于它的指针,可是也是最容易出错的,想要理解C,必需要掌握指针。虽说,语言只是一门工具,可是这是基础。或许,你能够说,如今是JAVA的天下了,满大街都是招聘JAVA工程师;或者你能够说C太底层,如今都是OOP的时代了,谁还会用面向过程的......大家不要忘了操做系统是用什么写的?是C;C实现的nginx的并发量是C++实现的apache的几十倍。不管是什么编程语言,好好学,深刻学就行,不要由于它今天流行就抛弃昨天所学的。
版权全部,欢迎转载,转载请注明出处。