免费空间建网站,网站网站开发逻辑,网站开发软件 论文 摘要,网店代运营需要多少钱我自己的原文哦~ https://blog.51cto.com/whaosoft/12428240
一、C语言和C的区别
C语言虽说经常和C在一起被大家提起#xff0c;但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准#xff0c;C是C99标准的。C89就是在1989年制定的标准#xff0c;如今最新… 我自己的原文哦~ https://blog.51cto.com/whaosoft/12428240
一、C语言和C的区别
C语言虽说经常和C在一起被大家提起但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准C是C99标准的。C89就是在1989年制定的标准如今最新的是C11和C11标准。根据不同的标准它们的功能也会有所不同但是越新的版本支持的编译器越少所以本文在讨论的时候使用的C语言标准是C89C标准是C99。 本文来介绍C语言和C中那些不同的地方
函数默认值 在C中我们在定义或声明一个函数的时候有时会在形参中给它赋一个初始值作为不传参数时候的缺省值例如
int FUN(int a 10); 代表没有传参调用的时候自动给a赋一个10的初始值。然而这种操作在c89下是行不通的在c语言下这么写就会报错。我们都知道系统在调用任何一个函数的时候都有函数栈帧的开辟如果函数有参数则需要压入实参。平常在我们人为给定实参的时候是按照参数列表从右向左依次将参数通过指令传入寄存器再通过push指令压入。现在我们已经给定了函数参数的默认值那么在压实参的时候只需要一步push初始值即可。效率更高。 另外需要注意的是赋初始值必须从参数列表的右边开始赋值从左边开始赋值将会出错
int sum1(int a 10,int b); //错误
int sum2(int a,int b 20); //正确
sum1( ,20) //很可惜这样属于语法错误
sum2(20); //合情合理没有任何问题
int fun(int a ,int b 10);
int fun(int a 20,int b); 其实这样声明完全没有问题两句声明是同一个函数(函数多次声明没有问题)第一句已经给b了一个初始值运行到第二句时已经等价于
int fun(int a 20,int b 10); 但是注意这两句的顺序不能反转否则就是错误的。
总结C89标准的C语言不支持函数默认值C支持函数默认值且需要遵循从右向左赋初始值。 inline内联函数 说到内联函数大家应当不陌生它又是一个C89标准下C语言没有的函数。它的具体做法和宏非常相似也是在调用处直接将代码展开只不过宏它是在预编译阶段展开而内联函数是在 编译阶段进行处理的。同时宏作为预处理并不进行类型检查而inline函数是要进行类型检查的也就可以称作“更安全的宏”。 内联函数和普通函数的区别内联函数没有栈帧的开辟回退一般我们直接把内联函数写在头文件中include之后就可以使用由于调用时直接代码展开所以我们根本不需要担心什么重定义的问题——它连符号都没有生成当然不会所谓重定义了。普通函数生成符号内联函数不会生成符号。 关于inline还需要注意的一点是我们在使用它的时候往往是用来替换函数体非常小1~5行代码的函数的。这种情况下函数的堆栈开销相对函数体大小来说就非常大了这种情况使用内联函数可以大大提高效率。相反如果是一个需要很多代码才能实现的函数则不适合使用。一是此时函数堆栈调用开销与函数体相比已经是微不足道了二是大量的代码直接展开的话会给调试带来很大的不便。三是如果代码体达到一个阈值编译器会将它变成普通函数。
同时递归函数不能声明为inline函数。说到底inline只是对编译器的建议最终能否成功也不一定。同时我们平常生成的都是debug版本在这个版本下inline是不起作用的。只有生成release版时才会起作用。 总结C89没有在调用点直接展开不生成符号没有栈帧的开辟回退仅在Release版本下生效。一般写在头文件中。
函数重载 C语言中产生函数符号的规则是根据名称产生这也就注定了c语言不存在函数重载的概念。而C生成函数符号则考虑了函数名、参数个数、参数类型。需要注意的是函数的返回值并不能作为函数重载的依据也就是说int sum和double sum这两个函数是不能构成重载的 我们的函数重载也属于多态的一种这就是所谓的静多态。
静多态函数重载函数模板动多态运行时的多态继承中的多态虚函数 使用重载的时候需要注意作用域问题请看如下代码
#include iostream
using namespace std;
bool compare(int a,int b)
{
return a b;
}
bool compare(double a,double b)
{
return a b;
}
int main()
{
//bool compare(int a,int b);
compare(10,20);
compare(10.5,20.5);
return 0;
}
我在全局作用域定义了两个函数它们由于参数类型不同可以构成重载。 此时main函数中调用则可以正确的调用到各自的函数。 但是请看main函数中被注释掉的一句代码。如果我将它放出来则会提出警告将double类型转换成int类型可能会丢失数据。这就意味着我们编译器针对下面两句调用都调用了参数类型int的compare。由此可见编译器调用函数时优先在局部作用域搜索若搜索成功则全部按照该函数的标准调用。若未搜索到才在全局作用域进行搜索。 总结C语言不存在函数重载C根据函数名参数个数参数类型判断重载属于静多态必须同一作用域下才叫重载。 const 这一部分非常重要。在我的另一篇博客“C语言的32个关键字”中对C语言中的const也有所讲解。当中提到了这么一个问题C语言中被const修饰的变量不是常量叫做常变量或者只读变量这个常变量是无法当作数组下标的。然而在C中const修饰的变量可以当作数组下标使用成为了真正的常量。这就是C对const的扩展。 C语言中的const被修饰后不能做左值可以不初始化但是之后没有机会再初始化。不可以当数组的下标可以通过指针修改。简单来说它和普通变量的区别只是不能做左值而已。其他地方都是一样的。 C中的const真正的常量。定义的时候必须初始化可以用作数组的下标。const在C中的编译规则是替换和宏很像所以它被看作是真正的常量。也可以通过指针修改。需要注意的是C的指针有可能退化成C语言的指针。比如以下情况
int b 20;
const int a b;
这时候的a就只是一个普通的C语言的const常变量了已经无法当数组的下标了。引用了一个编译阶段不确定的值
const在生成符号时是local符号。即在本文件中才可见。如果非要在别的文件中使用它的话在文件头部声明extern cosnt int data 10这样生成的符号就是global符号。 总结C中的const叫只读变量只是无法做左值的变量C中的const是真正的常量但也有可能退化成c语言的常量默认生成local符号。
引用 说到引用我们第一反应就是想到了他的兄弟指针。引用从底层来说和指针就是同一个东西但是在编译器中它的特性和指针完全不同。
int a 10;
int b a;
int *p a;
//b 20;
//*p 20;
首先定义一个变量a 10然后我们分别定义一个引用b以及一个指针p指向a。我们来转到反汇编看看底层的实现 可以看到底层实现完全一致取a的地址放入eax寄存器再将eax中的值存入引用b/指针p的内存中。至此我们可以说在底层引用本质就是一个指针。了解了底层实现我们回到编译器。我们看到对a的值的修改指针p的做法是*p 20即进行解引用后替换值。底层实现 再来看看引用修改 我们看到修改a的值的方法也是一样的也是解引用。只是我们在调用的时候有所不同调用p时需要*p解引用b则直接使用就可以。由此我们推断出引用在直接使用时是指针解引用。p直接使用则是它自己的地址。这样我们也了解了我们给引用开辟的这块内存是根本访问不到的。如果直接用就直接解引用了。即使打印b输出的也是a的地址。 注*的作用是引用指针指向的变量值引用其实就是引用该变量的地址“解”就是把该地址对应的东西解开解出来就像打开一个包裹一样那就是该变量的值了所以称为“解引用”。也就是说解引用是返回内存地址中对应的对象。 在此附上将指针转为引用的小技巧
int *p a
/*我们将 引用符号移到左边 将 *替换即可*/
int p a 接下来看看如何创建数组的引用
int array[10] {0}//定义一个数组 我们知道array拿出来使用的话就是数组array的首元素地址。即是int *类型。那么array是什么意思呢int **类型用来指向array[0]地址的一个地址吗不要想当然了array是整个数组类型。 那么要定义一个数组引用按照上面的小诀窍先来写写数组指针吧
int (*q) [10] array; 将右侧的对左边的*进行覆盖
int (q)[10] array; 测试sizeof(q) 10。我们成功创建了数组引用。经过上面的详解 我们知道了引用其实就是取地址。那么我们都知道一个立即数是没有地址的即
int b 10; 这样的代码是无法通过编译的。那如果你就是非要引用一个立即数其实也不是没有办法
const int b 10 即将这个立即数用const修饰一下就可以了。为什么呢 这时因为被const修饰的都会产生一个临时量来保存这个数据自然就有地址可取了。 总结引用底层就是指针使用时会直接解引用可以配合const对一个立即数进行引用。
malloc,free new,delete 这个问题很有意思也是重点需要关注的问题。malloc()和free()是C语言中动态申请内存和释放内存的标准库中的函数。而new和delete是C运算符、关键字。new和delete底层其实还是调用了malloc和free。它们之间的区别有以下几个方面
①malloc和free是函数new和delete是运算符。
②malloc在分配内存前需要大小new不需要。 例如
int *p1 (int *)malloc(sizeof(int));
int *p2 new int; //int *p3 new int(10); malloc时需要指定大小还需要类型转换。new时不需要指定大小因为它可以从给出的类型判断并且还可以同时赋初始值。
③malloc不安全需要手动类型转换new不需要类型转换。
详见上一条。
④free只释放空间delete先调用析构函数再释放空间如果需要。
与第⑤条对应如果使用了复杂类型先析构再call operator delete回收内存。
⑤new是先调用构造函数再申请空间如果需要。
与第④条对应我们在调用new的时候例如int *p2 new int;这句代码 底层代码的实现是首先push 4字节int类型的大小随后call operator new函数分配了内存。由于我们这句代码并未涉及到复杂类型如类类型所以也就没有构造函数的调用。如下是operator new的源代码也是new实现的重要函数
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p malloc(size)) 0)
if (_callnewh(size) 0)
{ // report no memory
_THROW_NCEE(_XSTD bad_alloc, );
}
return (p);
}
我们可以看到首先malloc(size)申请参数字节大小的内存。 如果失败(malloc失败返回0)则进入判断 如果_callnewh(size)也失败的话抛出bad_alloc异常。 _callnewh()这个函数是在查看new handler是否可用。 如果可用会释放一部分内存再返回到malloc处继续申请。 如果new handler不可用就会抛出异常。
⑥内存不足开辟失败时处理方式不同。
malloc失败返回0new失败抛出bad_alloc异常。
⑦new和malloc开辟内存的位置不同。
malloc开辟在堆区new开辟在自由存储区域。
⑧new可以调用malloc(),但malloc不能调用new。
new就是用malloc()实现的new是C独有malloc当然无法调用。
作用域 C语言中作用域只有两个局部全局。C中则是有局部作用域类作用域名字空间作用域三种。 所谓名字空间就是namespace我们定义一个名字空间就是定义一个新作用域。访问时需要以如下方式访问以std为例
std::cin 123 std::endl; 例如我们有一个名字空间叫Myname其中有一个变量叫做data。如果我们希望在其他地方使用data的话需要在文件头声明using Myname::data这样一来data就使用的是Myname中的值了。可是这样每个符号我们都得声明岂不是累死我们只要using namespace Myname就可以将其中所有符号导入了。所以我们经常看到如下代码
using namespace std; 二、C语言面试-指针和引用的使用场景
先解决两个疑问
◆ 指针和引用的不同之处是什么
◆ 何时用用指针何时用引用
指针和引用的不同之处 看如下代码 指针是用来表示内存地址的而指针这个整数正是被指向的变量地址。 而引用就是给变量重新起了一个名字引用也就是“别名”。
不同之处
◆ 指针在声明时可以暂时不初始化当然每次使用指针时都要做检查以防出现空指针异常的问题0。而引用永远都不会为空它一定得代表某个对象。 ◆ 指针存放的是地址可以被重新赋值。而引用总是指向它最初代表的那个对象。
指针和引用使用场合 引用主要是作为函数的参数和返回值来使用的看如下代码 通过vec[3] 3可改变vector容器的值这是因为[]操作符返回的就是引用。也就是为内部的变量取了一个别名还能让[]操作符返回一个指针即*vec[3]3。实际上引用可以做的事情指针都可以做那为什么还要使用引用
总结 用恰当的工具做恰如其分的工作。 指针可以毫无约束的操作内存中的任何东西功能十分强大但也很危险所以可以在恰当的时机使用引用。当你需要指向某个东西绝不会让其指向其它东西例如有些函数参数为了避免拷贝可以使用引用或者实现一个操作符而其语法需求无法由指针达成例如vec[3]3可以使用引用而其它任何时候都要使用指针。 三、C语言中return的各种用法 按初学的理解return的任务就是返回对应的参数在外层函数中对这个参数做进一步处理。 实际上return的用法不只这些。
为调用的函数返回参数值 此类应用最为普遍通常是在一个具有返回值的函数中返回一个参数值这个返回的参数可以是一个数、表达式。通常情况下返回值是一个值如果想返回多个值可以参考以下三点
设置全局变量全局变量是定义在函数外的一个变量其不属于任何一个函数所以虽然在调用函数中return返回一个值但可以将其余的返回值赋值给全局变量这样就做到了返回多个值的效果。使用数组名或指针作为函数形参返回的是一个地址主函数可以根据这个地址找到多个要返回的数据。使用结构体作为函数形参结构体中可以包含多种形式的变量以结构体作为形参相当于将所有返回值打包在一起一次返回。
提前结束函数 由于return具有把程序流程从被调函数转向主调函数的作用所以时候也会用来终止函数的调用也就是说return是提前结束函数的唯一方法。因为函数一遇到return就立即返回return后面的程序都不再执行所以一个函数中可以有多个return但只会执行一个。return后面可以跟一个参数作为返回值也可以不跟参数仅仅代表结束此函数。
返回一个函数 如果return后面跟的是一个函数的话就意味着跳出此调用函数并且跳出后执行return后的函数然后继续在主函数中执行程序。 有时利用return返回一个操作比如在程序调试中出错后需要return跳出错误函数并向串口打印出错误的原因这个时候return后面的函数就用来向串口打印错误原因的。 四、C语言基础知识点汇总
C语言入门程序hello world
#includestdio.h
int main()
{
/*在双引号中间输入Hello World*/printf(Hello World); //在屏幕打印输出Hello World
return 0;
} 注在最新的C标准中main函数前的类型为int而不是void
C语言的具体结构 简单来说一个C程序就是由若干头文件和函数组成。 #include stdio.h就是一条预处理命令, 它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作。 函数就是实现代码逻辑的一个小的单元。
必不可少之主函数 一个C程序有且只有一个主函数即main函数。 C程序就是执行主函数里的代码也可以说这个主函数就是C语言中的唯一入口而main前面的int就是主函数的类型printf()是格式输出函数这里就记住它的功能就是在屏幕上输出指定的信息return是函数的返回值根据函数类型的不同返回的值也是不同的\n是转义字符中的换行符。(注意C程序一定是从主函数开始执行的)
写代码的良好习惯
一个说明或一个语句占一行例如包含头文件、一个可执行语句结束都需要换行函数体内的语句要有明显缩进通常以按一下Tab键为一个缩进括号要成对写如果需要删除的话也要成对删除当一句可执行语句结束的时候末尾需要有分号代码中所有符号均为英文半角符号 程序解释——注释 注释是写给程序员看的不是写给电脑看的。 C语言注释方法有两种
多行注释/* 注释内容 */单行注释//注释一行
C标识符 C语言规定标识符可以是字母(AZaz)、数字(09)、下划线_组成的字符串并且第一个字符必须是字母或下划线。在使用标识符时还有注意以下几点
标识符的长度最好不要超过8位因为在某些版本的C中规定标识符前8位有效当两个标识符前8位相同时则被认为是同一个标识符标识符是严格区分大小写的。例如Imooc和imooc 是两个不同的标识符标识符最好选择有意义的英文单词组成做到见名知意不要使用中文标识符不能是C语言的关键字。想了解更多C语言关键字的知识
变量定义与赋值 变量就是可以变化的量而每个变量都会有一个名字标识符。变量占据内存中一定的存储单元。使用变量之前必须先定义变量要区分变量名和变量值是两个不同的概念。 变量定义与赋值的一般形式如下。 注意:在定义中不允许连续赋值如下操作是不合法的。
int abc5; //不合法的赋值操作 变量的赋值分为两种方式:
先声明再赋值声明的同时赋值
基本数据类型 C语言中的数据类型如下图所示。 最常用的整型、实型与字符型(char,int,float,double): 整型数据是指不带小数的数字(int,short int,long int, unsigned int, unsigned short int,unsigned long int): 注意
int short int long int是根据编译环境的不同所取范围不同而其中short int和long int至少是表中所写范围, 但是int在表中是以16位编译环境写的取值范围另外 c语言int的取值范围在于他占用的字节数 不同的编译器规定是不一样ANSI标准定义int是占2个字节TC是按ANSI标准的它的int是占2个字节的。但是在VC里一个int是占4个字节的 浮点数据是指带小数的数字。 生活中有很多信息适合使用浮点型数据来表示比如人的体重(单位公斤)、商品价格、圆周率等等。 因为精度的不同又分为3种(float,double,long double) 格式化输出语句 格式化输出语句也可以说是占位输出是将各种类型的数据按照格式化后的类型及指定的位置从计算机上显示。其格式为
printf(输出格式符输出项); 当输出语句中包含普通字符时可以采用一下格式
printf(普通字符输出格式符, 输出项); 注意格式符的个数要与变量、常量或者表达式的个数相对应。
不可改变的常量 在程序执行过程中值不发生改变的量称为常量比如 在C语言中可以用一个标识符来表示一个常量常量在使用之前必须先定义其一般形式为
#include stdio.h
#define POCKETMONEY 10 //定义常量及常量值
int main()
{
// POCKETMONEY 12; //小明私自增加零花钱对吗
printf(小明今天又得到%d元零花钱\n, POCKETMONEY);
return 0;
} 符号常量不可以被改变。
自动类型转换 数据类型存在自动转换的情况。 自动转换发生在不同数据类型运算时在编译的时候自动完成。 char类型数据转换为int类型数据遵循ASCII码中的对应值。 注意:
字节小的可以向字节大的自动转换但字节大的不能向字节小的自动转换char可以转换为intint可以转换为doublechar可以转换为double
强制类型转换 强制类型转换是通过定义类型转换运算来实现的。其一般形式为
(数据类型) (表达式) 其作用是把表达式的运算结果强制转换成类型说明符所表示的类型。在使用强制转换时应注意以下问题
数据类型和表达式都必须加括号, 如把(int)(x/2y)写成(int)x/2y则成了把x转换成int型之后再除2再与y相加了转换后不会改变原数据的类型及变量值只在本次运算中临时性转换强制转换后的运算结果不遵循四舍五入原则
C语言基本运算符 除法运算中注意
如果相除的两个数都是整数的话则结果也为整数小数部分省略如8/3 2而两数中有一个为小数结果则为小数如9.0/2 4.500000 取余运算中注意
该运算只适合用两个整数进行取余运算如10%3 1运算后的符号取决于被模数的符号如(-10)%3 -1而10%(-3) 1
自增与自减运算符 自增运算符为其功能是使变量的值自增1。 自减运算符为--其功能是使变量值自减1。 它们经常使用在循环中自增自减运算符有以下几种形式 赋值运算符 C语言中赋值运算符分为简单赋值运算符和复合赋值运算符。 例如
a 3; //变量a并赋值为3
a 5; //这个算式就等价于a a5,将变量a和5相加之后再赋值给a 注意复合运算符中运算符和等号之间是不存在空格的。
关系运算符 C语言中的关系运算符: 关系表达式的值是真和假在C程序中用整数1和0表示。 注意, , , !这种符号之间不能存在空格。
逻辑运算符 C语言中的逻辑运算符: 逻辑运算的值也是有两种分别为真和假C语言中用整型的1和0来表示。其求值规则如下
与运算 参与运算的两个变量都为真时结果才为真否则为假。例如55 75 运算结果为真。
或运算 || 参与运算的两个变量只要有一个为真结果就为真。两个量都为假时结果为假。例如55||58运算结果为真
非运算! 参与运算的变量为真时结果为假参与运算量为假时结果为真。例如!(58)运算结果为真
三目运算符 C语言中的三目运算符其格式为
表达式1 ? 表达式2 : 表达式3; 程序执行过程是 判断表达式1的值是否为真如果是真的话执行表达式2如果是假的话执行表达式3。
运算符大比拼之优先级比较 各种运算符号的顺序: 优先级别为1的优先级最高优先级别为10的优先级别最低。
分支结构之简单if语句 C语言中的分支结构语句中的if条件语句。 简单if语句的基本结构如下
if(表达式)
{执行代码块;
} 其语义是如果表达式的值为真则执行其后的语句否则不执行该语句。 注意if()后面没有分号直接写{}。
分支结构之简单if-else语句 简单的if-else语句的基本结构: 语义是: 如果表达式的值为真则执行代码块1否则执行代码块2。 注意if()后面没有分号直接写{},else后面也没有分号直接写{}。
分支结构之多重if-else语句 C语言中多重if-else语句其结构如下 语义是依次判断表达式的值当出现某个值为真时则执行对应代码块否则执行代码块n。 注意当某一条件为真的时候则不会向下执行该分支结构的其他语句。
分支结构之嵌套if-else语句 C语言中嵌套if-else语句。嵌套if-else语句的意思就是在if-else语句中再写if-else语句。其一般形式为 循环结构之while循环 反复不停的执行某个动作就是江湖人称的循环 。 C语言中有三种循环结构先看一下C语言while循环的结构 其中表达式表示循环条件执行代码块为循环体。 while语句的语义是计算表达式的值当值为真(非0)时 执行循环体代码块。
while语句中的表达式一般是关系表达或逻辑表达式当表达式的值为假时不执行循环体反之则循环体一直执行一定要记着在循环体中改变循环变量的值否则会出现死循环无休止的执行循环体如果包括有一个以上的语句则必须用{}括起来组成复合语句
循环结构之do-while循环 C语言中的do-while循环一般形式如下 do-while循环语句的语义是 它先执行循环中的执行代码块然后再判断while中表达式是否为真如果为真则继续循环如果为假则终止循环。因此do-while循环至少要执行一次循环语句。 注意使用do-while结构语句时while括号后必须有分号。
循环结构之for循环 C语言中for循环一般形式 它的执行过程如下
执行表达式1对循环变量做初始化判断表达式2若其值为真(非0)则执行for循环体中执行代码块然后向下执行若其值为假(0)则结束循环执行表达式3(i)等对于循环变量进行操作的语句执行for循环中执行代码块后执行第二步;第一步初始化只会执行一次循环结束程序继续向下执行 注意for循环中的两个分号一定要写 在for循环中:
表达式1是一个或多个赋值语句它用来控制变量的初始值表达式2是一个关系表达式它决定什么时候退出循环表达式3是循环变量的步进值定义控制循环变量每循环一次后按什么方式变化这三部分之间用分号分开
结束语句之break语句 需要中断循环在C语言中可以使用break语句进行该操作使用break语句时注意以下几点
在没有循环结构的情况下break不能用在单独的if-else语句中。在多层循环中一个break语句只跳出当前循环。
结束语句之continue语句 continue语句的作用是结束本次循环开始执行下一次循环。 break语句与continue语句的区别是: break是跳出当前整个循环continue是结束本次循环开始下一次循环。
分支结构之switch语句 switch语句结构如下: switch语句时还应注意以下几点
在case后的各常量表达式的值不能相同否则会出现错误在case子句后如果没有break;会一直往后执行**一直到遇到break;**才会跳出switch语句switch后面的表达式语句只能是整型或者字符类型在case后允许有多个语句**可以不用{}**括起来各case和default子句的先后顺序可以变动而不会影响程序执行结果default子句可以省略不用
自定义函数 自定义函数的一般形式 注意
[]中包含的内容可以省略数据类型说明省略时默认是int类型函数参数省略时表示该函数是无参函数参数不省略表示该函数是有参函数
函数调用 需要用到自定义函数的时候就得调用它在C语言中函数调用的一般形式为
函数名[参数]; 注意对无参函数调用的时候可以将[]包含的省略。
有参与无参 在函数中不需要函数参数的称之为无参函数在函数中需要函数参数的称之为有参函数。 有参和无参函数的一般形式如下 有参函数和无参函数的唯一区别在于函数 () 中多了一个参数列表。
形参与实参 函数的参数分为形参和实参两种。 形参是在定义函数名和函数体的时候使用的参数目的是用来接收调用该函数时传入的参数。 实参是在调用时传递该函数的参数。 函数的形参和实参具有以下特点
形参只有在被调用时才分配内存单元在调用结束时即刻释放所分配的内存单元。因此形参只有在函数内部有效实参可以是常量、变量、表达式、函数等在参数传递时实参和形参在数量上类型上顺序上应严格一致否则会发生类型不匹配的错误 函数的返回值 函数的返回值是指函数被调用之后执行函数体中的程序段所取得的并返回给主调函数的值。 函数的返回值要注意以下几点
函数的值只能通过return语句返回主调函数 return语句的一般形式为:
return 表达式 或者为
return (表达式);
函数值的类型和函数定义中函数的类型应保持一致 如果两者不一致则以函数返回类型为准自动进行类型转换。
没有返回值的函数返回类型为void
递归函数 递归就是一个函数在它的函数体内调用它自身递归函数必须有结束条件。 执行递归函数将反复调用其自身每调用一次就进入新的一层。 一句话总结递归自我调用且有完成状态。 例题 有5个人坐在一起问第5个人多少岁他说比第4个人大2岁。问第4个人岁数他说比第3个人大2岁。问第3个人又说比第2人大两岁。问第2个人说比第1个人大两岁。最后 问第1个人他说是10岁。请问第5个人多大 程序分析 利用递归的方法递归分为回推和递推两个阶段。要想知道第5个人岁数需知道第4人的岁数依次类推推到第1人10岁再往回推。
#include stdio.h
int dfs(int n) {
return n 1 ? 10 : dfs(n - 1) 2;
}
int main()
{
printf(第5个人的年龄是%d岁, dfs(5));
return 0;
}
局部与全局 C语言中的变量按作用域范围可分为两种即局部变量和全局变量。
局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内 离开该函数后再使用这种变量是非法的。在复合语句中也可定义变量其作用域只在复合语句范围内全局变量也称为外部变量它是在函数外部定义的变量。它不属于哪一个函数它属于一个源程序文件。其作用域是整个源程序
变量存储类别 C语言根据变量的生存周期来划分可以分为静态存储方式和动态存储方式。 静态存储方式是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量如全局变量。 动态存储方式是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的通常包括函数形式参数自动变量函数调用时的现场保护和返回地址等。 C语言中存储类别又分为四类
自动auto静态static寄存器的register外部的extern 用关键字auto定义的变量为自动变量auto可以省略auto不写则隐含定为“自动存储类别”属于动态存储方式。如 用static修饰的为静态变量如果定义在函数内部的称之为静态局部变量如果定义在函数外部称之为静态外部变量。如下为静态局部变量 注意静态局部变量属于静态存储类别在静态存储区内分配存储单元在程序整个运行期间都不释放静态局部变量在编译时赋初值即只赋初值一次如果在定义局部变量时不赋初值的话则对静态局部变量来说编译时自动赋初值0对数值型变量或空字符对字符变量。 为了提高效率C语言允许将局部变量得值放在CPU中的寄存器中这种变量叫“寄存器变量”用关键字register作声明。例如 注意只有局部自动变量和形式参数可以作为寄存器变量一个计算机系统中的寄存器数目有限不能定义任意多个寄存器变量局部静态变量不能定义为寄存器变量。 用extern声明的的变量是外部变量外部变量的意义是某函数可以调用在该函数之后定义的变量。如 内部函数与外部函数 在C语言中不能被其他源文件调用的函数称谓内部函数 内部函数由static关键字来定义因此又被称谓静态函数形式为
static [数据类型] 函数名[参数] 这里的static是对函数的作用范围的一个限定限定该函数只能在其所处的源文件中使用因此在不同文件中出现相同的函数名称的内部函数是没有问题的。 在C语言中能被其他源文件调用的函数称谓外部函数 外部函数由extern关键字来定义形式为
extern [数据类型] 函数名([参数]) C语言规定在没有指定函数的作用范围时系统会默认认为是外部函数因此当需要定义外部函数时extern也可以省略。
数组 程序中也需要容器只不过该容器有点特殊它在程序中是一块连续的大小固定并且里面的数据类型一致的内存空间它还有个好听的名字叫数组。可以将数组理解为大小固定所放物品为同类的一个购物袋在该购物袋中的物品是按一定顺序放置的。 我们来看一下如何声明一个数组
数据类型 数组名称[长度]; 数组只声明也不行啊看一下数组是如何初始化的。说到初始化C语言中的数组初始化是有三种形式的分别是
数据类型 数组名称[长度n] {元素1,元素2…元素n};
数据类型 数组名称[] {元素1,元素2…元素n};
数据类型 数组名称[长度n]; 数组名称[0] 元素1; 数组名称[1] 元素2; 数组名称[n-1] 元素n; 我们将数据放到数组中之后又如何获取数组中的元素呢 获取数组元素时
数组名称[元素所对应下标]; 如初始化一个数组 int arr[3] {1,2,3}; 那么arr[0]就是元素1。 注意
数组的下标均以0开始数组在初始化的时候数组内元素的个数不能大于声明的数组长度mtianyan: 如果采用第一种初始化方式元素个数小于数组的长度时多余的数组元素初始化为0在声明数组后没有进行初始化的时候静态static和外部extern类型的数组元素初始化元素为0自动auto类型的数组的元素初始化值不确定
数组的遍历 数组就可以采用循环的方式将每个元素遍历出来而不用人为的每次获取指定某个位置上的元素例如我们用for循环遍历一个数组 注意以下几点
最好避免出现数组越界访问循环变量最好不要超出数组的长度C语言的数组长度一经声明长度就是固定无法改变并且C语言并不提供计算数组长度的方法 由于C语言是没有检查数组长度改变或者数组越界的这个机制可能会在编辑器中编译并通过但是结果就不能肯定了因此还是不要越界或者改变数组的长度。 C语言获取数组长度
int length sizeof(arr)/sizeof(arr[0]);
数组作为函数参数 数组可以由整个数组当作函数的参数也可以由数组中的某个元素当作函数的参数。 整个数组当作函数参数即把数组名称传入函数中例如 数组中的元素当作函数参数即把数组中的参数传入函数中例如 数组作为函数参数时注意以下事项
数组名作为函数实参传递时函数定义处作为接收参数的数组类型形参既可以指定长度也可以不指定长度数组元素作为函数实参传递时数组元素类型必须与形参数据类型一致
数组的应用-冒泡排序 以升序排序为例冒泡排序的思想相邻元素两两比较将较大的数字放在后面直到将所有数字全部排序。就像小学排队时按大小个排一样将一个同学拉出来和后面的比比如果高就放后面一直把队伍排好。
#include stdio.h
int main()
{
double arr[]{1.78, 1.77, 1.82, 1.79, 1.85, 1.75, 1.86, 1.77, 1.81, 1.80};
int i,j;
printf(\n************排队前*************\n);
for(i0;i10;i){
if(i ! 9)
printf(%1.2f, , arr[i]); //%1.2f表示小数点前一位小数点后精确到两位
else
printf(%1.2f, arr[i]); //%1.2f表示小数点前一位小数点后精确到两位}
for(i8; i0; i--){
for(j0;ji;j){
if( arr[j]arr[j1]) //当前面的数比后面的数大时{
double temp; //定义临时变量temptemparr[j];//将前面的数赋值给temparr[j]arr[j1]; //前后之数颠倒位置arr[j1]temp;//将较大的数放在后面 } } }
printf(\n************排队后*************\n);
for(i0;i10;i){
if(i ! 9)
printf(%1.2f, , arr[i]); //%1.2f表示小数点前一位小数点后精确到两位
else
printf(%1.2f, arr[i]); //%1.2f表示小数点前一位小数点后精确到两位}
return 0;
}
数组的应用-数组查找功能 当我们购物之后拎着购物袋回到家会一一检查购物袋中的物品看是否缺少或者都是想购之物。那么应用到程序中可以使用数组查找功能看看是否存在该数据如果存在并返回该元素的下标。
#include stdio.h
int getIndex(int arr[5],int value)
{
int i;
int index;
for(i0;i5;i){
/* 请完善数组查询功能 */
if(arr[i]value){
indexi;
break;}
index-1;}
return index;
}
int main()
{
int arr[5]{3,12,9,8,6};
int value 8;
int index getIndex(arr,value); //这里应该传什么参数呢
if(index!-1){
printf(%d在数组中存在下标为%d\n,value,index); }
else{
printf(%d在数组中不存在。\n,value); }
return 0;
}
字符串与数组 C语言中是没有办法直接定义字符串数据类型的但是我们可以使用数组来定义我们所要的字符串。一般有以下两种格式
char 字符串名称[长度] “字符串值”; //[]中的长度是可以省略不写的
char 字符串名称[长度] {‘字符1’,‘字符2’,…,‘字符n’,’\0’}; //最后一个元素必须是’\0’’\0’表示字符串的结束标志 注意在输出字符串的时候可使用如下语句。
printf(“%s”,字符数组名字);
puts(字符数组名字);
字符串函数 常用的字符串函数如下(strlen,strcmp,strcpy,strcat,atoi): 使用字符串函数注意以下事项 strlen()获取字符串的长度在字符串长度中是不包括‘\0’而且汉字和字母的长度是不一样的。比如 strcmp()在比较的时候会把字符串先转换成ASCII码再进行比较,返回的结果为0表示s1和s2的ASCII码相等,返回结果为1表示s1比s2的ASCII码大,返回结果为-1表示s1比s2的ASCII码小例如 strcpy()拷贝之后会覆盖原来字符串且不能对字符串常量进行拷贝比如 strcat在使用时s1与s2指的内存空间不能重叠且s1要有足够的空间来容纳要复制的字符串如 多维数组 多维数组的定义格式是
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n]; 定义了一个名称为num数据类型为int的二维数组。其中第一个[3]表示第一维下标的长度就像购物时分类存放的购物第二个[3]表示第二维下标的长度就像每个购物袋中的元素。 多维数组的初始化与一维数组的初始化类似也是分两种
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n] {{值1,…,值n},{值1,…,值n},…,{值1,…,值n}};
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n]; 数组名称[下标1][下标2]…[下标n] 值; 多维数组初始化要注意以下事项
采用第一种始化时数组声明必须指定列的维数。mtianyan: 因为系统会根据数组中元素的总个数来分配空间当知道元素总个数以及列的维数后会直接计算出行的维数采用第二种初始化时数组声明必须同时指定行和列的维数 二维数组定义的时候可以不指定行的数量但是必须指定列的数量。
多维数组的遍历 多维数组也是存在遍历的和一维数组遍历一样也是需要用到循环。不一样的就是多维数组需要采用嵌套循环。 注意多维数组的每一维下标均不能越界 实例代码
#include stdio.h
#define N 10
//打印分数
void printScore(int score[])
{
int i;
printf(\n);
for(i0;iN;i){
printf(%d ,score[i]); }
printf(\n);
}
//计算考试总分
int getTotalScore(int score[])
{
int sum 0;
int i;
for(i0;iN;i){sumscore[i]; }
return sum;
}
//计算平均分
int getAvgScore(int score[])
{
return getTotalScore(score)/N;
}
//计算最高分
int getMax(int score[])
{
int max -1;
int i;
for(i0;iN;i){
if(score[i]max){max score[i]; } }
return max;
}
//计算最低分
int getMin(int score[])
{
int min 100;
int i;
for(i0;iN;i){
if(score[i] min){min score[i]; } }
return min;
}
//分数降序排序
void sort(int score[])
{
int i,j;
for(iN-2;i0;i--){
for(j0;ji;j){
if(score[j]score[j1]){
int temp;temp score[j];score[j] score[j1]; score[j1]temp; } } }printScore(score);
}
int main()
{
int score[N]{67,98,75,63,82,79,81,91,66,84};
int sum,avg,max,min;sum getTotalScore(score);avg getAvgScore(score);max getMax(score);min getMin(score);
printf(总分是%d\n,sum);
printf(平均分是%d\n,avg);
printf(最高分是%d\n,max);
printf(最低分是%d\n,min);
printf(----------成绩排名---------\n);sort(score);
return 0;
} 五、C语言的编译过程 C语言的编译链接过程要把我们编写的一个C程序源代码转换成可以在硬件上运行的程序可执行代码需要进行编译和链接。过程图解如下 本文讲解C语言编译过程中所做的工作对我们理解头文件、库等的工作过程是有帮助的。而且清楚的了解编译链接过程还对我们在编程时定位错误以及编程时尽量调动编译器的检测错误会有很大的帮助的。
编译 编译是读取源程序字符流对之进行词法和语法的分析将高级语言指令转换为功能等效的汇编代码源文件的编译过程包含预处理与编译优化两个主要阶段。
预处理 第一个阶段是预处理阶段在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同因为可用的硬件或操作系统是不同的。在许多情况下可以把用于不同环境的代码放在同一个文件中再在预处理阶段修改代码使之适应当前的环境。 主要是以下几方面的处理
宏定义指令如 #define a b 对于这种伪指令预编译所要做的是将程序中的所有a用b替换但作为字符串常量的 a则不被替换。还有 #undef则将取消对某个宏的定义使以后该串的出现不再被替换。
条件编译指令如#ifdef#ifndef#else#elif#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件将那些不必要的代码过滤掉。
头文件包含指令如#include FileName或者#include FileName等。 在头文件中一般用伪指令#define定义了大量的宏最常见的是字符常量同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中只需加上一条#include语句即可而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号 。另外开发人员也可以定义自己的头文件这些文件一般与c源程序放在同一目录下此时在#include中要用双引号。
特殊符号预编译程序可以识别一些特殊的符号。 例如在源程序中出现的LINE标识将被解释为当前行号十进制数FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的但内容有所不同。下一步此输出文件将作为编译程序的输出而被翻译成为机器指令。
编译、优化 第二个阶段编译、优化阶段经过预编译得到的输出文件中只有常量如数字、字符串、变量的定义以及c语言的关键字如main,if,else,for,while,{,}, ,-,*,\等等。 编译程序所要做的工作就是通过词法分析和语法分析在确认所有的指令都符合语法规则之后将其翻译成等价的中间代码表示或汇编代码。 优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。 对于前一种优化主要的工作是删除公共表达式、循环优化代码外提、强度削弱、变换循环控制条件、已知量的合并等、复写传播以及无用赋值的删除等等。 后一种类型的优化同机器的硬件结构密切相关最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值以减少对于内存的访问次数。另外如何根据机器硬件执行指令的特点如流水线、RISC、CISC、VLIW等而对指令进行一些调整使目标代码比较短执行的效率比较高也是一个重要的研究课题。
汇编 汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段 代码段该段中所包含的主要是程序的指令。 该段一般是可读和可执行的但一般却不可写。 数据段主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读可写可执行的。 UNIX环境下主要有三种类型的目标文件
可重定位文件 其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
共享的目标文件 这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起创建一个进程映象。
可执行文件 它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到这个就是链接程序的工作了。
链接过程 由汇编程序生成的目标文件并不能立即就被执行其中可能还有许多没有解决的问题。 例如某个源文件中的函数可能引用了另一个源文件中定义的某个符号如变量或者函数调用等在程序中可能调用了某个库文件中的函数等等。所有的这些问题都需要经链接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。 根据开发人员指定的同库函数的链接方式的不同链接处理可分为两种
静态链接 在这种链接方式下函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合其中的每个文件含有库中的一个或者一组相关函数的代码。
动态链接 在此种方式下函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所做的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。 对于可执行文件中的函数调用可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小并且当共享对象被多个进程使用时能节约一些内存因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。 六、C语言总结的文件操作
C 语言把文件看作是一个字符字节的序列即由一个一个字符字节的数据顺序组成。
根据数据的组织形式可分为 ASCⅡ 文件和二进制文件。文件的操作包括文件的打开、文件的关闭、文件的读写操作、文件状态检查以及文件的定位等。
1 文件的打开
1.1 函数原型
FILE *fopen(char *pname,char *mode)
1.2 功能说明
按照 mode 规定的方式打开由 pname指定的文件。若找不到由 pname 指定的相应文件就按以下方式之一处理
此时如 mode 规定按写方式打开文件就按由pname 指定的名字建立一个新文件此时如 mode 规定按读方式打开文件就会产生一个错误
打开文件的作用
分配给打开文件一个FILE 类型的文件结构体变量并将有关信息填入文件结构体变量开辟一个缓冲区调用操作系统提供的打开文件或建立新文件功能打开或建立指定文件
FILE *指出 fopen 是一个返回文件类型的指针函数
1.3 参数说明
pname是一个字符指针它将指向要打开或建立的文件的文件名字符串。mode是一个指向文件处理方式字符串的字符指针。
1.4 返回值
正常返回被打开文件的文件指针。 异常返回NULL表示打开操作不成功。
//定义一个名叫fp文件指针
FILE *fp
//判断按读方式打开一个名叫test的文件是否失败
if((fpfopentestr) NULL)//打开操作不成功
{printf(The file can not be opened.\n) exit(1);//结束程序的执行
}
要说明的是C 语言将计算机的输入输出设备都看作是文件。例如键盘文件、屏幕文件等。
ANSI C 标准规定在执行程序时系统先自动打开键盘、屏幕、错误三个文件。这三个文件的文件指针分别是标准输入 stdin、标准输出 stdout 和标准出错 stderr。
2 文件的关闭
2.1 函数原型
int fclose(FILE *fp)
2.2 功能说明
关闭由 fp 指出的文件。此时调用操作系统提供的文件关闭功能关闭由 fp-fd 指出的文件释放由 fp 指出的文件类型结构体变量返回操作结果即 0 或 EOF。
2.3 参数说明
fp一个已打开文件的文件指针。
2.4 返回值
正常返回0。 异常返回EOF表示文件在关闭时发生错误。
int nfclose(fp);
3 文件的读写操作
3.1 从文件中读取一个字符
3.1.1 函数原型
int fgetc(FILE *fp)
3.1.2 功能说明
从fp所指文件中读取一个字符。
3.1.3 参数说明
fp这是个文件指针它指出要从中读取字符的文件。
3.1.4返回值
正常返回返回读取字符的代码。 非正常返回返回 EOF。例如要从写打开文件中读取一个字符时会发生错误而返回一个 EOF。
显示指定文件的内容:
//程序名为display.c
//执行时可用display filename1 形式的命令行运行。显示文件filename1中的内容。例如执行命令行display display.c将在屏幕上显示display的原代码。//File display program.
#include stdio.h
void main(int argc,char *argv[]) //命令行参数
{int ch;//定义文件类型指针FILE *fp;//判断命令行是否正确if(argc!2){printf(Error format,Usage: display filename1\n);return; //键入了错误的命令行结束程序的执行}//按读方式打开由argv[1]指出的文件if((fpfopen(argv[1],r))NULL){printf(The file %s can not be opened.\n,argv[1]);//打开操作不成功return;//结束程序的执行}//成功打开了argv[1]所指文件chfgetc(fp); //从fp所指文件的当前指针位置读取一个字符while(ch!EOF) //判断刚读取的字符是否是文件结束符{putchar(ch); //若不是结束符将它输出到屏幕上显示chfgetc(fp); //继续从fp所指文件中读取下一个字符} //完成将fp所指文件的内容输出到屏幕上显示fclose(fp); //关闭fp所指文件
}
3.2 写一个字符到文件中去3.2.1 函数原型
int fputc(int ch,FILE *fp)
3.2.2 功能说明
把 ch中的字符写入由 fp 指出的文件中去。
3.2.3 参数说明
ch是一个整型变量内存要写到文件中的字符C 语言中整型量和字符量可以通用。fp这是个文件指针指出要在其中写入字符的文件。
3.2.4 返回值
正常返回要写入字符的代码。 非正常返回返回 EOF。例如要往读打开文件中写一个字符时会发生错误而返回一个EOF。
将一个文件的内容复制到另一个文件中去:
//程序名为copyfile.c
//执行时可用copyfile filename1 filename2形式的命令行运行将文件filename1中的内容复制到文件filename2中去。
//file copy program.
#include stdio.h
void main(int argc,char *argv[]) //命令行参数
{int ch;FILE *in,*out; //定义in和out两个文件类型指针if(argc!3) //判断命令行是否正确{printf(Error in format,Usage: copyfile filename1 filename2\n);return; //命令行错结束程序的执行}//按读方式打开由argv[1]指出的文件if((infopen(argv[1],r))NULL){printf(The file %s can not be opened.\n,argv[1]);return; //打开失败结束程序的执行}//成功打开了argv[1]所指文件再//按写方式打开由argv[2]指出的文件if((outfopen(argv[2],w))NULL){printf(The file %s can not be opened.\n,argv[2]);return; //打开失败结束程序的执行}//成功打开了argv[2]所指文件chfgetc(in); //从in所指文件的当前指针位置读取一个字符while(ch!EOF) //判断刚读取的字符是否是文件结束符{fputc(ch,out); //若不是结束符将它写入out所指文件chfgetc(in); //继续从in所指文件中读取下一个字符} //完成将in所指文件的内容写入复制到out所指文件中fclose(in); //关闭in所指文件fclose(out); //关闭out所指文件
}
按十进制和字符显示文件代码若遇不可示字符就用井号#字符代替之。
//程序名为dumpf.c
//执行时可用dumpf filename1 形式的命令行运行。
// File dump program.
#include stdio.h
void main(int argc,char *argv[])
{char str[9];int ch,count,i;FILE *fp;if(argc!2){printf(Error format,Usage: dumpf filename\n);return;}if((fpfopen(argv[1],r))NULL){printf(The file %s can not be opened.\n,argv[1]);return;}count0;do{i0;//按八进制输出第一列作为一行八个字节的首地址printf(%06o: ,count*8);do{// 从打开的文件中读取一个字符chfgetc(fp);// 按十进制方式输出这个字符的ASCII码printf(%4d,ch);// 如果是不可示字符就用#字符代替if(ch ||ch~) str[i]#;// 如果是可示字符就将它存入数组str以便形成字符串else str[i]ch;// 保证每一行输出八个字符if(i8) break;}while(ch!EOF); // 遇到文件尾标志结束读文件操作str[i]\0; // 在数组str加字符串结束标志for(;i8;i) printf( ); // 一行不足八个字符用空格填充printf( %s\n,str); // 输出字符串count; // 准备输出下一行}while(ch!EOF); // 直到文件结束fclose(fp); // 关闭fp所指文件
}
3.3 从文件中读取一个字符串3.3.1 函数原型
char *fgets(char *str,int n,FILE *fp)
3.3.2 功能说明
从由 fp 指出的文件中读取 n-1 个字符并把它们存放到由 str 指出的字符数组中去最后加上一个字符串结束符 \0。
3.3.3 参数说明
str接收字符串的内存地址可以是数组名也可以是指针。 n指出要读取字符的个数。 fp这是个文件指针指出要从中读取字符的文件。
3.3.4 返回值
正常返回返回字符串的内存首地址即 str 的值。 非正常返回返回一个 NULL 值此时应当用 feof() 或 ferror() 函数来判别是读取到了文件尾还是发生了错误。例如要从写打开文件中读取字符串将发生错误而返回一个 NULL 值。
3.4 写一个字符串到文件中去
3.4.1函数原型
int fputs(char *str,FILE *fp)
3.4.2 功能说明
把由 str 指出的字符串写入到 fp 所指的文件中去。
3.4.3 参数说明
str指出要写到文件中去的字符串。fp这是个文件指针指出字符串要写入其中的文件。
3.4.4 返回值
正常返回写入文件的字符个数即字符串的长度。 非正常返回返回一个 NULL 值此时应当用 feof() 或 ferror( )函数来判别是读取到了文件尾还是发生了错误。例如要往一个读打开 文件中写字符串时会发生错误而返回一个NULL值。
将一个文件的内容附加到另一个文件中去。
//程序名linkfile.c
//执行时可用linkfile filename1 filename2形式的命令行运行将文件filename2的内容附加在文件filename1之后。
// file linked program.
#include stdio.h
#define SIZE 512
void main(int argc,char *argv[])
{char buffer[SIZE];FILE *fp1,*fp2;if(argc!3){printf(Usage: linkfile filename1 filename2\n);return;}// 按追加方式打开argv[1] 所指文件if((fp1fopen(argv[1],a))NULL){printf(The file %s can not be opened.\n,argv[1]);return;}if((fp2fopen(argv[2],r))NULL){printf(The file %s can not be opened.\n,argv[2]);return;}// 读入一行立即写出直到文件结束while(fgets(buffer,SIZE,fp1)!NULL)printf(%s\n,buffer);while(fgets(buffer,SIZE,fp2)!NULL)fputs(buffer,fp1);fclose(fp1);fclose(fp2);if((fp1fopen(argv[1],r))NULL){printf(The file %s can not be opened.\n,argv[1]);return;}while(fgets(buffer,SIZE,fp1)!NULL)printf(%s\n,buffer);fclose(fp1);
}
3.5 往文件中写格式化数据3.5.1 函数原型
int fprintf(FILE *fp,char *format,arg_list)
3.5.2 功能说明
将变量表列arg_list中的数据按照 format 指出的格式写入由 fp 指定的文件。fprintf() 函数与 printf() 函数的功能相同只是 printf() 函数是将数据写入屏幕文件stdout。
3.5.3 参数说明
fp这是个文件指针指出要将数据写入的文件。 format这是个指向字符串的字符指针字符串中含有要写出数据的格式所以该字符串成为格式串。格式串描述的规则与 printf() 函数中的格式串相同。 arg_list是要写入文件的变量表列各变量之间用逗号分隔。
//存储文件名save.txt
//程序代码如下
// file display program.
#include stdio.h
void main()
{char name[10];int nAge,nClass;long number;FILE *fp;if((fpfopen(student.txt,w))NULL){printf(The file %s can not be opened.\n,student.txt);return;}fscanf(stdin,%s %d %d %ld,name,nClass,nAge,number);fprintf(fp,%s %5d %4d %8ld,name,nClass,nAge,number);fclose(fp);if((fpfopen(student.txt,r))NULL){printf(The file %s can not be opened.\n,student.txt);return;}fscanf(fp,%s %d %d %ld,name,nClass,nAge,number);printf(name nClass nAge number\n);fprintf(stdout,%-10s%-8d%-6d%-8ld\n,name,nClass,nAge,number);fclose(fp);
}
3.6 以二进制形式读取文件中的数据3.6.1 函数原型
int fread(void *buffer,unsigned sife,unsigned count,FILE *fp)
3.6.2 功能说明
从由 fp 指定的文件中按二进制形式将 sife*count 个数据读到由 buffer 指出的数据区中。
3.6.3 参数说明
buffer这是一个 void 型指针指出要将读入数据存放在其中的存储区首地址。sife指出一个数据块的字节数即一个数据块的大小尺寸。 count指出一次读入多少个数据块sife。 fp这是个文件指针指出要从其中读出数据的文件。
3.6.4 返回值
正常返回实际读取数据块的个数即 count。 异常返回如果文件中剩下的数据块个数少于参数中 count 指出的个数或者发生了错误返回 0 值。此时可以用feof() 和 ferror() 来判定到底出现了什么情况。
3.7 以二进制形式写数据到文件中去
3.7.1 函数原型
int fwrite(void *buffer,unsigned sife,unsigned count,FILE *fp)
3.7.2 功能说明
按二进制形式将由 buffer 指定的数据缓冲区内的 sife*count 个数据写入由 fp 指定的文件中去。
3.7.3 参数说明
buffer这是一个 void型指针指出要将其中数据输出到文件的缓冲区首地址。 sife指出一个数据块的字节数即一个数据块的大小尺寸。 count一次输出多少个数据块sife。 fp这是个文件指针指出要从其中读出数据的文件。
3.7.4 返回值
正常返回实际输出数据块的个数即 count。 异常返回返回0值表示输出结束或发生了错误。
#include stdio.h
#define SIZE 4
struct worker
{ int number;char name[20];int age;
};
void main()
{struct worker wk;int n;FILE *in,*out;if((infopen(file1.txt,rb))NULL){printf(The file %s can not be opened.\n,file1.txt);return;}if((outfopen(file2.txt,wb))NULL){printf(The file %s can not be opened.\n,file2.txt);return;}while(fread(wk,sizeof(struct worker),1,in)1)fwrite(wk,sizeof(struct worker),1,out);fclose(in);fclose(out);
}
3.8 以二进制形式读取一个整数 3.8.1 函数原型
int getw(FILE *fp)
3.8.2 功能说明
从由 fp 指定的文件中以二进制形式读取一个整数。
3.8.3 参数说明
fp是文件指针。
3.8.4 返回值
正常返回所读取整数的值。 异常返回返回 EOF即 -1。由于读取的整数值有可能是 -1所以必须用 feof() 或 ferror() 来判断是到了文件结束还是出现了一个出错。
#include stdio.h
void main(int argc,char *argv[])
{int i,sum0;FILE *fp;if(argc!2){printf(Command error,Usage: readfile filename\n);exit(1);}if(!(fpfopen(argv[1],rb))){printf(The file %s can not be opened.\n,argv[1]);exit(1);}for(i1;i10;i) sumgetw(fp);printf(The sum is %d\n,sum);fclose(fp);
}
3.9 以二进制形式存贮一个整数3.9.1 函数原型
int putw(int n,FILE *fp)
3.9.2 功能说明
以二进制形式把由变量 n 指出的整数值存放到由 fp 指定的文件中。
3.9.3 参数说明
n要存入文件的整数。 fp是文件指针。
3.9.4 返回值
正常返回所输出的整数值。 异常返回返回 EOF即 -1。由于输出的整数值有可能是 -1所以必须用 feof() 或 ferror() 来判断是到了文件结束还是出现了一个出错。
#include stdio.h
void main(int argc,char *argv[])
{int i;FILE *fp;if(argc!2){printf(Command error,Usage: writefile filename\n);return;}if(!(fpfopen(argv[1],wb))){printf(The file %s can not be opened.\n,argv[1]);return;}for(i1;i10;i) printf(%d\n, putw(i,fp));fclose(fp);
}
4 文件状态检查
4.1 文件结束
4.1.1 函数原型
int feof(FILE *fp)
4.1.2 功能说明
该函数用来判断文件是否结束。
4.1.3 参数说明
fp文件指针。
4.1.4 返回值
0假值表示文件未结束。 1真值表示文件结束。
#include stdio.h
void main(int argc,char *argv[])
{FILE *in,*out;char ch;if(argc!3){printf(Usage: copyfile filename1 filename2\n);return;}if((infopen(argv[1],rb))NULL){printf(The file %s can not be opened.\n,argv[1]);return;}if((outfopen(argv[2],wb))NULL){printf(The file %s can not be opened.\n,argv[2]);return;}while(!feof(in)){chfgetc(in);if(ferror(in)){printf(read error!\n);clearerr(in);}else{fputc(ch,out);if(ferror(out)){printf(write error!\n);clearerr(out);}}}fclose(in);fclose(out);
}
4.2 文件读/写出错4.2.1 函数原型
int ferror(FILE *fp)
4.2.2 功能说明
检查由 fp 指定的文件在读写时是否出错。
4.2.3 参数说明
fp文件指针。
4.2.4 返回值
0假值表示无错误。 1真值表示出错。
4.3 清除文件错误标志
4.3.1 函数原型
void clearerr(FILE *fp)
4.3.2 功能说明
清除由 fp 指定文件的错误标志。
4.3.3 参数说明
fp文件指针。
4.3.4 返回值
无。
#include stdio.h
void main(int argc,char *argv[])
{FILE *in,*out;char ch;if(argc!3){printf(Usage: copyfile filename1 filename2\n);return;}if((infopen(argv[1],rb))NULL){printf(The file %s can not be opened.\n,argv[1]);return;}if((outfopen(argv[2],wb))NULL){printf(The file %s can not be opened.\n,argv[2]);return;}while(!feof(in)){chfgetc(in);if(ferror(in)){printf(read error!\n);clearerr(in);}else{fputc(ch,out);if(ferror(out)){printf(write error!\n);clearerr(out);}}}fclose(in);fclose(out);
}
4.4 了解文件指针的当前位置4.4.1 函数原型
long ftell(FILE *fp)
4.4.2 功能说明
取得由 fp 指定文件的当前读/写位置该位置值用相对于文件开头的位移量来表示。
4.4.3参数说明
fp文件指针。
4.4.4 返回值
正常返回位移量这是个长整数。 异常返回-1表示出错。
5 文件定位
5.1 反绕
5.1.1 函数原型
void rewind(FILE *fp)
5.1.2 功能说明
使由文件指针 fp 指定的文件的位置指针重新指向文件的开头位置。
5.1.3 参数说明
fp文件指针。
5.1.4返回值
无。
#include stdio.h
void main()
{FILE *in,*out;infopen(filename1,r);outfopen(filename2,w);while(!feof(in)) fputc(fgetc(in),out);rewind(out);while(!feof(in)) putchar(fgetc(in));fclose(in);fclose(out);
}
5.2 随机定位5.2.1 函数原型
int fseek(FILE *fp,long offset,int base)
5.2.2 功能说明
使文件指针 fp 移到基于 base 的相对位置 offset 处。
5.2.3 参数说明
fp文件指针。 offset相对 base 的字节位移量。这是个长整数用以支持大于 64KB 的文件。 base文件位置指针移动的基准位置是计算文件位置指针位移的基点。ANSI C 定义了 base 的可能取值以及这些取值的符号常量。
5.2.4 返回值
正常返回当前指针位置。 异常返回-1表示定位操作出错。
#include stdio.h
#include string.h
struct std_type
{int num;char name[20];int age;char class;
}stud;
int cstufile()
{int i;FILE *fp;if((fpfopen(stufile,wb))NULL){printf(The file cant be opened for write.\n);return 0;}for(i1;i100;i){stud.numi;strcpy(stud.name,aaaa);stud.age17;stud.class8;fwrite(stud,sizeof(struct std_type),1,fp);}fclose(fp);return 1;
}
void main()
{int n;FILE *fp;if(cstufile()0) return;if((fpfopen(stufile,rb))NULL){printf(The file can not be opened.\n);return;}for(n0;n100;n2){fseek(fp,n*sizeof(struct std_type),SEEK_SET);fread(stud,sizeof(struct std_type),1,fp);printf(%10d%20s%10d%4c\n,stud.num,stud.name,stud.age,stud.class);}fclose(fp);
}
5.3 关于exit()函数
5.3.1 函数原型
void exit(int status)
5.3.2 功能说明
exit() 函数使程序立即终止执行同时将缓冲区中剩余的数据输出并关闭所有已经打开的文件。
5.3.3 参数说明
status为 0 值表示程序正常终止为非 0 值表示一个定义错误。
5.3.4 返回值
无。
5.4 关于feof()函数
5.4.1 函数原型
int feof(FILE *fp)
5.4.2 功能说明
在文本文件ASCII 文件中可以用值为 -1 的符号常量 EOF 来作为文件的结束符。但是在二进制文件中 -1 往往可能是一个有意义的数据因此不能用它 来作为文件的结束标志。为了能有效判别文件是否结束ANSI C 提供了标准函数 feof()用来识别文件是否结束。
5.4.3 参数说明
fp文件指针。
5.4.4 返回值
返回为非 0 值已到文件尾。返回为 0 值表示还未到文件尾。