紫薇星上的C语言(4)

第一部分我们整理了C语言的基本特点、环境、语法等详情可戳👉紫薇星上的C语言(1)

第二部分我们整理了C语言的运算符、判断、循环与数组等详情可戳👉紫薇星上的C语言(2)

第三部分我们整理了C语言的指针、字符串等详情可戳👉紫薇星上的C语言(3)

这一部分我们将会整理C语言的结构体,文件读写等知识点,这些内容基本就是C语言的基础啦~胜利的曙光就在前面!冲!


14.枚举

枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum DAY{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

需要注意的是:

  1. 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
  2. 可以在定义枚举类型时改变枚举元素的值:
enum season {spring, summer=3, autumn, winter};
/*
没有指定值的枚举元素,其值为前一元素加 1。
也就说 spring 的值为 0,summer 的值为 3,
autumn 的值为 4,winter 的值为 5
*/

我们可以通过以下三种方式来定义枚举变量:

  • 先定义枚举类型,再定义枚举变量

enum DAY{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

enum DAY day;
  • 定义枚举类型的同时定义枚举变量
enum DAY{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
  • 省略枚举名称,直接定义枚举变量
enum{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

示例:

#include<stdio.h>
 
enum DAY{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
 
int main(){
    enum DAY day;
    day = WED;
    printf("%d",day);
    return 0;
}

这段代码被编译和执行后,结果为:  3

在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。不过在一些特殊的情况下遍历是可以实现的,条件是枚举类型必须连续 。以下实例使用 for 来遍历枚举的元素

#include<stdio.h>
 
enum DAY{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

int main(){
    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }
}

这段代码被编译和执行后,结果如下:

枚举元素:1 
枚举元素:2 
枚举元素:3 
枚举元素:4 
枚举元素:5 
枚举元素:6 
枚举元素:7

以下枚举类型不连续,这种枚举无法遍历:

enum{
    ENUM_0,
    ENUM_10 = 10,
    ENUM_11
};

枚举在 switch 中的使用:

#include <stdio.h>
#include <stdlib.h>
int main(){
 
    enum color { red=1, green, blue };
 
    enum  color favorite_color;
 
    /* ask user to choose color */
    printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
    scanf("%d", &favorite_color);
 
    /* 输出结果 */
    switch (favorite_color){
    case red:
        printf("你喜欢的颜色是红色");
        break;
    case green:
        printf("你喜欢的颜色是绿色");
        break;
    case blue:
        printf("你喜欢的颜色是蓝色");
        break;
    default:
        printf("你没有选择你喜欢的颜色");
    }
 
    return 0;
}

如果我们输入1,那么这段代码被编译和执行后,结果如下:

请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 1
你喜欢的颜色是红色

15.结构体

数组允许定义可存储相同类型数据项的变量,结构是 C 语言中另一种用户自定义的可用数据类型,它允许存储不同类型的数据项。

  • 定义结构体

结构用于表示一条记录,假设想要跟踪图书馆中书本的动态,可能需要跟踪每本书的下列属性:Title、Author、Subject、Book ID等。为了定义结构,必须使用 struct 语句,struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

注意:

  1. tag 是结构体标签。
  2. member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。
  3. variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。

下面以刚才所说的Book 结构示例:

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。示例:

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct {
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

在上面的代码中,第一个和第二声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的,结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。示例:

//此结构体的声明包含了其他的结构体
struct COMPLEX{
    char string[100];
    struct SIMPLE a;
};
 
//此结构体的声明包含了指向自己类型的指针
struct NODE{
    char string[100];
    struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:

struct B;    //对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A{
    struct B *partner;
    //other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B{
    struct A *partner;
    //other members;
};
  • 结构体变量初始化

和其它类型变量一样,对结构体变量可以在定义时指定初始值。示例:

#include <stdio.h>
 
struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "ZiJun", "编程语言", 123456};
 
int main(){
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", 
book.title, book.author, book.subject, book.book_id);
}

这段代码被编译和执行后,结果如下:

title : C 语言
author: ZiJun
subject: 编程语言
book_id: 123456
  • 访问结构体成员

为了访问结构的成员,我们使用成员访问运算符(.)成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号,可以使用 struct 关键字来定义结构类型的变量。下面的示例演示了结构体的用法:

#include <stdio.h>
#include <string.h>
 
struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main(){
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printf( "Book 1 title : %s\n", Book1.title);
   printf( "Book 1 author : %s\n", Book1.author);
   printf( "Book 1 subject : %s\n", Book1.subject);
   printf( "Book 1 book_id : %d\n", Book1.book_id);
 
   /* 输出 Book2 信息 */
   printf( "Book 2 title : %s\n", Book2.title);
   printf( "Book 2 author : %s\n", Book2.author);
   printf( "Book 2 subject : %s\n", Book2.subject);
   printf( "Book 2 book_id : %d\n", Book2.book_id);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
  • 结构体作为函数参数

可以把结构作为函数参数,传参方式与其他类型的变量或指针类似,可以使用上面实例中的方式来访问结构变量:

#include <stdio.h>
#include <string.h>
 
struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books book );
int main(){
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printBook( Book1 );
 
   /* 输出 Book2 信息 */
   printBook( Book2 );
 
   return 0;
}
void printBook( struct Books 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);
}

上面这段代码不同的地方在于它将printf()函数单独拿出来放在printBook()函数中,而结构体则作为函数参数传递给printBook()函数。当上面的代码被编译和执行时,它会产生下列结果:

Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
  • 指向结构体的指针

昨天学习了指针后,还可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如:

struct Books *struct_pointer;

现在可以在上述定义的指针变量中存储结构变量的地址,为了查找结构变量的地址要把 & 运算符放在结构名称的前面,如:

struct_pointer = &Book1;

为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如:

struct_pointer->title;

让我们使用结构指针来重写上面的实例,这将有助于理解结构指针的概念:

#include <stdio.h>
#include <string.h>
 
struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books *book );
int main(){
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 通过传 Book1 的地址来输出 Book1 信息 */
   printBook( &Book1 );
 
   /* 通过传 Book2 的地址来输出 Book2 信息 */
   printBook( &Book2 );
 
   return 0;
}
void printBook( struct Books *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);
}

上面这段代码唯一不同的地方就是函数参数由结构体变量变为了结构体指针,指向所在地址中的结构体。当上面的代码被编译和执行时,它会产生下列结果:

Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700

16.共用体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

  • 定义共用体

为了定义共用体必须使用 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 类型的变量可以存储一个整数、一个浮点数或者一个字符串,这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据,可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。

共用体占用的内存应足够存储共用体中最大的成员。例如在上面的代码中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。下面的示例将显示上面的共用体占用的总内存大小:

#include <stdio.h>
#include <string.h>
 
union Data{
   int i;
   float f;
   char  str[20];
};
 
int main(){
   union Data data;        
 
   printf( "Memory size occupied by data : %d\n", sizeof(data));
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Memory size occupied by data : 20
  • 访问共用体成员

为了访问共用体的成员,我们使用成员访问运算符(.)成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号,可以使用 union 关键字来定义共用体类型的变量。下面的示例演示了共用体的用法:

#include <stdio.h>
#include <string.h>
 
union Data{
   int i;
   float f;
   char  str[20];
};
 
int main(){
   union Data data;        
 
   data.i = 10;
   data.f = 220.5;
   strcpy( data.str, "C Programming");
 
   printf( "data.i : %d\n", data.i);
   printf( "data.f : %f\n", data.f);
   printf( "data.str : %s\n", data.str);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming

在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:

#include <stdio.h>
#include <string.h>
 
union Data{
   int i;
   float f;
   char  str[20];
};
 
int main(){
   union Data data;        
 
   data.i = 10;
   printf( "data.i : %d\n", data.i);
   
   data.f = 220.5;
   printf( "data.f : %f\n", data.f);
   
   strcpy( data.str, "C Programming");
   printf( "data.str : %s\n", data.str);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

data.i : 10
data.f : 220.500000
data.str : C Programming

在这里,所有的成员都能完好输出,因为同一时间只用到一个成员。

17.位域

如果程序的结构中包含多个开关量,只有 TRUE/FALSE 变量,如下:

struct{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status;

这种结构需要 8 字节的内存空间,但实际上在每个变量中,我们只存储 0 或 1,在这种情况下C 语言提供了一种更好的利用内存空间的方式,如果在结构内使用这样的变量,可以定义变量的宽度来告诉编译器将只使用这些字节。例如,上面的结构可以重写成:

struct{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status;

现在,上面的结构中,status 变量将占用 4 个字节的内存空间,但是只有 2 位被用来存储值,如果用了 32 个变量,每一个变量宽度为 1 位,那么 status 结构将使用 4 个字节,但只要再多用一个变量,如果使用了 33 个变量,那么它将分配内存的下一段来存储第 33 个变量,这个时候就开始使用 8 个字节。让我们看看下面的实例来理解这个概念:

#include <stdio.h>
#include <string.h>
 
/* 定义简单的结构 */
struct{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status1;
 
/* 定义位域结构 */
struct{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status2;
 
int main( ){
   printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
   printf( "Memory size occupied by status2 : %d\n", sizeof(status2));
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Memory size occupied by status1 : 8
Memory size occupied by status2 : 4

所谓位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数,每个域有一个域名,允许在程序中按域名进行操作,这样就可以把几个不同的对象用一个字节的二进制位域来表示。

  • 位域声明

在结构内声明位域的形式如下:

struct{
  type [member_name] : width ;
};

下面是有关位域中变量元素的描述:

元素描述
type只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
member_name位域的名称。
width位域中位的数量。宽度必须小于或等于指定类型的位宽度。

带有预定义宽度的变量被称为位域,位域可以存储多于 1 位的数,如果需要一个变量来存储从 0 到 7 的值,可以定义一个宽度为 3 位的位域,如下:

struct{
  unsigned int age : 3;
} Age;

上面的结构定义指示 C 编译器 age 变量将只使用 3 位来存储这个值,如果试图使用超过 3 位则无法完成,示例:

#include <stdio.h>
#include <string.h>
 
struct{
  unsigned int age : 3;
} Age;
 
int main(){
   Age.age = 4;
   printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 7;
   printf( "Age.age : %d\n", Age.age );
 
   Age.age = 8; // 二进制表示为 1000 有四位,超出
   printf( "Age.age : %d\n", Age.age );
 
   return 0;
}

当上面的代码被编译时,它会带有警告,当上面的代码被执行时,它会产生下列结果:

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0

再例如:

struct bs{
    int a:8;
    int b:2;
    int c:6;
}data;

说明 data 为 bs 变量,共占两个字节,其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位。再来看一个例子:

struct packed_struct {
  unsigned int f1:1;
  unsigned int f2:1;
  unsigned int f3:1;
  unsigned int f4:1;
  unsigned int type:4;
  unsigned int my_int:9;
} pack;

在这里,packed_struct 包含了 6 个成员:四个 1 位的标识符 f1...f4、一个 4 位的 type 和一个 9 位的 my_int。

对于位域的定义有以下几点说明:

  1. 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
    struct bs{
        unsigned a:4;
        unsigned  :4;    /* 空域 */
        unsigned b:4;    /* 从下一单元开始存放 */
        unsigned c:4
    }
    在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。
  2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。
  3. 位域可以是无名位域,这时它只用来作填充或调整位置,无名的位域是不能使用的。例如:
struct k{
    int a:1;
    int  :2;    /* 该 2 位不能使用 */
    int b:3;
    int c:2;
};

从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。

  • 位域的使用

位域的使用和结构成员的使用相同,其一般形式为:

位域变量名.位域名
位域变量名->位域名

位域允许用各种格式输出,请看下面的实例:

void main(){
    struct bs{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit,*pbit;

    bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */

    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 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
    pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */

    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
}

上例程序中定义了位域结构 bs,三个位域为 a、b、c。说明了 bs 类型的变量 bit 和指向 bs 类型的指针变量 pbit。这表示位域也是可以使用指针的。

18.typedef

C 语言提供了 typedef 关键字,可以使用它来为类型取一个新的名字,下面的实例为单字节数字定义了一个术语 BYTE

typedef unsigned char BYTE;

在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:

BYTE  b1, b2;

一般定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写,但也可以使用小写字母,如下:

typedef unsigned char byte;

也可以使用 typedef 来为用户自定义的数据类型取一个新的名字,可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:

#include <stdio.h>
#include <string.h>
 
typedef struct Books{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;
 
int main(){
   Book book;
 
   strcpy( book.title, "C 教程");
   strcpy( book.author, "ZiJun"); 
   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);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

书标题 : C 教程
书作者 : ZiJun
书类目 : 编程语言
书 ID : 12345
  • typedef 与 #define 的区别

#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

  1. typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如可以定义 1 为 ONE。
  2. typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

下面是 #define 的最简单的用法:

#include <stdio.h>
 
#define TRUE  1
#define FALSE 0
 
int main(){
   printf( "TRUE 的值: %d\n", TRUE);
   printf( "FALSE 的值: %d\n", FALSE);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

TRUE 的值: 1
FALSE 的值: 0

19.文件读写

一个文件,无论是文本文件还是二进制文件,都代表了一系列的字节,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, ...) 函数来写把一个字符串写入到文件中。尝试下面的实例:

#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 目录,如果不存在该目录,则需要在计算机上先创建该目录。/tmp 一般是 Linux 系统上的临时目录,如果在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmp、D:\tmp等。

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

  • 读取文件

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

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, ...) 函数来从文件中读取字符串,但是在遇到第一个空格和换行符时会停止读取。

#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() 完整地读取第二行。

  • 二进制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);

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


这一部分整理了枚举、结构体、共用体、位域和文件读写等知识点,学会文件读写后,C语言的基础部分就差不多结束了。

我个人觉得,C语言是其它语言的基础,学会了C语言就能更好地学习其他的语言!明天将会把一些比较零散的知识点全部整理,这个C语言系列也就快到尾声了。明天见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值