C语言入门第六章知识点总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
传智播客C/C++课堂笔记C语言入门6
6.1如何节约内存-位运算
内存单元,即1B,我们说char型占1个内存单元(1B),而short型占2个内存单元(2B)。
1B被当成整体来看。
但同时1 B = 8 bits1个字节有8个位,每个位有0、1两个取值。
总体来说,C语言中的位运算符有以下两类:
位逻辑运算符:(位逻辑运算以位(bit)为单位)
&(位“与”)、^(位“异或”)、|(位“或”)、~(位“取反”)。
移位运算符:<<(左移)、>>(右移)
位“取反”:操作符为“~”,如果A为10101010,那么~A返回的结果为01010101,即每位都取反,0变成1,1变成0,需要注意的是,位取反运算并不改变操作数的值位“与”:操作符为&,将对两个操作数的每一位进行与运算,位“与”运算的准则如下:1 & 1=1 1 & 0=0 0 & 1=0 0 & 0=0
位“或”:操作符为|,将对两个操作数的每一位进行或运算,位“或”运算的准则如下:1 | 1=1 1 | 0=1 0 | 1=1 0 | 0=0
位“异或”:操作符为^,将对两个操作数的每一位进行异或运算。
通俗地讲,如果两个位相同(同为0或同为1),结果为0,若两个位不同(一个为0,另一个为1),结果为1,对应的准则为:1 ^ 1=0 1 ^ 0=1 0 ^ 1=1 0 ^ 0=0
移位运算表达式的基本形式为:
A << n; /*左移*/
或
A >> n; /*右移*/
A称为操作数,其必须为数字型变量或数字型常量,此处的数字型包括整型、浮点型和char型,A中存储的0、1序列向左或右移动n位,移动后的值作为整个表达式的输出,执行移位运算并不改变操作数A的值。
小结:
位运算主要分为位逻辑运算和移位运算两大类,位逻辑运算主要有位取反运算、位或运算、位与运算和位异或运算,使用时应注意和普通变量的逻辑运算区分。
移位运算分为向左移动和向右移动两类,对无符号数或有符号正数来说,编译器会自动为空白位补0,对有符号负数来说,当填充的空白位牵扯到符号位时,编译器会对符号位进行特殊处理。
6.2不局限内存-文件
输入输出都是由printf函数和scanf函数来完成,完成的也只是极其简单的任务,所有的变量和数字什么的都是放在内存中,一旦断电,所有的数据都会丢失,有时,希望能将结果保存起来,下次开机时再使用,这就要用到文件。
文件:存储在外部介质上数据的集合,是操作系统数据管理的单位。
文件分类
按文件的逻辑结构:
记录文件:由具有一定结构的记录组成(定长和不定长)
流式文件:由一个个字符(字节)数据顺序组成
按存储介质:
普通文件:存储介质文件(磁盘、磁带等)
设备文件:非存储介质(键盘、显示器、打印机等)
按数据的组织形式:
文本文件:ASCII文件,每个字节存放一个字符的ASCII码
二进制文件:数据按其在内存中的存储形式原样存放
6.2.1 二进制文件和文本文件的区别
6.2.2 文件标识
⏹每个文件都以文件名为标识,I/O设备的文件名是系统定义的,如:
⏹COM1或AUX——第一串行口,附加设备
⏹COM2——第二串行口,此外,还可能有COM3、COM4等
⏹CON——控制台(console),键盘(输入用)或显示器(输出用)
⏹LPT1或PRN——第一并行口或打印机
⏹LPT2——第二并行口,还可能有LPT3等
⏹NUL——空设备
磁盘文件可以由用户自己命名,但上述被系统(windows和dos下均是如此)保留的设备名字不能用作文件名,如不能把一个文件命名为CON(不带扩展名)或CON.TXT(不带扩展名)6.2.3流
流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在设备、文件和程序之间的传输就是流,类似于水在管道中的传输,可以看出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。
通过对输入输出源的抽象,屏蔽了设备之间的差异,使程序员能以一种通用的方式进行存储操作,通过对传输信息的抽象,使得所有信息都转化为字节流的形式传输,信息解读的过程与传输过程分离。
C语言中,I/O操作可以简单地看作是从程序移进或移出字节,这种搬运的过程便称为流(stream)。
程序只需要关心是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定I/O设备的细节对程序员是隐藏的。
6.2.4文件的处理方法
缓冲文件系统:高级文件系统,系统自动为正在使用的文件开辟内存缓冲区
非缓冲文件系统:低级文件系统,由用户在程序中为每个文件设定缓冲区
6.2.5重定向
重定向是由操作系统来完成的,一般来说,标准的输出和输入设备通常指的是显示器和键盘,在支持重定向的操作系统中,标准输入输出能被替换。
DOS系统为例,看一段代码:
#include <stdio.h> /*使用printf要包含的头文件*/
void main(void) /*主函数*/
{
printf("本段文字用来测试重定向"); /*输出提示信息*/
getchar(); /*等待,按任意键继续*/
}
Hello.exe <输入,>输出
6.2.6文件类型指针
指针变量说明:FILE *fp;
用法:
文件打开时,系统自动建立文件结构体,并把指向它的指针返回来,程序通过这个指针获得文件信息,访问文件
文件关闭后,它的文件结构体被释放
6.2.7文件型结构体
使用printf函数时,输出设备默认为标准输出设备(一般是显示器),因此,不需要告诉printf 函数显示器在哪。
但如果想从文件中读取输入,情况就不同了,系统中有不同的磁盘,每个磁盘又有成千上万的文件,到底应该从哪个读呢?要想对文件进行操作,系统需要很多控制信息,包括文件名,文件当前读写位置,缓冲区位置和大小等,为此,C语言提供了“文件型”结构来标示记录待操作文件的信息,该结构定义于头文件stdio.h中,其形式为:
struct _iobuf
{
char *_ptr;//当前缓冲区内容指针
int _cnt;//缓冲区还有多少个字符
char *_base;//缓冲区的起始地址
int _flag;//文件流的状态,是否错误或者结束
int _file;//文件描述符
int _charbuf;//双字节缓冲,缓冲2个字节
int _bufsiz;//缓冲区大小
char *_tmpfname;//临时文件名
};
typede f struct _iobuf FILE;
6.2.8文件操作步骤
C语言程序在进行文件操作时遵循如下操作步骤:打开读写操作关闭,通俗地说,打开是
打开文件
函数原型:FILE *fopen(char *name,char *mode)
功能:按指定方式打开文件
返值:正常打开,为指向文件结构体的指针;打开失败,为NULL
关闭文件
作用:使文件指针变量与文件“脱钩”,释放文件结构体和文件指针
函数原型:int fclose(FILE *fp)
功能:关闭fp指向的文件
返值:正常关闭为0;出错时,非0
不关闭文件可能丢失数据
6.2.10字符读写函数fgetc和fputc
fgetc(fp) fp为文件句柄,函数值为得到的字符。
fputc(ch,fp) ch为字符变量,fp为句柄。
成功函数返回相应字符;失败返回EOF。
按照文本的方式读取字符,以及写入字符。
feof
函数原型:int feof(FILE *fp)
功能:判断文件是否结束
返值:文件结束,返回真(非0);文件未结束,返回0
函数原型:
char *fgets(char *s,int n,FILE *fp)
int fputs(char *s,FILE *fp)
(fputs把s指向的字符串写入fp指向的文件)
从fp指向的文件读/写一个字符串
返值:
●fgets正常时返回读取字符串的首地址;出错或文件尾,返
回NULL
fputs正常时返回写入的最后一个字符;出错f
gets从fp所指文件读n-1个字符送入s指向的内存区,
并在最后加一个‘\0’
(若读入n-1个字符前遇换行符或文件尾(EOF)即结束)
fputs把s指向的字符串写入fp指向的文件
6.2.11数据块I/O:fread与fwrite
数据块I/O:fread与fwrite
函数原型:
size_t fread(void *buffer,size_t size, size_t count,FILE *fp)
size_t fwrite(void *buffer,size_t size, size_t count,FILE *fp)
功能:读/写数据块
返值:成功,返回读/写的块数;出错或文件尾,返回0
说明:
typedef unsigned size_t;
buffer: 指向要输入/输出数据块的首地址的指针
size: 每个要读/写的数据块的大小(字节数)
count: 要读/写的数据块的个数
fp: 要读/写的文件指针
fread与fwrite 一般用于二进制文件的输入/输出
6.2.12格式化I/O:fprintf与fscanf
格式化I/O:fprintf与fscanf
函数原型:
int fprintf(FILE *fp,const char *format*,argument,…+)
int fscanf(FILE *fp,const char *format*,address,…+)
功能:按格式对文件进行I/O操作
返值:成功,返回I/O的个数;出错或文件尾,返回EOF 例fprintf(fp,“%d,%6.2f”,i,t); //将i和t按%d,%6.2f格式输出到fp文件
fscanf(fp,“%d,%f”,&i,&t); //若文件中有3,4.5 ,则将3送入i, 4.5送入t
6.2.13如何检测错误
出错的检测
ferror函数
函数原型:int ferror(FILE *fp)
功能:测试文件是否出现错误
返值:未出错,0;出错,非0
说明
每次调用文件输入输出函数,均产生一个新的ferror函数值,所以应及时测试
fopen打开文件时,ferror函数初值自动置为0
6.2.14文件定位
前面介绍的文件读写是针对顺序读写的情况,实际上,文件的读写方式有两种,一是顺序读写,位置指针按字节顺序从头到尾移动,另一种是随机读写,位置指针按需要移动到任意位置,随机形式多用于二进制文件的读写。
如果要对文件进行随机读写,就需要控制文件位置指针的值,这就是文件定位,与文件定位有关的函数是rewind函数,fseek函数和ftell函数
文件定位移动到开头rewind
⏹rewind函数没有返回值,其调用形式为;
⏹rewind(FILE* fp);
⏹该函数使得文件位置指针返回文件开头。
得到当前位置—ftell
⏹随机形式允许文件位置指针跳来跳去,为得到文件指针的当前位置,C语言标准库
提供了ftell函数,其原型为:
⏹long ftell(FILE *);
⏹执行成功时,返回当前文件指针到文件头有多少个字节,否则,返回-1。
移动指针—fseek
起始点并不是任意设定的,C语言给出了3中起始点方式,如所示:
和第一种形式比较,不难发现A[i][j]= *(A[i]+j),A[i]是二级指针,其值为&A[i][0]。
(3)*(*(*(A+i)+j)+k)
将第2种形式的A[i]替换成了*(A+i),此处A是三级指针,其值为&A[0]。
此处以3维数组举例,还可进一步推广到更高维的情况。
6.3.3指针数组
指针也可作为数组中的元素,将一个个指针用数组形式组织起来,就构成了指针数组。
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
定义一维指针数组的一般形式为
类型名*数组名[数组长度];
int *p[4];
例1:有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。
然后从小到大显示。
例2:将若干字符串按字母顺序(由小到大)输出。
解题思路:定义一个指针数组,用各字符串对它进行初始化,然后排序,但不是移动字符串,而是改变指针数组的各元素的指向。
指针数组的一个重要应用是作为main函数的形参。
在以往的程序中,main函数的第一行一般写成以下形式:
int main() 或 int main(void)
表示main函数没有参数,调用main函数时不必给出实参。
这是一般程序常采用的形式。
6.3.4指向指针数据的指针
在了解了指针数组的基础上,需要了解指向指针数据的指针变量,简称为指向指针的指针。
char *name*+=,“Follow”,“Great”,“FORTRAN”,“Computer”-;
char **p; int i;
for(i=0;i<5;i++)
{
p=name+i;
printf("%s\n",*p);
}
6.3.5命令行
实际上,在某些情况下,main函数可以有参数,例如:
int main(int argc,char *argv[])
其中,argc和argv就是main函数的形参,它们是程序的“命令行参数”。
argv是*char指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串。
通常main函数和其他函数组成一个文件模块,有一个文件名。
对这个文件进行编译和连接,得到可执行文件(后缀为.exe)。
用户执行这个可执行文件,操作系统就调用main函数,然后由main函数调用其他函数,从而完成程序的功能。
#include <stdio.h>
int main(int argc,char *argv[])
{ while(argc>1)
{ ++argv;
printf(“%s\n”, *argv);
--argc;
}
return 0;
}
6.3.6指向数组结构的指针
普通数组名可以看成是“指向数组元素首地址的常指针”,结构体数组名同样可以看成是指向结构体数组元素首地址的常指针,也可以声明一个结构指针变量,使其指向数组元素首地址,这两种方式都能实现通过指针访问数组元素,我们来亲自动手实践一下。
6.3.7函数指针内存原理
函数被载入内存,函数必然有一个地址是函数的入口,我们用这个地址来调用,函数名也是
指向函数入口点的指针,我们可以通过函数名找到函数的执行入口。
同时C语言的编译器(无论VC或者GCC)都有这样的规则。
针对函数void run(),函数名run 解析为函数的地址,run,&run,*run都解析为run的入口地址,即为&run函数的首地址。
而且函数名不可以用sizeof操作符。
6.3.8函数指针数组
先复习下指针数组的概念,当数组元素都是同种类型的指针时,该数组称为指针数组,如“int* A*3+;”即声明了一个指针数组A,大小为3,其中每个元素都是int型指针。
如果数组元素都是指向同型函数(返回值类型相同,参数类型相同)的指针,该数组称为函数指针数组,来看一个例子:
double (*f[5])( );
f是一个数组,有5个元素,元素都是函数指针,指向的函数类型是没有参数且返回double 类型的函数。
函数指针数组的使用方式和普通数组完全一致,我们来亲自演练一下。
double (*f[5])( );
已经知道,数组名可作为指向数组首元素起始地址的常指针,那函数指针数组的数组名是什么呢?类推得出,函数指针数组名,对应上面语句中的f,是指向函数指针的常指针,下述代码声明了一个指向函数指针的指针变量p,并用f为其初始化:
double (**p)( )=f;
6.3.9对比define与typedef
C语言中,内置类型,如int型,其指针(int*)可以看成种新的类型,那有没有函数指针类型呢?借助前面介绍的typedef,能容易地将函数指针类型化。
在结构体和共用体一节中介绍了typedef和#define的基本用法,对比了以下两个语句:typedef double* DP;
DP pDouble1, pDouble2;
与
#define DP double*
DP pDouble1, pDouble2;
不知大家是否还记得两者的不同,通俗地说,#define是种字面替换,而typedef却是引入一个新的助记符号,这么说稍显枯燥,下面给出一个简单的理解方式:试着将上面语句中的typedef和#define去掉试试看。
对typedef语句“typedef double* DP;”来说,去掉typedef后,其仍然是条完整的C语句“double* DP;”,该语句用以声明一个double类型的指针变量DP。
由此可以理解:typedef 的作用是将变量名作为(或说定义为)该变量所属类型的别名(或说助记符)。
#define不具备这种特点,去掉#define后,“DP double*”并不是一条合法的C语句。
如何用define与typedef定义函数指针?
6.3.10
6.4函数进阶
本节从更深层次帮助大家理解函数。
主要是函数的参数的传递两种形式,传值与传地址。
函数的输入-参数,函数的输出-返回值不仅可以是int,double等等也可以是数组,结构体等等。
6.4.1参数传递的副本机制
如果将函数比作剧本,那形参和实参的关系相当于角色和演员的关系,函数的参数传递有传值和传地址两种方式。
传值调用时,在函数内对形参的改变都不会影响实参,要想在函数内对实参进行操作,必须采用传地址调用的方式。
这是形象化的理解,从本质上说,这是由参数传递的副本机制决定的。
所谓副本机制,是指copy(拷贝)的思想,不论是传值调用还是传址调用,编译器都要为每个参数制作临时副本,或称拷贝,函数体中对参数的修改都是对副本的修改,下面具体分析之。
6.4.2传址调用的副本机制
相比传值调用,传址调用似乎要复杂一点,但只要知道,传址调用也是通过副本机制,便能很好地理解传址调用的机理.
6.4.3return 局部变量为什么合法
函数返回的副本机制很好地解释了为什么return一个局部变量是合法的,来看一段简单的求和函数代码:
int sum(int a,int b) /*函数定义*/
{
int c=a+b; /*局部变量c*/
return c; /*返回*/
}
……
int d=sum(1,2); /*函数调用*/
来看语句“int d=sum(1,2);”,该语句先执行函数sum,sum函数执行完毕后将结果赋值给int 型变量d,如果从字面上理解,是将c赋值给d,但实际上,在执行赋值操作时,由于函数sum已经执行完毕返回,函数中的局部变量c已被撤销,不存在了。
实际上,在c被撤销前,函数已经为返回值c创建了副本,保存在特定的位置上,赋值操作是由该位置处的副本完成的,形象的示意如所示。
6.4.4函数返回值的副本机制
6.5
6.6。