C语言学习笔记 中

目录

变量详细解释

局部变量

全局变量

变量的存储方式和生存期

局部变量的存储类型

全局变量的存储类型

变量的作用域和存在性

善用指针

指针和指针类型

字符指针

野指针 

指针运算

指针和数组

二级指针 

指针数组

数组参数和指针参数 

函数指针 

字符函数和字符串函数

Strlen数组

Strcat函数

Strcmp

Strncpy

Strncat

strncmp

Strstr

strtok

strerror

字符分类函数:​

字符转换:

内存函数

Memcpy

memcpy函数一个拷贝不重叠的内存

memmove

memcmp

memest

结构体是用来自己建立数据类型 

结构体内存对齐

结构体传参

文件操作 

文件的打开和关闭

 文件的顺序读写

 文件的随机读写

文本文件和二进制文件 

 文件读取结束的判定

文件缓冲区



变量详细解释

局部变量

定义变量有三种情况

1在函数的开头定义

2在函数的复合语`句中定义

3在函数的外部定义

前两者所定义的变量都是局部变量,在一个函数的内部定义的变量只在函数范围内有效,也就是说只能在本函数内部引用它们,在复合语句中定义的变量只能在本复合语句内部有效,只能在本复合语句内才能引用它们。

全局变量

在函数外部定义的变量,在整个项目都有效

1全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才会开辟单元

2在同一个源文件中,全局变量和局部变量同名,在局部变量的作用范围内,局部变量有效,全局变量失效。

#include<stdio.h>
int a = 3, b = 5;
int main()
{
	int a = 4;
	int b = 3;
	printf("%d\n", a);//4
	printf("%d\n", b);//3
	return 0;
}

变量的存储方式和生存期

变量值存在的时间就是生存期,变量有两种不同的存储有两种方式,静态存储方式和动态存储方式

供用户使用的存储空间分为三个部分

1程序区

2动态存储区

3静态存储区

全局变量全部放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕后释放

在动态存储区存放以下数据

1函数形式参数,在函数调用时给形参分配存储空间

2函数中定义没有用关键字static声明的变量

3函数调用时的现场保护和返回地址等

对应以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间,在程序执行时,这些分配和释放是动态的,如果在一个程序中两次调用同一个函数,而在此函数定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的

所以每一个变量和函数都有两个属性,数据类型和数据的存储类型

存储类型有4个种类,自动的(auto),静态的(static),寄存器的(register),外部的(extern)

局部变量的存储类型

自动变量

函数的局部变量,如果不专门声明为static(静态)存储类型,都是动态地分配存储空间,数据存储在动态存储区,函数中的形参和在函数中定义的局部变量,包括复合函数中定义的局部变量,在函数调用时分配存储空间,在函数调用结束时自动释放这些存储空间

实际上定义auto变量时,可以将auto省略,int a=3与auto int b=3的效果是一样的

静态局部变量

有时候希望函数的值在函数调用结束后不消失,而能继续保存原值,其占用的内存空间不释放,就可以把这个局部变量变成静态局部变量

#include<stdio.h>
//int a = 3, b = 5;
int main()
{
	int f(int);
	int a = 2, i;
	for ( i = 0; i < 3; i++)
	{
		printf("%d\n", f(a));
	}
    //输出 7 8 9 
	return 0;
}
int f(int a)
{
	int b = 0;//自动类型的局部变量
	static int c = 3;//定义了静态局部变量
	b = b + 1;//每次调用f函数的b的值都是0。因为在这个函数的开头都会重新变成0
	c = c + 1;//每次调用c的值会随调用的增加而增加
	return a + b + c;
}

1静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在整个程序运行期间都不释放

2对静态局部变量是在编译是赋初值的,即只赋值初值一次,在程序运行时它已经有初值,以后每次调用函数时,不在重新赋值而只是保留上次函数调用结束时的值 ,而对于自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新赋一次初值

3如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时会自动赋初值0(整形变量),或者空字符(字符变量),而对自动变量来说,它的值是一个不确定的值,这是因为每次调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元内容是不可知的

4虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用的

寄存器变量(register)

一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的,当程序用到哪一个变量的值,由控制器发出指令将内存中该变量的值送到运算器中

如果有一些变量使用频繁,如循环10000次,那每次都要使用某局部变量,从内存存取变量值要花费不少时间,为了提高执行效率,允许将局部变量的值放在CPU的寄存器中,需要时直接从寄存器中取出参加运算,这种变量叫做寄存器变量

全局变量的存储类型

全局变量都是存放在静态存储区中,因此它们的生存期是固定的,存于程序的整个运行过程,一般来说外部变量的定义处开始,到本程序文件的末尾,但是为了扩张外部变量的作用域,出现下面几个情况

1在一个文件内扩展外部变量的作用域

如果外部变量不在文件开头定义,其有效作用范围只限于定义处到文件结束,在定义点之前就不能引用该外部变量,在定义点之前的函数需要引用这个全局变量,则在引用前应该加上extern对该变量作外部变量声明,表示把该外部变量的作用域扩展到此位置

#include<stdio.h>
//int a = 3, b = 5;
int main()
{
	
	int max();
    extern int a, b, c;//如果没有这个变量声明,会导致出现错误
	scanf("%d%d%d", &a, &b, &c);
	printf("%d\n", max());
	return 0;
}
int a, b, c;
int max()
{
	int m;
	m = a > b ? a : b;
	if (c > m) m = c;
	return m;

}

将外部变量的作用域扩展到其他文件

一个c程序可以由一个或多个源程序文件组成,怎么让同一个项目下的源文件去使用其他源文件的全局变量

 在将外部变量的作用域限制在本文件中

在定义外部变量时,加上static,这样这个外部变量只能在本源文件中,把这种外部变量称为静态外部变量

1不要误认为对外部变量加static声明后才采取静态存储方式,而没有使用static的采取动态存储,声明全局变量和局部变量的存储类型的含义是不同的,对于局部变量来说,声明存储类型的作用是指定变量的存储区域,而对于全局变量来说,由于都是在编译时候分配内存的,都存放在静态存储区,声明存储类型的作用只是变量作用域的扩展问题

变量的作用域和存在性

如果一个变量在文件内或者函数内是有效的,就成该范围是该变量的作用域,如果一个变量值在某一个时刻是存在的,则这个时刻属于该变量的生存期,或称该变量在此时刻存在

善用指针

什么是指针

指针是编程语言的一个对象,利用地址,它的值指向存在电脑存储器中另一个地方的值,因此把地址形象化为指针,意思是通过它可以找到以它为地址的内存单元

 存储区被分为一个个存储单元,为每一个存储单元给一个编号,编号也被称为存储单元的地址,也被称为指针

指针变量
我们可以通过 & (取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量
#include<stdio.h>
int main()
{
	int a = 10;//在vs中是占4个字节的地址
	int *pa=&a;//拿到的是a的4个字节第一个字节的地址
	*pa = 20;//pa就是一个指针变量
	printf("%d\n", a);
	return 0;
}
1指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
2指针是用来存放地址的,地址是唯一标示一块地址空间的。
3指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节

指针和指针类型

    int* pa;
	char* pc;
	float* pf;
	printf("%d\n", sizeof(pa));//4
	printf("%d\n", sizeof(pc));//4
	printf("%d\n", sizeof(pf));//4

指针类型的意义

可以看到每种指针类型的大小是一样的,但是

#include<stdio.h>
int main()
{
	int a = 0x11223344;
	int* pa = &a;
	char* pc =(char*) &a;
	*pc = 0;//只会将第一个字节的数据变成00
    printf("%x\n", a);//1122330
	*pa = 0;//会将四个字节的数据都变成00
    printf("%x\n", a);//0
	return 0;
}

1指针类型决定了指针解引用的权限有多大  char 1 int 4 double 8

#include<stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("%p\n", &n);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	printf("%p\n", pi);
	printf("%p\n", pi + 1);
	return 0;
}

 2指针类型决定了指针走一步,能走多远

#include<stdio.h>
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8 ,9 };
	int* p = arr;
	int i = 0;
	for (int i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}
	return 0;
}

字符指针

#include<stdio.h>
int main()
{
	char ch = 'q';
	char* pc = &ch;
	char* pstr = "hello world";
    //本质是将hello world 的首字母的h的起始地址存在ps中
	printf("%c\n", *pstr);//输出h
	return 0;
}

 面试题

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");//输出
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");//输出
    else
 printf("str3 and str4 are not same\n");
       
    return 0; }

两个数组的内容相同,但是地址是不相同的,但是后面的两个指针变量放的是hello bit的字符串的首字符地址,这个字符串的常量,是不能改的,所以两个指针存储的地址是相同的,这里直接用等号比较,比较的是两个变量的地址

野指针 

野指针就是表示指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)

成因

1定义指针未初始化,就直接用

    int* p;//p是一个局部指针变量,局部变量不初始化的话,默认值是一个随机值
	*p = 20;//非法访问内存  p就是野指针

2指针越界访问

#include<stdio.h>
int main()
{

	int arr1[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}
    //因为小于等于10,所以越界了
	return 0;
}

 3指针指向的空间释放

#include<stdio.h>
int main()
{    
    int* test();
	int* p = test();
	*p = 20;
	return 0;
}
int* test()
{
	int a = 10;
	return &a;
}

由于test中定义的a是局部变量,所以在执行完函数调用语句后,a就会释放内存空间,把这块内存空间还给操作系统了,所以p非法去访问内存空间,所以p就成为为了野指针
如何规避野指针
1. 指针初始化
2. 小心指针越界  ,c语言本身不会检测越界的
3. 指针指向空间释放即使置 NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性,空指针也是不能用的
#include <stdio.h>
int main()
{
    int *p = NULL;//当前不知道该指针初始化为何地址,先将其初始化为空指针
    //....
    int a = 10;
    p = &a;//明确知道初始化的值
    if(p != NULL)//使用前检查指针的有效性
   {
        *p = 20;
   }
    return 0;
 }

指针运算

  指针+- 整数
  指针-指针
指针的关系运算   
指针加减整数 加几个字节是却决于指针的类型,int 在vs中一次加1就是加4个字节
#include <stdio.h>
#define N_values 5
int main()
{    
    float values[N_values];
	float* vp;
	for ( vp = &values[0]; vp < &values[N_values];)
	{
		*vp++=0;
	}
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int* pend = arr + 9;
	while (p<=pend)
	{
		printf("%d\n", *p);
		p++;
	}// 1 2 3 4 5 6 7 8 9 10
    return 0;
 }

指针减指针   得到的是两个指针之间的元素个数 两个指针相减的前提是两个指针类型相同

#include<stdio.h>
int main()
{
	
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", &arr[9]-&arr[0]);//9
    //printf("%d\n", &arr[9] - &c[0]);错误写法
	return 0;
}

#include<stdio.h>
int main()
{
	int my_strlen(char*);
	char c[10]="abc";
	int len = my_strlen(c);
	printf("%d\n", len);
	return 0;
}
int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}

指针加指针是没有任何意义的

指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) 
{
    *vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组

数组名是数组第一个元素的首元素,所以可以通过指针去遍历一个数组的全部元素

#include<stdio.h>
int main()
{
	
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		printf("%p <->%p", &arr[i], p + i);
	}
    for ( i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}// 0 1 2 3 4 5 6 7 8 9
	return 0;
}

#include<stdio.h>
int main()
{
	
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	printf("%d\n", arr[2]);
	printf("%d\n", 2[arr]);
	printf("%d\n", p[2]);
	/*arr[2]=*(arr+2)=*(p+2)=*(2+p)=*(2+arr)=2[arr]*/
	/*因为[]是一个操作符,2和arr是两个操作数 并且满足
	 因为arr[2]在编译器的时候会被变成*(arr+2)*/
	return 0;
}

数组指针

数组指针是一种指针,是指向数组的指针;

#include<stdio.h>
int main()
{
	
	int a = 10;
	int* pa = &a;
	char ch = 'w';
	char* ch = &ch;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//arr是首元素的的地址,是arr[0]的地址
	int(*paar)[10]=&arr;//取出数组的地址
	//paar是一个数组指针 其中存放的是数组的地址
	int* arr1[10];//是一个指针数组
	//paar是一个数组指针
	double* d[5];
	double*(*pd)[5] = &d;
	return 0;
}

 &数组名VS数组名

 

 

 &arr是表示的数组的地址,而不是数组首元素的地址,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

还有一个sizeof里面用的数组名是整个数组

#include<stdio.h>
void print(int arr[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for ( i = 0; i <r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print1 (int (*p)[5],int r,int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ",*(*(p+i)+j));
		}
		printf("\n");
	}
}
int main()
{
	
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*pa)[10] = &arr;
	int i = 0;
	for ( i = 0; i <10; i++)
	{
		printf("%d ", *((*pa) + i));//*pa相当于arr
	}//1 2 3 4 5 5 7 8 9 10
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
	//arr是首元素的地址,二维数组的数组名表示第一行
    print1(arr, 3, 5);
	return 0;
}

二维数组的数组名表示二维数组第一行的首地址

第一个表示是一个大小为5的整形数组

第二个表示一个大小为10的指针数组

第三个表示一个数组指针,这个指针指向一个数组,数组有10个整型元素

第四个 paar3是一个存放数组指针的数组,该数组能存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素的int 类型

数组参数和指针参数

二级指针 

什么是二级指针

指针也是一个变量,所以指针的变量也需要存储空间,二级指针就是存放指针地址的变量

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;//pa是指针变量,一级指针
	int* *ppa = &pa;//pa也是一个变量,&pa取出pa在内存的起始地址
	//ppa就是一个二级指针变量
	return 0;
}

指针数组

指针数组是指针还是数组

指针数组当然是数组,是用来存放指针的数组

#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = { &a,&b,&c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
    //这种写法不常见
    int a[5] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };
	int* arr[] = { a,b,c };
	for ( int i = 0; i <3;  i++)
	{
		int j = 0;
		for ( j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j));
            printf("%d ", (arr[i][j]));//这两个效果一样 因为[j]编译能变成+j;
		}
		printf("\n");
	}
	return 0;
}

数组参数和指针参数 

一维数组传参

1第一个可以

2第二个可以

3第三个可以,因为数组在传参的时候就是将首元素的地址传过去,int *arr是一个整型指针

4第四个可以

5第五个也可以,因为传过去的是一个int*的地址,所以要用一个int**来存储这个地址

二维数组传参

1第一个可以

2第二个不可以,可以省略行,但是不能省略列

3第三个可以

4第四个不可以,因为二维数组传过去的是首行的地址,

5第五个不可以,因为这是一个指针数组,

6第六个可以,因为这是一个数组指针,是用来存放数组的地址的

7第七个也不可以,因为传过去的是一个int的地址,匹配不上

一级指针传参

#include<stdio.h>
void print3(int* ptr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(ptr + i));
	}
}
int main()
{    
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//p是一级指针
	print3(p, sz);
	return 0;
}
二级指针传参
#include <stdio.h>
void test(int** ptr) {
 printf("num = %d\n", **ptr); 
}
int main()
{
     int n = 10;
     int*p = &n;
     int **pp = &p;
     test(pp);
     test(&p);
     int* arr[10]={0};
     test(arr);//因为这是一个int*的数组,所以数组的首元素的地址也是一个二级指针
     return 0;
 }

函数指针 

指向函数的指针,存放函数地址的指针

#include <stdio.h>

int main()
{	
    int Add(int,int);
	printf("%p\n", &Add);//输出函数的地址
    printf("%p\n", Add);//函数名是==&函数名
    int (*p)(int,int) = &Add;//p就是一个函数指针
    int ret=(*p)(3, 5);//利用指针去调用函数 
    //这个*没有任何作用,只是方便理解
    int ret1=p(3,5);//利用指针去调用函数
	printf("%d\n", ret);//8
    printf("%d\n", ret);//8
    return 0;
 }
int Add(int x, int y)
{
	return x + y;
}

函数名就是函数的地址

        该函数无参,返回类型是void

        1   void(*)() 函数指针类型

        2(void(*)())0 将0强制转换为一个函数指针类型,0就作为一个地址了

        3 *(void(*)())0) 对0地址进行解引用操作

        4(*(void(*)())0)();调用这个0地址这个函数 0地址就是这个函数的地址

        void (*signal(int , void(*)(int)))(int);

        1void(*)(int)是一个函数指针类型     int是一个整型

        2 signal(int , void(*)(int)))是一个函数声明 第一个参数是int 第二个是函数指针 该函数指针指向一个参数为int返回类型是void的,

        2 void (*)(int);也是一个函数指针类型  void (*signal(int , void(*)(int)))(int);去掉siganl函数

        2void (*signal(int , void(*)(int)))(int);signal的返回类型是一个函数指针,该函数指针指向一个参数为int,返回类型的void的函数

函数指针数组

就是存放相同函数指针类型的数组

#include <stdio.h>

int main()
{	
    int Add(int, int);
	int Sub(int, int);
	int (*pf)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
    //pf pf2的函数指针类型是一样的,因为返回值和参数是一样的
	int(*pff[2])(int, int) = { Add,Sub };//函数指针数组
    return 0;
 }
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
 
 冒泡排序的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	
	int arr[10] = {9,7,8,6,5,4,3,2,1,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	void bubble_sort(int arr[], int);
	bubble_sort(arr,sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
void bubble_sort(int arr[], int l)
{
	int i = 0;
	for (i = 0; i < l - 1; i++)
	{
		for (int j = 0; j < l - 1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
qsort的使用
 
#include<stdio.h>
#include<cstdlib>
//void qsort(void* base,//base中存放者待排序数据的第一个对象的地址
//	size_t num,//排序数据元素的个数
//	size_t size,//排序数据的一个元素的大小
//	int (*cmp)(const void*, const void*)//用来比较待排序数据中2
//										//个元素的函数
//);
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void print_arr(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
struct Stu
{
	char name[20];
	int age;
};
int sort_age(const void*e1,const void*e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}
void test2()
{
	struct Stu s[] = { {"zhangsan",30},{"list",34},{"wangwu",20} };
	//按年龄排序
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), sort_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name, s[i].age);
	}
}
void test3()
{
	struct Stu s[] = { {"zhangsan",30},{"list",34},{"wangwu",20} };
	//按名字排序
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), sort_name);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", s[i].name, s[i].age);
	}
}
int main()
{
	test1();//整型排序
    test2();//结构体排序
	return 0;
}
使用回调函数,模拟实现qsort(采用冒泡的方式)。
注意:这里第一次使用 void* 的指针,讲解 void* 的作用。 void*可以放任意类型数据的地址
#include<stdio.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//实现整型数据比较
}
void print_arr(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);//实现数组数据输出
	}
}
void swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}//实现数据交换
}
void bulle_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if(cmp((char*)base+j*width,(char*)base + (j +1)* width )>0)
			{
				swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
int main()
{
	//test1();
	//test2();
	/*test3();*/
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
	return 0;
}
#include <stdio.h>

int main()
{
    int a = 10;
	char ch = 'w';
	void* p = &ch;
	p = &ch;
    //void*可以放任意类型数据的地址
    //但是使用的时候需要将其强制类型为一个数据类型
    //因为指针解引用需要指针的一次要访问几个字节
     return 0;
 }

面试题

//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16
printf("%d\n",sizeof(a+0));//4/8 因为a+0是一个地址 地址的长度由系统决定
printf("%d\n",sizeof(*a));//4 int的长度是4
printf("%d\n",sizeof(a+1));// 4/8
printf("%d\n",sizeof(a[1]));// 4
printf("%d\n",sizeof(&a));//4/8
printf("%d\n",sizeof(*&a));// 16计算的是数组的大小
printf("%d\n",sizeof(&a+1));//4/8 是数组后面的空间的地址
printf("%d\n",sizeof(&a[0]));// 4/8
printf("%d\n",sizeof(&a[0]+1));//4 /8
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6
printf("%d\n", sizeof(arr+0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr+1));4/8
printf("%d\n", sizeof(&arr[0]+1));4/8
printf("%d\n", strlen(arr));//随机值  这个strlen传的参数是一个地址
printf("%d\n", strlen(arr+0));//随机值
printf("%d\n", strlen(*arr));//error
printf("%d\n", strlen(arr[1]));//error9
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr+1));//随机值-6
printf("%d\n", strlen(&arr[0]+1));//随机值-1
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr+0));//4
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr+1));//4/8
printf("%d\n", sizeof(&arr[0]+1));//4/8
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr+0));//6
printf("%d\n", strlen(*arr));//error
printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr+1));随机值
printf("%d\n", strlen(&arr[0]+1));//5
char *p = "abcdef";
printf("%d\n", sizeof(p));//4/8
printf("%d\n", sizeof(p+1));//4/8
printf("%d\n", sizeof(*p));//1
printf("%d\n", sizeof(p[0]));//1
printf("%d\n", sizeof(&p));//4/8
printf("%d\n", sizeof(&p+1));//4/8
printf("%d\n", sizeof(&p[0]+1));//4/8
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p+1));//5
printf("%d\n", strlen(*p));//error
printf("%d\n", strlen(p[0]));//error
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p+1));//随机值
printf("%d\n", strlen(&p[0]+1));//随机值
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//二维数组在内存中是连续存放的 ,3*4*4
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//16 第一行的数组 4*4
printf("%d\n",sizeof(a[0]+1));//4 a[0]作为数组名并没有单独放在sizeof内部,也没有取地址
//所以a[0]是第一行第一个算的地址,a[0]就是第一行第二个元素
printf("%d\n",sizeof(*(a[0]+1)));//4 表示第一行第二个元素
printf("%d\n",sizeof(a+1));// 4第二行的首地址
printf("%d\n",sizeof(*(a+1)));// 16 a+1是表达第二行的地址,所以*(a+1)表示第二行
printf("%d\n",sizeof(&a[0]+1));//4 a[0]表示第一行的数组名,
//取出的就是第一行的地址,&a[0]就是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16 取出的是第二行的数组大小
printf("%d\n",sizeof(*a));//16 a作为二维数组名,没有&,没有单独放在sizeof内部
//a就是首元素的地址,即第一行的地址,所以*a就是第一行的大小
printf("%d\n",sizeof(a[3]));//16a[3]其实是第四行的数组名(如果存在的)
//所以其实不存在,也是通过类型计算大小,sizeof里面不会进行运算
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));//2 5
    //&a取的是a数组的地址 所以加1是到下一个地址
    return 0;
 }
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 printf("%p\n", p + 0x1);//加地址加1 相当于加一个结构体的内存的大小 0x100014
 printf("%p\n", (unsigned long)p + 0x1);//将这个地址强制类型转换为一个整型 所以是0x100001
 printf("%p\n", (unsigned int*)p + 0x1);//强制转换为一个整型变量 0x100004
 return 0; }
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);//指向下一个数组的位置
    int *ptr2 = (int *)((int)a + 1);//指针的指向下一个字节的位置
    printf( "%x,%x", ptr1[-1], *ptr2);//4 0x200000
    //我的机器采用小端储存模式 所以在内存中(int)a+1指向的地址是 
    return 0; }

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//注意用的是括号,不是花括号
    int *p;
    p = a[0];
    printf( "%d", p[0]);//1
 return 0; }

int main()
{
    int a[5][5];
    int(*p)[4];//这是一个数组指针 存放数组的地址
    p = a;//将a数组首元素的地址放入指针p 也就是a数组第一行的数组的地址
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);// 0xfffffff4 -4
    // &p[4][3] 是&(*(p+4)+3) 相当于p[4]+3 由于p是一个数组长度为4的指针
    //所以加1,只会向后移动四个位置 故 &p[4][2] - &a[4][2] 是-4
    //-4在内存中是怎么储存  10000000 00000000 00000000 00000100 原码
    //                    11111111  11111111 11111111 11111011 反码
    //                    11111111   11111111  1111111 11111100 补码
    return 0; }

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);//&aa 取出来的是aa整个数组的地址
    int *ptr2 = (int *)(*(aa + 1));//aa+1 是aa首元素数组地址加1 *(aa + 1)相当于a[1]
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
    //
    return 0; }
#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};//a是一个指针数组 类型是char*
 char**pa = a;//pa是存放a数组的第一个char*的地址
 pa++; //加一个char*类型的长度
 printf("%s\n", *pa);//at
 return 0;
 }
int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};//cp是一个数组每个元素是char**
 char***cpp = cp;//cpp是一个指针 类型是char*** 存放着char**的地址
 printf("%s\n", **++cpp);//POINT 
 printf("%s\n", *--*++cpp+3);//ER printf里面的操作会真实的执行
 printf("%s\n", *cpp[-2]+3);//**(cpp-2)+3 ST
 printf("%s\n", cpp[-1][-1]+1); *(*(cpp-1)-1)+1 EW
 return 0; }

字符函数和字符串函数

求字符串长度
strlen
长度不受限制的字符串函数
strcpy
strcat
strcmp
长度受限制的字符串函数介绍
strncpy
strncat
strncmp
字符串查找
strstr
strtok
错误信息报告
strerror
字符操作
内存操作函数
memcpy
memmove
memset
memcmp
  
 

 Strlen数组

#include<stdio.h>
#include<string.h>
#include<assert.h>
//计算器版本
int my_strlen1(const char* str)
{
	int count = 0;//计数器
	assert(str != NULL);
	while (*str!='\0')
	{
		count++;
		str++;
	}
	return count;
}
int my_strlen2(const char* str)
{
	if (*str == '\0')//不能使用计数器版本
		return 0;
	else
		return 1 + my_strlen1(str + 1);
}
//指针-指针的方式
int my_strlen3(char* s) {
	char* p = s;// 指针 - 指针的方式 指针减指针的值是两个指针相差的数据个数
	while (*p != '\0')
		p++;
	return p - s;
}
int main()
{ 
	char arr[] = "abc";
	char arr1[] = { 'a','b','c' };
	int len = strlen(arr);
	int len1 = strlen(arr1);
	int len2 = my_strlen1(arr);
	printf("%d\n", len);//3
	printf("%d\n", len1);//随机数
	printf("%d\n", len2);//3
	return 0;
}

1字符串以'\0'作为结束标志,strlen函数返回的是在字符串中'\0'前面出现的字符个数(不包括\0)

 2参数指针指向的字符串必须要以'\0'结束

3注意函数返回值是size_t,是无符号数

#include<stdio.h>
#include<string.h>

int main()
{ 
	
	if (strlen("abc") - strlen("abcdef") > 0)
	{
		printf("大于");
	}
	else
	{
		printf("小于");
	}
	return 0;
}

输出大于,因为返回的是一个无符号数,相减的数也应该是一个无符号数

 strcpy数组

#include<stdio.h>
#include<string.h>
char* my_strcpy(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest != NULL);
	assert(src != NULL);
	while ((*dest++ = *src++))
	{
		;
	}
	return ret;
}
int main()
{ 
	
	char arr[20] = { 0 };
	const char* p = "hello";//只是把hello的h字符的地址放入p
	strcpy(arr, "hello");
	printf("%s\n", arr);
    char arr2[] = { 'a','b','c' };
	strcpy(arr, arr2);
	printf("%s", arr);//不是预想的结果,因为不知道/0在哪
    const char arr3[3] = "###";
	strcpy(arr, p);//会发生编译错误,因为arr3的大小不够放hello
	printf("%s\n", arr);
	return 0;
}

 1源字符串必须以‘\0'结束

2会将源字符串中的'\0'拷贝到目标空间

3目标空间必须足够大,比确保能存放到源字符串

4目标空间必须可变

5学会模拟实现

Strcat函数

#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
	assert(dest && src);
	while (*dest)
	{
		dest++;
	}
	while (*dest++=*src++)
	{
		;
	}
	return ret;
}
int main()
{ 
	
    char arr[20] = "hello\0######";
	char arr1[20] = "world";
	strcat(arr, arr1);
	printf("%s\n", arr);//helloworld 遇到arr的斜杆零就开始将world追加到后面 
     //,并在最后加一个\0
    //strcat(arr, arr);自己连接自己不行 因为斜杆零被覆盖了,所以会陷入死循环
	return 0;
}

Strcmp

#include<stdio.h>
#include<string.h>
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1==*s2)
	{
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	return *s1 - *s2;
int main()
{ 
	
	int ret = strcmp("abc", "abcd");//-1
    int ret1 = strcmp("abc", "abc");//0
    int ret2 = strcmp("abcb", "abd");//-1
    //这个函数比较的是两个字符串每个位置的字符的ascii码的值的大小、
    //,一样的话就比较下一个字符的ASCII码值
	printf("%d\n", ret);
	return 0;
}

 以上三个函数与字符串长度没有关系,不受限

Strncpy

#include<stdio.h>
#include<string.h>

int main()
{ 
	
	char arr1[20] = "abcdefg";
	char arr2[] = "qwer";
	strncpy(arr1, arr2, 2);//qwcdefg
	printf("%s\n", arr1);//如果arr2的长度不够n,缺失的部分用'\0'表示
	return 0;
}

Strncat

#include <stdio.h>
#include <string.h>
int main ()
{
 char str1[20];
 char str2[20];
 strcpy (str1,"To be ");
 strcpy (str2,"or not to be");
 strncat (str1, str2, 6);
 puts (str1);//To be or not
 return 0;
}

strncmp

#include <stdio.h>
#include <string.h>

int main ()
{
     const char* p = "abcdefgh";
	const char* q = "abcqwert";
	int ret = strncmp(p, q, 4);//只比较前四个字符
	printf("%d\n", ret);
 return 0;
}
比较到出现另个字符不一样或者一个字符串结束或者
num个字符全部比较完。

Strstr

 Returns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of

str1.
#include <stdio.h>
#include <string.h>
char* my_strstr(const char* str1,const  char* str2)
{
	assert(str1 && str2);
	const char* s1 = NULL;
	const char* s2 = NULL;
	const char* cp = str1;//const 表示指向的内容不可更该,但是指针可以更改,
	//这个const 是修饰这个指针指向的变量是不可更改的
    if (*str2=='\0')
	{
		return (char*)str1;
	}
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1&&*s2&&(*s1==*s2))
		{
			s1++;
			s2++;
		}
		if (*s2=='\0')
		{
			return (char*)cp;
		}
		cp++;
	}
	return NULL;
}//模仿实现
int main ()
{
  char arr1[] = "abcdefabcdef";
	char arr2[] = "bcd";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了 %s\n",ret);
	}//bcdefabcdef
     return 0;
}

strtok

#include <stdio.h>
#include <string.h>
int main ()
{
   char arr[] = "zpw@bitdu.tech";
	const char* p = "@.";
	char tmp[20] = { 0 };
	strcpy(tmp, arr);
	char* ret = NULL;
	ret = strtok(tmp, p);
	printf("%s\n", ret);//zpw
	ret = strtok(NULL, p);
	printf("%s\n", ret);//bitdu
	ret = strtok(NULL, p);
	printf("%s\n", ret);//tech
	strtok(arr, p);
    strcpy(tmp, arr);
    for (ret = strtok(tmp, p); ret != NULL; ret = strtok(NULL, p))
	{
		printf("%s\n", ret);
	}
 return 0;
}

1sep参数是个字符串,定义了用作分隔符的字符集合

2第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
3strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
4strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
5strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
6如果字符串中不存在更多的标记,则返回 NULL 指针。
用static来解决这个记录的功能

strerror

在调用库函数发生错误的时候,都会设置错误码

#include <stdio.h>
#include <string.h>
#include <errno.h>
//全局的错误码
int main ()
{
     printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	printf("%s\n", strerror(5));
   // No error
   // Operation not permitted
   // No such file or directory
   // No such process
    //Interrupted function call
    //Input/output error
    FILE* pf = fopen("text.txt", "r");
	if (pf==NULL)
	{
		printf("%s\n", strerror(errno));
        perror("fopen");//这个函数更加高明,首先将错误码转换为错误信息,
        //然后自己打印错误信息,包含自定义的信息fopen:No such file or directory
		return 1;//如果没有这个文件,就是没有打开i,
        //就会输出No such file or directory
	}
	fclose(pf);
	pf = NULL;
 return 0;
}

字符分类函数:

#include<stdio.h>
#include<ctype.h>
int main()
{
	char ch = 't';
	int ret=isdigit(ch);
	printf("%d\n", ret);//0 如果是数字字符,返回0
	char ch1 = '2';
	int ret1 = isdigit(ch1);
	printf("%d\n", ret1);//4  如果不是数字字符,返回非0
	return 0;
}

字符转换:

int tolower ( int c );
int toupper ( int c );
#include<stdio.h>
#include<ctype.h>
int main()
{
	
	char arr[20] = { 0 };
	scanf("%s", arr);
	int i = 0;
	while (arr[i]!='\0')
	{
		if (isupper(arr[i]))
		{
			arr[i] = tolower(arr[i]);
			
		}
		printf("%c", arr[i]);
		i++;
	}
	return 0;
}

内存函数

Memcpy

函数memcpysource的位置开始向后复制num个字节的数据到destination的内存位置。这个函数在遇到 '\0' 的时候并不会停下来。 如果sourcedestination有任何的重叠,复制的结果都是未定义的。

这个函数只要实现了不重叠拷贝就可以了,但是vs在实现既可以实现不重叠拷贝,也可以实现重叠拷贝

#include<stdio.h>
#include<ctype.h>
void* my_memcpy(void* dest, const void* src,size_t num)
{
	assert(dest && src);//因为需要解引用,所以不能为空指针
	void* sr = dest;
	char* s1 = (char*)dest;
	char* s2 = (char*)src;
	while (num--)
	{
		*s1 = *s2;
		s1++;
		s2++;
	}
	return sr;
}
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[20] = { 0 };
	memcpy(arr2, arr1, 20);//arr2前五个变成1 2 3 4 5 
    int arr3[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr4[20] = { 0 };
	my_memcpy(arr4, arr3, 20);
	for (int i = 0; i < 20; i++)
	{
		printf("%d ", arr4[i]);
	}
	return 0;
}

memcpy函数一个拷贝不重叠的内存

但是memmpve可以处理内存重叠的问题

memmove

memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include <cassert>
void* my_memmove(void* dest, const void* src, size_t num)
{
	//可以将原数据将从后往前拷贝,或者从前往后拷贝,防止数据覆盖
	//当源数据的指针指向的地址在目标数据空间指针的后面,只能从前往后拷贝
	//当目标数据空间的指针地址在源数据的空间内,则从后往前拷贝
	//如果目标数据空间的指针指向的地址在源数据的空间的后面,则没有覆盖,无关系
	assert(dest && src);//因为需要解引用,所以不能为空指针
	void* sr = dest;
	char* s1 = (char*)dest;
	char* s2 = (char*)src;
	if (dest < src)
	{
		while (num--)
		{
			*s1 = *s2;
			s1++;
			s2++;
		}
		//从前向后拷贝
	}
	else
	{
		//从后向前拷贝
		while (num--)
		{
			*(s1 + num) = *(s2 + num);
		}
	}
	return sr;
}
int main()
{
	
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr1 + 2, arr1, 20);
	for (int i = 0; i <20; i++)
	{
		printf("%d ", arr1[i]);
	}//1 2 1 2 3 4 5 8 9 10
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memmove(arr1 , arr1+2, 20);
	for (int i = 0; i <10; i++)
	{
		printf("%d ", arr1[i]);
	}//3 4 5 6 7 6 7 8 9 10
	return 0;
}

 memcmp

 内存比较

#include<stdio.h>
#include<ctype.h>

int main()
{
    float arr1[] = { 1.0,2.0,3.0,4.0,5.0 };
	float arr2[] = { 1.0,3.0};
	int ret = memcmp(arr1, arr2, 4);
	printf("%d ", ret);//0
    int ret1 = memcmp(arr1, arr2, 8);//-1
	return 0;
}

memest

#include<stdio.h>
#include<ctype.h>

int main()
{
    int arr[10] = { 0 };
	memset(arr, 1, 20);//以字节为单位设置内存
	return 0;
}

文件操作 

 为什么使用文件

我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯 录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。 我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。 这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。 使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
什么是文件
磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
程序文件
包括源程序文件(后缀为 .c , 目标文件( windows 环境后缀为 .obj , 可执行程序( windows 环境 后缀为.exe )。
  数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显 示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理 的就是磁盘上文件。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3 部分:文件路径 + 文件名主干 + 文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为 文件名

文件的打开和关闭

文件指针
缓冲文件系统中,关键的概念是 文件类型指针 ,简称 文件指针
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统 声明的,取名FILE
每当打开一个文件的时候,系统会根据文件的情况自动创建一个 FILE 结构的变量,并填充其中的信息,
使用者不必关心细节。
一般都是通过一个 FILE 的指针来维护这个 FILE 结构的变量,这样使用起来更加方便。
下面我们可以创建一个 FILE* 的指针变量 :

FILE *pf;//创建一个文件指针变量
定义 pf 是一个指向 FILE 类型数据的指针变量。可以使 pf 指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联 的文件

文件的打开和关闭 

文件在读写之前应该先 打开文件 ,在使用结束之后应该 关闭文件
在编写程序的时候,在打开文件的同时,都会返回一个 FILE* 的指针变量指向该文件,也相当于建立了指 针和文件的关系。
ANSIC 规定使用 fopen 函数来打开文件, fclose 来关闭文件
#include<stdio.h>
int main()
{
    //打开文件
	FILE* pf = fopen("test.txt","w");
	FILE* pf1 = fopen("E:\\c代码\\新建文本文档.txt", "w");
    //如果文件不存在直接报错
	if (pf==NULL)
	{
		perror("fopen");
		return 1;
	}
	if (pf1 == NULL)
	{
		perror("fopen");
		return 1;
	}
    //关闭文件
	fclose(pf);
	fclose(pf1);
	pf = NULL;
	pf1= NULL;
	return 0;
}

以w写 ,如果这个文件已经有数据,会格式化然后再往里面写

 文件的顺序读写

 

F

#include<stdio.h>
int main()
{
	
	fputc('b', stdout);
	fputc('i', stdout);
	fputc('t', stdout);
    //stdout是标志输出流,输出到屏幕,所以输出bit
    int ret = fgetc(stdin);//从键盘输入
	printf("%c\n", ret);
	ret = fgetc(stdin);
	printf("%c\n", ret);
	ret = fgetc(stdin);
	printf("%c\n", ret);
	return 0;
}

fputc

#include<stdio.h>
int main()
{
	
	//打开文件,以读的方式访问
	FILE* pf = fopen("test.txt","w");
	//如果没有成功访问,直接结束
	if (pf==NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputc('b', pf);//一次写入一个字符
	fputc('i', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

fgetc

#include<stdio.h>
int main()
{
		//打开文件,以读的方式访问
		FILE* pf = fopen("test.txt", "r");
		//如果没有成功访问,直接结束
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
		//读文件
		int ret=fgetc( pf);//一次读出一个字符
		printf("%c\n", ret);//a
		 ret = fgetc(pf);
		printf("%c\n", ret);//b
		 ret = fgetc(pf);
		printf("%c\n", ret);//c
		//关闭文件
		fclose(pf);
		pf = NULL;
		return 0;
}

fputs

#include<stdio.h>
int main()
{
	
		//打开文件,以读的方式访问
		FILE* pf = fopen("test.txt", "w");
		//如果没有成功访问,直接结束
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
		//按行来写
		fputs("abcdef", pf);//一次写入一个字符串 并不是按行写,
        //而是连续的的写 以文本写入
		fputs("qwertyuiio", pf);
        fputs("abcdef\n", pf);
		fputs("qwertyuiio\n", pf);
		//关闭文件
		fclose(pf);
		pf = NULL;
		return 0;
}

 

fgets

#include<stdio.h>
int main()
{
	
		//打开文件,以行读的方式访问
		char arr[10] = { 0 };
		FILE* pf = fopen("test.txt", "r");
		//如果没有成功访问,直接结束
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
		//按行来读
		fgets(arr, 4, pf);//读出4个字符
		printf("%s\n", arr);//abc 最后一个空用来放/0
		fgets(arr, 4, pf);
		printf("%s\n", arr);//def
		//关闭文件
		fclose(pf);
		pf = NULL;
		return 0;
}

fprintf

#include<stdio.h>
struct S
{
	char arr[10];
	int num;
	float sc;
};
int main()
{
    struct S s = { "abcdefg",10,5.5f };
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fprintf(pf, "%s %d %f", s.arr, s.num, s.sc);//以格式化写入文件
	//关闭文件
	fclose(pf);
	pf = NULL;
	
		return 0;
}

fscanf

 

#include<stdio.h>
struct S
{
	char arr[10];
	int num;
	float sc;
};
int main()
{
    struct S s = { 0};
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fscanf(pf, "%s %d %f", s.arr,&( s.num), &(s.sc));//格式化写出 以文本写出
    printf(" % s % d % f", s.arr, (s.num), s.sc);
	//关闭文件
	fclose(pf);
	pf = NULL;
	
		return 0;
}

fwrite

 

#include<stdio.h>
struct S
{
	char arr[10];
	int num;
	float sc;
};
int main()
{
   struct S s = { "abcdef",10,5.5f};
	FILE* pf = fopen("test.txt", "w");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fwrite(&s, sizeof(struct S), 1, pf);//以二进制输入
	//关闭文件
	fclose(pf);
	pf = NULL;
	
		return 0;
}

 fread

#include<stdio.h>
struct S
{
	char arr[10];
	int num;
	float sc;
};
int main()
{
    struct S s = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (NULL == pf)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	fread(&s, sizeof(struct S), 1, pf);//以二进制形式读
    //1是表示最大读取的数量
	printf("%s %d %f", s.arr, s.num, s.sc);
	//关闭文件
	fclose(pf);
	pf = NULL;
	
	return 0;
}

 

总结

sprintf

#include<stdio.h>
struct S
{
	char arr[10];
	int age;
	float f;
};
int main()
{
	struct S s = { "hello",20,5.5f };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.arr, s.age, s.f);//把一个格式化的数据,转换成字符串
	printf("%s\n", buf);
	return 0;
}

  

 sscanf

#include<stdio.h>
struct S
{
	char arr[10];
	int age;
	float f;
};
int main()
{
	struct S s = { "hello",20,5.5f };
	struct S tmp = { 0 };
	char buf[100] = { 0 };
	sprintf(buf, "%s %d %f", s.arr, s.age, s.f);
	printf("%s\n", buf);
	sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.f));
    //从一个字符串中读取一个格式化的数据
	printf("%s %d %f", tmp.arr, tmp.age, tmp.f);
	return 0;
}

 文件的随机读写

fseek

origin的三个位置选择

#include<stdio.h>

int main()
{
	
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	int ret=fgetc(pf);
	printf("%c\n", ret);//a
    //修改文件指针的位置
	fseek(pf, -1, SEEK_CUR);//指针当前位置减1
	ret = fgetc(pf);
	printf("%c\n", ret);//a
	ret = fgetc(pf);
	printf("%c\n", ret);//b
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

ftell

#include<stdio.h>

int main()
{
	
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	int ret=fgetc(pf);
	printf("%c\n", ret);
	//改变文件指针的位置
	fseek(pf, -1, SEEK_CUR);//指针当前位置减1
	ret = fgetc(pf);
	printf("%c\n", ret);//a
	ret = fgetc(pf);
	printf("%c\n", ret);//b
	//找出文件指针的位置
	int num = ftell(pf);
	printf("%d\n",num);//2
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
rewind

#include<stdio.h>

int main()
{
	
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	int ret=fgetc(pf);
	printf("%c\n", ret);
	//改变文件指针的位置
	fseek(pf, -1, SEEK_CUR);//指针当前位置减1
	ret = fgetc(pf);
	printf("%c\n", ret);//a
	ret = fgetc(pf);
	printf("%c\n", ret);//b
	//让指针回到起始位置
	rewind(pf);
	ret = fgetc(pf);
	printf("%c", ret);//a
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

文本文件和二进制文件 

根据数据的组织形式,数据文件被称为文本文件和二进制文件,数据在内存中以二进制存储,如果不加转换输出到外存,就是二进制文件,如果要求外存上以ASCII码值的形式存储,则需要在存储前转换,以ASCII码值形式存储的就是文本文件

一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII 形式存储,也可以使用二进制形式存储。 如有整数10000 ,如果以 ASCII 码的形式输出到磁盘,则磁盘中占用 5 个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4 个字节( VS2013 测试)。
#include<stdio.h>
int main()
{
	
	int a = 10000;
	FILE* pf = fopen("test.txt", "wb");
	if (pf==NULL)
	{
		perror("fopen");
	}
	//写文件
	fwrite(&a, sizeof(4), 1, pf);//以二进制形式将10000写入内存
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

 第一个是以文本形式打开 第二个以二进制形式打开 10000的二进制是 

0000 0000 0000 0000 0010 0111 0001 0000

 文件读取结束的判定

 fgetc函数在读取结束的时候,会返回EOF,正常读取的时候,返回的是读取到字符的ASCII码值,

fgets函数在读取结束的时候,会返回NULL,正常读取的时候,返回存放字符串的空间起始地址

fread函数在读取的时候,返回的是实际读取到的完整元素的个数,如果发现读取到的完整元素的个数小于指定的元素个数,这就是最后一次读取

被错用的feof

feof在函数读取的过程中,不能用feof的返回值来判断是否读取结束,而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

#include<stdio.h>
int main()
{
	

	//将test.txt文件拷贝一份,生成test1.txt
	FILE* pfread = fopen("test.txt", "r");
	if (pfread == NULL)
	{
		perror("fopen");
		return 1;
	}
	FILE* pfwrite = fopen("test1.txt", "w");
	if (pfwrite == NULL)
	{
		perror("fopen");
		fclose(pfread);
		return 1;
	}
	//读写文件
	int ch=0;
	while (((ch = fgetc(pfread)) != EOF))
	{
		//写文件
		fputc(ch, pfwrite);
	}
	//关闭文件
	fclose(pfread);
	pfread = NULL;
	fclose(pfwrite);
	pfwrite = NULL;
	return 0;
}

 

文件缓冲区

ANSIC 标准采用 缓冲文件系统 处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序 中每一个正在使用的文件开辟一块“ 文件缓冲区 。从内存向磁盘输出数据会先送到内存中的缓冲区,装 满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓 冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根 据C 编译系统决定的。
#include<stdio.h>
#include<Windows.h>
int main()
{
	
	//pfwrite = NULL;
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
	//注:fflush 在高版本的VS上不能使用了
	printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

 真滴可以实现

结论

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。 如果不做,可能导致读写文件的问题,因为在关闭文件时会刷新缓冲区

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值