[TOC]
6 结构体 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 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 1 )结构体类型定义struct Stu { char name[32 ]; char tile[32 ]; int age; char addr[50 ]; }; typedef struct Stu { int a; }Stu; 2 )结构体变量的定义struct Stu a ; struct _Stu { char name[32 ]; char tile[32 ]; int age; char addr[50 ]; }c; struct { char name[32 ]; char tile[32 ]; int age; char addr[50 ]; }e, f; 3 )结构体变量初始化struct Stu g = { "lily" , "teacher" , 22 , "guangzhou" };4 )变量和指针法操作结构体成员struct Stu h ;strcpy (h.name, "^_^" );(&h)->name struct Stu *p ;p = &h; strcpy (p->name, "abc" );(*p).name 5 )结构体数组typedef struct Teacher { char name[32 ]; int age; }Teacher; Teacher t1[2 ] = { { "lily" , 18 }, { "lilei" , 22 } }; Teacher t1[2 ] = {"lily" , 18 , "lilei" , 22 };
2、结构体套指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef struct Teacher { char *name; int age; }Teacher; typedef struct Teacher { char *name; char **stu; int age; }Teacher;
3、结构体赋值 1 2 3 4 Teacher t1 = { "lily", "teacher", 18, "beijing" }; //相同类型的结构体变量,可以相互赋值 //把t1每个成员的内容逐一拷贝到t2对应的成员中 Teacher t2 = t1;
4、浅拷贝和深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 typedef struct Teacher { char *name; int age; }Teacher; //结构体中嵌套指针,而且动态分配空间 //同类型结构体变量相互赋值 //不同结构体成员指针变量指向同一块内存 Teacher t1; t1.name = (char *)malloc(30); //浅拷贝 strcpy(t1.name, "lily"); t1.age = 22; Teacher t2; t2 = t1; //深拷贝,人为增加内存,重新拷贝一下 t2.name = (char *)malloc(30); strcpy(t2.name, t1.name);
5、结构体偏移量(了解) 1 2 3 4 5 6 7 8 9 10 11 12 13 //结构体类型定义下来,内部的成员变量的内存布局已经确定 typedef struct Teacher { char name[64]; //64 int age; //4 int id; //4 }Teacher; Teacher t1; Teacher *p = &t1; int n1 = (int)(&p->age) - (int)p; //相对于结构体首地址 int n2 = (int)&((Teacher *)0)->age; //绝对0地址的偏移量
6、结构体字节对齐(以空间换时间) 详情请看《结构体字节对齐规则.doc》
1 2 3 实际上访问特定类型的变量只能在特定的地址访问, 这就需要 各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是 内存对齐。
7 文件操作 一、基本概念 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1、文件分类 普通文件:存放在硬盘中的文件 设备文件:屏幕、键盘等特殊文件 文本文件:ASCII文件,每个字节存放一个字符的ASCII码,打开文件看到的是文本信息 二进制文件:数据按其在内存中的存储形式原样存放,打开文件看到的是乱码 2、文件缓冲区(了解) ANSI C(标准C语言库函数)标准采用“缓冲文件系统”处理数据文件。 写文件(设备文件除外),并不会直接写到文件中,会先放在缓冲区,默认情况下,关闭文件或缓冲区满了才写到文件。 如果没有关闭文件,缓冲区也没有满,可以通过程序正常结束,或者人为刷新缓冲区fflush(fd)来把缓冲区的内容写到文件中。 缓冲区了解一下即可,增加缓冲区只是为了提高效率,减少频繁交互的次数,我们写程序基本上不用关心。
缓冲区验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <stdio.h> int main(void) { char buf[] = "this is a test\n"; FILE *fp = fopen("/machine/test.txt", "w+"); fputs(buf, fp); fflush(fp); fclose(fp); printf("\n"); return 0; }
二、读写文件 1、按照字符读写文件:fgetc()、fputc() 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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> //stdout stdin stderr int main01(void) { fputc('a', stdout); //stdout -> 屏幕, 打印普通信息 char ch; ch = fgetc(stdin); //stdin -> 键盘 printf("ch = %c\n", ch); //fprintf(stderr, "%c", ch ); //stderr -> 屏幕, 错误信息 fputc(ch, stderr); printf("\n"); return 0; } //相对路径 int main02(void) { FILE *fp = NULL; //下面两个等级 //C:\\Users\\apple\\Documents\\C提高视频\\03.txt, windows //C:/Users/apple/Documents/C提高视频/03.txt, windows linux // "C:\\Users" windows的写法 // "C:/Users" linux, windows都支持, 建议"/" //相对路径,/ , 45度, ./, ../(建议), linux, windows //vs: 编译代码时,路径相对于项目工程(当前代码) //直接运行可执行程序,路径相对于程序 char *p = "1234353454364"\ "lgkjfdljhlkfdjhlfdjk"; printf("%s\n", p); //r+ : 允许读和写,文件必须已存在 //w+ : 允许读和写,如果文件不存在则创建,如果文件 已存在则把文件长度截断为 0 字节再重新 写 。 fp = fopen("./05.txt", "w+"); if (fp == NULL) { perror("fopen"); system("pause"); return -1; } if (fp != NULL) { fclose(fp); fp = NULL; } printf("\n"); return 0; } //写文件 void my_fputc(char *path) { FILE *fp = NULL; //"w+", 写读方式打开,如果文件不存在,则创建 // 如果文件存在,清空内容,再写 fp = fopen(path, "w+"); if (fp == NULL) { //字符串 perror("my_fputs fopen"); return; } //写文件 char buf[] = "this is a test for fputc"; int i = 0; int n = strlen(buf); for (i = 0; i < n; i++) { //返回值,成功写入文件的字符 int ch = fputc(buf[i], fp); printf("ch = %c\n", ch); } if (fp != NULL) { fclose(fp); fp = NULL; } } //读文件 void my_fgetc(char *path) { FILE *fp = NULL; //读写方式打开,如果文件不存在,打开失败 fp = fopen(path, "r+"); if (fp == NULL) { perror("my_fgetc fopen"); return; } char ch; #if 0 while ( ( ch = fgetc(fp) ) != EOF ) { printf("%c", ch); } printf("\n"); #endif while (!feof(fp)) //文件没有结束 { ch = fgetc(fp); printf("%c", ch); } printf("\n"); if (fp != NULL) { fclose(fp); fp = NULL; } } int main03(void) { //my_fputc("../03.txt"); my_fgetc("../03.txt"); printf("\n"); return 0; }
2、按照行读写文件:fputs()、fgets() 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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> //写 void my_fputs(char *path) { FILE *fp = NULL; //"w+", 写读方式打开,如果文件不存在,则创建 // 如果文件存在,清空内容,再写 fp = fopen(path, "w+"); if (fp == NULL) { //字符串 perror("my_fputs fopen"); return; } //写文件 char *buf[] = { "123456\n", "bbbbbbbbbb\n", "ccccccccccc\n" }; int i = 0; int n = 3; for (i = 0; i < n; i++) { //返回值,成功, 和失败, 成功是0, 失败非0 int len = fputs(buf[i], fp); printf("len = %d\n", len); } if (fp != NULL) { fclose(fp); fp = NULL; } } //读 void my_fgets(char *path) { FILE *fp = NULL; //读写方式打开,如果文件不存在,打开失败 fp = fopen(path, "r+"); if (fp == NULL) { perror("my_fgets fopen"); return; } char buf[100]; while (!feof(fp)) //文件没有结束 { //sizeof(buf), 最大值,放不下,只能放100, 不超过,实际大小存放 //返回值,成功读取文件内容 //把“\n”会读取,以“\n”作为换行的标志 //fgets()读取完毕后,自动加字符串结束符0 //memset(buf, 'a', sizeof(buf)); char *p = fgets(buf, sizeof(buf), fp); if (p != NULL) { printf("buf = %s", buf); printf("p = %s", p); } } printf("\n"); if (fp != NULL) { fclose(fp); fp = NULL; } } int main(void) { my_fputs("../04.txt"); my_fgets("../04.txt"); printf("\n"); system("pause"); return 0; }
3、按照块读写文件:fread()、fwirte() 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 typedef struct Stu { char name[50]; int id; }Stu; Stu s[3]; 1)写文件 //写文件,按块的方式写 //s:写入文件内容的内存首地址 //sizeof(Stu):块数据的大小 //3:块数, 写文件数据的大小 sizeof(Stu) *3 //fp:文件指针 //返回值,成功写入文件的块数目,不是数据总长度 int ret = fwrite(s, sizeof(Stu), 3, fp); printf("ret = %d\n", ret); 2)读文件 //读文件,按块的方式读 //s:放文件内容的首地址 //sizeof(Stu):块数据的大小 //3:块数, 读文件数据的大小 sizeof(Stu) *3 //fp:文件指针 //返回值,成功读取文件内容的块数目,不是数据总长度 int ret = fread(s, sizeof(Stu), 3, fp); printf("ret = %d\n", ret);
4、按照格式化进行读写文件:fprintf()、fscanf() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1)写文件 //格式化写文件 int a = 250; int b = 10; int c = 20; //和printf()用法一样,只是printf是往屏幕(标准输出)写内容 //fprintf往指定的文件指针写内容 //返回值:成功:写入文件内容的长度,失败:负数 fprintf(fp, "Tom = %d, just like %d, it is %d", a, b, c); 2)读文件 int a, b, c; //按格式取 fscanf(fp, "Tom = %d, just like %d, it is %d", &a, &b, &c); printf("a = %d, b = %d, c = %d\n", a, b, c);
5、随机读写 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 1)fseek ftell rewind //文件光标移动到文件结尾 //SEEK_SET:文件开头 //SEEK_CUR:文件当前位置 //SEEK_END:文件结尾 //文件指针 偏移量 参考点 fseek(fp, 0, SEEK_END); //获取光标到文件开头文件的大小ftell long size = ftell(fp); //文件光标恢复到开始位置 rewind(fp); 2)使用 typedef struct Stu { char name[50]; int id; }Stu; Stu tmp; //读第3个结构体 //假如文件中写了三个结构体 //从起点位置开始,往后跳转2个结构体的位置 fseek(fp, 2*sizeof(Stu), SEEK_SET); //从结尾位置开始,往前跳转一个结构体的位置 //fseek(fp, -1 * (int)sizeof(Stu), SEEK_END); int ret = 0; ret = fread(&tmp,sizeof(Stu), 1, fp); if(ret == 1) { printf("[tmp]%s, %d\n", tmp.name, tmp.id); } //把文件光标移动到文件开头 //fseek(fp, 0, SEEK_SET); rewind(fp);
三、综合案例 1、加密文件读写(使用别人写好的接口)
2、配置文件读写(自定义接口) 8 链表和函数指针 一、链表 1、数组和链表的区别 1 2 3 4 5 6 7 8 9 10 11 12 13 数组:一次性分配一块连续的存储区域 优点: 随机访问元素效率高 缺点: 需要分配一块连续的存储区域(很大区域,有可能分配失败) 删除和插入某个元素效率低 链表:现实生活中的灯笼 优点: 不需要一块连续的存储区域 删除和插入某个元素效率高 缺点: 随机访问元素效率低
2、相关概念 1 2 3 4 5 6 7 8 节点:链表的每个节点实际上是一个结构体变量,节点,既有 数据域 也有 指针域 typedef struct Node { int id; //数据域 struct Node *next; //指针域 }SLIST; 尾结点:next指针指向NULL
3、结构体套结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 typedef struct A { int b; }A; /* 1、结构体可以嵌套另外一个结构体的任何类型变量 2、结构体嵌套本结构体普通变量(不可以) 本结构体的类型大小无法确定,类型本质:固定大小内存块别名 3、结构体嵌套本结构体指针变量(可以) 指针变量的空间能确定,32位, 4字节, 64位, 8字节 */ typedef struct B { int a; A tmp1; //ok A *p1; //ok //struct B tmp2; //err struct B *next; //32位, 4字节, 64位, 8字节 }B;
4、链表的操作 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 实际上是指针的拓展应用:指向指向谁,就把谁的地址赋值给指针。 typedef struct Stu { int id; //数据域 char name[100]; struct Stu *next; //指针域 }Stu; (1)静态链表 //初始化三个结构体变量 Stu s1 = { 1, "mike", NULL }; Stu s2 = { 2, "lily", NULL }; Stu s3 = { 3, "lilei", NULL }; s1.next = &s2; //s1的next指针指向s2 s2.next = &s3; s3.next = NULL; //尾结点 Stu *p = &s1; while (p != NULL) { printf("id = %d, name = %s\n", p->id, p->name); //结点往后移动一位 p = p->next; //&s2 } (2)动态链表 //Stu *p1 = NULL; //p1 = (Stu *)malloc(sizeof(Stu)); Stu *p1 = (Stu *)malloc(sizeof(Stu)); Stu *p2 = (Stu *)malloc(sizeof(Stu)); Stu *p3 = (Stu *)malloc(sizeof(Stu)); p1->next = p2; p2->next = p3; p3->next = NULL; //尾节点 Stu *tmp = p1; while(tmp != NULL) { printf("id = %d, name = %s\n", tmp->id, tmp->name); //结点往后移动一位 tmp = tmp->next; }
二、函数指针 1、指针函数,它是函数,返回指针类型的函数 1 2 3 4 5 6 7 8 9 //指针函数 //()优先级比*高,它是函数,返回值是指针类型的函数 //返回指针类型的函数 int *fun2() { int *p = (int *)malloc(sizeof(int)); return p; }
2、函数指针,它是指针,指向函数的指针 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 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。 函数指针变量,它也是变量,和int a变量的本质是一样的。 int fun(int a) { printf("a ========== %d\n", a); return 0; } //定义函数指针变量有3种方式: (1)先定义函数类型,根据类型定义指针变量(不常用) //有typedef是类型,没有是变量 typedef int FUN(int a); //FUN是函数类型,类型模式为: int fun(int); FUN *p1 = NULL; //函数指针变量 p1 = fun; //p1 指向 fun 函数 fun(5); //传统调用 p1(6); //函数指针变量调用方式 (2)先定义函数指针类型,根据类型定义指针变量(常用) //()()优先级相同,从左往右看 //第一个()代表指针,所以,它是指针 //第二个括号代表函数,指向函数的指针 typedef int(*PFUN)(int a); //PFUN是函数指针类型 PFUN p2 = fun; //p2 指向 fun p2(7); (3)直接定义函数指针变量(常用) int(*p3)(int a) = fun; p3(8); int(*p4)(int a); p4 = fun; p4(9);
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 void add() {} void minus() {} void multi() {} void divide() {} void myexit() {} //函数指针变量,fun1指向add()函数 void(*fun1)() = add; fun1(); //调用add()函数 //函数指针数组 void(*fun[5])() = { add, minus, multi, divide, myexit }; //指针数组 char *buf[] = { "add", "min", "mul", "div", "exit" }; char cmd[100]; int i = 0; while (1) { printf("请输入指令:"); scanf("%s", cmd); for (i = 0; i < 5; i++) { if (strcmp(cmd, buf[i]) == 0) { fun[i](); break; //跳出for()循环,最近的循环 } } }
4、回调函数,函数的形参为:函数指针变量 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 int add(int a, int b) { return a + b; } int minus(int a, int b) { return a - b; } //int(*p)(int a, int b), p 为函数指针变量 void fun(int x, int y, int(*p)(int a, int b) ) { int a = p(x, y); //回调函数 printf("a = %d\n", a); } typedef int(*Q)(int a, int b); //Q 为函数指针类型 void fun2(int x, int y, Q p)//p 为函数指针变量 { int a = p(x, y); //回调函数 printf("a = %d\n", a); } //fun()函数的调用方式 fun(1, 2, add); fun2(10, 5, minus);
三、函数的递归 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 递归:函数可以调用函数本身(不要用main()调用main(),不是不行,是没有这么做,往往得不到你想要的结果) (1)普通函数调用(栈结构,先进后出,先调用,后结束) void funB(int b) { printf("b = %d\n", b); return; } void funA(int a) { funB(a-1); printf("a = %d\n", a); } 调用流程: funA(2) -> funB(1) -> printf(b) (离开funB(),回到funA()函数)-> printf(a) (2)函数递归调用(调用流程和上面是一样,换种模式,都是函数的调用而已) void fun(int a) { if(a == 1) { printf("a == %d\n", a); return; //中断函数很重要 } fun(a-1); printf("a = %d\n", a); } fun(2);
9 预处理 一、预处理概念 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1)预处理的基本概念 C 语言对源程序处理的四个步骤:预处理、编译、汇编、链接。 预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程 序源代码进行的处理。 这个过程并不对程序的源代码语法进行解析, 但它会把 源代码分割或处理成为特定的符号为下一步的编译做准备工作。 2)预编译命令 C编译器提供的预处理功能主要有以下四种: 1)文件包含 #include 2)宏定义 #define 3)条件编译 #if #endif .. 4)一些特殊作用的预定义宏
二、文件包含处理:#include
1 2 3 4 5 6 7 8 9 10 11 12 #include< > 与 #include ""的区别 ""表示系统先在 file1.c 所在的当前目录找 file1.h,如果找不到,再按系 统指定的目录检索。 < >表示系统直接按系统指定的目录检索。 注意: 1. #include <>常用于包含库函数的头文件 2. #include ""常用于包含自定义的头文件 3. 理论上#include 可以包含任意格式的文件(.c .h 等) ,但我们一 般用于头文件的包含。
三、宏定义:#define 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1)基本概念 在源程序中,允许一个标识符(宏名)来表示一个语言符号字符串用指 定的符号代替指定的信息。 在 C 语言中,“宏”分为:无参数的宏和有参数的宏。 2)无参数的宏定义 #define 宏名 字符串 例: #define PI 3.141926 在编译预处理时,将程序中在该语句以后出现的所有的 PI 都用 3.1415926 代替。 这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时 将宏名替换成字符串的过程称为“宏展开”。宏定义,只在宏定义的文件中起 作用。 3)带参数的宏定义 1) 格式:#define 宏名(形参表) 字符串 2) 调用:宏名(形参表) 3) 宏展开:进行宏替换 #define S(a,b) a*b …… Area = S(3,2);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 说明: 1) 宏名一般用大写,以便于与变量区别 2) 字符串可以是常数、表达式等 3) 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错 4) 宏定义不是 C 语言,不在行末加分号 5) 宏名有效范围为从定义到本源文件结束 6) 可以用#undef 命令终止宏定义的作用域 7) 在宏定义中,可以引用已定义的宏名
宏定义
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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX2(a, b) (a) > (b) ? (a) : (b) #define MAX3(a, b, c) (a) > ( MAX2(b, c) ) ? (a) : ( MAX2(b, c) ) // 1, 2, 3 #define PI 3.14 #define TEST(a, b) (a)*(b) void fun2() { //int a = A; //err } void fun() { #define A 10 //定义了宏定义,下面的代码都可以用,类似于全局变量 int i = 10; } void test() { int a = A; //ok //int j = i; //err //取消宏定义 #undef A //int b = A; //err } int main(void) { int r = 10; printf("%lf\n", PI*r*r); //printf("%lf\n", 3.14*r*r); int a = TEST(1, 2); //int a = 1*2 a = TEST(1 + 1, 2); // 2 * 2 //a = 1+1*2 = 3 printf("a = %d\n", a); printf("%d\n", MAX2(1, 2) ); printf("%d\n", MAX3(1, 2, 3)); // printf("\n"); system("pause"); return 0; }
四、条件编译:#if #endif 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 )基本概念 一般情况下,源程序中所有的行都参加编译。 但有时希望对部分源程序 行只在满足一定条件时才编译, 即对这部分源程序行指定编译条件。 2 )条件编译的作用 1 )防止头文件被重复包含引用 #ifndef _SOMEFILE_H #define _SOMEFILE_H #endif 2 ) 软件裁剪
条件编译
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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #define D1 #define TEST 0 int main(void) { // #ifdef:测试存在 // #ifndef:测试不存在 #ifndef D1 printf("D1111111111111111111\n"); #else printf("others\n"); #endif // #if 表达式:根据表达式定义 #if TEST printf("1111111111111\n"); #else printf("2222222222222222"); #endif #if 1 #endif printf("\n"); system("pause"); return 0; }
10 动态库 一、静态库的封装和使用 1 2 3 4 5 6 7 8 linux(mac)的静态链接库后缀是 .a win是 .lib 编译代码时,需要链接此文件 静态库和可执行文件没有依赖关系 意义:不开源,第三方提供API
CLion创建/调用静态链接库
创建
1 2 3 新建工程 > C Library > type: static run > build > 会在debug文件夹下生成 .a文件
调用
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> //自定义的头文件 #include "library.h" int main(void) { //使用library.h 里的函数 hello(); printf("\n"); return 0; }
需要编写CMakeLists.txt,将.a文件添加为静态库
1 2 set(LIB_DIR /machine/codding/c_0906) link_libraries( ${LIB_DIR}/libc_static_lib_0909.a)
二、动态库的封装和使用 1 2 3 4 5 6 7 8 9 linux的动态链接库后缀是 .so mac .dylib** win是 .dll 运行程序时,需要链接此文件 静态库和可执行文件没有依赖关系 意义:也称为共享库,可以被多个可执行文件同时使用,减少程序体积
CLion创建/调用动态链接库
创建
1 2 3 新建工程 > C Library > type: shared run > build > 会在debug文件夹下生成 .dylib文件
调用
1 将 .dylib文件和.h文件拷贝到main.c同级
1 2 3 4 5 6 7 8 9 10 11 12 #include <stdio.h> //自定义的头文件 #include "library.h" int main(void) { //使用library.h 里的函数 hello(); printf("\n"); return 0; }
需要编写CMakeLists.txt,将.a文件添加为静态库
1 2 set(LIB_DIR /machine/codding/c_0906) link_libraries( ${LIB_DIR}/libc_shared_lib_0909.dylib)