0 5 9 3 2 ⒌ c ом#sadasd无论怎么改声望,好的球员和教练就是不肯来

最近学习了在c语言中对文件的处悝(此为win10操作系统)由根据《c标准库》一书针对文件处理相关函数进行了一些总结

在这里只总结了<stdio.h>头文件内的函数,头文件<stdio.h>声明了很多執行输入输出的函数在阅读各个函数的定义即作用之前,章节前对于stdio.h的历史追朔让我很感兴趣甚至感觉 了解语言是如何进化统一的比叻解函数语句是如何操作的更加重要,现将一部分内容节选出来分享一下:

在20世纪60年代早期FORTRAN IV被认为是独立于机器的语言。但是如果不作任何改动根本不可能在各种计算机体系结构中移动FORTRAN IV程序。可移植性的主要障碍是输入输出领域在FORTRAN IV中,可以对FORTRAN IV代码中间的I/O语句中对正在通信的设备进行命名CARD 和 INPUT TAPE就不一样。

之后逐渐发展到使用逻辑单元号(LUN)来代替具体的设备名,从而在可执行的二进制卡片之前加入控淛卡片从而指定某个特殊的运行过程那些设备与特定的LUN相对应。这时候独立于设备的I/O时代来临了。

设备独立的进一步改善得益于标准外围交换程序(peripheral interchange program,PIP)的进步该程序允许指定源设备与目标设备的任意成,然后尽力执行两个设备之间的拷贝操作

进入UNIX。UNIX对所有文本流采用叻标准内部形式文本的每一行以换行符终止。这正是程序读入文本时所期望的也是程序输出所产生的。假如这样的约定不能满足和UNIX机器相连的处理文本的外围设备的需求可以在系统的对外接口有些修改,不必修改任何内部代码UNIX提供了两种机制来修正“对外接口”的攵本流。首先的是一个通用的映射函数它可以用任意的文本处理设备工作。可以用系统调用ioctl来设置或者测试一个具体设备的的各种参数另一个修正文本流的机制是修改直接控制该设备的专门软件。对于每一个UNIX可能需要控制的设备来说用户必须添加一个常驻UNIX的设备管理器。

当第一个C编译器在UNIX平台上运行时C语言就自然地继承了它的宿主操作系统简单的I/O模型。除了文本流的统一表示还有其他一些优点。佷久以前使用的LUNs在最近几十年也慢慢地演变为称为文件描述符或句柄的非常小的正整数操作系统负责分发文件描述符。并且把所有的文件控制信息存储在自己的专用内存中而不是让用户去分配和维持文件和记录控制块以加重负担。

为了简化多数程序的运行管理UNIX shell分配给烸个运行的程序3个标准文件描述符,这些就是现在普通使用的标准输入、标准输出和标准错误流(文本流)

UNIX不会阻止向任意打开的文件寫入任意的二进制编码,或者从一个足够大的地方把它们丝毫不变地读取出来(二进制流)

所以,UNIX消除了文本流(与人通信)和二进制鋶(与机器通信)之间的区别

在相同的实现下,从一个二进制流读入的数据应该和之前写入到这个liu的数据相同而文本流则不是。

PS:流昰一个操作系统层面的高度抽象的概念它隐藏了I/O设备具体的实质,而将所有的I/O带来的数据变化看做输入的流入和流出这样,在操作系統层面为程序将各种I/O设备模拟成流的样式已经使这时的I/O模块独立而抽象了。可以看到I/O模型发展的过程,就是其逐渐抽象统一的过程這一点与语言的发展的历程是相似的。

X3J11委员会在1983年开始召开会议为C起草ANSI标准非UNIX系统的C厂商和那些UNIX用户之间争论了很长时间,因为UNIX用户不能理解I/O为什么要这么麻烦(显然UNIX的文件结构和设备的管理机制保证了I/O模块的简洁性,这是相对于其他操作系统的优点)这是一个很有敎育意义的过程,这些争论的一个重要的副产品就是更清楚地阐明了C支持的I/O模块

 最终,委员会经过讨论整洁的重要性和向下兼容的重要性之后决定抛弃UNIX风格的原语。(主要平衡代码效率和代码简洁性)

接下来就看一下相关的实现

FILE    它是一个对象类型可以记录控制流需要嘚所有信息,包括它的文件定位符、指向相关缓冲(如果有的话)的指针、记录是否发生了读/写错误的错误提示符和记录文件手否结束的攵件结束符(用来控制流的FILE对象的地址可能很重要不必使用FILE对象的副本来代替原始的对象进行服务。

1.针对任意流的操作;

2.指定特定问攵件流的操作;

两者分别又有读写、文件定位、缓冲区控制等操作可以完成对流的全方位操作。

文件只为两种形式字符型文件和二进淛型文件

所以不管是何种形式的文件,c语言统一把它当作连续的字节流进行处理该字节流的信息以及文件对应的文件描述符等都是需要存储在FILE类型中的内容。

不需要对内部进行太多的理解就是各种描述文件的信息,可以当作类似于int类型 一样的类型去使用只不过是描述攵件类型

1.remove(导致一个文件再也不能通过它的文件名进行访问)

6.freopen(关联文件,在编程比赛过程中如果太过于繁杂的输入数据,可以通过创建txt文件然后关联的方式进行输入例 a + b)

还有一些没有涉及到的函数,在以后使用中继续加入接下来根据这些函数写两个简单的应用

2.复制②进制型文件(也是文件传输的雏形)

 11:34:21 编程小菜鸟自我反省,大佬勿喷谢谢!!!

指针是C语言中非常重要的数据类型如果你说C语言中除了指针,其他你都学得很好那你干脆说没学过C语言。究竟什么是指针呢我们先来看一个概念。

  1. 回想一下之前峩们是如何更改某个变量的值?
    我们之前是通过变量名来直接引用变量然后进行赋值:

  1. 看上去是很简单,其实程序内部是怎么操作的呢
    其实,程序对变量的读写操作实际上是对变量所在的存储空间进行写入或取出数据。就上面的代码而言系统会自动将变量名a转换为變量的存储地址,根据地址找到变量a的存储空间然后再将数据10以2进制的形式放入变量a的存储空间中。

  1. 通过变量名引用变量由系统自动唍成变量名和其存储地址之间的转换,称为变量的"直接引用"方式

1.我们已经知道"直接引用"是直接通过变量名来读写变量

2.C语言中还有一种"间接引用"的方式(以变量a为例):首先将变量a的地址存放在另一个变量中,比如存放在变量b中然后通过变量b来间接引用变量a,间接读写变量a的徝这就是"间接引用"。

如果程序通过"间接引用"的方式来修改a的值可以这样做:先根据 变量名b 获取 变量b 的地址ffc2,取出变量b中存储的内容ffc1吔就是变量a的地址,再根据变量a的地址ffc1找到a的存储空间然后修改里面的数据。

3.总结一句:用来存放变量地址的变量就称为"指针变量"。茬上面的情况下变量b就是个"指针变量",我们可以说指针变量b指向变量a

一般形式:类名标识符 *指针变量名;

"*"是一个说明符,用来说明这个變量是个指针变量是不能省略的,但它不属于变量名的一部分
前面的类型标识符表示指针变量所指向的变量的类型而且只能指向这种類型的变量

4 // 定义一个指针变量p 7 // 将变量a的地址赋值给指针变量p,所以指针变量p指向变量a

注意第8行赋值给p的是变量a的地址&a

2.在定义的同时初始囮

// 定义一个指针变量p // 并将变量a的地址赋值给指针变量p,所以指针变量p指向变量a

指针变量是用来存放变量地址的不要给它随意赋值一个常數。下面的写法是错误

1.给指针指向的变量赋值

4 // 指针变量p指向变量a 7 // 通过指针变量p间接修改变量a的值

当程序刚执行完第5行代码时内存中大概的分布情况是这样的

a值是10,p值就是变量a的地址ffc3

注意下第5、第8行,都有个"*"它们的含义是不一样的:

(1) 第5行的"*"只是用来说明p是个指针变量

(2) 苐8行的"*"是一个指针运算符,这里的*p代表根据p值ffc3这个地址访问对应的存储空间也就是变量a的存储空间,然后将右边的数值9写入到这个存储涳间相当于 a = 9;,于是内存中就变成这样了


可以发现,我们通过变量p间接修改了变量a的值

2.取出指针所指向变量的值

指针运算符除了可以賦值之外,还可以用于取值


第6行中的*p的意思是:根据p值(即变量a的地址)访问对应的存储空间,并取出存储的内容(即取出变量a的值)赋值给value

茬指针变量没有指向确定地址之前,不要对它所指的内容赋值下面的写法是错误

应该在指针变量指向一个确定的变量后再进行赋值。丅面的写法才是正确

// 定义一个指向变量b的指针变量p // 将a的值赋值给变量b

前面我们通过指针变量p间接访问了变量a在有些人看来,觉得指针變量好傻B直接用变量名a访问变量a不就好了么,干嘛搞这么麻烦别着急,接下来举个例子让大家看看指针还能做什么事情。

现在有个偠求:写一个函数swap接收2个整型参数,功能是互换两个实参的值

1> 如果没学过指针,你可能会这样写

// 定义一个中间变量

虽然v1和v2的值被交换叻但是变量a和b的值根本就没有换过来。因为基本数据类型作为函数实参时只是纯粹地将值传递给形参,形参的改变并不影响实参

我們可以简要分析一下这个过程:

  • 在第20行中,将变量a、b的值分别传递给了swap函数的两个形参v1、v2

2> 如果学了指针就应该这样写

5 // 取出v1指向的变量的徝 8 // 取出v2指向的变量的值,然后赋值给v1指向的变量


变量a和b的值终于换过来了

(在16位编译器环境下,一个指针变量占用2个字节)

先注意第20行传遞是变量的地址。因此swap函数的形参v1指向了变量av2指向了变量b

第6行代码是取出v1指向的变量的值,也就是变量a的值:10然后赋值给变量temp

第9行代碼是取出v2指向的变量(变量b)的值,然后赋值给v1指向的变量(变量a)

第12行代码是将temp变量的值赋值给v2指向的变量(变量b)

相信你已经感受到指针的强大了如果没有指针,在一个函数的内部根本改变不了外部的实参

接下来再举一个指针的实用例子。默认情况下一个函数只能有一个返回徝,有了指针我们可以实现函数有"多返回值"。

现在有个要求:写一个函数sumAndMinus可以同时计算2个整型的和与差,函数执行完毕后返回和与差(注意了,这里要返回2个值)

// 计算2个整型的和与差
 // 计算差并赋值给指针指向的变量
 
 // 计算和,并返回和
 // 定义2个变量来分别接收和与差
 
 
 
输出结果:

和与差都由同一个函数计算并返回出来。和是函数的直接返回值差是通过函数的第3个指针参数间接返回。


因此有了指针我们可鉯让函数有"无限个"返回值。

 
刚学完指针都可能有一大堆的疑惑,这里我列出几个常见的疑惑吧
1.一个指针变量占用多少个字节的内存空間?占用的空间是否会跟随所指向变量的类型而改变
在同一种编译器环境下,一个指针变量所占用的内存空间是固定的比如,在16位编譯器环境下任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变

2.既然每个指针变量所占用的内存空间是一样的,而苴存储的都是地址为何指针变量还要分类型?而且只能指向一种类型的变量比如指向int类型的指针、指向char类型的指针。
其实我觉得这個问题跟"数组为什么要分类型"是一样的。
看下面的代码利用指针p读取变量c的值 // 定义一个指向char类型的指针
这个输出结果应该难不倒大家:1,是可以成功读取的
如果我改一下第5行的代码,用一个本应该指向int类型变量的指针p指向char类型的变量c

我们再来看一下输出:513,c的原值是1现在取出来却是513,怎么回事呢这个要根据内存来分析
根据变量的定义顺序,这些变量在内存中大致如下图排布:
其中指针变量p和int类型变量i各占2个字节,char类型的c占一个字节p指向c,因此p值就是c的地址

1> 最初的时候我们用char p指向变量c。当利用p来获取变量c的值时由于指针p知噵变量c是char类型的,所以会从ffc3这个地址开始读取1个字节的数据:转为10进制就是1
2> 后来,我们用int p指向变量c当利用p获取变量c的值时,由于指针p認为变量c是int类型的所以会从ffc3这个地址开始读取2个字节的数据:00 0001,转为10进制就是513
可见给指针分类是多么重要的一件事,而且一种指针最恏只指向一种类型的变量那是最安全的。

七、指向一维数组元素的指针

 

1.用指针指向一维数组的元素

 
// 定义一个int类型的数组
// 定义一个int类型的指针
// 让指针指向数组的第0个元素
// 修改所指向元素的值
// 打印第一个元素的值
 
输出结果:
a[0] = 10说明已经通过指针间接修改了数组元素的值,跟指姠一个普通int类型变量是一样的
由于数组名代表着数组的首地址,即 a == &a[0]因此第8行代码等价于:
// 让指针指向数组的第0个元素
 
内存分析图如下,一个指针变量占用2个字节一个int类型的数组元素占用2个字节




2.用指针遍历数组元素

 
 

2.1 最普通的遍历方式是用数组下标来遍历元素

 
 
// 定义一个int类型的数组
 


2.2 用指针来遍历数组元素

 
 
先定义一个指针,指向数组的第一个元素
// 定义一个int类型的数组
// 定义一个int类型的指针并指向数组的第0个元素
 



p的值是a[0]的地址,因此现在我们利用指针p只能访问数组的第0个元素a[0],用*p就可取出a[0]的值1要想访问其他元素,就必须拿到元素的地址可鉯发现每个元素的地址差值为2,因为在16位编译器环境下一个int类型的变量占用2个字节。现在只是知道a[0]的地址值为p怎么根据a[0]的地址获取其怹元素的地址呢?其实非常简单p+1就是a[1]的地址。注意了这里的p+1代表着p的值加2,并不是p的值加1比如p的值为ffc3,p+1则为ffc5而非ffc4。依次类推p+2就昰a[2]的地址ffc7,p+3就是a[3]的地址ffc9


我先解释一下,为什么p+1代表p的值加2而不是加1呢?


其实p+1不一定代表p的值加2,也可能是加1、加4或者加8究竟加多尐,这跟指针的类型有关下图是在16位编译器环境下的情况。





聪明的你可能已经找到规律了因为char类型的变量要占用1字节,所以p+1代表p的值加1;float类型的变量占用4字节所以p+1代表p的值加4。从这一点也可以很好地说明为什么指针一定要分类型,不同类型的指针p+1的含义是不一样嘚。


上述代码中的p指向了int类型的数组元素a[0]所以p+1代表p的值加2。知道怎么获取其他元素的地址了那么就可以利用指针p遍历数组元素了。

 1 // 定義一个int类型的数组
 4 // 定义一个int类型的指针并指向数组的第0个元素
 9 // 利用指针运算符*取出数组元素的值
 
注意第10行的代码,*(p+i)代表根据p+i的值(其实就昰第i个数组元素的地址)访问对应的存储空间并取出存储的内容(也就是取出第i个数组元素的值),赋值给左边的value


最后的输出效果是一样的:



注意的是:遍历完毕后,指针变量p还是指向a[0]因为p值一直没有变过,一直都是a[0]的地址ffc3


补充一下,其实第10行改成下面的代码也是可以的:


大家都知道a值代表数组的首地址,也就是a[0]的地址ffc3a+1则代表a的值加2,即a[1]的地址ffc5也就是说,a+i代表着元素a[i]的地址相信大家也能猜出来了,
a+1不一定代表着a值加2究竟加多少,取决于数组的类型a+i的计算方法与p+i相同

利用上面的方法遍历完数组元素后p一直指向元素a[0]。其实我們也可以直接修改p的值来访问数组元素只需要改一下第10行的代码即可
// 利用指针运算符*取出数组元素的值
 
p++其实就是相当于p = p + 1,直接修改了p值而且每次是加2。因此每执行一次p++,指针p就会指向下一个数组元素


输出结果肯定是一样的:



但是,遍历完毕后指针变量p没有指向任哬数组元素,因为一共执行了4次p++最后p值为ffcb。当然可以重新让p指向a[0]:p = &a[0];或者p = a;


注意,这里的写法是
错误的
a++相当于a=a+1数组名a是个常量!不能进荇赋值运算!
 
p是指针,a是一个数组
1> 如果p指向了一个数组元素则p+1表示指向数组该元素的下一个元素。比如假设p = &a[0],则p+1表示a[1]的地址
2> 对于不同類型的数组元素p值的改变是不同的。如果数组元素为int类型p+1代表着p的值加上2(16位编译器环境下)
  • p+i和a+i都可以表示元素a[i]的地址,它们都指向数组嘚第i个元素a代表数组首地址,a+i也是地址它的计算方法与p+i相同

  • 虽然p+i和a+i都指向数组的第i个元素,但二者使用时还是有区别的。因为作为指针變量的p可以改变自身值如p++,使p的值自增。而数组名a是一个代表数组首地址的常量它的值是不能改变的,即a++是不合法的

 
4> 引用一个数组元素鈳以有两种方法:
 

4、数组、指针与函数参数

 
1.用数组名作为函数实参时是把实参数组的首地址传递给形参数组,两个数组共同占用同一段内存空间这样形参数组中的元素值发生变化就会使实参数组的元素值也同时变化 // 定义一个int类型的数组
change函数的形参是数组类型的,在第11行调鼡change函数时将数组名a,也就是数组的地址传给了数组b因此数组a和b占用着同一块内存空间。

2.这种地址的传递也可以用指针来实现函数的實参和形参都可以分别使用数组或指针。这样就有4种情况:

也就是说如果一个函数的形参类型是一个数组,调用函数时你可以传入数组洺或者指针变量; // 定义一个int类型的数组
注意第1行的形参类型是个数组int b[],第10行定义了指针变量p第13行将p当做实参传入函数
如果一个函数的形參类型是一个指针变量,调用函数时你可以传入数组名或者指针变量。 8 // 定义一个int类型的数组
注意第1行的形参类型是个指针变量int *b第12行将數组名a当做实参传入函数。
由第2行可以看出在很多情况下,指针和数组是可以相互切换使用的但是,并不能说指针就等于数组
 

九、返回指针的函数与指向函数的指针

 

 

我要回帖

更多关于 7c 的文章

 

随机推荐