b站视频链接:【史上最强最细腻的linux嵌入式C语言学习教程【李慧芹老师】】https://www.bilibili.com/video/BV18p4y167Md?p=69&vd_source=a36d0c31e1b9411aed0d55d6d2e02437

目录

嵌入式C语言

C语言学习笔记

一、gcc和vim的使用

1、c源文件编译顺序

2、编程注意的问题

二、数据类型、运算符、表达式

1、数据类型

2、常量与变量

3、运算符和表达式

三、输入、输出操作

1、printf 用法

2、scanf用法

3、字符串输入输出

4、练习题

四、流程控制

1、if...else...

2、switch-case

3、循环

4、练习题

五、数组

1、一维数组

2、二维数组

3、字符数组

4、多维数组

5、练习题

六、指针

1、指针相关定义

2、直接或间接访问

3、空指针与野指针、空类型

4、指针与一维数组

5、指针与二维数组

(1)数组指针

(2)指针数组

6、指针与字符数组

7、const与指针

(1)常量指针和指针常量的区别

8、多级指针

七、函数

1、函数的定义

2、函数的传参

(1)值传递

(2)地址传递*

3、函数的调用

(1)嵌套调用

(2)递归(常考)

4、函数和数组

(1)函数和一维数组

(2)函数与二维数组

(3)字符数组

八、构造类型

1、结构体

(1)类型描述

(2)定义变量、初始化

(3)占用内存空间大小

(4)函数传参

(5)练习(微型学生管理系统)

2、共用体

(1)类型描述

(2)定义变量

(3)将32位数的高16位与低16位相加

(4)位域

3、枚举类型

九、动态内存管理

1、malloc

(1)定义

(2)易错点***

2、free

3、重构学生管理系统

4、typedefine


嵌入式C语言

C语言学习笔记

一、gcc和vim的使用

1、c源文件编译顺序

c源文件->预处理->编译->汇编->链接->可执行

(1)直接用gcc hello.c

gcc自动进行编译汇编链接操作,在输入./a.out即可运行文件

(2)用make hello(即解释hello.c 文件,如果输入make hello.c,则就解释的是hello.c.c文件)

再输入./hello即可运行文件

2、编程注意的问题

(1)头文件要正确包含

malloc函数是包含在#include <stdlib.h>中(输入man malloc可查看malloc()函数源码):

所以使用任何一个函数时,要注意其在哪个函数库里,否则c语言会识别不出malloc函数,将其当成一个整形来看待,则会发生数据类型错误

(2)以函数为单位进行程序编写

(3)声明部分+实现部分

(4)return 0 是给其父进程看的,main函数的父进程就是shell

例如printf()也是有返回值的

Hello world10个字符加回车共11个字符,作为main函数的返回值返回给shell接收

(5)多用空格空行以及注释

(6)算法:解决问题的方法(流程图、NS图、有限状态机FSM)

二、数据类型、运算符、表达式

1、数据类型

2、常量与变量

(1)常量

*define的使用

在编译处理时并不进行检错,直接用后面数字替换define。

例如#define ADD 2+3

#define ADD(2+3)这样才可以计算正确

define更深层次用法

#define处理在程序的预处理阶段,占编译时间,不占运行时间,一改全改。缺点:不检查语法,只是单纯的宏体与宏名之间的替换!!!

(2)变量

如果没有指定存储类型,默认为auto:自动分配空间,自动回收空间(不进行初始化的话默认为随机值,但现在GCC一般会优化为0

register:(建议型)寄存器类型。(如果某个变量需要反复使用,建议将该变量放在寄存器中,但GCC不一定会听取建议)只能定义局部变量,大小有限制, 只能定义同机器字长大小的数据类型,如32位机不能定义double类型(64位),寄存器无地址,所以寄存器类型的变量无法打印出地址查看或使用。

static:静态型。自动初始化为0或者空值,并且其变量的值有继承性,常用于修饰变量或函数static修饰变量时表示该变量只能在该文件中使用,其他文件无法访问,防止变量在多个文件中重复定义发生冲突。修饰函数时表示该函数只能在当前文件中使用。(防止当前函数对外扩展)

extern:说明型。意味着不能改变被说明的变量的值或类型。(使用extern修饰的关键字不管是类型还是值,都无法被修改

使用方式:假设A.c有一个全局int型变量a, 在B.c中使用extern int a可以引用A.c的a变量。注意extern修饰时不能进行修改。

在main.c中定义全局变量i,后在proj.c中用extern声明i,表示i变量取值从其他函数中寻找,即为main.c中的i=10。

注意:用extern声明后的变量,不需要再进行赋值,并且与相同变量要保持类型一致

3、运算符和表达式

%取余运算必须是整型

5.0 % 2 运算错误!

前自增(++i)和后自增(i++)的区别:

逻辑运算符

&& ||的短路的特性:若为逻辑或,则m为0会继续判断n,m为1则不会继续判断n

位运算符(重要!)

按位与(&)、按位或(|)、异或(^)(相同为0,不同为1) B表示二进制

原则:若为0,则0可通过或运算使操作数保持不变,若为1,则1可通过与运算使操作数保持不变

三、输入、输出操作

1、printf 用法

(1) (float浮点数,精度为6位,需要进行补齐或删除)

(2)printf中修饰字符讲解

修饰符m

如果输出%6d,则i= 123;

如果输出%2d,则小于i赋值长度,原样输出i= 123;

修饰符.n

输出则为hello,但并不代表str变为hello,str仍为helloworld

修饰符-

在平时的编程中,应当注意整型数太大时而导致的溢出问题,这时候我们应该有意识的在数字后面加上LL,如下面的一个面试题,问如何表示一年有多少秒,可以使用一个宏定义,如下:

#define SEC_YEAR (60LL60LL24LL*365LL)

(3)关于\n的一个知识点

\n除了用来表示输出换行以外,还有刷新缓冲区的作用

a、本应输出死循环之前的printf()内容,但由于没有使用\n来刷新缓冲区,所以导致没有任何输出。

没有加\n,before这条语句就放到了输出缓冲区当中,此时只有三种情况会刷新缓冲区:

1、等程序结束,自动刷新缓冲区

2、遇到强制刷新的函数,如flash()

3、等缓冲区满了,一次性刷新

而上面的printf什么情况也不满足,所以before在缓冲区中刷新不出来

b、在第一个printf函数中使用了\n,所以在死循环前先将要输出的内容打印到了屏幕上。

c、中间不是while(1)而是sleep的情况

2、scanf用法

在使用scanf函数进行输入时,其输入的内容格式要与scanf中的格式保持完全一致,否则输入会出错,假设:

scanf(“%d,%f”, &i, &f);

那么在输入的时候,输完了第一个数i之后,要再输入一个”,”再才能输入第二个数,所以建议不要再格式化的输入之间添加任何其他字符,比如可以像这样:

scanf(“%d%f”, &i, &f);

这样的话在输入完第一个数之后,可以使用 空格符,tab键,或者回车来间隔i和f。

注意,在对字符串进行输入时,不需要加“&“符号,因为字符串名称就是该字符串的首地址。

同时,使用%s获得输入时,不能有空格,即若输入了 “hello world”到字符串str中,则str只保留了hello,而没有后面的空格和world。(%s比较危险,容易内存越界,不建议使用)

scanf()函数放在循环中很危险,如果确有需要,记得做判断(scanf函数有返回值,即scanf中有几个值得到了正确的输入,就返回几,可以依据此来做if判断,在适当的时机退出循环。)

抑制符*的使用

在连续使用scanf时,输入i的值后,需要点击回车/空格/tab键才可以继续输入c的值,若中间不使用抑制符,则回车/空格/tab键将会被判定为c的值,此时需要使用“*”去吃掉回车/空格/tab操作,才能继续输入c的值。

或者使用getchar()去吃掉回车/空格/tab操作。

用scanf("%s")没有办法去输入带有空格的一串字符,原因与上放类似。

3、字符串输入输出

可以使用getline去解决越界问题,getline实现动态存储

4、练习题

(1)一个水分子的质量大约为3.0e-23g,一夸脱水大约950克,编写一程序,要求从终端输入水的夸脱数,然后显示这么多夸脱水中包含有大概多少个水分子。

如果将num定义为int型,那么需要使用显示类型转换:int sum = (int) num*kq/waterpiece;

浮点型与整形做除法,会自动隐式转换为浮点型

(2)从终端输入三角形三边长,求面积

s = 1/2 * (a+b+c);

area = sqrt(s(s-a)(s-b)*(s-c));

(注:当源文件中包含math.h时,在使用gcc进行编译时,要在编译命令后面加上-lm参数)

因为s=1/2*(a+b+c)出错!1/2为整形与(a+b+c)相乘确实直接隐式转换为float型。但是1/2是整形除法,结果为0!

可以这样修改,注意细节!

四、流程控制

1、if...else...

注意:else只与离他最近的if想匹配,除非用{}隔开

2、switch-case

case后面只能使用常量表达式,即执行过程中不会发生变化的表达式,不能用>/</=这种关系型表达式

尤其要记住每个case后面很有可能都要有一个break跳出switch语句,如果不加就会顺着下一个case继续顺序执行语句,直到遇到break或者switch语句末尾才会跳出switch语句(当然也有特殊情况,牢记这个关键字,根据自己的需求判断是否需要使用break关键字),最后记得要有一个default控制,表示默认情况,即所有的case都不执行的时候,就去执行default修饰的语句。

同时跟上面的if的注意事项差不多,不要相信自己的判断,假设以为只有两种情况,也要在default中做出错相关的处理,如下:

3、循环

(1)while和do...while

(2)for循环

(3)if...goto

(慎用:goto实现的是无条件的跳转,且不能跨函数调用,不能跨函数调用的原因是goto不会保存现场,将相关变量等压栈,而一般的函数调用时,会将现场保存在栈中,待函数调用完之后,回到原处,再出栈恢复现场。所以goto不能跨函数调用。)

(4)典型的死循环

while(1);

for(; ;);

杀掉死循环:Ctrl+C(终止当前进程)

(5)辅助控制

break: 跳出当前循环

continue: 跳出本次循环(即跳过这一次的循环内容,继续执行下一轮循环)

4、练习题

五、数组

1、一维数组

(1)定义

数组越界,系统不会进行检查,因为数组是通过指针偏移来构成的。例如:

定义arr的长度的3,此时我再初始化arr[3]=10,不会报错。对于arr[3]=10操作的解释:将10赋值给从arr数组第一个地址偏移3个单位的地址上。

(2)练习题

2、二维数组

(1)定义(二维数组在定义时,只有行号可以省略,列号不可以省略)

通过这张图,我们可以把a2理解成一个一维数组,然后a中的每个元素又都是一个一个数组,所以a表示的是a[0], a+1表示的是a[1]。如果这时我们定义一个int类型的执政去指向a,即 int *p; p = a; 可以以p的方式来访问a中的第一行(虽然会有类型不匹配的warning)即 p[0]表示的a[0] [0]。如果令 p = a+1;这时p将指向a的第二行,此时p[0]表示a[1] [0],最后,如果想要消除类型不匹配的warning,可以将p=a改写成p=a[0] p = a+1 改写成p = a[1];(具体原因同学们可以自己调试查看提示信息)

(2)练习题

3、字符数组

(1)定义

特殊:区别于一维二维数组,字符数组在存储时需要在末尾多占用一个地址,添加尾零。

例如 str[N]={'a','b','c'} 对应存储--------> 'a','b','c','/0'

输入输出gets()很危险,不要用。因为gets()是将数据直接读取到缓冲区中,并没有直接存储到数组所造物理地址。puts()就相当于从缓冲区中取出数据,并在末尾加"/0"。这就导致gets()并不会去检查越界情况,且对数据类型也不会检测

(2)字符数组中常用的函数

strlen(str1):以尾零“/0"为单位计算数组的长度。

strcpy(str1,str2)/strcpy(str1,"abcde"):将后面的字符串内容复制到前面的字符串中。弥补了不能使用str = "abcde"的特性(str是字符串常量,不是变量)。

strncpy(str1,str2,n):将str2中前n个字节复制到str1中,防止越界问题。(一般n设置为str1的长度)。注意:实际从str2取出的是n-1个字符,还要包含一个尾零。

strcat(str1,str2):将str2追加到str1后面。

strncat(str1,str2,n):将str2最多n个字节追加到str1后面。

strcmp(str1,str2):str1的ASI码-str2的ASI码值,返回ASI码之间的差值

strncmp(str1,str2,n):比较前n个ASI码的差值。

4、多维数组

如上图,分到第三级即为在内存中连续存储的数组下标。

5、练习题

(1)字符数组中的单词计数问题

六、指针

1、指针相关定义

(1)变量与地址

指针等价于地址,变量名就是抽象出来某块空间的别名。

(2)指针与指针变量

用于存放指针的地址的变量叫指针变量

(3)指针运算

& * 关系运算 ++ --

2、直接或间接访问

(1)*和&的含义

*的含义: *p表示的是p对应的地址中存储的值,即为i。

&的含义: &p表示存储p的地址,即为ox3000。

*与&二者类似互逆操作

(2)解释int *p=&i;

注意:c语言定义变量格式如下: TYPE NAME = VALUE;

int *p=&i 中TYPE=int *,NAME=p, VALUE=&i。

int *表示的TYPE含义是int类型的指针。

(3)指针的大小

不管指针是几级的指针或者何种类型定义,在某个平台下,指针定义的大小始终一致。

例如在64位OS下,指针大小始终为8个字节。

(定义指针类型需要和指向地址中所在的值类型相同,如i为int型,那么*p=&i也必须为int型,否则在做指针运算时很容易发生错误)

3、空指针与野指针、空类型

(1)指针一旦确定就必须有指向,否则就变成了野指针,为了避免这种情况,需要把没有明确指向的指针定义为空指针:int *p = NULL;

注意:像下图这样的野指针一定不要用,指针一旦定义出来就要有明确的变量指向。

(2)空类型指针:void *q=NULL;

在不知道需要什么数据类型时,可以使用空类型指针,可以表示任意数据类型

4、指针与一维数组

从上例中,a与p似乎是等价关系,唯一不同点在于a是数组常量,p是指针变量。

(1)区分p++和p+1

p++ 即 p=p+1 --> p指针变动了,指向下一个元素的地址。

p+1 --> p指针没有变动,仍指向这个元素,但不获得它的地址,而是获得它下一个元素的地址。

*a= *(a+0)即为a[0];

在上图中p指针是一直向下移动的,所以在scanf输入后,p指针指向a[2]下一个位置,此时在进行printf输出,则输出的是a[2]后面三个地址的值和对应元素。

printf("%p->%d\n",p, * p++)存在运算级的问题,printf在装载函数时,先存 *p++这个参数,导致p向前移动了一位(同级别运算符,从左往右一次运算,则先运算了p++再取*,导致p又多移动了一个单元,故将p++移到for循环中)

例如:

上图中输出y的值为5,a[0]为6

(2)指针可以直接引用数组

创建了一个匿名临时对象,直接用指针引用数组,数组并没有名字。

5、指针与二维数组

a表示行指针,a+1表示第二行地址指针

(1)数组指针

int (* p)[3]对应type为数组int [3],对应name为*p;

此时p+1不再是移动一个地址单元,而是一个int [3]大小的地址单元

(2)指针数组

归根结底是一个数组

int * arr[3]; -->TYPE NAME; -->int *[3] arr;

指针数组的选择排序

6、指针与字符数组

(1)指针长度

7、const与指针

const:将变量给常量化。

例如:

用const将pi常量化,此时在给pi直接赋值就会发生错误。而通过定义指针变量p指向pi的地址,从而间接给pi赋值,不会出错,但非常危险,不建议使用。

(1)常量指针和指针常量的区别

常量指针:指针的指向可以发生变化,但是指针指向当前地址处的值不可以发生变化。

指针常量:指针的指向永远不能变化,但是目标变量的值可以发生变化。

(谁在前谁就不能变!)

区分:

常量指针:const int *p / int const *p

指针常量:int *const p

const在前就是常量指针,*在前就是指针常量

常量指针

指针常量

去看一些函数比如memcpy

都会将*src给const修饰,通过定义常量指针意味着传过来的参数stc是不可改变的,从而复制给dest变量。在平时定义函数时,也可以借鉴这些定义原则。

8、多级指针

还是这个图

七、函数

1、函数的定义

(1)函数返回

一个进程返回状态是给其父进程看的。main函数的父进程就是shell

main函数第一个参数为函数名。

注意shell有自动解析通配符的功能

char *agrv[]:字符指针数组

2、函数的传参

(1)值传递

(2)地址传递*

值传递,形参会对实参拷贝一份进行操作,所以实参不会交换。传地址可以实现交换,C++引用就是传地址

3、函数的调用

(1)嵌套调用

(2)递归(常考)

递归:一个函数直接或间接的调用自己

递归解决阶乘问题

//用递归解决阶乘问题
​
#include <stdio.h>
#include <stdlib.h>
​
int func(int i) {
    if (i > 1) {
        return  func(i - 1) * i;
    }
    else
        return 1;
}
​
int  main() {
​
    int i;
    int ret;
    printf("请输入您要计算的阶乘数为:");
    scanf_s("%d", &i);
​
    ret = func(i);
    printf("输出结果为%d\n", ret);
}

递归解决斐波那契数列问题

//递归解决斐波那契数列问题
int fib(int n) {
    if (n < 1)
        return -1;
    if (n == 1)
        return 1;
    if (n == 2)
        return 1;
    else
        return fib(n - 1) + fib(n - 2);
}
​
int main() {
    int n;
    int res;
​
    scanf_s("%d", &n);
​
    res = fib(n);
    printf("fib[%d] = %d\n", n, res);
​
}

4、函数和数组
(1)函数和一维数组

故要传入数组长度的参数。

void printf_arr(int *p,int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", *(p + i));
    }
}
​
int main() {
    int a[] = { 1,3,6,7,8 };
    /* 定义打印数组的函数
    for (int i = 0; i < sizeof(a) / sizeof(*a); i++) {
        printf("%d ", a[i]);
    }*/
    printf_arr(a,sizeof(a)/sizeof(*a));
}

注意,如果参数指针改为数组int p[],在形参的角度来说,它的本质依然是一个指针,一个[]对应一个*

传参类型对应

函数实现逆序功能

//输出
void printf_arr(int *p,int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", *(p + i));
    }
}
​
//交换函数
void rechange (int* a, int* b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
​
//逆序函数
void func(int *p, int n) {
    int tmp;
    for (int i = 0; i < n / 2; i++) {
        /*交换函数
        tmp = *(p + i);
        *(p + i) = *(p + n - i-1);
        *(p + n - i - 1) = tmp;
        */
        rechange((p + i), (p + n - i - 1));
    }
}
​
int main() {
    int a[] = { 1,3,6,7,8 };
    /* 定义打印数组的函数
    for (int i = 0; i < sizeof(a) / sizeof(*a); i++) {
        printf("%d ", a[i]);
    }*/
    func(a, sizeof(a) / sizeof(*a));
    printf_arr(a, sizeof(a) / sizeof(*a));
}

(2)函数与二维数组

二维数组打印输出

法一:当成一维数组

法二:当成二维数组

注意:sizeof(a)=48,sizeof(p)=8

法三

形参就是一个指针大小。

(3)字符数组

将一个字符数组复制给另一个字符数组,即定义strcpy函数。(面试会问)

将一个字符数组的n个字符复制给另一个字符数组,即定义strncpy函数。

正确输出:

整个代码:

char *mystrcpy(char *dest, const char *sor) {
    char *ret = dest;
    if (dest != NULL && sor != NULL) {
        while ((*dest++ = *sor++) != '\0'); //!!!考虑优先级问题!先进行复制运算,在与尾零进行对比
    }
    return ret;
}
​
char *mystrncpy(char *dest, const char *src,int n) {
    char *ret = dest;
    int min = strlen(src) - n <= 0 ? strlen(src) : n;
    while (min > 0) {
        *dest++ = *src++;
        min--;
    }
    *dest = '\0';//!!!!注意此时dest指针位于第n个位置,但并不是尾零,所以如果不将dest置尾零,则后续还会输出乱码!
    return ret;
}
int main() {
    char str1[] = "helloworld";
    char str2[128];
    char str3[128];
    mystrcpy(str2, str1);
    printf("str2=");
    puts(str2);
​
    mystrncpy(str3, str1, 2);
    printf("str3=");
    puts(str3);
​
    exit(0);
}

八、构造类型

1、结构体

(1)类型描述

简单定义

嵌套定义

(2)定义变量、初始化

#include <stdio.h>
#include <stdlib.h>
​
struct birthday_st {
    int year;
    int month;
    int day;
};
​
struct stu {
    int id;
    char name[128];
    struct birthday_st birth;
    int math;
    int chinese;
};
​
int main() {
    struct stu stu_1 = { 18,"jack",{2002,05,06},91,88 };
    stu_1.birth.day = 2024;
    printf("%d\n%d\n", stu_1.birth.day, stu_1.math);
    exit(0);
}

部分初始化

用指针来引用成员变量

数组存放结构体

int main() {
​
    
    struct stu arr[2] = { { 10011,"alan",{2002,11,1},38,29 }, {10012,"snick",{2021,11,1},24,99} };
    struct stu *p = &arr[0];
​
    for (int i = 0; i < 2; i++, p++) { //p++ -> p=p+1;这里的“1”为一个结构体单位
        printf("%d %s %d-%d-%d %d %d\n", p->id, p->name, p->birth.year, p->birth.month, p->birth.day, p->math, p->chinese);
    }
    exit(0);
}

(3)占用内存空间大小

结构体默认对齐:sizeof(struct)=12bite

(4)函数传参

使用结构体传参的参数占用空间大小非常大!

故应采用指针传参,指针固定8bite大小

输出为8

(5)练习(微型学生管理系统)

微型学生管理系统:

struct student_st {
    int id;
    char name[NAMESIZE];
    int math;
    int chinese;
};
​
void stu_set(struct student_st *p) //通过指针传参,减少开销!
{
    p->id = 10011;
    //注意字符常量不能通过赋值定义!不能通过p->name="Alan"错误!
    strncpy_s(p->name, "Alan", NAMESIZE);
    p->math = 90;
    p->chinese = 88;
};
​
void stu_show(struct student_st* p) {
    printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
}
​
int main() {
​
    struct student_st stu;
    
    stu_set(&stu);
    stu_show(&stu);
​
    exit(0);
}

改善:通过取输入学生信息

struct student_st {
	int id;
	char name[NAMESIZE];
	int math;
	int chinese;
};

void stu_set(struct student_st *p,struct student_st *q) //通过指针传参,减少开销!
{
	/*
	p->id = q->id;
	//注意字符常量不能通过赋值定义!不能通过p->name="Alan"错误!
	strcpy_s(p->name, q->name);
	p->math = q->math;
	p->chinese = q->chinese;
	*/
	*p = *q;
};

void stu_show(struct student_st* p) {
	printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
}

int main() {

	struct student_st stu,tmp;
	printf("please enter for the stu[id name math chinese]:");

	//复习scanf用法!!!
	scanf_s("%d", &tmp.id);
	scanf_s("%s", tmp.name,NAMESIZE);
	scanf_s("%d%d", &tmp.math, &tmp.chinese);
	stu_set(&stu,&tmp);
	stu_show(&stu);

	exit(0);
}

完整微型学生管理系统菜单:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NAMESIZE 32

struct student_st {
	int id;
	char name[NAMESIZE];
	int math;
	int chinese;
};

void stu_set(struct student_st *p) //通过指针传参,减少开销!
{
	/*
	p->id = q->id;
	//注意字符常量不能通过赋值定义!不能通过p->name="Alan"错误!
	strcpy_s(p->name, q->name);
	p->math = q->math;
	p->chinese = q->chinese;
	*/
	printf("please enter for the stu[id name math chinese]:");
	//复习scanf用法!!!
	scanf_s("%d", &p->id);
	scanf_s("%s", p->name, NAMESIZE);
	scanf_s("%d%d", &p->math, &p->chinese);

};

void stu_show(struct student_st* p) {
	printf("%d %s %d %d\n", p->id, p->name, p->math, p->chinese);
}

void stu_changename(struct student_st* p, char *q) {
	printf("please enter for newname:");
	scanf_s("%s", q, NAMESIZE);
	strcpy_s(p->name, q);
}

void menu(void) {
	printf("\n1 set\n2 changename\n3 show\n\n");
	printf("please enter the num:(q for quit)");
}

int main() {

	struct student_st stu;
	char newname[NAMESIZE];
	int choice;
	int ret;//用于退出循环

	do {//必定循环一次
		menu();
		ret = scanf_s("%d", &choice);

		if (ret != 1)
			break;//输入的choice不为整型就退出while循环

		switch (choice)
		{
		case 1:
			stu_set(&stu);
			break;
		case 2:
			stu_changename(&stu, newname);
			break;
		case  3:
			stu_show(&stu);
			break;
		default:
			exit(1);//直接退出while循环
		}

	} while (1);
	
	exit(0);
}

2、共用体

多个变量共用同一块空间。

(1)类型描述

(2)定义变量

(3)将32位数的高16位与低16位相加

法一:通过位运算

法二:通过共用体的概念

(4)位域

w.x.a输出结果为-1

硬件中的得扣掉符号位再取反加一

补码符号位与原码的符号位相同

3、枚举类型

输出结果为5

常见用法:

将enum枚举当做多个define宏来使用,在预处理时并不会被数字取代,从而让代码更加清晰,方便debug。

若用define宏定义,那么switch语句中就会case 1,case 2,case 3

注意:多个无头枚举里面有相同的成员会报错

九、动态内存管理

原则:谁申请谁释放

1、malloc
(1)定义

即p指针指向申请的int型大小的空间

使用动态内存分配的方式做的动态数组

(2)易错点***

两种修改方法:

(1)通过地址传参(回顾前面所学!)

(2)修改func函数返回值,让p指针接收

2、free

此时p指针就变成了野指针!

所以警惕:在free(p)后就立马将p=null置空!

free类似于去租房子,房子就是那套空间,p就是你在使用。free之后,即你退租了。这时候在进去就不合适了。

free代表着通过p对原来那块空间再也没有引用的权限了。p指针依然存在,那块空间也依然存在,p可能仍指向那块空间,也可能指向别的地方。

3、重构学生管理系统

4、typedefine

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐