【C语言第七章Files & Error Handling】

Working With Files

Accessing Files

An external file can be opened, read from, and written to in a C program. For these operations, C includes the FILE type for defining a file stream. The file stream keeps track of where reading and writing last occurred.

The stdio.h library includes file handling functions:
FILE Typedef for defining a file pointer.

fopen(filename, mode) Returns a FILE pointer to file filename which is opened using mode. If a file cannot be opened, NULL is returned.
Mode options are:
r open for reading (file must exist)
w open for writing (file need not exist文件不需要存在)
- a open for append (file need not exist)
- r+ open for reading and writing from beginning
- w+ open for reading and writing, overwriting file
- a+ open for reading and writing, appending to file

fclose(fp) Closes file opened with FILE fp, returning 0 if close was successful. EOF (end of file) is returned if there is an error in closing.

The following program opens a file for writing and then closes it:

#include <stdio.h>

int main() {  
  FILE *fptr;
  
  fptr = fopen("myfile.txt", "w");
  if (fptr == NULL) {
    printf("Error opening file.");
    return -1;
  }
  fclose(fptr);
  return 0;
}

When a string literal is used to specify a filename, the escape sequence \ indicates a single backslash. In this program, if there is an error when opening the file, a -1 error code is returned to the system. Error handling is explained in a future lesson.
Closing a file when you are done using it is a good programming practice.

instead of using “printf (“Error bla bla”);” use “perror(” Error");" to know the reason of the error. (output example: Error: No such file or directory). The error would only work, in this case, if there is no file that exists and that a mode like r (read-only) is used to open the file. When you use any mode that doesn’t require files to exist, the error won’t pop up because a file would be created anyway.

When a file stream is opened with ‘w’ flag, if the file exists it is overwritten, so take care

Reading from a File

The stdio.h library also includes functions for reading from an open file. A file can be read one character at a time or an entire string can be read into a character buffer, which is typically a char array used for temporary storage.

fgetc(fp) Returns the next character from the file pointed to by fp. If the end of the file has been reached, then EOF is returned.

fgets(buff, n, fp) Reads n-1 characters from the file pointed to by fp and stores the string in buff. A NULL character ‘\0’ is appended as the last character in buff. If fgets encounters a newline character or the end of file before n-1 characters is reached, then only the characters up to that point are stored in buff.

fscanf(fp, conversion_specifiers, vars) Reads characters from the file pointed to by fp and assigns input to a list of variable pointers vars using conversion_specifiers. As with scanf, fscanf stops reading a string when a space or newline is encountered.

The following program demonstrates reading from a file:

#include <stdio.h>

int main() {  
  FILE *fptr;
  int c, stock;
  char buffer[200], item[10];
  float price;

  /* myfile.txt: Inventory\n100 Widget 0.29\nEnd of List */

  fptr = fopen("myfile.txt", "r");

  fgets(buffer, 20, fptr);	/* read a line */
  printf("%s\n", buffer);

  fscanf(fptr, "%d%s%f", &stock, item, &price); /* read data */
  printf("%d  %s  %4.2f\n", stock, item, price);

  while ((c = getc(fptr)) != EOF) /* read the rest of the file */
    printf("%c", c);

  fclose(fptr);
  return 0;
}

The gets() function reads up until the newline. fscanf() reads data according to conversion specifiers. And then the while loop reads one character at a time until the end of file. Checking for a problem when opening the file (a NULL pointer) was left out to shorten the example.

Writing to a File

The stdio.h library also includes functions for writing to a file. When writing to a file, newline characters ‘\n’ must be explicitly added.

fputc(char, fp) Writes character char to the file pointed to by fp.

fputs(str, fp) Writes string str to the file pointed to by fp.

fprintf(fp, str, vars) Prints string str to the file pointed to by fp. str can optionally include format specifiers and a list of variables vars.

The following program demonstrates writing to a file:

#include <stdio.h>

int main() {
  FILE *fptr;
  char filename[50];
  char c;

  printf("Enter the filename of the file to create: ");
  gets(filename);
  fptr = fopen(filename, "w");

  /* write to file */
  fprintf(fptr, "Inventory\n");
  fprintf(fptr, "%d %s %f\n", 100, "Widget", 0.29);
  fputs("End of List", fptr);

  fclose(fptr);

  /* read the file contents */
  fptr = fopen(filename, "r");
  while ((c = getc(fptr)) != EOF)
    printf("%c", c);
  fclose(fptr);
  return 0;
}

The “w” argument defines “writing mode” for the fopen function.
Here a simple tutorial on how to work with this in your system: 1. Create a file (e.g. writeFile.c), copy the code, replace ‘gets’ call with ‘fgets(filename,50, stdin);’ (gets function shouldn’t be available #BufferOverflows) 2. Compile your code with GNU C Compiler by typing ‘gcc -Wall -o fileprogram writeFile.c’ in your terminal window at the directory of your source-code file. 3. Run the program with ‘./fileprogram’ in your current terminal window.

Binary File I/O

Writing only characters and strings to a file can become tedious when you have an array or structure. To write entire blocks of memory to a file, there are the following binary functions:

Binary file mode options for the fopen() function are:
- rb open for reading (file must exist)
- wb open for writing (file need not exist)
- ab open for append (file need not exist)
- rb+ open for reading and writing from beginning
- wb+ open for reading and writing, overwriting file
- ab+ open for reading and writing, appending to file

fwrite(ptr, item_size, num_items, fp) Writes num_items items of item_size size from pointer ptr to the file pointed to by file pointer fp.

fread(ptr, item_size, num_items, fp) Reads num_items items of item_size size from the file pointed to by file pointer fp into memory pointed to by ptr.

fclose(fp) Closes file opened with file fp, returning 0 if close was successful. EOF is returned if there is an error in closing.

feof(fp) Returns 0 when the end of the file stream has been reached.

The following program demonstrates writing to and reading from binary files:

#include <stdio.h>

int main() {
  FILE *fptr;
  int arr[10];
  int x[10];
  int k;

  /* generate array of numbers */
  for (k = 0; k < 10; k++)
    arr[k] = k;

  /* write array to file */
  fptr = fopen("datafile.bin", "wb");
  fwrite(arr, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
  fclose(fptr);

  /* read array from file */
  fptr = fopen("datafile.bin", "rb");
  fread(x, sizeof(arr[0]), sizeof(arr)/sizeof(arr[0]), fptr);
  fclose(fptr);

  /* print array */
  for (k = 0; k < 10; k++)
    printf("%d", x[k]);
  return 0;
}

This program wrote an array of ints to a file, but an array of structures could just as easily have been written to a file. Notice that the item size and number of items were determined by using the size of an element and the size of the entire variable.

File extensions alone do not determine the format of data in a file, but they are useful for indicating the type of data to expect. For example, a .txt extension indicates a text file, .bin is for binary data, .csv indicates comma separated values, and .dat is a data file.

I found out that the order of the second and third parameters did not matter. Behind the scenes these two values are multiplied. So, you can write your structure to binary file this way: fwrite(&myStruct, 1, sizeof(myStruct), filePtr); or : fwrite(&myStruct, sizeof(myStruct), 1, filePtr);

Controlling the File Pointer

There are functions in stdio.h for controlling the location of the file pointer in a binary file:
ftell(fp) Returns a long int value corresponding to the fp file pointer position in number of bytes from the start of the file.

fseek(fp, num_bytes, from_pos) Moves the fp file pointer position by num_bytes bytes relative to position from_pos, which can be one of the following constants:
SEEK_SET start of file
SEEK_CUR current position
SEEK_END end of file

The following program reads a record from a file of structures:

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

typedef struct {
  int id;
  char name[20];
} item;

int main() { 
  FILE *fptr;
  item first, second, secondf;

  /* create records */
  first.id = 10276;
  strcpy(first.name, "Widget");
  second.id = 11786;
  strcpy(second.name, "Gadget");
  
  /* write records to a file */
  fptr = fopen("info.dat", "wb");
  fwrite(&first, 1, sizeof(first), fptr);
  fwrite(&second, 1, sizeof(second), fptr);
  fclose(fptr); 

  /* file contains 2 records of type item */
  fptr = fopen("info.dat", "rb");

  /* seek second record */
  fseek(fptr, 1*sizeof(item), SEEK_SET);
  fread(&secondf, 1, sizeof(item), fptr);
  printf("%d  %s\n", secondf.id, secondf.name);
  fclose(fptr);
  return 0;
}

This program wrote two item records to a file. To read just the second record, fseek() moved the file pointer to 1*sizeof(item) bytes from the start of the file. For example, if you wanted to move the pointer to the fourth record, then you seek to 3*sizeof(item) from the beginning of the file (SEEK_SET).

Error Handling

Exception Handling

Central to good programming practices is using error handling techniques. Even the most solid coding skills may not keep a program from crashing should you forget to include exception handling.

An exception is any situation that causes your program to stop normal execution. Exception handling, also called error handling, is an approach to processing runtime errors.

C does not explicitly support exception handling, but there are ways to manage errors:
- Write code to prevent the errors in the first place. You can’t control user input, but you can check to be sure that the user entered valid input. When performing division, take the extra step to ensure that division by 0 won’t occur.
- Use the exit statement to gracefully end program execution. You may not be able to control if a file is available for reading, but you don’t need to allow the problem to crash your program.

Use errno, perror(), and strerror() to identify errors through error codes.

The exit Command

The exit command immediately stops the execution of a program and sends an exit code back to the calling process. For example, if a program is called by another program, then the calling program may need to know the exit status.

Using exit to avoid a program crash is a good practice because it closes any open file connections and processes.

You can return any value through an exit statement, but 0 for success and -1 for failure are typical. The predefined stdlib.h macros EXIT_SUCCESS and EXIT_FAILURE are also commonly used.

#include <stdio.h>
#include <stdlib.h>
int main() {
  int x = 10;
  int y = 0;

  if (y != 0)
    printf("x / y = %d", x/y);
  else {
    printf("Divisor is 0. Program exiting.");
    exit(EXIT_FAILURE);
  }
  
  return 0;
}

what is the difference between exit() and return ?
https://stackoverflow.com/questions/17383015/difference-between-return-0-and-exit-0 “return exits from the function while exit exits from the program.” You can use exit() inside any function and terminate the main() function.

exit(EXIT_SUCCESS) is same as exit(0), exit(-256) exit(EXIT_FAILURE) is same as exit(-1), exit(-257)

why is -1 used for failure exit? just arbitrary choice?
Tradition from UNIX
actually if you’d use EXIT_FAILURE on Windows it will be 1. It really does depend on the system. This constant is used throughout the operating system, therefore each system inplements success and failure code differently. (btw, Code::Blocks IDE on Windows can preview what are the values of the constants).
-1 is not typical for EXIT_FAILURE. Most UNIX-like systems have an unsigned 8-bit range for error codes (0 - 255) so an error code of -1 would result in 255 due to overflow. The typical value for EXIT_FAILURE is 1.

Using Error Codes

Using errno

Some library functions, such as fopen(), set an error code when they do not execute as expected. The error code is set in a global variable named errno, which is defined in the errno.h header file. When using errno you should set it to 0 before calling a library function.

To output the error code stored in errno, you use fprintf to print to the stderr file stream, the standard error output to the screen. Using stderr is a matter of convention and a good programming practice.

You can output the errno through other means, but it will be easier to keep track of your exception handling if you only use stderr for error messages.

To use errno, you need to declare it with the statement extern int errno; at the top of your program (or you can include the errno.h header file).

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

extern int errno;

int main() {
  FILE *fptr;

  errno = 0;
  fptr = fopen("c:\\nonexistantfile.txt", "r");
  if (fptr == NULL) {
    fprintf(stderr, "Error opening file. Error code: %d\n", errno);
    exit(EXIT_FAILURE);
  }

  fclose(fptr);
  return 0;
}

Extern is a truly global variable that is accessible beyond the scope of its declaration. By declaring a variable “extern” we let the compiler know we want to access a variable being defined in another program or file, and hence not to allocate any memory for this one. Instead, it simply points to the global variable defined in the other file (with the same definition).
Extern is a truly global variable that is accessible beyond the scope of its declaration. By declaring a variable “extern” we let the compiler know we want to access a variable being defined in another program or file, and hence not to allocate any memory for this one. Instead, it simply points to the global variable defined in the other file (with the same definition).

The perror and strerror Functions

When a library function sets errno, a cryptic(神秘的) error number is assigned. For a more descriptive message about the error, you can use perror(). You can also obtain the message using strerror() in the string.h header file, which returns a pointer to the message text.

perror() must include a string that will precede(先于) the actual error message. Typically, there is no need for both perror() and strerror() for the same error, but both are used in the program below for demonstration purposes:

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

int main() {
  FILE *fptr;

  errno = 0;
  fptr = fopen("c:\\nonexistantfile.txt", "r");
  if (fptr == NULL) {
    perror("Error");
    fprintf(stderr, "%s\n", strerror(errno));
    exit(EXIT_FAILURE);
  }

  fclose(fptr);
  return 0;
}

There are more than a hundred error codes. Use these statements to list them:

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

extern int errno;

int main() {
    for (int x = 0; x < 135; x++)
      fprintf(stderr, "%d: %s\n", x, strerror(x));
}

EDOM and ERANGE Error Codes

Some of the mathematical functions in the math.h library set errno to the defined macro value EDOM when a domain(定义域) is out of range.
Similarly, the ERANGE macro value is used when there is a range(值域) error.

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

int main() { 
  float k = -5;
  float num = 1000;
  float result;

  errno = 0;
  result = sqrt(k);
  if (errno == 0)
    printf("%f ", result);
  else if (errno == EDOM)
    fprintf(stderr, "%s\n", strerror(errno));

  errno = 0;
  result = exp(num);
  if (errno == 0)
    printf("%f ", result);
  else if (errno == ERANGE)
    fprintf(stderr, "%s\n", strerror(errno));

  return 0;
}

The feof and ferror Functions

In addition to checking for a NULL file pointer and using errno, the feof() and ferror() functions can be used for determining file I/O errors:
feof(fp) Returns a nonzero value if the end of stream has been reached, 0 otherwise. feof also sets EOF.
ferror(fp) Returns a nonzero value if there is an error, 0 for no error.

The following program incorporates(包含) several exception handling techniques:

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

int main() {  
  FILE *fptr;
  int c;

  errno = 0;
  fptr = fopen("c:\\myfile.txt", "r");
  if (fptr == NULL) {
    fprintf(stderr, "Error opening file. %s\n", strerror(errno));
    exit(EXIT_FAILURE);
  }

  while ((c = getc(fptr)) != EOF) /* read the rest of the file */
    printf("%c", c);

  if (ferror(fptr)) {
    printf("I/O error reading file.");
    exit(EXIT_FAILURE);
  }
  else if (feof(fptr))
    printf("End of file reached.");

  fclose(fptr);
  return 0;
}

Program output will vary. But if the file opens properly and the program completes reading the entire file, then the following message displays: “End of file reached.”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值