前言:本篇文章主要用于记录博主本人从事C软件开发过程中遇到的各种疑难杂症和奇技淫巧,以知识点的形式将其总结,以做后观。(亦是记录博主如何从C语言菜鸟一步一步走向软件开发......)
目录
1 const
const用以声明变量永不变化的常数。一般来说这些变量都应该被放在ROM区(也就是Flash程序空间)以节约宝贵的RAM内存。但一个简单的const声明并不能保证变量最后会被分配到ROM区,安全的做法必须配合#pragma声明的CONST_SEG或INTO_ROM数据段一起实现。
任何类型的变量,都可以冠以const声明。
2 运算符
- %:取余数
- >>:移位运算符。a >>= b相当于 a = a >> b,将二进制a右移b位(低位丢弃)
- &&:逻辑与运算符
- &:位与运算符。a &= b 相当于 a = a & b,按位与运算之后赋值给a
- ||:逻辑或运算符
- |:位或运算符
- ^:异或运算符,位运算的一种。a ^= b,a、b相同a为0,a、b不同a为1
- ! :逻辑非运算符。!0为真,其余为假
- ~:按位取反运算符
3 数据格式声明
格式字符:
- %d:有符号十进制整数
- %c:一个字符
- %s:一个字符串
- %f:输出实数【以小数形式,默认实数的整数部分全部输出,小数部分输出6位】
- %e:以指数形式输出实数
- %ld、%lld:长整型数据、双长整型数据
- %x:十六进制数
% 是格式声明符,若想输出 %,则应连用两个 % 表示,例如:printf("%%\n");
- %m.nf:m代表输出数据的宽度,该宽度包括整数。n代表输出数据的小数位数(末尾以四舍五入原则,若格式为%-m.nf,表示数据向左对齐)
- %m.ns:m代表输出字符的宽度,n代表只打印前n个字符(%-m.ns标记使得文本左对齐输出)
4 逃逸字符
- \b:回退一格
- \t:到下一个表格位
- \n:换行
- \r:回车
- \’:单引号
- \”:双引号
- \\:反斜杠本身
\ 在C语言中被称之为转义字符。
5 scanf函数
scanf类似于一个获取单词的函数,若用其读取字符串,从第一个非空白字符作为字符串的开始,以下一个空白字符作为字符串的结束。若指定了字段宽度,则在读取字段宽度的字符或读到空白字符后结束。其具有返回值,可以分成以下3类情况:
- 正整数:表示成功读取的项数
- 0:表示用户输入的值不匹配
- EOF:表示检测到“文件结尾”
scanf中的 * :用于抑制赋值
6 取整相关
- i=(int)2.5; // int:直接舍去小数部分,i=2
- floor(x); // 返回的是小于或等于x的最大整数
- ceil(x); // 返回的是大于x的最小整数
7 单反斜杠(\)的作用
- 可以作为转义字符使用,例如 \n 表示换行符
- 可以作为续行符使用,用于一行的结尾,表示本行与下一行连接起来
8 switch 与 if
- 若根据浮点类型的变量或表达式来选择,则无法使用 switch
- 若根据变量在某范围类决定程序流的去向,则推荐使用 if
- 使用 switch 程序通常运行快一些,生成的代码少一些
9 for
for 循环后面直接加分号,则表示只进行 for 循环而跳过后面的语句。且当其后面没有语句时,必须要加一个分号。
10 链表和数组
选择何种数据类型取决于具体的问题。
- 如果因频繁地插入和删除项导致经常调整大小,而且不需要经常查找,选择链表会更好。
- 如果只是偶尔插入或删除项,但是经常进行查找,使用数组会更好。
- 如果既需要频繁插入和删除项,又要频繁查找,则选择二叉查找树。
11 const与数组
a[] = {1,2,3,4,5}; // 表示a只能指向该数组, 不能指向其他数组 const int a[] = {1,2,3,4,5}; // 不止a为const的指针,数组内部的每个单元都是const int类型,若有以上类型定义,则必须通过初始化进行赋值
12 const与指针
int* const q = &i; // 指针本身被定义为const // 表示一旦q得到了某个变量的地址,其就不可以再指向其他变量, q只能指向一个固定的地址 *q = 26; // ok q++; // ERROR const int* p = &i; // 指针所指的变量被定义为const // 表示不能通过这个指针去修改指向的变量的值(并不表示指向的变量变成const) *p = 26; //error i = 26; // ok p = &j; // ok
13 数组指针
数组指针定义:int (*p)[n];
()优先级高,先与p结合成为一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
// 将二维数组赋给数组指针: int a[3][4]; int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。 p=a; // 将该二维数组的首地址赋给p, 也就是a[0]或&a[0][0] p++; // 该语句执行过后, 也就是执行p=p+1后, p跨过行a[0][]指向了行a[1][] // 所以数组指针也称指向一维数组的指针, 亦称行指针。
14 指针数组
指针数组定义:int* p[n];
[]优先级高,先与p结合成为一个数组,再由 int* 说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,p会指向下一个数组元素。
// 将二维数组赋给指针数组: int a[3][4]; int* p[3]; for (i=0; i<3; i++) { p[i]=a[i]; } // 这里int* p[3]表示一个一维数组内存放着三个指针变量, 分别是p[0]、p[1]、p[2], 所以要分别赋值 // p=a; 这样赋值是错误的 // 因为p是个不可知的表示, 只存在p[0]、p[1]、p[2]…p[n-1], 而且它们分别是指针变量可以用来存放变量地址。 // *p=a; 这样赋值是可以的, *p表示指针数组第一个元素的值, 即相当于 p[0] = a[0][]
通过以上,数组指针和指针数组两者的区别就豁然开朗了,数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。
当数组指针和指针数组都指向二维数组,要表示数组中i行j列的一个元素时,以下引用方式等价:
*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]
符号优先级:()>[]>*
15 灵活数组成员
灵活数组是在C99标准中提供的一种编程技巧,使用此项特性,我们需要声明一个带灵活数组成员的结构。例如:
typedef struct { int count; double average; double scores[]; // 灵活数组成员 } FlexibleArrayStruct;
C99的意图不是让我们声明FlexibleArrayStruct
类型的变量,而是希望我们声明一个FlexibleArrayStruct
类型的指针,然后利用malloc
来分配足够的空间,以存储FlexibleArrayStruct
类型结构的常规内容和灵活数组成员所需要的额外空间。
FlexibleArrayStruct* pf; pf = malloc(sizeof(FlexibleArrayStruct) + n * sizeof(double));
现在即有足够的空间存储count、average和一个内含n个double类型的数组,并可以使用指针pf访问这些成员。
注意:灵活数组成员必须是结构体的最后一个成员。
16 void
void
关键字在C语言中一般用于指示 函数无返回值 或者 函数无参数。
在嵌入式开发中,还有一种额外的用法:(void)<寄存器名称>,其表示单片机执行一次读取该寄存器的操作,例如:
(void)SCI0SR1; // 执行一次读取寄存器SCI0SR1, 其作用是清除各类标志位
17 exit()和atexit()
atexit()
的形参是一个不带任何参数且返回类型为void的函数指针,通常这些函数会执行一些清理任务或重置环境等。atexit()
的作用是将形参中的函数注册到函数列表,当调用exit()
时就会执行这些函数。
注意:
- 注册的函数执行顺序和注册顺序相反,最后注册的函数最先执行
- 通过
atexit()
注册函数之后,即使没有显示调用exit()
,被注册函数还是会被执行, 因为main()
结束时会隐式调用exit()
exit()
在执行完atexit()
指定的函数后,会完成一些清理工作:刷新所有输出流,关闭所有打开的流和关闭由标准IO函数tmpfile()
创建的临时文件,然后将控制权返回主机环境,并向主机环境报告终止状态。
18 原码、补码、反码
- 正数:原码、反码和补码是一样的,即看到符号位(第一位)是0,就可以照着写出其他两种码。
- 负数:
- 原码转反码:符号位不变(1),数值位按位取反。
- 反码转补码:末位加1
- 原码转补码:符号位不变,数值位按位取反,末位再加1
- 补码转原码:符号位不变,数值位按位取反,末位再加1。(补码的补码等于原码)
19 main 函数定义规范
- 无参数形式:
int main(void) { // 程序代码 return 0; // 返回 0 表示成功执行 }
- 带参数形式:
int main(int argc, char *argv[]) { // 程序代码 return 0; // 返回 0 表示成功执行 }
20 缓冲区
缓冲区一般分为两类:完全缓冲IO和行缓冲IO。
- 行缓冲IO:在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下ENTER键后才刷新缓冲区。
- 完全缓冲IO:当缓冲区被填满时才刷新缓冲区,即把缓冲区的内容发送至目的地,通常出现在文件输入中。
21 定义与声明
定义包含声明,但是声明不包含定义
- 定义:分配内存空间的声明
- 声明:不需要分配内存空间的声明
22 函数特征标
函数特征标指的是函数的参数列表(与函数类型,即函数返回值无关)。
如果两个函数的参数数目和类型相同,且参数的排列顺序也相同,则它们的特征标相同。
23 代码风格
名称 | 示例 | 描述 |
---|---|---|
全局作用域变量 | G_global_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
全局作用域数组 | G_global_scope_array | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
全局作用域指针变量 | Gp_global_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
文件作用域变量 | S_file_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
文件作用域数组 | S_file_scope_array | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
文件作用域指针变量 | Sp_file_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
函数作用域变量 | function_scope_variable | 专有缩写大写,其余单词小写,以 区分单词 |
函数作用域指针变量 | p_function_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
函数作用域变量-静态 | s_function_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
函数作用域指针变量-静态 | sp_function_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
函数作用域数组 | function_scope_array | 专有缩写大写,其余单词小写,以 区分单词 |
函数作用域数组-静态 | s_function_scope_array | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
形参 | function_scope_variable | 专有缩写大写,其余单词小写,以 区分单词 |
结构体成员-变量 | function_scope_variable | 专有缩写大写,其余单词小写,以 区分单词 |
结构体成员-指针 | p_function_scope_variable | 专有缩写大写,其余单词小写,以 开头,以 区分单词 |
函数 | GTK_window_set_default_size | 专有缩写大写,其余单词小写,以 区分单词 |
结构体、枚举、联合 | AppLaunchContext | 采用大驼峰写法 |
枚举变量 | APPLICATION_DEFAULT_FLAGS | 全部大写,以 区分单词 |
宏 | DEFAULT_TIMESTAMP | 全部大写,以 区分单词 |
24 函数头
/** * @author * @brief * @param * @return * @note */
25 文件头
/** * @file * @author * @brief * @version * @date * @note [change history] * * @copyright GEELY AUTOMOBILE RESEARCH INSTITUTE CO.,LTD Copyright (c) 2022 * ----------------------------------------------------------------------------- * [change history] * */