C语言标准
- K&R C
- ASCII C、C89(以此为标准)
- ISO C、C90
- C99(主流编译器并非100%支持)
- GNU C
- C11
C语言的语法构成
- 关键字:89标准32个;99标准增加5个
- 标识符:变量、函数、宏
- 分隔符:空白符(空格、回车、TAB)
- 标点符号:, ; … () [] {} ? :
数据类型
- 整数类型
- short
- int:所有的整数默认用int存放
- long(long long 不是基础类型,强制类型,64位数)
- C语言中整数类型以补码形式存放
- 浮点类型
- float
- double:所有的浮点默认用double存放
- 字符类型
- char:以ASCII码存放,0-127的小整数
- 注:C语言不原生支持字符串类型,字符串使用字符数组的方式模拟。
- bool类型
- 空类型
- void:只起到占位置的作用,不能直接操作该数据类型
- 构造类型
- 变量与常量:整数常量:0开头的八进制,0x开头的十六进制,普通的十进制
运算符
- 算术运算:+ - * / % ++ –
a+++++b
:通不过编译,贪心算法,a++ 不是空间无法自增(从左向右扫描发现第三个+后还是+,由于 ++ 优先级比+高,所以看成++,故出错)- 双目运算符的左右操作数类型一致时,其结果也是相同类型,所以整数/整数其结果仍为整数
- %:a%b,最终结果的符号与a保持一致
- 使用++、–运算符时不要连续使用
- 关系运算符:> >= < <= == !=
- 注意:不要混淆使用==和=
if(1 == res)
- 逻辑运算:&& || !
- 注:&&、||规定了求值顺序(从左到右)
- 短路效应
- 表达式1 && 表达式2:表达式1为假时短路
- 表达式 || 表达式2:表达式1为真时短路
- 位运算:& | ^ ~ << >>
- 使用位运算能够更高效的完成某些运算:a = a^ b^a
- 赋值运算符:= += …
- 条件运算符:? :
- 右结合性: 当有多个条件运算符组合使用时,从右到左计算
优先级和结合性
- 优先级:确定不同优先级的运算先后顺序
- 结合性:确定相同优先级的运算先后顺序
- 记忆口诀
- 单目运算符 > 双目运算符 > 多目运算符
- 算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
- 表达式求值顺序:C语言中的多目运算符并未规定求值顺序(除&&、||之外)
类型转换
输入输出(带缓冲)
- printf(行缓冲)
- printf(“格式控制串”, 变量列表)
- 普通字符:原样输出
- %字符:占位符,替换为后面的变量列表相同位序的变量的值
- \字符:转义字符,转义后使用
- 缓冲区满,则自动将缓冲区的数据显示在屏幕上(标准输出文件)
- 碰到换行符,将缓冲区的数据显示在屏幕上
- 强制刷洗缓冲区
- scanf(行缓冲)
- scanf(“格式控制串”, 地址列表)
- 注意格式控制串的末尾不要添加空白符(空格、tab、\n)
- 空白符在格式控制串的作用是吸收连续的0个或多个空白符
- %d:读入十进制int整数
- %c:读入单个字符
- %s:读入一个不含空格的字符串
- %f:读入一个单精度浮点数
- %lf:读入一个双精度浮点数
- getchar/putchar
空语句
代码结构
- 顺序结构(绝对执行的代码,且必被执行一次)
- 按照一定的前后顺序执行相应的操作步骤
- 一般情况用于表示逻辑的先后
- 分支结构(条件执行的代码,最多被执行一次)
- 按条件选择执行
- if…else
- 常见错误:多写分号,少写{};else悬挂
- else的配对规则:从后向前查找未完成配对的if
- switch…case(switch后面的括号中的数据类型,必须为数值型,包括int、char、long、short以及枚举类型)
- 循环结构(循环执行的代码,可能被执行多次)
- 计算机最擅长的操作
- while:通常用于条件循环
- do…while
- for:通常用于确定次数循环
- 使用goto构造循环
- continue:提前结束本轮循环,进入下一轮
- break:结束整个循环体,继续执行循环后的语句
- 注:一般不要使用三层循环
高级特性
- 数组(批量定义同一类型的变量)
- 一维数组
- 内存空间连续,且一次性申请
- 数组的元素个数必须为确定的
- 不能整体访问,需要使用下标访问具体元素
- 二维数组
- 二维数组是数组元素为一维数组的数组
- 是按优先的规则存储的
- 整体分配空间,单个操作
- 使用下标访问数组元素,下标分为
- 多维数组
指针
- 指针的基本概念
- 内存按字节编号
- 内存地址:某一个内存所关联的内存字节编号- 变量的地址:分配给变量的多个内存字节中起始内存字节的地址
- 指针:一般用于说明变量的地址,即地址值
- 指针变量:存储指针值(地址值)的变量
- 取地址运算符 &:获取指定变量在内存中的起始字节编号(地址)
- 间接取值运算符 *:获取指定地址处的值
- 指针的运算
- 赋值运算:注意:一定要将有效的地址值赋给指针变量
- 算术运算
- +:指针加一个整数
- -:指针减去一个整数
++、--
:向前或向后移动一个元素的位置;最常用的使用方式:*p++(单目运算符从右向左结合)int a[5]={0,1,2,3,4};
int *p = a;
printf("%d %d\n", *p++, (*p)++); // 1 0 printf从右向左计算值
printf("%d\n", a[0]); // 1
printf("%d\n", *p); // 1
- 关系运算
- 注意:指针的运算使用指针变量的值参与运算
- 指针与数组
- 指针与一维数组
- 数组名蕴含了整个数组第一个元素的地址
- 数组名是一个地址常量,即无法被更改值
- 指针与二维数组
- 注意:二维数组名不是数组第一个元素的起始地址,而是第一行元素的起始地址
- 行地址:即每一行的起始地址,每次+1偏移一行
- 万能公式:与一维数组类似,每一个中括号的下标访问相当于一次基地址偏移寻址
- data[i][j] <—> *(data[i] + j) <—> *(*(data + i) + j)
- int (*p)[3] = arr; 先算小括号,代表p是一个指针变量,再算[]代表这个指针不是指向一个普通变量,而是一个数组
函数
- 函数概述:为了代码的封装、模块化、代码复用
- 函数的定义与声明
- 函数的定义:函数的具体实现部分
- 函数的声明:指明函数的名称、返回值类型、参数个数及其类型
- 函数调用
- 通过函数名(),在()内给函数传递数据
- 通过函数名找到函数的指定代码
- 保存当前的执行现场
- 为函数执行分配内存空间
- 定义形式参数,并使用实际参数的值初始化形式参数
- 执行函数体代码
- 函数返回值
- 销毁函数执行空间
- 恢复现场
- 函数传参
- 参规则:利用实际参数的值初始化形式参数
- 复制传参:形式参数获取实际参数的值,在函数内操作的是形式参数
- 指针传参
- 形式参数获取了函数外某个变量的地址,即完成了指向关系的建立
- 通过形式参数对函数外某个变量的指向关系完成间接引用,即可修改变量的值
- 注意:如果需要在一个函数内修改函数外某个变量的值,则需要将该变量的地址传递给函数
- 数组传参:使用数组名传参只传入了数组第一个元素的起始地址
- 注:在函数内部操作的是形式参数本身,实际参数只起到提供数据值的作用
- 指针与函数
const
函数递归
内存区域
- 栈区:存放局部变量
- 全局区(静态区):存放全局变量和静态变量
- 堆区:存放程序员开辟的空间
- 代码区:二进制代码
- 文字常量区:存放常量字符串,不可更改
作用域:变量起作用的区域
- 按照范围分
- 全局变量:定义在函数外部的变量
- 局部变量:定义在函数内部的变量
- 按照生存周期分
字符串
- 定义:必须要有一个结束标志
char string1[10] = {[0] = '1', '4'}; // 输出14。若给9个字符,则不是字符串,是字符数组
char string2[10] = "hello"; // 对字符常量区的一个拷贝,不影响字符常量区的值
char *p = "hello"; // 不可用下标修改常量字符串
p = "world"; // 可重新给p赋值
- 常用字符串处理函数
char string1[10] = "hello";
strlen():计算字符串的实际长度,不包括\0
strcpy(dst, src):将src拷贝给dst,完全拷贝
strcmp(str1, str2):返回值根据编译器的高低优劣,
编译器低级:前者>后者,返回值为1
前者<后者,返回值为-1
编译器比较高级:不相同的那个字符位置的字符:前者-后者
strncpy:将右边的字符串里面的前n个字符串拷贝到左字符串里面,不是完全拷贝
strncmp:比较前面n个字符
strncat:将源字符串的前n个字符拼接到目的字符串后面
字符串数组
- 数组里面的所有元素都是字符串
- 定义
char string[2][10] = {"hello", "world"};
char *P[2] = {"hello", "world"}; // 先算[]代表它是一个数组,再算char *代表这个数组里面存储的都是字符串
printf("string[0] = %s\n", string[0]);
printf("p[0] = %s\n", p[0]);
编译过程
- 1.源文件(.c)----->2.预处理文件(.i)------->3.汇编文件(.s)-------->4.二进制文件(.o)----->5.链接所有文件生成可执行文件
- 1—>2:gcc -E (处理的是带#的语句,#include, #define, #ifdef, #ifndef)
- 2—>3:gcc -S(检查语法规则,生成汇编代码)
- 3—>4:gcc -c(生成二进制文件)
宏
- C语言常用宏定义
- #define:实际上就是一个语句的替换
- 一般语法:#define 宏名 宏体
- 最简单的宏:#define N 8
- 无参宏
#define TEST() printf("====\n"); // printf要带上\n
- 有参宏:#define MAX(a, b) a>b?a:b
#define XX(a, b) ((a) + (b)) // a可能是一个表达式
- #define STR(x) #x
#的作用是把x转变为“x”,括号中的x不用再加引号
- #define中\的作用:代表宏体还未结束
#define OUTPUT \ #define OUTPUT \
printf("hello\n");\ do\
\ {\
printf("=====\n"); printf("hello\n");\
\
printf("====\n");\
}while(0);
- #define中 ##的作用:拼接字符串
#define getMax(type, funcname) \
type get##funcname(type a, type b)\
{\
return a>b?a:b;\
}
getMax(int, IntMax) // 不能放在main函数中,main函数中调用的时候要使用拼接后的函数名
getMax(int, FloatMax)
结构体
struct Person p;
- 初始化
struct Person p1 = {"xxx", 10, 120};
- 点语法,相当于“的”
p.age = 10;
p.weight = 100;
// p.name = "xxxx"; 不可以
strcpy(p.name, "xxx");
- 复制
struct Person p2 = p1; //复制需要左右类型匹配
- 别名
typedef struct Person
{
.....
}Person, * pPerson;
内存对齐的三个原则
- 如果第一个成员属性是基本类型,那么它的内存偏移量是从0开始的
后面的成员变量是从它本身所占字节数的倍数开始 - 如果成员属性不是基本类型,那么内存是从它本身结构体里面所占字节数最大的成员变量的最小倍数开始分配
- 最后收尾的时候总的字节数要是最大成员变量字节数的倍数
struct Student
{
char sex; // 0
int num; // 4-7
};
printf("%lu\n", sizeof(struct Student)); // 8
struct xx
{
char ch; // 0
int value; // 4--7
double kill; // 8--15
}; // 16
struct Person
{
char sex; // 0
struct xx xxx; // 8--23
int num; // 24--27
}; // 28--31
文件分类
文件的操作
- 打开文件
- FILE *fp = fopen(" ", " ");
- fopen是打开普通文件
- 第一个参数:文件名路径
- 第二个参数:文件打开方式
- 返回值:成功返回文件指针,失败返回NULL
- 文件打开失败原因
- 以r一族的方式打开一个不存在的文件
- 运行内存已经不足以打开一个文件
- 读写数据
- 字符读写函数
- fputc:将字符写到文件里面
- fgetc:将文件里的字符拿出来,成功则返回读出来的字符,失败返回EOF
- 字符串读写函数
- fputs:将字符串写入文件
- fgets:将文件里的字符串读出来,成功则返回读出的字符串,失败则返回NULL
- 若要求读出的字符长度小于文件内容长度,那么读出size-1个字符
- 若要求读出字符长度大于文件内容长度,读出文件所有内容
- 碰到\n结束,\n是被读出的
- 格式化读写函数
- fprintf:是将格式化好的字符串写入文件中
- fscanf:将格式化好的数据读出
- 二进制读写函数
- fwrite:将数据以二进制形式写入文件(成功则返回成功写入的块数,失败则返回0)
- fread:将二进制数据读出(成功则返回读入的块数,失败则返回0)
- 定位文件指针
- fseek:成功则返回0,失败返回-1
- 第二个参数:指的是相对于第三个参数的位置偏移量,如果第三个是SEEK_SET,
那第二个参数>=0 - 第三个参数
- SEEK_SET:文件首部
- SEEK_CUR:当前位置
- SEEK_END:文件结尾
- ftell:返回当前文件指针位置
- 文件尾函数
- 关闭文件
static
- 如果修饰的是一个局部变量,那么变量不会被释放,其他函数必须通过地址访问该数据
- 如果修饰的是一个全局变量,那么这个变量将不能被外界文件访问
- 如果修饰一个函数,则该函数将不能被外界访问,只能被当前文件访问
extern
条件编译:根据条件去编译
- if
#if 条件
语句体
#endif
- if-else
#if 条件
语句体
#else
语句体
#endif
- if-elif-elif
#if 条件
语句体
#elif 条件
语句体
#else
语句体
#endif
- ifdef
#ifdef 宏名 // 宏名有被定义过则执行语句体
语句体
#endif
- ifndef
#ifndef 宏名 // 宏名没有被定义则执行语句体
语句体
#endif
头文件包含
链表思想
- 数组局限:实际分配时不能确定数组大小
- malloc:配置内存空间
- void *malloc(size_t size);
- 返回值可用(type *)强转,参数可用sizeof计算
- 需要分配出一个堆时才调用创建空间
- free:在需要删除一个堆空间的时候才去释放
- 链表:随时分配,随时释放
- 链表分类
- 单链表
- 有头:head指向的第一个数据域不存放数据
- 无头:head指向的第一个就存放数据
- 双链表
- 循环链表
可变参数函数
- #include <stdarg.h>
- 必须要有至少一个参数
- 怎么拿到可变参数里面的值