# c_learning **Repository Path**: telei/c_learning ## Basic Information - **Project Name**: c_learning - **Description**: C语言学习记录 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-04 - **Last Updated**: 2021-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # &在C语言中的用法 1. 逻辑与:if((a>1)&&(b<0)) 2. 位运算与:x=a&b 3. 逻辑与赋值:x&=y;与 x=x&y含义相同 4. 求地址运算符:p=&x;读法:把x的地址赋给p(指针) # *在C语言中的用法 1. 乘法运算:x=y*z; 2. 乘法赋值运算:x*=y;相当于x=x*y 3. 注释:/*这里是你的注释*/ 4. 指针的声明:int *p 或 int* p; 读法:p是指向一个整数类型的指针 5. 复合指针: int **p; 或 int** p; 读法 p是一个指向一个指向整数类型的指针的指针。(同样道理, int***p等等) 6. 解引用: x=*p 把指针p指向的值赋值给x # 1.关键字 一般用在变量名前或函数名前 C语言提供了一下几种不同的存储类型: **(1) 自动变量(auto)** **(2) 静态变量(static)** **(3) 外部变量(extern)** **(4) 寄存器变量(register)** # 2.定义 声明 ***声明可以多次,定义只能一次*** **定义**:表示创建变量或分配存储单元 **声明**:说明变量的性质,但并不分配存储单元 ~~~c extern int i; //是声明,不是定义,没有分配内存 int i; //是定义 ~~~ ![img](https://img2018.cnblogs.com/blog/1590962/201907/1590962-20190716223706760-1422940322.jpg) # 3.转义字符 | **转义序列** | **含义** | | :------------- | :----------------------------- | | **\\** | **\ 字符** | | **\'** | **' 字符** | | **\"** | **" 字符** | | **\?** | **? 字符** | | **\a** | **警报铃声** | | **\b** | **退格键** | | **\f** | **换页符** | | **\n** | **换行符** | | **\r** | **回车** | | **\t** | **水平制表符** | | **\v** | **垂直制表符** | | **\ooo** | **一到三位的八进制数** | | **\xhh . . .** | **一个或多个数字的十六进制数** | # 4.定义常量 1. **使用 #define 预处理器。** 2. **使用 const 关键字。** ~~~c // 常量定义 #include #define LENGTH 10 #define WIDTH 5 #define NEWLINE '\n' int main() { int area; area = LENGTH * WIDTH; printf("value of area : %d", area); printf("%c", NEWLINE); const int LENGTH1 = 10; const int WIDTH1 = 5; const char NEWLINE1 = '\n'; int area1; area1 = LENGTH1 * WIDTH1; printf("value of area : %d", area); printf("%c", NEWLINE); return 0; } ~~~ # 5.存储类 - **auto** 存储类是所有局部变量默认的存储类 - **register** 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置 - **static** 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值 - **extern** 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 **extern** 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置 # 6.位运算符 | 运算符 | 描述 | 实例 | | :----- | :----------------------------------------------------------- | :----------------------------------------------------------- | | & | 按位与操作,按二进制位进行"与"运算。运算规则:`0&0=0; 0&1=0; 1&0=0; 1&1=1;` | (A & B) 将得到 12,即为 0000 1100 | | \| | 按位或运算符,按二进制位进行"或"运算。运算规则:`0|0=0; 0|1=1; 1|0=1; 1|1=1;` | (A \| B) 将得到 61,即为 0011 1101 | | ^ | 异或运算符,按二进制位进行"异或"运算。运算规则:`0^0=0; 0^1=1; 1^0=1; 1^1=0;` | (A ^ B) 将得到 49,即为 0011 0001 | | ~ | 取反运算符,按二进制位进行"取反"运算。运算规则:`~1=-2; ~0=-1;` | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 | | << | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 | | >> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 | ~~~c // 位运算符 #include int main() { unsigned int a = 60; /* 60 = 0011 1100 */ unsigned int b = 13; /* 13 = 0000 1101 */ int c = 0; c = a & b; /* 12 = 0000 1100 */ printf("Line 1 - c 的值是 %d\n", c); c = a | b; /* 61 = 0011 1101 */ printf("Line 2 - c 的值是 %d\n", c); c = a ^ b; /* 49 = 0011 0001 */ printf("Line 3 - c 的值是 %d\n", c); c = ~a; /*-61 = 1100 0011 */ printf("Line 4 - c 的值是 %d\n", c); c = a << 2; /* 240 = 1111 0000 */ printf("Line 5 - c 的值是 %d\n", c); c = a >> 2; /* 15 = 0000 1111 */ printf("Line 6 - c 的值是 %d\n", c); } ~~~ # 7.赋值运算符 | 运算符 | 描述 | 实例 | | :----- | :----------------------------------------------------------- | :------------------------------ | | = | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C | | += | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A | | -= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A | | *= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A | | /= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A | | %= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A | | <<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 | | >>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 | | &= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 | | ^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 | | \|= | 按位或且赋值运算符 | C \|= 2 等同于 C = C \| 2 | ~~~c // 赋值运算符 #include int main() { int a = 21; int c; c = a; printf("Line 1 - = 运算符实例,c 的值 = %d\n", c); c += a; printf("Line 2 - += 运算符实例,c 的值 = %d\n", c); c -= a; printf("Line 3 - -= 运算符实例,c 的值 = %d\n", c); c *= a; printf("Line 4 - *= 运算符实例,c 的值 = %d\n", c); c /= a; printf("Line 5 - /= 运算符实例,c 的值 = %d\n", c); c = 200; c %= a; printf("Line 6 - %%= 运算符实例,c 的值 = %d\n", c); c <<= 2; printf("Line 7 - <<= 运算符实例,c 的值 = %d\n", c); c >>= 2; printf("Line 8 - >>= 运算符实例,c 的值 = %d\n", c); c &= 2; printf("Line 9 - &= 运算符实例,c 的值 = %d\n", c); c ^= 2; printf("Line 10 - ^= 运算符实例,c 的值 = %d\n", c); c |= 2; printf("Line 11 - |= 运算符实例,c 的值 = %d\n", c); } ~~~ # 8.三元运算符 | 运算符 | 描述 | 实例 | | :------- | :--------------- | :----------------------------------- | | sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 | | & | 返回变量的地址。 | &a; 将给出变量的实际地址。 | | * | 指向一个变量。 | *a; 将指向一个变量。 | | ? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y | ~~~c #include int main() { int a = 4; short b; double c; int* ptr; /* sizeof 运算符实例 */ printf("Line 1 - 变量 a 的大小 = %lu\n", sizeof(a) ); printf("Line 2 - 变量 b 的大小 = %lu\n", sizeof(b) ); printf("Line 3 - 变量 c 的大小 = %lu\n", sizeof(c) ); /* & 和 * 运算符实例 */ ptr = &a; /* 'ptr' 现在包含 'a' 的地址 */ printf("a 的值是 %d\n", a); printf("*ptr 是 %d\n", *ptr); /* 三元运算符实例 */ a = 10; b = (a == 1) ? 20: 30; printf( "b 的值是 %d\n", b ); b = (a == 10) ? 20: 30; printf( "b 的值是 %d\n", b ); } ~~~ # 9.函数 ```c return_type function_name( parameter list ) { body of the function } ``` - **返回类型:**一个函数可以返回一个值。**return_type** 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 **void**。 - **函数名称:**这是函数的实际名称。函数名和参数列表一起构成了函数签名。 - **参数:**参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。 - **函数主体:**函数主体包含一组定义函数执行任务的语句。 ~~~c // 函数 #include int max(int x, int y) { return x > y ? x : y; } //传值方式调用函数 void swap(int x, int y) { int temp = x; x = y; y = temp; return; } //引用方式调用函数 void swap1(int *x, int *y) { int temp = *x; *x = *y; *y = temp; return; } int main() { int x = 90; int y = 80; int result = max(x, y); printf("result: %d\n", result); // 传值方式调用虽然在函数内改变了 a 和 b 的值,但是实际上 a 和 b 的值没有发生变化 swap(x, y); printf("交换后,x 的值: %d\n", x); printf("交换后,y 的值: %d\n", y); // 引用调用在函数内改变了 a 和 b 的值,实际上也改变了函数外 a 和 b 的值 swap1(&x, &y); printf("交换后,x 的值: %d\n", x); printf("交换后,y 的值: %d\n", y); return 0; } ~~~ # 10.实参 形参 ~~~c #include int test(int,int); // 形参,只声明 int main() { int a,b; printf("%d",test(5,3)); // 实参,已赋值 } int test(int a,int b) // 形参 { a=a+b; return a; } ~~~ # 11.enum(枚举) ~~~c 格式: enum 枚举名 {枚举元素1,枚举元素2,……}; // enum枚举 #include enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN } day; int main() { // 遍历枚举元素 for (day = MON; day <= SUN; day++) { printf("枚举元素:%d \n", day); } } ~~~ # 12.字符串 ~~~c // 字符串 #include int main() { char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'}; printf("菜鸟教程: %s\n", site); char str1[14] = "runoob"; char str2[14] = "google"; char str3[14]; int len; /* 复制 str1 到 str3 */ strcpy(str3, str1); printf("strcpy( str3, str1) : %s\n", str3); /* 连接 str1 和 str2 */ strcat(str1, str2); printf("strcat( str1, str2): %s\n", str1); /* 连接后,str1 的总长度 */ len = strlen(str1); printf("strlen(str1) : %d\n", len); return 0; } ~~~ | 序号 | 函数 & 目的 | | :--- | :----------------------------------------------------------- | | 1 | **strcpy(s1, s2);** 复制字符串 s2 到字符串 s1。 | | 2 | **strcat(s1, s2);** 连接字符串 s2 到字符串 s1 的末尾。 | | 3 | **strlen(s1);** 返回字符串 s1 的长度。 | | 4 | **strcmp(s1, s2);** 如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0。 | | 5 | **strchr(s1, ch);** 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 | | 6 | **strstr(s1, s2);** 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 | # 13.结构体 ~~~c // 结构体 #include struct Books { char title[50]; char author[50]; char subject[100]; int book_id; } book = {"C 语言", "RUNOOB", "编程语言", 123456}; int main() { printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id); struct Books book1; strcpy(book1.title, "book1_title"); strcpy(book1.author, "book1_author"); strcpy(book1.subject, "book1_subject"); book1.book_id = 456789; printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book1.title, book1.author, book1.subject, book1.book_id); // 结构作为函数参数 printBook(book); } void printBook( struct Books book ) { printf( "Book title : %s\n", book.title); printf( "Book author : %s\n", book.author); printf( "Book subject : %s\n", book.subject); printf( "Book book_id : %d\n", book.book_id); } ~~~ # 14.共用体 ~~~c // 共用体 #include #include union Data { int i; float f; char str[20]; }; int main() { union Data data; printf("Memory size occupied by data : %d\n", sizeof(data)); union Data data1; data1.i = 10; data1.f = 20.1; strcpy(data1.str, "data1_str"); printf("data1.i : %d\n", data1.i); printf("data1.f : %f\n", data1.f); printf("data1.str : %s\n", data1.str); return 0; } ~~~ # 15.位域 ~~~c #include #include struct { unsigned int age : 3; } Age; int main( ) { Age.age = 4; printf( "Sizeof( Age ) : %d\n", sizeof(Age) ); printf( "Age.age : %d\n", Age.age ); Age.age = 7; printf( "Age.age : %d\n", Age.age ); Age.age = 8; // 二进制表示为 1000 有四位,超出 printf( "Age.age : %d\n", Age.age ); return 0; } int main(){ struct bs{ unsigned a:1; unsigned b:3; unsigned c:4; } bit,*pbit; bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */ printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */ pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */ pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */ pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */ pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */ printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */ } ~~~ # 16.typedef | #define **#define** 是 C 指令,用于为各种数据类型定义别名,与 **typedef** 类似,但是它们有以下几点不同: - **typedef** 仅限于为类型定义符号名称,**#define** 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。 - **typedef** 是由编译器执行解释的,**#define** 语句是由预编译器进行处理的。 # 17.输出 ~~~c // 输出 #include int main() { int c; printf("Enter a value :"); c = getchar(); printf("\nYou entered: "); putchar(c); printf("\n"); // char str[100]; // printf("Enter a value :"); // gets(str); // printf("\nYou entered: "); // puts(str); return 0; } ~~~ # 18.预处理器 | 指令 | 描述 | | :----------- | :----------------------------------------------------------- | | **#define** | **定义宏** | | **#include** | **包含一个源代码文件** | | **#undef** | **取消已定义的宏** | | **#ifdef** | **如果宏已经定义,则返回真** | | **#ifndef** | **如果宏没有定义,则返回真** | | **#if** | **如果给定条件为真,则编译下面代码** | | **#else** | **#if 的替代方案** | | **#elif** | **如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码** | | **#endif** | **结束一个 #if……#else 条件编译块** | | **#error** | **当遇到标准错误时,输出错误消息** | | **#pragma** | **使用标准化方法,向编译器发布特殊的命令到编译器中** | ## 预定义宏 | 宏 | 描述 | | :------- | :-------------------------------------------------- | | __DATE__ | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 | | __TIME__ | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 | | __FILE__ | 这会包含当前文件名,一个字符串常量。 | | __LINE__ | 这会包含当前行号,一个十进制常量。 | | __STDC__ | 当编译器以 ANSI 标准编译时,则定义为 1。 | ~~~c // 预处理器 #include 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__); } ~~~ # 19.头文件 ***头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件。*** ## 语法 ```c #include ``` # 20.指针 #### 指针和指针变量的关系 -   指针就是地址,地址就是指针 -   地址就是内存单元的编号 -   指针变量是存放地址的变量 -   指针和指针变量是两个不同的概念 -   但是要注意: 通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样 -   指针里存的是100, 指针: 地址–具体 -   指针里存的是地址, 指针: 指针变量 – 可变 #### 为什么要使用指针 -   直接访问硬件 (opengl 显卡绘图) -   快速传递数据(指针表示地址) -   返回一个以上的值(返回一个数组或者结构体的指针) -   表示复杂的数据结构(结构体) -   方便处理字符串 -   指针有助于理解面向对象 #### *号的三种含义 - 数学运算符: 3 * 5 -   定义指针变量: int* p; -   指针运算符(取值): *p (取p的内容(地址)在内存中的值) # 21.串 **串是由零个或多个任意字符组成的字符序列** ## 21.1术语 - **子串与主串**: - 串中任意连续的字符组成的子序列称为该串的子串。包含子串的串相应地称为主串。 - **子串的位置**: - 子串的第一个字符在主串中的序号称为子串的位置。 - **串相等**: - 称两个串是相等的,是指两个串的长度相等且对应字符都相等。 - **空串:** - 不含任何字符的串称为空串,即串的长度n=0时的串为空串 - **空格串/空白串:** - 由一个或多个称为空格的特殊字符组成的串称为空格串,它的长度是串中空格字符的个数 - **模式匹配:** - **子串的定位运算又称为串的模式匹配**,是一种求子串的主串中第几位出现的第一个字符的位置 - **被匹配的主串被称为目标串** - **子串称为目标** ## 模式匹配: 1. #### 暴力匹配算法(BF) 1. 也称:朴素、古典算法 2. 从主串的第一个字符开始与子串进行比对,如果相等则逐一比对后续字符;如果不等则从主串第二个字符开始匹配子串,直到发现全部相等的子串 2. #### KMP算法 1. 当主串和模式串在某个字符不匹配时,指示主串匹配位置的变量不需要回退,而直接回退指示模式串匹配位置的变量,而且该变量回退时也不需要像BF算法中回退到起始位置,而是基于原来已匹配过的结果来回退 ## 21.2串的存储结构 ### 1.顺序串存储 #### 1.1顺序存储的类型定义 顺序串的类型定义与顺序表的定义相似,可以用一个字符数组和一个整型变量表示其中字符数组存储串,整型变量表示串的长度 #### 1.2存储方式 - 非紧凑存储。设S=“Hello boy”,计算机字长为32位(4个Byte),用非紧凑格式一个地址只能存一个字符。其优点是运算处理简单,但缺点存储空间浪费 - 紧凑存储,用紧凑格式一个地址能存4个字符。紧凑存储的优点是空间利用率高,缺点是对串中字符处理的效率低 ### 2.链式存储 #### 2.1链接存储的描述 用链表存储字符串,每个结点有两个域:一个数据域(Data)和一个指针域(next) - 在串的链式存储结构中,如下: 数据域(Data)——存放串中的字符。 指针域(next)——存放后继节点的地址 - 链接存储的优点——插入、删除运算方便 - 链接存储的缺点——存储、检索效率低 - #### 2.2串的存储密度 - 存储密度=串值所占的存储位/实际分配的存储位 - 串链接存储的存储密度小,存储量比较浪费,但运算处理,特别是对串的连接等操作的实现比较方便 ### 3.串的堆分配存储结构 #### 3.1堆分配存储的方法 - 开辟一块地址连续的存储空间,用于存储各串的值,该存储空间称为“堆”(即自由存储区) - 另外建立一个索引表,用来存储串的名字、长度和该串在“堆”中存储的起始地址 - 程序执行过程中,每产生一个串,系统就从“堆”中分配一块大小与串的长度相同的连续空间,用于存储该串的值,并且在索引表中增加一个索引项,用于登记该串的信息。 #### 3.2带长度的索引表的C语言描述 ~~~c typedef struct{ char name[MAXLEN]; int length; char *start; }LNode; ~~~ #### 3.3“堆”的管理 - C中利用动态分配函数,malloc和free来管理“堆”。利用malloc为每一个新串分配一块实际串长所需要的存储空间,分配成功返回一个指向起始地址的指针,作为串的基址,同时,约定的串长也作为存储结构的一部分。函数free则用来释放用malloc分配的存储空间