11 函数指针与回调函数
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
//下面示例就是一个声明指向函数max的函数指针变量p
int (* p)(int, int) = & max;
回调函数
函数指针作为某个函数的参数来使用,而回调函数就是一个通过函数指针调用的函数
可以说回调函数就是某函数执行时调用了另一个函数
12 字符串
字符串的最后是null字符\0为终止的一维数组字符
所以字符数组的大小比看到或输入的字符大小要大一
//字符串的定义可以使用数组或指针,比如下面使用数组来定义
char s[]="abcd";
char s[]={"abcd"};
char s[]={'a','b','c','d','\0'};
char s[]={'a','b','c','d'};
//以上四句等价
c中操作字符串的函数
strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
strlen(s1); 返回字符串 s1 的长度。
strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
13 结构体
C 数组允许定义可存储相同类型数据项的变量
结构是 C 编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。
定义
必须使用struct语句,这个语句定义了一个包含多个成员的新的数据类型
struct tag { //结构体标签
member-list//变量定义
member-list
member-list
...
} variable-list ;//结构变量
下面是实例
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
访问结构体成员
使用成员访问运算符(.)
例如对book结构体进行访问
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
结构体作为函数参数
可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。
14共用体
可以通过共用体在相同的内存位置存储不同的数据类型
定义共用体
为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
union [union tag]//union tag 是可选的
{
member definition;//标准的变量定义
member definition;
...
member definition;
} [one or more union variables];//最后一个分号之前,您可以指定一个或多个共用体变量
下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{
int i;
float f;
char str[20];
} data;
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。
这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。
您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
访问
同样使用成员访问运算符(.)
例如下面对data的访问
printf( "data.i : %d\n", data.i);
下面这种方式i和f的值会有损坏,
因为最后赋给变量的值占用了内存位置
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
若是在同一时间只使用一个变量
则不会有成员输出损坏的情况
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);
15 typedef vs #define
可以使用typedef来为类型取一个新的名字
typedef unsigned char BYTE;
定义之后,BYTE就可以作为类型unsigned char的缩写
BYTE b1, b2;
与define的区别
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
16 文件读写
打开文件
使用fopen()函数来创建新文件或者打开一个已有的文件
这个调用会初始化类型FILE的一个对象,类型FILE包含了所有用来控制流的必要信息
FILE *open(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
fp = fopen("/tmp/test.txt", "w+");//通常/tmp是Linux系统上的临时目录,若不存在此目录,需要先建立此目录。若是在windows上,则修改为已存在的目录,例如C:/tmp
fprintf(fp, "This is testing for fprintf...\n");//将字符串写入到文件中
fputs("This is testing for fputs...\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, …) 函数来从文件中读取字符串,
但是在遇到第一个空格和换行符时,它会停止读取。
读取上面文件的实例
#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);//在fp所指向的输入流开始读取255个字符,到换行符或末尾停止
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);//k可以读取到最后
printf("3: %s\n", buff );
fclose(fp);
}
17 预处理器
所有的预处理器命令都是以井号(#)开头。
它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
重要的预处理器指令
#define 定义宏
#define MAX_ARRAY_LENGTH 20//这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量来增强可读性。
#include 包含一个源代码文件
#include <stdio.h>//此指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。
#include "myheader.h"//下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
#undef 取消已定义的宏
#undef FILE_SIZE
#define FILE_SIZE 42//这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif//这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#ifdef DEBUG
/* Your debugging statements here */
#endif//这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
预定义宏
ANSI C 定义了许多宏。
在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
__DATE__ 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
__TIME__ 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
__FILE__ 这会包含当前文件名,一个字符串常量。
__LINE__ 这会包含当前行号,一个十进制常量。
__STDC__ 当编译器以 ANSI 标准编译时,则定义为 1。
预处理器运算符
宏延续运算符(\)
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。
#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!
标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。
它允许在宏定义中两个独立的标记被合并为一个标记。
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
token34 = 40
defined() 运算符
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。
如果指定的标识符已定义,则值为真(非零)。
如果指定的标识符未定义,则值为假(零)。