存储分配
c程序的内存映像
c语言当中数据存储的内存空间是有区分的 (我自己分的不准确,差不多理解这个意思就行,而且这个问题实际是由操作系统具体实现决定的)
程序代码和只读数据(部分常量)存放在 代码段 当中
程序的静态数据存放在 静态数据段 当中
程序的动态数据存放在 动态数据段(栈/堆) 当中
一般用户进程空间(低->高)从一小段保留地址,接下来就是代码段(.text),存放的是编译好的机器码
静态存储区分配 全局变量、静态变量、常量
数据段(.data)、常量段(.rodata)、未初始化的全部和静态变量段(.bss)
在堆(heap)上存放用户申请的内存(这部分空间容量比栈大一个量级(GB和MB),所以程序中实际数据对象最好在这里分配),堆一般向上生长
在程序运行期间,用 动态内存分配函数 来申请的内存都是从堆上分配的,动态内存的生存周期由人为的进行决定
在栈(stack)上存放的是 函数参数,局部变量值等,栈一般向下增长,当栈堆指针相等时就说明内存空间必然是不够了
在执行函数调用时,系统在栈上为函数的局部变量和形参分配内存,函数执行结束时,自动释放这些内存
此外, ...
unix系统调用
系统调用
UNIX操作系统通过一系列系统调用提供服务,而这些系统调用实际上是操作系统内的函数,可以被用户程序调用
在UNIX系统中,经常会需要借助于系统调用以获得最高的效率,或访问标准库中没有的某些功能
尤其是,ANSI C标准函数库是以UNIX系统为基础建立起来的(比如fopen等文件操作函数是基于open等系统调用接口实现的)
此外,在任何特定的系统中,标准库函数的实现往往必须通过宿主系统提供的功能来实现
另一方面,许多程序并不是系统程序,而是仅仅使用由操作系统维护的信息
对于这样的程序,很重要的一点是信息的表示仅出现在标准头文件中,使用它们的程序只需要在文件中包含头文件即可,而不需要包含每个相应的声明
其次,在构建应用程序时为了可移植性,有可能需要为与系统相关的对象创建一个与系统无关的接口
文件描述符
在UNIX系统中,所有的外围设备(包括键盘和显示器)都被看作文件系统的文件,因而所有输入/输出都要通过读文件或写文件完成
即,在UNIX系统中通过单一的接口就可以处理外围设备和程序之间的所有通信
通常,读写文件之前必须将意图通知系统,该过程称为打开文件
如果是写一个文件,则可 ...
文件输入输出
文件访问
UNIX系统程序cat可以说明访问的文件是否已经连接到程序,它把一批命名文件串联后输出到标准输出上
cat可用来在屏幕上打印文件,对于无法通过名字访问文件的程序而言,它还可以用作通用的输入收集器
如:命令行 cat x.c y.c 将在标准输出上打印文件x.c和y.c的内容
对一个访问文件的程序而言,关键的问题在于如何设计命名文件的读取过程,即如何将用户需要使用的文件的外部名同读取数据的语句联系起来
方法其实很简单:
在读写一个文件之前,必须通过库函数fopen()打开该文件
fopen函数原型:FILE* fopen(char* name,char* mode);
fopen()用类似于x.c或y.c这样的外部名与操作系统进行某些必要的连接和通信,并返回一个随后可以用于文件读写操作的指针
该指针称为文件指针,指向一个包含文件信息的结构,这些信息包括:
一个指向缓冲区位置的指针,通过它可以一次读入文件的一大块内容;
一个记录缓冲区中剩余字符数的计数器;
一个指向缓冲区下一个字符的指针;
文件描述符;
描述读/写模式的标志;
描述错误状态的标志等
用户无需关心这些细 ...
标准输入输出
输入与输出并不是c本身的功能
一般讨论的输入输出都是建立在标准输入输出之上的,标准输入(stdin)和标准输出(stdout)是操作系统自动提供给程序访问的
ANSI标准精准地定义了包括输入\输出函数、字符串处理函数、存储管理函数以及数学函数等标准库函数
在任何可以使用c的系统中都有这些函数兼容形式,如果程序的系统交互部分仅用了标准库提供的功能,则可以不经修改地从一个系统移植到另一个系统
这些库函数的属性分别在十多个头文件中声明,如<stdio.h>、<string.h>、<ctype.h>等
标准输入输出
标准库实现了简单的文本输入和输出模式
文本流由一系列行组成,每行结尾是一个换行符,如果系统没有遵循这种模式,则标准库将通过一些措施使得该系统适应这种模式
如:标准库可以在输入端将回车符和换页符都转换为换行符,而在输出端进行反向转换
字符输入函数getchar()
使用getchar函数从标准输入(一般为键盘)中一次读取一个字符
函数原型:int getchar(void);
getchar函数每次在被调用时返回下一个输入字符,若遇到 ...
函数、指针、复杂声明
函数与指针
指针函数
函数内部数据是地址,需要传递给调用函数,可以将指针返回给函数
或者也可以通过双向传递参数来获取地址,但一般不推荐
不用函数参数双向传递的原因在于:
一旦调用的函数中对指针的值做了修改,原指针的值并没有变化(此时相当于传值调用),那么程序中接下来围绕指针的操作就与愿指针指向的值无关了
返回指针的函数一般定义格式为:
数据类型 *函数名(参数列表){函数体;} 数据类型为返回的指针指向的数据类型,同时也要return指针类型
函数返回指针示例程序1:
实现匹配函数match:在输入字符串中查找一个给定字符,如果找到则从该字符开始打印余下的子字符串,及该字符是字符串的第几个字符,- 否则输出"no match found"
由于函数的功能一般是独立的,函数的输出一般通过函数双向传递的参数或返回值获得,因此函数的输出一般是函数计算结果反馈给调用者
至于调用者是把结果输出到屏幕还是作为其他函数的输出,由调用者根据需要而定,除非必要一般不在函数内部通过打印函数这种显示语句输出数据
#include<stdio.h>
#in ...
函数递归
递归函数
函数可以调用其他函数,也可以调用自身
递归函数(Recursive Function),即自调用函数,在函数体内有直接的或间接地自己调用自己的语句
递归函数很特殊,因为自己调用自己,很容易出现死循环
因此自调用过程在函数内必须设置某些条件,当条件成立时终止自调用过程,并使程序控制逐步从函数中返回,称为条件递归\
递归程序必然包含两部分:
递归循环继续的过程
递归调用结束的过程
递归程序分析:
根据观察,找到输入变量:m1,m2…mn
设求的递归函数是f(m1,m2…mn)
前一个项和后一个项肯定是有关系的,不然递归无法进行。即f(m1n,m2n…mnn)与f(m1n-1,m2n-1…mnn-1)之间有一定关系
例1:求n的阶乘
算法分析:
n=0 n!=1;
n!=0 n!=n*(n-1)!
int Factorial(int n)
{
if(n==0)return 1; 递归结束条件
if(n! ...
函数参数、函数调用
函数参数
数据传递,函数调用的过程实际上就是将实参数据原样拷贝到形参当中
参数会出现在两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的
用void定义参数表示没有参数,不接收外部传输数据
形参(形式参数)
在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参
形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
实参(实际参数)
函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时都必须有确定的值
实参和形参在数量上、类型上、顺序上必须严格一致\
函数调用
函数调用实质,程序执行流程转向由函数名指定的被调用函数
主要流程为:
实参一一对应地传递给函数定义中的形参
执行函数定义中的函数体
执行结束,通过return语句将值返回到调用处
程序执行流程返回调用处,执行后面的语句
传值,传址
函数参数传值 ...
函数
函数
函数是c语言模块化编程的最小单位,可以把每个函数看作一个“模块”(module)
把这些函数单独设计,测试,调试好,用时再拿来装配,总体调试,这些函数可以是自己设计制造/别人设计制造/现成的标准产品
函数的概念
函数是按给定的任务,把相关语句组织在一起的程序块,也称为例程或过程
可以将大的计算任务分解成若干较小的任务,并且可以基于函数进一步构造程序,而不需重新编写一些代码
设计得当的函数可以将程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,降低修改程序的难度
若干相关的函数可以合并成一个模块(源程序文件),一个c程序结构一般由一个或多个源程序文件组成
c在设计中考虑了函数的高效性和易用性两个因素,c程序一般都由许多小的函数组成,而不是少量较大的函数组成
函数之间通信可以通过参数,函数返回值以及外部变量进行,
函数在源文件中出现的次序可以是任意的,只要保证每个函数不被分离到多个文件,源程序就可以分为多个文件
通常在解决实际问题中,最好将程序划分成若干个与问题的自然划分相一致的函数,并通过主函数控制其他函数的执行
函数的分类
标准库函数(ANSI/I ...
指针、数组、结构体
指针与一维数组
通过指针访问内存,执行速度快于通过数组名访问内存,同时可以通过指针移动的方式访问数组
任何通过数组下标所能完成的操作都可以通过指针来实现
指针,数组名都是地址,都可以访问数组元素
缺点是,用指针实现的程序理解稍微困难,不够直观\
数组元素值和地址的等价引用形式,例:
int a[4]={1,2,3,4};
int *pa=a; 等价于初始化int *pa=&a[0]; 因为a中存放的地址就是数组中首元素地址
pa=a,相当于用数组名给指针赋值,pa就相当于数组名这一指针,可以用指针名加下标的方式表示元素
如果pa指向数组中某个特定元素,那么根据指针运算的定义,pa+i指向pa所指对象之后的第i个对象,pa-i指向pa所指对象之前第i个对象
此时:a[i],pa[i],(a+i),(pa+i)是完全等价的
对应地址:&a[i],&pa[i],(a+i),(pa+i)也是完全等价的
无论数组中元素的类型或长度是什么,以上结论都成
产生这种现象的原因在于 ...
指针
指针
指针是一种保存变量地址的变量
在c中指针的使用非常广泛,原因之一是指针常常是表达某个计算的唯一途径
另一个原因是同其他方法相比较,使用指针往往可以生成更高效更紧凑的代码
指针和数组之间的关系十分密切,使用数组的场景下基本上都会涉及指针
指针和goto语句一样,会导致程序难以理解
如果使用者不够细心的话,指针很容易就会指向错误的地方,但如果谨慎地使用指针,就可以利用其写出简单,清晰的程序
ANSI C的一个最重要的变化在于,它明确规定了操纵指针的规则,而事实上这些规则早已被很多优秀的程序设计人员和编译器所采纳
此外,ANSI C使用类型void*(指向void的指针)代替char*作为通用指针的类型
void*可以指向任意类型的数据,可用任意类型指针对void*赋值,在部分编译环境下也可用void*给任何类型赋值
但void*无法进行算数运算,因为不知道需要操作几字节的数据
(注意在c++中void*可以接受任何类型,反过来不可以,也就是一个有类型的指针不能指向一个void*所指向的地址)
(void几乎只有说明和限制程序的作用,从来没有人会定义一个void变 ...