一、概述
最近家里有点事,趁在家的这段时间,复习一下C语言核心知识,后的底层开发、音视频开发、跨平台开发、算法等方向的进一步研究与学习埋下伏笔
本篇文章接着上一篇继续对C语言的核心语法知识进行复习
二、C 语言核心语法|结构体、共用体
1. 结构体
C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项
。
结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性:
- Title
- Author
- Subject
- Book ID
定义结构
为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct name{
member-list;
member-list;
...
}name_tag;
name 是结构的标签。
member-list 是标准的变量定义,比如 int i;或者 float f,或者其它有效的变量定义。
name_tag 结构变量,定义在结构的末尾,最后一个分号之前,你可以指定一个或多个结构变量,下面是声明 Book 的结构方式:
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
注意:在定义结构体的时候name、member-list、name_tag 这 3 部分至少要出现 2 个。
结构体变量的初始化
和其它类型变量一样,在初始化的时候可以指定初始值。
//
// main.c
// 08-结构体
//
// Created by VanZhang on 2022/5/17.
//
#include <stdio.h>
//定义一个 Books 结构体,类似于 iOS 中的 类
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
double rmb;
};
void test1(void) {
// insert code here...
/*
//定义 结构体,且初始化一个 该类型的结构体的临时变量
struct Books{
char title[50];
char author[50];
char subject[100];
int book_id;
double rmb;
} book = {"ObjC", "iOS", "C 语言", 666, 55.5};
*/
struct Books book = {"ObjC", "iOS", "C 语言", 666, 55.5};
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\nrmb: %f\n", book.title,
book.author, book.subject, book.book_id, book.rmb);
}
int main(int argc, const char * argv[]) {
test1();
return 0;
}
输出:
title : ObjC
author: iOS
subject: C 语言
book_id: 666
rmb: 55.500000
Program ended with exit code: 0
访问结构成员
#include <string.h>
struct Books2 {
char title[50];
char author[50];
char subject[100];
int book_id;
};
void test2(void){
// char tmp[50] = "abcd";
// printf("tmp:%s\n",tmp);
//访问 Books2 结构成员
struct Books2 Books2A;//声明 Books2A 类型为 Books2
struct Books2 Books2B;//声明 Books2B 类型为、 Books2
// Books2A.title[5] = "ABCD";//无法正确赋值?
//Books2A 详述//可以正确赋值
strcpy(Books2A.title, "C Plus");
strcpy(Books2A.author, "LuckyVan");
strcpy(Books2A.subject, "C");
Books2A.book_id = 666888;
//Books2B 详述
strcpy(Books2B.title, "C++ Plus");
strcpy(Books2B.author, "LuckyVan");
strcpy(Books2B.subject, "C++");
Books2B.book_id = 666999;
// 输出 Book1 信息
printf("Book 1 title : %s\n", Books2A.title);
printf("Book 1 author : %s\n", Books2A.author);
printf("Book 1 subject : %s\n", Books2A.subject);
printf("Book 1 book_id : %d\n", Books2A.book_id);
// 输出 Book2 信息
printf("Book 2 title : %s\n", Books2B.title);
printf("Book 2 author : %s\n", Books2B.author);
printf("Book 2 subject : %s\n", Books2B.subject);
printf("Book 2 book_id : %d\n", Books2B.book_id);
}
int main(int argc, const char * argv[]) {
// test1();
test2();
return 0;
}
输出:
Book 1 title : C Plus
Book 1 author : LuckyVan
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : LuckyVan
Book 2 subject : C++
Book 2 book_id : 666999
Program ended with exit code: 0
结构作为函数参数
//函数声明
void printBook(struct Books2 books2);
void test3(void){
// char tmp[50] = "abcd";
// printf("tmp:%s\n",tmp);
//访问 Books2 结构成员
struct Books2 Books2A;//声明 Books2A 类型为 Books2
struct Books2 Books2B;//声明 Books2B 类型为、 Books2
// Books2A.title[5] = "ABCD";//无法正确赋值?
//Books2A 详述//可以正确赋值
strcpy(Books2A.title, "C Plus");
strcpy(Books2A.author, "LuckyVan");
strcpy(Books2A.subject, "C");
Books2A.book_id = 666888;
//Books2B 详述
strcpy(Books2B.title, "C++ Plus");
strcpy(Books2B.author, "LuckyVan");
strcpy(Books2B.subject, "C++");
Books2B.book_id = 666999;
// 输出 Book1 信息
printf("Book 1 title : %s\n", Books2A.title);
printf("Book 1 author : %s\n", Books2A.author);
printf("Book 1 subject : %s\n", Books2A.subject);
printf("Book 1 book_id : %d\n", Books2A.book_id);
// 输出 Book2 信息
printf("Book 2 title : %s\n", Books2B.title);
printf("Book 2 author : %s\n", Books2B.author);
printf("Book 2 subject : %s\n", Books2B.subject);
printf("Book 2 book_id : %d\n", Books2B.book_id);
printf("\n\n\n");
//结构作为函数参数
printBook(Books2A);
printBook(Books2B);
}
void printBook(struct Books2 book) {
printf("Book title : %s\n", book.title);
printf("Book author : %s\n", book.author);
printf("Book subject : %s\n", book.subject);
printf("Book book_id : %d\n", book.book_id);
}
int main(int argc, const char * argv[]) {
// test1();
// test2();
test3();
// Test *t = [[Test alloc]init];
// NSLog(@"t:%@",t);
return 0;
}
输出:
Book 1 title : C Plus
Book 1 author : LuckyVan
Book 1 subject : C
Book 1 book_id : 666888
Book 2 title : C++ Plus
Book 2 author : LuckyVan
Book 2 subject : C++
Book 2 book_id : 666999
Book title : C Plus
Book author : LuckyVan
Book subject : C
Book book_id : 666888
Book title : C++ Plus
Book author : LuckyVan
Book subject : C++
Book book_id : 666999
Program ended with exit code: 0
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
struct_pointer->title;
例子:
//指向结构体的指针「结构体变量」
struct hp_objc_class{
size_t isa;
};
typedef struct hp_objc_class *HPClass;
struct HP_Person_IMPL{
HPClass class;
int count;
// ...
};
//定义指向结构的指针
void printBookZZ(struct Books2 *books2);
void test4(void){
//访问 Books2 结构成员
struct Books2 Books2A;//声明 Books2A 类型为 Books2
struct Books2 Books2B;//声明 Books2B 类型为 Books2
//Books2A 详述 ,将 CPlus copy 到 title 中
strcpy(Books2A.title, "C Plus");
strcpy(Books2A.author, "Nuha Ali");
strcpy(Books2A.subject, "C");
Books2A.book_id = 666888;
//Books2B 详述
strcpy(Books2B.title, "C++ Plus");
strcpy(Books2B.author, "LuckyVan");
strcpy(Books2B.subject, "C++");
Books2B.book_id = 666999;
//通过内存地址传递信息,为了查找结构变量的地址,请把 & 运算符放在结构名称的前面
printBookZZ(&Books2A);
printBookZZ(&Books2B);
}
/**
* 为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:
* @param book
*/
void printBookZZ(struct Books2 *book) {
printf("Book -> title : %s\n", book->title);
printf("Book -> author : %s\n", book->author);
printf("Book -> subject : %s\n", book->subject);
printf("Book -> book_id : %d\n", book->book_id);
}
int main(int argc, const char * argv[]) {
// test1();
// test2();
// test3();
// Test *t = [[Test alloc]init];
// NSLog(@"t:%@",t);
test4();
return 0;
}
位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位
。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。
为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为”位域”或”位段”。
所谓”位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。
- 每个域有一个域名,允许在程序中按域名进行操作。
- 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
典型的实例:
- 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
- 读取外部文件格式——可以读取非标准的文件格式。
位域定义:
struct 位域结构名称{
位域列表
};
位域列表的形式为:
类型说明符 位域名:位域长度
例如:
//位域结构体
struct status1{
int a;
int b;
int c;
};
struct status2{
int a:1;
int b:1;
int c:1;
};
void test5(void){
struct status1 s;
struct status2 s2;
printf("status:%lu\n",sizeof(s));
printf("status2:%lu\n",sizeof(s2));
}
int main(int argc, const char * argv[]) {
// test1();
// test2();
// test3();
// Test *t = [[Test alloc]init];
// NSLog(@"t:%@",t);
// test4();
test5();
return 0;
}
struct bean {
int a:8;
int b:4;
int c:4;
}data;
说明 data
为 bean类型的 变量,共占 2个字节。其中位域 a 占 8 位,位域 b 占 4 位,位域 c 占 4 位。「1个字节8个位置:0000 0000」
注意:
- 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bean{
unsigned a:4;
unsigned :4;//空域
unsigned b:4;//从下一个单元开始存放
unsigned c:4;
}
在这个位域定义中共占用 2 个字节,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。
- 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。
- 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct k{ int a:1; int :2; /* 该 2 位不能使用 */ int b:3; int c:2; };
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
位域的使用
位域的使用和结构成员的使用相同,其一般形式为:
位域变量名.位域名
位域变量名->位域名
位域允许用各种格式输出。
例子:
void main(){
//位域
struct bs {
unsigned int a:1;//占 位段a 1 位
unsigned b:6;//占 位段b 3 位
unsigned c:7;//占 位段c 4 位
} bit, *pbit;
// 给位域赋值(应注意赋值不能超过该位域的允许范围)
bit.a = 1; //以二进制 1 表示 1 bit位
bit.b = 50;//以二进制 110010 表示 6 bit位
bit.c = 100;//以二进制 1100100 标志 7 bit位
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); // 以整型量格式输出三个域的内容
pbit=&bit; //把位域变量 bit 的地址送给指针变量 pbit
pbit->a=0; //用指针方式给位域 a 重新赋值,赋为 0
pbit->b&=3; //使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 50,与 3 作按位与运算的结果为 2(110010&011=010,十进制值为 2)
pbit->c|=1; //使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 (1100100 | 0000001)= 1100101 = 101
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); //用指针方式输出了这三个域的值
}
输出:
1,50,100
0,2,101
2. 共用体
共用体 是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
定义共同体
为了定义共用体,您必须使用 union 语句,方式与定义结构类似
。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
union [union tag]
{
member definition;
member definition;
...
member definition;
}[one or more union variables];
union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f;
或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{
int i;
float f;
char str[20];
}
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。
您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
共用体占用的内存应足够存储共用体中最大的成员。
例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
下面的实例将显示上面的共用体占用的总内存大小:
union Data {
int i;
float f;
char str[20];
};
void main(){
union Data data;
printf("Memory size occupied by data: %d\n", sizeof(data));
}
输出:
Memory size occupied by data: 20
访问共同体成员
为了访问共用体的成员,我们使用成员访问运算符(.) 。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。您可以使用 union 关键字来定义共用体类型的变量。下面的实例演示了共用体的用法:
union Data {
int i;
float f;
char str[20];
};
void main() {
//1. 访问共同体 no
data.i = 10;
data.f = 1314.520;
strcpy(data.str,"C/C++");
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
printf("\n\n\n");
//2. 访问共同体 yes
data.i = 10;
printf( "data.i : %d\n", data.i);
data.f = 1314.520;
printf( "data.f : %f\n", data.f);
strcpy(data.str,"C/C++");
printf( "data.str : %s\n", data.str);
}
输出:
data.i : 725823299
data.f : 0.000000
data.str : C/C++
data.i : 10
data.f : 1314.520020
data.str : C/C++
在这里,我们可以看到上面注释 1 共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。我们看注释 2 ,这次我们在同一时间只使用一个变量成员,所以都能完好输出。
3. 位域
参考 17.(位域的介绍)
4. typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。
下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但您也可以使用小写字母,如下:
typedef unsigned char byte;
您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:
typedef struct Books {
char title[50];
char author[50];
char subject[50];
int book_id;
} Book;
#define TRUE 1
#define FALSE 0
void main(){
Book book;
strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);
printf( "TRUE 的值: %d\n", TRUE);
printf( "FALSE 的值: %d\n", FALSE);
}
输出:
书标题 : C 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
TRUE 的值: 1
FALSE 的值: 0
typedef vs define
define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称, #define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的, #define 语句是由预编译器进行处理的。
例子可以参考上面是 #define 使用。
三、C 语言核心语法|输入&输出、文件读写
1. 输入 & 输出
当我们提到输入时,这意味着要向程序填充一些数据。输入可以是以文件的形式或从命令行中进行。C 语言提供了一系列内置的函数来读取给定的输入,并根据需要填充到程序中。
当我们提到输出时,这意味着要在屏幕上、打印机上或任意文件中显示一些数据。C 语言提供了一系列内置的函数来输出数据到计算机屏幕上和保存数据到文本文件或二进制文件中。
标准输出
C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 您的屏幕 |
文件指针是访问文件的方式,本节将讲解如何从屏幕读取值以及如何把结果输出到屏幕上。
C 语言中的 I/O (输入/输出) 通常使用 printf()
和 scanf()
两个函数。
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)。
例子:
void main(){
float f;
printf("Enter a float number: \n");
// %f 匹配浮点型数据
scanf("%f",&f);
printf("Value = %f", f);
}
输出:
Enter a float number:
12.3
Value = 12.300000
getchar()&putchar() 函数
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
void main(){
int c;
printf( "\nEnter a value :");
//函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
c = getchar( );
printf( "\nYou entered: ");
//读取第一个字符
putchar( c );
}
输出:
Enter a value :abcdef
You entered: a
gets() & puts() 函数
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
void main(){
char str[100];
printf( "\nEnter a value :");
//读取一行
gets( str );
printf( "\nYou entered: ");
puts( str );
}
输出:
Enter a value :大家好,才是真的好!
You entered: 大家好,才是真的好!
2. 文件读写
上一节我们讲解了 C 语言处理的标准输入和输出设备。本章我们将介绍 C 程序员如何创建、打开、关闭文本文件或二进制文件。
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。本章将讲解文件管理的重要调用。
打开文件
您可以使用 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"
关闭文件
为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
写入文件
下面是把字符串写入到流中的最简单的函数:
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, …) 函数来写把一个字符串写入到文件中。尝试下面的实例:
void main(){
//定义一个空指针文件
FILE *fp = NULL;
//打开文件,打开一个文本文件,允许读写文件。
// 如果文件不存在,则会创建一个新文件。
// 读取会从文件的开头开始,写入则只能是追加模式。
fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","a+");
fprintf(fp, " fprintf 我是添加进来的1\n");
fprintf(fp, "fprintf 我是添加进来的2\n");
fputs("fputs 我是添加进来的1\n", fp);
fputs("fputs 我是添加进来的2\n", fp);
fclose(fp);
}
读取文件
下面是从文件读取单个字符的最简单的函数:
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, …) 函数来从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。
例子:
void main(){
FILE *fp = NULL;
//读取文件
char buff[255];
fp = fopen("/Users/devyk/Data/ClionProjects/NDK_Sample/README.md","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);
}