结构体

  • 结构体声明
1
2
3
4
5
6
7
struct Book
{
	结构体成员1;
	结构体成员2;
	结构体成员3;
	......
};
  • 定义结构体类型变量

    • struct 结构体名称 结构体变量名
  • 访问结构体变量

    • 要访问结构体成员,我们需要引入一个新的运算符——点号(.)运算符。比如book.title就是引用book结构体的title成员,它是一个字符数组。
  • 初始结构体的指定成员值

    • 其语法和数组指定初始化元素类似,不过结构体指定初始化成员使用点号(.)运算符和成员名
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

struct Book{
    char title[128];
    char author[40];
    float price;
    unsigned int data;
    char publisher[40];
}book;

int main(void){
//    struct Book book1;
    printf("请输入书名:");
    scanf("%s", book.title);
    printf("请输入作者:");
    scanf("%s", book.author);
    printf("请输入售价:");
    scanf("%f", &book.price);
    printf("请输入出版日期:");
    scanf("%d", &book.data);
    printf("请输入出版社:");
    scanf("%s", book.publisher);

    printf("\n====数据录入完毕====\n\n");

    printf("书名:%s\n", book.title);
    printf("作者:%s\n", book.author);
    printf("售价:%.2f\n", book.price);
    printf("出版日期:%d\n", book.data);
    printf("出版社:%s\n", book.publisher);

    return 0;
}

结构体嵌套

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

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

struct Book{
    char title[128];
    char author[40];
    float price;
    struct Data data;
    char publisher[40];
}book = {
        // 初始化
        "c学习",
        "Luenci",
        48.8,
        {2019,11,13},
        "文华出版社"
};

int main(void){

    printf("\n====数据录入为====\n\n");

    printf("书名:%s\n", book.title);
    printf("作者:%s\n", book.author);
    printf("售价:%.2f\n", book.price);
    printf("出版日期:%d%d%d\n", book.data.year, book.data.month, book.data.day);
    printf("出版社:%s\n", book.publisher);

    return 0;
}

out
书名:c学习
作者:Luenci
售价:48.80
出版日期:20191113
出版社:文华出版社

结构体数组

  • 第二种方法是先声明一个结构体类型(如上例子中Book),再用此类型定义一个结构体数组:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct 结构体名称
{
	结构体成员;
};
struct 结构体名称 数组名[长度];

// 初始化结构体数组

struct Book book[3] = {
    {"《零基础学c》", "Luenci", 49.5, {2016, 11, 22}, "清华大学出版社"} 
    {"《零基础学c》", "Luenci", 49.5, {2016, 11, 22}, "清华大学出版社"} 
    {"《零基础学c》", "Luenci", 49.5, {2016, 11, 22}, "清华大学出版社"}  
}

结构体指针

1
2
struct Book *ptr
ptr = &book
  • 通过结构体指针访问结构体成员
    • (*结构体指针).成员名
    • 结构体指针->成员名
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//
// Created by Luenci on 2019/10/13.
//
#include <stdio.h>

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

struct Book{
    char title[128];
    char author[40];
    float price;
    struct Data data;
    char publisher[40];
}book = {
        // 初始化
        "c学习",
        "Luenci",
        48.8,
        {2019,11,13},
        "文华出版社"
};

int main(void){

    struct Book *ptr;
    ptr = &book;

    printf("\n====数据录入为====\n\n");

    printf("书名:%s\n", (*ptr).title);
    printf("作者:%s\n", (*ptr).author);
    printf("售价:%.2f\n", (*ptr).price);
    printf("出版日期:%d%d%d\n", (*ptr).data.year, (*ptr).data.month, (*ptr).data.day);
    printf("出版社:%s\n", (*ptr).publisher);


    printf("书名:%s\n", ptr->title);
    printf("作者:%s\n", ptr->author);
    printf("售价:%.2f\n", ptr->price);
    printf("出版日期:%d%d%d\n", ptr->data.year, ptr->data.month, ptr->data.day);
    printf("出版社:%s\n", ptr->publisher);

    return 0;
}
out:
====数据录入为====

书名:c学习
作者:Luenci
售价:48.80
出版日期:20191113
出版社:文华出版社

链表

单链表

1
2
3
4
5
graph LR
head --> A
A[信息域- 指针] --> B[信息域- 指针]
B[信息域- 指针] --> c[信息域- 指针]
c[信息域- 指针] --> NULL

头插法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// ------------ 头插法实现链表的插入 ---------------
//
// Created by Luenci on 2019/10/14.
//
#include <stdio.h>
#include <stdlib.h>

struct Book{
    char title[128];
    char author[40];
    struct Book *next;
};

void getInput(struct Book *book){
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);

}


void add_book(struct Book **library){
    struct Book *book, *temp;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL){
        printf("分配内存失败");
        exit(1);
    }

    getInput(book);

    if (*library != NULL){
        temp = *library;
        *library = book;
        book->next = temp;


    } else{
        *library = book;
        book->next = NULL;
    }
}

void printLibrary(struct Book *library){
    struct Book *book;
    int count = 1;

    book = library;
    while(book != NULL){
        printf("Book%d: ", count);
        printf("书名:%s", book->title);
        printf("作者:%s", book->author);
        book = book->next;
        count++;
    }
}

void releaseLibrary(struct Book *library){
    while(library != NULL){
        library = library->next;
        free(library);
    }

}

int main(void){
    struct Book *library = NULL;
    int ch;

    while (1){
        printf("请问你是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        }while (ch != 'Y' && ch != 'N');
        if (ch == 'Y'){
            add_book(&library);
        } else{
            break;
        }
    }

    printf("请问你是否需要打印书籍信息(Y/N):");
    do{
        ch = getchar();
    }while (ch != 'Y' && ch != 'N');
    if (ch == 'Y'){
        printLibrary(library);
    }
    releaseLibrary(library);

    return 0;
}

尾插法

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
//
// Created by Luenci on 2019/10/14.
//

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

struct Book{

    char title[128];
    char author[40];
    struct Book *next;

};


void getInput(struct Book *book){

    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);

}

void add_book2(struct Book **library){
    /* ------------ 尾插法 ------------- */

    struct Book *book,*temp;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL){
        printf("分配内存失败");
        exit(1);
    }

    getInput(book);

    if (*library != NULL){
        temp = *library;
        // 定位单链表的尾部位置
        while (temp->next != NULL){
            temp = temp->next;
        }
        // 插入数据
        temp->next = book;
        book->next = NULL;
    } else{

        *library = book;
        book->next = NULL;

    }

}


void printLibrary(struct Book *library){
    struct Book *book;
    int count = 1;

    book = library;
    while(book != NULL){
        printf("Book%d: ", count);
        printf("书名:%s", book->title);
        printf("作者:%s", book->author);
        book = book->next;
        count++;
    }
}

void releaseLibrary(struct Book **library){
    struct Book *temp;

    while(library != NULL){
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }

}


int main(void){
    struct Book *library = NULL;
    int ch;

    while (1){
        printf("请问你是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        }while (ch != 'Y' && ch != 'N');
        if (ch == 'Y'){
            add_book2(&library);
        } else{
            break;
        }
    }

    printf("请问你是否需要打印书籍信息(Y/N):");
    do{
        ch = getchar();
    }while (ch != 'Y' && ch != 'N');
    if (ch == 'Y'){
        printLibrary(library);
    }
    releaseLibrary(&library);

    return 0;
}

尾插法改进

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//
// Created by Luenci on 2019/10/14.
//

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

struct Book{

    char title[128];
    char author[40];
    struct Book *next;

};

void getInput(struct Book *book);
void add_book(struct Book **library);
void getInput(struct Book *book);
void add_book2(struct Book **library);
void printLibrary(struct Book *library);
struct Book *searchBook(struct Book *library, char *target);
void releaseLibrary(struct Book **library);
void printBook(struct Book *book);


void getInput(struct Book *book){

    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);

}


void add_book(struct Book **library){
    /* ----------- 头插法 ------------- */

    struct Book *book, *temp;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL){
        printf("分配内存失败");
        exit(1);
    }

    getInput(book);

    if (*library != NULL){
        temp = *library;
        *library = book;
        book->next = temp;

    } else{
        *library = book;
        book->next = NULL;
    }
}


void add_book2(struct Book **library){
    /* ------------ 尾插法 ------------- */

    struct Book *book;
    static struct Book *tail;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL){
        printf("分配内存失败");
        exit(1);
    }

    getInput(book);

    if (*library != NULL){
        tail->next = book;
        book->next = NULL;

    } else{
        *library = book;
        book->next = NULL;
    }
    tail = book;

}


void printLibrary(struct Book *library){
    struct Book *book;
    int count = 1;

    book = library;
    while(book != NULL){
        printf("Book%d: ", count);
        printf("书名:%s", book->title);
        printf("作者:%s", book->author);
        book = book->next;
        count++;
    }
}

void releaseLibrary(struct Book **library){
    struct Book *temp;

    while(library != NULL){
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }

}

struct Book *searchBook(struct Book *library, char *target){
    struct Book *book;

    book = library;
    while (book != NULL){
        // C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false
        if (!strcmp(book->title, target) || !strcmp(book->author, target)){
            break;
        }
        book = book->next;
    }
    return book;
}


void printBook(struct Book *book){

    printf("书名: %s\n", book->title);
    printf("作者: %s\n", book->author);

}

int main(void){
    struct Book *library = NULL;
    struct Book *book;
    char *input[128];
    int ch;

    while (1){
        printf("请问你是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        }while (ch != 'Y' && ch != 'N');
        if (ch == 'Y'){
            add_book2(&library);
        } else{
            break;
        }
    }

    printf("请问你是否需要打印书籍信息(Y/N):");
    do{
        ch = getchar();
    }while (ch != 'Y' && ch != 'N');
    if (ch == 'Y'){
        printLibrary(library);
    }

    printf("\n请输入书名或作者:");
    scanf("%s", input);

    book = searchBook(library, input);
    if(book == NULL){
        printf("很抱歉没能找到!\n");
    } else{
        do{
            printf("已找到符合条件的书籍...\n");
            printBook(book);
        } while ((book = searchBook(book->next, input)) != NULL);
    }

    releaseLibrary(&library);

    return 0;
}

链表的插入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//
// Created by Luenci on 2019/10/16.
//
#include <stdio.h>
#include <stdlib.h>

struct Node{
    int value;
    struct Node *next;
};

void insertNode(struct Node **head, int value);
void printNode(struct Node *head);

void insertNode(struct Node **head, int value){
    struct Node *previous;
    struct Node *current;
    struct Node *new;

    current = *head;
    previous = NULL;
    while (current != NULL && current->value < value){
        previous = current;
        current = current->next;
    }
    new = (struct Node *)malloc(sizeof(struct Node));
    if (new == NULL){
        printf("分配内存失败!\n");
        exit(1);
    }

    new->value = value;
    new->next = current;

    if (previous == NULL){
        *head = new;
    } else{
        previous->next = new;
    }
}

void printNode(struct Node *head){
    struct Node *current;

    current = head;
    while (current != NULL){
        printf("%d ", current->value);
        current = current->next;
    }
    putchar('\n');
}


int main(){
    struct Node *head = NULL;
    int input;

    while (1){
        printf("请输入一个整数(-1结束):");
        scanf("%d", &input);
        if(input == -1){
            break;
        }
        insertNode(&head, input);
        printNode(head);
    }

    return 0;
}

链表的删除

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
//
// Created by Luenci on 2019/10/16.
//
#include <stdio.h>
#include <stdlib.h>

struct Node{
    int value;
    struct Node *next;
};

void insertNode(struct Node **head, int value);
void printNode(struct Node *head);
void delectNode(struct Node **head, int value);

void insertNode(struct Node **head, int value){
    struct Node *previous;
    struct Node *current;
    struct Node *new;

    current = *head;
    previous = NULL;
    while (current != NULL && current->value < value){
        previous = current;
        current = current->next;
    }
    new = (struct Node *)malloc(sizeof(struct Node));
    if (new == NULL){
        printf("分配内存失败!\n");
        exit(1);
    }

    new->value = value;
    new->next = current;

    if (previous == NULL){
        *head = new;
    } else{
        previous->next = new;
    }
}

void delectNode(struct Node **head, int value){
    struct Node *previous;
    struct Node *current;

    current = *head;
    previous = NULL;

    while (current != NULL && current->value != value){
        previous = current;
        current = current->next;
    }
    if (current == NULL){
        printf("找不到匹配的节点");
        return;
    } else{
        if(previous == NULL){
            *head = current->next;
        } else{
            previous->next = current->next;
        }
        free(current);
    }

}

void printNode(struct Node *head){
    struct Node *current;

    current = head;
    while (current != NULL){
        printf("%d ", current->value);
        current = current->next;
    }
    putchar('\n');
}


int main(){
    struct Node *head = NULL;
    int input;

    printf("开始测试插入整数...\n");
    while (1){
        printf("请输入一个整数(-1结束):");
        scanf("%d", &input);
        if(input == -1){
            break;
        }
        insertNode(&head, input);
        printNode(head);
    }

    printf("开始测试删除整数...\n");
    while (1){
        printf("请输入一个整数(-1结束):");
        scanf("%d", &input);
        if(input == -1){
            break;
        }
        delectNode(&head, input);
        printNode(head);
    }

    return 0;
}

内存池

优点:

  • 设计内存池的目标是为了保证服务器长时间高效的运行,通过对申请空间小而申请频繁的对象进行有效管理,减少内存碎片的产生,合理分配管理用户内存,从而减少系统中出现有效空间足够,而无法分配大块连续内存的情况。

结论:

  • 内存池基本上满足初期设计目标,但是她并不是完美的,有缺陷,比如,不能申请大于256字节的内存空间,无内存越界检查,无内存自动回缩功能等。只是这些对我们的影响还不是那么重要。

typedef基础

  • 相比起宏定义的直接替换, typedef是对类型的封装。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//
// Created by Luenci on 2019/10/16.
//
#include<stdio.h>

typedef int integer;

int main(void){
    integer a;
    int b;

    a = 1;
    b = a;

    printf("a = %u\n", a);
    printf("b = %d\n", b);
    printf("size of a  = %d\n", sizeof(a));

    return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//
// Created by Luenci on 2019/10/16.
//
#include<stdio.h>
#include <stdlib.h>

typedef struct Data{
    int year;
    int mouth;
    int day;
} DATA, *PDATA;

int main(void){
    struct Data *data;

    data = (PDATA)malloc(sizeof(DATA));
    if (data == NULL){
        printf("内存分配失败!\n");
        exit(1);
    }
    data->year = 2019;
    data->mouth = 10;
    data->day = 16;

    printf("%d-%d-%d\n", data->year, data->mouth, data->day);

}

进阶Typedef

  • 在变成中使用typedef目的一般有两个:
    • 一个是给变量起一个容易记住且意义明确的别名
    • 另一个是简化一些比较复杂的类型声明

共用体

  • 共用体的所有成员共享同一个内存地址。
1
2
3
4
5
6
union 共用体名称
{
	共用成员1
	共用成员2
	共用成员3
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
// Created by Luenci on 2019/10/19.
//
#include <stdio.h>
#include <string.h>

union Test{
    int i;
    double pi;
    char str[6];
};

int main(void){

    union Test test;

    test.i = 50;
    test.pi = 1.2;
    strcpy(test.str, "Luenci");

    printf("addr of test.i:%p\n", &test.i);
    printf("addr of test.pi:%p\n", &test.pi);
    printf("addr of test.str:%p\n", &test.str);

    printf("value of test.i:%d\n", test.i);
    printf("value of test.i:%.2f\n", test.pi);
    printf("value of test.i:%s\n", test.str);


    return 0;
}

out
addr of test.i:0061FF18
addr of test.pi:0061FF18
addr of test.str:0061FF18
value of test.i:1852142924
value of test.i:0.00
value of test.i:Luenci

枚举类型

  • 如果一个变量只有几种可能的值,那么就可以将其定义为枚举(enumeration)类型。
1
2
3
4
5
// 声明
enum 枚举类型名称 {枚举值名称, 枚举值名称...}

// 定义
enum 枚举类型名称 枚举变量1, 枚举变量;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
// Created by Luenci on 2019/10/19.
//
#include <stdio.h>
#include <time.h>

int main(void){

    enum  Week {sun, mon, tue, wed, thu, fri, sat};
    enum  Week today;
    struct tm *p;
    time_t t;

    time(&t);
    p = localtime(&t);

    today = p->tm_wday;

    switch (today){
        case mon:
        case tue:
        case wed:
        case thu:
        case fri:
            printf("学习!!!\n");
            break;
        case sat:
        case sun:
            printf("放假!\n");
            break;
        default:
            printf("Error!\n");
    }

    return 0;
}

位域

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

无名位域

  • 位域成员可以没有名称,只要给出数据类型和位宽即可
1
2
3
4
5
6
7
struct Test
{
	unsigned int x:100;
	unsigned int y:200;
	unsigned int z:300;
	unsigned int :424;
};

单片机

  • 单片机(Microcontrollers)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/0口和中新系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机条统,在工业控制领域广泛应用。

位操作

逻辑位运算符

  • 按位取反(~
    • 逻辑位运算符中优先级最高的是按位取反运算符,它的运算符是一个~符号,作用是将1变成0,将0变成1:
  • 按位与(&
    • 优先级第二高的是按位与运算符,它的运算符是一个&符号(而逻辑与是两个&符号)
  • 按位异或(^
    • 优先级排第三的是按位异或运算符,它的运算符是一个个符号,只有当两个操作数对应的二进制位不同时,它的结果才为1,否则为0:
  • 按位或(|
    • 逻辑位运算符中优先级最低的是按位或运算符,它的运算符是一个|符号(而逻辑或是两个I符号)
  • 和赋值号结合
    • 这四个运算符,除了按位取反只有一个操作数之外,其它三个都可以跟赋值号(=)结合到一块,使得代码更加简洁!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// Created by Luenci on 2019/10/19.
//
#include <stdio.h>

int main(void){
    int mask = 0xFF;
    int v1 = 0xABCDEF;
    int v2 = 0xABCDEF;
    int v3 = 0xABCDEF;

    v1 &= mask;
    v2 |= mask;
    v3 ^= mask;

    printf("v1 = 0x%x\n", v1);
    printf("v2 = 0x%x\n", v2);
    printf("v3 = 0x%x\n", v3);
    
    return 0;
}

out:
v1 = 0xef
v2 = 0xabcdff
v3 = 0xabcd10

移位运算符

A = 0011 1100

左移位运算符

二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。A « 2 将得到 240,即为 1111 0000

右移位运算符

二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。A » 2 将得到 15,即为 0000 1111

一些未定义行为

  • 左移、右移运算符右边的操作数如果是为负数,或者右边的操作数火于左边操作数支持的最大宽度,那么表达式的结果均是属于“未定义行为”。
  • 左边的操作数是有符号还是无符号数其实也对移位运算符有着不同的影响。无等号数肯定没问题,因为这时候变量里边所有的位都用于表示该数值的大小。但如果是有符号数,那就要区别对待了,因为有符号数的左边第一位是符号位,所以如果恰好这个操作数是个负数,那么移动之后是否覆盖符号位的决定权还是落到了编译器上。

文件操作

文件概念

  • 计算机文件(或称文件、电脑档案、档亲),是存储在基种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。所谓“长期储存设备”一般指磁盘、光盘、磁带等。而“短期存储设备”一般指计算机内存。需要注意的是,存储于长期存储设备的文件不一定是长期存储的,有些也可能是程序或条统运行中产生的临时数据,并于程序或系统退出后删除。

打开文件

您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

1
FILE *fopen( const char * filename, const char * mode );

在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:

模式描述
r打开一个已有的文本文件,允许读取文件。
w打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+打开一个文本文件,允许读写文件。
w+打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

1
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"

关闭文件

为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:

1
 int fclose( FILE *fp );

如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。

C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。

写入文件

下面是把字符写入到流中的最简单的函数:

1
int fputc( int c, FILE *fp );

函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:

1
int fputs( const char *s, FILE *fp );

函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。您也可以使用 int fprintf(FILE *fp,const char *format, …) 函数来写把一个字符串写入到文件中。

读写单个字符

fgetc函数

描述

C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

声明

下面是 fgets() 函数的声明。

1
char *fgets(char *str, int n, FILE *stream)

参数

  • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

返回值

如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。

如果发生错误,返回一个空指针。

getc函数

描述

C 库函数 int getc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。

声明

下面是 getc() 函数的声明。

1
int getc(FILE *stream)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。

返回值

该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF

单个字符写入

fputc函数

描述

C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

声明

下面是 fputc() 函数的声明。

1
int fputc(int char, FILE *stream)

参数

  • char – 这是要被写入的字符。该字符以其对应的 int 值进行传递。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

返回值

如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。

puts函数

描述

C 库函数 int putc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。

声明

下面是 putc() 函数的声明。

1
int putc(int char, FILE *stream)

参数

  • char – 这是要被写入的字符。该字符以其对应的 int 值进行传递。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。

返回值

该函数以无符号 char 强制转换为 int 的形式返回写入的字符,如果发生错误则返回 EOF。

区别

  • fgetc(fputc)函数和getc (putc)函数两个的功和描述基本上是一模一样的,它们的区别主要在于实现上:fgetc(fputc)是一个函数;而getc (putc)则是一个宏的实现
  • 一般来说宏产生较大的代码,但是避免了函数调用的堆栈操作,所以速度会比较快。
  • 由于getc是由宏实现的,对其参数可能有不止一次的调用,所以不能使用带有副作用(side effects)的参数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//
// Created by Luenci on 2019/10/20.
//
#include <stdio.h>
#include <stdlib.h>

int main(void){
    FILE *fp1;
    FILE *fp2;
    int ch;

    if ((fp1 = fopen("D:\\c_study\\test.txt", "r")) == NULL){
        printf("打开文件失败!\n");
        exit(EXIT_FAILURE);
    }

    if ((fp2 = fopen("D:\\c_study\\demo2.txt", "w")) == NULL){
        printf("打开文件夹失败!\n");
        exit(EXIT_FAILURE);
    }

    while ((ch = fgetc((fp1))) != EOF){
        fputc(ch, fp2);
    }

    fclose(fp1);
    fclose(fp2);

    return 0;
}

读写整个字符串

fgets函数

描述

C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

声明

下面是 fgets() 函数的声明。

1
char *fgets(char *str, int n, FILE *stream)

参数

  • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。

返回值

  1. 如果成功,该函数返回相同的 str 参数。
  2. 如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
  3. 如果发生错误,返回一个空指针。

fputs函数

描述

C 库函数 int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符。

声明

下面是 fputs() 函数的声明。

1
int fputs(const char *str, FILE *stream)

参数

  • str – 这是一个数组,包含了要写入的以空字符终止的字符序列。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。

返回值

该函数返回一个非负值,如果发生错误则返回 EOF。

feof()函数

描述

C 库函数 int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。

声明

下面是 feof() 函数的声明。

1
int feof(FILE *stream)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
// Created by Luenci on 2019/10/20.
//
#include <stdio.h>
#include <stdlib.h>

#define MAX 1024

int main(void){
    FILE *fp1;
    char buff[MAX];

    if ((fp1 = fopen("D:\\c_study\\demo3.txt", "w")) == NULL){
        printf("打开文件失败!\n");
        exit(EXIT_FAILURE);
    }

    fputs("Luenci 的文件操作1 。\n", fp1);
    fputs("Luenci 的文件操作2 。\n", fp1);

    fclose(fp1);

    if ((fp1 = fopen("D:\\c_study\\demo3.txt", "r")) == NULL){
        printf("打开文件失败!\n");
        exit(EXIT_FAILURE);
    }

    while (!feof(fp1)){
        fgets(buff, MAX, fp1);
        printf("%s", buff);
    }

    fclose(fp1);

    return 0;
}

格式化读写文件

fprintf()函数

描述

C 库函数 int fprintf(FILE *stream, const char *format, …) 发送格式化输出到流 stream 中。

声明

下面是 fprintf() 函数的声明。

1
int fprintf(FILE *stream, const char *format, ...)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

  • format – 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是

    1
    
    %[flags][width][.precision][length]specifier
    

    ,具体讲解如下:

specifier(说明符)输出
c字符
d 或 i有符号十进制整数
e使用 e 字符的科学科学记数法(尾数和指数)
E使用 E 字符的科学科学记数法(尾数和指数)
f十进制浮点数
g自动选择 %e 或 %f 中合适的表示法
G自动选择 %E 或 %f 中合适的表示法
o有符号八进制
s字符的字符串
u无符号十进制整数
x无符号十六进制整数
X无符号十六进制整数(大写字母)
p指针地址
n无输出
%字符
flags(标识)描述
-在给定的字段宽度内左对齐,默认是右对齐(参见 width 子说明符)。
+强制在结果之前显示加号或减号(+ 或 -),即正数前面会显示 + 号。默认情况下,只有负数前面会显示一个 - 号。
(space)如果没有写入任何符号,则在该值前面插入一个空格。
#与 o、x 或 X 说明符一起使用时,非零值前面会分别显示 0、0x 或 0X。 与 e、E 和 f 一起使用时,会强制输出包含一个小数点,即使后边没有数字时也会显示小数点。默认情况下,如果后边没有数字时候,不会显示显示小数点。 与 g 或 G 一起使用时,结果与使用 e 或 E 时相同,但是尾部的零不会被移除。
0在指定填充 padding 的数字左边放置零(0),而不是空格(参见 width 子说明符)。
width(宽度)描述
(number)要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断。
*宽度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
.precision(精度)描述
.number对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。 对于 e、E 和 f 说明符:要在小数点后输出的小数位数。 对于 g 和 G 说明符:要输出的最大有效位数。 对于 s: 要输出的最大字符数。默认情况下,所有字符都会被输出,直到遇到末尾的空字符。 对于 c 类型:没有任何影响。 当未指定任何精度时,默认为 1。如果指定时不带有一个显式值,则假定为 0。
.*精度在 format 字符串中未指定,但是会作为附加整数值参数放置于要被格式化的参数之前。
length(长度)描述
h参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)。
l参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)。
L参数被解释为长双精度型(仅适用于浮点数说明符:e、E、f、g 和 G)。
  • 附加参数 – 根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。

返回值

如果成功,则返回写入的字符总数,否则返回一个负数。

fread()函数

描述

C 库函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中。

声明

下面是 fread() 函数的声明。

1
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

参数

  • ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
  • size – 这是要读取的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值

成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。

fwrite()函数

描述

C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)ptr 所指向的数组中的数据写入到给定流 stream 中。

声明

下面是 fwrite() 函数的声明。

1
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

参数

  • ptr – 这是指向要被写入的元素数组的指针。
  • size – 这是要被写入的每个元素的大小,以字节为单位。
  • nmemb – 这是元素的个数,每个元素的大小为 size 字节。
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。

返回值

如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。

文件随机读写

ftell()函数

描述

C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。

声明

下面是 ftell() 函数的声明。

1
long int ftell(FILE *stream)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

返回值

该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。

rewind() 函数

描述

C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。

声明

下面是 rewind() 函数的声明。

1
void rewind(FILE *stream)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

fseek() 函数

描述

C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。

声明

下面是 fseek() 函数的声明。

1
int fseek(FILE *stream, long int offset, int whence)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
  • offset – 这是相对 whence 的偏移量,以字节为单位。
  • whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量描述
SEEK_SET文件的开头
SEEK_CUR文件指针的当前位置
SEEK_END文件的末尾

返回值

如果成功,则该函数返回零,否则返回非零值。

可移植性问题

想要编写可移植的代码,就需要考虑以下问题:

  • 对于以二进制模式打开的文件,fseek函数在某些操作系统可能不支持SEEK_END位置。
  • 一对于以文本模式打开的文件,fseek函数的whence参数只能取SEEK_SET才是有意义的,并且传递给offset参数的值要么是0,要么是上一次对同个文件调用fte11函数获得的返回值。

标准流和错误处理

文件流

  • 标准输入(stdin)
  • 标准输入(stdout)
  • 标准错误输出(stderr)

重定向

由于标准输出和标准错误输出通常都是直接打印到屏幕上,为了区分宅们,我们可以使用Linux shell的重定向功能:

  • 重定向标准输入使用 <
  • 重定向标准输出使用
  • 重定向标准错误输出使用 2〉

错误处理

  • 错误指示器 - ferror()

    • 描述

      C 库函数 int ferror(FILE *stream) 测试给定流 stream 的错误标识符。

      声明

      下面是 ferror() 函数的声明。

      1
      
      int ferror(FILE *stream)
      

      参数

      • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

      返回值

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

  • 使用clearerr函数可以人为地清除文件末尾指示器和错误指示器的状态。

  • ferror函数只能检测是否出错,但无法获取错误原因。不过,大多数系统函数在出现错误的时候会将错误原因记录在errno中。

  • perror函数可以直观地打印出错误原因。

    • 描述

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

      声明

      下面是 perror() 函数的声明。

      1
      
      void perror(const char *str)
      

      参数

      • str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。

      返回值

      该函数不返回任何值

  • strerror()函数直接返回错误码对应的错误信息。

    • 描述

      C 库函数 char *strerror(int errnum) 从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。

      声明

      下面是 strerror() 函数的声明。

      1
      
      char *strerror(int errnum)
      

      参数

      • errnum – 错误号,通常是 errno

      返回值

      该函数返回一个指向错误字符串的指针,该错误字符串描述了错误 errnum。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
// Created by Luenci on 2019/10/20.
//
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void){

    FILE *fp;

    if(fp = fopen("bucunzai.txt", "r") == NULL){
        printf("出错啦,原因就是:%d\n", errno);
        fprintf(stderr, "出错啦,原因就是:%s", strerror(errno) );
        perror("出错啦,原因就是:");


//        fputs("打开文件失败!\n", stderr);
        exit(EXIT_FAILURE);
    }

    fclose(fp);

    return 0;
}

I/O 缓冲区

I/O缓冲区

标准I/O提供的三种类型的缓冲模式

  • 按块执行

    • 按块缓存也称为全缓存,即在填满缓冲区后才进行实际的设备读写操作;
  • 按行执行

    • 按行缓存是指在接收到换行符(’\n’)之前,数据都是先缓存在缓冲区的;
  • 不缓存

    • 也就是允许你直接读写设备上的数据。

setvbuf()函数

描述

C 库函数 int setvbuf(FILE *stream, char *buffer, int mode, size_t size) 定义流 stream 应如何缓冲。

声明

下面是 setvbuf() 函数的声明。

1
int setvbuf(FILE *stream, char *buffer, int mode, size_t size)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
  • buffer – 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
  • mode – 这指定了文件缓冲的模式:
模式描述
_IOFBF全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
_IOLBF行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
_IONBF无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。
  • size –这是缓冲的大小,以字节为单位。

返回值

如果成功,则该函数返回 0,否则返回非零值。

fflush()函数

描述

C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区。

声明

下面是 fflush() 函数的声明。

1
int fflush(FILE *stream)

参数

  • stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个缓冲流。

返回值

如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)