1. c文件执行过程
C语言程序的执行过程可以分为四个基本步骤:预处理、编译、汇编和链接。下面是这些步骤的简要概述:
预处理:在这个步骤中,预处理器将源代码中以 # 开头的指令进行处理,例如 #include 和 #define。预处理器还可以执行条件编译,将一些代码包含或排除在编译过程中。
编译:在这个步骤中,编译器将经过预处理的源代码转换成汇编语言。编译器会进行词法分析、语法分析和语义分析,生成相应的中间代码。
汇编:在这个步骤中,汇编器将中间代码转换成可执行的机器码。汇编器会将每条汇编指令翻译成一条或多条机器指令,并生成目标文件。
链接:在这个步骤中,链接器将目标文件和库文件组合在一起,生成可执行文件。链接器会解决符号引用问题,将代码和数据段放置在内存中的正确位置,并生成可执行文件的元数据(如入口地址和符号表)。
一旦完成了这些步骤,可执行文件就可以被操作系统加载和执行了。操作系统会将程序加载到内存中,并将控制权转移到程序的入口点。程序开始执行,按照代码的逻辑执行指令,直到程序结束或者遇到异常错误。
2.Void*
类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。
3.C 中的变量声明
变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
变量的声明有两种情况:
• 1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。
• 2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。
• 除非有extern关键字,否则都是变量的定义。
extern int i; //声明,不是定义
int i; //声明,也是定义
不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的,最好初始化。
4.C 中的左值(Lvalues)和右值(Rvalues)
C 中有两种类型的表达式:
- 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
4.常量
4.1 整数常量
- 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
- 整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
- 下面列举几个整数常量的实例:
- 212 /* 合法的 */
- 215u /* 合法的 */
- 0xFeeL /* 合法的 */
- 078 /* 非法的:8 不是八进制的数字 */
- 032UU /* 非法的:不能重复后缀 */
4.2浮点数常量
可以带有一个后缀表示数据类型,例如:
11. float myFloat = 3.14f;
double myDouble = 3.14159;
5. C存储类
5.1 auto
auto 存储类是所有局部变量默认的存储类。
定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。
int mount;
auto int month;
上面的实例定义了两个带有相同存储类的变量,auto 只能用在函数内,即 auto 只能修饰局部变量。
5.2 register 存储类
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
register 存储类定义存储在寄存器,所以变量的访问速度更快,但是它不能直接取地址,因为它不是存储在 RAM 中的。在需要频繁访问的变量上使用 register 存储类可以提高程序的运行速度。
register int miles;
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
5.3 static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,,而不需要在每次它进入和离开作用域时进行创建和销毁。若在函数内部定义,外部不可访问
静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。
void func1(void)
{
/* ‘thingy’ 是 ‘func1’ 的局部变量 - 只初始化一次
- 每次调用函数 ‘func1’ ‘thingy’ 值不会被重置。
*/
static int thingy=5;
thingy++;
printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}
输出:thingy 6 7 8 9
5.4 extern 存储类
extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,而只是指示编译器该变量在其他文件中定义。
6.运算符
a++: 先赋值后运算 ++a:先运算后赋值
7.判断语句switch 与无限循环
1.switch:
当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。
不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。‘
2.无限循环
C 程序员偏向于使用 for( ; ; ) 结构来表示一个无限循环。
8.枚举
义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
8.1枚举变量的定义
- enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day; - enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
例子:
#include <stdio.h>enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};int main()
{enum DAY day;day = WED;printf("%d",day);return 0;
}
输出3 里面的元素如果不赋值初始化,默认从前面一个递增
9. 函数指针与回调函数
1.函数指针
#include <stdio.h>int max(int x, int y)
{return x > y ? x : y;
}int main(void)
{/* p 是函数指针 */int (* p)(int, int) = & max; // &可以省略int a, b, c, d;printf("请输入三个数字:");scanf("%d %d %d", & a, & b, & c);/* 与直接调用函数等价,d = max(max(a, b), c) */d = p(p(a, b), c); printf("最大的数字是: %d\n", d);return 0;
}
2.回调函数
函数指针作为某个函数的参数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
实例:实例中 populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
实例中我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。
populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组
#include <stdlib.h>
#include <stdio.h>void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{for (size_t i=0; i<arraySize; i++)array[i] = getNextValue();
}// 获取随机值
int getNextRandomValue(void)
{return rand();
}int main(void)
{int myarray[10];/* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/populate_array(myarray, 10, getNextRandomValue);for(int i = 0; i < 10; i++) {printf("%d ", myarray[i]);}printf("\n");return 0;
}
10.字符串相关函数
11. 结构体与结构体指针
12.union共用体
同一时间只能使用一个变量
- 定义共用体
为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下
- 访问
为了访问共用体的成员,我们使用成员访问运算符(.)。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。您可以使用 union 关键字来定义共用体类型的变量。下面的实例演示了共用体的用法:
13.位域
C语言中的位域是一种用来定义数据结构成员(即结构体成员)的特殊方式,其中这些成员被定义为占用指定数量的二进制位,而不是完整的字节。
- 位域的定义和位域变量的说明
在下面例子中,定义Age结构体中的age变量值只占用三位,也就是数值不能大于7
- 位域使用
下面示例中,定义了位域结构图变量和位域结构体指针
14.typedef关键字
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字
- 别名
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写。 - typedef定义结构体
- typedef vs #define
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
15 C 预处理器
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之==,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理==。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
#include <stdio.h>
#include “myheader.h”
这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
- 宏延续运算符(\)
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
16. C错误处理
C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。
- 程序退出状态
通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。
如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1。
17 C可变参数
后面的参数的个数和类型都不固定
18 内存管理
C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。
- C语言中常用的内存管理函数和运算符
-
malloc() 函数:用于动态分配内存。它接受一个参数,即需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。
-
free() 函数:用于释放先前分配的内存。它接受一个指向要释放内存的指针作为参数,并将该内存标记为未使用状态。
-
calloc() 函数:用于动态分配内存,并将其初始化为零。它接受两个参数,即需要分配的内存块数和每个内存块的大小(以字节为单位),并返回一个指向分配内存的指针。
-
realloc() 函数:用于重新分配内存。它接受两个参数,即一个先前分配的指针和一个新的内存大小,然后尝试重新调整先前分配的内存块的大小。如果调整成功,它将返回一个指向重新分配内存的指针,否则返回一个空指针。
-
sizeof 运算符:用于获取数据类型或变量的大小(以字节为单位)。
-
指针运算符:用于获取指针所指向的内存地址或变量的值。
-
& 运算符:用于获取变量的内存地址。
-
*运算符:用于获取指针所指向的变量的值。
-
-> 运算符:用于指针访问结构体成员,语法为 pointer->member,等价于 (*pointer).member。
-
memcpy() 函数:用于从源内存区域复制数据到目标内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
-
memmove() 函数:类似于 memcpy() 函数,但它可以处理重叠的内存区域。它接受三个参数,即目标内存区域的指针、源内存区域的指针和要复制的数据大小(以字节为单位)。
19. C命令行参数
执行程序时,可以从命令行传值给 C 程序。这些值被称为命令行参数,它们对程序很重要,特别是当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了。
命令行参数是使用 main() 函数参数来处理的,其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数。
- 执行的程序名为数组第一个值 argv[0], argc是int类型的,它表示的是命令行参数的个数。不许要用户传递,它会根据用户从命令行输入的参数个数,自动确定。argv是char**类型的,它的作用是存储用户从命令行传递进来的参数。它的第一个成员是用户运行的程序名字。
- 多个命令行参数之间用空格分隔,但是如果参数本身带有空格,那么传递参数的时候应把参数放置在双引号 “” 或单引号 ‘’ 内部。
./test hello world hello world
5个参数,test为执行程序名,后面以空格划分./test "hello world" "hello world"
3个参数