C(下)

全局变量

在变量声明前使用变量可用extern关键字告知编译器

extern int count;

全局变量初始化时,若没有赋初值,会自动初始化为默认值

代码块作用域

作用域在代码块内部

{
}
文件作用域

任何在代码块之外声明的标识符都具有文件作用域(代码块之外声明的函数具有文件作用域)

原型作用域

只在函数原型的声明中起作用

函数作用域

函数作用域只适用于goto语句的标签,作用是将goto语句的标签限制在同一个函数内部

链接属性
external(外部的)

多个文件中声明的同名表示符表示同一个实体

internal(内部的)

单个文件中声明的同名表示符表示同一个实体

none(无)

声明的同名标识符被当做独立不同的实体

static关键字

static关键字可以使原先拥有external属性的表示符变为internal属性(只能在本文件的文件作用域生效)

两点注意:

  • 使用static关键字修改链接属性,只对具有文件作用域的标识符生效(对拥有其他作用域的标识符是另一种功能)
  • 链接属性只能修改一次,一旦变为internal就无法变回external
生存期
静态存储期

具有文件作用域的变量属于静态存储期,属于静态存储期的变量在程序执行期间一直占据内存,知道程序关闭才释放

自动存储期

具有代码块作用域的变量一般情况下属于自动存储期,属于自动存储期的变量在代码块结束时将自动释放存储空间

存储类型

存储类型其实是指存储变量值得内存类型,C语言提供了5种不同的存储类型

  • auto
  • register(寄存器变量)
  • static
  • extern
  • typedef
auto

具有代码块作用域,自动存储期和空连接属性

寄存器变量(register)

将一个变量声明为寄存器变量,那么该变量就有可能被存放于CPU的寄存器中

寄存器变量和自动变量在很多方面是一样的,拥有代码块作用域,自动存储期和空连接属性

将变量声明为寄存器变量就没办法通过取址运算符获得该变量的地址

静态局部变量(static)

使用static来声明局部变量,那么就可以将局部变量指定为静态局部变量

static是的局部变量具有静态存储期,所以它的生存期与全局变量一样,直到程序结束才释放。但是变量的作用域不会发生改变

#include <stdio.h>



int func(void) {
    static int count = 0;
    printf("count = %d\n", count);
    count++;
}



int main(void) {
    
    for (int i = 0; i < 10; i++) {
        func();
    }
    
    return 0;
}

count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9

内存管理方式
  • malloc
    申请动态内存空间
  • free
    释放动态内存空间
  • calloc
    申请并初始化一系列内存空间
  • realloc
    重新分配内存空间
malloc

函数原型

void *malloc(size_t size);

向系统申请size个字节的内存空间,并返回一个指向这块空间的指针(void类型)

函数调用失败返回NULL,如果size设置为0,返回值也可能是NULL

free

函数原型

void free(void *ptr);

free函数释放ptr参数指向的内存空间。该内存空间必须是由malloc、calloc或realloc函数申请的。

free函数不会修改ptr参数的值,所以调用后它仍指向原来的地方(变为非法空间)

内存泄漏

  1. 隐式内存泄漏(用完内存块没有及时使用free函数释放)
  2. 丢失内存块地址
calloc

函数原型

void *calloc(size_t nmemb, size_t size);

calloc 函数在内存中动态的申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为0

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

#define N 10

int main(void) {
    
    int *ptr = (int *)calloc(N, sizeof(int));

    for (int i = 0; i < N; i++) {
        printf("%d\n", *(ptr + i));
    }

    return 0;
}

0
0
0
0
0
0
0
0
0
0

realloc

函数原型

void *realloc(void *ptr, size_t size);
  • realloc函数修改ptr指向的内存空间大小为size字节
  • 如果新分配的内存空间比原来的小,会导致数据的丢失
  • 该函数移动内存空间的数据并返回新的指针
  • ptr参数为NULL,相当于malloc
  • size参数为0且ptr不为NULL,相当于free(ptr)
  • 除非ptr参数为NULL,否则ptr的值必须由先前调用malloc、calloc或realloc函数返回
mem初始化内存空间

mem开头的函数被编入string.h头文件中

  • memset -- 使用一个常量字节填充内存空间
  • memcpy -- 拷贝内存空间
  • memmove -- 拷贝内存空间
  • memcmp -- 比较内存空间
  • memchr -- 在内存空间中搜索一个字符
memset
memset(ptr, 常量, 内存尺寸);
C语言内存布局

C内存空间划分
根据内存地址从低到高分别划分为:

  • 代码段(Text segment)
  • 数据段(Initialized data segment)
  • BSS段(Bss segment/Uninitialized data segment)
  • 栈(Stack)
  • 堆(Heap)
代码段(Text segment)

代码段通常是指用来存放程序执行代码的一块内存区域。

这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。

在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

数据段(Initialized data segment)

数据段通常用来存放已经初始化的全局变量和局部静态变量。

BSS段(Bss segment/Uninitialized data segment)

BSS 段通常是指用来存放程序中未初始化的全局变量的一块内存区域。

BSS 是英文 Block Started by Symbol 的简称,这个区段中的数据在程序运行前将被自动初始化为数字 0。

前边我们学习了动态内存管理函数,使用它们申请的内存空间就是分配在这个堆里边。

所以,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。

当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上;当利用 free 等函数释放内存时,被释放的内存从堆中被剔除。

大家平时可能经常听到堆栈这个词,一般指的就是这个栈。

栈是函数执行的内存区域,通常和堆共享同一片区域。

堆和栈的对比

堆和栈则是 C 语言运行时最重要的元素,下面我们将两者进行对比。

申请方式:

堆由程序员手动申请
栈由系统自动分配

释放方式:

堆由程序员手动释放
栈由系统自动释放

生存周期:

堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问
栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问

发展方向:

堆和其它区段一样,都是从低地址向高地址发展
栈则相反,是由高地址向低地址发展

不带参数的宏定义
  • 宏定义的作用域是从定义的位置开始到整个程序结束
  • 可用#undef来终止宏定义的作用域
  • 宏定义允许嵌套
带参数的宏定义
#define MAX(x, y) (((x) > (y))? (x):(y)) // 最好加上括号,防止出现不必要的错误
#

在带参数的宏定义中,# 运算符后面应该跟一个参数,预处理器会把这个参数转换为一个字符串

#include <stdio.h>
#define STR(s) # s
int main(void) {
	printf(STR(hello\n));
	return 0;
}

hello

##

## 运算符被称为记号连接运算符,可以使用它来连接多个参数。

#include <stdio.h>
#define TOGETHER(x, y) x ## y 
int main(void) {
	printf("%d\n", TOGETHER(1, 25));
	return 0;
}

125

可变参数
#include <stdio.h>
#define SHOWLIST(...) printf(# __VA_ARGS__)
int main(void) {
    SHOWLIST(today, is, friday);
	return 0;
}

可变参数传入空值

内联函数
inline int func(void) {
;
}

内联函数会把函数加入到执行代码中,不必向一般调用函数那样重复申请栈空间。类似于带参数的宏定义,但减少了不必要的bug

只有编译时使用优化选项"-O"否则内联函数可能不会被嵌入到调用者代码中

gcc test.c -O && ./a.out
结构体

struct 结构体名称 结构体变量名

定义结构体

struct Book 
{
	char title[120];
	char author[40];
	unsigned int date;
	char publisher[40];
};

注意最后的;

定义结构体同时定义全局变量book

struct Book 
{
	char title[120];
	char author[40];
	unsigned int date;
	char publisher[40];
} book;
初始化结构体

方式一

struct Book
{
    char *title;
    float price;
    unsigned int date;
};

int main(void) {
    struct Book book = {
        "西游记",
        100.0,
        20200201
    };
}

方式二,使用 .成员名

struct Book
{
    char *title;
    float price;
    unsigned int date;
};

int main(void) {
    struct Book book = {
        .price = "西游记",
        .date = 20200201
    };
}
访问结构体

使用.加成员名访问结构体变量

结构体嵌套
#include <stdio.h>
#define print(format, args...) printf(format"\n", ## args)

struct Date {
    int year;
    int month;
    int day;
};

struct Book {
    char title[120];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
} book = {
    "《带你学C带你飞》",
    "小甲鱼",
    48.8,
    {2017, 11, 11},
    "清华大学出版社"
};


int main() {
    print("书名:%s", book.title);
    print("作者:%s", book.author);
    print("价格:%.2f", book.price);
    print("日期:%d-%d-%d", book.date.year, book.date.month, book.date.day);
    print("出版社:%s", book.publisher);
}
结构体数组

方法一

struct 结构体名称 {
	结构体成员
} 数组名[长度];

方法二

struct 结构体名称 {
	结构体成员
};

struct 结构体名称 数组名[长度];
结构体指针
struct Book * pt;  // 指向结构体的指针
pt = &book;

// 访问成员
// 方式一
(*pt).title
// 方式二
pt -> title
结构体做函数参数、返回值

两个类型相同的结构体变量之间可以直接赋值 -> 可以作为函数的参数传递

传递指向结构体变量的指针

#include <stdio.h>
#define print(format, args...) printf(format"\n", ## args)
struct Date {
    int year;
    int month;
    int day;
};
struct Book {
    char title[120];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};
void InputBook(struct Book *book) {
    printf("请输入书名:");
    scanf("%s", &book->title);
    printf("请输入作者:");
    scanf("%s", &book->author);
    printf("请输入价格:");
    scanf("%f", &book->price);
    printf("请输入出版日期:");
    scanf("%d-%d-%d", &book -> date.year, &book -> date.month, &book -> date.day);
    printf("请输入出版社:");
    scanf("%s", &book->publisher);

}
void PrintBook(struct Book *book) {
    print("书名:%s", book -> title);
    print("作者:%s", book -> author);
    print("价格:%.2f", book -> price);
    print("日期:%d-%d-%d", book -> date.year, book -> date.month, book -> date.day);
    print("出版社:%s", book -> publisher);

}

int main() {
    struct Book book;
    struct Book *pb;
    pb = &book;
    InputBook(pb);
    PrintBook(pb);
}
动态申请结构体

使用malloc函数为结构体分配存储空间

链表
头插法
#include <stdio.h>
#include <stdlib.h>
struct Book {
    char title[20];
    struct Book *next;
};


void addBookHeader(struct Book **header) {
    struct Book *book = (struct Book *)malloc(sizeof(struct Book)), *temp;
    printf("请输入书名:");
    scanf("%s", &book->title);

    if (*header == NULL) {
        *header = book;
    } else {
        temp = *header;
        *header = book;
        book->next = temp;
    }
}

void printBook(struct Book *header) {
    int i = 0;
    struct Book *bk = header;
    while (1) {
        printf("第%d本书\n", ++i);
        printf("书名:%s\n", bk->title);
        if (bk->next == NULL) break;
        bk = bk->next;
    }
}


int main() {
    struct Book *header = NULL;
    char inp, flag = 0;
    while (1) {
        printf("是否录入书籍<y/n>:");
        scanf("%c",&inp);
        
        if (inp == 'y' || inp == 'Y') {
            addBookHeader(&header);
        } else {
            flag = 1;
        }
        if (header == NULL) continue;
        printBook(header);
        if (flag) break;
        getchar();
    }
}
尾插法、索引插入
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Book {
    char title[20];
    struct Book *next;
};


void addBookTail(struct Book **header) {
    static struct Book *tail = NULL;
    struct Book *book = (struct Book *)malloc(sizeof(struct Book)), *temp;
    printf("请输入书名:");
    scanf("%s", &book->title);

    if (*header == NULL) {
        tail = *header = book;
        book->next = NULL;
    } else {

        tail->next = book;
        book->next = NULL;
        tail = book;
    }
}

void printBook(struct Book *header) {
    int i = 0;
    struct Book *bk = header;
    while (1) {
        printf("第%d本书\n", ++i);
        printf("书名:%s\n", bk->title);
        if (bk->next == NULL) break;
        bk = bk->next;
    }
}

void insertBook(struct Book **book, int index) {
    
    struct Book *newbook = (struct Book *)malloc(sizeof(struct Book));
    printf("请输入书名:");
    scanf("%s", &newbook->title);
    
    int i = 0;
    struct Book *temp;
    while (1) {
        
        if (index == 0) {
            temp = *book;
            *book = newbook;
            newbook->next = temp;
            break;
        }
        else if (i == index - 1) {
            temp = (*book)->next;
            (*book)->next = newbook;
            newbook->next = temp;
            break;
        } else if ((*book)->next == NULL) {
            (*book)->next = newbook;
            newbook->next = NULL;
            
            printf("指定序列%d超出链表索引范围,默认添加至链尾\n", index);

            break;
        }
        *book = (*book)->next;
        i++;
    }
}

int main() {
    struct Book *header = NULL;
    // struct Book *tail = NULL;
    char inp[20], flag = 0;
    int index;
    while (1) {



        printf("请输入关键字:");
        scanf("%s",&inp);
        
        if (!strcmp(inp, "add")) {
            addBookTail(&header);
        } else if (!strcmp(inp, "insert")) {
            getchar();
            printf("请输入要插入的索引位置:");
            scanf("%d", &index);
            getchar();
            insertBook(&header, index);
        } else if(!strcmp(inp, "show")) {
            printBook(header);
        }
    }
}
typedef
typeof 数据类型 别名1,别名2,*别名3; // 别名3是指向数据类型的指针
typedef struct Date {
    int year;
    int month;
    int day;
} DATE,*PDATE;   
// DATE为struct Date的别名,PDATE 为struct Date的指针
别名函数指针
#include <stdio.h>

typedef int (*PTR_TO_FUN)(void);

int func(void) {
    return 100;
}

int main() {
    PTR_TO_FUN f;
    f = &func;
    printf("%d\n", f());
}
别名函数指针数组
#include <stdio.h>

typedef char (*PTR_TO_FUN)(void);

char funcA(void) {
    return 'A';
}
char funcB(void) {
    return 'B';
}
char funcC(void) {
    return 'C';
}
int main() {
    PTR_TO_FUN f[3] = {&funcA, &funcB, &funcC};
    printf("%c\n", f[0]());
    printf("%c\n", f[1]());
    printf("%c\n", f[2]());
}

别名数组指针
#include <stdio.h>

typedef int (*PTR_TO_ARRAY)[3];


int main() {
    int array[][3] = {1,2,3,4,5,6};
    PTR_TO_ARRAY pa;
    pa = array;

    for (int i = 0; i < 6; i++) {
        printf("%d\n", (*pa)[i]);
    }

}

共用体
union data {
	int i;
	char ch;
	float f;
};

union data a, b, c;


union data {
	int i;
	char ch;
	float f;
} a, b, c;

union {
	int i;
	char ch;
	float f;
} a, b, c;
共用体初始化
union data {
	int i;
	char ch;
	float f;
};

union data a = {520}; // 初始化第一个成员
union data b = a; // 用一个共用体初始化另一个共用体
union data c = {.ch = 'C'} // C99新增,指定初始化成员
枚举类型

如果一个变量只有几种可能的值,那么就可以将其定义为枚举类型

声明枚举类型
enum 枚举类型名称 {枚举值名称, 枚举值名称...};
定义枚举变量
enum 枚举类型名称 枚举变量1, 枚举变量2;
#include <stdio.h>
int main() {
    enum Week {sun, mon, tue, wed, thu, fri };
    enum Week w;
    printf("sizeof w = %d\n", sizeof(w));
    printf("sun = %d\n", sun);
    printf("mon = %d\n", mon);
    printf("tue = %d\n", tue);
    printf("wed = %d\n", wed);
    printf("thu = %d\n", thu);
    printf("fri = %d\n", fri);
}

sizeof w = 4
sun = 0
mon = 1
tue = 2
wed = 3
thu = 4
fri = 5

枚举类型默认从0开始增加

指定枚举值

#include <stdio.h>
int main() {
    enum Week {sun, mon, tue = 10, wed, thu, fri };
    enum Week w;
    printf("sizeof w = %d\n", sizeof(w));
    printf("sun = %d\n", sun);
    printf("mon = %d\n", mon);
    printf("tue = %d\n", tue);
    printf("wed = %d\n", wed);
    printf("thu = %d\n", thu);
    printf("fri = %d\n", fri);
}

sizeof w = 4
sun = 0
mon = 1
tue = 10
wed = 11
thu = 12
fri = 13

C中枚举变量运行进行自增操作,C++中则不行

位域

使用位域的做法是在结构体定义时,在结构体成员后面使用冒号(:)和数字来表示该成员所占的位数。

位域大小得小于

无名位域

位域成员可以没有名称,只要给出数据类型和位宽即可。

不能对位域进行取址操作

struct Test {
	unsigned int x:1;
	unsigned int y:2;
	unsigned int z:3;
	unsigned int  :4;   //  
};
逻辑位运算符
运算符含义优先级举例说明
~按位取反~a如果a为1,~a为0
如果a为0,~a为1
&按位或a&b只有a和b同时为1,结果才为1
只要a和b其中一个为0结果为0
^按位异或a^b如果a和b不同,结果为1
如果a和b相同,结果为0
|按位或最低a|b只要a和b其中一个为1结果为1
只有a和b同时为0,结果才为0
移位运算符

操作数 >> 位移数
操作数 << 位移数

时间操作
time

time 函数

#include <stdio.h>
#include <time.h>

int main(void)
{
        time_t seconds;

        // 下面语句也可以写成:time(&seconds);
        seconds = time(NULL);

        printf("1970年1月1日零点到现在经过了%ld个小时!\n", seconds / 3600);

        return 0;
}
localtime

localtime 函数是将一个 time_t 类型的值转换成具体的本地时间和日期,所以需要先使用 time 函数来返回表示当前时间的 time_t。

tm 结构体

struct tm 
{
           int tm_sec;     /* 秒,范围为 0~59,60 的话表示闰秒 */
           int tm_min;     /* 分,范围是 0~59 */
           int tm_hour;    /* 时,范围是 0~23 */
           int tm_mday;    /* 一个月中的第几天,范围是 1~31 */
           int tm_mon;     /* 一年中的第几个月,范围是 0~11 */
           int tm_year;    /* 自 1900 年往后的第几年 */
           int tm_wday;    /* 星期几,自星期天开始计算,范围是 0~6 */
           int tm_yday;    /* 一年中的第几天,范围是 0~365 */
           int tm_isdst;   /* 指定日光节约时间是否生效,正数表示生效,0 表示不生效,负数表示该信息不可用 */
};

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t seconds;
    tm *pt;

    time(&seconds);
    printf("current time %d\n",seconds);

    pt = localtime(&seconds);


    printf("tm_sec %d\n", pt->tm_sec);
    printf("tm_min %d\n", pt->tm_min);
    printf("tm_hour %d\n", pt->tm_hour);
    printf("tm_mday %d\n", pt->tm_mday);



    return 0;
}
文件操作
fopen

fopen 函数用于打开一个文件并返回文件指针。

#include <stdio.h>
...
FILE *fopen(const char *path, const char *mode);
参数
含义
path该参数是一个 C 语言字符串,指定了待打开的文件路径和文件名(见备注)
mode1. 该参数是一个 C 语言字符串,指定了文件的打开模式

2. 下面列举了所有可使用的打开模式:
模式
描述
"r"1. 以只读的模式打开一个文本文件,从文件头开始读取
2. 该文本文件必须存在
"w"1. 以只写的模式打开一个文本文件,从文件头开始写入
2. 如果文件不存在则创建一个新的文件
3. 如果文件已存在则将文件的长度截断为 0(重新写入的内容将覆盖原有的所有内容)
"a"1. 以追加的模式打开一个文本文件,从文件末尾追加内容
2. 如果文件不存在则创建一个新的文件
"r+"1. 以读和写的模式打开一个文本文件,从文件头开始读取和写入
2. 该文件必须存在
3. 该模式不会将文件的长度截断为 0(只覆盖重新写入的内容,原有的内容保留)
"w+"1. 以读和写的模式打开一个文本文件,从文件头开始读取和写入
2. 如果文件不存在则创建一个新的文件
3. 如果文件已存在则将文件的长度截断为 0(重新写入的内容将覆盖原有的所有内容)
"a+"1. 以读和追加的模式打开一个文本文件
2. 如果文件不存在则创建一个新的文件
3. 读取是从文件头开始,而写入则是在文件末尾追加
"b"1. 与上面 6 中模式均可结合("rb", "wb", "ab", "r+b", "w+b", "a+b")
2. 其描述的含义一样,只不过操作的对象是二进制文件(见备注)

返回值:

  1. 如果文件打开成功,则返回一个指向 FILE 结构的文件指针;

  2. 如果文件打开失败,则返回 NULL 并设置 errno 为指定的错误。

备注:

  1. path 参数可以是相对路径(…/fishc.txt)也可以是绝对路径(/home/FishC/fishc.txt),如果只给出文件名而不包含路径,则表示该文件在当前文件夹中

  2. 从本质上来说,文本文件也是属于二进制文件的,只不过它存放的是相应的字符编码值。

  3. 打开方式要区分文本模式和二进制模式的原因,主要是因为换行符的问题。C 语言用 \n 表示换行符,Unix 系统用 \n,Windows 系统用 \r\n,Mac 系统则用 \r。如果在 Windows 系统上以文本模式打开一个文件,从文件读到的 \r\n 将会自动转换成 \n,而写入文件则将 \n 替换为 \r\n。但如果以二进制模式打开则不会做这样的转换。Unix 系统的换行符跟 C 语言是一致的,所以不管以文本模式打开还是二进制模式打开,结果都是一样的。

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

int main(void)
{
        FILE *fp;
        int ch;

        if ((fp = fopen("hello.txt", "r")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        while ((ch = getc(fp)) != EOF)
        {
                putchar(ch);
        }

        fclose(fp);

        return 0;
}
fclose

fclose 函数用于关闭先前由 fopen 函数打开的文件。

fclose 函数会将缓冲区内的数据写入文件中,并释放系统所提供的文件资源。

参数说明
fp 指向一个待关闭的文件指针

返回值:

  1. 如果文件关闭成功,返回值是 0;

  2. 如果文件关闭失败,返回值是 EOF,并设置 errno 为指定的错误。

备注:

  1. 磁盘已满、设备出错或者 I/O 错误均可能导致 fclose 函数调用失败。
fgetc (单个字符读取)

fgetc 函数用于从文件流中读取下一个字符并推进文件的位置指示器(用来指示接下来要读写的下一个字符的位置)。

#include <stdio.h>
...
int fgetc(FILE *stream);
参数解析:

stream 该参数是一个 FILE 对象的指针,指定一个待读取的文件流

返回值:
  1. 该函数将读取到的 unsigned char 类型转换为 int 类型并返回;

  2. 如果文件结束或者遇到错误则返回 EOF。

备注:
  1. fgetc 函数和 getc 函数两个的功能和描述基本上是一模一样的,它们的区别主要在于实现上:fgetc 是一个函数;而 getc 则是一个宏的实现

  2. 一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。

  3. 由于 getc 是由宏实现的,对其参数可能有不止一次的调用,所以不能使用带有副作用(side effects)的参数。

fputc (单个字符写入)

fputc 函数用于将一个字符写入到指定的文件中并推进文件的位置指示器(用来指示接下来要读写的下一个字符的位置)。

#include <stdio.h>
...
int fputc(int c, FILE *stream);

参数解析:

参数
含义
c 指定待写入的字符
stream 该参数是一个 FILE 对象的指针,指定一个待写入的文件流

返回值:

  1. 如果函数没有错误,返回值是写入的字符;

  2. 如果函数发生错误,返回值是 EOF。

备注:

  1. fputc 函数和 putc 函数两个的功能和描述基本上是一模一样的,它们的区别主要在于实现上:fputc 是一个函数;而 putc 则是一个宏的实现

  2. 一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。

  3. 由于 putc 是由宏实现的,对其参数可能有不止一次的调用,所以不能使用带有副作用(side effects)的参数。

ungetc(ch, stdin); // 将字符ch放回输入流

fgets

fgets 函数用于从指定文件中读取字符串。

fgets 函数最多可以读取 size - 1 个字符,因为结尾处会自动添加一个字符串结束符 ‘\0’。当读取到换行符(’\n’)或文件结束符(EOF)时,表示结束读取(’\n’ 会被作为一个合法的字符读取)。

#include <stdio.h>
...
char *fgets(char *s, int size, FILE *stream);

s 字符型指针,指向用于存放读取字符串的位置
size 指定读取的字符数(包括最后自动添加的 ‘\0’)
stream 该参数是一个 FILE 对象的指针,指定一个待操作的数据流

返回值:

  1. 如果函数调用成功,返回 s 参数指向的地址。

  2. 如果在读取字符的过程中遇到 EOF,则 eof 指示器被设置;如果还没读入任何字符就遇到这种 EOF,则 s 参数指向的位置保持原来的内容,函数返回 NULL。

  3. 如果在读取的过程中发生错误,则 error 指示器被设置,函数返回 NULL,但 s 参数指向的内容可能被改变。

fputs

fputs 函数用于将一个字符串写入到指定的文件中,表示字符串结尾的 ‘\0’ 不会被一并写入。

#include <stdio.h>
...
int fputs(const char *s, FILE *stream);

参数解析

s 字符型指针,指向用于存放待写入字符串的位置
stream 该参数是一个 FILE 对象的指针,指定一个待操作的数据流

返回值:

  1. 如果函数调用成功,返回一个非 0 值;

  2. 如果函数调用失败,返回 EOF。

feof

文件读取结束eof指示器被设置,函数返回0值

函数原型

#include <stdio.h>
...
int feof(FILE *stream);
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        FILE *fp;
        int ch;

        if ((fp = fopen("file.txt", "r")) == NULL)
        {
                printf("打开文件失败!\n");
                exit(EXIT_FAILURE);
        }

        while (1)
        {
                ch = fgetc(fp);

                if (feof(fp))
                {
                        break;
                }

                putchar(ch);
        }

        fclose(fp);

        return 0;
}
fscanf、fprintf
fscanf

fscanf 函数用于从指定文件中读取格式化字符串。

#include <stdio.h>
...
int fscanf(FILE *stream, const char *format, ...);
fprintf

fprintf 函数用于打印格式化字符串到指定的文件。

#include <stdio.h>
...
int fprintf(FILE *stream, const char *format, ...);

参数解析:

1、stream 参数

该参数是一个 FILE 对象的指针,指定一个待操作的数据流。

2、format 参数

format 参数是一个格式化字符串,由格式化占位符和普通字符组成。

格式化占位符(以 % 开头)用于指明输出的参数值如何格式化

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

int main(void) {
    time_t seconds;
    tm *pt;
    FILE *fp;

    int a,b,c;


    time(&seconds);
    printf("current time %d\n",seconds);
    pt = localtime(&seconds);


    printf("tm_sec %d\n", pt->tm_sec);
    printf("tm_min %d\n", pt->tm_min);
    printf("tour %d\n", pt->tm_hour);
    printf("tm_mday %d\n", pt->tm_mday);

    if ((fp = fopen("t.txt","w")) == NULL){
        perror("open file error");
        exit(EXIT_FAILURE);
    }

    fprintf(fp,"%d,%d,%d\n", pt->tm_sec, pt->tm_min, pt->tm_hour);
    fclose(fp);

    if ((fp = fopen("t.txt","r")) == NULL){
        perror("open file error");
        exit(EXIT_FAILURE);
    }

    fscanf(fp, "%d,%d,%d", &a, &b, &c);
    printf("file get %d,%d,%d", a, b, c);
    fclose(fp);
    




    return 0;
}
fwrite(二进制写入)

fwrite指定尺寸数据写入文件中

函数原型

#include <stdio.h>
...
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

参数解析

参数
含义
ptr 指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
size 指定要写入的每个元素的尺寸,最终尺寸等于 size * nmemb
nmemb 指定要写入的元素个数,最终尺寸等于 size * nmemb
stream 该参数是一个 FILE 对象的指针,指定一个待写入的文件流

返回值:

  1. 返回值是实际写入到文件中的元素个数(nmemb);

  2. 如果返回值与 nmemb 参数的值不同,则有错误发生。

fread(二进制读取)

fread 从文件中读取指定尺寸的数据

函数原型

#include <stdio.h>
...
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

参数解析

参数
含义
ptr 指向存放数据的内存块指针,该内存块的尺寸最小应该是 size * nmemb 个字节
size 指定要读取的每个元素的尺寸,最终尺寸等于 size * nmemb
nmemb 指定要读取的元素个数,最终尺寸等于 size * nmemb
stream 该参数是一个 FILE 对象的指针,指定一个待读取的文件流

返回值:

  1. 返回值是实际读取到的元素个数(nmemb);

  2. 如果返回值比 nmemb 参数的值小,表示可能读取到文件末尾或者有错误发生(可以使用 feof 函数或 ferror 函数进一步判断)。

文件随机读写
ftell 获取文件位置指针当前位置

ftell

语法:
#include <stdio.h>
long ftell( FILE *stream );

返回值 :
ftell()函数返回stream(流)当前的文件位置,如果发生错误返回-1.

文件头是0

rewind 将文件位置指针移动到文件头

rewind

函数原型

void rewind(FILE *stream)
fseek 设置文件位置指针的位置

fseek

函数原型

#include <stdio.h>
...
int fseek(FILE *stream, long int offset, int whence);

参数解析

参数
含义
stream 该参数是一个 FILE 对象的指针,指定一个待操作的文件流
offset 指定从 whence 参数的位置起偏移多少个字节
whence 指定从开始偏移的位置,该参数可以使用下面任一值:
描述
SEEK_SET 文件头
SEEK_CUR 当前的读写位置
SEEK_END 文件末尾
标准流

标准输入流 stdin
标准输出流 stdout
标准错误输出 stderr

linux shell 重定向

linux 系统中使用

  • 重定向标准输入 <
  • 重定向标准输出 >
  • 重定向标准错误输出 2>
./a.out > std.out 2> error.out
错误指示器
ferror

ferror

函数原型

int ferror(FILE *stream);

返回值
如果设置了与流关联的错误标识符,该函数返回一个非零值,否则返回一个零值。

#include <stdio.h>

int main()
{
   FILE *fp;
   char c;

   fp = fopen("file.txt", "w");

   c = fgetc(fp);
   if( ferror(fp) )
   {
      printf("读取文件:file.txt 时发生错误\n");
   }
   clearerr(fp);
   if( ferror(fp) )
   {
      printf("读取文件:file.txt 时发生错误\n");
   }
   fclose(fp);

   return(0);
}
clearerr 清除文件末尾指示器 和 错误指示器状态

clearerr
函数库 <stdio.h>

函数原型

void clearerr(FILE *stream);

返回值
这不会失败,且不会设置外部变量 errno,但是如果它检测到它的参数不是一个有效的流,则返回 -1,并设置 errno 为 EBADF。

clearerr(fp);
errno 错误码, stderr

C 库宏 extern int errno 是通过系统调用设置的,在错误事件中的某些库函数表明了什么发生了错误。

#include <stdio.h>
#include <errno.h>
#include <string.h>
 
extern int errno ;
 
int main ()
{
   FILE *fp;
 
   fp = fopen("file.txt", "r");
   if( fp == NULL ) 
   {
      fprintf(stderr, "Value of errno: %d\n", errno);
      fprintf(stderr, "Error opening file: %s\n", strerror(errno));
   }
   else 
   {
      fclose(fp);
   }
   
   return(0);
}
perror 打印错误描述信息

C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格

函数原型

void perror(const char *str)这里插入代码片
if( fp == NULL ) {
      perror("Error: ");
      return -1;
   }
IO 缓冲区

标准IO提供三种类型的缓冲模式

  • 按快缓存
  • 按行缓存
  • 不缓存
setvbuf 函数指定一个数据流的缓存模式

setvbuf 函数用于指定一个数据流的缓存模式。

对于数据流,有三种缓存模式:不缓存,按块缓存和按行缓存。如果输出流设置为不缓存,数据会直接写入目标文件或打印到屏幕上;如果设置为按块缓存,那么数据会先写入到缓存块中;如果设置为按行缓存,那么在接收到换行符(’\n’)之前,数据都是先缓存在缓冲区的。

fflush 函数可以强制刷新缓冲区。

函数原型

#include <stdio.h>
...
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

参数解析

参数
含义
stream 该参数是一个 FILE 对象的指针,指定一个打开的数据流
buf1. 指定一个用户分配的缓冲区

2. 如果该参数为 NULL,那么函数会自动分配一个指定尺寸的缓冲区
mode 指定数据流的缓存模式:
模式
描述
_IOFBF 按块缓存
_IOLBF 按行缓存
_IONBF 不缓存
size 指定缓冲区的尺寸(字节)
#include <stdio.h>
#include <string.h>

int main(void)
{
        char buff[1024];

        memset(buff, '\0', sizeof(buff));

        // 指定 buff 为缓冲区,_IOFBF 表示当缓冲区已满时才写入 stdout
        setvbuf(stdout, buff, _IOFBF, 1024);

        fprintf(stdout, "This is bbs.fishc.com\n");
        fprintf(stdout, "This output will go into buff\n");

        // fflush强制将上面缓存中的内容写入stdout
        fflush(stdout);

        fprintf(stdout, "this will appear when progream\n");
        fprintf(stdout, "will come after sleeping 5 seconds\n");

        sleep(5);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值