指针入门到精通(一)
指针是什么
如果在程序中定义了一个变量,在对程序进行编译时,系统就会给该变量分配内存单元,编译系统根据程序中定义的变量类型,分配一定长度的空间
例如:VC++为整型变量分配了4个字节,对单精度浮点型变量分配4个字节,对字符型变量分配1个字节。
内存区的每一个字节有一个编号,这就是“地址”,它相当于旅馆中的房间号。在地址所标识的内存单元中存放数据,就相当于旅馆房间中居住的旅客一样。由于通过地址能找到所需的变量单元,我们可以,地址指向该变量单元。将地址形象化地称为“指针”。
务必弄清楚存储单元的地址和存储单元的内容这两个概念的区别。
#include<stdio.h>
int main()
{
int i = 5, j = 9, k;
printf("\ni = %d, j = %d, k = %d\n\n", &i, &j, &k);
int *i_pointer = &i;
printf("i = %d\n", *i_pointer);
*i_pointer = 200;
printf("i = %d\n",i);
//int *i_pointer = &i; //将i变量的地址赋给指针变量i_pointer
//printf("\ni_pointer = %d, i_pointer = %d, i_pointer = %d\n\n", &i_pointer, *i_pointer, i_pointer);
return 0;
}
为了表示将数值3送到变量中,可以有两种表达方法:
1. 将3直接送到变量i所标识的单元中,例如:i = 3;
2. 将3送到变量i_pointer所指向的单元(即变量i的存储单元),例如:*i_pointer = 3;其中\*i_pointer表示i_pointer指向的对象。
指向就是通过地址来体现的:
假设i_pointer中的值是变量i的地址(2000)这样就在i_pointer和变量i之间建立起一种联系,即通过i_pointer能知道i的地址,从而找到变量i的内存单元。
由于通过地址能找到所需的变量单元,因此说,地址指向该变量单元。将地址形象化地称为“指针”。意思是通过它能找到以它为地址的内存单元。
一个变量的地址称为该变量的"指针"
例如:地址2000是变量i的指针。
如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”。i_pointer就是一个指针变量。指针变量就是地址变量,用来存放地址的变量,指针变量的值是地址(即指针)。
“指针”和"指针变量"是不同的概念
可以说变量i的指针是2000,而不能说i的指针变量是2000。指针是一个地址,而指针变量是存放地址的变量。
#include<stdio.h>
int main()
{
int i = 5, j = 9, k;
printf("\ni = %d, j = %d, k = %d\n\n", &i, &j, &k);
int *pi = &i;
printf("pi的值:%d\n\n", pi);
}
指针变量
-
指针变量定义
定义指针变量的一般形式为:
类型 *指针变量名
如:int *pointer_1, *pointer_2;
int是为指针变量指定的“基类型”
基类型指定指针变量可指向的变量类型
如pointer_1可以指向整型变量,但不能指向浮点型变量
下面都是合法的定义和初始化:
float *pointer_3;
char *pointer_4;
int a,b;
int *pointer_1 = &a, *pointer_2=&b;
在引用指针变量时,可能有三种情况
-
给指针变量赋值。如:p = &a;
-
引用指针变量指向的变量。如有
p = &a; *p = 1;
则执行printf("%d",*p);将输出1
-
引用指针变量的值。如:printf("%o",p); %o:以八进制输出a的地址
要熟练掌握两个有关的运算符
-
& 取地址运算符
&a是变量a的地址
-
*指针运算符(“简介访问”运算符)
如果:p指向变量a,则*p就代表a。
k = *p; (把a的值赋给k)
*p = 1;(把1赋给a)
-
-
指针变量作为函数参数
#include<stdio.h> void swap(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; } int main() { int a, b; int *pa, *pb; printf("\n请输入两个整数(a,b):"); scanf_s("%d,%d", &a, &b); pa = &a; pb = &b; if(a < b) swap(pa, pb); printf("\nmax = %d, min = %d\n\n", a, b); return 0; }
指针引用数组
-
数组元素的指针
一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址。指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓数组元素的指针就是数组元素的地址。
int a[10] = {1,2,3,4,5,6,7,8,9,10};
int *p;p = &a[0];
int *p;p = a;
int *p = a;
int *p = &a[0];
注意:数组名a不代表整个数组,只代表数组首元素的地址。“p = a;"的作用是”把a数组的首元素的地址赋给指针变量p",而不是“把数组a各元素的值赋给p”。
#include<stdio.h> int main() { int a[10] = {1,3,5,7,9,11,13,15,17,19}; int *pa = a;//将数组首地址给变量pa for(int i = 0; i < 10; i++) printf("%4d",*(pa + i)); printf("\n"); return 0; }
-
在引用数组元素时指针的运算
在指针指向数组元素时,允许如下运算:
加一个整数(用+或+=),如p + 1
减一个整数(用-或-=),如p - 1
自加运算,如p++,++p
自减运算,如p--,--p
两个指针相见,比如p1 - p2(只有p1和p2都指向同一个数组中的元素时才有意义)。
-
通过指针引用数组元素
引用一个数组元素,可用下面两种方法:
下标法,如a[i]形式
指针法,如*(a + i)或*(p +i)
其中a是数组名,p是指向数组元素的指针变量,其初值p = a
-
用数组名作为函数参数
用数组名做函数参数时,因为实参数组名代表该数组首元素的地址,形参应该是一个指针变量。C编译都是将行参数组名作为指针变量来处理的。
-
通过指针引用多维数组
多维数组元素的地址
行指针:a代表第0行首地址,a+1代表第1行首地址,a+2代表第2行首地址。
列指针:a[0]代表第0行第0列首地址,a[0]+1代表第0行第1列首地址,a[0]+2代表第0行第2列首地址。
-
指针引用字符串
字符串是存放在字符数组中的。引用一个字符串,可以用一下两种方法。
- 用字符数组存放一个字符串,可以通过数组名和格式声明“%s"输出该字符串,也可以通过数组名和下标引用字符串中一个字符。
- 用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
#include<stdio.h> int main() { char str[50] = "I Love China!"; printf("str=%s\n",str); char %pstr = "I Love China!"; printf("str=%s\n",str); return 0; }
指针函数与函数指针
-
指针函数
是指带指针的函数,即本质是一个函数,函数返回类型是某一类型的指针。
类型标识符 *函数名(参数表)
int *f(x,y);
首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。
#include<stdio.h> int *GetDateFunc(int week, int day) { static int cale[5][7]= { {1,2,3,4,5,6,7}, {8,9,10,11,12,13,14}, {15,16,17,18,19,20,21}; {22,23,24,25,26,27,28}; {29,29,30,31,-1} }; return &cale[week - 1][day - 1] } int main() { int wk, dy; do { printf("请输入week(1-5),day(1-7):"); scanf_S("%d, %d",&wk, &dy) }while(wk < 1 || wk > 5 || dy < 1 || dy > 7); printf("%d\n\n",*GetDateFunc(wk,dy)); return 0; }
-
函数指针
函数指针是指函数的指针变量,即本质是一个指针变量。
int (*f)(int x);//声明一个函数指针
f = func;//将func函数的首地址赋给指针f
指向函数的指针包含了函数的地址的入口地址,可以用过它开调节函数。声明格式如下:
类型说明符(*函数名)(参数)
其实这里不能称为函数名,应该叫做指针的变量名。这个特殊的指针指向一个返回整型值的函数。指针的声明和它指向的函数保持一致。
指针名和指针运算符外面的括号改变了默认的元素符的优先级。如果没有圆括号,就变成了一个返回整型指针的函数的原型声明。
#include<stdio.h> void (*Pfunc)(); void a() { printf("调用函数a().\n\n"); } void b() { printf("调用函数b().\n\n"); } int main() { Pfunc = a; (*Pfunc)(); Pfunc = b; (*Pfunc)(); return 0; }
指针入门到精通(二)
指针数组与数组指针
-
指针数组:
可以说成是“指针的数组”,首先这个变量是一个数组,其次,“指针”修饰这个数组,意思就是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
例如:int *P1[5];
int * int * int* int* int* #include<stdi.h> int main() { char *PointerArr[5] = {"你","是","最","棒","的"}; for(int i = 0; i < 5; i++) printf("%s",PointerArr[i]); printf("\n"); return 0; }
-
数组指针
数组指针:数组指针可以说成是“数组的指针",首先这个变量是一个指针,其次,”数组“修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
例如:int (*p2)[5];
P2 ——> int int int int int 数组的首地址 #include<stdi.h> int main() { char *Arr[][4] = {"你","是","最","棒","的","加","油"}; char (*pi)[4] = Arr; for(int i = 0; i < 5; i++) printf("%s",*(pi + i)); printf("\n"); return 0; }
动态内存分类详解
非静态的局部变量是分配在内存中的动态存储区的,这个存储区是一个称为栈的区域,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆区。
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc、calloc、free、realloc这4个函数。
-
malloc函数
其函数原型为:
void *malloc(unsigned int size);
其作用是在内存的动态存储区中分配一个长度为size的连续空间。
函数的值是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
malloc(100);
开辟100字节的临时分配域,函数值为其第1个字节的地址
注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址,如果次函数未能成功的执行(例如内存空间不足),则返回空指针(NULL)。
-
calloc函数
其函数原型为:
void *calloc(unsigned n, unsigned size);
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL。
例如:
p = calloc(50,4);
开辟50 x 4个字节的临时分配域,把起始地址赋给指针变量p
-
free函数
其函数原型为:
void free(void *p);
其作用是释放指针变量p做指向的动态空间,使这部分空间能重新被其他变量所使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
free(p);
释放指针变量p所指向的已分配的动态空间,free函数无返回值。
-
realloc函数
其函数原型为
void *realloc(void *p, unsigned int size);
如果已经通过malloc函数或者calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重新分配。
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。
例如:
realloc(p, 50);
将p所指向的已分配的动态空间改为50字节。
-
以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include <stdlib.h>指令把stdlib.h头文件包含到程序文件中。
/* 编程实现:开辟动态自由存储区域,存入5个学生的成绩 */ #include<stdio.h> #include<stdlib.h> void CheckScore(int *ps) { printf("成绩如下:\n"); for(int i = 0; i < 5; i++) if(ps[i] < 60) printf("%5d\n",ps[i]); printf("\n"); } int main() { int *ps; ps = (int *)malloc(5 * zieof(int)); printf("\n请输入学生的成绩:\n"); for(int i = 0; i < 5; i++) scanf_s("%d",ps + i); printf("\n学生的成绩为:\n"); for(int i = 0; i < 5; i++) printf("%4d",*(ps + i); printf("\n"); CheckScore(ps); return 0; }
结构体类型
定义和使用结构体变量
-
自己建立结构体类型
用户自己建立有不同类型的数据组合型的数据结构,它称为结构体。
例如:一个学生的学号、姓名、性别、年龄、成绩、家庭地址等项,是属于同一个学生的,因此组成一个组合数据,如student_1的变量,反映它们之间的内在联系。
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};
- 由程序设计者指定一个结构体类型struct Student
- 它包括num,name,sex,age,score,addr等不同类型的成员
声明一个结构体类型的一般形式为:
struct 结构体名
{成员列表};
#include<Stdio.h> struct date { int year; int month; int day; }; struct student { int inum; char cname[10]; struct date birday;//使用结构体类型定义结构变量(结构体变量作为结构体的成员) double score; }st3,st4; struct student st1, st2; int main() { return 0; }
说明:
-
结构体类型并非只有一种,而是可以设计出许多中结构体类型,例如:
stuct Teacher
struct Worker
struct Data等结构体类型
各自包含不同的成员。
-
成员可以属于另一个结构体类型。
struct Date
{
int month; int day; int year;
};
struct Stu
{
int num; char name[20];
char sex; int age;
struct Data birthday;
char addr[30];
};
前面只是建立了一个结构体类型,它相当于一个模型,并没有定义变量,其中并无具体数据,系统对之也不分配存储单元。
相当于设计好了图纸,但并未建成具体的房屋。为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。
-
定义结构体类型变量
-
先声明结构体类型,再定义该类型变量
声明结构体类型struct Student,可以用它来定义变量
struct Student student1, student2; Student:结构体类型名 student1,student2 结构体变量名
Student1 10001 Zhang Xin M 19 90.5 shanghai Student2 10002 Wang Li F 20 98 beijing -
在声明类型的同时定义变量
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}student1, student2;
-
不指定类型名而直接定义结构体类型变量
其一般形式为:
struct
{成员表列}变量名表列;
指定了一个无名的结构体类型。
-
-
结构体变量的初始化和引用
把一个学生的信息(包括学号、姓名、性别、住址)放在一个结构体变量中,然后输出这个学生的信息。
解题思路:
- 自己建立一个结构体类型,包括有关学生信息的各成员;
- 用它定义结构体变量,同时赋一初值;
- 输出该结构体变量的成员。
#include<Stdio.h> struct date { int year; int month; int day; }; struct student { int inum; char cname[10]; struct date birday;//使用结构体类型定义结构变量(结构体变量作为结构体的成员) double score; }st3 = {103, "王五", {2020, 6, 29}, 88},st4 = {104, "马六", {2020, 8, 29}, 99}; struct student st1 = {101, "张三", {2020, 12, 29}, 98}, st2 = {102, "李四", {2020, 3, 29}, 78}; int main() { printf("No.1:%d %s %d-%d-%d %lf\n",st1.inum, st1.cname, st1.birday, st1.score); printf("No.2:%d %s %d-%d-%d %lf\n",st2.inum, st2.cname, st2.birday, st2.score); printf("No.3:%d %s %d-%d-%d %lf\n",st3.inum, st3.cname, st3.birday, st3.score); printf("No.4:%d %s %d-%d-%d %lf\n",st4.inum, st4.cname, st4.birday, st4.score); return 0; }
使用结构体数组
-
定义结构体数组
说明:
-
定义结构体数组一般形式是:
-
struct 结构体名
{成员列表}数组名[数组长度];
-
先声明一个结构体类型,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:struct Person leader[3];
-
-
对结构体数组初始化的形式是在定义数组的后面加上:
= {初值列表};
如:sturct Person leader[3] = {"li",0,"zhang",0"Fun",0};
/* 编程题:有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的姓名,最后输出个人得票结果。 解题思路: 设计一个结构体数组,数组中包含3个元素; 每个元素中的信息应包括候选人的姓名(字符型)和得票数(整型); 输入被选人的姓名,然后与数组元素中“姓名”成员比较,如果相同,就给这个元素中的“得票数”成员的值加1; 输出所有元素的信息。 */ #include<stdio.h> #include<string.h> struct Persons { char name[20]; int iCount; }leader[3] = {{"zhangsan", 0}, {"lisi", 0}, {"wangwu", 0}}; int main() { char lname[20],//投票人输入姓名存放在lname for(int i = 1; i < 10; i++) { printf("请输入候选人的名字:"); scanf_s("%s", lname, 20); for(int j = 0; j < 3; j++) { if(strcmp(lname, leader[j].name) == 0) leader[j].iCount++; } } for(int i = 0; i < 3; i++) { printf("候选人%s:%d票\n",leader[j].name,leader[j].iCount); } return 0; }
-
结构体指针
-
指向结构体变量的指针
指向结构体对象的指针变量既可以指向结构体变量,也可以用来指向结构体数组中的元素。
指针变量的基类型必须与结构体变量的类型相同。例如:struct Student *pt;
说明:
为了使用方便和直观,C语言允许把(*p).num用p->num来代替
(*p).name 等价于 p->name
如果p指向一个结构体变量stu,以下等价:
-
stu.成员名(如stu.num)
-
(*p).成员名(如(*p).num)
p->成员名(如p->num)
-
-
指向结构体数组的指针
/* 有3个学生的信息,放在结构体数组中,要求输出全部学生的信息。 解题思路:用指向结构体变量的指针处理 (1)声明struct Student,并定义结构体数组、初始化 (2)定义指向struct Student了类型指针p (3)使p指向数组首元素,输出元素中各信息 (4)使p指向下一个元素,输出元素中各信息 (5)再使p指向结构体数组的下一个元素,输出它指向的元素中的有关信息 */ #include<Stdio.h> struct date { int year; int month; int day; }; struct student { int inum; char cname[10]; struct date birday;//使用结构体类型定义结构变量(结构体变量作为结构体的成员) double score; }str[4] = { {103, "王五", {2020, 6, 29}, 88}, {104, "马六", {2020, 8, 29}, 99}, {101, "张三", {2020, 12, 29}, 98}, {102, "李四", {2020, 3, 29}, 78}}; int main() { str student *ps; printf("\n结构体数组输出学生信息\n") for(int i = 0; i < 4; i++) printf("No.%d: %d %s %d-%d-%d %.1lf\n", i + 1, st[i].inum, st[i].cname, st[i].birday, st[i].score); printf("\n结构体指针输出学生信息\n") for(int i = 0; i < 4; i++) printf("No.%d: %d %s %d-%d-%d %.1lf\n", i + 1, (*(ps+i)).inum, (*(ps+i)).cname, (*(ps+i)).birday, (*(ps+i)).score); printf("\n结构体指针输出学生信息\n") for(int i = 0; i < 4; i++) printf("No.%d: %d %s %d-%d-%d %.1lf\n", i + 1, (ps+i)->inum, (ps+i)->cname, (ps+i)->birday, (ps+i)->score); return 0; }
-
用结构体变量和结构体变量的指针作函数的参数
将一个结构体变量的值传递个另一个函数,有3个方法:
-
用结构体变量的成员做参数。
例如:用stu[1].num或stu[2].name作函数实参,将实参值传给形参
用法和用普通变量作实参一样,属于“值传递”方式。应当注意实参与形参的类型保持一致
-
用结构体变量作实参。
用结构体变量作实参时,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。
在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,在被调用函数期间改变形参(也是结构体变量)的值,不能返回主调函数。一般较少用这种方法。
-
用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。
-
常用字符串函数
-
puts函数
puts()函数用来向标准输出设备(屏幕)输出字符串并换行,具体为:把字符串输出到标准输出设备,将'\0'转换为回车换行。其调用方式为,puts(s);其中s为字符串字符(字符串数组名或字符串指针)
功能:将字符串输出到终端,puts函数一次只能输出一个字符串,字符串可以包括转义字符。
用法:int puts(const char *string);
-
gets函数
gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。
功能:从stdin流中读取字符串,直至接受到换行符或EOF时停止,并将读取的结果存放在buffer指针所指向的字符数组中。换行符不作为读取串的内容,读取的换行符被转换为'\0'空字符,并由此来结束字符串。
原型:char *gets(char *str);
-
strcat函数
原型:extern char *strcat(char *dest, const char *src);
用法:#inlcude<string.h>
头文件
在C中,函数原型存在<string.h>头文件中。
在C++中,则存在于<cstring.h>头文件中。
功能:把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的"\0")。要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针。
-
strcpy函数
strcpy是一种C语言的标准库函数,strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*。
原型声明:char *strcpy(char *dest, consr char *src);
头文件:#include<string.h> 和 #include<stdio.h>
功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间。
-
strncpy函数
strncpy函数用于将指定长度的字符串复制到字符数组中,是C语言的库函数之一,来自C语言标准库,定义于string.h。
语法形式为:char *strncpy(char *dest, const char *str, int n);
表示把str所指向的字符串以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。
语法:char *strncpy(char *destinin, char *source, int maxlen);
参数:
destinin:表示复制的目标字符数组;
source:表示复制的源字符数组;
maxlen:表示复制的字符串长度。
-
strlen函数
strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值(长度不包含'\0')。
函数原型:extern unsigned int strlen(char *s);
头文件:string.h或cstring
格式:strlen(字符指针表达式)
功能:计算给定字符串(unsigned int型)长度,不包括'\0'在内
说明:返回s的长度,不包括结束符NULL
-
strupr函数
strupr,函数的一种,将字符串s转换为大写形式。
原型:extern char *strupr(char *s);
用法:#include<string.h>
功能:将字符串s转换为大写形式(说明:只转换s中出现的小写字母,不改变其他字符。返回指向s的指针)
-
strcmp函数
strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。基本形式为strcmp(str1,str2),若str1 = str2,则返回零;若str1 < str2,则返回负数;若str1 > str2,则返回正数。
语法:extern int strcmp(const char *s1, const char *s2);
规则:
当s1 < s2时,返回为负数;
当s1 = s2时,返回值为0;
当s1 > s2时,返回为正数。
-
strlwr函数
strlwr函数的功能是将字符串中的S参数转换为小写形式。
原型:extern char *strlwr(char *s);
用法:#include<sttring.h>
功能:将字符串s参数转换小写形式
说明:只转换s参数中出现的大写字母,不改变其他字符。返回指针s参数据的指针。
枚举-共用体-位运算
枚举
如果一个变量只有几种可能的值,则可以定义为枚举类型。
所谓“枚举”就是指把可能的值一一列举出来,变量的值只限于列举出来的值的范围内。
声明枚举类型用enum开头。
例如:
enum Weekday{sun, mon, tue, wed, thu, fri, sat};
声明了一个枚举类型enum Weekday
然后可以用此类型来定义变量
enum Weekday workday,weekend;
workday = mon;正确
weekend = sun;正确
weekday = monday;错误
说明:
-
C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。例如:sun = 0; mon = 1; 错误
-
每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5......
在上面定义中,sun的值为0,mon的值为1,。。。sat的值为6。
如果有赋值语句:workday = mon;
相当于workday = 1;
enum Weekday{sun = 7, mon = 1,tue, wed, thu, fri, sat}workday, week_end;
指定枚举常量sun的值为7,mon为1,以后顺序加1,sat为6.
-
枚举元素可以用来作判断比较。例如:
if(workday == mon)...
if(workday > sun)...
枚举元素的比较规则是按其初始化时指定的整数来进行比较的。
如果定义时未指定,则按上面的默认规则处理,即第一个枚举元素的值为0,故mon > sun, sat > fri。
#include<Stdio.h> int main() { enum week {Mon=1, Tues, Wed, Thurs, Fri, Sat, Sun} day; printf("\n请输入对应的星期(1-7):"); scanf_s("%d",&day); switch(day) { case Mon: printf("\nMonday.\n\n"); break; case Tues: printf("\nTuesday.\n\n"); break; case Wed: printf("\nWednesday.\n\n"); break; case Thurs: printf("\nThursday.\n\n"); break; case Fri: printf("\nFriday.\n\n"); break; case Sat: printf("\nSaturday.\n\n"); break; case Sun: printf("\nSunday.\n\n"); break; dafault: Printf("\n你输入的数字错误,不在指定的范围内\n"); } reutrn 0; }
共用体
有时想用同一段内存单元存放不同类型的变量。使几个不同的变量共享同一段内存的结构,称为“共用体”类型的结构。
定义共用体类型变量的一般形式为:
union 共用体
{成员列表
}变量列表;
例如:
union Data
{int i;
char ch;
float f;
}a,b,c;
"共用体"与“结构体”的定义形式相似,但他们的含义是不同的。
结构体变量所占内存长度是各成员占的内存长度之和,每个成员分别占有其自己的内存单元。而共用体变量所占的内存长度等于最长成员的长度。
【引用共用体变量的方式】
只有先定义了共用体变量才能引用它,但应注意,不能引用共用体变量,而只能引用共用体变量的成员。
例如:前面定义了a,b,c为共用体变量,下面的引用方式是正确的:
a.i a.ch a.f
【共用体类型数据的特点】
在使用共用体类型数据时要注意以下一些特点:
- 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬间时只能存放其中一个成员,而不是同时存放几个。
- 可以对共用体变量初始化,但初始化变种只能有一个常量。
- 共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就取代。
- 共用体变量的地址和它的个成员的地址都是同一地址。
- 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
- 以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。C99允许用共用体变量作函数参数
- 共用体类型可以出现结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现共用体类型定义中,数组也可以作为共用体的成员。
#include<stdio.h>
struct sdata
{
int i;
char c;
double d;
};
union udata
{
int i;
char c;
double d;
};
int main()
{
stract sdata s;
union udata u;
printf("s = %d\n\n",sizeof(s));
printf("u = %d\n\n",sizeof(u));
u.i = 10;
printf("%d,%c,%.2lf\n",u.i,u.c,u.d);
u.c = 'A';
printf("%d,%c,%.2lf\n",u.i,u.c,u.d);
u.d = 67.33;
printf("%d,%c,%.2lf\n",u.i,u.c,u.d);
printf("i = %d\n",&i);
printf("c = %d\n",&c);
printf("d = %d\n",&d);
return 0;
}
位运算
C语言程序可以用独立的位或多个组合在一起的位来存储信息。文件访问许可就是一个常见的应用案例。位运算符允许对一个字节或更大的数据单位中的独立的位做处理:可以清除、设定,或者倒置任何位或多个位。也可以将一个整数的位模式(bit pattern)向右或向左移动。
运算符 | 示例 | 对于每个位位置的结果(1 = 设定, 0 = 清除) |
---|---|---|
& | x&y | 如果x和y都为1,则得到1;如果x或y任何一个为0,或都为0,则得到0 |
| | x|y | 如果x或y为1,或都为1,则得到1;如果x和y都为0,则得到0 |
^ | x^y | 如果x或y的值不同,则得到1;如果两个值相同,则得到0 |
~ | ~x | 如果x为0,则得到1,如果x为1,则得到0 |
移位运算符
移位运算符将左操作数的位模式移动数个位置,至于移动几个位置,由右操作数指定。它们如表所示:
预算符 | 表示意义 | 示例演示 | 计算结果 |
---|---|---|---|
<< | 向左移动 | x<<y | x的每个位向左移动y个位 |
>> | 向右移动 | x>>y | x的每个位向右移动y个位 |
typedef与预处理
typedef
-
简单地用一个新的类型名代替原有的类型名
typedef int Integer;
typedef float Real;
int i,j;float a,b;与Interger i,j; Real a,b;等价
-
命名一个简单的类型名代替复杂的类型表示方法
-
命名一个新的类型名代表结构类型
typedef struct
{int month; int day; int year}Date;
Date birthday;
Data *p;
-
命名一个新的类型名代表数组类型
typedef int Num[100];
Num a;
-
命名一个新的类型名代表一个指针类型
typedef char *String;
String p,s[10];
-
命名一个新的类型名代表指向函数的指针类型
typedef int (*Poninter)();
Pointer p1,p2;
归纳起来,声明一个新的类型名方法是
-
先按定义变量的方法写出定义体(int i;)
-
将变量名换成新类型名(将i换成Count)
-
在最前面加typedef
(typedef int Count)
-
用新类型名去定义变量
以定义上述的数组类型为例来说明:
-
先按定义数组变量形式书写:int a[100];
-
将变量名a换成自己命名的类型名:int Num[100];
-
在前面加上typedef,得到typedef int Num[100];
用来定义变量: Num a;
相当于定义了:int a[100];
对字符指针类型,也是:
char *p;
char *String;
typedef char *String;
String p;等价 char *p;
【说明:】
-
以上的方式实际上是为特定的类型指定了一个同义字(synonyms)。例如:
typedef int Num[100]; Num a; Num 是int[100] 的同义词
typedef int (*Pointer)[]; Pointer p1; Pointer是int(*)()的同义词
-
用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。
-
用typedef声明数组类型、指针类型、结构体类型、共用体类型、枚举类型等,使得编程更加方便。
-
typedef与#define表面上有相似之处
-
当不同源文件中用到同一类型数据时,常用typedef声明一些数据类型。可以把所有的typedef名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。
-
使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。
-
预处理
预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语义分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
-
宏定义
C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为宏的标识符称为“宏名“。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去代换,这称为宏替换或宏展开。
无参宏定义
无参宏的宏名后不带参数。其定义的一般形式为:
#define 标识符 字符串
其中,“#”表示这是一条预处理命令(以#开头的均为预处理命令)。“#define"为宏定义命令。”标识符“为符号常量,即宏名。”字符串“可以是常数、表达式、格式串等。
定义常量:
- #define MAX_TIME 1000
带参宏定义
C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
带参宏定义的一般形式为:
#define 宏名(形参表) 字符串
在字符串中含有各个形参。
带参宏调用的一般形式为:
宏名(实参表);
在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
#define INC(x) x+1 //宏定义 y = INC(5);//宏调用
在宏调用时,用实参5去代替形参x,经预处理宏展开后的语句为y = 5 + 1
-
文件包含
#include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include也是C语言预处理命名的一种。
#include的用法有两种,如下所示:
#include<stdHeader.h>
#include“myHeader.h"
使用尖括号<>和双引号“”的区别在于头文件的搜索路径不同:
使用尖括号<>,编译器会在系统路径下查找头文件;
使用双引号“”,编译器首先在当前目录下查找头文件,如果没有找到,再在系统路径下查找。
也就是说,使用双引号比使用尖括号多一个查找路径,它的功能更为强大。
-
条件编译
条件编译是指预处理器根据条件编译指令,有条件地选择源程序代码中的一部分代码作为输出,送给编译器进行编译。主要是为了有选择性地执行相应操作,防止宏替换内容(如文件等)的重复包含。
条件编译指令 说明 #if 如果条件为真,则执行相应操作 #elif 如果前面条件为假,而该条件为真,则执行相应操作 #else 如果前面条件均为假,则执行相应操作 #endif 结束相应的条件编译指令 #ifdef 如果该宏已定义,则执行相应操作 #ifndef 如果该宏没有定义,则执行相应操作 -
#if-#else-#endif
其调用格式为:
#if 条件表达式
程序段1
#else
程序段2
#endif
功能为:如果#if后的条件表达式为真,则程序段1被选中,否则程序段2被选中。
-
#ifnedf-#define-#endif
其调用格式为:
#ifndef 标识符
#define 标识符 替换列表
//...
#endif
功能为:一般用于检测程序中是否已经定义了名字为某标识符的宏,如果没有定义该宏,则定义该宏,并选中从#define开始到#endif之间的程序段;如果已定义,则不再重复定义该符号,且相应程序段不被选中。
-
#if-\elif-#else-#endif
其调用格式为:
#if 条件表达式1
程序段1
#elif 条件表达式2
程序段2
#else
程序段3
#endif
功能为:先判断条件1为真,如果为真,则程序段1被选中编译;如果为假,而条件表达式2的值为真,则程序段2被选中编译;其他情况,程序段3被选中编译。
-
#ifnedf-#endif
其调用格式为:
#ifndef 标识符
程序段
#endif
功能为:如果检测到已定义该标识符,则选择执行相应程序段被选中编译;否则,该程序段会被忽略。
-
文件操作知识(一)
文件基础知识
-
什么是文件
文件有不同的类型,在程序设计中,主要用到两种文件:
-
程序文件
包含源程序文件(后缀为.c)、目标文件(后缀为.obj)、可执行文件(后缀为.exe)等。这种文件的内容是程序代码。
-
数据文件
文件的内容不是程序,而是供程序运行时读写的数据,如在程序运行过程中输出到磁盘(或其他外部设备)的数据,或在程序运行过程中供读入的数据。如一批学生的成绩数据,或货物交易的数据等。
本章主要讨论的是数据文件
在以前各章中所处理的数据的输入和输出,从终端的键盘输入数据,运行结果输出到终端显示器上常常需要将一些数据输出到磁盘上保存起来,以后使用,这就要用到磁盘文件。
操作系统把各种设备都统一作为文件处理,从操作系统的角度看,每一个与主机相连的输入输出设备都看作是文件。例如:
终端键盘是输入文件
显示器和打印机是输出文件
“文件”指存储在外部介质上数据的集合
一批数据是以文件的形式存放在外部介质上的,操作系统是以文件为单位对数据进行管理,想找存放在外部介质上的数据,先按文件名找到所指定的文件,然后再从该文件读数据,要向外部介质上存储数据也必须先建立一个文件(以文件名作为标志),才能想它输出数据。
输入输出是数据传送的过程,数据如流水一样从一处流向另一处,因此常将输入输出形象地称为流(stream),即数据流。流表示了信息从源到目的端的流动。
输入操作时,数据从文件流向计算机内存,输出操作时,数据从计算机流向文件,无论是用word打开或保存文件,还是C程序中的输入输出都是通过操作系统进行的,“流”是一个传输通道,数据可以从运行环境流入程序中,或从程序流至运行环境。
从C程序的观点来看,无论程序一次读写一个字符,或一行文字,或一个指定的数据区,作为输入输出的各种文件或设备都是统一以逻辑数据流的方式出现的。C语言把文件看作是一个字符(或字节)的序列。一个输入输出流就是一个字符流或字节(内容为二进制数据)流。
C语言的数据文件有一连串的字符(或字节)组成,而不考虑行的界限,两行数据间不会自动加分隔符,对文件的存取是以字符(字节)为单位的。输入输出数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制,这就增加了处理的灵活性。这种文件称为流式文件。
-
-
文件名
文件要有一个唯一的文件标识,以便用户识别和引用。
文件标识包括三部分:
- 文件路径(表示文件在外部存储设备中的位置)
- 文件名主干(命名规则遵守标识符的命名规则)
- 文件后缀(一般不超过3个字母 doc,txt,dat,c,cpp,obj,exe,ppt,bmp等)
如:D:\\cc\temp\file1.dat
-
文件的分类
根据数据的组织形式,数据文件可分为ASCII文件和二进制文件。
数据在内存中是以二级制形式存储的,如果不加转换地输出到外存,就是二级制文件。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称为文本文件,一个字节放一个字符的ASCII代码。
字符一律以ASCII形式存储
数值型数据既可以用ASCII形式存储,也可以用二进制形式存储。如有整数10000,如果用ASCII码形式输出到磁盘,则在磁盘中占5个字节(每一个字符占一个字节),而用二进制形式存储,则在磁盘上只占4个字节(用VC++时)。
-
文件缓冲区
ANSI C标准采用“缓冲文件系统”处理数据文件,所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。
从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去,如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个将数据送到程序数据区(给程序变量)。
-
文件类型指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”
每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中。该结构体类型是由系统声明的,取名为FILE。
声明FILE结构体类型的信息包含在头文件“stdio.h"中。一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。
打开关闭文件
-
用fopen函数打开数据文件
对文件读写之前应该“打开”该文件,在使用结束之后应“关闭”该文件。所谓“打开”是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
在编写程序时,在打开文件的同时,一般都指定一个指针变量指向该文件,也就是建立起指针变量与文件之间的联系,这样就可以通过该指针变量对文件进行读写。所谓“关闭”是指撤销文件信息区和文件缓冲区。
fopen函数的调用方式为:
fopen(文件名,使用文件方式);
例如:
fopen("a1","r");
表示要打开名为“a1"的文件,使用文件方式为”读入“;
fopen函数的返回值是指向a1文件的指针。
通常将fopen函数的返回值赋给一个指向文件的指针变量。如:
FILE *fp;
fp = fopen("a1","r");
fp和文件a1相联系,fp指向了a1文件。
#include<stdio.h> int main() { FILE *fp; fp = fopen(".\\text1.txt","r"); if(fp == NULL) { printf("\n打开文件失败.\n\n"); } else printf("\n打开文件成功.\n\n"); fclose(fp); reutrn 0; }
【在打开一个文件时,通知编译系统以下3个信息:】
-
需要访问的文件的名字
-
使用文件的方式(“读”还是“写”等)
-
让哪一个指针变量指向被打开的文件
使用文件方式:
读取 写入 添加 文本 "r" "w" "a" 二进制 "rb" "wb" "ab" 说明:
-
用“r"方式打开的文件只能用于向计算机输入而不能用作向该文件输出数据,而且该文件应该已经存在,并存有数据,这样程序才能从文件中读数据。
不能用“r"方式打开一个并不存在的文件,否则出错。
-
用“w"方式打开的文件只能用于向该文件写数据(即输出文件),而不能用来向计算机输入。
如果原来不存在该文件,则在打开文件前新建立一个以指定的名字命名的文件。
如果原来已存在一个以该文件名命名的文件,则在打开文件前先将该文件删去,然后重新建立一个新文件。
-
如果希望向文件末尾添加新的数据(不希望删除原有数据),则应用“a”方式打开,但此时应保证该文件已存在;否则将得到出错信息。打开文件时,文件读写标记移到文件末尾。
-
用r+、w+、a+方式打开的文件既可以用来输入数据,也可以用来输出数据。用r+方式时该文件应该已经存在。用w+方式则新建立一个文件,先向此文件写数据,然后可以读此文件中的数据。用a+方式打开的文件,原来的文件不被删去,文件读写位置标记移到文件末尾,可以添加,也可以读。
-
如果打开失败,fopen函数将会带回一个出错信息。fopen函数将带回一个空指针值NULL。
常用下面的方法打开一个文件:
if((fp = fopen("file1","r")) == NULL)
{
printf("cannot open this file\n");
exit(0);//终止正在执行的程序
}
-
C标准建议用上表列出的文件使用方式打开文本文件或二进制文件,但目前使用的有些C编译系统可能不完全提供所有这些功能。
-
计算机输入从ASCII文件读入字符时,遇到回车换行符,系统把它转换为一个换行符,在输出时把换行符转换成回车和换行两个字符。在用二进制文件时,不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
-
程序中可以使用3个标准的流文件:标准输入流、标准输出流、标准出错输出流。
系统已对这3个文件指定了与终端的对应关系
标准输入流是从终端的输入
标准输出流是向终端的输出
标准出错输出流是当程序出错时将出错信息发送到终端
程序开始运行时系统自动打开这3个标准流文件。因此,程序编写者不需要在程序中用fopen函数打开它们。所以以前我们用到的从终端输入或输出到终端都不需要打开终端文件。
-
-
-
用fclose函数关闭数据文件
关闭文件用fclose函数。fclose函数调用的一般形式为
fclose(文件指针);
例如:fclose(fp);
如果不关闭文件将会丢失数据。
顺序读写数据文件(二)
在顺序写时,先写入的数据存放在文件中的前面,后写入的数据存放在文件中后面,在顺序读时,先读文件中前面的数据,后读文件中后面的数据,对顺序读写来说,对文件读写数据的顺序和数据在文件中的物理顺序是一致的,顺序读写需要用库函数实现。
-
怎样向文件读写字符:
读写一个字符的函数:
函数名 调用形式 功能 返回值 fgetc fgtc(fp) 从fp指向的文件读入一个字符 读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1) fputc fputs(ch,fp) 把字符ch写到文件指针变量fp所指向的文件中 写成功,返回值就是输出的字符,输出失败则返回文件结束标志EOF(即-1) /* 编程实现:要求从键盘输入一串字符,将它们输送到磁盘文件当中,以“#”为终止符。 编程思路:用fgetc函数从键盘读入字符,然后用fputs函数写到磁盘文件当中。 */ #include<stdio.h> int main() { FILE *fp; char ch, FileName[20]; printf("\n请输入所用的文件名:"); scanf("%s", FileName); if((fp = fopen(FileName,"w")) == NULL) { printf("\n无法打开此数据文件.\n\n"); exit(0); } ch = getchar(); printf("\n请输入字符串(以#结束):"); ch = getchar(); while(ch != '#') { fputc(ch, fp);//将ch里边的字符写入到文件指针指向的文件 putchar(ch); ch = getchar(); } fclose(fp); putchar(10); return 0; }
-
怎样向文件读写一个字符串
读写一个字符串的函数:
函数名 调用形式 功能 返回值 fgets fgets(str,n,fp) 从fp指向的文件读入长度为(n-1)的字符串,存放到字符数组str中 读成功,返回地址str,失败则返回NULL fputs fputs(str,fp) str所指向的字符串写到文件指针变量fp所指向的文件中 写成功,返回0;否则返回非0值 //向数据文件写入数据 #include<stdio.h> int main() { FILE *fp; char str[300]; fp = fopen("demoss.txt","r"); if(fp == NULL) printf("\n打开文件失败.\n\n"); else printf("\n打开文件成功.\n\n"); while(!feof(fp)) { if(fgets(str,300,fp) != NULL) printf("\n读取文件数据如下:\n%s\n\n",str); } fclose(fp); reutrn 0; }
#include<stdio.h> #include<stdlib.h> int main() { FILE *fp; char *str = "How are you."; fp = fopen("datax.dat","w"); if(fp == NULL) printf("\n打开或创建文件失败.\n\n"); else printf("\n文件打开或创建成功.\n\n"); fputs(str,fp); fclose(fp); reutrn 0; }
【说明:】
fgets函数的函数原型为:
char *fgets(char *str, int n, FILE *fp);
其作用是从文件读入一个字符串。
调用时可以写成:
fgets(str,n,fp);
【说明:】
fgets(str,n,fp);中n是要求得到的字符个数,单实际上只读n-1个字符,然后在最后加一个'\0'字符,这样得到的字符串共有n个字符,把它们放到字符数组str中。
如果在读完n-1个字符之前遇到换行符'\n'或文件结束符EOF,读入即结束,但将所遇到的换行符“\n”也作一个字符读入。
执行fgets成功,返回str数组首地址,如果一开始就遇到文件尾货读数据错,返回NULL。
【说明:】
fputs函数的函数原型为:
int fputs(char *str, FILE *fp);
str指向的字符串输出到fp所指向的文件中。
调用时可以写成:fputs("China",fp);
fputs函数中第一个参数可以是字符串常量、字符数组名或字符型指针。
字符串末尾的'\0'不输出
输出成功,函数值为0;失败,函数值为EOF。
-
用二进制方式向文件读写一组数据
一般调用方式为:
fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);
buffer:是一个地址
对fread来说,它是用来存放从文件读入的数据的存储区的地址
对fwrite来说,是要把此地址开始的存储区的数据向文件输出
size:要读写的字节数
count:要读写多少个数据项
fp:FILE类型指针
//向数据文件写结构体数据块 #include<stdio.h> #include<stdlib.h> struct student { int no; char name[10] }; int main() { struct student st[2] = { {101,"张三"}, {102,"李四"} }; FILE *fp = fopen(".\\studentdata.txt","w"); if(fp == NULL) { perror("\nOpen File Fail.\n\n"); exit(0); } fwrite(st, sizeof(struct student), 2, fp); fclose(fp); reutrn 0; } //从数据文件当中读取结构体数据块 #include<stdio.h> #include<stdlib.h> struct student { int no; char name[10] }; int main() { struct student st[2]; FILE *fp = fopen(".\\studentdata.txt","w"); if(fp == NULL) { perror("\nOpen File Fail.\n\n"); exit(0); } fread(st, sizeof(struct student), 2, fp); printf("No:%s\tName:%s\n",st[0].no, st[0].name); printf("No:%s\tName:%s\n",st[1].no, st[1].name); fclose(fp); reutrn 0; }
随机读取数据文件(三)
-
随机读写数据
对文件进行顺序读写比较容易理解,也容易操作,但有时效率不高。随机访问不是按数据在文件中的物理位置次序进行读写,而是可以在任何位置上的数据进行访问,显然这种方法比顺序访问效率高得多。
-
文件位置标记及其定位
为了对读写进行控制,系统为每个文件设置了一个文件读写位置标记(简称文件标记),用来指示“接下来要读写的下一个字符的位置”
文件指示——> 文件头 读写当前位置——> 文件为——> 一般情况下,在对字符文件进行顺序读写时,文件标记指向文件开头,进行读的操作时,就读第一个字符,然后文件标记向后移一个位置,在下一次读操作时,就将位置标记指向的第二个字符读入。依次类推,直到遇文件尾,结束。
如果是顺序写文件,则每写完一个数据后,文件标记顺序向后移一个位置,然后在下一次执行写操作时把数据写入指针所指的位置。直到把全部数据写完,此时文件位置标记在最后一个数据之后。
可以根据读写的需要,人为地移动了文件标记的位置。文件标记可以向前移、向后移,移动文件头或文件尾,然后对该位置进行读写——随机读写。
随机读写可以在任何位置写入数据,在任何位置读取数据。
-
文件位置标记的定位
可以强制使文件位置标记指向指定的位置
可以用以下函数实现:
-
用rewind函数使文件标记指向文件开头
rewind函数的作用是使文件标记重新返回文件的开头,此函数没有返回值。
-
用fseek函数改变文件标记
fseek函数的调用形式为:
fseek(文件类型指针,位移量,起始点)
起始点0代表“文件开始位置”,1为“当前位置”,2为”文件末尾位置“
C标准指定的名字
起始点 名字 用数字代表 文件开始位置 SEEK_SET 0 文件当前位置 SEEK_CUR 1 文件末尾位置 SEEK_END 2 位移量指以起始点为基点,向前移动的字节数。位移量应是long型数据(在数字的末尾加一个字母L)。fessk函数一般用于二进制文件。下面是fseek函数调用的几个例子:
fessk(fp, 100L, 0);
fessk(fp, 50L, 1);
fessk(fp, -10L, 2);
-
用ftell函数测定文件位置标记的当前位置
ftell函数的作用是得到流式文件中文件位置标记的当前位置。
由于文件中的文件位置标记经常移动,人们往往不容易知道其当前位置,所以常用ftell函数得到当前位置,用相对于文件开头的位移量来表示。如果调用函数时出错(如不存在fp指向的文件),ftell函数返回值为-1L。例如:
i = ftell(fp);
if(i == -1L)
printf("error\n");
-
-
-
文件读写的出错检测
-
ferror函数
ferror函数的一般调用形式为:
ferror(fp);
如果返回值为0,表示未出错,否则表示出错。
每次调用输入输出函数,都产生新的ferror函数值,因此调用输入输出函数后立即检查调用fopen时,ferror的初始值自动置为0。
-
clearerr函数
作用是使文件错误标志和文件结束标志置为0。
调用一个输入输出函数时出现错误(ferror值为非零值),立即调用clearerr(fp),使ferror(fp)值变0,以便再进行下一次检测。
只要出现文件读写错误标志,它就一直保留,直到对同一文件调用clearerr函数或rewind函数,或任何其他一个输入输出函数。
-
/*
编程实现:
1. 将磁盘文件里边的数据信息显示出来
2. 把这些数据信息赋值到另一个文件中。
*/
#include<stdio.h>
int main()
{
FILE *fpold, *fpnew;
fpold = fopen("old,dat","r");
fpnew = fopen("new.dat","w");
while(!feof(fpold))
putchar(getc(fold));
printf("\n");
rewind(fpold);
while(!feof(fpold))
putc(getc(fpold),fpnew);
fclose(fpold);
fclose(fpnew);
return 0;
}
标签:文件,专题,变量,int,提高,C语言,函数,printf,指针
From: https://www.cnblogs.com/sha-ckle/p/17178093.html