C语言基础知识讲解(入门)

发布于:2022-10-17 ⋅ 阅读:(1042) ⋅ 点赞:(0)

目录

一、hello world!程序说明

二、计算机中数据的存储

2.1数值型数据的存储

2.1.1 十进制

2.1.2 二进制

2.1.3 八进制

2.1.4 十六进制

2.2 非数值型数据的存储

 三、词法符号

3.1.关键字

3.2 标识符

四、数据类型

4.1 概念

4.2 数据类型的分类

4.3 整数类型

4.3.1 char

4.3.2 short

4.3.3 int

4.3.4 long

4.3.5 long long

4.4 实型数据的存储

五、原码、反码、补码的问题

六、常量

6.1 概念

6.2 常量的分类

6.2.1 整型常量

6.2.2 浮点型常量

6.2.3 字符常量

6.2.4 字符串常量

6.2 .5标识常量 --宏定义

七、变量

7.1 概念

7.2 变量的初始化和赋值

17.3.1 显式强转

7.3.2 隐式强转

八、运算符

8.1 概念

8.2 运算符的分类

8.3 算数运算符

8.4 自增自减运算符

8.5 关系运算符

8.6 逻辑运算符

8.7 位运算符

8.8 条件运算符

8.9 赋值运算符

8.10 sizeof运算符

8.11 逗号运算符

3.12 运算符的优先级问题

九、C语言中常用的输入输出函数

9.1 putchar/getchar

9.1.1 putchar()

9.1.2 getchar

9.2.2 gets

9.3 printf/scanf

9.3.1 printf

9.3.2 scanf

9.4 垃圾字符的处理

十、分支控制语句

10.1 if..else 语句

10.2.1 格式及执行逻辑

10.2.2 注意事项

10.2.3 if..else 语句练习

10.2 switch..case 语句

10.2.1 格式

10.2.2 注意事项

十一、循环控制语句

11.1使用goto实现循环

11.1.1 goto的基本使用

11.1.2 使用goto实现循环

11.2 while循环

11.2.1 格式

11.2.2 执行逻辑

11.3 do..while 循环

11.3.1 格式

11.3.2 执行逻辑

11.4 for 循环

11.4.1 格式

11.4.2 执行逻辑

11.5 死循环

11.6 辅助控制关键字

11.6.1 break

11.6.2 continue

11.6.3 return

十二、控制语句练习

十三、数组

13.1 概念

13.2 一维数组

13.2.1 概念

13.2.2 定义一维数组的格式

18.2.3 一维数组的性质

13.2.4 一维数组的初始化

十四、冒泡排序

14.1 基本思想

14.2 实现思路(以升序为例)

14.3 代码实现

十五、二维数组

15.1 概念

15.2 定义二维数组的格式

15.3 二维数组的性质

15.4 二维数组的初始化

十六、字符数组和字符串

十七、字符串处理函数

17.1 strlen

17.2 strcpy

17.3 strcat

17.4 strcmp

十八、指针

18.1 概念

18.2 指针相关的操作

18.3 定义指针变量的格式

18.4 指针和变量的关系图

 18.5 指针的基本使用及注意事项

18.7 指针的大小

18.8指针和一维数组

18.9指针和二维数组

18.10数组指针

18.11指针和字符串

18.12二级指针

18.13const 关键字

十九、函数

19.1 概念

19.2 函数的定义和调用

19.4 函数的参数

19.5 函数的返回值

19.6 全局和局部

19.7 函数的传参方式

19.7.1 全局传参

19.7.2 复制传参(值传递)

19.7.3 地址传参(地址传递)

19.8 数组的传参方式

19.8.1 字符串的传参

19.8.2 一维整型数组传参

19.8.3 二维数组的传参方式

19.9 main函数的参数

19.10 指针函数


废话不多说直接上干货!

一、hello world!程序说明

#include <stdio.h>
int main(int argc, const char *argv[])
{//{}里面包住的部分叫做函数体,就是我们实际要执行的代码 

    printf("hello world!\n"); //C语言中每条指令结束 都需要加 分号;
        //printf 是系统给我们提供的输出的函数 
        //功能就是将 "" 内的内容打印到终端  \n 表示换行的意思 就是回车
    return 0;    //函数的返回值
}

        1.C语言中以#开头的行 称为预处理行 是在预处理阶段处理的

        2. #include 是包含头文件的关键字

        3.stdio.h 标准输入输出的头文件 我们的使用的printf就在这个头文件里

        4.int 函数的返回值类型 

        5.main 主函数 是程序的入口 每个程序必须有且仅有一个

        6.() 里面是main函数的参数 先不用管 ()里面的内容可以不写 但是 ()必须写。可以写成 int main()

        7.()圆括号 []方括号 {}花括号

二、计算机中数据的存储

        计算机中数据的存储分为两大部分:

                (1)数值型数据 520 1314

                (2)非数值型数据 "www.baidu.com" "百度"

2.1数值型数据的存储

2.1.1 十进制

        在C语言中,没有特殊说明和前导符时,默认的都是10进制数据

        特点:逢10进1 每一位上的数字范围 [0, 9]

        例如:520 1314

         十进制转二进制

        使用除2取余法:用十进制数除以2,保留商和余数,然后再用商继续除以2,保留商和余数,依次类推直到商为 0,将得到的余数 倒序取出 就是对应的二进制数了。

2.1.2 二进制

        前导符 0b

        特点:逢2进1 每一位上的数字 只能是 0 或者 1

        例如:0b1110 0b1100

二进制转十进制:

        从右到左算

        0b1110 == 0*2^0 + 1*2^1 + 1*2^2 + 1*2^3 == 0 + 2 + 4 + 8 = 14

        其他进制转换十进制都可以使用这种方式,只不过将底数的2换成对应进制的数字即可

        也可以使用8421的方式转换

        1000 --> 8

        0100 --> 4

        0010 --> 2

        0001 --> 1

2.1.3 八进制

        前导符 0 (是数字零 不是字母o)

        特点:逢8进1 每一位上的数字范围 [0,7]

        例如:0456 0666 0765

八进制转二进制:

        方法1:八先转十 然后十转二

        方法2:从右向左,1位八进制数表示 3位二进制数

        0735 -->0b111011101

二进制转八进制:

        从右向左,每3位二进制数表示 1位八进制数,高位不够补0

        0b010110101 --> 0265

2.1.4 十六进制

        前导符 0x

        特点:逢16进1 每一位上的数字范围 0~9 a10 b11 c12 d13 e14 f15

        例如:0x4F5D 0x1234 0xABCD

十六进制转二进制:

        方法1:十六先转十 然后十转二

        方法2:从右向左,1位十六进制数表示 4位二进制数

        0x45EF -->0b0100010111101111

二进制转十六进制:

        从右向左,每4位二进制数表示 1位十六进制数,高位不够补0

        0b001010110101 --> 0x2B5

注意:不管几进制的数据,在计算机中都会转换成二进制来处理。

例如:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //int 是数据类型 是用来定义变量的
    //value 是我自己起的变量名 变量可以用来存储数据
    int value;

    value = 520;//用十进制的 520 给变量赋值
    printf("十进制 %d\n", value);// %d 是十进制的占位符
    printf("八进制 %#o\n", value);// %o 是八进制的占位符 #表示输出前导符
    printf("十六进制 %#x\n", value);// %x 是十六进制的占位符 #表示输出前导符
    //printf 函数没有按照二进制输出的方式 等后面我们学了位运算 可以自己写一个

    printf("-----------------\n");

    value = 0b10100011;//用二进制数 给变量赋值
    printf("十进制 %d\n", value);
    printf("八进制 %#o\n", value);
    printf("十六进制 %#x\n", value);
    
    printf("-----------------\n");
    
    value = 0765;//用八进制数 给变量赋值
    printf("十进制 %d\n", value);
    printf("八进制 %#o\n", value);
    printf("十六进制 %#x\n", value);

    printf("-----------------\n");

    value = 0xAB45;//用十六进制数 给变量赋值
    printf("十进制 %d\n", value);
    printf("八进制 %#o\n", value);
    printf("十六进制 %#x\n", value);

    return 0;
}

 运行结果:

2.2 非数值型数据的存储

        计算机中只能处理二进制的数值型数据,而在编码的过程中,经常也会出现一些非数值型的数据

如:网址、人名、企业名 等.

        所以科学家们就发明一种叫 ascii码 的东西,专门用来表示非数值型数据

        在C语言中遇到的 所有被单引号 或者 双引号引起来的 都是非数值型数据 'm' 'H' "csdn"

        linux系统中可以使用 man ascii 来查看ascii码

常见的字符对应的ascii码:

'\0'  --> 0
'\n'  --> 10
空格 SPACE --> 32
'0'~'9' --> 48~57
'A'~'Z' --> 65~90
'a'~'z' --> 97~122

转义字符:

        在C语言中,任何一个字符都可以使用一个 '\'+八进制数 来表示

        C语言中还定义了一个 '\' + 字母 来表示一些无法显示的字符

        这些字符就叫做转义字符,因为字母已经不是本身的含义了

注意 : 转义字符虽然有两个符号 (一个反斜杠 和 一个字母),但是转义字符也是一个字符。

 三、词法符号

3.1.关键字

        所谓的关键字就是编译器已经规定好的一些单词,直接使用即可,不用提前声明或者定义。

一.数据类型关键字:
 1   char	//声明字符变量
 2   double	//声明双精度变量
 3   float	//声明浮点型变量
 4   int	    //声明整型变量
 5   short	//声明短整型变量
 6	 long	//声明长整型变量
 7	 unsigned	//声明无符号类型变量
 8	 signed	//声明有符号类型变量
 9	 struct	//声明结构体变量
 10	 union	//声明共用体或联合数据类型
 11	 void	//声明函数无返回值或无参数,声明无类型指针
 12	 enum	//声明枚举类型
二.循环语句关键字:
1、循环语句类型关键字(5个)
 1	 for	    //遍历循环
 2 	 do	    //其后紧跟循环体
 3	 while	//条件循环或死循环
 4	 break	//跳出当前循环
 5	 continue	//终止本次循环,开始下次循环
2、条件语句类型关键字(3个)
 6	 if	    //条件语句
 7	 else	//条件语句否定分支
 8	 goto	//无条件跳转语句
3、开关语句类型关键字 (3个)
 9	 switch	//用于多条件判断语句
 10	 case	//多条件判断语句分支
 11	 default	//开关语句的其它分支
三、存储类型关键字
 1	 auto	//声明自动变量
 2	 extern	//声明变量是在其他文件定义
 3	 register	//声明寄存器变量
 4	 static	//声明静态变量
四、其它关键字
 1	 onst	//声明只读变量
 2	 sizeof	//计算数据类型长度(字节数)
 3	 typedef	//给数据类型取别名
 4	 volatile	//所修饰的对象不能被编译器优化

3.2 标识符

        标识符就是我们自己起的名字,如 变量名 函数名 结构体名 共用体名 等。。。

标识符的命名规范:

        1.只能由数字、字母、或者下滑线组成

        2.不能以数字开头

        3 .不能和关键字冲突

自己起的名字尽量做到望文知意

四、数据类型

4.1 概念

        C语言的本质是操作内存。

        内存和硬盘的区别:

                内存:数据访问快 掉电丢失

                硬盘:数据访问慢 掉电不丢失

                C语言内存分配的最小单位是 字节 B

数据类型的作用:

        相当于与一个模子,告诉操作系统需要给该类型定义的变量分配多大的内存空间。

        不同的数据类型占用的内存空间大小是不一样的,

占        用的内存空间大小不同,决定了能存储的数据范围也不同。

4.2 数据类型的分类

基本类型:

        整数类型

        实型(小数)

        枚举类型

构造类型:

        数组

        结构体

        共用体

指针类型:*

空类型:void

4.3 整数类型

        整数类型又细分为 char(字符类型) short(短整型) int(整型) long(长整型) long long(长长整型)

        都是整数类型,区别是占用的内存空间大小不同,能存储的数据范围不同

        上面的每个类型又分为有符号(signed)的和无符号(unsigned)的

        对于有符号数 最高位为符号位 剩下的叫数据位

        符号位为0 正数 符号位为1 负数

        如果不写有无符号,默认的都是有符号的

4.3.1 char

        占用内存空间的大小 1字节 8bit

        存储数据的范围

                无符号:0 ~ 2^8-1

                有符号:-2^7 ~ 2^7-1

        有符号数说明:

                符号位 数据位

                7 6 5 4 3 2 1 0

                0 1 1 1 1 1 1 1 --> 2^7-1 最大值

                1 0 0 0 0 0 0 0 --> -2^7 最小值

4.3.2 short

        占用内存空间的大小 2字节 16bit

        存储数据的范围

                无符号:0 ~ 2^16-1

                有符号:-2^15 ~ 2^15-1

4.3.3 int

        占用内存空间的大小 4字节 32bit

        存储数据的范围

                无符号:0 ~ 2^32-1

                有符号:-2^31 ~ 2^31-1

4.3.4 long

        写可以写成 long int 输出占位符 %ld (小写的L)

                在32位操作系统中 和 int 一样

                在64位操作系统中 和 long long 一样

4.3.5 long long

                写可以写成 long long int 输出占位符 %lld (小写的L)

                占用内存空间的大小 8字节 64bit

                 存储数据的范围

                        无符号:0 ~ 2^64-1

                        有符号:-2^63 ~ 2^63-1

4.4 实型数据的存储

        实型数据就是小数,也叫浮点型。 如:3.14

                单精度浮点型 float 4字节

                双精度浮点型 double 8字节

                 涉及到小数的二进制,是一个拼凑的近似值

五、原码、反码、补码的问题

        数据在存储的时候设计到原码、反码和补码相互转换的问题。

                原码:是给人类看的

                反码:用来做原码和补码转换的

                补码:是给计算机看的

无符号数:原码、反码、补码都是一样的

有符号的正数:原码、反码、补码都是一样的

有符号的负数:

                反码 = 原码的符号位不变,其他位按位取反 0变1 1变0

                补码 = 反码+1

                注意:以char为例,1000 0000 规定为 -128 的补码

                小技巧:存储时看数据(正负) 取出看类型(有无符号)

#include <stdio.h>

int main(int argc, const char *argv[])
{
    unsigned char a = 10;//正常
    //存储时:
    //原码: 0000 1010
    //反码: 0000 1010
    //补码: 0000 1010
    //取出时:
    //补码: 0000 1010
    //反码: 0000 1010
    //原码: 0000 1010 --> 10
    printf("a = %d\n", a);//10

    signed char b = -10;//正常
    //存储时:
    //原码: 1000 1010
    //反码: 1111 0101
    //补码: 1111 0110
    //取出时:
    //补码: 1111 0110
    //反码: 1111 0101
    //原码: 1000 1010 --> -10
    printf("b = %d\n", b);// -10

    signed char c = 129;//异常
    //存储时:
    //原码: 1000 0001
    //反码: 1000 0001
    //补码: 1000 0001
    //取出时:
    //补码: 1000 0001
    //反码: 1000 0000
    //原码: 1111 1111 --> -127
    printf("c = %d\n", c);// -127

    unsigned char d = -1;//异常
    //存储时:
    //原码: 1000 0001
    //反码: 1111 1110
    //补码: 1111 1111
    //取出时:
    //补码: 1111 1111
    //反码: 1111 1111
    //原码: 1111 1111 --> 255
    printf("d = %d\n", d);// 255

    return 0;
}

六、常量

6.1 概念

        在整个程序运行的过程中,值不会发生变化的量。

520 1314 3.14 "csdn"

6.2 常量的分类

整型常量:

                        前导符         输出占位符         例如

        二进制         0b                 无                 0b1010

        八进制         0                 %#o                 0765

        十进制         无                 %d                 1314

        十六进制     0x                 %x                 0xAB34

浮点型常量(实型):

        float                                 %f                     3.14

        double                             %lf(小写的L)5.28

字符常量                                 %c                 'M' '8' 单引号

字符串常量                              %s                 "www.baidu.com" 双引号

指数常量                                 %e                 3.14e2 ---> 3.14 * 10^2 --> 314

标识常量         宏定义 #define

6.2.1 整型常量

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //常量一般是用来给变量赋值的
    short v1 = 520;
    printf("v1 = %d\n", v1);//520

    long int v2 = 1314;
    printf("v2 = %ld\n", v2);//1314
    
    long long int v3 = 0xAB34;
    printf("v3 = %#llx\n", v3);//0xab34

    return 0;
}

6.2.2 浮点型常量

        程序中出现的小数,都是十进制小数。

一般形式(带有小数位的):

        单进度 float 双精度 double

        3.14 5.28

指数形式

格式: [+/-]M.Ne[+/-]T

如: -3.14e-2 --> -3.14*10^(-2) --> -0.0314

#include <stdio.h>

int main(int argc, const char *argv[])
{
    float f1 = 3.1415926;
    printf("f1 = %f\n", f1);//  3.141593 默认显示6位小数 超过的部分 四舍五入
    //也可以使用 %.nf 的方式 来指定显示n位小数
    printf("f1 = %.2f\n", f1);// 3.14

    //如果是double 占位要用 %lf
    double f2 = 3.1415926;
    printf("f2 = %lf\n", f2);// 3.141593 默认也是显示6位小数
    printf("f2 = %.2lf\n", f2);// 3.14

    //指数形式
    float f3 = 314;
    printf("指数: f3 = %e\n", f3);// 3.140000e+02  可以按指数形式输出

    //也可以使用指数常量给变量赋值
    float f4 = -5.67e-3;
    printf("f4 = %f\n", f4);//-0.005670

    return 0;
}

6.2.3 字符常量

        C语言中所有的字符常量必须用 单引号 引起来,且单引号中只能引一个字符。

转义字符算一个字符

例如:'m' 'H' '8' '\n' '\0' 注意 '8' 和 8 是不一样的

输出方式:

        想输出对应的字符 %c

        想输出字符对应的ascii码 %d

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //定义一个字符类型的变量 用来保存 字符常量 'H'
    char v1 = 'A';
    printf("v1 = [%d] [%c]\n", v1, v1);// 65  A

    //常量也可以直接输出  但是一般不这样用
    printf("[%d] [%c]\n", 'B', 'B');// 66 B
    printf("[%d] [%c]\n", '\n', '\n');// 10 换行
    printf("[%d] [%c]\n", 10, 10);// 10 换行

    //字符常量也可以参与运算  --本质就是 ascii码的运算
    char v2 = 'M'+1;  //v2中实际存储的是 'N' 的ascii码
    printf("v2 = [%d] [%c]\n", v2, v2);// 78 N

    char v3 = 67;//使用字符的ascii码给变量赋值也可以
    printf("v3 = [%d] [%c]\n", v3, v3);// 67 C

    //上述例子中可以得到结论:
    //字符就是整形 整形就是字符 具体看我们怎么用

    //思考
    //如何将 'M' 转换成 'm'  ?
    char v4 = 'M';
    //v4 = v4+32;
    v4 = v4+'a'-'A';
    printf("v4 = [%d] [%c]\n", v4, v4);// 109 m
    
    //思考
    //如何将 '8' 转换成 8  ?
    char v5 = '8';
    //v5 = v5 - 48;
    v5 = v5 - '0';
    printf("v5 = %d\n", v5);//8

    return 0;
}

6.2.4 字符串常量

字符串常量需要用 双引号 "" 引起来

        "www.baidu.com" "1234"

每个字符串常量结尾都会有一个隐藏的字符 '\0' 用来标识字符串结束的

也就是说 "hello" 字符串实际要占用6个字节的内存空间

注意下面几个用法

        a 变量a

        'a' 字符a

        "a" 字符串a

将数据按照字符串方式输出,使用的占位符是 %s

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //可以将字符串直接输出
    printf("hello\n");
    printf("%s\n", "hello");

    //也可以将字符串保存在字符数组中
    char str[32] = "www.baidu.com";//--数组的用法 后面讲
    printf("str = [%s]\n", str);

    //也可以定义一个指针直接指向字符串常量
    char *p = "beijing"; //--指针的用法 后面讲
    printf("p = %s\n", p);
 
    //C语言中对字符串的处理 遇到 '\0' 就结束了
    char *p2 = "bei\0jing";
    printf("p2 = %s\n", p2);//bei

    return 0;
}

6.2 .5标识常量 --宏定义

宏定义就是给常量表达式起一个别名,使用这个名字,就相当于使用这个常量表达式,

如果常量表达式修改了,程序中所有使用这个名字的地方都会发生改变。

这个名字是一个标识符,要符合标识符的命名规范,宏定义的名字一般都是大写的。

格式:

#define  宏名  宏值

例如:

#include <stdio.h>

#define STUDENT 30  //当宏值发生变化时,所有使用宏名的地方都会变化

int main(int argc, const char *argv[])
{
    printf("当前班级已有人数 %d\n", STUDENT);
    printf("当前班级已有人数 %d\n", STUDENT);
    printf("当前班级已有人数 %d\n", STUDENT);

    return 0;
}

注意事项:

        1.宏定义是在预处理阶段完成替换的;

        2.宏定义只是一个简单的替换、是无脑替换;

#include <stdio.h>

#define M 10
#define N 20
#define SUM1 M+N
#define SUM2 (M+N)

int main(int argc, const char *argv[])
{
    int ret = SUM1*4;
    //int ret = M+N*4;
    //int ret = 10+20*4;
    printf("%d\n", ret);//90

    int ret2 = SUM2*4;
    //int ret2 = (M+N)*4;
    //int ret2 = (10+20)*4;
    printf("%d\n", ret2);//120

    return 0;
}

七、变量

7.1 概念

在程序运行的过程中,值允许发生改变的量。

变量的大小:取决于定义变量的类型

变量的名字:是一个标识符,要符合标识符命名规范

定义变量的格式:

存储类型  数据类型  变量名;

存储类型:

const static extern register volatile auto 

局部变量不写存储类型默认的都是 auto

7.2 变量的初始化和赋值

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //初始化:在定义变量的同时,给变量赋一个初值
    int v1 = 1314;
    printf("v1 = %d\n", v1);//1314

    //如果定义变量没有初始化 里面就都是随机值
    int v2;
    printf("v2 = %d\n", v2);//不确定的值

    //如果定义变量不知道用谁初始化 可以使用0来初始化
    //v3 = 520;//错误的 变量需要先定义 才能使用
    int v3 = 0;
    printf("v3 = %d\n", v3);//0

    //变量是可以被重新赋值的
    v3 = 520;// = 赋值运算符  将右边的值赋值给左边
    printf("v3 = %d\n", v3);//520
    v3 = v1;//也可以使用变量给变量赋值
    printf("v3 = %d\n", v3);//1314

    //变量之间可以做运算
    v2 = v1+v3;
    printf("v1 = %d  v2 = %d  v3 = %d\n", v1, v2, v3);//1314 2628 1314
    
    //一行中可以定义多个变量 用逗号分隔
    int v4,v5,v6;

    //同一个作用域(同一个{})内 不允许定义重名的变量
    //int v4; //错误的 重名了

    return 0;
}

73 强制类型转换

在某次运算中,通过某种方式将变量的类型转换成其他类型来参与本次运算。

变量本身的类型不会改变,只是在本次运算中使用其他类型参与运算了。

强制类型转换简称 强转

注意:强制类型转换是不安全的,要谨慎使用。

        小的类型转大的类型一般没问题

        但是大的类型转小的类型就可能会出现数据的截断和丢失

17.3.1 显式强转

格式:

(新的类型)变量名;
#include <stdio.h>

int main(int argc, const char *argv[])
{
    int m = 5;
    int n = 2;
    float ret1 = m/n;//这种写法 因为 m 和 n本身是 int类型 所以运算的结果也是int类型
    //m/n 得到的结果就是 2  相当于把2赋值给 ret1
    printf("ret1 = %f\n", ret1);//2.000000

    //将 m 和 n 的类型转换成 float 来参与本次运算  得到的结果就是 float 类型的
    //相当于 把 2.500000 赋值给 ret2
    float ret2 = (float)m/(float)n;
    printf("ret2 = %f\n", ret2);//2.500000

    //m和n的类型不会改变
    printf("m = %d  n = %d\n", m, n);//5  2

    return 0;
}

7.3.2 隐式强转

        隐式强转是编译器根据上下文自动推导的,

        如果编译器觉得本次转换是安全的,则正常执行

        如果编译器觉得是有风险的,一般会报一个警告,具体报不报取决于编译器的严谨程度。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    float f = 3.1415;
    int m = f;//这种操作是 取整 的操作  会舍弃小数部分
    printf("f = %f  m = %d\n", f, m);//3.141500  3

    return 0;
}

注意:

        有符号数和无符号数参与运算时,会将有符号数转换成无符号数参与运算。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    signed int a = -20;
    unsigned int b = 10;

    if(a+b > 0){
    printf("yes\n");//输出的是 
    }else{
    printf("no\n");
    }

    //不要尝试保存 a+b 的结果 
    //因为如果使用 signed int 保存 相当于把结果隐式强转成 有符号了
    //如果使用 unsigned int 保存 虽然结果是无符号的 
    //但是就没法说明 是在运算的过程中 将有符号数转换成 无符号数了

    //实际开发过程中不会出现这种场景

    return 0;
}

八、运算符

8.1 概念

        运算符就是一个符号,是用来多个值之间进行运算的。

        运算符可以连接多个表达式。

        所谓的表达式就是由 运算符 运算量 标点符号 等组成的一个有效序列,

        是用来说明一个运算过程的。

8.2 运算符的分类

        算数运算符: + - * / %(模除,就是取余数的意思)

        自增自减运算符:++ --

        关系运算符: > < >=

        逻辑运算符: && || !

        位运算符: & | ^ ~ >

        条件运算符(三目运算符): ?:

        赋值运算符:= += -= *= ... (复合赋值运算符)

        sizeof运算符

        逗号运算符: (,,,) ----不常用

8.3 算数运算符

        + - * / %(模除,就是取余数的意思)

        注意:模除要求左右操作数必须是整数。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int m = 10;
    int n = 3;
    printf("m + n = %d\n", m + n);
    printf("m - n = %d\n", m - n);
    printf("m * n = %d\n", m * n);
    printf("m / n = %lf\n", (double)m / (double)n);//整型做除法 需要强转
    printf("m %% n = %d\n", m % n);//想输出% 需要敲两个%

    double a = 5.28;
    double b = 3.14;
    printf("a + b = %lf\n", a + b);
    printf("a - b = %lf\n", a - b);
    printf("a * b = %lf\n", a * b);
    printf("a / b = %lf\n", a / b);
    //printf("a %% b = %lf\n", a % b);//% 要求左右操作数必须是 整数

    return 0;
}

C语言中输入的函数, scanf

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int value = 0;
    printf("请输入一个整数 : ");
    //阻塞等待用户输入
    scanf("%d", &value); // & 取地址符
    printf("你输入的数据是 : %d\n", value);

    return 0;
}

练习:

        从终端输入1个三位数 [100,999]

        输出 个位+十位+百位 求和的结果,

        如:输入:135

        则输出:1 + 3 + 5 = 9

#include <stdio.h>
int main(){
    int num = 0;
    printf("请输入一个三位数:");
    scanf("%d", &num);
    
    int g = num % 10;//个位
    int s = num % 100 / 10;//十位
    int b = num / 100;//百位
    
    printf("%d + %d + %d = %d\n", b, s, g, b+s+g);
    
    return 0;
}

8.4 自增自减运算符

++ --

a++ a = a+1

++a a = a+1

a-- a = a-1

--a a = a-1

注意:以自增运算符为例 前加加和后加加 变量的值都加了1 但是表达式的结果是不一样的。

a = 10;

b = ++a;

上述两行之后,a = 11, b = 11

a = 10;

b = a++;

上述两行之后,a = 11, b = 10

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = ++a;//++a表达式的结果 是 加之后的值
    printf("a = %d  b = %d\n", a, b);//11 11

    int m = 10;
    int n = m++;//a++表达式的结果 是 加之前的值
    printf("m = %d  n = %d\n", m, n);//11 10

    return 0;
}

                                                    

8.5 关系运算符

        > < >=

        关系运算符一般是用于比较大小关系的

        关系运算符表达式的结果是一个 bool 类型,bool类型只有两种值 0 假 非0 真

        一般多用于控制语句中

注意:

        = 和 == 的区别

        = 赋值运算符

        == 关系运算符 判断是否相等的

//if else 语句说明
if(表达式){
    代码块1;
}else{
    代码块2;
}
//执行逻辑,如果表达式为真 则执行 代码块1 否则执行代码块2

例如:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 0;
    int b = 0;
    scanf("%d%d", &a, &b);//scanf可以连续输入多个数 输入时以空格或回车区别

    //其他关系运算符 用法同理
    if(a > b){
        printf("a>b\n");
    }else{
        printf("a<=b\n");
    }

    if(a == b){
        printf("yes\n");
    }else{
        printf("no\n");
    }

    return 0;
}

8.6 逻辑运算符

        逻辑运算符是用来连接多个由关系运算符组成的表达式的。

        逻辑运算符的结果 也是一个 bool 类型。

        && 逻辑与 表示并且的意思

        逻辑与连接的多个表达式,如果都为真,整个表达式的结果才为真

        有一个表达式结果为假,整个表达式的结果就为假

        || 逻辑或 表示或者的意思

        逻辑或连接的多个表达式,有一个为真,整个表达式的结果就为真

        如果所有表达式都为假,整个表达式才为假

        ! 逻辑非 表示逻辑取反的意思

        真变假 假变真

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 0;
    int b = 0;
    scanf("%d%d", &a, &b);

#if 0
    //逻辑与
    if(a >= 0 && b >= 0){
        printf("yes\n");
    }else{
        printf("no\n");
    }
#endif

#if 0
    //逻辑或
    if(a >= 0 || b >= 0){
        printf("yes\n");
    }else{
        printf("no\n");
    }
#endif

    //逻辑非
    if( !(a>b) ){
        printf("yes\n");
    }else{
        printf("no\n");
    }

    return 0;
}

注意:

        C语言中不允许使用这样的写法 (10<x<20)

                需要改写成 (x>10 && x<20)

   逻辑运算符的短路原则:

   逻辑与的短路原则:

        逻辑与连接的多个表达式,如果遇到某个表达式为假了,

        足以判断整个表达式为假,后面的表达式就都不执行了

逻辑或的短路原则:

        逻辑或连接的多个表达式,如果遇到某个表达式为真了,

        足以判断整个表达式为真,后面的表达式就都不执行了

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = 20;
    int c = 30;
    int ret = 0;

    ret = (a>0 && b<0 && ++c);//因为b<0为假 所以++c 不执行
    printf("ret = %d  a = %d  b = %d  c = %d\n", 
            ret, a, b, c);//0 10 20 30

    a = 10;
    b = 20;
    c = 30;
    ret = 0;
    ret = (a<0 || ++b || ++c);//a<0为假 则执行++b  ++b为真 所以不执行 ++c
    printf("ret = %d  a = %d  b = %d  c = %d\n",
            ret, a, b, c);//1 10 21 30

    return 0;
}

8.7 位运算符

位运算是针对二进制而言的,位指的是bit位,但凡涉及位运算,

不管几进制的数据计算机中都会转换成二进制参与运算。

一般情况下涉及位运算使用的都是 无符号 数

如果是有符号数 可能需要考虑 原码、反码、补码转换的问题

位运算一般多用于硬件设备的控制或者标志位的控制。

& 按位与 按位运算 全1为1 有0为0

| 按位或 按位运算 有1为1 全0为0

^ 按位异或 按位运算 不同为1 相同为0

~ 按位取反 按位运算 0变1 1变0

>> 按位右移 整个数据向右移动 高位补0 舍弃低位

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    unsigned char a = 0xa5;
    unsigned char b = 0x97;
    unsigned char c = 0;

    //按位与
    c = a & b;
    //a: 10100101
    //b: 10010111
    // &
    //c: 10000101 -->0x85
    printf("a & b = %#x\n", c);//0x85

    //按位或
    c = a | b;
    //a: 10100101
    //b: 10010111
    // |
    //c: 10110111 -->0xB7
    printf("a | b = %#x\n", c);//0xB7

    //按位异或
    c = a ^ b;
    //a: 10100101
    //b: 10010111
    // ^
    //c: 00110010 -->0x32
    printf("a ^ b = %#x\n", c);//0x32
    
    //按位取反
    c = ~a;
    //a: 10100101
    // ~
    //c: 01011010 -->0x5A
    printf("~a = %#x\n", c);//0x5A
    
    //按位左移
    c = a<<2;
    //a: 10100101
    // <<2
    //c: 10010100 -->0x94
    printf("a<<2 = %#x\n", c);//0x94

    //按位右移
    c = a>>2;
    //a: 10100101
    // >>2
    //c: 00101001 -->0x29
    printf("a>>2 = %#x\n", c);//0x29

    return 0;
}

小技巧:

        1或任何数 结果都是1

        1与任何数 结果都是任何数

        0与任何数 结果都是0

        0或任何数 结果都是任何数

练习:

        使用位运算模拟控制硬件设备。

        已知:有8个led灯使用共阴极的接法连接在同一组GPIO上,初始状态未知。

灯的编号 76543210

1.将1357号灯点亮

2.将6号灯点亮

3.将5号灯熄灭

#include <stdio.h>

int main(int argc, const char *argv[])
{
    unsigned char led = 0;//初始状态全熄灭
    //1.点亮1357号灯
    //led = 0x55;//这样操作不好 会影响到其他灯的状态

    //如果不想影响其他灯 就需要用位运算了
    led = led | 0xAA;
    printf("led = %#x\n", led);//0xAA

    //2.将6号灯点亮
    //led = led | 0x40;
    led = led | (1<<6);//这样操作就无需计算数了
    printf("led = %#x\n", led);//0xEA

    //3.将5号灯熄灭
    //led = led & 0xDF;
    led = led & ~(1<<5);
    printf("led = %#x\n", led);//0xCA

    return 0;
}

8.8 条件运算符

?:

        也是C语言中唯一一个三目运算符。

格式:

        表达式1 ? 表达式2 : 表达式3;

执行逻辑:

        先执行表达式1,如果表达式1为真,则执行表达式2,否则执行表达式3.

        逻辑和if..else语句特别像。

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = 20;
    int c = 0;

    c = (a<b ? ++a : ++b);

    printf("c = %d  a = %d  b = %d\n", c, a, b);//11 11 20

    //上面的语句等价与
    if(a<b){
        c = ++a;
    }else{
        c = ++b;
    }

    return 0;
}

8.9 赋值运算符

        =

一般形式:

        左 = 右

        注意是将右边的值赋值给左边。

        注意 = 和 == 的区别。

复合赋值运算符

        += -= *= 等

        a+=b a = a+b;

        a-=b a = a - b;

其他同理。。

8.10 sizeof运算符

        sizeof是用来计算变量或者类型占用的内存空间的大小的。单位 字节

使用格式:sizeof(变量名或者类型名)  //会返回计算的结果

注意:sizeof的用法和函数调用很像,但是要切记 sizeof 不是函数调用

        例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    char a;
    short b;
    int c;
    long d;
    long long e;
    float f;
    double g;
    //64位系统sizeof返回的是一个long类型
    //32位系统sizeof返回的是一个int类型
    printf("sizeof(char) = %ld  sizeof(a) = %ld\n", sizeof(char), sizeof(a));//1
    printf("sizeof(short) = %ld  sizeof(b) = %ld\n", sizeof(short), sizeof(b));//2
    printf("sizeof(int) = %ld  sizeof(c) = %ld\n", sizeof(int), sizeof(c));//4
    printf("sizeof(long) = %ld  sizeof(d) = %ld\n", sizeof(long), sizeof(d));//4 或 8
    printf("sizeof(long long) = %ld  sizeof(e) = %ld\n", sizeof(long long),                 
    sizeof(e));//8
    printf("sizeof(float) = %ld  sizeof(f) = %ld\n", sizeof(float), sizeof(f));//4
    printf("sizeof(double) = %ld  sizeof(g) = %ld\n", sizeof(double), sizeof(g));//8

    long ret1 = sizeof(int);//可以保存sizeof计算的结果
    printf("ret1 = %ld\n", ret1);

    int ret2 = sizeof(int);//这样写也可以 会做隐式强转
    printf("ret2 = %d\n", ret2);
    
    return 0;
}

8.11 逗号运算符

         (,,,) ----不常用,了解即可

        所谓的逗号运算符,就是用逗号连接多个表达式

       执行逻辑就是从左到右依次运算,最后一个表达式的结果就是整个逗号运算符表达式的结果。

格式:

(表达式1,表达式2,表达式3,...,表达式n);

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 0;

    d = (++a, ++b, ++c);//从左到右依次运算 ++c的结果 就是整个表达式的结果
    printf("a = %d,  b = %d,  c = %d,  d= %d\n", a, b, c, d);//11 21 31 31
    
    //如果是逗号运算符 一定要用括号扩起来
    d = ++a, ++b, ++c;//这就不是逗号运算符了
    //只是从左到右依次运算  d 中保存的 ++a 的结果
    printf("a = %d,  b = %d,  c = %d,  d= %d\n", a, b, c, d);//12 22 32 12

    return 0;
}

3.12 运算符的优先级问题

如果在写复杂的表达式的时候,分不清优先级,可以使用 () 来保证正确

()的优先级是最高的,括号加多了不算错 只要写的对就可以

((5+(6*7))-8)

运算符的优先级参考下图

优先级顺口溜:

        单算移关与,异或逻条赋。

思考:

        如何交换两个变量的值。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int m = 10;
    int n = 20;

#if 0
    //方法1:三杯水交换 需要借助临时变量--最常用
    int temp = m;
    m = n;
    n = temp;
#endif

#if 0
    //方法2: 通过加减的方式实现
    //这样操作数据小的时候 可以实现 但是如果两个数都比较大
    //就可能出现数据溢出的情况
    m = m+n;
    n = m-n;
    m = m-n;
#endif

    //方法3:三次异或实现两个数的交换 没有溢出的风险,
    //且能不是用第三个变量实现两个数的交换
    m = m ^ n;
    n = m ^ n;
    m = m ^ n;

    printf("m = %d, n = %d\n", m, n);//20  10

    return 0;
}

九、C语言中常用的输入输出函数

        输入输出的方式,了解一下即可,会用就行,无需过度纠结他。

原因:

        1、实际开发的项目中,基本没有和终端的交互,一般都是和文件交互;

        2、笔试面试的过程中,基本上也不会考很多关于输入输出的方式;

        我们学习这些方法就是为了方便学习阶段看现象,从而更好的理解知识点的。

9.1 putchar/getchar

9.1.1 putchar()

        功能:向终端输出一个字符

        头文件:#include

        函数原型:int putchar(int c);

        参数: c 用来保存要输出字符的ascii码的

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //可以传 字符的 ascii码
    putchar(65);//A
    putchar(10);

    //也可以传字符常量
    putchar('B');//B
    putchar('\n');

    //也可传保存ascii码的变量
    int num = 67;
    putchar(num);//C
    putchar('\n');

    //也可以传一个表达式
    putchar('A'+3);//D
    putchar(10);
    
    return 0;
}

9.1.2 getchar

        功能:在终端获取一个字符

        头文件:#include

        函数原型:int getchar(void);

        返回值: 输入的字符

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //可以直接输出字符串常量
    //puts 是自带换行符的
    puts("hello");//hello
    
    //也可以输出保存字符串的数组
    char str[32] = "beijing";
    puts(str);//beijing

    //也可以输出指向字符串的指针
    char *p = "www.baidu.com";
    puts(p);//www.baidu.com

    //puts遇到 '\0' 就停止了 后面的字符 不再继续输出
    puts("abcd\0efg");//abcd

    return 0;
}

9.2.2 gets

        功能:在终端获取一个字符串

        头文件:#include

        函数原型:char *gets(char *s);

        参数:s 用来保存输入的字符串的缓冲区的首地址

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //注意:使用gets 要保证用来保存字符串的数组足够大 否则会出现越界访问
    char s1[32] = {0};//定义了一个数组 并全部初始化成0

    gets(s1);

    puts(s1);

    return 0;
}

9.3 printf/scanf

9.3.1 printf

        功能:按照指定的格式,向终端输出一个字符串----格式化输出

        头文件:#include

        函数原型:int printf(const char *format, ...);

        参数:

                format :格式

                        %c         输出字符

                        %d         输出整型,有符号

                        %u         输出整型,无符号

                        %o         输出八进制

                        %x         输出十六进制

                        %f         浮点型

                        %e         指数形式

                        %%         输出%

        附加格式说明:

                        l           (小写的L)输出 long 或者 long long 或者double时用的

                        #         输出前导符

                        .n         输出n位小数 n是一个数字

                        m         m是一个数字,可以用来指定输出的数据的位宽

                        0         如果数据不足指定的位宽 则使用0填充

                        -         左对齐

                        +         如果是正数 输出时显示正号 负数会忽略+

                        ... :         可变参,个数和类型取决于format

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int hour = 10;
    int min = 9;
    int sec = 5;

    printf("%d:%d:%d\n", hour, min, sec);// 10:9:5
    printf("%2d:%2d:%2d\n", hour, min, sec);// 10: 9: 5
    printf("%02d:%02d:%02d\n", hour, min, sec);// 10:09:05

    int value = 1234;
    printf("%2d\n", value);//1234  如果指定的位宽不够 以实际为准

    printf("%10d\n", value);//默认右对齐
    printf("%-10d\n", value);//左对齐

    printf("%+d\n", value);// +1234
    value = -100;
    printf("%+d\n", value);// -100  负数会忽略 +

    return 0;
}

9.3.2 scanf

        功能:按照指定的格式,在终端获取数据----格式化输入

        头文件:#include

        函数原型:int scanf(const char *format, ...);

                参数:

                        format :格式

                        和前面printf的用法基本一样。

        注意:scanf的格式不能乱写,写成什么样的格式,输入就得按照什么格式输入

                    ... : 可变参,个数和类型取决于format 如果是变量,记得要 &

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //--------------------int-----------------
#if 0
    int m = 0;
    int n = 0;
    //scanf 可以连续获取多个数据
    //输入时 使用 空格 或者 回车 分隔
    scanf("%d%d", &m, &n);
    printf("m = %d, n = %d\n", m, n);
    scanf("%d:%d", &m, &n);//如果是这样写格式 输入时就必须用 : 分隔
    //否则获取不到
    printf("m = %d, n = %d\n", m, n);
#endif

    //------------------char-----------
#if 0
    char m;
    scanf("%c", &m);
    printf("%c\n", m);
#endif

    //-------------float--------------
#if 0
    float m;
    scanf("%f", &m);
    printf("%f\n", m);
#endif

    //----------------string--------
    char str[32];
    //scanf("%s", str);//数组名就是首地址  无需加  &
    //printf("%s\n", str);
    //scanf不能输入带有空格的字符串
    //如果想输入带有空格的字符串  可以使用  gets
    //或者使用正则表达式也行 --不常用
    scanf("%[^\n]", str);//--正则表达式,了解即可
    printf("%s\n", str);
    
    return 0;
}

9.4 垃圾字符的处理

#include <stdio.h>

int main(int argc, const char *argv[])
{
    //场景1:多次连续调用 getchar() 时
    //处理方式:在每两次调用 getchar之间加一个getchar 用来吃掉垃圾字符 回车

#if 0
    //场景2:多次使用scanf连续获取字符时
    //处理方式:在每两次调用 scanf之间加一个getchar 用来吃掉垃圾字符 回车
    char a,b,c;
    scanf("%c", &a);
    getchar();
    scanf("%c", &b);
    getchar();
    scanf("%c", &c);
    printf("a = [%d] [%c]\n", a, a);
    printf("b = [%d] [%c]\n", b, b);
    printf("c = [%d] [%c]\n", c, c);
#endif
    //场景3:使用scanf 一次获取多个字符时
    char a,b,c;
    //scanf("%c%c%c", &a, &b, &c);
    
    //处理方式1:使用抑制符来处理  %*c
    //抑制符的作用是获取数据 但是 不赋值给后面的变量
    //注意:抑制符是一定要吃掉一个字符的 他并不管是不是垃圾字符
    //scanf("%c%*c%c%*c%c", &a, &b, &c);

    //处理方式2: 可以在格式中使用空格来处理--推荐的处理方式
    //空格可以吃掉多个空格 或者 回车
    scanf("%c %c %c", &a, &b, &c);
    printf("a = [%d] [%c]\n", a, a);
    printf("b = [%d] [%c]\n", b, b);
    printf("c = [%d] [%c]\n", c, c);

    //注意  %d 是不会有垃圾字符的问题的

    return 0;
}

十、分支控制语句

        C语言的程序结构一共有三种:顺序结构、分支结构(选择结构)、循环结构。

10.1 if..else 语句

10.2.1 格式及执行逻辑

1.简化格式
    if(表达式){
        代码块;
    }
    执行逻辑:
        先执行表达式,如果表达式为真,
        则执行代码块,否则就不执行代码块

    if(表达式){
        代码块1;
    }else{
        代码块2;
    }
    执行逻辑:
        先执行表达式,如果表达式为真,
        则执行代码块1,否则执行代码块2

2.阶梯形式
    if(表达式1){
        代码块1;
    }else if(表达式2){
        代码块2;
    }else if(表达式n){
        代码块n;
    }else{
    其他分支;
    }

执行逻辑:
    先执行表达式1,如果为真,则执行代码块1,
    否则执行表达式2,如果为真,则执行代码块2,
    依次类推,如果前面的表达式都为假,则执行else分支

3.嵌套格式
    if(表达式1){
        if(表达式11){
            代码块11;
    }else if(表达式12){
        代码块12;
    }else{
        其他分支1;
    }
    }else if(表达式2){
        if(表达式21){
            代码块21;
    }else if(表达式22){
        代码块22;
    }else{
        其他分支2;
    }
    }else if(表达式n){
            代码块n;
    }else{
            其他分支;
    }
执行逻辑:
    就是前面阶梯形式的嵌套使用
#include <stdio.h>

int main(int argc, const char *argv[])
{
	printf("----start----\n");
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);

#if 0
	//简化格式1
	if(a > b){
		printf("a>b\n");
	}
#endif

#if 0
	//简化格式2
	if(a > b){
		printf("a>b\n");
	}else{
		printf("a<=b\n");
	}
#endif

#if 0
	//阶梯格式
	if(a > 0 && b >0){
		printf("a b 都大于 0\n");
	}else if(a > 0){
		printf("a > 0  b <= 0\n");
	}else if(b > 0){
		printf("a <= 0  b > 0\n");
	}else{
		printf("a b 都小于等与 0\n");
	}
#endif

	//嵌套格式
	if(a > 0){
		printf("a>0\n");
		if(b>0){
			printf("b>0\n");
		}else{
			printf("b<=0\n");
		}
	}else{
		printf("a<=0\n");
		if(b>0){
			printf("b>0\n");
		}else{
			printf("b<=0\n");
		}
	}

	printf("----end----\n");
	return 0;
}

10.2.2 注意事项

        1.如果if..else语句的代码块只有一行,那么 {} 可以不用写;

注意,如果不加{} if..else 只对下面的一行指令生效

        2.else必须要有if与之对应,不能单独出现

        3.else与同一层次的前面最近的if结合

        4.关于if..else语句的表达式:

                一般情况下都是有关系运算符和逻辑运算符组成的,用来表示真假

                如果是特殊情况,注意下面的用法

                        if(a = 10)         //条件恒为真

                        if(a = 0)         //条件恒为假

        //建议:变量和常量判断相等时 将常量放在左边 这样少写等号的时候 编译会报错

                        if(a=b)          //这时表达式的结果取决于 b的值 b为0 假 b为非0 真

                        if(a)         //这时表达式的结果取决于 a的值 a为0 假 a为非0 真 等价于 if(a!=0)

                        if(!a)         //这时表达式的结果取决于 a的值 a为0 真 a为非0 假 等价于 if(a==0)

        5.下面这种写法时,两个if..else 语句之间没有任何关系

                        if(){}

                        if(){}

        6.分支控制语句只会选择一条分支执行

int a = 10;
if(a > 0){
    printf("11\n");
    a = -10;
}else if(a < 0){
    printf("22\n");
}
//上面的代码只输出 11

10.2.3 if..else 语句练习

        1.从终端输入一个字符

                如果是小写字母 转换成大写输出

                如果是大写字母 转换成小写输出

                如果是 '0'--'9' 都输出数字0

                其他字符 都输出 #

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int value = 0;
	value = getchar();

	if(value >= 65 && value <= 90){//大写
		value = value+32;
		printf("%c\n", value);
	}else if(value >= 'a' && value <= 'z'){//小写
		value = value+'A'-'a';
		printf("%c\n", value);
	}else if(value >= '0' && value <= '9'){//'0'~'9'
		value -= '0';
		printf("%d\n", value);
	}else{//其他字符
		printf("#\n");
	}

	return 0;
}
//注意:分支控制语句的代码 测试时 要把每个分支都测试到

2.在终端输入一个成绩 [0,100]

        输出成绩对应的等级

                [90, 100] A

                [80, 90) B

                [70, 80) C

                [60, 70) D

                [0, 60) 不及格

#include <stdio.h>

int main(){
    int s = 0;
    scanf("%d", &s);
    if(s>=90 && s<=100){
        printf("A\n");
    }else if(s>=80 && s<90){
        printf("B\n");
    }else if(s>=70 && s<80){
        printf("C\n");
    }else if(s>=60 && s<70){
        printf("D\n");
    }else{
        printf("不及格\n");
    }
    
    return 0;
}

3.在终端输入三角形的三边长 (整型)

        判断能否构成三角形 任意两边之和大于第三边

        如果能

        输出能构成三角形

                再判断是什么类型的三角形

                        等边                 三条边相等

                        等腰                 有两条边相等

                        直角                 勾股定理

                        普通

        如果不能

                输出不能构成三角形

#include <stdio.h>
int main(){
    int a = 0, b = 0, c = 0;
    scanf("%d%d%d", &a, &b, &c);
    
    if(a+b>c && a+c>b && b+c>a){
        printf("能构成\n");
        if(a==b && b==c && a==c){
            printf("等边\n");
        }else if(a==b || b==c || a==c){
            printf("等腰\n");
}else if(a*a+b*b==c*c || b*b+c*c==a*a || a*a+c*c==b*b ){
            printf("直角\n");
}else{
printf("普通\n");
}
    }else{
        printf("不能构成\n");
    }
    
    return 0;
}

4.输入一个年份,输出这一年是平年还是闰年

        闰年:能被4整除且不能被100整除 或者 能被400整除

                2004 闰年

                2000 闰年

                1900 平年

#include <stdio.h>

int main(){
    int year = 0;
    scanf("%d", &year);
    if((year%4==0 && year%100 != 0) || year%400==0){
        printf("闰年\n");
    }else{
        printf("平年\n");
    }
    
    return 0;
}

5.预测身高的程序

                    每个做父母的都关心自己孩子成人后的身高,

                据有关生理卫生知识与数理统计分析表明,

                影响小孩成人后身高的因素有遗传、饮食习惯与坚持体育锻炼等。

                小孩成人后身高与其父母身高和自身性别密切相关。

                父亲、母亲、孩子 身高都用double

                设faHeight为其父身高,moHeight为其母身高,基础身高预测公式为:  

                男性成人时身高 = (faHeight + moHeight) * 0.54(cm) 

                女性成人时身高 = (faHeight * 0.923 + moHeight) / 2(cm)  

                此外,如果喜爱体育锻炼,那么可在基础身高上增加身高 2%

                如果有良好的卫生饮食习惯,那么可在基础身高上增加身高1.5%

                程序要求:父亲的身高与母亲的身高、小孩的性别、是否喜爱体育锻炼

                和是否有良好的卫生饮食习惯也从键盘上输入,最终输出预测的身高。  

提示:

                     小孩性别的输入方式,

                可在屏幕给出提示“请输入小孩的性别(男孩输入1,女孩输入0):”,

                然后通过if语句来判断从键盘输入的字符是 1 还是 0。

                是否喜爱体育锻炼也可以通过类似的方式实现。

                测试:

                    180  160   男  体  习  --->190.026    %.3f

#include <stdio.h>

int main(int argc, const char *argv[])
{
	double faHeight = 0;
	double moHeight = 0;
	double childHeight = 0;
	int sex = 0;//性别
	int pe = 0;//体育
	int food = 0;//饮食
	double d_pe = 0;//体育运动的增量
	double d_food = 0;//饮食习惯的增量

	printf("请输入父亲的身高:");
	scanf("%lf", &faHeight);
	printf("请输入母亲的身高:");
	scanf("%lf", &moHeight);
	printf("请输入孩子的性别(0 女 1 男):");
	scanf("%d", &sex);
	printf("请输入是否喜欢体育运动(0 不喜欢  1 喜欢):");
	scanf("%d", &pe);
	printf("请输入是否有好的饮食习惯(0 没有  1 有):");
	scanf("%d", &food);

	//先计算基础身高
	if(0 == sex){
		childHeight = (faHeight * 0.923 + moHeight) / 2;
	}else if(1 == sex){
		childHeight = (faHeight + moHeight) * 0.54;
	}

	//计算体育运动的增量
	if(1 == pe){
		d_pe = childHeight * 0.02;
	}

	//计算饮食习惯的增量
	if(1 == food){
		d_food = childHeight * 0.015;
	}

	//计算总身高
	childHeight = childHeight + d_pe + d_food;

	printf("预测孩子的身高位:%.3lf\n", childHeight);

	return 0;
}

10.2 switch..case 语句

10.2.1 格式

witch(表达式){
case 常量表达式1:
	代码块1;
	break;
case 常量表达式2:
	代码块2;
	break;
case 常量表达式n:
	代码块n;
	break;
default:
	其他分支;
	break;
}
执行逻辑:先执行表达式,判断表达式的结果属于哪个case
属于哪个就执行哪个case的代码块

10.2.2 注意事项

        1.switch后面括号的例表达式就是我们要判断的值,每个case后面的常量表达式就是

前面表达式所有可能的结果。

        2.switch后面的表达式一般是 整型或者字符类型 注意不能是 浮点型

        3.break的作用是 执行完某一个分支后,就结束整个switch..case语句

如果没有break,程序会继续执行下面case的代码块,直到遇到break或者

整个switch..case语句结束,----case击穿

        4.default表示其他分支,如果前面的所有case都不满足,则执行default分支的代码块

作用相当于if..else语句中的 else 分支,如果不关心其他分支的情况 整个default分支可以不写

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int num = 0;
	scanf("%d", &num);

	switch(num){
	case 10:
		printf("AAAAAAAAAAA\n");
		break;
	case 20:
		printf("BBBBBBBBBBB\n");
		break;
	case 30:
		printf("CCCCCCCCCCC\n");
		break;
	default:
		printf("other\n");
		break;
	}

	printf("----end----\n");

	return 0;
}

练习:

        1.使用switch..case语句 实现简易的计算器功能: ( 实现 + - * )功能即可

                要求,在终端输入 表达式 如: 5+21 %d%c%d

                输出计算的结果 :26

#include <stdio.h>

int main(){
    int lvalue = 0;
    char operator = 0;
    int rvalue = 0;
    scanf("%d%c%d", &lvalue, &operator, &rvalue);

    switch(operator){
        case '+':    //注意:如果是字符常量 需要加 单引号
            printf("%d\n", lvalue + rvalue);
            break;
        case '-':
            printf("%d\n", lvalue - rvalue);
            break;
        case '*':
            printf("%d\n", lvalue * rvalue);
            break;
        default:
            printf("目前只支持 + - * 运算\n");
            break;
    }
    
    return 0;
}

         if..else 语句和 switch..case 语句都是用来实现分支控制的,

        if..else 语句能实现的功能 switch..case 也一定能实现

        switch..case 语句能实现的功能 if..else 也一定能实现

        一般情况下,表示范围的时候,用 if..else 方便一些

        判断某个变量值是 多少的时候用 switch..case 方便一些

2.将前面学生成绩管理的练习,使用switch..case语句实现一次

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int num = 0;
	scanf("%d", &num);

	switch(num/10){
	case 10://利用了case击穿的规则
	case 9:
		printf("A\n");
		break;
	case 8:
		printf("B\n");
		break;
	case 7:
		printf("C\n");
		break;
	case 6:
		printf("D\n");
		break;
	default:
		printf("不及格\n");
		break;
	}

	return 0;
}

十一、循环控制语句

11.1使用goto实现循环

        goto 本来是用于代码的跳转的,注意:只能在同一个函数中进行跳转。

        注意:goto对代码的可读性和逻辑性有一定的影响,使用goto要谨慎

11.1.1 goto的基本使用

格式:

    代码块1;
    goto NEXT;
    代码块2;
NEXT:    //标签
    代码块3;

执行逻辑:

        先执行代码块1,然后遇到goto 直接跳转到对应的标签向下执行,代码块2被跳过,不执行。

        标签名也是一个标识符,需要符合命名规范,一般情况下都采用大写

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    printf("1111111111\n");
goto NEXT;
    printf("2222222222\n");//不执行
    printf("3333333333\n");//不执行
    printf("4444444444\n");//不执行
NEXT:
    printf("5555555555\n");
    return 0;
}

11.1.2 使用goto实现循环

        如果想实现循环 标签应该在 goto语句的前面

        一般情况下 我们使用的循环都是有结束条件的 否则就是死循环了。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    printf("----start----\n");
    int count = 0;//防止随机值影响  此处必须要用0初始化
LOOP:
    printf("hello world\n");
    count++;
    if(count < 5){//结束条件,否则就是死循环
        goto LOOP;
    }
    printf("----end----\n");
    return 0;
}

练习:

        使用 goto 计算 1+2+3+...+100 求和的结果。

#include <stdio.h>

int main(){
	/*
   思路
    1.
	   int sum = 0;//用来保存求和的结果
	   sum = sum + 1;
	   sum = sum + 2;
	   sum = sum + 3;
	   ....
	   sum = sum + 100;

	2.
	   int sum = 0;
	   int i = 1;
	   sum = sum + i;
	   i++;
	   sum = sum + i;
	   i++;
	   sum = sum + i;
	   i++;
	   ....
	   */

	int sum = 0;//用来保存求和的结果
	int i = 1;     //一般在循环中控制循环执行次数的变量
	//我们称之为 循环变量  一般使用 i  j  k 。。即可
LOOP:
	sum = sum + i;
	i++;
	if(i <= 100){
		goto LOOP;
	}

	printf("sum = %d\n", sum);

	return 0;
}

如果练习时,死循环了 可以使用 ctrl + c 终止进程。

11.2 while循环

11.2.1 格式

while(表达式){
    代码块;
}

11.2.2 执行逻辑

        先执行表达式,如果表达式为真,则执行代码块

        然后再执行表达式,如果还为真,则继续执行代码块。。

        直到表达式为假,循环立即结束。。

        表达式的用法:和 if 语句的表达式用法一样

注意:代码块中一般要有能改变表达式真假的语句,否则就是死循环了

例:使用while循环打印5行hello world

include <stdio.h>

int main(int argc, const char *argv[])
{
	printf("----start----\n");
	int i = 0;//循环变量
	while(i < 5){
		printf("hello world\n");
		i++;
	}
	printf("----end----\n");//没有被while的{}包住的代码不受循环的影响

	return 0;
}

练习:

        1.使用 while 循环,实现 1~100 求和。

#include <stdio.h>

int main(){
    int sum = 0;
    int i = 1;
    while(i <= 100){
        sum += i;
        i++;
    }
    printf("sum = %d\n", sum);
    
    return 0;
}

2.思考:下面的代码会输出什么?

int i = 100;
while(i--);
printf("%d\n", i);//-1   因为i--表达式的结果是减之前的值
                        //所以 i-- 为0时   i的值已经是 -1 了
int i = 100;
while(--i);
printf("%d\n", i);//0  因为--i表达式的结果是减之后的值

3.猴子吃桃问题

        一只猴子第一天摘了若干个桃,

        当即就吃了一半数量的桃,没吃过瘾,又多吃了一个

        第二天,把剩下的桃又吃了一半数量,没吃过瘾,又多吃了一个

        以后的每天都按照这种方式吃桃

        到第十天,再想吃桃的时候,发现只剩下一个桃了。

                问:猴子第一天摘了多少个桃? 使用while循环解决

第10天  1 个
第9天   4 个    (1+1)*2
第8天   10 个   (4+1)*2
。。。。。

#include <stdio.h>

int main(){
    int count = 1;//记录桃子的数量
    int i = 0;//控制循环次数
    while(i < 9){
        count = (count + 1) * 2;
        i++;
    }
    printf("猴子第1天摘了 %d 个桃\n", count);
    
    return 0;
}

再要求:输出每天的桃子数量

        第10天 1 个

        第9天 4 个

        第8天 10 个

        ...

        第1天 1524个

#include <stdio.h>

int main(){
    int count = 1;//记录桃子的数量
    int i = 0;//控制循环次数
    while(i < 9){
        printf("猴子第 %-2d 天摘了 %-4d 个桃\n", 10-i, count);
        count = (count + 1) * 2;
        i++;
    }
    printf("猴子第 %-2d 天摘了 %-4d 个桃\n", 10-i, count);
        
    return 0;
}

11.3 do..while 循环

11.3.1 格式

do{ 代码块; }while(表达式); //注意:最后要有一个分号

do{
    代码块;
}while(表达式);    //注意:最后要有一个分号

11.3.2 执行逻辑

        先执行代码块,然后执行表达式,如果表达式为真,则继续执行代码块

        然后再执行表达式,如果还为真,则继续执行代码块,直到表达式为假,循环立即结束

do..while 和 while的区别

        while 先判断 后执行

         do..while 先执行 后判断

         do..while的代码块无论如何都是要至少执行一次的。

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int sum = 0;
    int i = 1;
    do{
        sum += i;
        i++;
    }while(i<=100);//别落下此处的分号

    printf("sum = %d\n", sum);

    return 0;
}

11.4 for 循环

11.4.1 格式

for(表达式1; 表达式2; 表达式3){
    代码块;
}

11.4.2 执行逻辑

        先执行表达式1,然后执行表达式2,如果表达式2为真,则执行代码块

        然后执行表达式3,然后再执行表达式2,如果还为真,则继续执行代码块和表达式3

        直到表达式2为假,循环立即结束。

        关于for循环的三个表达式:

                表达式1:只执行一次,一般是用来给循环变量赋初值的

                表达式2:和while循环的表达式一样,一般是用来判断真假确定是否继续执行循环的

                表达式3:一般是用来改变循环变量的值,从而控制循环执行次数的

                如果也许有需求,表达式1和表达式3可以使用逗号分隔 写多个

                也就是说,可以给多个变量赋初值,可以修改多个变量的值

                for循环的每个表达式 如果不需要,都可以不写,但是两个分号必须写

例:使用for循环打印5行hello world

#include <stdio.h>

int main(int argc, const char *argv[])
{
    printf("----start----\n");
    int i = 0;
    for(i = 0; i < 5; i++){
        printf("hello world\n");
    }

    printf("----end----\n");//不受循环控制

    return 0;
}

练习:

                1.使用for循环 计算 1~100 求和。

#include <stdio.h>

int main(int argc, const char *argv[])
{
    int sum = 0;
    int i = 0;
    for(i = 1; i <= 100; i++){
        sum += i;
    }
    printf("sum = %d\n", sum);

    printf("i = %d\n", i);//这里也可以访问

    return 0;
}

关于 for循环的第一个表达式内定义变量

include <stdio.h>

int main(int argc, const char *argv[])
{
	int sum = 0;
	for(int i = 1; i <= 100; i++){//C99标准的编译器允许for循环的表达式中定义变量
		//注意:在for循环的表达式1中定义的变量 
		//作用域 和 生命周期都是循环内部
		sum += i;//循环内部使用没问题
	}
	printf("sum = %d\n", sum);

	printf("i = %d\n", i);//这里就不能访问了

	return 0;
}

2.输出 [100,999] 范围内所有的水仙花数。

                水仙花数:一个三位数,如果 个*个*个 + 十*十*十 + 百*百*百 == 自身

                如: 153 == 1*1*1 + 5*5*5 + 3*3*3 = 1 +125 + 27 == 153

#include <stdio.h>

int main(){
    int num = 0;
    int g = 0;
    int s = 0;
    int b = 0;
    for(num = 100; num < 1000; num++){
        //int g = num%10;
        //int s = num/10%10;
        //int b = num/100;
        //注意:尽量不要在循环里面定义变量
        g = num%10;
        s = num/10%10;
        b = num/100;
        if(num == g*g*g + s*s*s + b*b*b){
            printf("%d 是水仙花是\n", num);
        }
    }

    return 0;
}

C语言中计算幂和开平方的函数

编译时,需要加编译选项 -lm

#include

pow 功能:计算x的y次幂

        double pow(double x, double y);

sqrt 功能:计算x的非负平方根

        double sqrt(double x);

#include <stdio.h>
#include <math.h>

int main(int argc, const char *argv[])
{
    int a = 5;
    int b = 3;
    double ret1 = pow(a, b);
    printf("ret1 = %lf\n", ret1);//125.000000

    double ret2 = sqrt(16);
    printf("ret2 = %lf\n", ret2);//4.000000

    return 0;
}

3.使用循环输出下面的图案

F
_FE
__FED
___FEDC
____FEDCB
_____FEDCBA


拆分:使用 putchar()
	1.
	_____
	_____
	_____
	_____
	_____
	_____
	2.

	_
	__
	___
	____
	_____
	3.
	FFFFFF
	_FFFFFF
	__FFFFFF
	___FFFFFF
	____FFFFFF
	_____FFFFFF
	4.
	F
	_FF
	__FFF
	___FFFF
	____FFFFF
	_____FFFFFF
	5.
	F
	_FE
	__FED
	___FEDC
	____FEDCB
	_____FEDCBA

	答案:
#include <stdio.h>

int main(int argc, const char *argv[])
{
	int i = 0;
	int j = 0;
	char value = 0;
	//外层循环控制行数
	for(j = 0; j < 6; j++){
		//这个循环用来输出 下划线
		for(i = 0; i < j; i++){
			putchar('_');
		}
		//这个循环用来输出字母
		value = 'F';//每次需要重置F的值
		for(i = 0; i <= j; i++){
			putchar(value--);
		}

		putchar(10);//换行
	}

	return 0;
}

11.5 死循环

        所谓的死循环就是条件恒为真,循环体一直执行的逻辑。

        有些业务场景需要程序一直执行,就需要用到死循环。

格式:

hile(1){   //----常用的用法
    循环体;
}

for(;;){//表达式可以不写,但是两个分号必须写
    循环体;
}

#include <stdio.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
#if 0
	while(1){
		printf("hello world\n");
		sleep(1);
	}
	printf("----end----\n");//永远不会被执行
#endif

	for(;;){
		printf("hello world\n");
		sleep(1);
	}
	printf("----end----\n");//永远不会被执行

	return 0;
}

11.6 辅助控制关键字

11.6.1 break

        break可以用在switch..case 语句中,表示执行完某个分支就立即结束整个switch..case语句

        break也可以用在循环中,表示立即结束本层循环

11.6.2 continue

        continue只能用在循环中,表示立即结束本层的本次循环

11.6.3 return

        return 如果用在函数中,表示返回函数执行的结果;

        如果用在主函数中,表示立即结束整个程序。

例:


#include <stdio.h>

int main(int argc, const char *argv[])
{
	int i = 1;
	int j = 1;
	for(i = 1; i <= 5; i++){
		for(j = 1; j <= 5; j++){
			if(3 == j){
				//break;
				//continue;
				return 0;
			}
			printf("%d--%d\n", i, j);
		}
	}

	return 0;
}

编码:

        输入一个日期:如 2022/9/30

        输出这个日期是这一年的第几天。 ----注意得考虑平年和闰年

#include <stdio.h>

//输入一个日期,输出当前日期是这一年的第几天

#define MONTH_1 31
#define MONTH_2 28
#define MONTH_3 31
#define MONTH_4 30
#define MONTH_5 31
#define MONTH_6 30
#define MONTH_7 31
#define MONTH_8 31
#define MONTH_9 30
#define MONTH_10 31
#define MONTH_11 30
#define MONTH_12 31

int main()
{
	int year, month, day;
	printf("请输入要查询的日期(年/月/日):");
	scanf("%d/%d/%d", &year, &month, &day);

	//闰年的增量  也是闰年的标志位  0  平年  1 闰年
	int leap = 0;

	//printf("%d/%d/%d\n", year, month, day);

	//判断输入的日期是否有误
	//判断年是闰年还是平年
	if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){
		leap = 1;
	}

	//判断月份输入的是否有误
	if(month < 1 || month > 12){
		printf("您输入的月份有误,请重新输入\n");
		//在main函数里面使用return,结束整个程序
		return -1;
	}

	//判断日是否有误
	//月份:1 3 5 7 8 10 12 最多31天
	if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
		if(day < 1 || day > 31){
			printf("您输入的日期有误,请重新输入\n");
			return -1;
		}
	}

	//月份:4 6 9 11 最多30天
	if(month==4 || month==6 || month==9 || month==11){
		if(day < 1 || day > 30){
			printf("您输入的日期有误,请重新输入\n");
			return -1;
		}
	}

	//月份:2 闰年最多29天 平年最多28天
	if(month == 2){
		if(leap == 1){ //闰年
			if(day < 1 || day > 29){
				printf("您输入的日期有误,请重新输入\n");
				return -1;
			} 
		}else if(leap == 0){ //平年
			if(day < 1 || day > 28){
				printf("您输入的日期有误,请重新输入\n");
				return -1;
			}
		}
	}

	int days = 0;
	switch(month){
	case 12:
		days += MONTH_11;
	case 11:
		days += MONTH_10;
	case 10:
		days += MONTH_9;
	case 9:
		days += MONTH_8;
	case 8:
		days += MONTH_7;
	case 7:
		days += MONTH_6;
	case 6:
		days += MONTH_5;
	case 5:
		days += MONTH_4;
	case 4:
		days += MONTH_3;
	case 3:
		days += MONTH_2 + leap;
	case 2:
		days += MONTH_1;
	case 1:
		days += day;
		break;
	}

	printf("%d年%d月%d日%d年的第%d天\n", year, month, day, year, days);

	return 0;
}

十二、控制语句练习

1.输出1~100范围内所有的偶数

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int i = 0;
	for(i = 1; i <= 100; i++){
		if(i%2==0){
			printf("%d\n", i);
		}
	}

	return 0;
}

2.输入一个数,输出这个数的所有因子。

        如:输入:12

        则输出:1 2 3 4 6 12

#include <stdio.h>

int main(){
    int num = 0;
    printf("请输入一个数:");
    scanf("%d", &num);
    
    int i = 0;
    for(i = 1; i <= num; i++){
        if(num%i == 0){
            printf("%d是%d的因子\n", i, num);
        }
    }
    
    return 0;
}

3.输出 [2,500] 范围内所有的完数。

        完数:除了自身之外的所有因子和还等于自身。

                6 的 所有真因子 1 2 3

                1 + 2 + 3 == 6 所以 6 就是完数

#include <stdio.h>

int main(){
    int num = 0;
    int sum = 0;//保存因子和的
    int i = 0;
    for(num = 2; num <= 500; num++){
    sum = 0; //此处的sum=0; 是必要的 因为sum中还保存着上一个数的因子和呢
        //计算num的因子和
        for(i = 1; i < num; i++){
            if(num % i == 0){
                sum += i;
            }
        }
        if(sum == num){
            printf("%d 是完数\n", num);
        }
    }
    
    return 0;
}

4. 输出 [2,100] 范围内所有的质数。

        质数:因子只有 1 和 自身 的数

        处理方式1:使用标记位来处理

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int num = 0;
	//处理方式1:使用标记位来处理
	int flag = 0;//质数的标记位 0 是质数  1  不是质数
	int i = 0;
	for(num = 2; num <= 100; num++){
		flag = 0;//使用标记位的方式 需要每次给 flag重置初始值
		//遍历 [2,num-1]
		//看看是否有一个数能把num整除 有 就说明 num不是质数
		for(i = 2; i < num; i++){
			if(num % i == 0){
				flag = 1;
				break;
			}
		}

		if(0 == flag){
			printf("%d 是质数\n", num);
		}
	}

	return 0;
}

处理方式2:根据循环结束条件的不同来处理

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int num = 0;
	int i = 0;
	for(num = 2; num <= 100; num++){
		//遍历 [2,num-1]
		//看看是否有一个数能把num整除 有 就说明 num不是质数
		for(i = 2; i < num; i++){
			if(num % i == 0){
				break;
			}
		}
		//上面的循环有两种结束条件
		//1:由于break导致的提前结束
		//说明 num%i==0 满足了 说明不是质数
		//2:由于表达式2为假导致的正常结束
		//说明 num%i==0 从头到为都没有满足过 说明是质数

		//如果循环结束时 num和i 相等 就说明 是由于第二种情况导致结束的
		if(i == num){
			printf("%d 是质数\n", num);
		}
	}

	return 0;
}

十三、数组

13.1 概念

        数组是用来保存一组相同数据类型的数据的

        数组是一个构造类型

        数组中的每个数据叫做数组的元素,或者叫做数组的成员

        数组在内存上需要分配一块连续的空间来存储数据,不管几维数组,成员之间都是连续的

13.2 一维数组

13.2.1 概念

        所谓的一维数组,就是下标只有一个的数组。

13.2.2 定义一维数组的格式

存储类型  数据类型  数组名[下标];

        存储类型:默认就是 auto

        数据类型:指的是数组中每个元素的数据类型,

                                可以是基本类型、也可以是构造类型(数组除外)

        数组名: 是一个标识符,要符合标识符的命名规范

        下标:在定义数组的时候,下标是个常量,用来表示数组的元素的个数

                在其他场景下,下标表示访问数组中的第几个元素,

                既可以是变量、也可以是常量、也可以是一个表达式

例如:

int  s[5];
//定义了一个一维数组 数组名叫s 数组中每个元素都是一个 int类型的变量
//数组中共有5个元素

18.2.3 一维数组的性质

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//定义了一个一维数组 数组名是s 数组中共有5各元素
	//每个元素都是一个int类型的变量
	int s[5];

	//数组访问元素的方式   数组名[下标]
	//注意,下标是从0开始的
	//当取出数组的元素操作时 和 操作单个的变量就是一样的了
	//给数组元素赋值
	s[0] = 100;
	s[1] = 200;
	s[2] = 300;
	s[3] = 400;
	s[4] = 500;
	//读取数组元素的值
	printf("s[0] = %d\n", s[0]);//100
	printf("s[1] = %d\n", s[1]);//200
	printf("s[2] = %d\n", s[2]);//300
	printf("s[3] = %d\n", s[3]);//400
	printf("s[4] = %d\n", s[4]);//500

	//数组名是一个常量
	//s = 1314;//错误的
	//s++;//错误的  s = s+1;

	//数组的元素在内存上是连续的
	//printf  %p 可以按照16进制打印地址编号
	// &变量名  & 取地址符  可以获取变量的地址
	printf("&s[0] = %p\n", &s[0]);//连续的
	printf("&s[1] = %p\n", &s[1]);
	printf("&s[2] = %p\n", &s[2]);
	printf("&s[3] = %p\n", &s[3]);
	printf("&s[4] = %p\n", &s[4]);

	//数组的长度:元素的个数
	//数组的大小:占用的内存空间的大小
	//一维数组的大小 = 元素的个数 * 单个元素的大小
	printf("sizeof(s) = %ld\n", sizeof(s));//20 == 5*sizeof(int)

	//数组的元素是变量 允许被重新赋值
	s[0] = 520;
	printf("s[0] = %d\n", s[0]);//520

	//下标可以是一个变量
	int m = 1;
	s[m] = 1234;
	printf("s[m] = %d\n", s[m]);//1234
	//下标可以是一个表达式
	s[s[0]-519] = 1314;
	printf("s[1] = %d\n", s[1]);//1314

	//遍历一维数组 方式1
	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	//遍历一维数组 方式
	for(i = 0; i < sizeof(s)/sizeof(s[0]); i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	return 0;
}

13.2.4 一维数组的初始化

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//如果一维数组没有初始化,里面存放的都是随机值
	//int s[5];

	//一维数组初始化的方式
	//完全初始化
	//int s[5] = {10, 20, 30, 40, 50};

	//不完全初始化
	//这时是从左到右依次初始化 没有初始化的元素 会用0初始化
	int s[5] = {10, 20};

	//全部初始化成 0
	//int s[5] = {0};  //--最常用的用法

	//省略下标的初始化
	//编译器会根据后面初始化的元素的个数 来自动计算数组的长度
	//int s[] = {10, 20, 30};

	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	//关于数组访问越界的问题
	//注意:数组越界访问的问题 编译器不会检查
	//需要程序员自己检查,而且数组越界的问题是很严重的
	//越界访问的现象是不可预知的:
	//可能正常执行
	//可能是段错误
	//可能修改了其他变量的数据
	//尤其是变量 或者 表达式做为下标使用的使用 要特别注意检查边界
	int hqyj[8] = {0};
	hqyj[12345] = 1314;//错误的操作
	printf("%d\n", hqyj[12345]);//错误的操作

	return 0;
}

练习:

        1.定义一个长度为10的int类型的一维数组

                从终端给数组的元素赋值

                找出数组中最大的值,输出最大值。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s[10] = {0};

	int i = 0;
	//循环给数组赋值
	for(i = 0; i < 10; i++){
		scanf("%d", &s[i]);
	}

	//遍历一维数组
	for(i = 0; i < 10; i++){
		printf("%d  ", s[i]);
	}
	putchar(10);

#if 0
	//找数组中的最大值
	//int max = 0;//这些操作方式 如果选了0为比较的基准
	//那么都是负数时就没法得到结果了

	//要选取数组中的元素做位比较的基准 才合理
	int max = s[0];

	for(i = 1; i < 10; i++){
		if(s[i] > max){
			max = s[i];
		}
	}
	//当循环结束的时候 max 中记录的就是最大值
	printf("max = %d\n", max);
#endif

	//保存最大值的下标
	int max_index = 0;
	for(i = 1; i < 10; i++){
		if(s[max_index] < s[i]){
			max_index = i;
		}
	}
	//当循环结束的时候 max_index 中记录的就是最大值的下标
	printf("max_value = %d\n", s[max_index]);

	return 0;
}

2.使用一维数组保存斐波那契数列的前20位,并输出。

        斐波那契数列 1 1 2 3 5 8 13 21 ....

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s[20] = {1, 1};

	int i = 0;
#if 0
	//循环给数组赋值--注意不要出现越界访问
	for(i = 2; i < 20; i++){
		s[i] = s[i-1] + s[i-2];
	}
#endif

	//循环给数组赋值--注意不要出现越界访问
	for(i = 0; i < 18; i++){
		s[i+2] = s[i+1] + s[i];
	}

	//遍历一维数组
	for(i = 0 ; i < 20; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	return 0;
}

十四、冒泡排序

14.1 基本思想

        升序:从小到大 降序:从大到小

        相邻的两个数进行比较,按照要求(升序还是降序)进行交换。

        冒泡排序的时间复杂度是 O(n^2)

11, 22, 43, 567, 1, 4, 9, 8, 55, 98

14.2 实现思路(以升序为例)

第一趟排序:

        第1元素和第2个元素进行比较,将较大的值放在第2个位置上,

        然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上,。。

        依此类推,直到第一趟排序结束,最大的元素就在整组数据的最后一个位置上了

第二趟排序:

        第1元素和第2个元素进行比较,将较大的值放在第2个位置上,

        然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上,。。

        依此类推,直到第二趟排序结束,第二大的数据就在倒数第二个位置上了

        依此类推,直到整个数据有序。

冒泡排序的动图:

14.3 代码实现

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s[10] = {11, 22, 43, 567, 1, 4, 9, 8, 55, 98};
	int len = sizeof(s)/sizeof(s[0]);//数组的长度:元素的个数
	int temp = 0;
	//排序前
	int i = 0;
	int j = 0;
	for(i = 0 ; i < 10; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

#if 0
	//一趟排序的过程
	for(i = 0; i < len-1; i++){
		if(s[i] > s[i+1]){
			//交换
			temp = s[i];
			s[i] = s[i+1];
			s[i+1] = temp;
		}
	}
#endif

	//完整的排序
	//外层循环控制排序的趟数 此处的 -1 是因为 最后一趟只有一个元素 无需再排序了
	for(j = 0; j < len-1; j++){
		//内层循环控制一趟排序
		for(i = 0; i < len-1-j; i++){
			//此处的 -1 是防止越界访问的
			//此处的 -j 是因为每趟都可以少比较一个元素
			if(s[i] > s[i+1]){//如果是降序 只需要将此处的 > 改成 < 即可
				//交换
				temp = s[i];
				s[i] = s[i+1];
				s[i+1] = temp;
			}
		}
	}

	//排序后
	for(i = 0 ; i < 10; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	return 0;
}

十五、二维数组

15.1 概念

        所谓的二维数组,就是下标有两个的数组。

15.2 定义二维数组的格式

        存储类型 数据类型 数组名[行数][列数];

注意:

        二维数组虽然有行号和列号,但是所有的元素在内存上也是连续的,

        列数:决定了按行访问元素时的跨度。

例:

int s[3][4];
//定义了一个二维数组 数组名叫s 数组中有3行4列共计12个元素
//每个元素都是一个 int 类型的变量

15.3 二维数组的性质

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//定义了一个二维数组 数组名叫s  数组中共有3行4列共计12个元素
	//每个元素都是一个 int 类型的 变量
	int s[3][4];

	//二维数组访问元素   数组名[行号][列号]
	//注意:行号和列号都是从 0 开始的
	//二维数组取出元素后的操作 和 操作单个变量也是一样的
	s[0][0] = 520;
	s[0][1] = 1314;
	s[2][2] = 1234;
	printf("s[0][0] = %d\n", s[0][0]);//520
	printf("s[0][1] = %d\n", s[0][1]);//1314
	printf("s[2][2] = %d\n", s[2][2]);//1234

	//二维数组的大小 = 行数*列数*单个元素的大小
	printf("sizeof(s) = %ld\n", sizeof(s));//48  == 3 * 4 * sizeof(int)

	//二维数组的数组名 也是一个常量
	//也不能被赋值  不能++
	//int s2[3][4];
	//s2 = s;//错误的
	//s2++;//错误的

	//二维数组的元素再内存上也是连续的
	printf("%p\n", &s[0][3]);
	printf("%p\n", &s[1][0]);//连续的 一次相差一个 int

	printf("%p\n", &s[1][3]);
	printf("%p\n", &s[2][0]);//连续的 一次相差一个 int

	//遍历二维数组
	int i = 0;
	int j = 0;
	//外层循环控制行数
	for(i = 0; i < 3; i++){
		//内层循环控制列数
		for(j = 0; j < 4; j++){
			printf("%d  ", s[i][j]);
		}
		printf("\n");
	}

	return 0;
}

15.4 二维数组的初始化

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//如果二维没有初始化 里面也都是随机值
	//int s[3][4];

	//二维数组的初始化格式
	//以行为单位
	//完全初始化
	//int s[3][4] = {{1,2,3,4},\
					 {5,6,7,8},\
				 	 {9,10,11,12}};
	//不完全初始化 没有初始化的位 也是用0初始化
	//int s[3][4] = {{1,2},\
					 {5,6},\
					 {9}};

	//不以行为单位
	//完全初始化
	//int s[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	//不完全初始化 没有初始化的位 也是用0初始化
	//int s[3][4] = {1,2,3,4,5,6};

	//全部初始化成0  --常用的用法
	//int s[3][4] = {0};

	//省略下标的初始化
	//行数可以省略 但是列数不能省略 因为列数决定按行操作时的跨度
	//如果给定的元素不够整行 也按照整行分配空间
	//没有给定的位也是用 0 初始化
	int s[][4] = {1,2,3,4,5,6,7,8,9};

	printf("sizeof(s) = %ld\n", sizeof(s));//48 说明是按照整行分配的空间

	//遍历二维数组
	int i = 0;
	int j = 0;
	for(i = 0; i < 3; i++){
		for(j = 0; j < 4; j++){
			printf("%d  ", s[i][j]);
		}
		putchar(10);
	}	

	//数组一旦定义好了就不能整体赋值了 只能一位一位的赋值

	return 0;
}

练习:

        1.定义一个3行4列的二维数组,并以行为单位完全初始化

           初始化的数据随便指定,找出数组中最大的元素,及最大元素所在的行号、列号,并输出

#include <stdio.h>

int main(){
    int s[3][4] = {{12,23,34,45},{11,22,33,44},{456,1,2,3}};
    int max_h = 0;
    int max_l = 0;
    int i = 0;
    int j = 0;
    for(i = 0; i < 3; i++){
        for(j = 0; j < 4; j++){
            if(s[i][j] > s[max_h][max_l]){
                max_h = i;
                max_l = j;
            }
        }
    }
    printf("max_value = %d  max_hang = %d max_lie = %d\n",
            s[max_h][max_l], max_h, max_l);
    
    return 0;
}

2.使用10*10的二维数组保存杨辉三角的数据 并输出。

#include <stdio.h>

#define N 10

int main(){
	int s[N][N] = {0};
	s[0][0] = 1;

	//循环给数组赋值
	int i = 0;
	int j = 0;
	for(i = 1; i < N; i++){
		s[i][0] = 1;//给每行的第一列赋值
		//其他列的值循环
		for(j = 1; j <= i; j++){
			s[i][j] = s[i-1][j] + s[i-1][j-1];
		}
	}

	//输出数组的值
	for(i = 0; i < N; i++){
		for(j = 0; j <= i; j++){
			printf("%-4d", s[i][j]);
		}
		printf("\n");
	}

	return 0;
}

十六、字符数组和字符串

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//字符数组的使用
	char s1[5] = {'h', 'e', 'l', 'l', 'o'};
	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%c", s1[i]);
	}
	printf("\n");

	//也可以将字符串保存在字符数组中
	//注意:字符串的结尾会有一个隐藏的 '\0' 所以要多分配一个字节来存储
	//格式1
	char s2[6] = {"world"};
	//格式2//--常用
	char s3[6] = "12345";
	//格式3//--常用
	char s4[] = "www.baidu.com";//这种写法 编译器计算长度时会包括 '\0'
	printf("sizeof(s4) = %ld\n", sizeof(s4));//14

	//如果是字符串 可以使用 %s 处理
	printf("s2 = [%s]\n", s2);//world
	printf("s3 = [%s]\n", s3);//12345
	printf("s4 = [%s]\n", s4);//www.baidu.com

	//注意 %s 处理的时候是从给定的首地址开始 一直向后面找
	//找到 '\0' 才结束 如果没有 '\0'就一直找  可能会越界访问
	char s5[12] = "aabb\0ccdd";
	printf("s5 = [%s]\n", s5);//aabb
	//ccdd也在数组中 只不过通过 %s 的方式 访问不到了
	printf("s5[5] = %c\n", s5[5]);//c
	printf("s5[7] = %c\n", s5[7]);//d

	//如果不是字符串 就不能使用 %s 处理
	//因为不是字符串 就没有  '\0'
	//printf("s1 = [%s]\n", s1);//错误的 错误不可预知

	//下面的用法是可以的  因为不完全初始化 会用0来初始化那些没有初始化的位
	char s6[6] = {'h', 'e', 'l', 'l', 'o'}; // 0 就是 '\0'
	printf("s6 = [%s]\n", s6);

	return 0;
}

十七、字符串处理函数

strlen strcpy strcat strcmp

17.1 strlen

        功能:

                计算字符串的长度,不包括'\0'的

        头文件:

                #include

        函数原型:

                size_t strlen(const char *s);

        参数:

                要计算长度的字符串的首地址

        返回值:

                计算的结果

例:

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

int main(int argc, const char *argv[])
{
	char hqyj[32] = "hello";

	//可以直接输出
	printf("strlen(hqyj) = %ld\n", strlen(hqyj));//5

	//也可以用变量保存计算的结果
	int len = strlen(hqyj);
	printf("len = %d\n", len);//5

	//注意 C语言中对字符串的处理 都是到 '\0' 就结束了
	char hqyj2[32] = "hello\0wrold";
	printf("strlen(hqyj2) = %ld\n", strlen(hqyj2));//5

	//注意:如果不是字符串 就不能使用 strlen 计算长度
	//因为 strlen会一直往后找 '\0'  错误是不可预知的
	char hqyj3[5] = {'h', 'e', 'l', 'l', 'o'};
	printf("strlen(hqyj3) = %ld\n", strlen(hqyj3));//5

	//弄清楚  sizeof  strlen 区别
	// 1.sizeof是关键字  strlen 是函数
	// 2.sizeof计算的是占用的内存的字节数 只和定义时有关
	// strlen计算的字符串中第一个 '\0' 之前的字符的个数
	char s[32] = "abcd";
	printf("sizeof(s) = %ld\n", sizeof(s));//32
	printf("strlen(s) = %ld\n", strlen(s));//4

	return 0;
}

自己实现 strlen 函数的功能

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char s1[32] = "hello"; //5

#if 0
	int count = 0;
	while(s1[count] != '\0'){
		count++;
	}
#endif

#if 1
	int count = 0;
	while(s1[count]){
		count++;
	}
#endif

	int count = 0;
	while(s1[count++]);
	count--;

	printf("%d\n", count);

	return 0;
}

17.2 strcpy

        功能:

                字符串的复制

        头文件:

                #include

        函数原型:

                char *strcpy(char *dest, const char *src);

        参数:

                src:源字符串

                dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界

        返回值:

                就是dest --一般不关心

例:

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

int main(int argc, const char *argv[])
{
	char s1[32] = "beijingnihao";
	char s2[32] = "hello";

	printf("操作前 s1 = [%s]\n", s1);//beijingnihao
	printf("操作前 s2 = [%s]\n", s2);//hello

	strcpy(s1, s2);//将s2 拷贝给 s1

	printf("操作后 s1 = [%s]\n", s1);//hello
	printf("操作后 s2 = [%s]\n", s2);//hello

	//将短的字符串 copy 给长的时
	//长的字符串后面剩下的部分还在里面
	//只不过通过字符串的方式 访问不到了
	printf("%c\n", s1[6]);//g
	printf("%c\n", s1[7]);//n
	printf("%c\n", s1[8]);//i

	//注意 要保证 目标字符串足够大 否则越界 错误不可预知
	//char s3[3] = {0};
	//char s4[32] = "aklhdjkaasdjfh";
	//strcpy(s3, s4);

	//字符串赋值
	char str[32] = {0};
	//str = "hello";//错误的
	strncpy(str, "hello", 6);//正确的

	return 0;
}

练习:

        自己实现 strcpy 函数的功能。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char s1[32] = "beijingnihao";
	char s2[32] = "hello";

	printf("操作前 s1 = [%s]\n", s1);
	printf("操作前 s2 = [%s]\n", s2);

	//你的操作
	int i = 0;
	while(s2[i] != '\0'){
		s1[i] = s2[i];
		i++;
	}
	//将s2结束的 '\0' 也copy给s1  下面两种写法都可以
	//s1[i] = '\0';
	s1[i] = s2[i];

	printf("操作后 s1 = [%s]\n", s1);
	printf("操作后 s2 = [%s]\n", s2);

	return 0;
}

17.3 strcat

        功能:

                字符串的拼接

        头文件:

                #include

        函数原型:

                char *strcat(char *dest, const char *src);

        参数:

                src:源字符串

                dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界

        返回值:

                就是dest --一般不关心

例:

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

int main(int argc, const char *argv[])
{
	char s1[32] = "beijingnihao";
	char s2[32] = "hello";

	printf("操作前 s1 = [%s]\n", s1);//beijingnihao
	printf("操作前 s2 = [%s]\n", s2);//hello

	strcat(s1, s2);//将s2 追加到 s1后面 会覆盖 s1的'\0'

	printf("操作后 s1 = [%s]\n", s1);//beijingnihaohello
	printf("操作后 s2 = [%s]\n", s2);//hello

	//注意 要保证 目标字符串足够大 否则越界 错误不可预知
	//char s3[3] = {0};
	//char s4[32] = "aklhdjkaasdjfh";
	//strcat(s3, s4);

	return 0;
}

17.4 strcmp

        功能:

                字符串的比较

        头文件:

                #include

        函数原型:

                int strcmp(const char *s1, const char *s2);

        参数:

                s1 和 s2 就是要参与比较的两个字符串

        比较方式:

                两个字符串逐个的比较每个字符的ascii码

                直到出现大小关系就立即返回

                两个字符串中第一次出现 '\0' 之前的字符都相等 才认为两个字符串相等

        注意:比的不是长度

                返回值:

                >0 s1>s2

                ==0 s1==s2

例:

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

int main(int argc, const char *argv[])
{
	char s1[32] = {0};
	char s2[32] = {0};

	gets(s1);
	gets(s2);

	printf("操作前 s1 = [%s]\n", s1);
	printf("操作前 s2 = [%s]\n", s2);

	int ret = strcmp(s1, s2);

	if(ret > 0){
		printf("s1>s2\n");
	}else if(ret<0){
		printf("s1<s2\n");
	}else{
		printf("s1==s2\n");
	}

	//返回值时 s1 和 s2 中第一个不相等的字符 ascii码的差值  是 s1-s2的
	//或者是0
	printf("ret = %d\n", ret);

	//带n的版本 表示只操作前n位
	char s3[32] = "hello1234";
	char s4[32] = "hello5678";
	int ret2 = strncmp(s3, s4, 5);
	printf("ret2 = %d\n", ret2);//0

	return 0;
}

练习:

1.字符串分割:从buff中解析关键信息。

char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
char logo[16];
char sender[16];
char recver[16];
char money[16];
char date[32];
char txt[128];

答案:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
	char logo[16];
	char sender[16];
	char recver[16];
	char money[16];
	char date[32];
	char txt[128];

	int i = 0;
	int j = 0;
	//解析logo
	while(buff[i] != ':'){
		logo[j++] = buff[i++];
	}
	logo[j] = '\0';

	//解析 sender
	i++;
	j = 0;
	while(buff[i] != ':'){
		sender[j++] = buff[i++];
	}
	sender[j] = '\0';

	//解析 recver
	i++;
	j = 0;
	while(buff[i] != ':'){
		recver[j++] = buff[i++];
	}
	recver[j] = '\0';

	//解析 money
	i++;
	j = 0;
	while(buff[i] != ':'){
		money[j++] = buff[i++];
	}
	money[j] = '\0';

	//解析 date
	i++;
	j = 0;
	while(buff[i] != ':'){
		date[j++] = buff[i++];
	}
	date[j] = '\0';

	//解析 txt
	i++;
	j = 0;
	while(buff[i] != 0){
		txt[j++] = buff[i++];
	}
	txt[j] = '\0';

	printf("buff = [%s]\n", buff);

	printf("logo = [%s]\n", logo);
	printf("sender = [%s]\n", sender);
	printf("recver = [%s]\n", recver);
	printf("money = [%s]\n", money);
	printf("date = [%s]\n", date);
	printf("txt = [%s]\n", txt);

	return 0;
}

        如果字符串解析再涉及bit位的操作 会更复杂些,如:判断某一位是否为1

buff[0];  //  11101010

if( (buff[0] & (1<<5)) != 0){
    //说明那一位是1
}

2.自己实现字符串转整型(atoi)

        "1314" --> 1314

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

int main(){
    char str[32] = "1314";
    int num = 0;

    //你的操作
    int i = 0;
    while(str[i] != '\0'){
        num *= 10;
        num += (str[i] - '0');
        i++;
    }
    printf("num = %d\n", num);//1314

int num2 = 0;
num2 = atoi(str);
    printf("num2 = %d\n", num2);//1314

    return 0;
}

十八、指针

18.1 概念

        内存中每个字节的空间都有一个编号,这个编号就是指针,也叫作地址。

        保存指针的变量叫做 指针变量

        在我们平时交流的时候:

                指针:指针变量

                地址:内存编号

18.2 指针相关的操作

        & 取地址符,获取变量在内存上占用的空间的地址编号

                对于多字节的变量取到的是编号最小的那个地址 叫做首地址

        * 在定义指针变量的时候,只起到标识的作用,表示定义的是一个指针变量

                其他场景下,都表示操作指针保存的那块内存空间

18.3 定义指针变量的格式

存储类型  数据类型  *指针变量名;

18.4 指针和变量的关系图

 18.5 指针的基本使用及注意事项

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//当程序执行到定义变量的语句时
	//会根据变量的类型分配对应大小的内存空间 用来存储数据
	int a = 10;

	// 通过 & 可以获取变量的地址
	// 对于多字节的变量取到的是编号最小的那个地址 叫做首地址
	printf("&a = %p\n", &a);

	//地址的范围
	//32位系统  0x0 ~ 0xFFFFFFFF 4字节
	//64位系统  0x0 ~ 0xFFFFFFFFFFFFFFFF 8字节

	//可以定义一个指针变量 来保存变量的地址
	//指针保存了变量的地址  称之位 指针 p1 指向变量a
	int *p1 = &a;  //定义了一个 int * 类型的指针变量 p1 保存a的地址
	printf("p1 = %p\n", p1);

	//指针保存了变量的地址后 就可以通过指针 操作指向的空间的内容了
	//读操作
	printf("a = %d,  *p1 = %d\n", a, *p1);//10 10
	//写操作
	*p1 = 520;
	printf("a = %d,  *p1 = %d\n", a, *p1);//520 520

	//地址本质也是一个整数
	//但是不能用普通的变量来存储地址
	//long value = &a;
	//printf("value = %p\n", value);//保存是可以保存
	//*value = 1314;//但是普通变量不能取 * 操作

	//指针类型的作用
	//决定了指针存保存的地址开始 一共能访问多少个字节
	//int * 指针  访问 4字节
	//char * 指针 访问 1字节
	//一般情况下 指针的类型要和指向的变量的类型 保持一致
	//目的是为了操作空间的大小一致

	//指针变量也是变量 值允许改变
	//指针的值改变 我们称为改变了指针的指向
	int b = 1234;
	p1 = &b;
	printf("&b = %p,  p1 = %p\n", &b, p1);

	//指针变量也需要分配内存空间
	//一级指针的地址要用二级指针来保存  ----后面讲
	printf("&p1 = %p\n", &p1);

	//常量没有地址可言
	//int *p2 = &100;//错误的

	//指针只能保存已经分配了地址:变量的地址 或者 自己malloc的地址
	//int *p3 = 0x1234;
	//printf("p3 = %p\n", p3);//0x1234  保存没问题
	//*p3 = 1314;//但是取*操作 错误是不可预知的

	//一行可以定义多个指针 但是要住用法
	//这种用法 p4 是指针  p5就是一个普通的int变量
	//int *p4,p5;
	//p4 = &a;
	//p5 = a;

	//这种写法才是正确的
	int *p4,*p5;
	p4 = &a;
	p5 = &a;

	//如果指针没有初始化 里面存的也是随机值 --就是一个随机的地址
	//这种指向不确定的指针 我们称之为  野指针
	//野指针是有害的,对野值的操作 错误是不可预知的
	//我们程序员要注意 防止野指针
	//int *p6;
	//*p6 = 1314;
	//printf("*p6 = %d\n", *p6);

	//如果定义指针时没确定指向  可以先 用 NULL 初始化指针
	//这种指针叫做 空指针
	int *p7 = NULL;// NULL 本质是 (void *)0
	//*p7 = 1314;//对 0 地址操作一定是段错误

	return 0;
}

18.6 指针的运算

        指针的运算本质就是指针保存的地址作为运算量来参与运算

        所以,能进行的运算就是有限的。

注意:只有相同类型的指针变量做运算才有意义。

                指针常见的运算:

                算数运算:+ - ++ --

                关系运算:> < == !=

                赋值运算:=

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//指针做算数运算时 一般在连续的空间上才有意义
	//两个指针做差
	//得到的结果是 相差的指针的类型的个数
	//和指针指向的变量的类型无关
	int s[5] = {10,20,30,40,50};
	int *p1 = &s[0];
	int *p2 = &s[3];
	printf("p2 - p1 = %ld\n", p2 - p1);//3 相差是3个int

	//指针的强转 是 安全的 因为指针的大小是固定的
	char *q1 = (char *)&s[0];
	char *q2 = (char *)&s[3];

	printf("p1 = %p   q1 = %p\n", p1, q1);//一样的
	printf("p2 = %p   q2 = %p\n", p2, q2);//一样的
	printf("q2 - q1 = %ld\n", q2 - q1);//12 相差是12个char


	//一个指针加上一个整数 n
	//得到的结果 是 加上 n个数据类型的大小
	//int s[5] = {10,20,30,40,50};
	int *p3 = &s[0];
	int *p4 = NULL;
	p4 = p3 + 2;
	printf("p3 = %p, p4 = %p\n", p3, p4);//相差8  2个int


	//关于++ -- 运算要注意下面的用法
	//以++为例
	int *p5 = &s[0];
	printf("p5 = %p\n", p5);//&s[0]
	int value1 = *++p5;
	printf("value1 = %d\n", value1);//20
	printf("p5 = %p\n", p5);//&s[1]

	int *p6 = &s[0];
	printf("p6 = %p\n", p6);//&s[0]
	int value2 = *p6++;
	printf("value2 = %d\n", value2);//10
	printf("p6 = %p\n", p6);//&s[1]

	//自己写代码的时候 别给自己找麻烦
	//*++p  -->  p++;  *p
	//*p++  -->  *p;  p++


	//关系运算
	//int s[5] = {10,20,30,40,50};
	int *p7 = &s[0];
	int *p8 = &s[4];
	if(p8 > p7){
		printf("yes\n");
	}else{
		printf("no\n");
	}

	//赋值运算
	p8 = p7;
	if(p8 == p7){
		printf("yes\n");
	}else{
		printf("no\n");
	}

	return 0;
}

练习:

                1.思考下面的代码会输出什么

int *p = NULL;
printf("%d  %d  %d\n", p+1, p, (p+1)-p);// 4 0 1
// p 是NULL  也就是  (void *)0       -->0
// p+1 加的一个int  也就是  4号地址   -->4
// 相差的是1个int  所以  (p+1)-p     -->1

        2.定义一个变量a 类型为int 存储数据8888,

        定义一个指针p 类型为 int * , 指向变量a

        通过指针p 将 a 的值修改为 0x12345678;

        再定义一个指针q 类型为 char *,也指向变量a

        分别使用 %#x 输出 *q *(q+1) *(q+2) *(q+3) 的值

 #include <stdio.h>
 int main(){
     int a = 8888;
     int *p = &a;
     *p = 0x12345678;
     char *q = (char *)p;
     printf("%#x %#x %#x %#x\n", *q, *(q+1), *(q+2), *(q+3));
     //0x78  0x56  0x34  0x12
     
     return 0;
 }

大小端存储问题(字节序问题):

        CPU和操作系统不同,对多字节数据存储的方式也不同。

        小端存储:地址低位存储数据低位,地址高位存储数据高位

        大端存储:地址低位存储数据高位,地址高位存储数据低位

请写一个简短的程序,来测试你使用的主机是大端存储还是小端存储?

#include <stdio.h>
int main(){
    int a = 0x12345678;
    char *p = (char *)&a;
    if(0x78 == *p){
        printf("小端\n");
    }else if(0x12 == *p){
    printf("大端\n");
    }
     
    return 0;
}

思考:

        在小端存储的主机上,下面的代码会输出什么?        

int num = 0x41424344;
printf("%s\n", &num);  //DCBA+不确定的值

18.7 指针的大小

        32位系统中 指针的大小 固定为 4字节

        64位系统中 指针的大小 固定为 8字节

#include <stdio.h>

int main(int argc, const char *argv[])
{
    char *a;
    short *b;
    int *c;
    long *d;
    long long *e;
    float *f;
    double *g;
    printf("sizeof(char *) = %ld, sizeof(a) = %ld\n", sizeof(char *), sizeof(a));//8
    printf("sizeof(short *) = %ld, sizeof(b) = %ld\n", sizeof(short *), sizeof(b));//8
    printf("sizeof(int *) = %ld, sizeof(c) = %ld\n", sizeof(int *), sizeof(c));//8
    printf("sizeof(long *) = %ld, sizeof(d) = %ld\n", sizeof(long *), sizeof(d));//8
    printf("sizeof(long long *) = %ld, sizeof(e) = %ld\n", sizeof(long long *),                 
    sizeof(e));//8
    printf("sizeof(float *) = %ld, sizeof(f) = %ld\n", sizeof(float *), sizeof(f));//8
    printf("sizeof(double *) = %ld, sizeof(g) = %ld\n", sizeof(double *), sizeof(g));//8

    return 0;
}

思考:

        在32位小端存储的主机上,下面的代码会输出什么?

int s[5] = {1,2,3,4,5};
int *p = (int *)((int)s+1);
printf("%x\n", *p);    //2000000

编码:

        有一个以空格分隔的字符串

        "this is a book"

        要求以单词为单位进行翻转

        "book a is this"

//思路:
//先整体翻转
//    "koob a si siht"
//再以单词为单位 分别翻转
//    "book a is this"

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char s[32] = {0};
	gets(s);
	printf("整体操作前:[%s]\n", s);

	//整体翻转
	int i = 0;
	int j = 0;
	int k = 0; //用来记录空格的下标
	char temp = 0;
	//先让j保存最后一个元素的下标
	while(s[j] != '\0'){
		j++;
	}
	j--;//因为上面的循环结束时  j 是 '\0' 的下标 所以得 --
	//交换前后的字符
	while(i < j){
		temp = s[i];
		s[i] = s[j];
		s[j] = temp;
		i++;
		j--;
	}
	printf("一步操作后:[%s]\n", s);

	//再单独对每个单词翻转
	i = 0;
	j = 0;
	while(s[k] != '\0'){
		while(s[j] != ' ' && s[j] != '\0'){
			j++;
		}
		k = j;//先保存一下空格的下标
		j--;
		while(i < j){
			temp = s[i];
			s[i] = s[j];
			s[j] = temp;
			i++;
			j--;
		}
		i = k+1;//重置单词的起始地址
		j = k+1;//重置单词的起始地址
	}
	printf("两步操作后:[%s]\n", s);

	return 0;
}

18.8指针和一维数组

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s1[5] = {10, 20, 30, 40, 50};

	s1[0] = 1314;

	//数组名 就是数组的首地址
	//数组名[下标]  访问成员的本质
	//就是以数组首地址为基准 偏移 然后取 * 操作
	printf("s1[0] = %d,  *s1 = %d\n", s1[0], *s1);//1314 1314
	//也就是说 数组名这个地址 的操作空间是一个元素
	printf("s1[1] = %d, *(s1+1) = %d\n", s1[1], *(s1+1));//20 20
	//s1[i] <==> *(s1+i)

	//可以定义一个指针来保存数组的首地址
	//写法如下
	int *p1 = s1; //数组名 就是首地址 这种是最常用的写法
	int *p2 = &s1[0];//这种写法也可以

	int *p3 = &s1;//注意:这种用法 保存的地址的值是一样的 
	//但是&s1 相当于给指针升维了 操作空间就变了
	//后面讲数组指针时再讲原因  记住 不要对数组名取地址!!!
	//下面5个的 值 是一样的
	printf("s1 = %p\n", s1);
	printf("&s1[0] = %p\n", &s1[0]);
	printf("p1 = %p\n", p1);
	printf("p2 = %p\n", p2);
	printf("p3 = %p\n", p3);

	//指针指向数组后 就可以通过指针来操作数组的元素了
	*p1 = 520;
	*(p1+1) = 1314;
	p1[2] = 1234;

	//指针指向数组后 有如下的等价关系
	// s1[i] <==> *(s1+i) <==> p1[i] <==> *(p1+i)


	//一位数组的遍历
	int i = 0;
	for(i = 0; i < 5; i++){
		//printf("%d  ", s1[i]);
		//printf("%d  ", *(s1+i));
		//printf("%d  ", *(p1+i));
		printf("%d  ", p1[i]);
	}
	printf("\n");


	//指针指向数组后  p1 和 s1 的区别
	//p1 是指针  是变量  可以改变指向
	int s2[5] = {10, 20, 30, 40, 50};
	p1 = s2;
	//s1 是数组名 是地址常量 不能被赋值
	//s1 = s2;//错误的

	p1++;//正确的  指针的指向向后偏移 一个 int
	s1++;//错误的 s1 是常量

	return 0;
}

18.9指针和二维数组

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s[3][4] = {{1,2,3,4},
		           {5,6,7,8},
		           {9,10,11,12}};
	//二维数组的数组名也是一个地址常量 也就是数组的首地址
	printf("s = %p\n", s);

	//研究二维数组数组名的操作空间
	printf("s+1 = %p\n", s+1);//依次相差 16  4 * sizeof(int)
	printf("s+2 = %p\n", s+2);
	//也就是说 二维数组数组名 的操作空间是一整行元素 +1 加的就是一整行
	//我们也称之为 行指针

	// * 操作可以给行指针 降维 
	// 本来操作空间是一行的指针 降维 成操作空间是一个元素的指针
	printf("*s = %p\n", *s);//依次相差 4  也就是一个int
	printf("*s+1 = %p\n", *s+1);
	printf("*s+2 = %p\n", *s+2);

	//对降维之后的指针再取 * 操作 才是操作里面的数据
	printf("*(*s+1) = %d\n", *(*s+1));//2
	printf("*(*(s+0)+1) = %d\n", *(*(s+0)+1));//2
	printf("*(*(s+2)+1) = %d\n", *(*(s+2)+1));//10

	printf("%d\n", *(s[1]+3));//8  ----是正确的用法 但是不常用

	//s[i]   <==> *(s+i)
	//s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)


	//不能使用普通的指针来保存二维数组的首地址
	int *p = s;
	printf("p = %p, s = %p\n", p, s);//两个值一样
	//但是不能这样做,原因是:
	//二维数组的数组名 是一个 行指针 操作空间 是一整行元素
	//已经超过了普通的指针的操作范围了
	//普通的指针操作空间都是一个元素

	//二维数组的遍历
	int i = 0;
	int j = 0;
	for(i = 0; i < 3; i++){
		for(j = 0; j < 4; j++){
			//printf("%d  ", s[i][j]);
			//printf("%d  ", *(s[i]+j));
			printf("%d  ", *(*(s+i)+j));
		}
		printf("\n");
	}

	return 0;
}

18.10数组指针

        本质是一个指针,指向一个二维数组,也叫作行指针。

        数组指针一般多用于函数传参,二维数组作为参数传递时。

格式:

        数据类型 (*数组指针名)[列宽];

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//可以使用二维数组来保存多个字符串
	//行号 表示能存储的字符串的个数
	//列号 表示每个字符串的最大长度
	char s[4][64] = {"zhangsan",
		"lisi",
		"xiaoming",
		"fulajier.fulajimiluoweiqi.pujing"};
	printf("%s\n", &s[0][0]);
	printf("%s\n", s[1]);
	printf("%s\n", s[2]);
	printf("%s\n", s[3]);
	//但是这种操作不太好 因为要以最长的字符串为准
	//会有空间上的浪费
	printf("sizeof(s) = %ld\n", sizeof(s));// 4 * 64 * sizeof(char) = 256


	printf("----------------------------------\n");

	//也可以使用 指针数组 来处理这种问题

	//定义了一个 指针数组 数组名叫 p 数组中共有 4个元素
	//每个元素 都是一个 char * 类型的指针
	char *p[4] = {NULL};
	p[0] = "zhangsan";
	p[1] = "lisi";
	p[2] = "xiaoming";
	p[3] = "fulajier.fulajimiluoweiqi.pujing";

	printf("%s\n", p[0]);
	printf("%s\n", *(p+1));//这种用法也可以
	printf("%s\n", p[2]);
	printf("%s\n", p[3]);

	//这种情况就只分配多个指针的空间即可
	printf("sizeof(p) = %ld\n", sizeof(p));// 4 * sizeof(char *) = 32 (64位系统)

	return 0;
}

18.11指针和字符串

虚拟内存的分区:以32位系统为例

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//可以使用字符数组保存字符串
	//这种操作 数组s1 在栈区 相当于将字符串常量区的 "hello world"
	//复制一份儿到栈区保存 通过s1操作 是操作 栈区的数据
	char s1[16] = "hello world";
	printf("s1 = %s\n", s1);//hello world

	//栈区的内存是允许修改的
	*s1 = 'H';//正确的
	printf("s1 = %s\n", s1);//Hello world

	//栈区两个数组的地址 一定是不一样的
	char s2[16] = "hello world";
	printf("s1 = %p, s2 = %p\n", s1, s2);//不一样的

	printf("-----------------------------------\n");

	//也可以直接使用指针指向字符串常量
	//但是 注意 这时 指针是指向的字符串常量区的 "hello world"
	//字符串常量区 是不允许修改的
	char *p1 = "hello world";
	printf("p1 = %s\n", p1);//hello world  读取是可以的
	//*p1 = 'H';//错误的 执行会报段错误

	//不管多少个指针 只要指向的是同一个字符串常量 保存的地址就是一样的
	char *p2 = "hello world";
	printf("p1 = %p, p2 = %p\n", p1, p2);//一样的

	return 0;
}

练习:

char a[] = "hello world";
char *p1 = a;
char *p2 = "hello world";
char *p3;

*p1 = 'M';  //正确的
char v1 = *p1++; //正确的
p1++;       //正确的 p1的指向向后偏移一个字节
(*p1)++;    //正确的 *p1 = *p1+1

*p2 = 'M';  //错误的 常量区不允许修改
char v2 = *p2++; //正确的 char v2 = *p2; p2++;
p2++;       //正确的 p1的指向向后偏移一个字节
(*p2)++;    //错误的 常量区不允许修改

*p3 = 'M';  //错误的 野指针

18.12二级指针

        二级指针是用来保存一级指针的地址的

        二级指针多用于函数传参,将一级指针的地址作为参数传递时。

格式:

        数据类型 **二级指针名;

        int a = 10;

        int *p = &a;

        int **q = &p;

        上面的代码执行完之后,有如下的等价关系

        **q *p a         //操作的都是 金色的空间

        *q p               //操作的都是 蓝色的空间

        q                    //操作的是 粉色的空间

        变量、一级指针、二级指针的关系:

例:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10;
	int *p = &a;

	//不能使用一级指针保存一级指针的地址
	//保存值可以
	//但是一级指针 没法 取  ** 操作  也就是说  无法访问 p指向的空间
	//int *value = &p;
	//printf("value = %p,  &p = %p\n", value, &p);
	//**value = 520;//错误的

	//保存一级指针的地址 要使用二级指针
	int **q = &p;

	printf("**q = %d,  *p = %d,  a = %d\n", **q, *p, a);//一样的
	printf("*q = %p, p = %p, &a = %p\n", *q, p, &a);//一样的
	printf("q = %p, &p = %p\n", q, &p);//一样的

	//二级指针的操作空间是 一个指针的大小
	printf("q = %p, q+1 = %p\n", q, q+1);//相差 8  (64位系统)

	return 0;
}

18.13const 关键字

        const用来修饰变量,表示是一个只读变量,不可以修改。--readonly

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//const 修饰变量
	const int value = 100;
	//value = 520;//错误的


	//const 用来修饰指针
	//要注意 const 和 * 的相对位置关系
	//如果 const 在 * 的左边 表示 修饰的 是 *p 
	//表示不能通过该指针修改指向的空间里的内容
	//但是指针的指向是可以修改的
	//如果 const 在 * 的右边 表示 修饰的 是 p
	//表示指针的指向是不能修改的
	//但是可以通过指针修改指向的空间里面的内容
	//
	// const int *p;
	// int const *p;
	// int * const p;
	// const int * const p;

#if 0
	//const int *p
	int a = 10;
	int b = 20;
	const int *p = &a;
	//*p = 1314;//错误的 不能通过指针修改指向空间的内容
	//a = 1234;//正确的  通过变量自身可以修改
	p = &b;//正确的  指针的指向 可以修改
#endif

	//int const *p  ----和上面的例子是一样的 一般使用上面的写法

#if 0
	//int * const p
	int a = 10;
	int b = 20;
	int * const p = &a;
	//p = &b;//错误的 指针的指向不能修改
	*p = 1314;//正确的  可以通过指针修改指向空间的内容
#endif

	// const int * const p  --都不能修改
	int a = 10;
	int b = 20;
	const int * const p = &a;
	//p = &b;//错误的
	//*p = 1314;//错误的

	return 0;
}

十九、函数

19.1 概念

        所谓的函数,就是将能实现某些功能的代码封装成代码块,当想使用这个功能的时候

        只要通过代码块的名字就可以调用这个功能,无需再写重复的代码了。

        这个代码块就叫做函数,代码块的名字就是函数名。

        如 printf scanf strlen strcat atoi 等

19.2 函数的定义和调用

返回值类型  函数名(函数的参数列表){
    //函数体 -- 也就是实现功能的代码块
}

#include <stdio.h>

//void 是函数的返回值类型  如果函数没有返回值 可以写成 void 但是不能不写
//my_function 是函数名 是一个标识符 要符合标识符命名规范
//()里面是函数的参数列表 如果函数没有参数 
//可以空着不写 或者写成void 但是注意括号必须写
//{}里面的是函数体,也就是我们用来实现功能的代码块
//return 是用来返回函数执行的结果的 如果没有返回值 可以不写 或者写成 return;
//如果有返回值  返回的类型要和 函数名前面的类型保持一致
void my_function(void){
	int i = 0;
	for(i = 0; i < 3; i++){
		printf("hello world\n");
	}
	return;
}

//一个程序是由一个main函数和若干个字函数构成的
void abcd(){
	printf("abcd\n");
	my_function();
}

int main(int argc, const char *argv[])
{
	printf("----start----\n");
	//函数的调用
	//函数一旦定义好之后 就可以被其他的函数调用了
	//调用函数的格式  函数名(参数)  如果没有参数  ()也必须写 且不能写 void
	//程序执行到调用函数的语句时 就会跳转到函数中执行 执行完函数后
	//回到调用处 继续向下执行
	my_function();

	//函数如果不被调用 是不会执行的
	abcd();

	printf("----end----\n");

	return 0;
}

执行结果:

19.3 函数的声明

        如果把所有的函数都定义在 main 函数的上面,在main函数中调用是没有问题的。

        但是函数之间相互调用就可能出现不认识的情况,这时就需要用到函数的声明。

#include <stdio.h>

//函数的声明
void my_print();
void abcd();

int main(int argc, const char *argv[])
{
    my_print();
    return 0;
}

    void my_print(){
    printf("hello world\n");
    abcd();
}

    void abcd(){
    printf("abcd\n");
}

19.4 函数的参数

        函数为什么要有参数?

        在函数中实现功能的过程中,有些值,函数内部没有,

        就需要调用者通过参数的方式给函数传递过去。

#include <stdio.h>

//void my_add(int x, int y);//也可以
void my_add(int, int);//有参数的函数声明 可以只写参数的个数和类型 不写参数名

void my_sub(const int x, const int y);//定义中形参有const 声明时也要有

int main(int argc, const char *argv[])
{
	//有参数的函数调用
	//函数的调用处的()里面的叫做函数的实际参数 --简称 实参
	my_add(10, 20);

	int a = 100;
	int b = 200;
	my_add(a, b);

	my_sub(b, a);

	return 0;
}

//在定义函数的时候 () 里面的叫做函数的形式参数 --简称形参
//在调用函数的时候,操作系统会给形参分配空间  然后用实参来初始化形参
//相当于 在调用函数的过程中 隐藏了这样的代码
//int x = 10;
//int y = 20;
//在函数中使用形参 就相当于 使用了实参的 值
//在函数中对形参的修改 并不会直接影响到实参 因为实参和形参不在同一块内存空间上
//形参的作用域和生命周期都是整个函数内部
//在函数调用结束的时候  操作系统会回收 形参占用的内存空间
void my_add(int x, int y){
	printf("%d\n", x+y);
}

//形参加const 的作用 是不允许在函数内部修改形参的值
//保护形参不被修改
//有些场景 加 const会更合理一些
void my_sub(const int x, const int y){
	printf("%d\n", x-y);
}

19.5 函数的返回值

        函数为什么要有返回值?

        有些场景,函数执行的结果,并不是说直接打印到终端就结束了。

        因为后面的代码可能会用到函数的执行结果。

        如 strlen 函数返回结果给调用者,调用者可以自己决定如果处理这个执行的结果。

        如果需要用到函数的返回值就写 如果不需要用,也可以不写。

#include <stdio.h>

int my_add(int x, int y){
	printf("my_add\n");
	int temp = x+y;
	//如果有返回值 返回的可以是变量/常量/表达式
	//要注意要和函数名前面的类型 保持一致
	return temp;
	//函数中遇到return 就立即返回 后面的代码都不执行了
	printf("----my_add end----\n");//不会被执行
}

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;

	//函数返回的结果会返回到调用处 调用者可以决定是否保存
	//以及如何处理返回值
	int ret = my_add(a, b);
	printf("ret = %d\n", ret);

	//有返回的函数 也可以不接返回值
	//不接返回值 函数也会被调用 就是没有执行结果了
	my_add(a, b);

	return 0;
}

一般情况下,定义函数都是有返回值的,可以利用函数的返回值来判断函数的执行情况。

如下面的伪代码:

//程序初始化的函数
#define ERR_NET -1
#define ERR_DATABASE -2
#define ERR_FILE -3
#define SUCCES 0
int pro_init(){
    if(连接网络失败){
        return ERR_NET;
    }
    
    if(连接数据库失败){
        return ERR_DATABASE;
    }
    
    if(打开日志文件失败){
        return ERR_FILE;
    }
    return SUCCES;
}

int main(){
    //调用初始化的函数
    int ret = pro_init();
    if(ret != SUCCES){
        执行对应的异常处理
    }
    
    return 0;
}

练习:

        自己封装一个能计算 m~n 求和的函数

        其中m和n由参数传递 通过返回值 返回计算的结果

#include <stdio.h>

/*
   功能:计算 m~n求和
   参数:
   m 起点
   n 终点
   返回值:求和的结果
   作者:yangfs
   时间:2022/10/12 11:36
   备注:xxxx
   */
int my_sum(int m, int n){
	if(m > n){
		printf("参数不合理 调用失败\n");
		return -1;
	}
	int i = 0;
	int sum = 0;
	for(i = m; i <= n; i++){
		sum += i;
	}
	return sum;
}

int main(int argc, const char *argv[])
{
	int ret = my_sum(1, 100);
	printf("ret = %d\n", ret);//5050

	return 0;
}

19.6 全局和局部

#include <stdio.h>

//生命周期:占用的空间何时被回收
//作用域:在哪个范围可以访问

//没有被任何{}包住的就叫做全局变量
//----如果初始化了就在data段 没初始化就在bss段
//生命周期:整个程序结束
//作用域:整个文件
int value3 = 300;
int value4;//全局变量不初始化也都是 0

int my_function(){
	//printf("value1 = %d\n", value1);//错误的
	int value2 = 200;
	printf("value2 = %d\n", value2);//正确的 200
	printf("func:value3 = %d\n", value3);//正确的  300
}

int main(int argc, const char *argv[])
{
	//被{}扩住的变量都叫做局部变量--在栈区
	//生命周期:就是最近的{}结束
	//作用域:当前最近的{}内部
	int value1 = 100;
	printf("value1 = %d\n", value1);//正确的  100
	//printf("value2 = %d\n", value2);//错误的
	printf("main:value3 = %d\n", value3);//正确的 300
	printf("main:value4 = %d\n", value4);//正确的  0

	my_function();

	return 0;
}

19.7 函数的传参方式

19.7.1 全局传参

        ----了解就行,不常用

#include <stdio.h>

//因为任何一个函数都可以访问全局变量
//函数调用多的时候,可能会造成冲突  --一般不是用这种方式传参
int lvalue;
int rvalue;
int ret;

void my_add(void){
	ret = lvalue + rvalue;
}

int main(int argc, const char *argv[])
{
	lvalue = 100;
	rvalue = 200;
	my_add();
	printf("ret = %d\n", ret);//300

	return 0;
}

19.7.2 复制传参(值传递)

#include <stdio.h>

//功能:将x和y扩大10被,计算的结果赋值给z
//这种传参方式 叫做复制传参(值传递)
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int z){
	x = x * 10;
	y = y * 10;
	z = x+y;
	printf("x = %d  y = %d  z = %d\n", x, y, z);//100 200 300
	printf("&x = %p  &y = %p  &z = %p\n", &x, &y, &z);//和下面不一样
}

int main(int argc, const char *argv[])
{
	int m = 10;
	int n = 20;
	int ret = 0;
	my_func(m, n, ret);
	printf("m = %d  n = %d  ret = %d\n", m, n, ret);//10 20 0
	printf("&m = %p  &n = %p  &ret = %p\n", &m, &n, &ret);//和上面不一样

	return 0;
}

19.7.3 地址传参(地址传递)

#include <stdio.h>

//函数功能:将x和y扩大10被,计算的结果赋值给 实参ret
//x和y传参方式 叫做复制传参(值传递)
//而 z 的传参方式叫做地址传参
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int *z){//z中保存的是 实参 ret 的地址
	x = x * 10;
	y = y * 10;
	*z = x+y;//对  *z 的操作 就是对 实参 ret 的操作
	printf("x = %d  y = %d  *z = %d\n", x, y, *z);//100 200 300
	printf("&x = %p  &y = %p  z = %p\n", &x, &y, z);//&x &y 和 下面 &m &n 不一样
	//但是 z 就是 &ret
}

int main(int argc, const char *argv[])
{
	int m = 10;
	int n = 20;
	int ret = 0;
	my_func(m, n, &ret);
	printf("m = %d  n = %d  ret = %d\n", m, n, ret);//10 20 300
	printf("&m = %p  &n = %p  &ret = %p\n", &m, &n, &ret);

	return 0;
}

一级指针的值传递和地址传递!

#include <stdio.h>

int m = 10;
int n = 20;

//一级指针的值传递
void my_test1(int *q){
	q = &n;
}

//一级指针的地址传递 需要用二级指针做形参
void my_test2(int **q){
	*q = &n;
}

int main(int argc, const char *argv[])
{
	int *p = &m;
	printf("%d\n", *p);// 10

	my_test1(p);
	printf("%d\n", *p);// 10

	my_test2(&p);
	printf("%d\n", *p);// 20

	return 0;
}

19.8 数组的传参方式

19.8.1 字符串的传参

        字符串传参只需要传递首地址即可,因为字符串有 '\0' 做为结束的标志

#include <stdio.h>

int my_strlen(char *p){
	*p = 'H';//数组传参 传首地址相当于 地址传递 通过指针是可以修改实参的
	//所以这种情况一般都使用 const 修饰形参的指针 防止修改
	int count = 0;
	while(*p != '\0'){
		count++;
		p++;
	}
	return count;
}

int main(int argc, const char *argv[])
{
	char s[32] = "hello world";
	int ret = my_strlen(s);
	printf("ret = %d\n", ret);
	printf("s = %s\n", s);//Hello world

	return 0;
}

19.8.2 一维整型数组传参

        既要传首地址,还要传数组的长度。

#include <stdio.h>

//遍历一维数组的函数
//首地址和长度 都要传
//
//这种写法 p 也是一个指针这种用法叫做代码的自注释
//void print_arr(int p[5], int len){//这种方式传参也可以
//void print_arr(int p[], int len){//这种方式传参也可以

void print_arr(int *p, int len){//--常用的用法
	int i = 0;
	//for(i = 0; i < sizeof(p); i++){  //这种用法不行 sizeof(p)只是一个指针的大小
	for(i = 0; i < len; i++){
		printf("%d  ", p[i]);
	}
	printf("\n");

	printf("sizeof(p) = %ld\n", sizeof(p));//8
}

int main(int argc, const char *argv[])
{
	int s[5] = {10, 20, 30, 40, 50};
	print_arr(s, 5);

	return 0;
}

19.8.3 二维数组的传参方式

#include <stdio.h>

//遍历二维数组的函数
//
//二维数组传参 需要用到 数组指针
void print_arr(int (*p)[4], int hang, int lie){
	int i = 0;
	int j = 0;
	for(i = 0; i < hang; i++){
		for(j = 0; j < lie; j++){
			printf("%d  ", p[i][j]);
		}
		putchar(10);
	}
}

int main(int argc, const char *argv[])
{
	int s[3][4] = {{1,2,3,4},
		{5,6,7,8},
		{9,10,11,12}};
	print_arr(s, 3, 4);

	return 0;
}

练习:

        封装一个能对整型一维数组的元素求和的函数,返回值返回计算的结果。

#include <stdio.h>

int my_sum(int *p, int len){
	int sum = 0;
	int i = 0;
	for(i = 0; i < len; i++){
		sum += p[i];
	}
	return sum;
}

int main(int argc, const char *argv[])
{
	int s[5] = {1,2,3,4,5};
	printf("%d\n", my_sum(s, 5));

	return 0;
}

19.9 main函数的参数

#include <stdio.h>

//argv 本质是一个指针 指向一个成员都是char * 类型的指针数组
int main(int argc, const char *argv[])
{
	//argc 是命令行执行程序时参数的个数 (包括可执行文件名)
	printf("argc = %d\n", argc);

	//如果执行 命令时 使用的是  ./a.out file1 file2
	//printf("argv[0] = %s\n", argv[0]);// "./a.out"
	//printf("argv[1] = %s\n", argv[1]);// "file1"
	//printf("argv[2] = %s\n", argv[2]);// "file2"

	//遍历命令行的所有参数
	int i = 0;
	for(i = 0; i < argc; i++){
		printf("%s\n", argv[i]);
	}

	return 0;
}

练习:

                使用命令行传参的方式实现简易的计算器功能:

                例如:输入 ./a.out 10 + 20 输出 30

                          输入 ./a.out 50 - 30 输出 20

#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]){
    int lvalue = atoi(argv[1]);
    int rvalue = atoi(argv[3]);
    //switch(argv[2][0]){//这种写法也行
    switch(*argv[2]){
        case '+':
            printf("%d\n", lvalue + rvalue);
            break;
        case '-':
            printf("%d\n", lvalue - rvalue);
            break;
    }
    return 0;
}

19.10 指针函数

        本质是一个函数,返回值是一个指针类型。

        注意:指针函数不能返回局部变量的地址

                因为函数调用结束后,局部变量占用的内存空间就被操作系统回收了

        可以返回:

                1.全局变量的地址

                2.static关键字修饰的局部变量的地址

                3.由参数传递过来的地址

#include <stdio.h>

int temp2 = 0;

int *my_add(int x, int y){
	//int temp = x+y;
	//return &temp;//错误的 不能返回局部变量的地址
#if 0
	//正确的 可以返回全局变量的地址
	temp2 = x+y;
	return &temp2;
#endif

	//static关键字可以将局部变量的生命周期延长至整个程序结束
	static int temp3 = 0;
	temp3 = x+y;//正确的 

	return &temp3;
}

char *my_strcpy(char *dest, const char *src){
	char *temp = dest;
	while(*src){
		*temp++ = *src++;
	}
	*temp = *src;
	return dest;//也可以返回由参数传递过来的地址
}


int main(int argc, const char *argv[])
{
	int *p = my_add(10, 20);
	printf("%d\n", *p);

	char s1[32] = "12345678";
	char s2[32] = "hello";
	char *q = my_strcpy(s1, s2);
	printf("%s\n", q);//hello
	printf("q = %p   s1 = %p\n", q, s1);//相等的

	return 0;
}
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到