音视频系列--c语言学习(二级指针,函数指针,复杂指针函数,字符串,常量指针,文件操作)

烈酒焚心 提交于 2020-11-16 08:37:12

一、指针间传值详解


1.1、值传递

行参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参 -> 形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

1.2、指针传递

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行操作。

1.3、引用传递

形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调
函数中的实参变量。

// pass by value
void swap(int a,int b){
   
    
int temp = a;
a = b;
b = temp;
}
// pass by address
void swap1(int* a,int *b){
   
    
int temp = *a;
*a = *b;
*b = temp;
}
// pass by reference
void swap2(int& a,int& b){
   
    
int temp = a;
a = b;
b = temp;
}
// pass by value ?
void swap3(int* a,int *b){
   
    
int* temp = a;
a = b;
b = temp;
}

二、宏函数与普通函数


定义:为了提高程序的效率,用define来定义一个函数,这样在频繁调用的时候就不会有函数调用的开销了,这就是宏函数,但宏定义只是在调用处做了代码替换,这就是宏函数

注意事项:

宏函数 需要加小括号 修饰 保证运算完整性

宏函数比普通函数在一定程序上执行效率高,省去函数入栈,出栈时间上的开销。

优点:以时间换空间

三、二级指针(指针的指针)


3.1、二级指针基本概念

先从一个例子开始,看下面的声明

int a = 12;
int *b = &a;

它们如下图进行内存分配:

在这里插入图片描述
假设有了第3个变量,名叫c,并用下面这条语句对它进行初始化

c = &b;

它在内存中的大概模样大致如下:

在这里插入图片描述
问题:c的类型是什么?显然它是一个指针,但它所指向的是什么?变量b是一个“指向整形的指针”,所以任何指向b的类型必须是指向“指向整型的指针”的指针,更通俗地说,是一个指针地指针。

它合法吗?是的!指针变量和其它变量一样,占据内存中某个特定地位置,所以用&操作符取得它地址是合法的。

那么这个变量的声明是怎样的声明的?

int **c = &b;

那么这个**c如何理解呢?*操作符具有从右向左的结合性,所以这个表达式相当于*(*c),我们从里向外逐层求职。*c访问c所指向的位置,我们知道这是变量b.第二个间接访问操作符访问这个位置所指向的地址,也就是变量a.指针的指针并不难懂,只需要留心所有的箭头,如果表达式中出现了间接访问操作符,你就要随箭头访问它所指向的位置。

3.2、二级指针实现方法一

如下图所示,先为二级整形指针ptr分配空间,然后赋值。

int **ptr=NULL;
int num=4, size=4, i,j;
ptr = (int **)malloc(num*sizeof(int*));
for(i=0; i<num; ++i)
{
   
      
	*(ptr+i) = (int *)malloc(size*sizeof(int));
	*(*(ptr+i) +j)=(i+1)*j;
}

在这里插入图片描述

#include <stdio.h>
#include <malloc.h>

int main() {
   
      

    int **ptr = NULL;
    int num = 4, size = 4, i, j;

    ptr = (int **) malloc(num * sizeof(int *));
    for (i = 0; i < num; ++i) {
   
      
        *(ptr + i) = (int *) malloc(size * sizeof(int));
        for (j = 0; j < size; ++j)
            *(*(ptr + i) + j) = (i + 1) * j;
    }
    for (int i = 0; i < num; ++i) {
   
      
        for (int j = 0; j < size; ++j) {
   
      
            printf("(%d, %d) -> %d\t", i, j, *(*(ptr + i) + j));
        }
        printf("\n");
    }

    for (int i = 0; i < num; ++i) {
   
      
        free(*(ptr + i));
    }
	free(ptr);

    return 0;
}

运行结果
在这里插入图片描述

3.3、二级指针实现方法二

如下图所示,先为二级整型指针ptr分配空间,然后赋值。与实现方法一的不同之处,在于使用数组形式就行相关操作。

int **ptr=NULL;
int num=4, size=4, i;
ptr = (int **)malloc(num*sizeof(int*));
for(i=0; i<num; ++i)
{
   
      
	ptr[i]= (int *)malloc(size*sizeof(int));
	ptr[i][j]=(i+1)*j;
}

在这里插入图片描述

#include <stdio.h>
#include <malloc.h>

int main() {
   
      

    int **ptr = NULL;
    int num = 4, size = 4, i, j;

    ptr = (int **) malloc(num * sizeof(int *));
    for (i = 0; i < num; ++i) {
   
      
        ptr[i] = (int *) malloc(size * sizeof(int));
        for (j = 0; j < size; ++j)
            ptr[i][j] = (i + 1) * j;
    }
    for (int i = 0; i < num; ++i) {
   
      
        for (int j = 0; j < size; ++j) {
   
      
            printf("(%d, %d) -> %d\t", i, j, ptr[i][j]);
        }
        printf("\n");
    }

    for (int i = 0; i < num; ++i) {
   
      
        free(ptr[i]);
    }
    free(ptr);

    return 0;
}

运行结果
在这里插入图片描述

四、函数指针


4.1、什么是函数指针

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针

那么这个指针变量怎么定义呢?虽然同样是指向一个地址,但指向函数的指针变量同之前讲的指向变量的指针变量的定义方式是不同的。

例如

int(*p)(int, int);

这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“ * ”,即
( * p)

其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数

后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数

所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int
型,且有两个整型参数的函数。

p 的类型为 int(*)(int,int)。

所以函数指针的定义方式为:

函数返回值类型 (* 指针变量名) (函数参数列表);

“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;
“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。
这个参数列表中只需要写函数的参数类型即可。

可以看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(* 指针变量名)”。

但是这里需要注意的是:“( * 指针变量名)”两端的括号不能省略

括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一
个返回值类型为指针型的函数。

那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?

首先看变量名前面有没有“ * ”,如果有“*”说明是指针变量;

其次看变量名的后面有没有带有形参类型的圆括号,

如果有就是指向函数的指针变量,即函数指针,
如果没有就是指向变量的指针变量。

最后需要注意的是,指向函数的指针变量没有 ++ 和 – 运算。

4.2、如何用函数指针调用函数

举例:

int Func(int x); /*声明一个函数*/
int (*p) (int x); /*定义一个函数指针*/
p = Func; /*将Func函数的首地址赋给指针变量p*/

赋值时函数 Func 不带括号,也不带参数。由于函数名 Func 代表函数的首地址,因此经过赋值以后,指针变量 p 就指向函数 Func() 代码的首地址了。

使用:

#include <stdio.h>

int Max(int x, int y) //定义Max函数
{
   
       
    int z;
    if (x > y) {
   
       
        z = x;
    } else {
   
       
        z = y;
    }
    return z;
}

int main(void) {
   
       

    int (*p)(int, int);  //定义一个函数指针
    int a, b, c;
    p = Max;
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);

    return 0;
}

运行结果:

在这里插入图片描述

#include <stdio.h>

int jia(int a, int b) {
   
       
    return a + b;
}
int jian(int a, int b) {
   
       
    return a- b;
}
void change(int (**p1)(int a,int b)){
   
       
    *p1 = jia;
}
int main() {
   
       
    int (*pn)(int, int) =jia;
    printf("reslut: %d\n ", pn(20,10));
    pn = jian;
    printf("reslut: %d\n ", pn(20,10));


    change(&pn);
    printf("reslut: %d\n ", pn(20,10));
//
//    函数改变变量    变量的地址
//  改变指针变量    指针的地址   二级指针
// pn函数   函数地址
    return 0;
}

五、函数指针数组


5.1、函数指针数组

三个词补充下

数组            int Array[N];
指针的数组       int *Array[N];
函数的指针的数组  int (*(*function_pointer)[N])(int, int);

从低级一点点进化到高级

  • 数组
int a[2];
数组就是这样,没什么好说的了.就是有两个格子,里面存了两个int类型的数字.这两个格子被看成a.大小使用
sizeof(a)来计算,就是两个int的大小.约定一个int大小是4个字节,那么sizeof(a)就是8个字节大小了.
  • 指针的数组(指针数组)
int *p[2];
难度加大了一点,但是我们冷静一下,还是可以理解的.
这还是一个数组,还是两个格子,每个格子里存的是指向int类型的指针.
每个指针的大小也约定是4个字节.那么sizeof(p);也就是8个字节了.
格子p1,格子p2
这两个格子分别指向int类型的数字. `p1->&a;p2->&b;`
指针->这个符号是指针专用的.只有指针才能使用->.
  • 指向数组的指针
int (*p)[2];
难度加大了一点,但是我们冷静一下,还是可以理解的.()的优先级高于[]
可以确定的是 他在内存中是一个单个指针,指针类型 和int *没有区别。而指向类型变成了数组,
这还是一个数组,还是两个格子,每个格子里存的是指向int类型
所以指向数组的指针,也成为数组指针
  • 函数的指针的数组
int (*(*func)[2]) (int, int);
先读标识符,和他相邻的是指针符号,那么func是一个指针,指向的是一个数组,
数组类型是指针类型,指针指向类型是函数
存的是 参数是两个int,返回值是一个int类型的函数.意思是这两个格子里存的是两个函数的地址.
#include <stdio.h>


int jia(int a, int b) {
   
        
    return a + b;
}
int jian(int a, int b) {
   
        
    return a- b;
}
int cheng(int a, int b) {
   
        
    return a *b;
}
int chu(int a, int b) {
   
        
    return a / b;
}

int main() {
   
        
    int *px[10];//指针数组   函数指针数组 指针 非常强大

    int (*pn[4])(int a, int b) ={
   
        jia, jian, cheng, chu };//定义一个函数指针

    int a = 30;
    int b = 20;
    for (int i = 0; i < 4; i++) {
   
        
        printf("result:%d\n", pn[i](a, b));
    }
    return 0;
}

复杂函数练习

#include <stdio.h>
//int[]   数组 常量
//int* getArray(){
   
        
//
//
//}
int main() {
   
        
/**
 * 首先找到那个未定义的标识符,就是func,
 * 它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,
 * 然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,
 * 而func是一个指向这类函数的指针,就是一个函数指针,

 *这类函数具有int*类型的形参,返回值类型是 int。

 */
    int (*func)(int *pInt);
/**
   func被一对括号包含,且左边有一个*号,说明func是一个指针,
   跳出括号,右边也有个括号,那么func是一个指向函数的指针,
   这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。
   再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,
   指向的函数具有int*类型的形参,返回值为int。
 */
//复杂  规则 本质
    int (*func1)(int *p, int (*f)(int *));


/**
 * func右边是一个[]运算符,说明func是一个具有5个元素的数组,
 * func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰 func的,
 * 而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,
 * 因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,
 * 说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,
 * 返回值类型为int。
 */
//    func2  本质 数组1     指针 2

    int (*func2[5])(int *pInt);

//func3   数组1   指针2
/**
 * func被一个圆括号包含,左边又有一个*,
 * 那么func是一个指针,跳出括号,
 * 右边是一个[]运算符号,说明func是一个指向数组的指针,
  现在往左看,左边有一个*号,说明这个数组的元素是指针,


    */
    int (*(*func3)[5])(int *pInt);

//输入  int * 地址变量  输出   int (*result)[5]
    int (*(*func4)(int *pInt))[5];
/**
 * func是一个返回值为具有5个int元素的数组的函数。
 * 但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,
 * 那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,
 * 它不能作为左值来接收另一个数组,
 * 因此函数返回值不能为数组。
 */
//    int func8(void)[5];//非法

/**
 *
 func是一个具有5个元素的数组,这个数组的元素都是函数。
 这也是非法的,因为数组的元素除了类型必须一样外,
 每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,
 即使函数的类型一样,但函数所占用的空间通常是不相同的。
 */
//    int func9[5](void)
    int func10(void);
    return 0;
}

六、typedof定义函数指针

#include <stdio.h>
#include <stdlib.h>

typedef int (*milaoshu)(int a, int b);
int func(int a, int b){
   
        
    return a+b;
}
int main() {
   
        
    int a = 10;
    int b = 20;
    printf("a+b = %d\n", a+b);
    milaoshu p = NULL; //如果不给这个类型起名叫米老鼠,那么在声明变量p的时候应该这样声明 int (*p)
    p = func;
    int c = p(a ,b);
    printf("a+b = %d\n", c);
    return 0;
}

上面声明了一个milaoshu类型的指针变量p,这个p目前是指向空的。除了指向空,它还可以指向milaoshu的类型,那就是p = fun,调用方式p(a, b),就等同于func(a, b);

七、字符串


字符串实际上就是以null字符‘\0’结尾的一维数组。’\0’是系统自动添加作为该字符串结束的标识符。

例:字符串"hello"总共有五个字符,但实际上是占据了六个字节。

C语言中,字符变量是用char定义的,但是C中没有储存字符串的数据类型。所以C中一般通过字符数组和字符指针来存储。

7.1、字符数组输入和输出

scanf: 函数有%c和%s两种类型。

getchar()、putchar()单字符输入输出。有时候多次输入之间需换行的话,有时候需要多加一个getchar()来吸收回车符。

gets()和puts()用来输入和输出一行字符串,以换行符作为输入结束。输出的时候会自动紧跟一个换行符。

注意:字符数组末尾是以’\0’结尾的,scanf的%s和gets会自动在末尾添加’\0’,但getchar则不会,所以要注意手动添加。

void test() {
   
         
    //    数组
    char str[] = {
   
         'h', 'e', 'l', 'l', 'o', '\0'};

    char str1[] = "hello";   //会自动加 \0
    char *str2 = "hello";   //会自动加 \0
//    *str2 = "world";  //不能修改常量池的内容
    printf("str=%s\n", str);
    printf("str=%s\n", str1);
    printf("str=%s\n", str2);
}

void test02(){
   
         

    printf("input str\n");
    while (1) {
   
         
        char str[10];
        //读取数据,空格当作换行符
        scanf("%s", str);//api  
        printf("str=%s\n", str);
    }

    return 0;

}

void test03(){
   
         

    printf("input str\n");
    char str[10];
    //gets 空格当成内容 回车 输出
    gets(str);
    printf("str=%s\n", str);
}

void test04(){
   
         
    
    printf("input str\n");
    while (1) {
   
         
        char str[10];
        //fgets 输入 stdin 输入设备 一次性最大读取10个字节的,超出部分作为下次获取
        fgets(str, 10, stdin);
        printf("str=%s\n", str);
    }
    
}

7.2、操作字符串的函数

函数 作用
1 strcpy(a,b) 复制字符串b至字符串a
2 strcat(a,b) 将字符串b连接至a的尾部
3 strlen(a) 返回字符串a的长度
4 strcmp(a,b) 如果a=b,则返回0如果a<b,则返回小于0如果a>b,则返回大于0
5 strchr(a,‘s’) strrchr(a,‘s’) 返回的是一个指针;strchr是从左至右指向字符’s’在字符串a中第一次出现的位置 strrchr则是从右至左指向字符’s’在字符串a中第一次出现的位置如果‘s’不存在则返回NULL
6 strstr(a,b) 返回一个指针指向字符串b在a中第一次出现的位置

进一步解释:

7.2.1、strlen函数和sizeof的区别

两者虽然都可以计算字符串的长度,但是区别还是相当的大。

1.从用法来说,strlen是专门针对计算字符串的函数,而sizeof作为单目运算符,它的参数可以是数组,指针,函数等等
2.返回长度,strlen作为函数,它遇到’\0’结束返回字符串的长度,但是sizeof则是把结束符’\0’计算在内。

例:

char s1[] = "abcdefghuijklmn";
printf("strlrn函数返回的字符长度是%d\n",strlen(s1));
printf("sizeof返回的字符长度是%d\n",sizeof(s1));
//输出:
//strlrn函数返回的字符长度是15
//sizeof返回的字符长度是16

3.返回类型,strlen函数返回的是size_t 类型(即无符号整型),所以在一些条件判断中使用要格外的小心

例:

if(strlen(a)-strlen(b)>=0)

因为返回的是unsigned int型,所以if条件句里永远是真

7.2.2、strchar函数

上面表格中已经介绍过了,strchr函数返回的是一个指针;

char s2[] = "3asdasdasdas";
char *q = strchr(s2,'d');
printf("%s\n",q);
//输出: dasdasdas

从上面可以看出它是从左至右开始查找目的字符,若查找成功返回的值是从目的字符向右到结束的字符
串。

7.2.3、strrchar函数

它是从右至左开始查找目的字符,返回的同样是从目的字符向右到结束的字符串。

char s2[] = "3asdasdasdas";
char *q = strrchr(s2,'d');
printf("%s\n",q);
//输出:das

7.2.4、strstr函数

strstr函数同样返回的是一个指针,strstr查找的是字符串

例:

char s2[] = "3asdasdasdas";
char *q1 = strstr(s2,"das");
printf("%s\n",q1);
//输出: dasdasdas

7.2.5、gets()/puts() 数组函数

gets可以把空格一块输入,puts输出完的时候自动跟一个换行符。

7.2.6、sscanf&sprintf

void test05(){
   
         
    char str[100] = "203200:132.12,nice!",str2[100],str3[100];
    int n;
    double db;
//sscanf是把字符数组str里面的内容赋值给其他的部分
    sscanf(str,"%d:%lf,%s",&n,&db,&str2);
    printf("%d:%lf,%s\n", n,db,str2);
//sprintf是把其他的内容赋值给字符数组str3
    sprintf(str3,"%d:%lf,%s",n,db,str2);
    printf("%s\n", str3);
}

sscanf()
sprintf()

八、常量指针与指针常量的区别


8.1、const

const是constant的简写,只要一个变量前面用const来修饰,就意味着该变量里的数据可以被访问,不能被修改。也就是说const意味着“只读”。任何修改该变量的尝试都会导致编译错误。const是通过编译器在编译的时候执行检查来确保实现的(也就是说const类型的变量不能改是编译错误,不是运行时错误。)所以我们只要想办法骗过编译器,就可以修改const定义的常量,而运行时不会报错。

规则:

1 .const离谁近,谁就不能被修改;
2 .因为常量在定义以后就不能被修改,所以使用const定义变量时必须初始化。

#include <stdio.h>

int main() {
   
          

    int i = 10;
    int j = 20;
    const int *ptr = &i;

    printf("ptr: %d\n", *ptr);
    *ptr = 100;   /* error: object pointed cannot be modified using the pointer ptr */

    ptr = &j;  /* valid */
    printf("ptr: %d\n", *ptr);

    return 0;
}

8.2、const point

声明指针时,可以在类型前或后使用关键字const,也可在两个位置都使用。三种定义形式如下:

常量指针

const int *ptr;
int const *ptr;

指针常量

int *const p2

加深记忆记住三句话:

指针和 const 谁在前先读谁 ;
*象征着地址,const象征着内容;
谁在前面谁就不允许改变。
例如:
int const *p1 = &b; //const 在前,定义为常量指针
int *const p2 = &c; // *在前,定义为指针常量




常量指针是指指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的
内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,
从而指向另一个常量。

指针常量是指指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它
指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初
值。

案例1:指针b指向的地址内容不能修改,而指针b可以指向其他地址。

//#改变指针b指向地址的内容
int main()
{
   
          
	int a = 2;
	int const *b = &a;
	*b = 3; //报错:expression must be a modifiable lvalue
	printf("albert:%d\n",a);
}

//#改变指针b的指向
int main()
{
   
          
	int a = 2;
	int b = 3;
	int const *c = &a;
	printf("albert:%p\n", c);
	c = &b;
	printf("albert:%p\n",c);
}
	

案例2:指针指向的地址不可以重新赋值,但内容可以改变。

int main()
{
   
          
	int a = 2;
	int b = 3;
	int *const c = &a;
	printf("albert:%p\n", c);
	c = &b; //报错:expression must be a modifiable lvalue
	printf("albert:%p\n",c);
}

int main()
{
   
          
	int a = 2;
	int b = 3;
	int *const c = &a;
	*c = 4;
	printf("albert:%d\n",*c); //4
}

案例3:数组名是指针常量

#include <stdio.h>
int main()
{
   
          
	int a[] = {
   
           1,5,10,20 };
	int b = *(a++); //*a++ 等同于*(a++)
	printf("b = %d\n",b);
}

数组名是指针常量,这里操作是会报错的

在这里插入图片描述

int main(){
   
          
//字符串 本身一个一维数组
//字符串数组  二维数组    * const


//    常量指针  不能修改指向的内容  但是能够重新赋值(重新换一个指向)   const int *ptr;

//    指针常量     能修改指向的内容   不能够重新赋值(不能重新换一个指向)  int *const  p2
    int * const p9;
    char arr[4][6] = {
   
          "abc", "efg", "hij", "klm"};  //指针常量

//  指针常量 int *const  arr
//    arr[0] = "frg";// 不能够重新赋值(不能重新换一个指向)
    strcpy(arr[0], "aaaaa");// 能修改指向的内容
//arr[4][6]   指针常量

    printf("value %s\n",  arr[0]);
//数组指针     1    arr1  指针常量 1  常量指针  2

//--------------------常量指针--------------
//    常量指针  不能修改指向的内容  但是能够重新赋值(重新换一个指向)   const int *ptr;

//    指针常量     能修改指向的内容   不能够重新赋值(不能重新换一个指向)  int *const  p2
    int const *p6;
    char *arr1[4]={
   
          "abc", "efg", "hij", "klm"};  //常量指针
//    换一个指向
    char *arr5 = "ABC";
    arr1[0] = "ABC";//重定向  地址
//
    printf("value %s\n",  arr1[0]);
//不能修改指向内容
//    strcpy(arr1[0], "BBBBB");
    printf("value %s\n",  arr1[0]);
}

九、文件读写


9.1、打开文件

您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

FILE *fopen( const char * filename, const char * mode );

在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:

模式 描述
r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

9.2、关闭文件

为了关闭文件,使用 fclose( ) 函数。函数的原型如下:

int fclose( FILE *fp );

如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件stdio.h 中的常量。

C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。

9.3、写入文件

下面是把字符写入到流中的最简单的函数:

int fputc( int c, FILE *fp );

函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:

int fputs( const char *s, FILE *fp );

函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。您也可以使用 int fprintf(FILE *fp,const char *format, …) 函数来写把一个字符串写入到文件中。尝试下面的实例:

注意:请确保您有可用的 tmp 目录,如果不存在该目录,则需要在您的计算机上先创建该目录。
/tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境
中已存在的目录,
例如: C:\tmp、D:\tmp等。


9.4、实例

#include <stdio.h>
int main()
{
   
           
	FILE *fp = NULL;
	fp = fopen("/tmp/test.txt", "w+");
	fprintf(fp, "This is testing for fprintf...\n");
	fputs("This is testing for fputs...\n", fp);
	fclose(fp);
}

当上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函
数写入两行。接下来让我们来读取这个文件。

9.5、读取文件

下面是从文件读取单个字符的最简单的函数:

int fgetc( FILE * fp );

fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回EOF。下面的函数允许您从流中读取一个字符串:

char *fgets( char *buf, int n, FILE *fp );

函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在
最后追加一个 null 字符来终止字符串。

如果这个函数在读取最后一个字符之前就遇到一个换行符 ‘\n’ 或文件的末尾 EOF,则只会返回读取到的
字符,包括换行符。

您也可以使用

int fscanf(FILE *fp, const char *format, …)

9.6、实例

#include <stdio.h>
int main()
{
   
           
	FILE *fp = NULL;
	char buff[255];
	fp = fopen("/tmp/test.txt", "r");
	fscanf(fp, "%s", buff);
	printf("1: %s\n", buff );
	fgets(buff, 255, (FILE*)fp);
	printf("2: %s\n", buff );
	fgets(buff, 255, (FILE*)fp);
	printf("3: %s\n", buff );
	fclose(fp);
}

当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:

//	1: This
//	2: is testing for fprintf...
//	3: This is testing for fputs...

首先,fscanf() 方法只读取了 This,因为它在后边遇到了一个空格。其次,调用 fgets() 读取剩余的部
分,直到行尾。最后,调用 fgets() 完整地读取第二行。

9.7、二进程 I/O 函数

下面两个函数用于二进制输入和输出:

size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,size_t number_of_elements, FILE *a_file);

这两个函数都是用于存储块的读写 - 通常是数组或结构体。

fwrite 和 fread函数的用法小结

#include <stdio.h>
#include <malloc.h>

//c---->.o---->.exe
//java -class- jar
int main1() {
   
           
    printf("file write\n");
    FILE *p = fopen("./test/test.txt", "w");
    if (p) {
   
           
        fclose(p);
//        p 悬空指针
        p = NULL;
//        文件存在
    } else {
   
           

    }
    return 0;
}
//读文件
int main3() {
   
           
    printf("file read\n");
    FILE *p = fopen("./test/test.txt","r");

    if (p) {
   
           
        char c = fgetc(p);
        printf("char %c\n", c);
    }
    fclose(p);
//        p 悬空指针
    p = NULL;
}
//循环读文件
int main4() {
   
           
    printf("file read\n");
    FILE *p = fopen("./test/test.txt","r");
    if (p) {
   
           
        while (1) {
   
           
            char c = fgetc(p);
            if (c == EOF) {
   
           
                break;
            }
            printf("char %c\n", c);
        }
    }
    fclose(p);
//        p 悬空指针
    p = NULL;
}
//写文件
int main5() {
   
           
    printf("file write\n");

    FILE *p = fopen("./test/test.txt", "w");
    if (p) {
   
           
        fputc('a', p);
        fclose(p);
        p = NULL;
    }
}
//copy文件
int main6(){
   
           
    printf("file copy\n");
    FILE *p = fopen("./test/test.txt", "r");
    FILE *p1 = fopen("./test/write.txt", "w");
    if (p == NULL||p1 == NULL) {
   
           
        return -1;
    }
    while (1) {
   
           
        char c = fgetc(p);
        if (c == EOF) {
   
           
            break;
        }
        fputc(c, p1);
    }
    fclose(p);
    fclose(p1);
    p = NULL;
    p1 = NULL;
}
int cacl(int a,char b,int c){
   
           
    int result = 0;
    switch (b) {
   
           
        case '+':
            result = a + c;
            break;
        case '-':
            result = a - c;
            break;
        case '*':
            result = a * c;
            break;
        case '/':
            result = a / c;
            break;
    }

    return result;
}

int main() {
   
           
    printf("file copy\n");
    FILE *p = fopen("./test/test.txt", "r");

    if (p) {
   
           

        while (1) {
   
           
            char buffer[100] = {
   
           0};
            fgets(buffer, sizeof(buffer), p);

            int a = 0;
            char b = 0;
            int c = 0;
            sscanf(buffer, "%d%c%d", &a, &b, &c);
//            printf("a %d  b %c c %d\n", a, b, c);
            printf("result %d \n ", cacl(a, b, c));
            if (feof(p)) {
   
           
                break;
            }
        }
        fclose(p);
        p = NULL;
    }
}


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!