🍓🐰🍭🧸🐾
声明定义💒
#背
编辑:编辑代码 edit
预处理:#开头的命令叫预处理命令pre-process
编译:编译成计算机识别的指令(0101)compile
汇编:把所有可识别的指令生成目标文件assemble
链接:链接到一起,生成可执行文件link
运行:加载都内存中去执行run
^90d2db
1.头文件
- 先声明后使用
c语言先声明 再使用头文件里所有内容替换 #include< > ;是预处理命令,主要做编译之前的代码替换;头文件中有printf原型的声明
- 调用函数要先声明
因为代码中用标准输入/输出的函数,所以要声明,包含这个头文件stdio.h就声明了,头文件含有所有关于输入输出的标准函数 function
- 头文件包含函数原型的声明
声明
定义 主函数
函数头 = 返回值类型int + 函数名main + (形式参数列表)
调用
自己定义的头文件用双引号
2.汇编
![[汇编过程.png]]
3.链接
把所有目标文件连接到一起,生成目标文件
4.运行
加载到内存中去执行
关于函数
1.函数声明
- 编译器会根据函数原型声明去判断函数调用语法是否正确
函数原型声明 #🤌
在stdio.h文件中
int printf (const char *__format, ...)
\n 代表换行符,任意加,占一个字符
int
函数返回值数据类型 prinf
函数名称 const char *__format
形参列表
#背
[!#🤌]
1.函数运算完之后返回的数据类型,调用者可以根据函数返回值类型定义相应类型的变量来接受函数返回值2.函数名称就是为了调用
3.形参列表:为了调用时分配空间接受实参的值
//调用时,我们传递的实际参数,就叫实参,而声明时叫形参
//调用时,我们是按照数目一样,类型一样,顺序一样的原则,实参向形参赋值,且是单向的(实参传递给形参,形参的改变不会影响到实参)
2.函数定义
定义函数头和函数体
任何c语言或其他程序,有且只有一个main
函数,它是程序执行的入口 头
关于字符串
双引号引起来的叫常量字符,字符串有两类
普通字符
对于printf
来说,原样输出
特殊字符
- 换行符
\n
输出多少行只于换行符\n
有关与调用函数无关 - 还有
%
开头表示格式化输出后面的值(变量) -
表示左对齐+
表示右对齐%%
表示输出一个%
printf("\n今天是%-6d号\n",17);
基本数据类型 int float char
int:4 bytes
float:4
char:1
关于变量定义(4条)
#背
1、比照类型分配对应大小的大小空间;
2、空间取名,即变量名
(变量名代表里面的值,代码都代表数值,所有的标识符都代表数值);
3、赋初始值为0;如果不赋值,则为乱值
4、变量名有首地址,记为&age
%d
表示输出有符号的%x
输出十六进制
以printf和scanf函数为例子 了解函数调用 #🤌
1.输入数字时产生的分割符,应该与格式控制中的分隔符一致
- 当格式控制中没有普通字符时,输入数据时,可以用
tab
键 空格键 回车键做分割符 fgets
为了安全地读取包含空格的字符串,推荐使用fgets
函数。fgets
允许你指定最大读取字符数,避免缓冲区溢出。
int n = scanf("%d%d",&a&b);//我们将函数调用赋值给变量,实际上是函数运算完成后,返回值赋给变量
编译器根据函数原型的声明去测试函数调用是否正确
返回值意思:(函数返回值时程序计算的结果)
scanf返回值:正确读入几个数
printf返回值:正确的输出了几个字符
比大小
- 法1
int main(){
int a = 0;
int b = 0;
int k = scanf("%d%d",&a,&b);
if(k != 2){ //检查是否成功读入两个整数
printf("输入错误,%d",k);
return 1; //返回错误码1
}
if(a<b){
a = b;
}
printf("max = %d\n",a);
return 0;
}
if(k != 2) 表示判断是否读入两个数
如果是其他的 (k!=1)表示判断是否成功输入一个数
- 法2
#include<stdio.h>
int main() {
int sum1;
int sum2;
scanf("a=%d,b=5%d", &sum1, &sum2);
if (sum1 > sum2) {
printf("%d\n", sum1);
} else if (sum2 < sum1) {
printf("%d\n", sum2);
}
return 0;
}
程序结构:先定义变量 赋初始值 运算 返回结果
作业
编程题:输入三个数,求最大值,最小值和三个值的和
定义变量要赋初值
(1)
#include<stdio.h>
int main(){
int a,b,c;
int max,min,sum;
printf("请输入三个数:\n");
int k =scanf("%d,%d,%d",&a,&b,&c);
if(k!=3){
printf("请重新输入三个数,%d",k);
return 1;
}
max =a;
if(b>max){
max=b; //max一定是a和b中的最大值
} if(c>max){
max=c;
} //如果max>c,就不运行这一步
min = a;
if(b<min){
min =b;
} if(c>min){
min =c;
}
sum = a + b + c;
printf("max=%d\n",max);
printf("min=%d\n",min);
printf("sum=%d\n",sum);
return 0;
}
(2)老师版本
/*
写任何程序,都有固定的套路:
1,先定义变量 2,赋初始值 3 运算 4 返回结果
* */
#include <stdio.h>
int main(){
int a = 0;
int b = 0;
int c = 0;
int max = 0;
int min = 0;
int sum = 0;
int nums = scanf("%d%d%d",&a,&b,&c);
//调用时,我们传递的实际参数,就叫实参。而声明时的参数,叫形参。
//调用时,我们是按数目一样,类型一样,顺序一样的的原则,实际参数向形参赋值,是单向的(实参传递给形参,形参的改变不会影响到实参。
//函数包括两部分:函数头和函数体,函数头包括三部分:函数返回值类型,函数名,形参列表
//(1)函数返回值类型就是函数运算完成后返回的数值的类型,调用者就可以根据函数返回值类型定义相应类型的变量来接收函数返回值,进而进行下一步的处理。
//(2)函数名就是为了调用
//(3)形参就是调用时分配空间,接收实参的值的。
if(nums != 3){
printf("输入错误");
return 1;
}
max = a;
if(b > max){
max = b;
}
//到这一步,max一定是a和b中的最大值。
if(max < c){
max = c;
}
//如果max(a,b中的最大值)大于等于c,不管了,截止到这一段代码,max已经是三个数的最大值。
min = a;
if(b < min){
min = b;
}
//到这一步,min一定是a和b中的最小值。
if(c < min){
min = c;
}
sum = a + b + c; //a,b,c前面没有被重新赋值,读取后的值不会被改变,放心大胆的用。
printf("max=%d,min=%d,sum=%d\n",max,min,sum);
return 0;
}
ANSI C:american national standart institate
常量
在程序执行期间保持不变的量
整型常量
有三种形式:
十进制整数
八进制整数:由数字0
开头
例如:014 011![[八进制.png]]
十六进制整数:有前缀0x
或0X
开头
十进制,八进制,十六进制表示的数,都是数字的计数方式不同,都是二进制的快速计法
16进制转二进制:1分4
0x32 -------- 0011 0010 -----------32+16+2=50
二进制转十六进制:四位转
8进制转二进制:1分3
062 ----------110 010 ------------32+16+2=50
二进制转八进制:三位转
2进制 8进制 16进制转10进制
110010 --------32+16+2 = 50
062 ----------68+2 = 50
0X32 ----------163+2 = 50
十进制转二进制、八进制、十六进制:短除法
各进制转换表格
#背
from\to | 2进制 | 8进制 | 10进制 | 16进制 |
---|---|---|---|---|
2进制 | / | 3位变成1位 | 各位求和 | 4位变1位 |
8进制 | 1位分开成3位 | / | 各位求和 | 8-2-16 |
10进制 | 短除法 | 短除法 | / | 短除法 |
16进制 | 1位分开成4位 | 16-2-8 | 各位求和 | / |
各位求和是按照对应的进制,进行各个位求和 | ||||
八进制和十六进制中间有二进制做桥梁 | ||||
#背 |
常见2的次方计算
2^5 = 32 2^6 = 64 2^7 = 128
2^8 = 256 2^10 = 1024
2^15 = 32768
2^16 = 65536
int-整型常量
基本类型 | 符号 | 字节数 | 数值范围 | 后缀 |
---|---|---|---|---|
基本整型 | int | 4 | -21亿-21亿 | |
短整型 | short int | 2 | -32768~32767 | |
长整型 | long int | 4 | -21亿~21亿 | L/I |
无符号基本整型 | unsigned int | 4 | 0~42亿 | U/u |
无符号短整型 | unsigned short int | 2 | 0~65535 | |
无符号长整型 | unsigned long int | 4 | 0~42亿 | UL/ul |
加后缀表示某种数据类型
如何理解有无符号?
从数据类型角度:
- 有符号无符号区别
无符号类型所有的二进制位为数值位-无负数(2^16-1)
有符号类型第一位为符号位,其余位数值位-1为负(2^15-1)
从有无符号输出角度
- 各个符号代表的输出意思
#背
%u
unsigned-表示按无符号输出的意思
%d
decimal-表示按有符号十进制输出的意思
%o
octal -按无符号八进制输出的意思
%x
hexadecimal-按无符号十六进制输出的意思。
%#o
octal-按无符号八进制输出的意思。
加个#号,表示输出前缀,八进制前缀为0
%#x
hexadecimal-按无符号十六进制输出的意思。
加个#号,表示输出前缀,16进制前缀为0x
,大写的X格式符,输出的十六进制数也是大写的
无论读入还是输出,读入输出格式符,要和变量类型匹配
补码
正数和负数在内存中,按补码存放 但正数的补码就是原码 负数的原码等于 补码 = 原码–取反–+1
1开头全是补码
-
以-10为例子:
原码:1000 0000 0000 1010
取反:1111 1111 1111 0101(取反,符号位不变)
+1: 1111 1111 1111 0110 -
想知道1000 0000 0000 0000是什么?
先算1000 0000 0000 0001再已知补码推原码
原码 = 补码 —-1
—取反
-1: 1000 0000 0000 0000
取反:1111 1111 1111 1111(原码)
计算:-2^15-1=-32767
最后计算:-32767-1=-32768
所以有符号短整型的最小值为-32768
输入小数,编译器默认为是双精度数
实型常量
float-浮点数
根据 IEEE 754 标准,32-bit 长度的 float
由以下三个部分构成。
- 符号位 S :占 1 位 ,对应 b31 。
- 指数位 E :占 8 位 ,对应 b30b29…b23 。
- 分数位 N :占 23 位 ,对应 b22b21…b0 。
float
的表示方式包含指数位,导致其取值范围远大于 int
。根据以上计算,float
可表示的最大正数为 2254−127×(2−2−23)≈3.4×1038 ,切换符号位便可得到最小负数。
尽管浮点数 float
扩展了取值范围,但其副作用是牺牲了精度。整数类型 int
将全部 32 比特用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 float
的数值越大,相邻两个数字之间的差值就会趋向越大。
- 浮点数有两种表示形式:
小数形式
1.float a = 123.f //123.000000 保留6位
指数形式,又叫科学计数法(内存中以科学计数法方式存储)
2.float a = 1.23e2f //1.23 * 10^2
科学计数法
- 它将一个数表示为一个系数乘以10的某个次方,
形式为:a × 10^b
其中a
是一个不小于1但小于10的浮点数,b
是一个整数。
#背
- 写一个正确的科学技术法表示的浮点数有两点需要注意:
1.e/E前必须有数字
2.e/E后必须有整型数
浮点型的格式化输出
float a = 123.456f;//用小数形式来表示。
float a1 = 1.23456E+02f; //表示123.456
printf("%f %f\n",a,a1);//%f,默认输出六位小数。
1.普通浮点数输出:
使用%f
格式符来输出,默认情况下输出六位小数
- 输出123.456001
由于浮点数的精度限制,可能会有误差
printf("%.2f %.2f\n",a,a1);//%f,默认输出六位小数。.2f,表示小数点后保留两位,四舍五入。
2.保留两位小数输出:
使用%.2f
格式说明符来输出a
和a1
,保留两位小数,四舍五入。
- 输出:123.46
printf("%e %e\n",a,a1);//%e,默认输出六位小数。
3.科学计数法默认输出:
使用%e
格式说明符来输出a
和a1
,默认情况下保留六位小数,使用科学计数法表示。
输出:1.234560e+02
printf("%.2e %.2e\n",a,a1);//%f,默认输出六位小数。
4.保留两位小数的科学计数法输出
使用%.2e
格式说明符来输出a
和a1
,保留两位小数,使用科学计数法表示。
输出1.23e+02
[[格式化符号%]]
char-字符常量
ASCII
-America standard code for information interchange
美国信息交换标准代码
#背
[!important]
空格----32 ‘\n’----10 ----通常对应回车
‘0’----48 ‘\0’-----0
‘1’----49 ‘9’-----57
‘A’----65 ‘a’----97
‘B’----66 ‘b’-----98
‘Z’----90 ‘z’----122
大小写转换加减32
‘\a’----7 ‘\b’----8 ‘\t’----9
\0
和0
的区别:
\0
和\000
是C语言设计出来的字符,表示字符串的末尾,无法键入,它在内存中存放的ASCII码值为0
0
是整形数0
它在内存中的ASCII码值为48
基本类型 | 符号 | 字节数 | 范围 |
---|---|---|---|
字符型 | char | 1 | -128~+127 |
无符号字符 | unsigened char | 1 | 0~255 |
在内存中,字符常量以ascii码值的形式存放,字符常量可以整数一样
![[常量变量.png]]
转义字符“”
🚀 重点 关于转义字符需要理解两点:
1.将</mark>后面的字符,转义成字符本身,使其失去系统赋予的含义
2.在单引号或双引号引起来的字符序列中:
\ooo表示8进制作为ASCII码值对应的字符
\xhh表示16进制作为ASCII码值对应的字符
**`char ch = '\x43';`**
- 这里定义了一个字符类型的变量`ch`。
- '\x43'是字符的十六进制表示法,\x 表示这是一个十六进制数,`43`是十六进制数字。
- 十六进制的`43`转换为十进制是67。
- ASCII值67对应的字符是`'C'`,所以`ch`被赋值为`'C'`。
字符串常量
字符串常量:双引号引起来的字符序列
系统自动在字符串末尾加上一个Null字符 \0
关于sizeof
🚀 重点
1.能测试常量 变量 数组 类型所占或应占内存空间大小,单位是字节(bytes)
2. 测试字符串,就是字符个数 +1
3. 测试变量,变量比照类型分配内存空间大小
long long int k = 1;
printf(“%d”,sizeof(k));`//输出 8
//是变量类型所占的内存空间大小
4. 测试数组名,就是数组所占的空间
5. 测试类型,就是类型应占的内存空间大小
6. 测试指针变量,32–4 64–8
数组名做形参退化成指针变量
sizeof和strlen的区别 #🤌
#include <stdio.h>
#include <string.h>
int main(){
printf("%d\n",sizeof("china"));
//测试空间大小,单位字节。
printf("%d\n",strlen("china"));
//strlen是函数,测试的是字符个数。
}
sizeof的功能是
-获取对象或类型所占用的内存字节数。
这个操作符的结果在编译时就已经确定(除非是在C99标准下使用可变长数组的情况),因此它可以被用作常量表达式。
strlen的功能:
size_t __cdecl strlen(const char *_Str);
函数原型声明
- 函数作用:从str代表的地址,截止到第一个\0,中间字符的个数
- 返回值?从str代表的地址,截止到第一个\0中间的字符的个数要被返回(不包括\0)
- str:函数调用时,分配空间,接受实际参数的值,保存到字符串某个字符的地址
【当字符串作为参数传递给函数时,系统为函数形参分配空间并将实际参数的地址复制给形参。形参接收到的实际上是字符串的第一个字符的地址,允许函数访问和操作整个字符串】
char * p = "abcdef" ;//字符串首地址赋值给指针变量
printf("%s\n",p);//从p代表的地址,输出截止到第一个\0,中间的字符输出
printf("%s\n","abcdef");//字符串作为函数的实参,实际上是字符串的首地址作为函数实际参数
作业
输入一个字符串“12\x08\12\x003\08abc”,请测试sizefo(串)和strlen(串)都是什么值。为什么?
1.明确字符串中的每个字符:
- 12是两个字符
1
和2
\x08
十六进制转义字符\b
表示退格键是一个字符\12
八进制转义字符\n
表示换行键\x00
NUL 空字符 是一个字符3
一个字符\08
是分开 `\b
表示退格键是一个字符abc
三个字符
2.计算字符串的大小
sizeof = 11
‘1’+‘2’+‘\b’+‘\n’+‘\0’+‘3’+‘\b’+‘a’+‘b’+‘c’+‘\0’
strlen = 5
宏
c语言语法——宏定义及其高级用法_c语言宏定义高级用法-CSDN博客
- 宏是一段代码的别名或替换,它在预处理阶段由预处理器替换为实际的代码文本。宏可以提高代码的可读性和可维护性。
#define NAME value- NAME 是宏名字 大写
- value 是替换的文本
宏类型(2种)
1.符号常量宏: #define 标识符 常量
定义常量值,用于代码中替换具体的数值和字符串
-
#define 标识符(一般大写) 替换列表
#define PI 3.14
2.参数宏
类似函数,可以带有参数,并且在使用时替换为相应表达式
-
#define 标识符(参数1,参数2,…,参数n) //替换列表
#define GET_MAX(a,b) ((a)>(b)?(a):(b))
#include<stdio.h>
#include<string.h>
#define GET_MAX(a,b) ((a)>(b)?(a):(b))
int main(){
int x = 0;
int y = 0;
scanf("%d%d",&x,&y);
//将被替换成printf( ((a)>(b)?(a):(b)))
scanf("%d",GET_MAX(x,y));
return 0;
}
宏的注意事项
-
括号使用:在定义参数化宏时,通常需要将参数和整个宏表达式用括号括起来,以确保在替换和计算时不会产生意外的优先级错误。
#define SQUARE(x) ((x) * (x)) // 正确使用括号
-
宏替换:宏替换是在预处理阶段进行的,可能会导致代码的可调试性下降,因为错误信息中会出现替换后的代码而非原始代码。
-
不建议复杂宏:复杂的宏定义可能导致代码难以阅读和维护,应尽量使用函数来替代复杂宏。
宏里面的#
和##
都是啥意思?
1. 宏中的 #
运算符
#
运算符被称为字符串化运算符(stringizing operator)它的作用是将宏参数转换为字符串文字.例如:
- 如果
a
是1111
,则#a
会变成"1111"
。 - 如果
a
是"1111"
,则#a
会变成"\"1111\""
,即字符串1111
包含在双引号内的字符串。
变量
定义一个变量:变量类型 变量名称
整型变量
整型变量的数据类型
基本类型 | 符号 | 字节数 | 数值范围 |
---|---|---|---|
基本整型 | int | 4 | -21亿-21亿 |
短整型 | short int | 2 | -32768~32767 |
长整型 | long int | 4 | -21亿~21亿 |
long long int | 8 | -263~263-1 | |
无符号基本整型 | unsigned int | 4 | 0~42亿 |
无符号短整型 | unsigned short int | 2 | 0~65535 |
无符号长整型 | unsigned long int | 4 | 0~42亿 |
unsigned long long int | 8 | 0~2^64-1 | |
- 在定义整型变量时,要注意数值允许的数值范围
实型变量
分为三类:单精度,双精度,长双精度
[[#实型常量]]
字符变量
[[#char-字符常量]]
char俩身份
[[#char-字符常量]]
1.当成字节整形数的数据类型,可以存放(-128~127)负数
当数值型类型使用
2.存放字符ASCII码值的数据类型(正半边范围恰好与ASCII表重合)
当字符型使用
不能把字符串赋给字符变量
循环
循环的本质就是执行相同的事情,面对一个需求
1.要分析出来反复处理什么事情,反复执行的就是循环体要写的代码
2.什么条件退出,循环条件
whlie 和 if 的区别
1.相同点:
都是循环条件为真,进入循环体,条件为假的时候退出
2.不同点:
for
循环用于已知开始值,已知结束值,并且知道步长
while
循环用于循环次数未知,循环次数是由中间运算决定的
关于数组array(7)
🚀 数组定义:
1.数目固定,类型相同,连续存放一组有序数的集合
2.C语言规定,数组名就是内存块的首地址,它是一个指针常量,不能改变它指向的地址
3.部分元素赋初值,其他未赋值的,自动为0
char str[10] ={1,2,3}
4.全部元素赋初始值,长度才可以省略
char str[] ={1,2,3,4,5}
5.使用元素用下标运算符[]
表示从0
开始下标为n
的内存块
下标n
的范围是{0,数组长度-1}
6.sizeof(arr)
就是数组所占空间大小
7.数组名做函数的形参要退化成指针变量
#include <stdio.h>
int main(){
double a = 0;
double b = 0;
double c = 0;
int k = 0;
while(k!=3){
char buf[100] = {0};
printf("请输入三个值:\n");
k = scanf("%lf%lf%lf",&a,&b,&c);
if(k!=3){
printf("输入错误,请重新输入\n");
}
gets(buf);
}
if(a + b <= c||a + c <= b ||c + b <=a ){
printf("构不成三角形\n");
}
}
运算符
#背
🚀 运算符规则
-
所有表达式的运算符规则:
1.从左往右两两运算符比较,左边优先级高,先计算左边,右边优先级高的,继续向右找,直到找到相对最高的运算符进行计算2.如果优先级相同,看结合性
单目运算右结合
双目运算左结合
三目运算嵌套右结合,计算左结合3.左结合,先计算左边的,右结合继续找,直到找到相对最高的,重复以上步骤 注意短路
关于/
1.C和C++中,两个整形数相除,必得整数
2.分子或分母有一个是浮点数,即为通常意义的除法
3.分母为 0 会崩溃
关于%
1.用于计算两个数相除后得到的余数
2.参与运算的量必须是整型量,其结果也是整型量
3.某个数与n取余,其范围是0~n-1
[[格式化符号%]]
作业
1.求100之内自然数中最大的能被17整除的数。
#include<stdio.h>
#include<math.h>
int main(){
int max = 0;
for(int i=1;i<=100;i++){
if(i % 17 == 0 && i > max){
max = i;
}
}
printf("最大能被17整除的数:%d",max);
}
2.计算并输出200-400之间不能被3整除的整数的和。
求和公式:num = num + 1
#include<stdio.h>
int main(){
int num = 0;
for(int i = 200;i <= 400;i++){
if(i%3 != 0){
num = num + i;
}
}
printf("num = %d",num);
}
3.从键盘输入10个数,统计非负数的个数,并计算非负数的和
-写一个输入循环
for (int i = 0; i < 10; i++) {
scanf("%d", &k[i]);
}
#include<stdio.h>
#include<string.h>
int main() {
int num = 0;
int k[10] = {0};//设一个能放十个数的数组
int count = 0;
for (int i = 0; i < 10; i++) {
scanf("%d", &k[i]); //键入的应该放进数组里面
} //这个i是上面循环定义的,在循环结束后也无用
for (int i = 0; i < 9; i++) {
if (k[i] >= 0) {
num = num + k[i];
count++;
}
}
printf("num=%d\n",num);
printf("count=%d\n",count);
return 0;
}
4.写宏,计算数组的长度
#include<stdio.h>
#define GET_SIZES(arr)(sizeof(a))/sizeof((arr[0]))
int main(){
int a [] ={1,5,9,7,8};
printf("数组的个数为:%d",GET_SIZES(a));
printf("数组的长度:%d\n",sizeof(a));
}
运算符
1. 位运算符& | ^ << >> ~
-
位与:两个都为1,则为1
int a = 10; //1010
int b = 13; //1101
printf(“%d”,a&b); // 1000----> 8 -
位或:有一个为1,则为1 a^0 ==> a
-
位异或^:相同为0,不同为1
int a = 10; //1010
int b = 13; //1101
printf(“%d”,ab); // 0111----> 7 -
左移<<:47<<3 101111-----101111000----
printf(“%d”,47<<3); // 376 左移n位,等于原数乘2的n次方 -
右移>>: 47>>3 101111----000101----
printf(“%d”,47>>3); // 5 右移n位,等于原数除以2的n次方 -
自反~:0变1,1变0
int a = 3; //0……0011
a = ~a;
//1……1100—推原码减一再取反–>1……1011–>1000……0100
printf(“%d”,a); // -4 -
逻辑右移:在前面补零,不考虑符号位。
-
算术右移:在前面补符号位的值(即原来的符号位),以保持符号不变。 例如:-1右移还是-1(1111… 1111…)
关系运算符 < <= > >= == !=
1、运算结果只能是真(1)或假(0)
2、只能表示简单的关系,两个数之间的关系 (3<a<5 错)
逻辑运算符 && || !
🚀 重点
- 运算结果只能是 真(1)或 假(0)
- 注意短路&& ||
&& || !
与 或 非 - 逻辑表达式形式:
表达式1 逻辑运算符 表达式2
1和2可以是任何表达式
- 表达式1 && 表达式2
只有表达式1为真的时候才求2的值 - 表达式1 || 表达式2
只有表达式1为假的时候才求2的值
int main()
{
int a = -1;
int x = ++a && ++a; 0&&-- a=0 x=0
printf("%d %d",a,x);
return 0;
}
- 短路特性:用来检测空指针保护电路
&& p是空指针就短路保护系统
if(p!= NULL && p[0] == 3){
printf()
}
练习 大小写变换
下标法
- 用char数组去存放字符串
char [100] = {0}
- 从0开始遍历字符串
int i = 0; str[i] != '\0'; i++
- 判断符合条件的字符
//输入一个字符串将字符串里的大写字母变小写,小写字母变大写
#include<stdio.h>
#include<string.h>
int main(){
char str[100];
printf("请输入字符串\n");
scanf("%s",str);
for(int i = 0; str[i] != '\0'; i++){
if(str[i] >= 'A'&& str[i]<='Z'){
str[i] = str[i] + 32;
}else if(str[i] >= 'a'&& str[i] <= 'z'){
str[i] = str[i] - 32;
}
}
printf("%s",str);
return 0;
}
指针法
#include<stdio.h>
#include<string.h>
int main(){
char str[100];
scanf("%s",str);
for(p = str;p[0] != '\0';p++){
if(p[i] >= 'A' && p[i] <= 'Z'){
p[i] = p[i] + 32;
}else if(p[i] >= 'a'&& p[i] <= 'z'){
str[i] = str[i] - 32;
}
}
printf("%s",str);
return 0;
}
位运算符重点
- 🚀 重点
- = 和 == 一个是赋值运算符 一个是比较运算符
- 赋值运算符=:左侧一定是变量,右侧类型匹配即可
- ,逗号运算符
- 在计算时,把,看成;,因为逗号表达式是多条语句表达式写法
- 以最后一个表达式的结果为整个逗号表达式的运算结果【多项赋值语句】
- & 位与–全为1则为1 其他为0【有0则0】
- | 位或–有1则为1 否则为0 【全0则0】
- ^ 异或–相同为1 不同为0【不同则0】
- << 左移–左移n位,就等于原数乘以2^n
- >> 右移–右移n位,就等于原数除以2^n
- ~ 自反 1变0 0变1
2. 位运算符应用场景
-
判断一个数是不是2^n
- 2^n 在二次方进制中具有唯一性
![[2n次方.png]] -
用你所知道的最快的算法计算无符号整型数中有多少个1
每次从数中减去1,并将结果与原数做按位与&
运算
循环次数等于1的个数 而不是位数
![[最快消去0.png]] -
把一个无符号的整型数第七 八位置 1
置1就按位或
a |= (3<<7);
把一个无符号整形数a的第七和第八位置1.
*/#include <stdio.h>
int main(){
unsigned int a = 0xFF000000;
a |= (3<<7);
printf("%x",a);
return 0;
-
把一个无符号的整型数第七 八位置 0
找一个7 8全0 其余全1的数和该数 |
0xFFFFFF3F 和 a 去按位与
置零就按位与 -
把无符号数第7 8 9 10取出,并用%x输出
#include <stdio.h>
int main(){
unsigned int a = 0;
scanf("%u",&a);
printf("%x",(a >> 7)& 0XF);
return 0;
}
3. 运算符优先级
-
算数运算符 + - * / % -(负号)
4 4 3 3 3 2 -
强制类型转换 2
-
自增自减 ++ – 2
-
关系运算符 > >= < <= == !=
6 6 6 6 7 7 -
逻辑运算符 && || !
11 12 2 -
三目运算符 ?: 13
-
赋值运算符 = 14 (把右侧值赋给左侧变量)
- 复合运算符 += -= /= %= *=
&= |= ^= <<= >>=
与 或 异或 左移 右移
-
逗号运算符 , 15 (左结合)
-
sizefof运算符
-
位运算符(逐个对应的比特位运算)
& | ^ << >> ~
与 或 异或 左移 右移 自反(所有位变换)
8 10 9 5 5 2 -
- 取值和 & 取址运算符
2 2
*p 根据p代表的地址,找对应类型的内存块 点找块
p为NULL *p崩溃 ;p为乱值 *p崩溃
& 块找点 根据内存块找对应的地址,*和&是互逆操作
- 取值和 & 取址运算符
-
[]下标运算符,下标从0开始,p代表的地址后第n个内存块; 1
-
. 和 → (和结构体使同) 结构体变量 指针
1 1
赋值运算的类型转换
![[赋值类型转换.png]]
4.char型数据的范围:-128~127
所以要考虑char的范围
1. 自增自减运算符
- 关于++,——
1.单独的i++
或者是++i
单独的i--
或--i
没区别
2.在其他表达式上有区别
++i
--i
符号在前,先自增/自减,再参与的运算
i++
i--
变量在前,先参运算,再自增自减
3.当连续多个++/–连写的情况
如:i+++j
i---j
编译器会识别前两个++/–是自增/自减运算符-i++
表示-(i++)
先运算再自增- 自增自只能用于变量,不能用于常量和表达式
🚀关于++,——
1.只有变量才能自增自减 【数组a[10] a++ (错的)】
- 单独的i++和++i 无区别,有其他表达式时有区别
如 int y = i++; //变量在前,先用变量参与所在表达式的运算,然后自增自减
int y = ++i; //符号在前,变量先自增自减,再参与所在表达式的运算
-i++ ——> -(i++)
- 如果有多余两个++或–连写的情况,则为前两个+或-为自增自减运算符
x+++y 等价于(x++)+y
-i++应理解为-(i++),而(-i )++是非法的。
++、–
① ++i或i++是单独一条语句时,没有区别
② ++i或i++在其他表达式上时:当++/–在后,先参与运算,然后再自增;++/–在前,先自增自减,再参与运算。
③ 连续几个++或—存在时,识别前两个+或前两个-为自增自减运算符
2.算术表达式
{}
表示一个作用范围
[]
下标运算符
()
表示运算优先级/函数声明调用
对应的数学函数的简写,再把【】化成()
3.不同数据类型的转换
不同类型的量运算时,转换成同一种类型然后运算
-
自动转换:数据类型自动由低转高
-
强制转换:将表达式的运算类型结果强制转换成指定的数据类型
printf(“%f”,(float)13/4);
原类型不变,只是运算过程中,用强制转换之后的类型计算
关于大小端 #🤌
cpu在处理整型数(int)的时候
把高字节放到低地址–大端
把低字节放到低地址–小端
![[大小端.png]]
强转指针类型
格式: (数据类型说明符) (表达式)
- 强制类型转换属于单目运算,运算优先级为2
int * p = &a;
char * q = (char *)p`
强制类型转换之后 只读一个字节的内容
按小端发送了四个字节流的数据,按照小端存放,请按照整数输出
char byte[4] = {0x78,0x56,0x34,0x12};
unsigned int a = ((int *)byte)[0];
printf("%#x",a);
实参的运算的次序是由右往左算,从左往右输出
作业
求125之内自然数中偶数之和。
#include <stdio.h>
int main() {
int sum = 0;
for (int i = 0; i <= 125; i += 2) {
sum += i;//求和公式
}
printf("偶数之和为:%d\n", sum);
return 0;
}
编程计算1*2*3+3*4*5+5*6*7+...+99*100*101的值。
#include <stdio.h>
int main() {
long long int sum = 0;// 使用 long long 以防止结果超出 int 范围
for (int n = 1; n <= 99; n += 2) {
sum += n * (n + 1) * (n + 2);
}
printf("表达式的值为:%lld\n", sum);
return 0;
}
解题:
1.看见和先写求和公式sum = sum + i
2.遍历再套循环
编写程序,将用户输入的字符串中所有的字符a用*代替,然后输出。
#include<stdio.h>
int main(){
char old_char = 'a';
char new_char = '*';
char str[100];
printf("请输入字符串:\n");
scanf("%s",str);
for(int i = 0;str[i] != '\0';i++){
if(str[i] == old_char){
str[i] = new_char;
}
}
printf("被替换之后的字符串为:%s\n",str);
return 0;
}
[[指针汇总]] #🤌
1. 指针(pointer)
也叫地址(address),内存块的首位置,是常量,程序在执行过程中不能被改变的量。指针不能被修改,不能自增自减。
数组名,就是内存块首地址,它就是指针常量
&a
是指针常量,,不能被改变赋值。
2. 指针变量
指针变量是保存指针(地址)的变量,也叫地址变量,是保存内存块首地址数据的。 int * a
存放int型内存块的地址;
- int * 是一种类型,是int型内存块地址类型;
- 未经初始化的指针类型禁止使用;int * p = NULL;表示未保存任何地址 0不是任何内存块的地址
- 把谁的地址赋值给指针变量(指针变量保存了谁的地址)我们就说指针变量指向了谁
int a = 10,int * p = &a——p指向a (p保存a的地址) - 32位系统下,所有指针变量是 4 个字节;
- 64位系统,指针变量是 8 个字节
3. *p的含义
- *p 的含义是点找块
根据p代表的地址值,找对应类型的内存块
- p 为 Null,*p崩溃
- p 为乱值,*p崩溃
4.p+n的含义
表示 p
代表的地址向右偏移 n
个存储单元 ,得到新地址
- 在连续空间中,两个指针相减等于间隔的内存单元的个数
#include<stdio.h>
int main(){
short int a [10]= {1,2,3,4,5,6,7,8,9};
short int * p = a + 5;
printf("%d\n",p-a);
printf("%d",(char*)p-(char*)a);
}
- 这里,将
short int
指针p
和a
强制转换为char
类型的指针。 char
类型是 1 个字节(8 位),而short int
类型通常是 2 个字节(16 位)。- 因此,
p
和a
之间的元素个数是 5,但字节数是5 * sizeof(short int)
。 sizeof(short int)
在大多数系统中是 2。- 所以
(char*)p - (char*)a
的结果是5 * 2 = 10
。
5.p[n]的含义
表示 p 代表的地址后第 n 个内存块
#include<stdio.h>
int main(){
int a[5] = {1,2,3,4,5};
int *p = a;
p[2] = 100;
*(p+3) = 50;
printf("%d\n",a[2]);
printf("%d",a[3]);
}
- 通过
p[2] = 100;
,我们将a[2]
的值从3
修改为100
。 - 通过
*(p + 3) = 50;
,我们将a[3]
的值从4
修改为50
。 - 最后,通过
printf
语句,我们打印出修改后的a[2]
和a[3]
的值,分别是100
和50
。
6.🚀指针支持的运算
![[指针支持的运算.png]]
- 算术运算符:+ - * / %
能相减的一定可以比较,反之成立
+ -
* / %无意义
+
:p + n没问题 两个指针相加无意义
-
:p - n在连续空间中,两个指针相减等于间隔的内存单元的个数
-
++,–:指针变量可以自增自减(指针常量如数组名不支持)
![[p++.png]] -
强转
一个指针类型可以随意强转其他类型
野指针
- 不能返回局部变量的地址
🚀*p++和(*p)++
1.*p++: 先得到*p
,使 p=p+1
,读取当前指针所指的值,并使指针移动到下一个位置,但返回的是原来的值
2.(*p)++: 点找块,是内存块对应的值++,p
本身没有变,它指向原来的位置,但那个位置的值已经变了
3.*(++p):先取 p+1
再取*p
标识符命名规则🦄
- 只能有英文,字母,数字,下划线构成
- 首字母不能是数字,只能是字母和下划线
- 不能和系统关键字,用户已经定义好的函数名,系统库函数重名
- 下划线开头的一般都是系统内部的标识符,建议用户避免使用这类标识符
- 大小写区分
- 要见名知意
自定义函数🍡
1. 关于函数
- 函数必须先声明再使用,如果函数定义在调用之前,可以不进行函数声明
- 函数调用在定义之前,必须进行函数声明
- 必须有函数原型声明,函数原型声明是告诉编译器,有这样的函数,参数有几个,函数类型是什么,函数返回值类型是什么,编译器就可以调用函数原型去检查函数调用是否正确
2. 函数返回值类型
- 函数返回值是函数运算完之后,返回的数据类型,调用者就可以根据函数返回值类型,定义相应的类型来接受返回值类型 (当然函数的返回值可以是void)
- 函数形参:
- 调用时,分配空间接受实参的值(比照类型接收值)
📌按照数目一样,类型一样,结果一样去进行实参和形参的结合,这种数据传递是单向的,只能由实参传递给形参,而形参的改变不会影响到实参
- 调用时,分配空间接受实参的值(比照类型接收值)
3. 局部变量
函数内部定义的变量叫做局部变量,保存程序或函数中间运算结果
作用范围:本函数 {}
不同函数可以定义相同的局部变量,他们代表不同的内存,形参是被调函数的局部变量
4. 函数被调时产生栈帧,调用完栈帧释放
- 函数名就是内存空间首地址,形参分配空间接受实参的值
⭐ ‘形参’ = ‘实参’(形参一定是变量,实参是什么都行,只要类型匹配)
❗形参带有*
的可能是调用者想得到什么东西 - 函数调用完毕后,栈帧自动释放,形参和局部变量分配的空间自动释放
- 调试要会的
step over 下一步
step into 进入函数
step out从函数跳出来
funto cursor 运行到光标外
不能返回局部变量的地址(野指针)
只能单向传递(实参到形参)
- 形参的改变不能影响到实参,但可以通过形参改变实参指向的内存块的值。
//用整型数组保存这些水仙花数
#include<stdio.h>
fun(int *arr,int *pcnt){
for(int i = 0;i <=999;i++){
int a = i%10;
int b = i/10%10;
int c = i/100;
if(a*a*a +b*b*b + c*c*c == i){
arr[*pcnt] = i;
(*pcnt)++;
}
}
}
void out_flower_number(int *a,int cnt){
for(int i=0;i<cnt;i++){
printf("%d",arr[i]);
}
}
int main(){
int a[1000] = {0};
int cnt = 0;
fun(a,&cnt);
out_flower_number(a,cnt);
}
刷题22 函数调用方法:
#include<stdio.h>
int get_sum(int a,int b){
a = 100;
b = 50;
return 0 ;
}
float caculate(float * h){
float init_h = 100;
float sum= 0;
for(int i = 0;i<10;i++){
sum += init_h + init_h/2;
init_h = init_h/2;//给下一次循环做准备。
}
sum -= init_h;
*h = init_h;
return sum;
}
int main(){
float height = 0;//要想改变调用者变量的值,必须将变量的地址传递到被调用函数中。
//被调用函数得到地址后,前面加*,就找到调用者的变量,就可以将其改变。
float sum = caculate(&height);
printf("sum = %.3f,height=%.3f",sum,height);
}
循环里用的关键字
continue
//结束本次循环。
int main(){
for(int i = 0;i<10;i++){
if(i%2 == 0){
continue;
//结束本次循环。
//下面代码不再执行,再次循环。
}
printf("%d ",i);
}
}
break语句
break
语句用于立即终止所在的循环或 switch
语句。也就是说,当程序执行到 break
语句时,会跳出当前的循环或 switch
语句,不再执行后续的迭代或分支代码。
continue语句
continue
语句用于跳过本次循环中剩下的语句,并立即开始下一次迭代(即继续下一次循环)。它不会终止整个循环,只是跳过当前的迭代。
#include<stdio.h>
int main(){
int i = 0;
for(i = 0;i<n;i++){
if(a[i]%2 == 0){
break; ,一旦找到第一个偶数,用break跳出循环
}
if(i<n){
return 1; ,跳出循环并且返回1
}
}
}
int main(){
int a[10] = {0};
for(int i = 0;i<10;i++){
scanf("%d",&a[i]);
}
ink k = fun(a,10);
printf("%d",k);
}
🍩const关键字作用
1.const char * str
: str指针可以改变,但str保存的内存块是const char型,不可以更改str指向的值 const 修饰变量类型
- 应用场景:用于指向只读字符串或字符数组的指针。
2.char * const str
: str指针不能改,但str保存的内存块是char型的,可以改变str指向的值 const修饰指针
- 应用场景:用于指向一个固定地址,但可以修改该地址内容的指针。
3.const char * const str
都不可更改
![[const.png]]
![[常量指针.png]]
c语句和顺序结构
c语句有5类:
- 表达式语句
- 函数调用语句:例如printf(" \n")
- 控制语句:if while return语句等
- 复合语句:用{}括起来的一个语句
- 空语句:仅有一个;构成的语句
赋值语句
- 赋值表达式加上;构成的: 变量=表达式
注意:=右边的表达式可以是一个赋值表达式 a=b=c=5 右结合
赋值表达式和赋值语句的区别是:
1.赋值语句
赋值语句是将一个值赋给变量,主要作用是更新变量的值,通常没有返回值
2.赋值表达式
赋值表达式不止是赋值操作,还会返回赋值之后的值,赋值表达式可以出现在任何允许表达式出现的地方
数据的输入输出
printf和scanf#🤌
✨printf()
-
格式:
printf("格式控制 ",输出列表);
格式控制:由双引号引起来的字符串用于输出指定格式。
输出:任意合法表达式用,
隔开 -
用于输出的修饰符
修饰符
| 意义 |
---|---|
l | 用于长整型和双精度的实型数据,可加在格式字符d,o,x,u,f前面 |
h | 用于短整型数据输出,可加在可加在格式字符d,o,x,u,f前面 |
m | 数据的最小宽度(超过这个宽度不影响) |
n | 对于实数,表示输出n位小数,对于字符串,表示截取的字符个数 |
# | 当整数以八进制或十六进制形式输出时,输出前缀,可加在O和x前面 |
- | 输出的数字或字符在域内向左靠,右边填空格 |
- 用于输出的格式字符
![[格式符.png]]
格式符说明
-
d
格式符 ➡️ 输出一个带符号的十进制整数%d
按照整型数据的实际长度输出%md
字段宽度%ld
输出长整型数据%hd
输出短整型的数据%0md
前面补0
-
o,x,u
格式符 ➡️ 输出一个无符号的八进制,十六进制,十进制整数 -
c
格式符 ➡️ 输出一个字符- 一个整数,如果在0~127之间,也可用字符形式输出,系统会将该整数转换成对应的ASCII码字符
-
s
格式符 ➡️ 输出一个字符串%s %ms
:输出的字符串占m列%m.ns
:输出字符串中左端n个字符
-
f
格式符 ➡️以小数的形式输出单精度,双精度实数%f
整数部分全部输出,默认6位小数%m.nf
n是小数位个数
-
e
格式符 ➡️ 标准化的指数形式输出实数(小数点前面有且只能有一位非0数字)%e
默认输出6位小数%m.ne
m字段宽度(指数部分占5列) n小数位数
%e
: 默认输出6位小数1.——e+2
2 4
📌X,E,G之外其他不能大写
- 格式说明个数<输出项时,多余的输出项不输出
- 格式说明个数>输出项时,输出不定值
![[格式化符.png]]
📌
void read_data(int *start,int *pass){
printf("请按%%d:%%d:%%d %%d:%%d:%%d输入整数:\n");
int k = scanf("%d:%d:%d %d:%d:%d",
&start[2],&start[1],&start[0],
&pass[2],&pass[1],&pass[0]);
if(k != 6){
char buf[100] = {0};
gets(buf);
}else{
break;
}
}
#define MOD(a) ((a)==2?24:60)
void caculate(int * start,int * pass,int * end){
int carry = 0;
int time = 0;
for(int i = 0;i<3;i++){
end[i] = ((time = start[i] + pass[i] + carry)) %MOD(i);
carry = time/MOD(i);
}
}
int main(){
int start[3] = {0};
int pass[3] = {0};
int end[3] = {0};
read_data(start,pass);
caculate(start,pass,end);
printf("%02d:%02d:%02d\n",end[2],end[1],end[0]);
return 0;
}
✨scanf(6点)
scanf(“%d”,&a) #背
- 输入数据时采用的分隔符应与格式控制中的分隔符一致。格式控制中无普通字符时,输入数值型数据可用空格、Tab键、回车键作分隔符
- 输入数据时不可规定精度, 但可指定宽度
- scanf(“%7.2f ”, &a);❌
scanf(“%3d%2d%3c”, &a, &b, &ch)⭕️
- scanf(“%7.2f ”, &a);❌
- 输入格式符带有*,表示指针读取位置向前改变
- scanf(“ %d%d%d ”, &a, &c) ;
输入12︺34︺567则 a=12, c=567, 34 被跳过
📌scanf中表示不要了,屏蔽了,表示读写位置向前走,跨过去了,返回值也没它了
- scanf(“ %d%d%d ”, &a, &c) ;
- 有无符号数可用 %d、%o、%x,%u 格式读入。
- 用C格式符输入字符时, 任何输入都是有效字符
- 在下列情况下可认为数据输入结束:遇空格、回车、 Tab键;遇宽度结束;非法输入。
#include <stdio.h>
int main(){
char name[100] = {0};
int age = 0;
char sex = 0;
float height = 0;
scanf("%8s%*2d%c%f",name,&sex,&height);
printf("%s,%d,%c,%.2f",name,age,sex,height);
return 0;
}
[!important]
📌 就是说有无符号数,都可以按照%d,%o,%x,%u来读取,如果是有符号数,用%o,%x,%u还是存的补码,再%d输出不变
如果是%x读,表示你输入的是十六进制,比如12,是16进制的18,用%d输出是18
遇见空格,tab键,回车键 | 遇指定宽度 | 遇到非法字符 | |
---|---|---|---|
%duxofe | k | k | k |
%s | k | k | 不能 |
%c | 不能 | k | 不能 |
#✏️ 输入一个表达式(只有加减乘除,不出现俩负号相邻,中间没有空格,除法保证整除),输出结果
输入:-3+15*2-6/3
输出:25
分析:用数组去模拟栈
1.考虑如何用循环,先读取一个数之后,会发现是一个字符一个数字这样子去读取的,如果字符读取结束,还需要判断一下,因为输入完成之后会按回车来表示读取结束,
2.如何运算?
因为有+ - * / 所以要考虑运算优先级,* 的优先级最高,需要先计算,遇见* /就直接计算,遇见±就先存进栈里面,
3.在运算的时候可以将减法看成是1-2就相当于1+(-2)将数前面添加一个负号在压入栈中
#include <stdio.h>
int main(){
int stack[1000] = {0};
int * p = stack;
char ch = 0;
int value = 0;
int sum = 0;
scanf("%d",&stack[0]);
p++;
while((ch= getchar())!='\n'){
scanf("%d",&value);
if(ch == '+'){
*p++ = value;
}else if(ch == '*'){
*(p-1) *= value;
}else if(ch == '-'){
*p++ = -value;
}
else if(ch == '/'){
*(p-1) /= value;
}
}
for(--p;p>=stack;p--){
sum += *p;
}
printf("%d",sum);
return 0;
}
putchar和getchar
putchar
`putchar(c);`
向终端输出一个字符 ,c
为参数,只能是单个字符而不是字符串
getchar
`getchar ();
`char ch = getchar();
从键盘读入一个字符,只能读取一个字符,该字符可赋给字符变量、整型变量或作为表达式的一部分。
#✏️ 设计一个转换进制的函数,将十进制转换为其他进制
思路:将数转换成对应进制,存入字符串中,然后将字符串逆序,因为使用短除法,除完要倒着输出
/*
第一个参数:要转换的10进制的无符号整形数。
第二个参数:转换成多少进制。数据范围 2<=radix<=36
第三参数:将无符号数,转成各种进制的字符串,存放到out这个内存块中。
返回值,把out返回。为啥这么设计此函数?不用白不用,因为有返回值就可以
当另外函数的实参了。否则如果是void,这个函数就没有办法作为另外函数的实参。
*/
#include <stdio.h>
char * convert(unsigned int a,int radix,char * out){
}
int main(){
char buf[100] = {0};
printf(convert(100,8,buf));
}
老师示范
itoa
是c语言提供的十进制转换其他进制的函数
![[Pasted image 20240728182547.png]]
#include <stdio.h>
#include<stdlib.h>
char * convert(unsigned int a,int radix,char * out){
char * p = out;
char * q = out;
while(a>0){
int b = a%radix;
*p++ = b<10? b+'0':(b-10)+'a';
a/=radix;
}
for(q = out,p--;q<p;q++,p--){
char t = *q;
*q = *p;
*p = t;
}
return out;
}
int main(){
char buf[100] = {0};
printf(convert(100,7,buf));
putchar('\n');
itoa(100,buf,7);//C语言提供的转换进制的库函数
printf("%s",buf);
}
sprintf/sscanf#🤌
sprintf(合并)
- 用sprintf来将很多元素合并进字符串
- sprintf和printf的区别是,不是打印到控制台,而是打印到字符串中,以某种格式
int main(){
int year = 2024;
int month = 7;
int day = 6;
int hour = 2;
int minute = 14;
int seconds = 7;
char buf[100]={0};
sprintf(buf,"%4d-%02d-%02d %02d:%02d:%02d",
year,month,day,
hour,minute,seconds);
printf("%s",buf);
}
sscanf(拆开)
- sscanf就是从字符串中以某种格式将数据读取出来,放到变量中
- scanf是从标准输入,以某种格式将数据读取出来,放到变量中
用sscanf拆串,用sprintf合并字符串
int main(){
char buf[100] = {"12:14:07"};
int hour = 0;
int minute = 0;
int seconds = 0;
sscanf(buf,"%d:%d:%d",&hour,&minute,&seconds);
printf("hour=%d,minute:%d,seconds=%d",hour,minute,seconds);
}
#✏️ 练习:输入一个表达式,只有加减乘除,不出现两个符号相邻的情况,中间没有空格,输出结果,除法(用例保证整除)
- 定义一个stack 栈指针为sp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int str2int(const char * str){
int ret =0;
char ch = 0;
int stack[10000] = {0};
int * sp = stack;
sscanf(str,"%d",sp);
sp++;
char buf[100] = {0};
sprintf(buf,"%d",*stack);
const char * p = str + strlen(buf);
while((ch = *p)!='\0'){
int value = 0;
for(p++;*p>='0'&& *p<='9';p++){
**value = value * 10 + (*p - '0')**;
//将字符数字转换为数字,*p取得字符值 若*p为'5',*p - '0'为5
//'0'的ASCII码值48,所以*p - '0'是将字符转换成数字,字符转成整型
}
if(ch == '+'){
*sp++ = value;
}else if(ch == '-'){
*sp++ = -value;
}else if(ch == '*'){
*(sp-1) *= value;
}else if(ch == '/'){
*(sp-1) /= value;
}
}
for(--sp;sp>=stack;sp--){
ret += *sp;
}
return ret;
}
int main(){
char str[100] = "-21+67*2-16/4+8";
int ret = str2int(str);
printf("%d",ret);//117
}
42、输入一个不多于5位的正整数,判断它是几位数,并逆序输出各位数字
#include<stdio.h>
int main(){
int a = 0;
int b = 0;
int cnt = 0;
while(1){
printf("shuru");
scanf("%d",&a);
if(a<=99999){//不多于5位所以限制条件
break;
}
}
while(a){
b = b*10+a%10;//通过加法将 a 的个位数添加到 b 的末尾。
a/=10;
cnt++;
}
printf("%d %d",cnt,b);
}
总结:通过累加和位移来处理数字
通用步骤:
- 初始化: 准备一个变量来存储结果。
- 处理数据: 通过位移(例如乘以 10)和累积(例如加上当前位的值)来更新结果。
- 结束: 完成处理后,结果即为所需的值
- 字符数字转换整数
-
value = value * 10 + (*p - '0')
通常用于字符数字转换成整数-
value
是当前数字的累积值。
value * 10
:将当前数字左移一位(即乘以 10)。+ (*p - '0')
:将当前字符代表的数字加到value
上。- //'0’的ASCII码值48,所以
*p - '0'
是将字符转换成数字,字符转成整型
-
假设我们要从字符串 "1234" 中提取整数 1234。
- 初始 value = 0。
- 遇到字符 '1',value = 0 * 10 + (1 - '0') = 1
- 遇到字符 '2',value = 1 * 10 + (2 - '0') = 12
- 遇到字符 '3',value = 12 * 10 + (3 - '0') = 123
- 遇到字符 '4',value = 123 * 10 + (4 - '0') = 1234
- 结果:value = 1234
#include <stdio.h>
int main() {
char str[] = "1234";
int value = 0;
for (char *p = str; *p; p++) {
value = value * 10 + (*p - '0');
}
printf("Converted number: %d\n", value);
return 0;
}
-
逆序整数
b = b*10+a%10;
📌定义一个变量b
用于存储逆序之后的数字
初始设置b = 0
-
a % 10
提取a
的个位数。
b * 10
将b
向左移动一位。- 通过加法将
a
的个位数添加到b
的末尾 - 去掉当前数字的个位:
a /= 10
。
-
- 逆序整数:将任意整数逆序,将个位数添加到结果末尾
- 数字分解:提取和处理数字的各个位
假设我们要逆序整数 `12345`。
- 初始:a = 12345, b = 0 a/=10
- 第一步:b = 0 * 10 + 5 = 5, a/=10 a = 1234
- 第二步:b = 5 * 10 + 4 = 54,a/=10 a = 123
- 第三步:b = 54 * 10 + 3 = 543, a/=10 a = 12
- 第四步:b = 543 * 10 + 2 = 5432, a/=10 a = 1
- 第五步:b = 5432 * 10 + 1 = 54321,a/=10 a = 0
- 结果:b = 54321
#include <stdio.h>
int main() {
int a = 1234;
int b = 0;
while (a) {
b = b * 10 + a % 10;
a /= 10;
} printf("Reversed number: %d\n", b);
return 0;
}
3.字符数字提取 digit= c - '0'
将单个字符数字转换为其对应的数值,用于处理和计算
通过char c获得数字
#include <stdio.h>
int main() {
char c = '7';
int digit = c - '0';
printf("Converted digit: %d\n", digit);
return 0;
}
#✏️ 找朋友
#include<stdio.h>
int main(){
int nums = 0;
int i = 0;
int j = 0;
scanf("%d",&nums);
int height[nums];
int friend[nums];
for(i = 0;i<nums;i++){
scanf("%d",&height[i]);
}
for(i = 0;i<nums;i++){
for(j = i+1;j<nums;j++){
if(height[j]>height[i]){
break;
}
}
/*循环结束后,根据j<nums是否满足,我们可以判定是否发生了break,
* 如果发生了break,第i个小朋友,就找到了朋友(第j个*/
if(j<nums){
friend[i] = j;
}else{
friend[i] = 0;
}
}
for(i=0;i<nums;i++){
printf("%d",friend[i]);
}
😕输入一串字符,逆序输出,要求用数组实现
数组传递参数:
- 字符型数组:只需要首地址,因为字符串末尾有‘\0’
- 数值型数组:需要两个参数,首地址和数组中元素的个数
数值型数组要设计地址后到底有多少内存块,字符串后有\0,没有必要设计n
![[Pasted image 20240728175150.png]]
typedef unsigned long long size_t;
1. 创建一个别名:将unsigned long long
类型定义为size_t
意味着使用两个名字都一样
2. size_t
用于存储对象的大小和表示数组索引
3. typedf
用于为已有类型创建新的名字或别名
//输入一串字符,逆序输出。要求使用数组实现。
#include <stdio.h>
#include <string.h>
typedef unsigned long long size_t;//typedef关键字的作用:就是给现有类型取个别名。在标准库中,`size_t` 通常用于表示对象的大小。
void reverse(char * str){
char * p = str;
char * q = str + strlen(str) - 1;
while(p<q){
char t = *p;
*p = *q;
*q = t;
p++;
q--;
}
}
void reverse1(char * str) {
int nums = 0;
int couples = (nums = strlen(str))/2;//赋值表达式可以出现在任何允许表达式出现的地方。
for(int i = 0;i < couples;i++){//有多少对,就循环多少次。
int t = str[i];
str[i] = str[nums-1-i];
str[nums-1-i] = t;
}
}
int main(){
char str[100] = {0};
scanf("%s",str);
//reverse(str);//数值型数组要设计地址后到底有多少内存块,字符串后有\0,没有必要设计n
reverse1(str);
printf("%s",str);
}
#✏️ 有一个字符串,由小写字母,大写字母和数字构成,按数字-大写-小写排序,已经去重
计数排序
![[Pasted image 20240727111020.png]]
if语句
🚀 重点
1.else
总是找离它最近的未配对的if
配对
2.如果if
和else
的数目不同,加{}
来确定配对关系
3.内层的选择结构必须完整的嵌套在外层的选择结构内,两者不许交叉
4.嵌套要缩进
多分支循环
- 多分支选择结构,只要进入一个分支,其他分支不会再判断,这条语句就运算结束
单分支循环
- if(表达式){语句块} 只一条语句,如果表达式为真,执行语句1,否则什么都不做
双分支循环
switch语句
- 注意:
break
终止switch
语句的执行- switch 后面必须是case后面的常量值的类型匹配,且只能是整型,字符型,枚举型
- 枚举类型:
enum option{T,S,C,W}
- 定义一个枚举类型,枚举类型的变量,只允许在枚举类型的范围内取值
T,S,C,W
可以看成符号常量- 在程序中直接使用,第一个默认0,后面依次1,2,3
- 如果第一个赋值1,后面依次2,3
#✏️ 老虎杠子鸡虫 枚举型
1. 1.0版本,用switch case语句举例子
2. case后面不能跟字符串
//老虎杠子鸡虫子
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
enum option{TIGER = 0,STICK,COCK,WORM,UNKNOWN};
//定义了一个枚举类型,枚举类型的变量,只允许在枚举类型的范围内取值。
//1,TIGER,STICK,COCK,WORM枚举值可以看成是符号常亮。
在程序中直接使用,第一个默认为0,依次为1,2,3...
//2,如果第一个赋值为1,那么依次就是2,3,4...
void output_computer(enum option computer){
if(computer==TIGER){
printf("计算机:%s\n","老虎");
}else if (computer==STICK){
printf("计算机:%s\n","杠子");
}else if (computer==COCK){
printf("计算机:%s\n","鸡");
}else{
printf("计算机:%s\n","虫子");
}
}
int main(){
enum option computer = UNKNOWN;
enum option player = UNKNOWN;
while(1){
srand(time(NULL));//时间做种子,让随机数序列不同。
computer = rand()%4;
printf("\n请输入:0,老虎,1,杠子,2,鸡,3,虫\n");
int val = 0;
scanf("%d",&val);
player = (val<0?-val:val)%4;
output_computer(computer);
switch(computer){
case TIGER:
switch(player){
case TIGER:
case WORM:
printf("平局");
break; case COCK:
printf("输了");
break; case STICK:
printf("赢了");
break;
}
break;
case STICK:
switch(player){
case STICK:
case COCK:
printf("平局");
break; case TIGER:
printf("输了");
break; case WORM:
printf("赢了");
break; }
break;
case COCK:
switch(player){
case COCK:
case STICK:
printf("平局");
break; case WORM:
printf("输了");
break; case TIGER:
printf("赢了");
break; }
break;
case WORM:
switch(player){
case WORM:
case TIGER:
printf("平局");
break; case STICK:
printf("输了");
break; case COCK:
printf("赢了");
break; }
break;
default:
break;
}
}
}
- 用二维数组来优化:
- 用查表的方法,速度变快。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef enum option{TIGER ,STICK,COCK,WORM,UNKNOWN} game_type;
int main(){
game_type computer_option = UNKNOWN;
game_type user_option = UNKNOWN;
int n = 5;
int i = 0;
while(i<5){
srand(time(NULL));//用时间做种子,不然每次产生的随机数序列会有规律
computer_option = rand()%4;
printf("请输入0:老虎,1,杠子,2,鸡,3,虫子\n");
scanf("%d",&user_option);
user_option%=4;
char * table[4][4] = {{"平","输","赢","平"},
{"赢","平","平","输"},
{"输","平","平","赢"},
{"平","赢","输","平"}};
char * value[4] = {"老虎","杠子","鸡","虫子"};
printf("计算机模拟了%s\n",value[computer_option]);
printf("用户输入了%s\n",value[user_option]);
printf("本轮结果:%s\n",table[user_option][computer_option]);
i++;
}
}
内存分区 #🤌
![[内存5分区.png]]
1. 👇栈区(stack)
- 函数的形参和局部变量分配到栈区,函数被调用时形参和局部变量分配空间,函数调用结束后,形参和局部变量分配的空间自动释放. 栈比较小 1-2m
2. 👆堆区(heap)
- 🚀 重点
- 由
malloc
函数分配的内存,分配到堆区,必须通过free
函数自动释放,否则内存泄漏memory leak
malloc的头文件:stdlib - “进程是资源管理的最小单元,进程中的所有函数都可以访问内存单元(只要内存不消失)”
- 由
📌#include<stdlib.h>
void * malloc(unsigned long long int_Size);
- 作用:在堆上分配_Size个字节的内存块。参数的单位是字节。就是产生多少个字节的意思。
- 参数:_Size,就是产生多少个字节。
- 返回值:返回内存块的首地址。void表示为空。void * 表示任意类型的地址。使用时需要强转。malloc在堆上产生的内存,要用free函数释放,否则内存泄漏。
3. 常量文本区(text)
- 存放常量字符串等,进程结束后,内存释放
⭕️常量文本区不能更改
4. 全局静态区(static/globle)
- 全局变量和静态局部变量在全局变量区
全局变量是函数外定义的变量
静态局部变量:static + 局部变量
5. 代码区(code)
存放每一条指令的
Fibonacci数列的定义
Fibonacci数列的前两个数字通常是 0 和 1,接下来的每个数字都是前两个数字的和。数学上,Fibonacci数列 F(n)可以用以下递推公式定义:
- F(0)=0
- F(1)=1
- F(n)=F(n-1) + F(n-2) (对于n≥2n)
斐波那契数列的前几个项是: 0,1,1,2,3,5,8,13,21,34,…
迭代法算斐波那契数列
int fibonacci_iterative(int n){
if (n <= 1) return n;
int a = 0, b = 1,c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
素数的定义
- 素数:大于 1 的自然数,且只能被 1 和它自己整除。
- 非素数:不能满足素数定义的自然数。包括 1 和大于 1 的所有合成数(即有多个因子的数)
- 排除小于 2 的数
- 任何小于等于 1 的数都不是素数。
- 排除偶数
- 除了 2 之外,所有的偶数都不是素数。因为偶数都可以被 2 整除,所以我们只需检查奇数。
- 利用平方根
- 对于一个数 nnn,如果它有一个因子 ddd(即 n%d0n % d == 0n%d0),那么 ddd 必定小于或等于 n\sqrt{n}n。这是因为如果 ddd 大于 n\sqrt{n}n,那么另一个因子 n/dn/dn/d 必定小于 n\sqrt{n}n。因此,只需检查从 2 到 n\sqrt{n}n 的所有整数是否能整除 nnn,可以显著减少计算量。
- 进一步优化检查
- 对于大于 3 的数,只需检查到 n\sqrt{n}n 并且排除能被 2 或 3 整除的数。之后,只检查形式为 6k±16k \pm 16k±1 的数,因为所有素数都可以表示成这种形式(除了 2 和 3)。
[[#指针汇总 🤌]]
数组 [[#关于数组array(7)]]
数组做函数参数
- 函数形参一定是变量[[#2. 指针变量]]
- 数组做函数参数,退化成指针变量,所以
sizeof
得到8
字节
当用sizeof
时你实际上是在处理一个指向数组首元素的指针,而不是整个数组
int fun(int nums[100],int numsSize){
printf("%d",sizeof(nums));
}
数组和指针的区别
![[Pasted image 20240731150115.png]]
二维数组 #🤌
![[二维数组 1.png]]
以这个为例子来了解二维数组
-
int a[3][4] = {xxx}
表示三行四列 在内存中按照行存放a[0]
是第一行,包含4个int型的数组,因为数组名是指向其首元素的指针,所以a[0]
是int*
型,
- 计算字节间距
- 1.
(char*)(p+1)-(char*p)
- p指向int[4],一行4个int,一行
4*sizeof(int)
,每行16字节
2.(char*)a([0]+1)-(char*)a[0]
- a[0]和a[0]+1之间相隔一个int,4字节
- p指向int[4],一行4个int,一行
- p+1-p:两个行相减间隔一行
- 不能对常量和表达式取地址(&a,&a[0]不能这样想)
- 用指针遍历二维数组时,第一层循环是定义数组指针,第二层是`*k=*q;
- 二维数组中
*a=a[0];
for (int(*q)[4]= p;q<p+n; q++){//按每一行循环呢。
for (int* k=*q; k<*q+4; k++){//按每一个科目循环
#include <stdio.h>
int main(){
int a[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
for(int i = 0;i<3;i++){ //按行循环
for(int j = 0;j<4;j++){ //按列循环
printf("%d ",a[i][j]);
}
putchar('\n');
}
int (* p)[4] = a;//所以我们称p是数组指针(变量)
//a就是a[0]这个行的首地址。
//二位的数值型数组,等于一维的行数组。a就是一维的行数组的首地址。
//a[0]是第0行的首地址,它是int * 型。
//而a行数组的首地址,列数是不能省略的。
// 是int (*)[4]的类型,列不能省略。
//所以,a和a[0]数值相同,但类型不同。
putchar('\n');
for(int i = 0;i<3;i++){ //按行循环
for(int j = 0;j<4;j++){ //按列循环
printf("%d ",p[i][j]);
}
putchar('\n');
}
printf("%d\n",(p+1)-p);//两个行类型的地址相减,间隔多少行。
printf("%d\n",(char*)(p+1)-(char *)p);//两个char *类型的地址相减,间隔多少个char。
printf("%d\n",(a[0]+1)-a[0]);//a[0]和a[0]+1都是int *型,两个int*型的地址相减,就是间隔多少个int
printf("%d\n",(char*)(a[0]+1)-(char *)a[0]);
//a[0]和a[0]+1都是char *型,两个char*型的地址相减,就是间隔多少个char
}
数组指针:数组元素类型是指针的数组
int * a[3][4]
–3行4列矩阵里面全是指针
![[数组指针.png]]
char*a
- ![[char.png]]
char*a
放在常量文本区,如果改变常量字符串首地址会崩溃
[[#char俩身份]]
因为char俩身份
![[char俩.png]]
![[int char型数组区别.png]]
![[73698f804e544413f0a3ab5368384d0.png]]
数值型二维数组和字符型二维数组的区别和联系
![[Pasted image 20240729163104.png]]
#✏️ 用二维数组来解决这个题
/*10、求3行3列矩阵a[3][3]={1,2,3,4,5,6,7,8,9}非对角线上元素之和。写个函数*/
#include <stdio.h>
int get_sum(int n ,int (*p)[n]){
int sum = 0;
for(int i = 0;i<n;i++){//n行循环
for(int j = 0;j<n;j++){//对于每一行来说,n列进行循环。
if(!(i == j || i + j == n-1)){
sum += p[i][j];
}
}
}
return sum;
}
int main(){
int a[5][5] = {{1,2,3,4,5},
{6,7,8,9,10},
{1,2,3,4,5},
{6,7,8,9,10},
{1,2,3,4,5}};
printf("%d", get_sum(5,a));
int b[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("\n%d", get_sum(3,b));
}
66. 加一 #✏️
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
思考:
- 从最后开始处理,对数组最后一个元素+1,如果最后一位+1时产生进位,就设置当前位为0,并继续往前处理
- 处理进位,如果当前的最后一位+1之后没进位,就重新赋值当前位
如果当前位+1之后等于10,产生进位,就设置当前为0,传递进位给前一个 - 最前面的进位,数组所有都变成0,就在数组最前面+1
// 说明: 返回的数组必须通过malloc来分配,假设调用者调用了free函数
int* plusOne(int* digits, int digitsSize, int* returnSize){"整数数组的指针 数组的大小 存储结果数组大小的指针
/*1,判断是否都是9*/
int i = 0;
int * ret = NULL;
for(i = 0;i< digitsSize;i++){
if(digits[i] != 9){
break;
}
}
if(i< digitsSize){ "int*指向加一操作结果数组的指针
ret = (int *)malloc(sizeof(int )* digitsSize);
*returnSize = digitsSize;
int t = digits[digitsSize -1] + 1;//最后一位先算
ret[digitsSize -1 ] = t == 10?0:t;//最后一位的结果。
int carry = t/10;
for(int j = digitsSize -2;j>=0;j--){
t = digits[j] + carry;
ret[j] = t%10;
carry = t/10;
}
}else{
ret = (int *)malloc(sizeof(int )* (digitsSize + 1));
ret[0] = 1;
*returnSize = digitsSize + 1;
for(int j = 1;j<digitsSize + 1;j++){
ret[j] = 0;
}
}
return ret; "释放动态内存以避免内存泄漏
}
循环
- 反复执行什么
- 何时结束
while
主要特点:当型循环,当条件为假就不进去循环
- 先判断后执行,有可能不执行
while
循环需要在循环体外单独初始化控制变量,并在循环体内必须修改循环变量while
循环适用于循环次数不确定或基于某个条件来控制循环的场景- 使用
while
循环可以更灵活地处理未知次数的循环,并且条件检查是最初执行时进行的。 - while(x!=0) 写成 while(x)
注意数据类型!!
#include<stdio.h>
int main(){
int n = 0;
float s = 1.0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
s *= i;
printf("%d的阶乘为%.0lf\n",n,s);
}
}
for
主要特点:先判断再执行,可能一次不执行
- 循环之前检查条件,条件为假之前会执行循环体。
for
循环将初始化和更新放在循环语句的一行中,适合在一个地方设置和更新循环控制变量for
循环通常用于确定循环次数时。- for(表达式1;表达式2;表达式3){}
- 表达式可以省略或全部省略但
;
不能省略 - 表达式2可以省略,则认为循环条件始终为真,程序将陷入死循环
- 表达式可以是多项,多项表达式用逗号表达式
- 表达式可以省略或全部省略但
#✏️ 泰勒展开式
#include<stdio.h>
#include<math.h>
int main(){
int n = 0;
double x = 0;
double sum = 1;
double t = 1;
scanf("%d%lf",&n,&x);
for(int i = 0;i<n;i++){
t = t*x/i; /这两句重要,每一项是前一项的x/i
sum +=t; /每一项的加和
}
printf("%.5lf",sum);
printf("%.5lf",esp(x));
do while
主要特点:先循环一次,再判断条件,条件为假就跳出循环
-
循环体至少执行一次:
do...while
循环的一个显著特点是,它保证循环体至少会执行一次。这是因为循环条件的检查发生在循环体执行之后。这与while
或for
循环不同,后者在循环体执行之前就会检查条件。
-
条件检查:
- 在循环体执行完毕后,
do...while
循环会检查条件表达式。如果条件为真 (true
),循环会继续执行;如果条件为假 (false
),循环会结束
- 在循环体执行完毕后,
- 输入一些整型数,用,分开,求最大值
- 1,5,7,9,4,5,3
- 反复在读取,和数字
- 直到读到
\n
结束
用 do-while 解决
#include<stdio.h>
#include<limits.h>
int main(){
int max = INT_MAIN;
int value = 0;
do{
scanf("%d",&value);
max = value>max?value:max;
}while(getchar() !='\n');
printf("%d\n",max);
}
#include <stdio.h>
#include <limits.h>
int main(){
int max = INT_MAIN;
int value = 0;
int flag = 0;//标志变量,第一次读取的值正确的赋给max
do{
scanf("%d",&value);
if(flag == 0){ //这一步的目的是将第一个数,赋值给max.
max = value;
flag = 1;
}
max = value > max?value:max;
}while(getchar()!='\n');//条件为真时,循环,为假的时候,退出。三个循环都是一样。
printf("%d\n",max);
}
无条件循环 goto
了解即可
三种循环比较
![[三种循环.png]]
循环嵌套
- 嵌套的不能用相同的循环变量,并列的循环可以用相同的循环变量
练习题
#✏️ 99乘法表
int main(){
for(int i = 1;i<=9;i++){//每一行9个数字
for(int j = 1;j<= i;j++){
//j表示当前列的数字,i行打印i个乘法结果
printf("%2d *%2d=%2d",j,i,j*i);
}
putchar("\n");
}
return 0;
}
#✏️
/*某班有n名学生, 已知他们参加某次考试的成绩(0~100间的整数 ), 求全班同学的平均成绩。
以及获得优秀(>=90分)的学生人数。
* */#include<stdio.h>
float get_value(int * arr,int n,int * count){
*count = 0; / 给指针指向的变量赋 0
float sum = 0.0;
for(int i = 0;i<n;i++){ /遍历越界 0 ~ n-1
sum += arr[i]; / 计算学生成绩的总和为 avg 做准备
if(arr[i] >=90){ /判断条件计算>=90的人数
(*count)++; /指针指向的变量++
}
}
return sum/n; /返回 avg
}
int main(){
int arr[] = {80,90,93,92,95,47,42};//学员成绩
int count = 0;//优秀学生数量
float avg = 0;
int n = sizeof(arr)/sizeof(arr[0]);
avg = get_value(arr,n,&count);
printf("平均分:%.2f\n",avg);
printf("优秀的学生数:%d\n",count);
return 0;
}
![[Pasted image 20240730152438.png]]
#include<stdio.h>
int main(){
int n = 0;
scanf("%d",&n);
for(int i = 0;i<n;i++){
for(int j = 0;j<n-i;j++){
putchar(32);//输出空格
}
for(int j = 1;j<=i;j++){
printf("%d",j);
}
for(int j = i-1;j>=1;j--){
printf("%d",j);
}
putchar('\n');
}
}
输出类似上一题的字母三角形
#include<stdio.h>
int main(){
int n = 0;
scanf("%d",&n);
for(int i = 1;i <= n;i++){//前面的空格
for(int j = 0;j < n-i;j++){//n-i个空格 第几行打几个
putchar(' ');
}
for(int j = 'A';j <'A'+i;j++){//前面递增的字母
printf("%c",j);
}
for(int j ='A'+i-2;j>='A';j--){
printf("%c",j);
}
putchar('\n');
}
}
![[Pasted image 20240730154317.png]]
#include<stdio.h>
#define N 100 "定义二维数组最大维度
int main(){
/*1,给二维数组赋值*/
int a[N][N] = {1};
int n = 0; 输入行数
int k = 2; 在数组中递增的数字
scanf("%d",&n);
for(int i = 1;i<n;i++){ //控制填充对角线行
int col = 0;
for(int j = i;j>=0;j--){ //从i行开始,逐行向上填充,直到行号为
a[j][col++] = k++; //将当前的k填充到[j][col]然后k和col都自增
}
}
/*2,按0行打n个数,1行打n-1个数*/
for(int i = 0;i<n;i++){
for(int j = 0;j<n-i;j++){ //每行打印n-i个数。
printf("%-4d",a[i][j]);
}
putchar('\n');
}
}
更精简的方法2
#include<stdio.h>
int main(){
int n = 0;
scanf("%d",&n);
int num = 0;
int cnt = 1;//存储矩阵的起始值
int cnt1 = 0;
for(int i=1;i<=n;i++){//外循环从i到n
cnt += num++;
cnt1 = cnt;
for(int j = i;j<=n;j++){
printf("%d ",cnt1);
cnt1 += (j+1);//每列增长
}
putchar('\n');
}
return 0;
}
- 写一个函数,将1到20之间,不能被3整除的数,放到数组里,调用函数时,按10个一行输出
/**/
/*返回值就是不能被3整除数的个数,
* 参数,接收调用者提供的数组首地址,将这些数放到数组里。*/
int fun(int * arr){
int cnt = 0;
for(int i = 1;i<=20;i++){
if(i%3 != 0){
arr[cnt++] = i;
}
}
return cnt;//返回值就是不能被3整除数的个数
}
int main(){
int arr[20] = {0};
int nums = fun(arr);
for(int i = 1;i<=nums;i++){
printf("%4d",arr[i-1]);
if(i%10 == 0){
printf("\n");
}
}
}
break和continue的区别
排序
冒泡排序O(n^2)
- 冒泡排序的基本思想是通过不断地交换相邻的元素,将最大的(或最小的)元素“冒泡”到数组的一端。这个过程会重复进行,直到整个数组有序
- 从数组的开始位置,依次比较相邻的两个元素。
- 依照需求把大的数排前面 or 后面,第一轮循环是为了搞清楚要比较多少次(n-1)
- 对数组的每一对相邻元素执行以上操作,完成一轮排序后,最大/最小的元素被放到最后,每一轮从(n-i)开始
- 继续对剩下的元素进行相同的操作,直到整个数组有序
#include <stdio.h>
void bubbleSort(int arr[], int n) {
int i, j;
// 外层循环控制总的排序轮数
for (i = 0; i < n - 1; i++) {
// 内层循环进行相邻元素的比较和交换
for (j = 0; j < n - i - 1; j++) {
// 如果前一个元素比后一个元素大,则交换
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
bubbleSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
选择排序O(n^2)
- 选择排序的基本思想是将数组分为已排序和未排序两个部分。在每一轮排序中,选择未排序部分的最小元素,并将其放到已排序部分的末尾
- 从未排序部分中找到最小的元素。
- 将找到的最小元素与未排序部分的第一个元素交换。
- 将已排序部分的范围扩展一个元素,未排序部分缩小一个元素。
- 重复以上步骤,直到所有元素都被排序
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
int buble_order(int * a,int n){
for(int i = 1;i<n;i++){
//比较的轮数,冒多少个泡 n个数比较n轮
for(int j = 0;j<n-i;j++){
//看从哪个下标比较到哪里
//不要用相同的循环变量
if(a[j] < a[j+1]){
int t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
}
}
void select_order(int * a,int n){
for(int i = 0;i<n-1;i++){
//依次计算每个下标,给每个下标找最大值,放到该位置。
for(int j = i+1;j<n;j++){
//当前位置和后面所有位置的数进行比较
if(a[j] > a[i]){
int t = a[j];
a[j] = a[i];
a[i] = t;
}
}
}
}
int main(){
int a[] = {1,5,2,11,34,57};
int n = sizeof(a)/sizeof(a[0]);
buble_order(a,n);
for(int i = 0;i<n;i++){
printf("%d ",a[i]);
}
int b[] = {1,5,2,11,34,57,4,92,17};
int nb = sizeof(b)/sizeof(b[0]);
select_order(b,nb);
putchar('\n');
for(int i = 0;i<nb;i++){
printf("%d ",b[i]);
}
return 0;
}
稳定排序和非稳定排序:
- 按某种比较规则,如果两个数按比较规则是相同的排序完成后,相同的两个元素的先后位置相对于初始位置先后位置发生了变化,称非稳定排序,否则称为稳定排序
冒泡排序为稳定排序
选择排序为非稳定排序- 按整型数的后三位排序 大到小
101 12101 105
冒泡:105 101 12101
选择:105 12101 101
- 按整型数的后三位排序 大到小
复杂度
1.算法复杂度
随着数据量的增加,运算次数是恒定的值,算法复杂度为O(1)
随着数据量的增加,运算次数是线性增长的,算法复杂度为O(n)
圈复杂度 静态检测质量
2.空间复杂度
动态规划
![[Pasted image 20240730214032.png]]
青蛙跳台阶,只能跳1个台阶或3个台阶,问有n级台阶有多少种跳法
int main(){
int n = 0;
scanf("%d",&n);
int dp[n+1];
dp[1] = 1;
dp[2] = 1;
dp[3] = 2;
for(int i = 4;i<=n;i++){
dp[i] = dp[i-1] + dp[i-3];"找出推导式,青蛙跳的总和"
printf("%d ",dp[i]);
}
printf("\n%d",dp[n]);
}
#✏️ 给定一个整数数组,找出总和最大的连续数列,并返回总和。
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
int maxSubArray(int* nums, int numsSize){
int dp[numsSize];
dp[0] = nums[0];//最大的数是自己
int max = nums[0];
for(int i = 1;i<numsSize;i++){
dp[i] = (dp[i-1]>0)?dp[i-1]+nums[i]:nums[i];
"判断前一项>0?如果大于0就加如果小于0就不加"
if(dp[i]>max){
max = dp[i];
}
}
return max;
}
#✏️ **输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4
int lengthOfLIS(int* nums, int numsSize) {
/*1,定义一个dp数组,记录截止到某个数时的最长子序列。
* 每个数都计算出来,然后取之中的最大值。
*2,从零下标开始循环,循环到numsSize-1下标,逐个运算。
* 逐个如何运算呢?当前这个数,要能够连接到前面的每个队伍后面
* 取最大的数。
*/
int dp[numsSize];//每个元素没有赋初始值,会是乱值,所以要小心。
int max = 1;
dp[0] = 1;
for(int i = 1;i<=numsSize-1;i++){//从零下标开始循环,循环到numsSize-1下标
dp[i] = 1;//每个dp[i]起始值一定是1,不可能比1小。
for(int j = 0;j<i;j++){ //i前面的所有队伍。
if(nums[i] > nums[j] && dp[j]+1 > dp[i]){//满足当前这个数 》前面队伍中最后一个。并且前面队伍人数 +1,比当前计算好的do【i】大,替换dp[i]
dp[i] = dp[j]+1;
max = max < dp[i]?dp[i]:max;
}
}
}
return max;
}
二分查找
int binary_search(int a[],int n,int value) {
int left = 0;
int right = n-1;
while(left <= right){
int mid = (left+right)>>1;
if(a[mid] == value){
return mid;
}else if(a[mid] > value){
right = mid-1;
}else{
left = mid + 1;
}
}
return -1;
}
int main(){
int a[ ] = {1,5,7,9,18,29,37,45,55,99,103};
int n = sizeof(a)/sizeof(a[0]);
printf("请输入要查找的数:\n");
int value = 99;
int ret = binary_search(a,n,value);
if(ret == -1){
printf("查无次数");
}else{
printf("此数的下标是%d",ret);
}
}
qsort
快排 复杂度O(nlogn) O(n)
- 选取数组左边为基准,初始化ij,分别指向数组两端
- 将一个长数组分成两个短数组
快排的例子
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int cmp(const void * a,const void * b){
int x = *(int*)a;
int y = *(int *)b;
if( x >= y) {
return -1;//按文档来说,返回1,由小到大排序。
}
return 1;
}
int main(){
int arr[] = { 1,9,2,8,3,7,4,6,5,0 };
int n = sizeof(arr)/sizeof(arr[0]);
qsort(arr,n,sizeof(arr[0]),cmp);
for(int i = 0;i<n;i++){
printf("%d ",arr[i]);
}
return 0;
}
函数指针:解决差异化问题用于回调
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
void bubblesort(int arr[],int n,int size,int (*cmp)(const int * a,const int * b) ){
for(int i = 1;i<n;i++){
for(int j = 0;j<n-i;j++){
if(cmp(&arr[j],&arr[j+1]) == 1){//此处就是回调你传入的函数
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
int cmp_by_3(const int * a,const int * b){
int x = *a%1000;
int y = *b%1000;
if(x>y){
return 1;
}
return -1;
}
int cmp_by_2(const int * a,const int * b){
int x = *a%100;
int y = *b%100;
if(x<y){
return 1;
}
return -1;
}
int main(){
int a[] = {12345,67,893,79};
int a_nums = sizeof(a)/sizeof(a[0]);
/*按后三位将数组有由小到大排序*/
bubblesort(a,a_nums,4,cmp_by_3);
for(int i = 0;i<a_nums;i++){
printf("%d ",a[i]);
}
/*按后二位将数组有由大到小排序*/
putchar('\n');
int b[] = {12345,67,893,79};
int b_nums = sizeof(b)/sizeof(b[0]);
bubblesort(b,b_nums,4,cmp_by_2);
for(int i = 0;i<b_nums;i++){
printf("%d ",b[i]);
}
}
库函数
函数:
- itoa(待转换数,buf,进制数) ----转换进制保存在数组中 buf——数组
-
拆串函数:strtok:把s字符串中所有含有delim里面的字符修改为‘\0’; 形参1是待拆字符串的首地址;形参2是一个不可修改的符号串(,、?等)的首地址;使用后,会破会原字符串的完整;拆完后复制到words的字符串数组中实现代码:
while((p = strtok(p,"!?',;. "))){
strcpy(words[cnt++],p); p = NULL;
}
-
sprintf(buf,“%d",传入变量)—将变量合成字符串(合成字符串存入buf)%d与变量格式一致,buf会加上一个’\0’
-
sscanf(buf,“%d”,&接收变量)—从buf字符串中拆分%d类型的值到变量(从buf里拆串)stdio.h;
-
void * malloc(unsigned long long int _Size);
-------作用:在堆上分配_Size个字节的内存块。参数的单位是字节。就是产生多少个字节的意思。参数:_Size
,就是产生多少个字节。返回值:返回内存块的首地址,void
表示为空。void *
表示任意类型的地址。使用时需要强制类型转换。malloc在堆上产生的内存,要用free函数释放,否则内存泄漏(Memory leak)。free(viid*);这对函数在stdlib.h头文件中。 -
enum option { }定义一个枚举类型,{}中取值可以看作是符号常量,第一个默认值是0,{A=1,b,c}依次是123;enum(枚举);在stdlib.h头文件中;
-
srand(time(NULL))时间做种子,让每次生成的随机数序列不同(srand是伪随机数函数);加入stdlib.h和time.h两个头文件;
-
qsort(快排函数)
void qsort(void*a(待排序数组地址),int n(数组元素个数),sizrof(数据类型int),cmp(写一个比较规则函数))
;在stdlib.h头文件中
int cmp(const void*a,const void* b) {
a>b return ((int)a - (int)b)};`
//大于0交换,由小到大排序,小于0不交换; qsort 算法复杂度nlogn
strncpy(*a,*s,n)
:把s地址后的n个字符复制到地址为a的后面;不会复制给一个’\0‘;
![[07a1c4bbb554b7f5e381ab19ed07fc7.png]]
qsort 标准例题
int cmp(const void * a,const void * b){
int x = *(int *)a;
int y = *(int *)b;
if(x<y){
return 1;//返回1,就交换
}
return -1;
}
bool checkDynasty(int* places, int placesSize) {
qsort(places,placesSize,sizeof(int),cmp);
int zero_nums = 0;
for(int i = 4;i>=0;i--){
if(places[i] == 0){
zero_nums++;
}else{
break;
}
}
if(zero_nums >=4){
return true;
}
int gap_nums = 0;
for(int i = 1;i<5;i++){
if(places[i] == 0){
break;
}
if(places[i-1] == places[i]){
return false;
}
gap_nums += places[i-1] - places[i] - 1;
}
if(gap_nums <= zero_nums){
return true;
}
return false;
}
递归
递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。
- 递:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。
- 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。
- 而从实现的角度看,递归代码主要包含三个要素。
- 终止条件:用于决定什么时候由“递”转“归”。
- 递归调用:对应“递”,函数调用自身,通常输入更小或更简化的参数。
- 返回结果:对应“归”,将当前递归层级的结果返回至上一层。
![[递归.png]]
递归与迭代的区别
- 迭代:“自下而上”地解决问题。从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。
- 递归:“自上而下”地解决问题。将原问题分解为更小的子问题,这些子问题和原问题具有相同的形式。接下来将子问题继续分解为更小的子问题,直到基本情况时停止(基本情况的解是已知的)。
5类型
- 基本类型(整型、实型、字符型)
- 构造类型(有基本类型构造起来的)
- 指针类型(某个类型后加颗星号,表示某个内存块的首地址)
- 空类型(void) void*表示不知道啥类型的地址,万能指针
- 自定义类型 typedef(给现有类型起个别名,增强代码可读性)
//自定义类型
typedef int money;
typedef int age;
int main(){
money a = 100000000;
age b = 30;
printf("小马哥在%d岁有%d元\n",b,a);
}
结构体
- 结构体特性:
- 继承
- 封装
- 抽象(概况总结事务的共性)
- 多态
#include <stdio.h>
#include <string.h>
typedef struct student2
{ char name[8] , sex ;
int score[2] ;
int year, month, day ;
} stu3, stu4;// struct student2的别名有两个:stu3,stu4。
struct student1
{ char name[8] , sex ;
int score[2] ;
int year, month, day ;
} stu1, stu2;//定义结构体类型的同时,定义了结构体变量。
int main(){
stu3 one_student = {0};
strcpy(one_student.name,"abc");
one_student.score[0] = 30;
stu1.year = 2024;
stu1.sex = 'F';
}
#include<stdio.h>
#include<string.h>
typedef struct student2{
char name[8],sex;
int score[2];
int year,month,day;
}
akk,*parr;
//typedef struct student2 * parr;
int main(){
akk student = {
.year = 2024,
.sex = 'f',
.score = {100,98},
.month = 8,
.day = 9,
.name = "laowang"
};
parr p = &student;
printf("姓名:%s\n"
"性别:%s\n"
"分数:%d %d\n"
"生日:%d年%d月%d日\n",p->name,p->sex=='f'?"女":"男",p->score[0],
p->score[1],
p->year,p->month,p->day);
}
关于结构体的字节对齐
- 结构体各成员起始位置相对于结构变量的偏移量应该是成员类型的倍数
- 结构体变量占的总字节数应该是结构体各成员中最大类型所占字节数的倍数
举例:
#include<stdio.h>
struct student
{ char name[8];
char sex ;
float score[2];
} ;
int main(){
struct student stu1;
printf("%d\n",sizeof(stu1));
}
结构体例子:
#include<stdio.h>
#include<stdlib.h>
struct student{
char name[20];
int id;
int score;
};
int cmp(const void*a,const void*b ){
struct student * p1 = (struct student*)a;
struct student * p2 = (struct student *)b;
if(p1->score < p2->score){
return 1;
}
return -1;
}
int main(){
int nums = 0;
printf("请输入学生的个数:\n");
scanf("%d",&nums);
struct student m[nums];
printf("请输入%d个学生的名称ID和学分,用空格区分:\n");
for(int i = 0;i<nums;i++){
printf("请输入%d个学生的名称ID和学分,用空格区分:\n",i+1);
scanf("%s%d%d",m[i].name,&m[i].id,&m[i].score);
}
printf("输入完毕");
qsort(m,nums,sizeof(struct student),cmp);
for(int i = 0;i<nums;i++){
printf("%s %d %d\n",m[i].name,m[i].id,m[i].score);
}
}
结构体封装
#include <stdio.h>
#include <stdlib.h>
struct student {
char name[20];
int id;
int score;
};
int cmp(const void * a,const void * b){
struct student * p1 = (struct student *)a;
struct student * p2 = (struct student *)b;
if(p1->score< p2->score){
return 1;
}
return -1;
}
void read_data(struct student m[],int * pNums){//封装函数
printf("请输入学生个数:\n");
scanf("%d",pNums);
printf("请依次输入名称 ID和 学分,用空格区分:\n");
for(int i = 0;i<*pNums;i++){
printf("请输入%d个学生的名称 ID和 学分,用空格区分:\n",i+1);
scanf("%s%d%d",m[i].name,&m[i].id,&m[i].score);
}
printf("输入完毕");
}
void order_by_qsort(struct student m[],int nums){
qsort(m,nums,sizeof(struct student),cmp);
}
void output(struct student m[],int nums){
for(int i = 0;i< nums;i++){
printf("%s %d %d\n",m[i].name,m[i].id,m[i].score);
}
}
int main(){
int nums = 0;
struct student m[1000] = {0};
read_data(m,&nums);
order_by_qsort(m,nums);
output(m,nums);
}
8.10
#include <stdio.h>
struct result{//结构体定义
int start;
int end;
};
void read_all_data(int sum_arr[],int * real_nums,int * minAverageLost){//读取数据
scanf("%d",minAverageLost);//读取最小平均值
do{
scanf("%d",&sum_arr[*real_nums]);
//读取每个数并存储在sum_arr之中
if(*real_nums > 0){
sum_arr[*real_nums] += sum_arr[*real_nums - 1];//实现了累加;
}
(*real_nums)++;//实际的数字计数器累加
}while(getchar()!='\n');//直到读取到行末符
}
void proc(int sum_arr[],int real_nums,int minAverageLost,struct result ret[],int * ret_nums) {
int max_len = 0;//最大子组长度
int i = 0;
int j = 0;
for(i = 1;i<real_nums;i++) {
float t = (float) sum_arr[i] / (i + 1);//计算从0到i的平均值
if (t <= minAverageLost){
if ((i + 1) > max_len) {
ret[0].start = 0;
ret[0].end = i;
*ret_nums = 1;
max_len = i+1;
} else if ((i + 1) == max_len) {
ret[*ret_nums].start = 0;
ret[*ret_nums].end = i;
(*ret_nums)++;
}
}
for(j = 1;j<i;j++) {
float t = (float)(sum_arr[i] - sum_arr[j-1])/(i-j+1);
if(t<=minAverageLost) {
if ((i - j + 1) > max_len) {
ret[0].start = j;
ret[0].end = i;
*ret_nums = 1;
max_len = i - j + 1;
} else if ((i - j + 1) == max_len) {
ret[*ret_nums].start = j;
ret[*ret_nums].end = i;
(*ret_nums)++;
}
}
}
}
}
//输出所有符合条件的子数组的起始和结束索引
void output(struct result ret[],int ret_nums){
for(int i = 0;i< ret_nums;i++){
printf("%d-%d",ret[i].start,ret[i].end);
if(i!=ret_nums-1){
printf(" ");
}
}
}
int main(){
int sum_arr[101] = {0};//前缀和数组
int real_nums = 0;//实际数字个数
int minAverageLost = 0;//最小平均值
struct result ret[100] = {0};//结果数组
int ret_nums = 0;
read_all_data(sum_arr,&real_nums,&minAverageLost);//读进来。
proc(sum_arr,real_nums,minAverageLost,ret,&ret_nums);
output(ret,ret_nums);
贪心算法
高效货运
- 老李是货运公司承运人,老李的货车额定载货重量为wt
- 2.现有两种货物,货物A单件重量为wa,单件运费利润为pa,货物B单件重量为wb,单件运费利润为pb
- 老李每次发车时载货总重量刚好为货车额定载货重量wt,车上必须同时有货物A和货物B,货物A、B不可切割
- 老李单车次满载运输可获得的最高利润是多少
输入描述
第一列输入为货物A的单件重量wa,0<wa<10000第二列输入为货物B的单件重量wb,0<wb<10000第三列输入为货车的额定载重wt,0<wt<100000第四列输入为货物A的单件运费利润pa,0<pa<1000第五列输入为货物B的单件运费利润pb,0<pb<1000
输出描述
单次满载运输的最高利润
- 题目思路:给定总重量的情况下获得最大价值
- 输入数据: 读取两个物品的重量、价值和总重量。
- 选择物品: 选择单位重量价值比更高的物品。
- 计算组合: 尝试所有可能的组合以找到最优的价值。
- 输出结果: 打印最大价值,若没有有效组合则返回
-1
#include<stdio.h>
int main() {
int wx[2] = {0};//存储两个物品的重量
int wt = 0;//总的最大重量
int px[2] = {0};//存储的两个物品的价值
scanf("%d%d%d%d%d", &wx[0], &wx[1], &wt, &px[0], wx[0]);
//wx[0]和wx[1]是两个物品的重量
//wt是背包的总重量
//px[0]和px[1]是两个物品的价值
int select = px[0] / wx[0] > px[1] / wx[1] ? 0 : 1;
//计算两个物品的单位重量价值比,选择价值比较大的物品,select用来选择单位价值比更高的物品
//如果select = 0,选择第一个,如果select = 1,选择第二个物品
int count = wt / wx[select];//总的除以物品的重量
for (int i = count; i >= 1; i--) {//从选择最大数量开始,向下递减尝试所有可能的数量
int t = wt - i * wx[select];//t是剩余的数量,总重量减去当前物品的总重量
//检查剩余的重量t 能否被另一个物品的重量整除(是否可以完全装入另一个物品)
//如果可以整除,计算最大输出值:当前的物品的总价值加上剩余物品的重量能够装入另一种物品的总价值
//如果找到了有效组合,打印最大价值并退出程序
//如果循环结束后没有找到有效组合,返回-1表示无解
if (t != 0 && t % wx[!select] == 0) {
printf("%d", px[select] * i + px[!select] * (t / wx[!select]));
return 0;
}
}
return -1;
}
链表,信息管理平台
#include<stdlib.h>
#include<stdio.h>
struct student{
char name[20];
char sex;
int age;
char comment[1000];
};
typedef struct list_node{
struct student stu_info;
struct list_node*next;
}lnode,*linklist;
linklist creat_list(struct student students[],int n){
linklist head = (linklist)malloc(sizeof(lnode));
//head是一个指向lnode类型的指针,类型为"linklist"
//分配一个lnode结构体的大小的内存是头节点
//头节点的内存地址赋值给头指针head,head指向第一个节点
linklist p = head;
//p是一个linklist类型的指针,初始时指向头节点
//p用于遍历链表,head用于遍历链表
/*for循环:
* 为新节点分配内存,并将其他的地址赋给当前的”next“
* p移动到新分配的节点,使p指向新节点
* 将当前信息分配给新节点的数据段
* 将新节点的next指针设为NULL,表示尾节点
*/ for(int i = 0;i<n;i++){
p->next = (linklist)malloc(sizeof(lnode));
p = p->next;
p->stu_info = students[i];
p->next = NULL;
}
return head;
}
void traversal_list(linklist head){
while(head->next){
head = head->next;
printf("姓名:%s,性别:%s,年纪:%s,评价:%s",head->stu_info,head->stu_info.sex=='F'?"女士":"先生",head->stu_info.age,head->stu_info.comment);
}
}
int main(){
struct student students[] = {{}};
int n = sizeof(students)/sizeof(students[0]);
linklist head = creat_list(students,n);
traversal_list(head);
}
文件[[文件]]