第一项
C 输入 & 输出
输入可以是以文件的形式或从命令行中进行。C 语言提供了一系列内置的函数来读取给定的输入,并根据需要填充到程序中。
输出时,这意味着要在屏幕上、打印机上或任意文件中显示一些数据。C 语言提供了一系列内置的函数来输出数据到计算机屏幕上和保存数据到文本文件或二进制文件中。
标准文件
C 语言把所有的设备都当作文件。设备(比如显示器)被处理的方式与文件相同。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 您的屏幕 |
以上文件自动打开
文件指针是访问文件的方式,本节将讲解如何从键盘上读取值以及如何把结果输出到屏幕上。
C 语言中的 I/O (输入/输出) 通常使用 printf() 和 scanf() 两个函数。
scanf() 函数用于从标准输入(键盘)读取并格式化, printf() 函数发送格式化输出到标准输出(屏幕)。
eg:
#include <stdio.h>
int main()
{
printf("ZH_edifier");
return 0;
}
ZH_edifier
详解
- C 语言程序都需要包含 main() 函数。 代码从 main() 函数开始执行。
- printf() 用于格式化输出到屏幕。printf() 函数在 "stdio.h" 头文件中声明。
- stdio.h 是一个头文件 (标准输入输出头文件) and #include 是一个预处理命令,用来引入头文件。
- return 0; 语句用于表示退出程序。
%d 格式化输出整数
#include <stdio.h>
int main()
{
int testInteger = 5;
printf("Number = %d", testInteger);
return 0;
}
Number = 5
在 printf() 函数的引号中使用 "%d" (整型) 来匹配整型变量 testInteger 并输出到屏幕。
%f 格式化输出浮点型数据
#include <stdio.h>
int main()
{
float f;
printf("Enter a number: ");
// %f 匹配浮点型数据
scanf("%f",&f);
printf("Value = %f", f);
return 0;
}
getchar() & putchar() 函数
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
eg:
#include <stdio.h>
int main( )
{
int c;
printf( "Enter a value :");
c = getchar( );
printf( "\nYou entered: ");
putchar( c );
printf( "\n");
return 0;
}
输入文本后
$./a.out Enter a value :runoob You entered: r
gets() & puts() 函数
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
#include <stdio.h>
int main( )
{
char str[100];
printf( "Enter a value :");
gets( str );
printf( "\nYou entered: ");
puts( str );
return 0;
}
输入文本后
$./a.out Enter a value :runoob You entered: runoob
scanf() 和 printf() 函数
int scanf(const char *format, ...) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, ...) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
format 可以是一个简单的常量字符串,可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。
eg:
#include <stdio.h>
int main( ) {
char str[100];
int i;
printf( "Enter a value :");
scanf("%s %d", str, &i);
printf( "\nYou entered: %s %d ", str, i);
printf("\n");
return 0;
}
$./a.out Enter a value :runoob 123 You entered: runoob 123
scanf() 期待输入的格式与您给出的 %s 和 %d 相同。在读取字符串时,只要遇到一个空格,scanf() 就会停止读取。
第二项
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 中的常量。
写入文件
字符写入流
int fputc( int c, FILE *fp );
fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。写入成功,返回写入的字符,如果发生错误,返回 EOF。
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等。
eg:
#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,并使用两个不同的函数写入两行。
读取文件
从文件读取单个字符的:
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, ...) 函数来从文件中读取字符串,遇到第一个空格和换行符时,它会停止读取。
eg:
#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 预处理器(C Preprocessor)简写为 CPP。
预处理器命令都是以井号(#)开头。必须是第一个非空字符,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
eg:
#define MAX_ARRAY_LENGTH 20
指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 定义为 20。使用 #define 定义常量来增强可读性。
#include <stdio.h> #include "myheader.h"
指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
#undef FILE_SIZE #define FILE_SIZE 42
指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#ifndef MESSAGE #define MESSAGE "You wish!" #endif
指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#ifdef DEBUG /* Your debugging statements here */ #endif
指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。它定义了 DEBUG,可以在编译期间随时开启或关闭调试。
预定义宏
ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
宏 | 描述 |
---|---|
__DATE__ | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
__TIME__ | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
__FILE__ | 这会包含当前文件名,一个字符串常量。 |
__LINE__ | 这会包含当前行号,一个十进制常量。 |
__STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1。 |
eg:
#include <stdio.h>
main()
{
printf("File :%s\n", __FILE__ );
printf("Date :%s\n", __DATE__ );
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
File :C:\Users\赵\Desktop\Vs-c\c++测试.cpp Date :Aug 17 2024 Time :01:01:26 Line :7 ANSI :1
预处理器运算符
C 预处理器提供了下列的运算符来帮助您创建宏:
宏延续运算符(\)
一个宏通常写在一个单行上。如果宏太长,一个单行容纳不下,使用宏延续运算符(\)。
#define message_for(a, b) \ printf(#a " and " #b ": We love you!\n")
字符串常量化运算符(#)
宏定义中,需要把一个宏的参数转换为字符串常量时,使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。
eg:
#include <stdio.h>
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void)
{
message_for(Carole, Debra);
return 0;
}
Carole and Debra: We love you!
标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。允许在宏定义中两个独立的标记被合并为一个标记。
eg:
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
token34 = 40
此例会从编译器产生下列的实际输出:
printf ("token34 = %d", token34);
此例演示了 token##n 会连接到 token34 中,使用了字符串常量化运算符(#)和标记粘贴运算符(##)。
defined() 运算符
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。指定的标识符已定义,值为真(非零)。指定的标识符未定义,则值为假(零)。
eg:
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void)
{
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
Here is the message: You wish!
参数化的宏
CPP 一个强大的功能是可以使用参数化的宏来模拟函数。
int square(int x) { return x * x; }
使用宏重写上面的代码
#define square(x) ((x) * (x))
在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。
eg:
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void)
{
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
Max between 20 and 10 is 20
第四项
C 头文件
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。
在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。
引用头文件相当于复制头文件的内容,但不会直接在源文件中复制头文件的内容。
A simple practice in C 或 C++ 程序中,建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。
引用头文件的语法
使用预处理指令 #include 可以引用用户和系统头文件。
#include <file>
用于引用系统头文件。它在系统目录的标准列表中搜索名为 file 的文件。在编译源代码时,可以通过 -I 选项把目录前置在该列表前。
#include "file"
这种形式用于引用用户头文件。它在包含当前文件的目录中搜索名为 file 的文件。在编译源代码时,可以通过 -I 选项把目录前置在该列表前。
引用头文件的操作
#include 指令会指示 C 预处理器浏览指定的文件作为输入。预处理器的输出包含了已经生成的输出,被引用文件生成的输出以及 #include 指令之后的文本输出。
char *test (void);
和一个使用了头文件的主程序 program.c,header.h为头文件
int x; #include "header.h" int main (void) { puts (test ()); }
编译器会看到
int x; char *test (void); int main (void) { puts (test ()); }
只引用一次头文件
#ifndef HEADER_FILE #define HEADER_FILE the entire header file file #endif
包装器 #ifndef。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。
有条件引用
需要从多个不同的头文件中选择一个引用到程序中。
在不同的操作系统上使用的配置参数,可以通过一系列条件来实现这点
#if SYSTEM_1 # include "system_1.h" #elif SYSTEM_2 # include "system_2.h" #elif SYSTEM_3 ... #endif
头文件比较多时,预处理器使用宏来定义头文件的名称。这就是有条件引用。不是用头文件的名称作为 #include 的直接参数,只需要使用宏名称代替即可:
#define SYSTEM_H "system_1.h" ... #include SYSTEM_H
SYSTEM_H 会扩展,预处理器会查找 system_1.h,就像 #include 最初编写的那样。SYSTEM_H 可通过 -D 选项被您的 Makefile 定义。
标准库头文件
C 标准库头文件(Standard Library Header Files)是由 ANSI C(也称为 C89/C90)和 ISO C(C99 和 C11)标准定义的一组头文件,这些头文件提供了大量的函数、宏和类型定义,用于处理输入输出、字符串操作、数学计算、内存管理等常见的编程任务。
以下是一些常见的 C 标准库头文件及其功能简介:
头文件 | 功能简介 |
---|---|
<stdio.h> | 标准输入输出库,包含 printf 、scanf 等函数 |
<stdlib.h> | 标准库函数,包含内存分配、程序控制等函数 |
<string.h> | 字符串操作函数,如 strlen 、strcpy 等 |
<math.h> | 数学函数库,如 sin 、cos 、sqrt 等 |
<time.h> | 时间和日期函数,如 time 、strftime 等 |
<ctype.h> | 字符处理函数,如 isalpha 、isdigit 等 |
<limits.h> | 定义各种类型的限制值,如 INT_MAX 等 |
<float.h> | 定义浮点类型的限制值,如 FLT_MAX 等 |
<assert.h> | 断言宏 assert ,用于调试检查 |
<errno.h> | 定义错误码变量 errno 及相关宏 |
<stddef.h> | 定义通用类型和宏,如 size_t 、NULL 等 |
<signal.h> | 处理信号的函数和宏,如 signal 等 |
<setjmp.h> | 提供非本地跳转功能的宏和函数 |
<locale.h> | 地域化相关的函数和宏,如 setlocale 等 |