设为首页 加入收藏

TOP

深入理解C语言(一)
2015-08-31 21:25:14 来源: 作者: 【 】 浏览:69
Tags:深入 理解 语言

  语言只是一种工具,任何语言之间都是相通的,一通则百通,关键是要理解语言背后的思想,理解其思想,任何语言,拿来用就行了。语言没有好坏之分,任何语言既然存在自然有它存在的价值。


  在一个到处是OOP的年代,为何面向过程的C语言依然可以如此活跃?这主要得益于C语言本身的语言特性。C语言小巧灵活,而且还有一个直接与硬件打交道的指针的存在,所以它是嵌入式开发唯有的高级语言;正因为他的小巧灵活,我们可以用它来开发一系列的小工具,Unix/Linux就是由这些小工具组成的操作系统;同时用C语言可以开发高性能的应用程序。


1、数据类型。C是一门面向过程的语言,但它依旧可以实现大多数面向对象所能完成的工作。比如面向对象的三大特性:封装、继承、多态。


  封装: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地址。


2、 指针和内存管理


  无论问哪一个C工程狮:C语言中最容易出错的地方在哪?我们基本上会得到同一个答案,那就是指针和内存溢出。那么指针是什么,指针其实就是一个地址,这个地址可以是一个变量的地址,也可以是一个函数的地址,不管是什么,反正都是内存中的一个地址。


  例如有一个变量a,我们定义一个指针来保存变量a的地址:


int a = 0;
int *p = &a;


  如果是一个函数呢?我们定义一个函数,然后用一个函数指针来保存这个函数地址:


int min(int a,int b){
? return a}


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函数分配的空间,由我们自己来管理,每次用完

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Shell编程之正则表达式 下一篇Java 9新功能之HTTP2和REPL

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: