# 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; //是定义
~~~

# 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分配的存储空间