第一部分        知识扩展

7.28 嵌入式产品特点、开发环境、计算机组成、Linux终端初识

1、嵌入式产品。特点:低功耗、根据用户需求定制。硬件:arm处理器。软件:Linux操作系统

        arm架构:精简指令集、低功耗(移动/嵌入式)。         x86架构:复杂指令集、高功耗。(PC机/服务器)

        Linux操作系统:开源可裁剪。          windows操作系统:非开源不可裁剪。

2、Linux发行版之一:ubantu。即对Linux内核进行封装和功能完善,形成了一个方便用户交互的完整操作系统。(详细见附录一)

3、C语言:面向过程的编程语言。(详细介绍见附录二)

4、   计算机基本组成:cpu、ram(内存)。

        cpu负责控制与运算、存储器负责数据中转。

        存储器分类:ram(随机存储器,掉电数据丢失)、rom(只读存储器)

        计算机存储最小单元:字节(byte)。1byte = 8bit。每个存储单元对应一个内存地址。

        (详细见附录三)

5、Linux终端:

        1)linux@ubantu:@前面是用户名,@后面是主机名
        2)相对路径:相对当前路径来说的路径(不包含根目录)。绝对路径:从根路径开始描述(包含根目录)。
        3)~/是家目录,扩展开是/home/linux。
        4)绝对路径的第一个/就是是linux根目录,后面/是分隔符。
        5) 命令组成:command [-option] [argument]
                                    命令符    选项       参数

        6)permission denied    表示权限不足,要在命令前加sudo,输入用户密码。
        7)不要随意点击终端、vim界面的窗口关闭(×),容易造成文件内容未保存丢失。

7.29 GPU(显卡)与cpu(处理器)、大小端存储、缓冲区

1、计算机的GPU参与浮点数运算、cpu整型运算,所以这两种数据类型不能混淆。

2、如果vim编辑器有高亮配置,关键词一般会高亮。(关键词见附录5)

3、在ram中,数据存储方式包括:

                大端存储:低地址存储高位。例如51单片机。

                小端存储:低地址存储低位(一个地址对应一个字节,8bit)。例如x86、arm。

0x12345678
小端存储                    大端存储
地址    数据                地址    数据
2000     78                2000     12      
2001     56                2001     34
2002     34                2002     56
2003     12                2003     78

4、C语言代码写完会保存到rom,当运行时会从rom提取代码到ram运行。

5、gcc编译器只作类型检查,所以代码只要类型匹配,编译不会报错,但运行时可能因为其他原因导致程序崩了。

6、缓冲区:当一个高速设备遇到低速设备,高速设备会等低速设备,造成性能损失,这时候就需要缓冲区,例如多字符输入时,键盘敲入的字符先进入缓冲区,是一个队列数据结构(FIFO先进先出模式),按回车后CPU就会来处理数据,当数据正确,CPU就会取走数据,数据错误,数据就不会离开缓冲区。

7.30  C语言函数编写原则

1、编写C语言源程序时要保持高内聚低耦合的原则。即不同函数间内容不要重合,单一函数内部功能单一,具体解释见附录七。

7.31 内存分区、指针大小与硬件的关系

1、计算机最基本的组成:中央处理器(cpu)和存储器(ram)。

2、cpu里面有几个kernel就是几核处理器。

3、内核(kernel)组成:算术逻辑单元(ALU)、控制单元(CU),寄存器(REG)、缓存(cache)

2、程序运行时,通过PC(程序计数器)当前运行指令的下一条指令、SP(栈指针)指向栈区位置。
3、内存分区:栈区、堆区、静态区、字符串常量区、代码区。
栈区(stack):由系统自动分配。存储函数调用的上下文:局部变量、参数、返回地址(即调用后的下一条指令地址)、保存的寄存器值等。Linux栈区有8M,即主线程栈默认大小。
堆区(heap):动态内存分配(如malloc/new)从堆区获取空间,需手动释放(free/delete)。
静态区(全局区):存储全局变量和静态变量(包括static修饰的局部变量)

                  静态区细分:

        .data段:已初始化的全局/静态变量。

        .bss段:未初始化的全局/静态变量(程序加载时置零)。
常量区:.rodata段,存储常量、const全局变量、字符串等。
代码区:.text段,存储可执行指令(机器码),通常是只读的。

4、指针大小由地址总线宽度决定。

第二部分        C语言知识点

7.28 C语言数据类型、数据存储格式、进制转化

1、C语言数据类型:

        1)常量:程序运行过程值不变的量。整型常量、字符常量

              变量:程序运行过程值改变的量。变量可以看成一段内存空间,程序运行结束空间销毁。

        2)常量分类

             整型常量:十进制8、八进制01000、十六进制0x8。

             浮点型常量:1.23、0.123e1 (常量小数默认是双精度,详细见附录四)。

             字符常量(ascll码):'a'、'\n'、'1'、'!',实际上亦可以看成整型,因为每一个字符都可以转化为对应整型数字。

             宏常量:#define  N 10;

        3)变量分类:

              整型变量:(无符号)短整型    (unsigned)short:2字节

                                (无符号)整型       (unsigned)int        4字节

                                (无符号)长整型    (unsigned)long     8字节

             浮点型变量  :单精度浮点型        float        4字节

                                     双精度浮点型        double    8字节

             字符变量:      (unsigned)char        1字节

                                字符常量用单引号括起来,当单引号里面有多个字符,默认值是最后一个字符。

2、数据存储格式      

              整数存储格式:补码存储。

                正数补码是它本身,负数补码是原码取反加一(最高位符号位不变)。

                当存储时数字超过类型范围会发生溢出,存储错误的结果(如unsigned char 256,实际存储0)。

              单精度浮点数存储格式:符号位1位,阶码8位,尾数23位。计算如下:

               x = (-1)^s × (1.M) × 2^(E-偏移值),其中,s是符号位,M是尾数(不包括隐含的1),E是阶码,偏移值对于32位浮点数是127,对于64位浮点数是1023。

3、进制转化

              十进制转二进制:整数部分:除二取余法。小数部分:乘二取整法。

              二进制转十进制:按位乘2的n次方再加起来。

              二进制转八进制:每三位二进制转一位八进制。

               二进制转十六进制:没四位二进制转一位十六进制

7.29 C语言标识符、#预处理、程序入口、转义字符、运算符、数据类型转化、标准输入输出

1、C语言标识符(变量名、函数名、标签等):

                1)由字母、数字、下划线组成

                2)首字符必须是字母或下划线

                3)标识符命名不能跟关键词重复

2、C语言带#都是预处理指令,在编译前先进行预处理,即文本替换,例如头文件包含会直接替换成头文件内容、宏定义直接文本替换、条件编译直接替换为为真的代码部分。

3、C语言程序必须有一个main函数作为程序入口,且只能有一个。

4、C语言转义字符:一些不可见字符,虽然看似两个字符,实际表示一个字符,仍为char类型。

\n    换行 
\t    水平制表(八格制表)    例:abc\tcd,输出为abc     cd,c前共有八格(abc加上5个空格);
\b    退格(左箭头)   例:Hello\b123输出hell123,Hello\b\b123输出Hel123;
\r    回车(光标移到本行开头) 如hello\r123输出123lo
\f    换页
\\    输出一个斜杠
\'    输出单引号
\"    输出双引号
\ooo    三个八进制数,输出Ascll表对应字符
\xhh    两个十六进制数,输出Ascll表对应字符

 5、C语言运算符(见附录六):

                优先级排序:自高到低,相同优先级按结合方向排序(只有单目运算符、三目运算符、赋值运算符是自右向左结合)

                       1、 初等运算符:()        []        ->        .

                       2、 单目运算符: !        ~        ++        --        (类型)        *        &        sizeof

                       3、算数运算符:*        /        %

                        4、算术运算符:+        -

                        5、移位运算符:<<        >>

                        6、关系运算符:<        <=        >        >=

                        7、关系运算符:==        !=

                        8、位与:&

                        9、位异或:^

                        10、位或:|

                        11、逻辑与: &&

                        12、逻辑或:||

                        13、条件运算符(唯一三目):? :  

                        14、赋值运算符:=        +=        -=        *=        /=        %=        >>=        <<=                                                             &=        ^=        |=

                        15、逗号运算符:,

        注意:求余运算%:操作数必须是整型,结果正负由左操作数决定,右操作数不能位0。

                   ++i 跟 i++:二者的i变量值实际都已经加一,但是整个表达式前者已经加一,后者没有加。前置加加效率高些。

                    逗号表达式:最后一个表达式作为整个表达式的值。

                    赋值运算:把double变量值存到int变量,会造成小数部分截断,反之没影响。

                                      short变量值存到int中,会根据short值的正负进行符号位扩充,负数会在空的高字节补1,正数补0。反之int值存到short会高字节截断。

                                     把有符号整型存到无符号中,内存中的值不变,因为存储空间相同。可以看出,赋值运算就是内存拷贝

6、C语言的变量定义时赋值称为初始化,定义后赋值称为赋初值,前者效率高些。

7、C语言的数据类型转化

        隐式类型转换:编译时自动转化,按下表进行。例如:表达式'a' + 10,两个操作数分别是字符型和整型,根据下标,字符型自动转化为整型,所以表达式的结果是整型。易错题:

unsigned int a = 5;        a = a+(-2);  a的值应该是7。

        显示类型转化:利用运算符:(类型)

8、C语言的临时变量(匿名变量):运算过程临时产生的值,必须为右值。例如表达式的值就是临时变量,计算完就销毁。

9、C语言的const关键字  :定义变量时前面加上这个关键词,表示该变量只读类型,后面不能改变其值。

10、C语言程序分为三种结构:顺序结构、选择结构、循环结构        

        一个C程序包含若干源程序文件,每个源程序文件包括预处理指令、数据声明和若干函数,每个函数包括函数首部和函数体,函数体包括数据声明和执行部分。

11、代码中输入输出是相对于计算机说的,从计算机向外部输出数据称输出(如打印机、显示器),输入设备指向计算机输入数据(如鼠标、键盘、扫描仪)。C语言本身不包含输入输出函数,输入输出来自标准输入输出库函数stdio.h。这种方式使得C语言简单高效。

12、C库的输入输出函数:

        单字符输入输出:    char putchar(int )        只能输出一个字符
                                         int getchar()                输入一个字符,返回字符整型值

getchar是从缓冲区拿走字符。当想要清理缓冲区字符时,可以用这个while(getchar != '\n'){}取出缓冲区字符。

        多字符输入输出:  

        int printf (const char*format,...)        第一个参数是字符串,字符串里可以放占位符,占位符由函数后面的参数代替。 函数返回值是整型,大小为实际字符个数。

        注意: const char*表示字符串。

        int scanf(const char*,...)         通过键盘向计算机输入数据。第一个参数是字符串,后面的参数放地址。当输入多个字符时,间隔符可以是空格 tab或者换行符。返回值是个整型,测试得到大小为正确输入的个数。

        注意:后面的参数如果不是地址,会出现段错误,导致程序运行崩溃segmentation fault(core dumped)。
                   scanf()函数中的字符串里除了占位符不要加其他东西,否则输入时还要加上这些东西,最后不要加\n。
                  输入函数占位符必须与输入数据类型一致。因为地址没有隐式必转。

                  浮点数输入不能指定精度、宽度。

                  输入遇到空格、tab、换行、到达指定宽度、非法输入就会结束

格式符
%hd      短整型
%010d    宽度为10,前面补零,只能补零或者空格。
%#o      会输出0...
%#x      会输出0x...
%e、%E   科学计数法表示,(如1230.0,表示为1.230000e+3)
%f、%F   十进制表示法,默认保留6位小数。输出函数单精度也按照双精度处理(隐式转换)
%m.nf    m是宽度,n是小数位数,宽度不够默认右对齐补空格,m前加- 表示左边补空格
%g,G     两种浮点数表示方法(浮点数、科学计数法)哪个短用哪个表示。
%c       输出字符
%s       输出字符串
%p       打印地址,十六进制
%%       输出%。
%lld     输出long long整型
%lu      无符号长整型。
7.30     循环结构、选择结构、数组及排序查找问题、C语言字符数组定义及初始化

1、C语言选择结构(条件分支结构):

        1)if        else结构

                if(表达式) {语句}:只有表达式逻辑值为1才执行语句。

                if(表达式){语句一}   else {语句二}:表达式逻辑值为1执行语句一,否则执行语句二

                if(表达式一){语句一}   else if(表达式二){语句二} else{语句三}:表达式一成立执行语句一,表达式二成立执行语句二,否则执行语句三。(三个表达式应满足互斥关系)

        2)switch结构

                switch(表达式)
                {
                    case 常量表达式1:语句一;break;
                    case 常量表达式2:语句二;break;
                    case 常量表达式3:语句三;break;
                    default:语句四
                }
        通过表达式与常量表达式等值比较,值为一就执行后面对应语句,break跳出花括号。(表达式需要是整型或者整型相兼容的类型,case后必须是常量表达式)

2、选择结构判断会用到关系运算符、逻辑运算符等,

        1)关系运算符:<= , < , >= , > , == , !=,前四个优先级高一些;
        2)关系运算符加操作数就是关系表达式,关系表达式的值是逻辑值(整型,非0即1);
        3)逻辑运算符:与&&、或||、非!(!优先级最高,&&优先级比||高);
        4)逻辑表达式:逻辑运算符加操作数就是逻辑表达式。(逻辑表达式中按照从左到右执行,在与表达式,只要左值为0,整个表达式值为零,不进行后面表达式的计算,在或表达式中,只要左表达式为1,整个表达式的值就为1,不进行后面的表达式计算

3、条件运算符:

                语句一?语句二 :语句三        语句一值为真执行语句二,否则执行语句三。

4、C语言循环结构:

        1)goto循环:给一个语句命名,然后goto 标识符,就可以无条件跳转到标识符处执行后面的语句。例:
label:
        printf("bbb");

        goto label;
        2)while循环:例:
while(1 == a)
{
    printf(“k”);
}
当a=1时输出k。
        3)do while循环:例:
do
{
    printf("s");
}while(1 == a);
先执行一次循环体,再进行判定语句,符合要求就继续执行循环体。
        4)for循环:例:
for(a = 1; a <= 4; ++a)
{
    printf("s");
}
先执行a = 1;当a < 4时,输出s,然后执行a++,再判断a<4是否成立,成立则输出s,再执行a++,再判断,如此循环。

5、跳出循环语句:
        1)break语句:执行后直接跳出所在层的循环。
        2)continue语句:执行后结束所在循环的本轮循环,直接执行下轮循环。

6、循环语句三要素:循环变量初始化,循环条件语句,使循环趋于结束的语句。

7、数组:   类型     标识符[数字]        数组即一段连续的存储空间。

8、数组相关注意点:

        1)数组只有在定义时初始化可以整体赋值,之后赋值只能一个个成员赋值

        2)当在定义时初始化,可以省略成员个数。

        3)数组成员下标从0开始

        4)void不能定义数组

        5)定义数组时的[]是类型说明符,之后是方括号运算符

        6)数组在内存中存储元素具有连续性、有序性、单一性。

        7)数组定义初始化如果花括号元素个数不足时会自动在后面补零。

        8)数组大小为元素个数乘元素类型大小。

        9)引用数组元素时要防止数组越界访问(下标不能超了)

        10)数组名就是首元素地址

9、实现数组的元素遍历、排序、查找(下面介绍核心思想,具体实现程序见附录八)

1)数组元素遍历

        直接按照下标一个个成员访问

2)数组元素排序

        逆序:把第一个跟最后一个成员换位、然后第二个跟倒数第二个换,,,直到下标为元素个数/2的位置即可。

        升序:

                1、选择排序:从第一个元素开始,依次把该元素跟后面所以元素比大小,如果该元素大于后面的元素,就换值(完成所有比较就会把最小的值放当前位置),接着继续处理下一个,直到处理完倒数第二个元素即可。

                2、冒泡排序:从第一个元素开始,相邻元素两两比较,如果前者大于后者就换值,直到倒数第二个元素(完成所有比较就把最大的值放到最后);接着处理下一个,直到处理完倒数第二个元素即可。

                3、插入排序:从第二个元素开始,跟前面的数一一比较,当遇到第一个小于它的数,把该数后面的数整体后移,然后把它插到第一个小于它的数的后面(这样就把从第一个元素到该元素的顺序先排好了);然后处理下一个,直到最后一个元素即可。

        数字查找:

                遍历查找:从第一个数开始,一一比较,直到发现哪个等于要找的数即可。

                二分查找法:先对数组排序,假设为升序,先把要找的数跟中间的数比较,大于中数就在左边找,小于中数就在右边找(这样就缩短了查找区间);然后在新区间继续处理,直到处理完最后一个数即可。

10、C语言字符数组:char 标识符[数字]

        初始化:

                方法一:char a[6] = {'h','e','l','l','o','\n'}; 

                方法二:char a[] = "hello";        字符串用双引号括起来,自带换行符。

        赋初值:

                方法一:一个一个成员赋值

                方法二:调用函数:scanf("%s",a);        gets(a);        fgets(a);

        输出:

                方法一:printf("%s",a) ;

                方法二:puts(a);

7.31  字符串处理函数、二维数组、函数定义、函数调用

1、数组名代表数组首元素地址,是一个定值。

2、C语言字符串相关处理函数(相同功能代码见附录九)

        1)strlen(a);        获取字符串的有效字符个数

        2)strcpy(a1,a2);        拷贝a1到a2

        3)strcat(a1,a2);        拼接a2到a1后面

        4)strcmp(a1,a2);        比较a1,a2大小,若a1>a2,返回正数,a1=a2返回0,否则返回负数

3、二维数组:类型   标识符[row][col]

        定义时初始化,可以省略行数row,列数col不可省略

                二维字符数组初始化:char a[][10] = {"aaa","bbb","ccc"};        注意字符串长度不能超过数组列数。

4、二维数组定义时初始化才可以用花括号整体赋值,之后不能用花括号整体赋值。

5、二维字符数组的相关操作:遍历、逆序、排序、查找,见附录

6、函数分类:库函数(c库、系统库函数、第三方库函数)、自定义函数

7、函数定义:  函数首部+函数体

      函数首部          类型 函数名(形参1类型 形参1标识符,形参2类型 形参2标识符)

      函数体              {声明部分;

                                语句部分;}

8、函数相关注意:

        1)返回值类型要跟函数类型一致,若无返回值,函数类型为void

        2)函数名不能跟库函数、其他自定义函数重名

        3)一个好的函数要保证低耦合高内聚

        4)函数内部不能定义函数,可以调用函数

        5)函数遇到return语句,就会结束运行

9、函数调用:函数名(实参)

        注意:1)实参需要跟形参类型匹配、数量一致

                   2)函数可以嵌套调用

                   3)函数参数传递从右至左进行    

10、函数递归调用:即在函数内部调用函数本身;

        相关注意:1)每次函数调用都会保护现场,主要是把返回地址入栈,调用结束出栈

                           2)可以利用函数递归调用完成循环操作,但是效率相较于循环语句会低很多,并且循环次数如果过多,由于多次返回地址入栈,会导致栈空间耗尽。

11、字符串与整型数字之间的转化(代码见附录十一)

                1)字符串转整型atoi:先单字符转整,然后乘10的倍数再相加即可

                2)整型转字符串itoa:先拆分单个整数,然后转化字符再拼接即可

第三部分        ubantu快捷键、终端命令、vim编辑器命令

//ubantu常用快捷键、终端命令、vim编辑器命令

一、Linux终端命令:
7.28:
ls                列出当前目录下文件及目录
ls -a             额外列出隐藏内容。 .是目录本身,..是上一层目录,带点文件是隐藏文件。
ls -l             列出当前目录下文件,同时显示文件详细信息(权限、创建时间、大小等);
ls --help         获取命令使用方法
pwd               打印当前路径
cd                回到家目录。
cd ..             退回上一层目录
touch 文件名      创建文件
mkdir 目录名      创建文件夹
rm 文件名         删除文件 
rm 文件夹 -r      删除文件夹
cp 文件 目录              文件拷贝至指定目录 
cp 文件夹 目录 -r         文件夹拷贝 
mv 文件 目录              移动文件
vi/vim 文件名             编辑文件
gcc 文件名(main.c)                c程序编译,生成a.out可执行程序
gcc 文件名 -o 目标名                编译生成指定目标名
./a.out                            可执行程序运行
7.29:
whatis 函数名/系统调用/命令         查看函数简短描述,可以知道在哪个章节描述
man 章节号 函数名/系统调用/命令      查看详细介绍
gcc 文件 -lm                       编译时链接数学库
7.30
clear                              终端清屏

二、vim编辑器命令:
7.28
刚进入默认命令模式,输入i进入编辑模式(INSERT),按ESC回到命令模式(NORMAL)

保存退出:  :w保存   :q退出文件   :q!强制退出   :wq保存退出

复制粘贴:命令模式下,移动光标到要操作的行:       
                    yy        复制1行
                    数字 yy    复制n行(包含当前行)
                    p         粘贴(以光标下1行为粘贴的第1行)
                    dd        剪切1行
                    数字 dd    剪切n行
    
O    在光标下一格开空行进入编辑模式

三、ubantu快捷键:
7.28:
ctrl alt t        开启终端
ctrl shift +      终端字体放大
ctrl -            终端字体减小
tab               文件名自动补全
ctrl+alt+fn+f2    回到图形交互界面(默认开机界面)      
ctrl+alt+fn+f3    进入文字交互界面(可用于帧缓冲相关程序运行)
7.29:
ctrl shift t      打开另一个终端标签页
alt 数字          多标签页时页面切换
7.30
ctrl 空格         显示或隐藏输入法提示框
ctrl c            结束程序运行
ctrl L            终端清屏

第四部分        附录

附录一  ubantu与Linux内核

你的笔记基本正确,但表述可以更严谨。以下是综合搜索结果后的修正与补充说明:


Linux发行版与Ubuntu的关系

  1. 核心概念

    • Linux内核:由Linus Torvalds维护的操作系统核心,负责管理硬件资源(CPU、内存、设备驱动等)。
    • 发行版(Distribution):在内核基础上集成图形界面、软件包管理工具、应用程序等,形成完整的操作系统。
    • Ubuntu:是基于Debian的Linux发行版之一,由Canonical公司维护,以用户友好性和稳定性著称。
  2. 你的笔记修正

    • 原句:“对Linux内核进行封装,形成方便用户交互的版本”
    • 更准确表述

      Ubuntu是基于Linux内核的完整操作系统发行版,不仅封装内核,还集成了桌面环境(如GNOME)、软件包管理器(APT/Snap)、系统工具和社区支持,提供开箱即用的用户体验。

  3. 补充说明

    • 封装内容:除内核外,还包括:
      • 桌面环境(如Ubuntu默认的GNOME)
      • 软件仓库(通过APT管理数千个开源软件包)
      • 系统工具(如防火墙、虚拟化支持)
      • 定制优化(如Ubuntu对Debian软件包的兼容性调整)
    • 交互设计:Ubuntu特别注重桌面用户的易用性,如图形化安装界面、Unity/GNOME桌面环境等。

Ubuntu与其他发行版的区别

特性 Ubuntu 其他发行版(如Fedora、CentOS)
基础 基于Debian,但软件包不完全兼容 Fedora基于RHEL,CentOS是RHEL的社区版
更新周期 每6个月发布新版本,LTS版支持5-10年 Fedora更新更快,RHEL/CentOS更注重稳定性
目标用户 个人桌面、开发者、云计算 Fedora面向技术爱好者,RHEL面向企业

总结与建议

  1. 笔记修正建议
    • 明确Ubuntu是“基于Linux内核的发行版”,而非单纯“封装内核”,强调其完整操作系统属性。
    • 补充Ubuntu的衍生版本(如Kubuntu、Xubuntu)和特色功能(如Snap包管理)。
  2. 扩展学习
    • Linux内核与发行版的关系类似“引擎与整车”,内核提供基础能力,发行版决定用户体验。
    • 其他主流发行版包括Debian(Ubuntu上游)、Red Hat(企业级)、Arch Linux(极简主义)等。

如需更详细的技术对比,可参考Ubuntu官方文档或Linux内核开发资料。

附录二  C语言详细介绍

C语言作为计算机科学领域最具影响力的编程语言之一,自1972年诞生以来,一直是系统编程和底层开发的首选工具。本文将全面介绍C语言的发展历程、语言特点、标准演进以及广泛的应用领域。

一、C语言的历史与发展

C语言的起源可以追溯到1960年代的ALGOL 60语言,经过多个阶段的演变才形成今天我们熟知的C语言。

  1. 前身语言

    • ALGOL 60(1960年):面向问题的高级语言,但离硬件较远
    • CPL语言(1963年):剑桥大学在ALGOL 60基础上开发,更接近硬件但过于复杂
    • BCPL语言(1967年):Martin Richards简化CPL而来
    • B语言(1970年):Ken Thompson在BCPL基础上进一步简化,用于编写第一个UNIX操作系统
  2. C语言的诞生

    • 1972年,贝尔实验室的Dennis Ritchie在B语言基础上设计出C语言(取BCPL的第二个字母)
    • 1973年,C语言主体完成,并用它重写了UNIX操作系统(90%以上代码)
    • 1978年,AT&T贝尔实验室正式发表C语言,同时Kernighan和Ritchie合著《The C Programming Language》(简称K&R)
  3. 标准化进程

    • K&R C:1978-1989年间事实上的标准
    • ANSI C(C89):1989年美国国家标准学会制定,1990年被ISO采纳为ISO C(C90)
    • C99:1999年ISO发布,增加新特性
    • C11:2011年发布,支持汉字函数名和标识符
    • C18:2018年发布,主要是对C11的修正
    • C23:最新的C语言标准(2023年发布)

二、C语言的核心特点

C语言之所以能经久不衰,得益于其独特的设计理念和语言特性:

  1. 高效性与低级控制

    • 直接访问内存物理地址,进行位一级操作
    • 代码效率高,通常只比汇编语言低10%-20%
    • 不需要运行环境支持,编译后可直接运行
  2. 结构化与模块化

    • 层次清晰,便于按模块化方式组织程序
    • 易于调试和维护
    • 丰富的运算符和数据类型,便于实现复杂数据结构
  3. 可移植性强

    • 标准规格写的程序可在多种平台编译运行
    • 从嵌入式处理器到超级计算机都支持
    • 与特定硬件架构解耦
  4. 兼具高级与低级语言特性

    • 具有高级语言的抽象能力
    • 保留低级语言的硬件操作能力
    • 既适合系统软件开发,也适合应用软件开发
  5. 丰富的表达能力

    • 处理能力极强,可处理各类复杂问题
    • 绘图能力强,适合图形和动画开发
    • 支持多重编程范式(过程式、数据抽象等)

三、C语言的版本与编译器

  1. 主要版本

    • Microsoft C(MS C)
    • Borland Turbo C
    • AT&T C
    • GNU C(GCC)
  2. 现代常用编译器

    • GCC:GNU组织开发的开源免费编译器,支持多平台
    • Clang:基于LLVM的开源编译器,BSD协议
    • Visual C++:微软开发的Windows平台编译器
    • MinGW:Windows下的GCC移植版
    • Xcode:macOS上的开发环境,使用Clang
  3. 集成开发环境(IDE)

    • Windows:Visual Studio、Dev-C++、Code::Blocks
    • Linux:Eclipse CDT、KDevelop
    • 跨平台:CLion、VS Code(配合插件)

四、C语言的应用领域

C语言因其独特的优势,在计算机科学的多个领域都有广泛应用:

  1. 系统软件开发

    • 操作系统内核(UNIX、Linux、Windows)
    • 编译器(如GCC)、解释器
    • 设备驱动程序
    • 文件系统和存储管理
  2. 嵌入式系统

    • 单片机开发(裸机或RTOS)
    • 智能家居设备(空调、洗衣机控制器)
    • 汽车电子(ECU、BCU)
    • 医疗设备(监护仪、血糖仪)
  3. 高性能计算

    • 游戏引擎开发(Unity、Unreal)
    • 图形渲染和3D建模
    • 科学计算和数值分析
    • 信号处理(DSP)
  4. 网络与安全

    • 网络协议栈实现(TCP/IP)
    • Web服务器(Apache)
    • 加密算法和安全系统
    • 防火墙和入侵检测
  5. 数据库系统

    • MySQL
    • SQLite
    • PostgreSQL
    • Oracle和SQL Server的核心组件
  6. 工具链开发

    • 开发其他编程语言(Python、PHP等的解释器)
    • 构建系统和版本控制工具
    • 性能分析工具
  7. 多媒体处理

    • 音视频编解码(FFmpeg)
    • 图像处理(OpenCV)
    • 语音识别和合成

五、C语言的职业方向

掌握C语言可开启多种职业路径:

  1. 系统软件工程师:开发操作系统、编译器、工具链等系统软件
  2. 嵌入式系统工程师:设计嵌入式软件,应用于智能设备、汽车电子等领域
  3. 游戏引擎开发工程师:开发高性能游戏引擎和图形渲染系统
  4. 网络安全工程师:开发安全系统和加密算法,保护系统安全
  5. 数据库开发工程师:设计和优化数据库管理系统
  6. 物联网(IoT)开发工程师:开发智能设备和物联网解决方案
  7. 自动化测试工程师:开发底层测试框架和工具

六、C语言的影响与未来

  1. 对后续语言的影响

    • 直接催生了C++、Objective-C、C#等语言
    • 影响了Java、JavaScript、Python等语言的语法设计
    • 成为许多新语言的学习参考
  2. 在现代开发中的地位

    • 仍然是系统编程和性能敏感领域的首选
    • 在物联网和边缘计算时代获得新的应用场景
    • 作为计算机科学教育的基石语言
  3. 未来展望

    • 随着C23标准的发布,语言持续现代化
    • 在安全性和并发支持方面不断改进
    • 仍将是底层开发和系统编程的核心工具

七、学习C语言的建议

  1. 从基础开始:掌握数据类型、控制结构、函数等基本概念
  2. 深入理解指针:这是C语言最强大也最容易出错的部分
  3. 实践项目驱动:通过实际项目(如小型操作系统、游戏等)巩固知识
  4. 阅读优秀代码:学习Linux内核等开源项目的代码风格和设计
  5. 关注现代特性:了解C11、C17和C23引入的新特性

C语言作为一门接近50年历史的语言,依然活跃在技术前沿,其设计理念和哲学影响了几代程序员。无论是想深入理解计算机系统,还是从事底层开发工作,C语言都是不可或缺的核心技能。

附录三   计算机基本组成

你的笔记基本正确,但部分表述可以更严谨,以下是补充和修正后的版本,结合了搜索结果中的权威信息:


计算机基本组成

  1. CPU(中央处理器)

    • 功能
      • 控制:作为计算机的"指挥中心",通过控制器(CU)从内存取指令、译码并生成控制信号。
      • 运算:通过算术逻辑单元(ALU)执行算术(如加减乘除)和逻辑运算(如与、或、非)。
    • 组成
      • 运算器(ALU) + 控制器(CU) = CPU核心。
      • 寄存器:高速暂存指令和数据(如程序计数器PC、指令寄存器IR)。
      • 缓存(Cache):分L1/L2/L3三级,缓解CPU与内存速度差异。
  2. RAM(随机存储器)

    • 功能:临时存储CPU正在处理的程序和数据,断电后丢失(易失性)。
    • 分类
      • DRAM:动态RAM,需刷新(如内存条),成本低但速度较慢。
      • SRAM:静态RAM,无需刷新(如CPU缓存),速度快但成本高。

存储器分类与特性

类型 特性 用途
RAM - 可读写,速度快
- 易失性(断电数据丢失)
临时存储运行中的程序和数据
ROM - 通常只读,写入需特殊操作(如固件升级)
- 非易失性(断电保留数据)
存储BIOS、嵌入式系统固件

:ROM演进类型包括PROM(一次性写入)、EPROM(紫外线擦除)、EEPROM(电擦除)和闪存(如SSD)。


存储单元与寻址

  1. 最小单元
    • 字节(Byte) = 8 比特(bit),是数据存储的基本单位。
    • 内存地址:每个字节对应唯一地址,CPU通过地址访问数据。
  2. 扩展概念
    • 字(Word):CPU一次处理的数据长度(如32位系统为4字节)。
    • 存储字长:存储单元中二进制代码的位数(如64位系统的字长为64bit)。

修正建议

  1. CPU部分
    • 补充控制器(CU)和运算器(ALU)的协同关系。
    • 增加寄存器组的作用(如PC、IR)。
  2. 存储器分类
    • 明确ROM的"只读"是历史名称,现代ROM(如EEPROM)支持有限写入。
  3. 存储单元
    • 区分"字节"与"字"的概念,避免混淆。

冯·诺依曼体系结构

计算机硬件五大核心组件:控制器运算器(合为CPU)、存储器输入设备输出设备,通过总线连接。你的笔记中"存储器负责数据中转"可更精确为"存储器(内存+外存)存储程序和数据,供CPU直接访问"。

附录四  小数在C语言默认双精度类型。

在Linux系统中,浮点数常量的默认类型是双精度浮点数(double。这一规则不仅适用于Linux系统,也是C/C++语言标准的通用规范。以下是具体分析:


1. 语言标准与默认类型

  • C/C++标准规定:根据C语言标准(如C18)和C++标准,未添加后缀的浮点数常量(如3.141.23e-5)默认类型为double

    • 若需指定为单精度(float),需显式添加后缀fF(如3.14f)。
    • 后缀lL表示long double(更高精度)。
  • 示例代码

    
      
    double a = 3.14;   // 默认double类型
    float b = 3.14f;   // 显式声明为float
    

2. 硬件与系统实现

  • IEEE 754标准:Linux系统遵循IEEE 754浮点数标准,double类型通常占用64位(8字节),提供约15-17位有效数字,而float为32位(4字节),仅6-7位有效数字。
  • 性能与精度权衡:现代处理器(如x86、ARM)对双精度运算有硬件优化,默认使用double可减少精度损失,但可能增加内存和计算开销。

3. 实际应用中的注意事项

  • 隐式转换风险:将double常量赋给float变量时,编译器会自动截断,可能导致精度损失或警告。
  • 科学计算与嵌入式场景
    • 科学计算优先使用double以保证精度。
    • 嵌入式系统(如ARM Cortex-M)可能因资源限制选择float,需显式加后缀。

4. 为什么默认是double而非float

  • 历史与兼容性:早期C语言设计时,double因其更高的精度和范围被选为默认类型,延续至今。
  • 避免意外截断:默认double可减少因隐式转换导致的精度问题,尤其在复杂表达式中。

总结

在Linux系统中,浮点数常量默认类型为double,这是由语言标准、硬件支持及精度需求共同决定的。开发者应根据场景选择是否显式指定类型(如floatlong double),以平衡性能与精度需求。

附录五·       C语言关键词介绍

以下是基于图片内容的 C语言关键词分类与功能解析,结合标准C11规范进行说明:


1. 数据类型相关关键字

关键字 作用
int 声明整型变量(通常占4字节,范围依赖平台)
char 声明字符型变量(1字节),也可用于小整数(-128~127)
float 单精度浮点数(4字节,约6-7位有效数字)
double 双精度浮点数(8字节,约15位有效数字)
short 短整型(通常2字节)
long 长整型(通常4或8字节),如long double扩展精度浮点
unsigned 修饰整型,表示无符号(如unsigned int范围0~4294967295)
signed 显式声明有符号类型(默认行为,通常省略)
void 表示无类型(用于函数返回值或指针泛型)

注意sizeof是运算符而非类型,用于计算对象/类型所占字节数(编译时确定)。


2. 流程控制关键字

类别 关键字 典型用法示例
条件分支 if/else if (x>0) { ... } else { ... }
多路选择 switch/case/default switch(n) { case 1: ...; break; default: ... }
循环 for/while/do for(int i=0; i<10; i++) / while(1) { ... }
跳转 break/continue/goto break;(跳出循环) / goto label;(慎用)

3. 存储类别与作用域

关键字 作用
auto 自动局部变量(C99后默认,极少显式使用)
register 建议编译器将变量存入寄存器(现代编译器自动优化,已弃用)
static 静态变量(局部变量持久化/全局变量文件内可见)或静态函数
extern 声明外部定义的变量/函数(跨文件引用)
volatile 防止编译器优化(用于硬件寄存器或多线程共享变量)
const 定义只读变量(实际可通过指针修改,但行为未定义)

4. 复合类型与自定义类型

关键字 作用
struct 定义结构体(成员内存连续,可能存在对齐填充)
union 定义联合体(所有成员共享同一内存空间)
enum 定义枚举类型(本质是整型常量)
typedef 类型别名(如typedef int MyInt;

5. 其他重要说明

  • 重复项:图片中structwhile在第五列重复列出,实际为排版错误。
  • C标准差异
    • C99新增_Bool_Complex等,C11新增_Generic等,但图中未包含。
    • goto虽合法,但易破坏代码结构,应避免使用。
  • 常见误用
    • const int* p vs int* const p(前者指针可变,后者指向不可变)。
    • register在C++17中被移除,C语言中仍保留但无效。

完整关键词列表(按字母排序)

auto break case char const continue default do double else enum extern 
float for goto if int long register return short signed sizeof static 
struct switch typedef union unsigned void volatile while

建议通过实际代码练习(如用switch实现状态机、union解析二进制数据)加深理解。

附录六 C语言运算符与优先级、结合方向

附录七  高内聚低耦合

你的笔记基本正确,但可以结合搜索结果中的专业定义和示例进一步细化和完善。以下是针对“高内聚低耦合”原则的详细解释与补充建议:


1. 高内聚(High Cohesion)

  • 定义:内聚指一个模块(如函数)内部各元素(代码逻辑)围绕同一功能的紧密程度。高内聚要求函数功能单一且明确,所有代码均服务于同一目标。
  • 你的笔记:提到“一个函数的功能尽量单一”,这是正确的,但需补充内聚的层次划分:
    • 功能内聚(最佳):函数仅完成一个独立功能(如计算平方数或格式化输出)。
    • 反例
      • 偶然内聚:函数内代码无逻辑关联(如同时计算面积和打印欢迎语)。
      • 逻辑内聚:函数通过参数选择不同功能(如print_info(type)同时处理学生和教师信息)。
  • 判断标准:删除函数中的某行代码后,若原功能无法完成,则内聚度高;否则可能存在冗余代码。

2. 低耦合(Low Coupling)

  • 定义:耦合指模块间依赖关系的强度。低耦合要求函数间接口简单、依赖最少,避免相互影响。
  • 你的笔记:提到“不同函数间的功能尽量不用重合”,需更准确表述为**“减少函数间的直接依赖”**,具体表现为:
    • 数据耦合(推荐):通过参数传递简单数据(如值传递),函数独立性最强。
    • 反例
      • 外部耦合(最差):函数共享全局变量,导致调试困难(如多个函数修改同一全局变量)。
      • 特征耦合:通过指针或文件等共享复杂数据,增加依赖风险。
  • 实现技巧:使用接口(如回调函数)或封装(如面向对象设计)降低耦合。

3. 你的笔记改进建议

  1. 明确术语:区分“功能不重合”(避免重复代码)与“依赖关系简化”(低耦合的核心)。
  2. 补充示例
    • 高内聚示例:将“计算平方数”和“格式化输出”拆分为两个函数。
    • 低耦合示例:用参数传递替代全局变量。
  3. 强调设计阶段:高内聚低耦合需在编码前规划模块职责,而非事后调整。

4. 实际应用场景

  • 简单任务:若功能单一(如输出200以内平方数),单函数实现可能已满足高内聚。
  • 复杂任务:按“输入-计算-输出”拆分模块,确保各模块功能独立且接口简洁。

总结

你的笔记抓住了核心思想,但需更精准地定义“功能单一性”和“依赖关系”,并辅以具体示例。高内聚低耦合的本质是模块职责清晰化交互最小化,通过合理设计提升代码的可维护性和可重用性。

附录八 数组的遍历、排序、查找相关问题

#include<stdio.h>
void Array_traversal(int *array)//数组遍历
{
    int i;
    for(i = 0; i < 10;++i)
    {
        printf("%d\n",array[i]);
    }
    return;
}
void quick_sort(int *begin,int *end)//快速排序
{
    if(begin >= end)
    {
        return;
    }
    int t = *begin;
    int *b = begin;
    int *e = end;
    while(b < e)
    {
        while(b < e && *e >= t)
        {
            --e;
        }
        while(b < e && *b <= t)
        {
            ++b;
        }
        swap(b,e);
    }
    swap(begin,b);
    quick_sort(begin,b-1);
    quick_sort(e+1,end);
}

int select_sort(int *a,int len)//选择排序
{
    int i,j;
    //int len = sizeof(a) / sizeof(a[0]);
    for(i = 0; i < len - 1;++i)
    {
        for(j = i + 1; j < len;++j)
        {
            if(a[i] > a[j])
            {
                int t = a[i];
                a[i] = a[j];
                a[j] = t;
            }
        }
    }
    return 0;
}

int reverse_sort(void)//逆序
{
    int i;
    int a[10] = {1,0,2,9,3,8,4,7,5,6};
    int len = sizeof(a) / sizeof(a[0]);
    select_sort(a,len);
    for(i = 0;i < len / 2;++i)
    {
        int t = a[i];
        a[i] = a[len - i - 1];
        a[len - i - 1] = t;
    }
    Array_traversal(a);
}

int bubble_sort(void)//冒泡排序
{
    int i,j;
    int a[10] = {1,0,2,9,3,8,4,7,5,6};
    int len = sizeof(a) / sizeof(a[0]);
    for(i = len; i > 0; --i)
    {
        for(j = 0;j < i - 1; ++j)
        {
            if(a[j] > a[j + 1])
            {
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
            }
        }
    }
    Array_traversal(a);
};

int insert_sort(int *a,int len)//插入排序
{
    int i,j;
    for(i = 1; i < len; ++i)
    {
        int t = a[i];
        j = i;
        while(a[j - 1] > t && j > 0)
        {
            a[j] = a[j - 1];
            --j;
        }
        a[j] = t;
    }
    //Array_traversal(a);
    return 0;
}

int binary_search()//二分查找
{   
    int i,j;
    int a[10] = {1,0,2,9,3,8,4,7,5,6};
    int len = sizeof(a) / sizeof(a[0]);
    int end = len - 3;
    int head = 0;
    int mid;
    insert_sort(a,len);
    int num = 6;
    while(head <= end)
    {
        mid = (head + end) / 2;
        if(num > a[mid])
        {
            head = mid + 1;
        }
        else if(num < a[mid])
        {
            end = mid - 1;
        }
        else
        {
            break;
        }
    }
    if(head <= end)
    {
        printf("%d\n",a[mid]);
    }
    else
    {
        puts("not found");
    }
    return 0;
}

int main()
{ 
    int i,j;
    int a[10] = {1,0,2,9,3,8,4,7,5,6};
    int len = sizeof(a) / sizeof(a[0]);

}

 附录九        字符串处理函数功能代码

#include<stdio.h>
int mystrlen()
{
    char a[] = "hello";
    int len = 0;
    while(a[len] != '\0')
    {
        ++len;
    }
    printf("%d\n",len);
    return 0;
}

int mystrcpy()
{
    char a1[] = "hello";
    char a2[10] = {0};
    int i = 0;
    while(a1[i] != '\0')
    {
        a2[i] = a1[i];
        ++i;
    }
    printf("%s\n",a2);
}

int mystrcat()
{
    char a1[11] = "hello";
    char a2[] = "hi";
    int i = 0,j = 0;
    while(a1[i] != '\0')
    {
        ++i;
    }
    while(a2[j] != '\0')
    {
        a1[i] = a2[j];
        ++i;
        ++j;
    }
    a1[i] = 0;
    printf("%s\n",a1);
    return 0;
}

int mystrcmp()
{
    char a1[] = "hello";
    char a2[] = "helloa";
    int i = 0;
    while(a1[i] == a2[i] && a1[i] != '\0' && a2[i] != '\0')
    {
        ++i;
    }
    printf("%d\n",a1[i] - a2[i]);
    return 0;
}

附录十   二维字符数组的遍历、排序、查找

#include<stdio.h>
#include<string.h>
int palindromic_judge()//回文判断
{
    char a[] = "2abcba2";
    int len = strlen(a);
    char b[len + 1];
    strcpy(b,a);
    for(int i = 0;i < len / 2;++i)
    {
        char t = a[i];
        a[i] = a[len - i - 1];
        a[len - i - 1] = t;
    }
    if(strcmp(a,b) == 0)
    {
        puts("is palindromic number");
    }
    else
    {
        puts("not is palindromic number");
    }
    return 0;
}

int frame_print()//边框打印
{
    int a[][4] = {1,1,1,1,
                  1,2,2,1,
                  1,1,1,1};
    int row = sizeof(a)/sizeof(a[0]);
    int col = sizeof(a[0]) / sizeof(a[0][0]);
    int i,j;
    for(i = 0;i < row;++i)
    {
        for(j = 0;j < col;++j)
        {
            if(i == 0 || i == row - 1 || j == 0 || j == col - 1)
            {
                printf("%-2d",a[i][j]);
            }
        }
        puts("");
    }
    return 0;
}

int reverse_double_array()//二维数组垂直逆序
{
    char a[][4] = {"111","222","333"};
    int row = sizeof(a) / sizeof(a[0]);
    int col = sizeof(a[0]) / sizeof(a[0][0]);
    int i;
    for(i = 0;i < row / 2; ++i)
    {
        char tmp[col];
        strcpy(tmp,a[i]);
        strcpy(a[i],a[row - i - 1]);
        strcpy(a[row - i - 1],tmp);
    }
    for(i = 0; i < row; ++i)
    {
        puts(a[i]);
    }
    return 0;
}

int select_sort()//选择排序
{
    char a[][4] = {"ccc","aaa","bbb"};
    int row = sizeof(a) / sizeof(a[0]);
    int col = sizeof(a[0]) / sizeof(a[0][0]);
    int i,j;
    for(i = 0; i < row - 1; ++i)
    {
        for(j = i + 1; j < row; ++j)
        {
            if(strcmp(a[i],a[j]) > 0)
            {
                char tmp[col];
                strcpy(tmp,a[i]);
                strcpy(a[i],a[j]);
                strcpy(a[j],tmp);
            }
        }
    }
    for(i = 0; i < row; ++i)
    {
        puts(a[i]);
    }
    return 0;
}

int bubble_sort()//冒泡排序
{
    char a[][4] = {"ccc","aaa","bbb"};
    int row = sizeof(a) / sizeof(a[0]);
    int col = sizeof(a[0]) / sizeof(a[0][0]);
    int i,j;
    for(i = row;i > 0; --i)
    {
        for(j = 0;j < i - 1;++j)
        {
            if(strcmp(a[j], a[j + 1]) > 0)
            {
                char tmp[col];
                strcpy(tmp,a[j]);
                strcpy(a[j],a[j+1]);
                strcpy(a[j+1],tmp); 
            }
        }
    }
    for(i = 0; i < row; ++i)
    {
        puts(a[i]);
    }
    return 0;
}

int insert_sort(char a[][4],int row)//插入排序
{
    //char a[][4] = {"ccc","aaa","bbb"};
    //int row = sizeof(a) / sizeof(a[0]);
    //int col = sizeof(a[0]) / sizeof(a[0][0]);
    int i,j;
    for(i = 1;i < row;++i)
    {
        char tmp[4];
        strcpy(tmp,a[i]);
        j = i;
        while(strcmp(a[j],a[j - 1]) < 0 && j > 0)
        {
            strcpy(a[j],a[j - 1]);
            --j;
        }
        strcpy(a[j],tmp);
    }/*
    for(i = 0; i < row; ++i)
    {
        puts(a[i]);
    }*/
    return 0;
}

int binary_find_the_temporary()//二分查找
{
    char tem[4] = "bbb1";
    char a[][4] = {"ccc","aaa","bbb"};
    int row = sizeof(a) / sizeof(a[0]);
    int col = sizeof(a[0]) / sizeof(a[0][0]);
    insert_sort(a,row);  
    int head = 0;
    int end = row - 1;
    while(head <= end)
    {
        int mid = (head + end) / 2;
        if(strcmp(tem,a[mid]) > 0)
        {
            head = mid + 1;
        }
        else if(strcmp(tem,a[mid]) < 0)
        {
            end = mid - 1;
        }
        else
        {
            break;
        }
    }
    if(head <= end)
    {
        printf("find the temporary\n");
    }
    else
    {
        printf("not found temporary\n");
    }
    return 0;
}

 附录十一  atoi 与 itoa

#include<stdio.h>
int itoa()
{
    int a = 1234;
    char b[1024] = {0};
    int i,j;
    i = 0;
    while(a != 0)
    {
        b[i] = a % 10 + '0';
        a /= 10;
        ++i;
    }
    for(j = 0;j < i / 2;++j)
    {
        int t = b[j];
        b[j] = b[i - j - 1];
        b[i - j - 1] = t;
    }
    puts(b);
    return 0;
}

int atoi()
{
    char a[] = "12340";
    int i,sum = 0;
    for(i = 0;a[i] != '\0';++i)
    {
        sum += a[i] - '0';
        sum *= 10;
    }
    printf("%d\n",sum / 10);
    return 0;
}

第五部分        面试题汇总

C语言

1、你使用的C语言开发环境是什么?

答:在windows操作系统下使用过Visual Studio、MinGW、和Vs Code+插件扩展+编译器配置。

        在Linux操作系统使用vim编辑器+gcc编译器。

        知识拓展:C语言开发环境包括操作系统(Linux、Windows)、编辑器(Vs Code、Vim、Motepad++)、编译器(gcc、Clang、Visual Studio编译器)。Visual Studio是一个集成开发环境,把编辑、编译等融合为一体了。

2、为什么用C语言开发?

答:C语言更接近底层,适合操作系统、驱动程序等需高效内存管理的程序。

        C语言没有垃圾回收机制,需要程序员自己管理内存,这使得程序更加高效,但是更容易出错

        C语言是编译型语言,编译后直接生成机器代码,执行速度通常比解释型语言(python)更快

        与java相比,C语言没有类和对象的概念,不支持面向对象编程,但其代码更加整洁,内存占用较少。

3、如何组织大型C语言项目中的代码?

答:使用makefile可以简化编译过程,自动处理依赖关系。

        通过定义目标、依赖项和规则,可以实现自动化编译、链接和生成可执行文件。

4、typedef和#define分别有什么作用,有什么区别?

答:typedef:为已有的数据类型定义一个别名,是在编译时起作用。

        #define:用来定义宏或者常量,在编译之前,预处理阶段进行文本替换。

5、什么是枚举类型?如何定义和使用?

答:枚举是一种用户自定义类型,用于定义一组命名的整型常量。enum weekday{mon、tue、wed、thu、fri、sat、sun},成员值为0-6,可用枚举类型表示状态、选型等,提供代码可读性.

6、全局变量和局部变量可以重名吗?需要注意什么?

答:可以重名,但是同一作用域局部变量会覆盖全局变量。代码可读性会变差,容易引起混淆。

7、if、switch、while、break、continue用法

答:if:条件判断语句,根据条件执行不同代码块。

        switch:多分支选择语句,适用于多个可能值得情况。

        while:条件循环,条件为真时重复执行代码块。

        break:立即跳出循环获switch语句。

        continue:跳出当前循环剩余部分,执行下一次循环。

8、手写strcpy、strcat、strcmp、memcpy

#include<stdio.h>
char *strcpy(char *src,const char *dst)
{
    while((*src++ = *dst++) !='\0');
    return src;
}

char *strcat(char *src,const char *dst)
{
    while(*src++ != '\0');
    --src;
    while((*src++ = *dst++) != '\0');
    return src;
}

int strcmp(const char *src,const char *dst)
{
    int len;
    while(*src == *dst)
        {
            src++;
            dst++;
        }
    len = *src - *dst;
    return len;
}

void *memcpy1(void *src,const void *dst,size_t n)
{
    char *s = (char *)src;
    const char *d = (const char *)dst;
    while(n--)
        *s++ = *d++;
    return s;
}

int main()
{
    char src[1024] = "hello";
    char dst[1024] = "world";
    //printf("%d\n",strcmp(src,dst));
    memcpy1(src,dst,sizeof(dst));
    puts(src);
    return 0;
}

-注意:memcpy 不适用于有内存重叠的情况,建议使用 memmove。

9、带参宏和函数的区别

答:带参宏是在编译前进行文本替换,函数是在编译时进行解析的。

        宏没有类型检查,因此使用时容易出错,而函数有类型检查。

        宏可以减少函数调用的开销,因为它是直接替换,但代码量大时会增加编译后的文件大小。

        函数适合代码复用和调试,而宏适合简单的代码片段。

10、内核中出现的inline是什么意思?

答:inline关键字用于建议编译器将函数内联,即将函数调用处替换为函数体的内容,避免函数调用的开销。

        inline并非强制要求,编译器可以根据情况决定是否内联。

        内联函数适合频繁调用、代码简单的函数,但对于大型函数可能导致代码膨胀。

11、带参宏实现两个数比较

#include<stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
    int a = 10;
    int b = 20;
    printf("%d\n",MAX(a,b));
}

12、带参宏实现两个数交换

#include<stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
#define SWAP(a,b) do{int t = a;a = b;b = t;}while(0)
int main()
{
    int a = 10;
    int b = 20;
    SWAP(a,b);
    printf("%d,%d\n",a,b);
}

13、函数是如何调用?

答:函数调用通过压栈传递参数、保存返回地址,跳转到函数体执行代码;

        执行完毕后,根据返回地址回到调用函数的位置;

        函数调用涉及到栈的管理,包括局部变量的分配与回收。

14、函数参数传递

1)解释值传递和指针传递的区别

答:值传递:将参数的副本传给函数,函数内的修改不会影响原始值;

        指针传递:传递的是参数的地址,函数内的修改会影响原始值。

2)如何通过指针实现指针传递?

答:通过传递变量的地址,可以实现函数内修改原始变量的值

3)一个函数怎么返回两个参数?

答:可以通过指针获结构体返回多个值。

15、递归函数

1)什么是递归?

答:递归是一种函数中调用自身的编程方法。常用于一个问题可以分解为更小规模的相同问题的场景,比如阶乘(4! = 4 * 3 * 2 * 1)、斐波那契数列(前两个值已知0、1,后面的每个数为前两个数的和)。

2)递归函数的优缺点是什么?

答:优点:代码简洁,适合处理递归定义的问题

                  易于理解自然分治问题,如树的遍历、图的深度优先搜索等。

        缺点:每次递归调用都会占用栈空间,容易导致栈溢出;

                   比迭代方法(循环结构)在性能上更差,尤其深度递归。

3)如何避免递归中的无限循环?

答:确保递归基准条件的存在和正确性,即在特定情况下不在递归。

        确保每次递归都向基准条件靠近。

16、数组

1)数组下标越界会导致什么问题?

答:数组下标越界是指访问的下标超过了数组的有效范围。问题:

         未定义行为:程序可能访问到未被分配的内存区域,导致数据损坏、程序崩溃或异常行为。

        数据覆盖:可能修改相邻内存区域的数据,造成不可预料的问题。

        安全漏洞:在某些情况下可能导致安全漏洞,如缓冲区溢出攻击

2)多维数组在内存中的存储方式是什么?

答:多维数组在内存中是按照行优先顺序存储的。即最右边的下标变化最快。

3)什么是字符串?

答:字符串是以'\0'结尾的字符数组。'\0'表示字符串的终止。

        字符串在C语言可以通过字符数组或char *指针来处理。字符串的操作包括strcpy、strlen、strcat、strcmp等标准库函数。

4)数组和链表的区别?

答:存储方式:数组:连续的内存空间,访问元素速度快(支持随机访问),但扩展性差。

                         链表:非连续的存储空间,通过指针连接每个节点,扩展性好,但访问速度慢。

17、指针

1)什么是指针,介绍一下你对指针的理解?

答:指针是一种表示地址的数据类型,指针变量是存储内存地址的变量,它的值是某个变量的地址。通过指针可以操作地址对应的内容。

        使用指针可以高效的处理函数形参,实现被调修改主调,可以方便的操作数组和结构体、动态分配内存等。

2)如何定义和初始化一个指针变量?

答:定义指针时,需要指定指针所指向的数据类型,定义时可以直接把地址赋给指针变量。

3)什么是地址运算符和解引用运算符?

答:地址运算符(&):用于获取变量地址。

        解引用运算符(*):用于访问指针所指变量的值。

4)空指针是什么?如何使用?

答:空指针:不指向任何有效内存地址的指针,通常用来表示指针未被初始化。

        使用指针时要先检查是否为空指针,以防对无效地址解引用操作。

5)野指针是什么?如何使用?

答:野指针:指向未知或已释放内存的指针,它指向的内存区域可能不再有效,易引发未定义行为。

        避免:在指针释放后,设置为空

                   在使用指针前确保其已经初始化。

                    在使用动态内存时,注意正确管理malloc、free

18、指针运算

1)指针可以进行哪些运算?

答:加减运算:如ptr+1会移动到下一个元素的地址

        比较运算:如==、!=可以比较两个指针是否相等

        差值计算:两个指针相减,结果是他们之间元素的个数。

2)如何比较两个指针?

答:直接用比较运算符。

19、指针与数组

1)数组名与指针有什么关系?

答:数组名表示首元素地址,类型为指向数组元素类型的指针。

       但是数组名不是普通的指针,不能对其进行加减运算改变其指向。

2)如何通过指针访问数组元素?

答:可以通过指针加偏移量的方式访问元素。

        也可以数组和指针结合的方式。

3)指针数组和数组指针的区别是什么?

答:指针数组:数组的每个元素都是指针。int *p[3]

        数组指针:指向数组的指针。int (*p)[3]

20、多级指针

1)什么是二级指针?

答:指向指针的指针,即它存储的是一个指针变量的地址。

2)如何定义和使用二级指针

答:定义二级指针时需要两个**表示

        使用二级指针时,可以用*和**访问。

二级指针在C语言的两个使用地方:

        1、在被调函数中修改主调函数的指针变量时,需要传递该变量的地址,形参使用二级指针接收。

        2、指针数组的数组名,本质是一个二级指针,传参可以使用二级指针接收。

21、函数指针

1)什么是函数指针?如何定义和使用函数指针?

答:函数指针是指向函数的指针,可以用来调用函数。其声明包括函数返回类型和参数列表。

        定义后可以指向特定函数,并通过指针调用该函数

2)指针函数和函数指针的区别是什么?

答:指针函数:返回一个指针类型值的函数

        函数指针:指向函数的指针,用于保存函数的地址。

22、结构体与共用体

1)如何定义和使用结构体指针?

答:定义:struct Student{成员列表};

        定义结构体指针并初始化:struct Student student = {值};

                                                    struct Student *ptr = &student;

        通过指针访问结构体成员,需要使用箭头操作符->。

2)如何将结构体作为函数参数?

答:值传递:函数内部会复制整个结构体,原结构体不会受影响。

        指针(引用)传递:通过结构体指针传递,会修改原结构体。

3)结构体的内存对齐是什么?如何按指定字节对齐?

答:内存对齐是为了提高内存访问效率,结构体中的成员会被自动对齐到某个字节的边界(通常是该成员类型大小的整数倍)。

        如果不使用对齐规则,可能会浪费内存。通过编译器提供的#pragma pack或_attribute_(aligned(x))。

例如#pragma pack(1)//设置为一字节对齐

        #pragma pack()//恢复默认对齐

23、共用体

1)共用体和结构体的区别是什么?

共用体:所以成员共享同一块内存,大小取决于最大成员的大小。

结构体:每个成员都有自己的存储空间,总大小是所有成员大小之和。

定义共用体:

union DATA{成员列表};

2)共用体在内存中的存储特点是什么?

答:共用体所有成员共用同一块内存,且共用体大小等同于最大成员大小。

        修改一个成员会覆盖其他成员的值。

3)如何判断大小端?

答:利用共用体的特性:通过访问统一内存地址的不同数据大小判断大小端。

union{short s;char c[];}

给s赋值,用数组c查看两个字节分布,如果s的低字节存在于低地址,就是小端存储,反之就是大端存储。

24、位域

1)什么是结构体位域?如何定义和使用位域?

答:位域:用于在结构体按位分配内存空间适用于存储二进制标志等信息。

        位域定义:

        struct Flags{unsigned int flag1 : 1;//使用1位        

                            unsigned int flag2 : 2;//使用2位

                            unsigned int flag1 : 1;//使用1位

                                };

2)位域的应用场景有哪些?

答:位域常用于节省内存空间,特别是在需要存储多个状态标志或硬件寄存器操作时。

        例如,用于嵌入式系统对寄存器的操作,或者存储大量的布尔标志

25、编译与连接

1)编译过程的四个阶段是什么?

答:预处理:处理宏定义、文件包含、条件编译等,生成.i文件。

        编译:将预处理后的代码转换为汇编代码,生成.s文件。

        汇编:将汇编代码转化为机器码,生成.o文件(目标文件)。

        链接:将多个目标文件及库文件链接成可执行文件。

2)预处理器的作用是什么?gcc如何只进行预处理?

答:预处理器主要负责#define、#include、#ifdef等指令,将代码进行替换、宏展开和条件编译。

        使用gcc进行预处理,只需加-E参数:gcc -E main.c -o main.i

        main.i文件包含了预处理后的代码。

3)编译器的作用是什么?gcc如何进行编译?

答:编译器将c代码转化为汇编代码,主要负责语法分析、语义分析、优化等。

        使用gcc编译时,使用-S参数:gcc -S main.c -o main.s

4)汇编器的作用是什么?gcc如何进行汇编?

答:汇编器将汇编代码转化为机器码,生成目标文件。

        使用gcc进行汇编时,使用-c参数:gcc -c main.c -o main.o

        main.o是机器码的目标文件.

5)链接器的作用是什么?

答:链接器负责将多个目标文件、库文件合并,解决函数和变量的引用,生成可执行程序。

        链接包括静态链接(在编译时确定库函数地址)和动态链接(在运行时加载库)

        使用gcc进行链接时,直接编译源文件会隐含链接过程:gcc main.o -o main

        最终生成的main就是可执行文件。

26、存储类

1)什么是auto存储类?它的默认作用域和生命周期是什么?

答:auto存储类:C语言中局部变量的默认存储类型,不需要显式声明。

        作用域:局限于变量所在代码块或函数内

        生命周期:仅在函数调用期间有效,函数调用结束后,变量会被销毁,释放其占用的内存空间。

2)什么是static存储类?如何使用static?

答:static存储类:用于将变量或函数的作用域限制在声明所在的文件或函数内,但变量的生命周期是全局的,即它们在程序运行期间一直存在。

        局部static变量:在函数定义的static变量在函数多次调用间保持其值不变。

        全局static变量:在文件中定义的全局static变量只能在该文件中访问,其他文件无法访问。

3)什么是extern存储类?如何使用extern?

答:extern存储类用于声明全局变量或函数,该变量或函数可能定义在其他文件中。使用extern可以在多个文件之间共享变量。

        使用extern声明变量,而不分配内存空间:

        file1.c

        int a = 10;

        file2.c

        extern int a;

        在file2.c中,extern告诉编译器global_var是在其他地方定义的,全局变量的值可以跨文件共享。

4)什么是register存储类?在什么情况下使用register变量?

答:register存储类:告诉编译器尽量将该变量存储在CPU的寄存器中,而不是内存中,提高访问速度。

        通常用于高频访问的局部变量,例如循环中的计数器变量。现代编译器已经自动优化了寄存器分配,因此显示使用register较为少见。

5)register存储类是否保证变量存储在寄存器中?

答:不保证。如果寄存器资源紧张,变量仍可能存储在内存。

6)你知道volatile关键字吗?

答:volatile关键字告诉编译器该变量的值可能会在程序之外被修改(例如通过硬件或其他线程),因此每次访问该变量是都必须从内存中重新读取其值,而不是使用寄存器中的缓存值。

        常用于硬件寄存器、共享内存或信号处理。

27、变量的作用域与生命周期

1)什么是变量的作用域?有哪些类型?

答:作用域是指变量在程序中的可见范围。分类:

                局部作用域:变量在函数或代码块内可见,离开该块后不可见。

                全局作用域:变量在整个程序中可见。

                文件作用域:用static修饰的全局变量只能在声明所在文件访问。

                块作用域:只在特点的代码块{}内有效,如函数内或循环内。

2)什么是变量的声明周期?局部变量和全局变量的生命周期有何不同?

答:生命周期是指变量在内存中的存在时间范围。

                局部变量的生命周期:从变量声明开始,到函数调用结束后销毁,内存被释放。

                全局变量的生命周期:在整个程序运行期间都存在,直到程序退出后销毁释放。

3)静态变量的生命周期是什么?它们在多次函数调用间是如何保持其值的?

答: 静态变量的生命周期:静态变量在程序运行的整个过程都存在。

        即时在局部函数中声明的静态变量,其生命周期也是全局的,程序开始时分配内存,程序结束时释放。

        静态局部变量在多次函数调用间会保持其值不变,因为它们只在程序加载时初始化一次,不会随着函数调用而销毁。

预处理器

28、宏定义与宏替换(#define)

1)什么是宏定义?如何使用#define定义一个常量?

答:宏定义是通过#define指令为一段代码或常量创建一个替代名称。

                编译器在编译前会将代码中的宏替换为其定义的内容。

        使用#define定义常量的方法:

                #define PI 3.14159

        这样,程序中所有出现PI的地方都会在预处理阶段被替换为3.14159.

2)如何使用#define定义一个带参数的宏?

答:带参数的宏类似于函数,但在预处理阶段进行文本替换。

                定义时给出参数名:#define add(a,b)        ((a) + (b))

                加括号确保运算的正确性。

3)宏和函数的区别是什么?使用宏有哪些优缺点?

答:区别:宏在预处理阶段展开,函数在运行时调用。

                  宏没有类型检查,函数有类型检查

                  宏没有参数求值,可能导致意外结果,函数则有参数求值。

                     (带参宏的参数只是文本替换,函数传参:是将实参(或表达式结果)传给形参)。

        优点:速度快,因为宏直接展开,不需要函数调用开销。

        缺点:代码可读性差,调试困难。

                   缺少类型检查,容易引发错误

4)什么是宏替换?宏替换的过程是怎样的?

答:宏替换:在预处理阶段,编译器将代码所有符合宏定义的地方替换为其定义的内容。

        过程:1、预处理扫描源代码。

                   2、遇到#define宏时,记录宏名及其替代文本。

                   3、遇到宏名,将其替换为宏的定义内容

29、文件包含(#include)

1)#include的作用是什么?

答:#include指令用于在预处理阶段将指定文件的内容插入到当前文件,通常用于导入头文件的函数声明、宏定义等。

2)#include<>和#include""的区别是什么?

答:#include<> :用于包含系统头文件,编译器会在标准库路径中搜索文件。

        #include"":用于包含用户定义的头文件,编译器会首先在当前目录中搜索文件,如果找不到,再在标准库路径中搜索。

3)如何避免头文件的重复包含?

答:使用头文件保护符或#pragma once:保证头文件只被包含一次。

        #pragma是一种编译器指令。

30、条件编译(#if,#ifdef,#ifndef,#else,#elif,#endif)

1)什么是条件编译?有哪些条件编译的指令?

答:条件编译是根据宏定义来决定是否编译某段代码的过程,主要用于跨平台代码或调试模式。

        常见的条件编译指令:

                #if,#ifdef,#ifndef:根据条件是否满足进行编译

                #else,#elif:条件不满足时执行其他代码

                #endif:结束条件编译块

2)如何使用ifdef和ifndef?

答:#ifdef:检查某个宏是否已定义,定义时编译其后的代码。

        #ifndef:检查某个宏是否未定义,未定义时编译其后的代码

3)在什么情况下使用条件编译?

答:多文件编程设计头文件:头文件中使用条件编译,防止重复包含,提高编译效率。

        调试和发布模式:调试代码和日志只在开发模式下编译

        库版本兼容:根据不同版本进行特定功能的代码处理。

        跨平台代码:根据平台差异编译不同代码段

31、内存管理

动态内存分配(malloc、calloc、realloc、free)

1、如何使用malloc分配动态内存?

答:malloc用于分配指定大小的内存块,并返回内存块的指针:

        int *ptr = (int *)malloc(10 * sizeof(int));分配了可以存储10个int的内存块。

2、malloc和calloc的区别是什么?

答:malloc:分配未初始化的内存,内存的数据是随机值。

        calloc:分配并初始化内存,所有分配的字节被初始化为0。

        int *ptr = (int *)calloc(10, sizeof(int))//分配并初始化10个int

3)如何释放动态分配的内存?为什么需要释放?

答:使用free函数释放内存,避免内存泄漏。free(ptr)

        释放动态分配的内存是必要的,否则会导致内存泄漏,程序长时间运行后可能导致耗尽内存。

4)堆和栈的区别?

答:栈:自动管理内存,用于局部变量,空间有限且生命周期短,函数返回后自动释放。

       堆:手动管理内存(如malloc分配),空间大但管理复杂,需手动释放。

5)什么是内存溢出?

答:指程序再请求内存时超出可用内存大小,导致程序崩溃或异常行为。

6)什么是内存泄漏?

答:指程序中分配的内存没有被释放,导致内存无法重新分配使用,最终可能耗尽系统内存。

7)什么是内存碎片?

答:指反复的内存分配和释放过程中,堆中产生许多无法使用的小内存块,导致无法满足大块内存的分配申请。

32、其他

1)const和#define的区别

答:const:编译时和运行时都有类型检查,可以用于定义常量变量,占用内存。

        #define:预处理阶段替换,没有类型检查,不占用内存。

2)sizeof和strlen的区别

答:sizeof:计算变量或类型所占内存大小(字节数),编译时计算,适用于数组指针等。

        strlen:计算字符串长度(字符数,不包含\0),需要遍历字符串直到'\0',运行时计算。

3)静态变量和局部变量的区别

答:静态变量:生命周期是程序整个运行期,值在函数调用间保持不变。

        局部变量:生命周期仅在函数执行期间,函数结束时被销毁。

33、数据结构

1)数据结构学过什么?

答:顺序表、单向链表、双向链表、栈、队列、哈希表,了解内核链表、二叉树。

2)顺序表和链表有什么区别?

答:顺序表:在内存中选取连续的存储空间

        1、访问单元方便,可以直接下标访问

        2、使用时需要预先分配内存空间,分配不合适可能出现内存越界或浪费

        3、插入删除效率低,需要移动后续数据。

      链表:在内存中可以使用非连续的内存空间存储

        1、访问单元需要从头遍历

        2、不需要预先分配空间,需要时动态申请

        3、插入删除方便,只需要移动具有连接关系的指针域即可。

3)单向链表和双向链表的区别

答:单向链表的每个单元只保存数据和后继指针,只能从前往后遍历。

        双向链表的每个单元保存数据、前驱指针和后继指针,从前往后或者从后往前遍历皆可。

4)什么是内存泄漏?如何排查和避免内存泄漏?

答:内存泄漏值在堆区申请的空间没有释放。

        排除:在Linux下安装valgrind,终端输入命令valgrind  可执行程序(a.out),即可打印内存探测报告,在程序运行过程中检测是否内存泄漏或者内存使用的其他错误。

        避免:编程时使用完内存及时调用free释放

                   利用内存管理机制统一管理释放,比如内存池。

5)什么是内存碎片?如何避免内存碎片?

答:内存碎片指内存空间被分割成不连续的小块,导致没法有效分配较大的连续内存。分为:

        内内存碎片:分配的内存块中有未被使用的现象,比如结构体对齐导致部分空间未使用。

        外内存碎片:空闲内存空间被分散成多个小块,虽然总空闲空间足够,但没法合成连续大块内存。比如频繁动态分配和释放不同大小的内存块,导致内存空间分割。

        如何避免:使用动态分配策略:根据需求分配内存,避免固定分区导致的浪费。

                          优化内存对齐:合理调整结构体的内存对齐,减少填充字节。

                           选择合适的内存池:为特定对象预分配固定大小内存块,避免频繁动态分配。

6)如何查找链表倒数第个节点?

答:方案一:先遍历得出总结点数,再遍历总结点-k个节点就找的了。

       方案二:快慢指针法:快指针先遍历k个节点,慢指针再开始遍历,等到快指针遍历完,慢指针就指向k节点。

7)如何实现双向链表插入删除?

答:假设p是要插入节点的前节点。

         插入1、让插入节点的后继指针指向p的后继结点

                2、让插入节点的前驱指针指向p

                3、让p的后继指针指向要插入节点

                4、让p的后继结点的前驱指针指向要插入节点

        删除1、让要删除节点的后继结点的前驱指针指向要删除节点的前驱节点

                2、让要删除节点的前驱节点的后继指针指向要删除节点的后继节点

                3、free删除节点空间

8)怎么判断一个链表是否有环?

答:快慢指针法:快指针一次走两步,慢指针一次走一步,如果将来快指针没走到NULL,且两指针相遇,为有环。

9)队列和栈的区别是什么?在什么场景使用?

答:队列:一端插入、另一端删除的线性表。先进先出(FIFO)

                  应用:要求数据按照原顺序存储的场景,比如数据缓冲区、二叉树层序遍历

        栈:一端插入删除。先进后出(FILO)

                  应用:需要回溯的情况,比如嵌套函数调用、二叉树深度优先遍历。

10)系统栈和数据结构中的栈有什么区别?

答:系统栈:操作系统在程序运行过程中维护的存储空间,由操作系统自动管理。

                  应用:保存程序运行过程中的局部变量、函数形参、返回值、函数的调用关系等

        数据结构栈:开发人员自己维护管理的数据结构。

                  应用:保存需要按照原顺序存储的数据。

11)如何实现二叉树的深度优先遍历算法和广度优先遍历算法?

答:深度优先:

                前序遍历:根左右        中序遍历:左根右        后序遍历:左右根

                实现:使用递归或栈辅助

        广度优先:

                层序遍历:按照从上至下,从左至右的方式逐层遍历。

                实现:队列辅助

12)满二叉树第k层有多少个节点?总共有多少个节点?

答:满二叉树第k层:2^(k-1)

       总共:2^k - 1zai

13)冒泡排序、选择排序、插入排序、快速排序如何实现?

答:冒泡排序:从第一个元素开始,往后相邻两元素比大小,前者大就交换二者值,直到最后一个元素,这样就把最大值放到最后。第二轮继续从首元素开始比较,直到倒数第二个,如此循环直到只剩首元素。

        选择排序:先从第一个元素开始,跟后面的元素一一比较,如果前者大就交换二者值,这样就把最小值放在前面。再从第二个元素比较,如此循环直到只剩最后一个元素。

        插入排序:从第二个元素开始,跟前面的元素比较,只要小于前面的第i个元素,就让i开始整体后移,直到遇到小于第二个元素的值,把它插到这个元素后面,没遇到就查到首元素位置,这样就把前面几个元素顺序排好了,如此循环,直到最后一个元素处理完。

        快速排序:采用自然分治的方法,先找一个基准值,让基准值右边小于基准值的元素移到左边,让基准值左边大于基准值的元素都移到右边,然后基准值两边再分别找一个基准值,重复上述操作,直到最后没法分区,只剩基准值。

14)什么是时间复杂度?常见时间复杂度有哪些?

答:时间复杂度:是对算法运行所花时间的一个度量,用来衡量算法的效率;时间复杂度是算法所花时间和数据量之间的增长关系。用大写O表述。

        常见时间复杂度:O(1)、O(logn)  O(n)  O(nlogn)  O(N^2)  O(2^n)  O(n)

        O(1):下标访问数组元素

        O(logn):二分法查找

        O(n):数组遍历

        O(nlogn):高效的排序算法:快速排序

        O(n^2):冒泡排序、选择排序、插入排序

15)什么是空间复杂度?常见空间复杂度有哪些?

答:空间复杂度:算法运行过程中临时占用存储空间的大小。

        可以看成是程序中定义变量的个数,表面程序需要多少额外内存来存储数据和执行计算,对资源受限的环境很重要。

        O(1):定义几个固定大小变量

        O(n):创建一个长度为n的数组。

        O(n^2):创建一个n*n矩阵。

        O(logn):二叉树的遍历,递归实现二分查找

        O(nlogn):归并排序

原始数组: [38, 27, 43, 3, 9, 82, 10]
分解过程:
[38, 27, 43, 3] | [9, 82, 10]
[38, 27] | [43, 3] | [9, 82] | [10]
[38] | [27] | [43] | [3] | [9] | [82] | [10]  (最小单元)
合并过程:
[27, 38] | [3, 43] | [9, 82] | [10]
[3, 27, 38, 43] | [9, 10, 82]
[3, 9, 10, 27, 38, 43, 82]  (最终有序)

16)冒泡排序、选择排序、插入排序、快速排序的时间复杂度分布是什么?是否稳定?

答:排序算法的稳定性:在待排数据中出现两个相同数据,经过排序,这两个相同数据的相对位置(前后顺序)没有发生变化,该排序算法就是一个稳定的排序算法。

        冒泡排序:O(n^2):稳定的排序算法

        选择排序:O(n^2):不稳定

        插入排序:O(n^2):稳定

        快速排序:O(nlogn):不稳定

17)二分查找的前提条件是什么?时间复杂度?

答:二分查找的序列必须是一个有序序列。

        时间复杂度O(logn)。

34、Linux软件编程

基本命令

1)文件与目录操作

答: ls:列出目录内容(-l详细信息,-a显示隐藏文件)

        cd:切换目录(cd~回家,cd .. 上级目录)

        pwd:显示当前路径

        cp:复制文件/目录(-r递归复制目录)

        mv:移动重命名文件

        rm:删除文件/目录(-r递归删除,-f强制删除)

        mkdir:创建目录(-p创建多级目录)

        touch:创建空文件或更新文件时间戳

        find:搜索文件(find /path -name “*.txt”)

        cat:查看文件内容

        less/more:分页查看文件

2)权限与用户管理

答:chmod:修改文件权限(chmod 755 file或chmod -x file)

        chown:修改文件所有者(chown user:group file)

        sudo:以管理员权限执行命令

        useradd/userdel:添加删除用户

        passwd:修改用户密码

        su:切换用户(su -username)

3)系统监控及进线程相关

答:top/htop:实时监控进程和资源占用

        ps:查看进程状态(ps aux显示所有进程)

        pstree:查看进程间关系

        kill:终止进程(kill -9 PID强制终止)

        df:查看磁盘空间(-h人类可读格式)

        du:查看目录大小(du -sh /path)

        free:查看内存使用(free -h)

        uname:查看系统信息(uname -a)

        shutdown/reboot:关机/重启

4)网络相关

答:ping:测试网络连通性

        ifconfig/ip:查看/配置网络接口(新版用ip addr)

        netstat:查看网络连接(netstat -tuln监听端口)

        ss:替代netstat的现代工具

        ssh:远程登录(ssh user@host)

5)文本处理

答:grep:文本搜索(grep “pattern” file)

        awk:文本分析工具(awk ‘{print $1}’提取第一列)

        head/tail:查看文件头/尾(tail -f实时追踪日志)

        sort:排序文本

        diff:比较文件差异

6)压缩与解压

答:tar:打包/解压(tar -czvf archieve.tar.gz.dir压缩,tar -xzvf解压)

        gzip/gunzip:压缩/解压.gz文件

        zip/unzip:处理zip文件

7)常用组合示例

答:查看指定进程信息:ps -aux|grep a.out

        统计代码行数:find . -name "*.c" | xargs wc -l

        实时监控日志:tail -f /var/log/syslog | grep “error”

35、进线程

1)什么是进程?

答:正在运行的程序称为进程。

2)在系统中使用什么结果表示一个进程?

答:在系统中使用pcb块(进程控制块),它表示一个进程相关的资源信息。、

3)系统通过什么区分两个正在运行的进程?

答:PID,进程标识符

4)当一个进程开始运行,系统(32位)给用户层分配的内存空间是多大?有哪些区域?每个区域存储什么东西?

答:3GB内存空间,从低到高地址,代码段(程序员编写的代码),数据段(全局变量,静态遍历),堆(由程序员控制生命周期的区域,在程序运行时需要内存),map/share(内存映射,共享库),栈(8M,局部变量,函数参数,返回地址)

5)什么是虚拟地址?

答:虚拟地址是操作系统为每个进程提供的一组逻辑地址,这些地址与物理内存地址分离。每个进程都有自己独立的虚拟地址空间,这样可以防止进程之间的内存操作互相干扰,提高系统稳定性和安全性。虚拟地址空间大小可以超过实际物理内存大小,通过分页和换页技术,操作系统可以在物理内存不足时,将部分数据存储在硬盘上,从而实现虚拟内存机制。

6)进程是干什么的?

答:并发

7)进程的状态有哪些?

答:运行态、就绪态、阻塞态。在linux中有运行态、就绪态、可中断睡眠、不可中断睡眠、僵尸态、暂停态。

8)进程中包含哪些资源?

答:包含PID、已经打开的文件、信号处理函数、工作路径、内存映射等。

9)怎么查看系统正在运行进程的状态信息?

答:ps aux

10)查看系统正在运行线程的状态信息

答:ps -elf

11)通过哪个函数新建进程?函数返回值的含义?父子进程关系?

答:fork,返回值>0表示本执行体是父进程,返回值是子进程的pid号。返回值=0,表示执行体是子进程。子进程会共享父进程0~3GB。当子进程或父进程中有数据发生变化的时候,子进程会发生对应的复制,这个叫写时复制。之前老的系统直接复制,那样效率没有写时复制高。

12)进程退出后,需要注意什么?如何避免僵尸进程的产生?

答:会变成孤儿进程和僵尸进程,僵尸进程需要父进程调用回收函数,回收对应的pcb块,僵尸进程才会彻底消亡。

  • 僵尸进程:子进程死了,但父进程不“收尸”。

  • 孤儿进程:父进程死了,子进程被 init 收养。

13)在子进程中需要执行一个外部可执行程序需要怎么做?与原来的子进程相比发生什么变化?

答:使用exec系统调用函数。子进程的代码段会被新执行程序段替换,当然数据段也会被替换。其他的堆、栈,map/share区,会根据新程序的需求来。

14)什么是线程?

答:线程是轻量级进程,一般是一个进程中的控制序列。

                进程是系统中最小资源分配单位

                线程是系统中最小执行单位

15)进线程区别

答:线程属于某个进程。

        共同点:都可以并发

        一个进程中至少有一个线程

        进程创建的开销比线程大

        进程空间独立,线程除了栈区外,其他共享。

        进程可以向系统分配资源,线程不行

        进程相对稳定,线程稳定性差些

        进程适合完成并发并且比较消耗资源的任务,线程适合完成并发资源消耗少的任务

16)线程创建函数是什么?函数指针的作用?

答:pthread_create();

#include <pthread.h>

int pthread_create(
    pthread_t *thread,          // 线程标识符指针
    const pthread_attr_t *attr, // 线程属性(可设为NULL)
    void *(*start_routine)(void *), // 线程执行的函数
    void *arg                  // 传递给线程函数的参数
);

使用函数名作为一个函数的参数时,被传递的函数是被动调用的。函数调用时机跟接收函数名的函数相关。

17)什么是线程分离属性?什么时候用?

答:当线程退出后,对应的栈区有系统回收。在多线程中,不能阻塞回收资源时使用。

18)什么是互斥和同步?Linux中提供对应机制的名称是什么?什么是内核对象?

答: 互斥:对临界资源的排他性访问

        同步:有顺序的排他性访问

        机制:互斥锁、信号量(变量本身是在本进程的pcb块中)。

        内核对象:是操作系统为一些系统级对象(进程、线程、信号量)维护的一些数据结构。

19)什么是死锁?死锁产生的四个必要条件是什么?如何避免死锁发生?如果发生死锁怎么解决?

答:死锁:在并发程序中使用锁机制,由于程序锁使用(申请、释放)过程中,逻辑不对,导致无线阻塞的现象。

        产生死锁四个必要条件:

                互斥条件:一个资源每次只能被一个进程使用

                请求与保持条件:一个资源因请求资源而阻塞时,对已获得的资源保持不放。

                不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

                循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

        解决死锁:分析程序中使用锁机制的地方,确保申请释放正确。

20)进程间通信方式有哪些?

答:管道、信号、共享内存、消息队列、信号量集、网络套接字

21)有名管道的读阻塞、写阻塞、管道破裂的触发条件是什么?

答:读阻塞:写端存在,读管道,如果管道为空会读阻塞

        写阻塞:读端存在,一直向管道中写,超过64k,会写阻塞。

        管道破裂:读端关闭,写管道,会导致管道破裂。

        read 0:写端关闭,读管道,如果管道为空,会读到0.

22)无名管道、有名管道区别?

答:无名管道(pipe):只能父子进程间使用

        有名管道(fifo):本机任意进程间使用

23)Linux中什么是信号?你用过哪些信号?

答:异步通信中,一个进程需要通知另一个进程,某件事件发生,需要采取什么动作。

        在Linux系统中共有64个信号,可以通过kill -l查看

        我用过:9、SIGKILL(强制终止进程)        10、SIGUSR1 (用户自定义,默认终止进程)                       11、SIGSEGV(段错误,默认终止进程并生成转储文件core dump)       

                     14、SIGALRM(定时器超时信号)        17、SIGCHLD(子进程状态变更信号)                           19、SIGSTOP(强制暂停进程)

24)进程收到信号后,有哪些处理?可以修改处理动作吗?

答:系统提供的默认动作:

        term        关闭进程        

        lgn           忽略信号

        core         关闭进程,产生core文件

        stop         进程暂停

        cont         进程继续

        用户可以通过signal捕获信号,自定义行为。9和19号信号不可修改

#include <signal.h>
void handler(int sig) {
    printf("捕获到信号 %d\n", sig);
}
signal(SIGINT, handler);  // 捕获 Ctrl+C(SIGINT)

25)共享内存有了解过吗?和管道的区别?

答:进程间通信的一种,在内核创建一片内存区域。由于内存在内核中,相关进程都可以访问(相当于线程的共享变量)。共享内存共享数据比管道效率高,因为管道有读写阻塞、管道破裂。共享内存是一个底层的内存结构(类似数组)。写入大小超过共享内存大小则越界。在读取共享内存,如果写入方没有写入数据,不会阻塞。

26)说出至少两种Linux中进程调度算法

答:man 2 SCHED_SETSCHEDULER

非实时:

        SCHED_OTHER        普通进程默认策略

        SCHED_BATCH        后台进程,优先级低于普通进程

        SCHED_IDLE            cpu空闲时执行,优先级最低

实时:

        SCHED_FIFO            先进先出,高优先级独占cpu,直到主动退出或阻塞

        SCHED_RR               时间片轮转(控制每个进程的执行时间,通过快速轮转达到伪并行),同优先级按固定时间片轮流执行,时间片用完重新排队

 27)软件编程中,现有一个队列结构,一边写入队列,一边读队列如果是多任务中,需要注意什么?

答:需要提供读写阻塞机制(信号量),可以利用锁机制保护队列(互斥锁)。

28)跨线程使用全局变量时,需要注意什么?

答:需要使用锁的机制,保护全局变量。

29)你知道哪些锁机制?

答:互斥锁:多线程/多进程中,只能有一个获得锁资源,不能获得资源则休眠,等待条件满足,对并发性能影响较大。

        读写锁:多进程/多线程对公告资源读写操作时,如果都是读操作,则可以同时执行,如果是写操作,则互斥

        自旋锁:类型互斥锁,如果不能获得资源则一直处于忙等待,并不响应中断。自旋锁保护的区域内,只能是非常快捷的操作。不能休眠、递归、复杂的运行。

36、网络

1)OSI模型是什么?

答:OSI指网络通信分层框架,共七层。

        应用层:为用户提供服务。如电子邮箱、文件传输等。

        表示层:为不同主机的通信提供统一的数据表现形式。

        会话层:负责信息传输的组织协调,管理进程会话过程

        传输层:负责网络通信两端的数据传输,提供可靠或不可靠的传输服务

        网络层:负责数据传输的路由选择和网际互连。

        数据链路层:负责物理相邻(通过网络介质相连)的主机间数据传输,主要作用包括物理地址寻址、数据帧封装、差错控制等。该层可分为逻辑链路控制子层(LLC)和介质访问控制子层(MAC)。

        物理层:负责把主机中的数据转化成电信号,再通过网络介质(双绞线、光纤、无线通道等)来传输。该层描述了通信设备的机械、电气、功能等特性。

2)请描述tcp/ip网络模型(协议栈)

答:应用层、传输层、网络层、接口层

3)tcp/ip协议族有哪些常用网络协议?

答:http(超文本传输协议):用于网页浏览器、web服务器

        dns(域名解析服务):域名转ip地址

        dhcp(主机动态配置协议):设备接入网络自动分配ip地址

        nfs(网络文件系统):用于主机间数据共享

        tftp(简单文件传输协议):局域网内文件传输

        ftp(文件传输协议):广域网文件传输

        arp(地址转换协议):通过MAC地址找ip地址

4)交换机和路由器工作在tcp/ip协议的哪一层?他们的主要区别是?

答:交换机工作在数据链路层,路由器工作在网络层。

        交换机用于组建局域网,路由器可以连接两个不同网络

        1、工作层次不同

        2、数据转发所依据得对象不同,交换机依据MAC地址,路由器依据ip地址

        3、交换机可以传递广播帧,路由器阻隔广播帧

        4、路由器提供防火墙,交换机不提供

5)ip地址的表现形式,组成?

答:ipv4点分十进制或大端无符号整数表示,网络号+主机号

        ipv6冒号16进制或大端无符号整数表示,网络前缀+接口标识

6)ubantu中配置静态ip地址的文件是哪个?

答:/etc/network/interfaces

7)查看本机有哪些网络连接的命令?

答:netstat -app

8)网络设备字节序和主机字节序是什么?

答:网络设备字节序指大端存储

        主机字节序指小端存储。

9)端口号的作用?端口号的长度?常用网络协议端口号有哪些?

答:标识一个网络进程。16bit

        talent 23        ftp 20/21        http 80        dns 53        tftp 69        ssh 22        https 443

        mqtt 1883        加密 8883

10)tcp和udp的区别是什么?

答:tcp面向连接(如打电话前要先拨号建立连接)提供可靠服务。可靠性:通过tcp连接传输的数据,无差错,不丢失,不重复,且按需到达

        udp是无连接的,即发送数据之前不需要建立连接,udp尽最大努力交付,即不保证可靠交付。

        tcp连接只能是一对一的(也可以连接多个,需要新链接才行)。udp支持一对一、一对多、多对一和多对多的交互通信

        tcp是面向字节流,把数据看成一连串无结构的字节流。udp是面向报文的一次交付一个完整的报文,报文不可分割,报文是udp处理数据的最小单位。

        tcp具有拥塞和流量控制。

11)三次握手、四次挥手是什么?

答:握手

        客户端发送请求

        服务端响应

        客户端收到响应后应答并分配资源,连接建立

挥手:

        客户端发送断连请求

        服务端收到请求后响应

        服务端发送断连信号

        客户端确认断连,等待几秒释放资源

12)tcp协议中还有哪些机制保证可靠传输?

答:应答机制、超时重传机制、拥塞控制、滑动窗口

13)说一下对wireshark的了解?

答:wireshark是一个网络抓包工具。用于网络协议分析或网络数据收发调试

        使用:

                1、sudo启动wireshark

                2、选择设备。ens33(网络数据与互联网交互)、loopback(本机自己测试)、any(所以与本机通信的数据)

                3、设置过滤条件。大多数只使用端口号、IP地址

        问题排除方法:可以使用wireshark设置端口,对双方收发数据进行捕获,根据收发情况,可以判断是收端还是发端的问题。

14)mtu是什么?ip协议头的ttl是什么?

答:mtu:最大传输单元,由硬件设备决定,例如以太网1500字节

        ttl是ip数据包头的一部分,是一个8位字段,用来限制数据包在网络中的存活时间,以防止数据包在网络中无休止的循环转发。

15)tcp、ip的协议头大小是?常用字段有哪些?

答:都是20字节。

        常用字段:ip中的ttl

                          tcp头中的tcp标志位(紧急指针、应答标志、push数据收发、reset、syn、fin),窗口大小等。

16)什么是io多路复用?与多线程的区别?

答:线程或进程同时检测若干文件描述符是否有执行IO操作的能力:

                使用并发处理的成本

                线程/进程创建成本

                cpu切换不同进程/线程成本

                多线程的资源竞争

        因此io多路复用可以实现用更少的资源完成更多的事。

17)服务器客户端模型有哪些?c/s和b/s模型的区别?

答:常见:c/s、b/s、p2p(点对点,既是客户又是服务)

        c/s、b/s区别:

                b/s通信使用固定的http协议。c/s通信协议不固定,可以是标准协议,也可以自定义

                b/s中client是通用的。c/s中客户端是专用的。

                b/s实现的功能相对简单,c/s可以实现更复杂的功能

                b/s中客户端就是浏览器,只需要开发服务端即可,成本低

                c/s中客户端服务端都需要开发,成本高、周期长

                b/s中,所有资源都是服务端发给客户端的,c/s中大部分资源都在客户端,服务端只需要跟客户端传输动态交互的数据

18)服务器并发模型有哪些?

答:多进程、线程模型,多路io模型

19)Linux中io模型有哪些?如何给文件设置非阻塞?

答:阻塞io、非阻塞io、信号驱动io、并发io、多路io。

        通过fcntl或open给文件设置O_NONBLOCK标志设置非阻塞。

20)select和epoll的区别是什么?应用层是如何使用的?

答:区别:1、epoll不限制监听的描述符个数(poll也是),select默认1024个,三者都受进程打开描述符总数的限制。

                  2、select/poll都是O(n)复杂度,需要轮询所有描述符。epoll是O(1)复杂度,监听性能不随着监听描述符的增加而增加,不再轮询描述符来探测事件,而是描述符主动上报事件。

                  3、select/poll每次调用都需要将描述符集合从用户空间拷贝到内核空间。epoll使用共享内存的方式,不在用户和内核之间反复传递监听的描述符信息。

                  4、select/poll返回后需要遍历整个集合检查哪些描述符就绪。epoll返回参数中就是触发事件的列表,不用再遍历输入事件表查询各个事件是否被触发。

        使用:FD_ZERO(fd_set *set):清空文件描述符集合,初始化为0       

                   FD_SET(int fd, fd_set *set):将文件描述符fd添加到set集合

                   FD_CLR(int fd, fd_set *set):从集合set中移除文件描述符fd     

                   FD_ISSET(int fd, fd_set *set):检查文件描述符fd是否在集合set中(是否就绪)

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);传统io多路复用。同步阻塞地监视一组文件描述符,直到有 fd 就绪(可读/可写/异常)或超时。跨平台实现

epoll_create     创建一个 epoll 实例,返回一个文件描述符(用于后续操作)。       

epoll_ctl       向 epoll 实例(epfd)添加/修改/删除(op)要监听的 fd(fd),并指定监听事件(event)。

epoll_wait    阻塞等待 epoll 实例中的 fd 就绪,返回就绪的事件列表(events)。

21)http协议用在哪?属于哪一层?默认端口是多少?

答:应用于网页服务器、应用层、80.        https是443

22)http如何交互?

答:建立连接、发送请求报文、回复响应报文、断开连接

23)http请求行的组成是什么?你知道哪些请求方式?状态码有哪些?

答:方法 URL 协议版本        例:GET /index.html HTTP/1.1

24)你的并发服务器的并发量是多少?你使用什么工具测试?你的并发服务器的响应速度?

答:20000以上。Apache Bench(ab)工具测试。0.2ms

37、数据库

1)你使用的是哪个数据库?为什么使用这个数据库?

答:sqlite3,因为这是一个轻量级数据库,适合在嵌入式设备使用。

2)关系型数据库和非关系型数据库了解吗?

非关系型(NOSQL):无表格结构,关系型(SQL):表格结构

3)你还知道哪些数据库?为什么使用sqlite3?

答:mysql、mssql,这两个都是桌面数据库(pc机或服务器安装的数据库)。嵌入式设备一般资源匮乏:主频、内存、硬盘等。

4)如何移植sqlite3到arm开发板?

答: 1、www.sqlite.org下载源码

        2、在本机解压:tar -xvf sqlite3.tar.bz2

        3、源码安装:./configure        make        make install

        4、把生成的库文件复制到板子的/lib目录下

5)动态库和静态库的区别是什么?你还移植过哪些库?

答:静态库:是在编译阶段将库的代码直接链接到可执行文件中。(链接是在编译时完成的,内容会复制到可执行文件,导致文件变大)

        动态库:在运行时被加载到内存,程序在运行时通过动态链接器链接这些库。动态库通常是以.so结尾,链接是运行时进行的,可执行文件只包含对库内容的引用。使用时由操作系统负责加载。可执行文件相对小。

6)如何查看库的基本信息的?

答:readelf -h ./libc.so

7)如何查看可执行文件依赖哪些系统库文件?

答:ldd ./a.out

8)如何查看你的系统是否安装sqlite3?

答:whereis sqlite3

9)sql语句中,如何增删改查?

答: select 列名 from 表名;

        insert into 表面 values();

        delete from 表名;

        updata 表名 set 列名 = 值;

10)sqlite3函数手册是如何查看的?

答:需要进入sqlite官网,找到documentation->programming interfaces->c/c++ api refernce

查找第三方函数说明。

11)你使用过哪些sqlite3相关api接口?

答:sqlite3_open        sqlite3_exec        sqlite3_close        sqlite3_errmsg

12)sqlite3_exec中的回调函数作用?

答:当执行数据库查询数据的时候,通过回调函数把查到的数据返回,每次只返回一条记录。这个回调函数会调用多次,次数由结果集的记录数决定。

13)你的数据库的表是怎么设计的?

答:分表设计。

14)你设计的数据库是如何维护的?

答:定期覆盖。(开发板上的flash空间有限)

15)你知道数据库的主键、外键吗?

答:主键是给字段设置的一个属性。具有唯一性,可以设置自动增长,在搜索的时候可以加快查询速率。

        外键是字段属性,用于多表间的数据关联。

16)你设计的数据库最多存储多少数据量?

答:50Mb

裸机开发

1)你使用的SOC采用的什么架构内核?什么型号?指令集采用的是哪个版本?

答:ARM架构,Cortex A7,ARMv7-A

  • ARMv7-A:应用处理器(Android/Linux)
  • ARMv7-R:实时处理器(汽车ECU)
  • ARMv7-M:微控制器(IoT设备)

  • ARMvX:指令集代际(如v7/v8/v9)
  • Cortex-X:微架构代号(如A7/A15, X1/X2)
文件io、目录io、标准io

答: 文件io:系统调用:open、read、write、close

        标准io:c库函数:fopen、fread

        目录io:特殊系统调用:opendir、mkdir、rmdir、closedir

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐