12th day

目录

  P272 课程介绍与知识点回顾

 P273 存储字符串数据的两种方式

  1.指针和字符变量。

 2.指针与字符数组。

3.字符串数据在C语言中的存储。

        3.1 使用字符数组来存储

        3.2 使用字符指针来存储字符串数据。

 P274 内存中的五大区域

 4. 内存中的五大区域

        4.1 内存中分为五块区域.

    4.2  每1个区域是来干嘛的?为什么要分五个区域?

P275 存储字符串数据的两种方式的区别

5. 存储字符串的两种方式的区别.

    5.1. 使用字符数组来存储:

    5.2. 使用字符指针来存储:

    1. 当他们都是局部变量的时候.

   

2. 当他们作为全局变量的时候.

这两种方式的区别

    a. 以字符数组存储:

    b. 以字符指针存储

总结:可变与不可变的区别

    

1. 以字符数组存储的字符串数据,可以修改字符数组的元素.

2. 以字符指针的形式存储字符串数据.这个时候字符指针指向的字符串数据是无法修改的.

 P276 字符串的恒定性

 6. 字符串的恒定性.

        6.1. 当我们以字符指针的形式存储字符串的时候

        6.2. 当我们以字符指针的形式要将字符串数据存储到常量区的时候

        6.3 当我们重新为字符指针初始化1个字符串的时候 

        6.4 最容易懵圈的地方.

P277 面试题讲解

 思路:

建议大家使用字符指针来存储字符串数据.

        1.  以字符数组来存储字符串数据:长度固定.

        2.  以字符指针的形式存储字符串数据:长度任意.

 P278 字符串数组

    1). 声明5个字符数组或者5个字符指针.

    2). 使用一个二维的字符数组 来存储多个字符串. 每1行就是1个字符串.

     3). 使用字符指针数组来存储多个字符串数据.

 P279 字符串数组的排序

1. 字符串数组的排序.

 2. 存储指针的数组每一个元素的类型是1个指针

课堂练习:冒泡排序,按字母顺序排序字符串数组。

 P280 使用fputs函数将字符串输出到指定的流

 1. fputs();  f --> File

    1). 要使用fputs函数将字符串数据输出到标准输出流.

    2). 将字符串存储到文件中.

 P281 使用fgets函数从标准输入流中读取字符串

    1.fgets()函数.

    1.1 使用fgets函数从标准输入流中读取数据.

        为什么是安全的?

    P282 使用fgets函数从文件流中读取数据

        1.使用fgets函数从文件流中读取数据(每次读取一行):

 P283 const修饰基本数据类型和数组

 1. const是1个关键字.是来修饰我们的变量的.

 2. const修饰基本数据类型的变量.

3. const修饰数组.

 P284 const修饰指针

 4. const修饰指针;

    1). const int* p1 = #

    2).  int const * p1 = #

    3).  int  * const p1 = #

    4).  int const  * const p1 = #

 P285 const关键字的使用场景

5. const的使用场景.

    1). const的特点:

    2). 当某些数据是固定的,在整个程序运行期间都不会发生变化. 并且你也不允许别人去修改.

 P286 malloc函数

 1. 内存的五大区域.

 2. 如何向堆区申请字节空间来使用.

    1). 我们在堆中申请的字节空间.如果我们不主动释放.那么系统是不会释放到的. 除非程序结束了.

    2). 在堆中申请字节空间的步骤.       

    3). 如何在堆区申请指定字节数的字节空间呢?

    4). malloc函数.

         5). 在堆区申请的字节空间是从低地址向高地址分配.     

         6). 在堆区申请的字节,里面是有值的.值是垃圾值.不会自动清零.

         7). 在向堆区申请字节空间的时候,有可能会申请失败.

        8). 申请的空间使用完毕之后,一定要记得释放.

 P287 calloc与realloc

 3. calloc函数.

 4. realloc函数.

 P288 推箱子游戏

 P289 推箱子优化


  P272 课程介绍与知识点回顾

     1.指针:

             1.分清楚变量的值和变量的地址。

               指针就是地址。地址就是指针。

     2.指针变量.

                专门用来存储地址的变量。

     3.指针变量的声明。

                 数据类型*指针变量名;

                 int* p1;

                 1个指针变量中只能存储和这个指针变量类型相同的普通变量的地址.w

     4.指针变量的初始化。

         4.1.使用&取地址符号取出变量的地址。

         4.2.然后将这个地址赋值给指针变量。

         int num=10;

         int* p1=#     

         p1是1个指针变量,存储的num是地址。

         p1指向了num变量

     

         p1的值是:num变量的地址。

         p1自己也有地址,因为他也是1个变量。

    5.指针的使用

     使用指针去间接的访问指针指向的变量。

     *指针变量名; 代表这个指针指向的变量。

        int num =10;

        int *p1 =#

        *p1 = 100;

    6.注意的细节。

        6.1 野指针.

        6.2 NULL值。

        6.3 多个指针指向同一变量的问题。

    7.二级指针。

        一级指针:1个指针如果指向的是1个普通变量,那么这个指针就叫做一级指针。

        二级指针:1个指针指向的变量如果是1个一指针,那么这个指针就叫做二级指针。

        声明.

        *

        **

        初始化。

        int* p0 = NULL;

        int** p1 = &p0 ;

        使用.

        *p1.

        **p1

        有几颗星就拐几个弯。

     8.指针作为函数的参数.

         8.1 效果:函数内部可以直接修改实参变量的值.

         8.2 当函数需要向调用者返回多个数据的时候。

     9.指针为什么要分类型。

         9.1 任意类型的指针变量.在内存中都是占据8个字节。

         9.2 通过指针变量只能找这个地址的字节。

         int num=10;

         int* p1=#

         找到这个字节以后,是按照那1种变量来操作,是根据指针的类型决定的.

         一定要指针的类型和指针指向的变量的类型要对应。

     10.指针与整数的加减法.

         int *p1=#

         p1+1;

         结果是1个地址。是1个指针,加1 代表加1个单位变量的字节数。

         之前,通过指针访问指向的变量的时候。

         *指针变量名;

         *(指针表达式)

11.指针与数组.;

         1.数组名代表数组的地址,代表数组的第0个元素的地址

         int arr[4] ={10,20,30,40};

         int *p1=arr;//p1指向了arr数组的第0个元素.

         2.使用指针来遍历数组.  

     12.当数组作为函数的参数的时候。

           如果数组要作为函数的参数,那么就不要写出数组了 而是写1个指向数组的指针。

    13.中括号的本质。

        a.指针变量可以使用中括号

       p1[n]  =====  *(p1+n)

        arr[3];

    14.指针与指针之间的减法运算。

    15.指针与指针之间的比较运算、

 P273 存储字符串数据的两种方式

  1.指针和字符变量。

char  ch = 'a' ;
char* p1 = &ch ;
*p1 = 'b';
printf("ch = %p",ch);

 2.指针与字符数组。

char chs[] = {'a','b','c','d'};
char* p1 = chs; //p1 指针指向了chs数组的第0个元素。
*p1 = 'x';
for (int i = 0 ; i< 4 ; i++) 
{
    printf("%c\n",chs[i]);
}

  也可以使用指针来遍历数组中的每一个元素;

【chs[i]可以用以下的三种方式来替代】

*(p1+i);
*(chs+i);
*(p1++);

3.字符串数据在C语言中的存储。

        3.1 使用字符数组来存储

            将字符串数据的每一个字符,存储到字符数组中,并追加一个'\0'代表存储结束。

            char name[] = "Jack";

            只有当我们直接初始化一个字符串数组的时候,才会自动加1个'\0'。

            char name[5] = {'j','a','c','k'}这种方式不会自动

            char name[]={"jack"};只有直接给组传输数组初始化1个串的时候,才会自动追加1个'\0',前提是字符数组的长度足够的情况下。

        3.2 使用字符指针来存储字符串数据。

            直接将1个字符串数据初始化给一个字符指针。

            char* name ="jack";

           虽然我们不知道这个原理,但是有1点值得肯定。

            绝对不是将'jack'存到指针变量name中。

            name 是1个指针变量,只能存储地址。

 P274 内存中的五大区域

 4. 内存中的五大区域

        4.1 内存中分为五块区域.

        a.

           是专门用来存储局部变量的.所有的局部变量都是声明在栈区域中的.

        b.

           允许程序员手动的从堆申请空间来使用.

           int 4

           double 8

           floar 4

           char  1

           程序员自己申请指定字节数的空间.

        c. BSS段

           是用来存储未初始化的全局变量静态变量.

           声明1个全局变量.如果我们没有初始化.

           【在程序运行的最开始时候, 这个全局变量是没有初始化的.存储在BSS段】.

        d. 数据段/常量区

           用来存储已经初始化的全局变量静态变量.还有常量数据.10 20

       e. 代码段

           用来存储序的代码/指令.

    4.2  每1个区域是来干嘛的?为什么要分五个区域?

        不管是那1个区域都是来存储数据的.

        不同的数据存储在不同的区域.方便系统的管理.

P275 存储字符串数据的两种方式的区别

5. 存储字符串的两种方式的区别.

    5.1. 使用字符数组来存储:

           将字符串数据的每1个字符存储到字符数组的元素中,追加1个'\0'表示结束.

char name[] = "jack";

    5.2. 使用字符指针来存储:

           直接为字符指针初始化1个字符串数据.

char* name = "jack";

    1. 当他们都是局部变量的时候.

char name1[] = "jack";
char *name2 = "rose";

        a. name1字符数组.是申请.字符串的每一个字符存储在这个字符数组的每1个元数中.

        b. name2是1个局部的指针变量,

           name2这个指针变量是声明在栈区的.

           这个时候, 字符串数据是存储在常量区中的.

           字符串数据就是以字符数组的形式存储在常量区.

           name2指针变量中.存储的是"rose"这个字符串在常量区的地址.

int main(int argc, const char * argv[]) {

    char  name1[] ="jack";
    char* name2 ="rose";
    
    printf("name1的地址 = %p\n",name1);
    printf("name2的地址 = %p\n",name2);
    return 0;
}

输出结果: 

name1的地址 = 0x7ffeefbff73b
name2的地址 = 0x100000f67

   

2. 当他们作为全局变量的时候.

char name1[] = "jack";
char* name2 = "rose";

    a. name1字符数组是存储在常量区的. 字符串的 每1个字符是存储在这个数组中的每一个元素中.

    b. name2指针也是存储在常量区的. 字符串也是以字符数组的形式存储在常量区.

        name2指针中存储的是 "rose"这个字符串在常量区的地址.

char  name1[] ="jack";
char* name2 ="rose";

int main(int argc, const char * argv[]) {

    printf("name1的地址 = %p\n",name1);
    printf("name2的地址 = %p\n",name2);
    return 0;
}

输出结果:

name1的地址 = 0x100001018
name2的地址 = 0x100000f66

这两种方式的区别

在内存中存储的结构是不同的.

    a. 以字符数组存储:

        无论如何是1个字符数组.然后字符串的每一个字符存储在数组的元素之中.  

    b. 以字符指针存储

        无论如何首先有1个字符指针变量. 字符串数据是以字符数组的形式存储在常量区的.

总结:可变与不可变的区别

    

1. 以字符数组存储的字符串数据,可以修改字符数组的元素.

        以字符数组的形式存储字符串数据,不管是全局的还是局部的,都可以使用下标去修改字符数组中的每一个元素.

2. 以字符指针的形式存储字符串数据.这个时候字符指针指向的字符串数据是无法修改的.

        以字符指针的形式存储字符串数据,不管是全局的还是局部的,都不能通过指针去修改指向的字符串数据.

 P276 字符串的恒定性

 6. 字符串的恒定性.

        大前提: 是以字符指针形式存储的字符串.

        6.1. 当我们以字符指针的形式存储字符串的时候

                无论如何 字符串数据是存储在常量区的,

                并且 一旦存储到常量区中去. 这个字符串数据就无法更改.

        6.2. 当我们以字符指针的形式要将字符串数据存储到常量区的时候

                并不是直接将字符串存储到常量区.

                而是先检查常量区中是否有相同内容的字符串.

                如果有,直接将这个字符串的地址拿过来返回.

                如果没有,才会将这个字符串数据存储在常量区.

    char* name1 ="rose";
    char* name2 ="rose";
    char* name3 ="rose";
    
    printf("name1的地址 = %p\n",name1);
    printf("name2的地址 = %p\n",name2);
    printf("name3的地址 = %p\n",name3);

输出结果:

name1的地址 = 0x100000f56
name2的地址 = 0x100000f56
name3的地址 = 0x100000f56

        6.3 当我们重新为字符指针初始化1个字符串的时候 

                并不是修改原来的字符串. 而是重新的创建了1个字符串.

                把这个新的字符串的地址赋值给它.

    char* name1 ="rose";
    char* name2 ="rose";
    char* name3 ="rose";
    
    name3 = "jack";
    
    printf("name1的地址 = %p\n",name1);
    printf("name2的地址 = %p\n",name2);
    printf("name3的地址 = %p\n",name3);

输出结果: 

name1的地址 = 0x100000f52
name2的地址 = 0x100000f52
name3的地址 = 0x100000f57

        6.4 最容易懵圈的地方.

                a.  char *name = "jack";

                    name = "rose";  //这样是可以的.但是

                                             //不是把"jack"改成了"rose" 而是重新创建了1个"rose"

                                            //把"rose"地址赋值给name

                b.  char name[] = "jack";

                     name = "rose"; 这样是不行的. name是数组名 代表数组的地址. 不能为数组名赋值.

                    name[0] = 'r';

                    name[1] = 'o';

                    name[2] = 's';

                    name[3] = 'e';

                    name[4] = '\0'   这么做是可以的 直接修改数组的元素

P277 面试题讲解

         有1个字符串  "dhiwdhiwdniwneiwneriwneiwneiwnewnerwneriwnrenwieniwneiwneiwneiwneiwnewi";

        求这个字符串中 字符'e' 出现的次数.

 思路:

        声明1个整型的变量记数:

       遍历每1个字符.判断这个字符如果是'e' 计数器++

以字符指针存储字符串数据 和 字符数组存储字符串数据的优势.

建议大家使用字符指针来存储字符串数据.

        1.  以字符数组来存储字符串数据:长度固定.

                一旦创建以后,就最多只能存储这么多个长度的字符串数据了.

        2.  以字符指针的形式存储字符串数据:长度任意.

#include <stdio.h>

int main(int argc, const char * argv[])
{
    char *str = "edhiwdhiwdniwneiwneriwneiwneiwnewnerwneriwnrenwieniwneiwneiwneiwneiwnewi";
    int count = 0;//计数器.
    //遍历字符串中的每1个字符.
    //通过指针可以找到字符串的第1个字符.
    //那么我们就可以不断+1 找下1个字符. 直到遇到'\0'结束.
    int i = 0;
    while (str[i] != '\0') //中括号的本质.  str[i]  ====  *(str+i)
    {
        if(str[i] == 'e')
        {
            count++;
        }
        i++;
    }
    printf("count = %d\n",count);
    return 0;
}

 P278 字符串数组

 1. 我们班有1个学习小组 5个人 每1个人有自己的名字.

    要将这5个人的名字全部存储起来..

    1). 声明5个字符数组或者5个字符指针.

        char name1[] = "jack";
        char name2[] = "rose";
        char name3[] = "lily";
        ......

        char *name1 =  "jack";
        char *name2 = "rose";

        .......

        一共有5个名字,要把这5个名字全部存储起来.

    2). 使用一个二维的字符数组 来存储多个字符串. 每1行就是1个字符串.

 char  names[][10] = 
{
    jack","rose","lily"
};

        这个数组的每一行是 1个长度为10的char一维数组.最多存储长度为9的字符串.

        缺点: 每1个字符串的长度不能超过 列数-1   

     3). 使用字符指针数组来存储多个字符串数据.

        char* names[4];

        这是1个一维数组.每1个元素的类型是char指针.

        char* names[4] = {"jack","rose","lily","lilei"};

        names数组的元素的类型是char指针

        初始化给元素的字符串数据是存储在常量区的.

        元素中存储的是 字符串在常量区的地址.

        优点: 每1个字符串的长度不限制.

 注意观察下面的演示:

char* names[4] ={"rose","jack","lily","lilei"};
for (int i = 0 ; i < 4 ; i++ ) 
{
    printf("name[%d]的地址:%p  name[%d]的值:%s\n",i,names[i],i,names[i]);
}
    

输出结果:

name[0]的地址:0x100000f50  name[0]的值:rose
name[1]的地址:0x100000f55  name[1]的值:jack
name[2]的地址:0x100000f5a  name[2]的值:lily
name[3]的地址:0x100000f5f  name[3]的值:lilei

 P279 字符串数组的排序

1. 字符串数组的排序.

    将字符串数组中的每一个字符串以 字母的顺序排序.

 2. 存储指针的数组每一个元素的类型是1个指针

    而每1个指针都是占据8个字节.

    我们在求存储指针的数组的长度: 用总字节数 / 8.

课堂练习:冒泡排序,按字母顺序排序字符串数组。

    char* countries[] =
    {
        "Nepal",
        "Cambodia",
        "Afghanistan",
        "China",
        "Singapore",
        "Bangladesh",
        "India",
        "Maldives",
        "South Korea",
        "Bhutan",
        "Japan",
        "Sikkim",
        "Sri Lanka",
        "Burma",
        "North Korea",
        "Laos",
        "Malaysia",
        "Indonesia",
        "Turkey",
        "Mongolia",
        "Pakistan",
        "Philippines",
        "Vietnam",
        "Palestine"
    };
    
    int lens = sizeof(countries) / sizeof(countries[0]) ;
    
    for (int i = 0 ; i < lens-1 ; i++ ) {
        for (int j = 0 ; j < lens - 1 - i ; j++) {
            int res  = strcmp(countries[j], countries[j+1]);
            if (res > 0) {
                char* temp = countries[j] ;
                countries[j] = countries[j+1] ;
                countries[j+1] = temp ;
            }
        }
        
    }
    
    for (int i = 0; i < lens ; i++) {
        printf("%s\n",countries[i]);
    }
    
    

结果输出:

Afghanistan
Bangladesh
Bhutan
Burma
Cambodia
China
India
Indonesia
Japan
Laos
Malaysia
Maldives
Mongolia
Nepal
North Korea
Pakistan
Palestine
Philippines
Sikkim
Singapore
South Korea
Sri Lanka
Turkey
Vietnam

课堂练习2:选择排序法,按字母长度从多到少排序

int main(int argc, const char * argv[]) {
    char* countries[] =
    {
        "Nepal",
        "Cambodia",
        "Afghanistan",
        "China",
        "Singapore",
        "Bangladesh",
        "India",
        "Maldives",
        "South Korea",
        "Bhutan",
        "Japan",
        "Sikkim",
        "Sri Lanka",
        "Burma",
        "North Korea",
        "Laos",
        "Malaysia",
        "Indonesia",
        "Turkey",
        "Mongolia",
        "Pakistan",
        "Philippines",
        "Vietnam",
        "Palestine"
    };
    
    int lens = sizeof(countries) / sizeof(countries[0]) ;
    
    for (int i = 0 ; i < lens-1 ; i++ ) {
        for (int j = i + 1 ; j < lens ; j++) {
            unsigned long len1 = strlen(countries[i]);
            unsigned long len2 = strlen(countries[j]);
            
            if (len1 < len2) {
                char* temp = countries[i] ;
                countries[i] = countries[j] ;
                countries[j] = temp ;
            }
        }
        
    }
    
    for (int i = 0; i < lens ; i++) {
        printf("%s\n",countries[i]);
    }
    
    

输出结果:

Afghanistan
South Korea
North Korea
Philippines
Bangladesh
Singapore
Indonesia
Sri Lanka
Palestine
Maldives
Mongolia
Pakistan
Cambodia
Malaysia
Vietnam
Turkey
Bhutan
Sikkim
Burma
India
Japan
Nepal
China
Laos

 P280 使用fputs函数将字符串输出到指定的流

 1. fputs();  f --> File

    作用: 将字符串数据 输出到 指定的流中.

         流: 标准输出流  -->  控制台.

                  文件流     -->  磁盘上的文件.

    使用格式:

    fputs(要输出的字符串,指定的流);

    1). 要使用fputs函数将字符串数据输出到标准输出流.

        标准输出流: 控制台. stdout

    2). 将字符串存储到文件中.

        a. 要先声明1个文件指针.指向磁盘上的文件.

           fopen函数可以创建1个指向文件的指针.

           fopen函数的两个参数:

           第1个参数: 文件的路径,代表创建的指针就指向这个文件.

           第2个参数: 操作文件的模式. 你要对这个文件做什么操作.必须要告诉他.

                    "w"  --> write  代表你要向这个文件写入内容.

                    "r"  --> read   代表你从这个文件中读取数据.

                    "a"  --> apped  追加 代表你向这个文件中追加数据

           当操作模式是"w"的时候,如果文件不存在. 就会创建这个文件.

                              如果文件存在. 就会将原来的文件替换掉.

           当操作模式是"a"的时候.如果文件存在,则追加.如果不存在就创建这个文件. 

        b. 使用fputs函数将字符串写入到指定的文件流中.

            fputs(字符串,文件指针);

        c. 写完之后,一定要记得使用fclose()函数 将这个文件关闭.

    char* name ="千帆过尽,方解油盐非易事,\n青丝渐白,才知岁月不饶人。\n年近四十志渐衰,久叹青春不重来。\n东拼西闯无一获,额头青丝一半白。\n目神散面染靂,满腹累赘腿难抬。\n岁月赠我百斤肉,可曾留下一两财。";
    fputs(name, stdout);
    FILE* pFile = fopen("/Users/babylon/Desktop/t.txt","w");
    fputs(name, pFile);
    fclose(pFile);
    

控制台输出如下: 

千帆过尽,方解油盐非易事,
青丝渐白,才知岁月不饶人。
年近四十志渐衰,久叹青春不重来。
东拼西闯无一获,额头青丝一半白。
目神散面染靂,满腹累赘腿难抬。
岁月赠我百斤肉,可曾留下一两财。

"/Users/babylon/Desktop/t.txt” 文件也创建成功。

 P281 使用fgets函数从标准输入流中读取字符串

    1.fgets()函数.

    作用: 从指定的流中读取字符串.

         这个流可以是我们的标准输入流-->控制台.

             也可以是我们的文件流.

    1.1 使用fgets函数从标准输入流中读取数据.

        使用fgets函数从控制台接收用户输入字符串.

        前面学习的scanf函数gets函数也可以实现这个功能.

        scanf的缺点:

        a. 不安全.

        b. 输入的空格会被认为结束.

        gets函数的缺点.

        a. 不安全.

        fgets函数的优点

        a. 安全

        b. 空格也会一并接收.

        语法:  fgets(要将字符串存储到哪1个数组中,最多接收多少个长度的字符串,指定流);

        第2个参数: 我们写1个n 那么函数最多就接收n-1个长度的字符串.

                  这个参数一般情况下和第1个参数数组的长度一致.

        stdin: 代表标准输入流. 也就是键盘流 控制台输入.

char input[10];
printf("请输入:\n");
fgets(input, 10, stdin);
printf("您输入的是%s\n",input);

        为什么是安全的?

        a. 如果我们输入的字符串的长度大于等于了 第2个参数n. 只会接收前面的n-1个. 然后最后1个自动是'\0'.

           这样,就不会崩了.

        b. 如果我们输入的字符串的长度刚好等于n-1 那就是最完美的.

        c. 如果我们输入的字符串的长度小于了n-1. 那么就会将我们最后输入的换行字符'\n'一并的接收.然后后面才是'\0'结束符.

解决方案: 输入完毕之后,判断字符数组中存储的字符串最后1个是不是'\n'

                 如果是'\n' 那么就将其替换为'\0'.

char input[5];
printf("请输入:\n");
fgets(input, 5, stdin);
size_t len = strlen(input);
if (input[len-1] == '\n')
{
    input[len-1] = '\0';
}
printf("您输入的是%s\n",input);

    P282 使用fgets函数从文件流中读取数据

        1.使用fgets函数从文件流中读取数据(每次读取一行):

        就是读取磁盘上文件的内容.

        a. 先创建1个文件指针.

             //1. 创建1个读取文件的文件流.

             FILE* pFile = fopen("/Users/Itcast/Desktop/abc.txt", "r");

        b. 准备1个字符数组.准备存储读取到的字符串数据.            

             char content[50];

        c. 使用fgets函数从指定的文件流中读取.

            fgets(content, 50, pFile);

        d. 读取成功:

            printf("读取的内容是: %s\n",content);

        e. 关闭文件流

            fclose(pFile);

FILE* pFile = fopen("/Users/babylon/Desktop/t.txt", "r");
char content[256];
while (fgets(content, 256, pFile) != NULL)
{
    printf("%s",content);
}
fclose(pFile);
    

输出结果: 

 P283 const修饰基本数据类型和数组

 1. const是1个关键字.是来修饰我们的变量的.

    也就是说在声明变量的同时,可以使用const关键字来修饰.

    const int num = 10;

    一般情况下来说,被const修饰的变量具备一定程度上的不可变性.

    被const修饰的变量我们叫做只读变量.

 2. const修饰基本数据类型的变量.

    基本数据类型: int、double、float、char.

       1). const int num = 10;

        这个时候.num变量的值只能去取值,而不能去修改.

 

Read-only variable is not assignable : 只读变量不可赋值

    2). int const num = 10;

          效果同上.

3. const修饰数组.

    1). const int arr[4] = {10,20,30,40};

        数组的元素的值不能修改.

     2). int const arr[4] = {10,20,30,40};

        效果同上.

 P284 const修饰指针

 4. const修饰指针;

    1). const int* p1 = &num;

        无法通过p1指针去修改指针指向的变量的值. 但是如果直接操作变量这是可以的.

        但是指针变量的值可以改.可以把另外1个变量的地址赋值给这个指针.

    2).  int const * p1 = &num;

         效果同上

    3).  int  * const p1 = &num;

         p1的值不能修改,但是可以通过p1去修改p1指向的变量的值.

    4).  int const  * const p1 = &num;

          既不能修改p1的值,也不能通过p1去修改p1指向的变量的值.

 P285 const关键字的使用场景

5. const的使用场景.

    1). const的特点:

        被const修饰的变量.是只读变量,只能取值.而不能改值.

        所以,const变量的值,至始至终都不会发生变化.

    2). 当某些数据是固定的,在整个程序运行期间都不会发生变化. 并且你也不允许别人去修改.

        那么这个时候,我们就可以使用const.

        const int  width: 800

        const int  height:600

    3). 当函数的参数是1个指针的时候.这个时候,函数的内部是有可能会修改实参变量的值.

        这个时候

        函数想传递给调用者1个信息: 你放心大胆的传给我吧.我肯定不会修改的.

        那么这个时候,就可以给参数加1个const

        所以.我们以后在调用函数的时候.如果看到了参数被const修饰. 放心大胆的给.

        函数内部只会使用我们的值 绝对改不了我们的值. 

 P286 malloc函数

 1. 内存的五大区域.

    栈: 局部变量.

    堆: 堆区中的字节空间允许程序员手动的申请.

    BSS段: 未初始化的全局变量、静态变量.

    数据段: 已经初始化的全局变量、静态变量 和 常量数据.

    代码段: 存储代码的.

 2. 如何向堆区申请字节空间来使用.

    1). 我们在堆中申请的字节空间.如果我们不主动释放.那么系统是不会释放到的. 除非程序结束了.

    2). 在堆中申请字节空间的步骤.       

        a. 申请.

        b. 使用.

        c. 释放.

    3). 如何在堆区申请指定字节数的字节空间呢?

       malloc()

       calloc()

       realloc() 

       这3个函数都是和申请字节空间有关的.  这几个函数的声明 是放在1个叫做stdlib.h的系统头文件中.

    4). malloc函数.

        0). 作用: 向堆空间申请指定字节的空间来使用.

        1). 参数只有1个:size_t类型的 也就是unsigned long(无符号数).

            参数代表的意义: 向堆内存申请多少个连续的字节空间.

        2). 做的事情: 在堆内存中申请连续的参数个字节空间.

        3). 返回值是: void *.

            void *代表.没有类型的指针.

            返回的是创建的空间中第1个字节的地址.

            地址没有类型的. 只是返回了第1个字节的地址. 没有说这个指针是什么类型的.

        4). 我们应该使用什么类型的指针变量来保存malloc函数返回的地址呢?

            那就要看你想要如何去操作申请的这些字节空间.

            如果你想要1个字节1个字节的操作.那么就使用char指针.

            如果你想要4个字节4个字节的操作.并且当做整型来操作 那么就是要int指针.

            如果你想要8个字节8个字节的操作.那么就是要double指针.

            如果你想要4个字节4个字节的操作.并且当做单精度浮点型来操作 那么就是要float指针.

            就要看你想如何操作申请的这些字节空间.       

            int* p1 = malloc(8);

            *p1 = 100; 操作的时候是以4个字节为基本单位.

            char* p1 = malloc(8);

            *p1 = 100; 操作的时候是以1个字节为基本单位.

      

            这样的话.我们就可以在堆内存中申请任意字节数的空间来使用.

            通过指针来使用申请的空间.

         5). 在堆区申请的字节空间是从低地址向高地址分配.     

             每次申请的字节地址都是从0开始. 每一次申请的字节空间不一定挨在一起.

             但是. 每一次申请的指定个字节 这些字节一定肯定决定是连续的.

 

 

         6). 在堆区申请的字节,里面是有值的.值是垃圾值.不会自动清零.

 

         7). 在向堆区申请字节空间的时候,有可能会申请失败.

             如果申请失败 返回的指针就是NULL值.

             我们申请完空间之后,最好是判断1下.判断是否申请成功.

int* p1 = malloc(4);
if(p1)
{

    //代表申请成功,要做的事情放这就可以了.
}

        8). 申请的空间使用完毕之后,一定要记得释放.

            释放申请的堆空间:

            free(指针);

            如果没有free 程序结束才会释放.

            记住,一定要释放. 因为我们的程序有可能会运行很久.

            如果非要等到结束的时候自动释放 那就晚了.      

 P287 calloc与realloc

 3. calloc函数.

    1). 作用: 向堆区申请指定字节数的空间.

    2). 格式:

        参数1: 多少个单位.

        参数2: 每1个单位的字节数.

        calloc(4,sizeof(int));

        表示申请4个int空间.

    3). 与malloc的优势

         calloc申请的字节,申请完之后,系统会将字节中的数据清零.

 

 4. realloc函数.

参数1:要扩容的空间指针。

参数2:要扩容的单位长度。

    1). 作用: 扩容.

    2). 注意: 我们有了指针 几乎就可以操作内存上的每一个字节.

             但是我们还是建议,不要乱来. 只操作我们申请的字节空间.

             因为.有可能会出现一些问题.    

    3). 当我们发现我们之前在堆区申请的字节空间不够用的时候.

        就可以使用realloc函数来为我们的字节空间扩容.

        a. 如果原来的空间后面还有剩余的空间.并且足够扩容.那么直接就扩容在屁股后面.

        b. 如果原来的空间后面有剩余空间但是剩下的空间不够扩容.就重新找1块足够的空间申请.

           将原来的数据拷贝过来. 原来的空间被自动释放.

 

 P288 推箱子游戏

 1. 应该将我们地图上的每一个格子的类型保存起来.

    墙: 

    路:

    人:

    箱子:

    并且地图是1个有行有列的表格.所以所以二维数组来保存我们的地图是非常合适的了.

 2. 游戏的流程.

    while(1)

    {

        1). 打印地图.

        2). 接收输入小人的前进方向.

        3). 根据小人的前进方向来前进或者推箱子.

    }

#include <stdio.h>
#include <stdlib.h>
#define ROWS 10
#define COLS 11
/**
 *  地图数组,用来保存地图上的每一个格子的类型.
 */
char map[ROWS][COLS] = {
    "##########",
    "#  ####  #",
    "# X####  #",
    "# O      #",
    "######   #",
    "#  ####  #",
    "#        #",
    "#   ######",
    "#         ",
    "##########"
};

//小人当前所在的行左边. 默认在第3行
int personCurrenRow = 3;
//小人当前所在的列坐标. 默认在第2列.
int personCurrentCol = 2;
/**
 *  根据map数组打印地图.
 */
void showMap();

/**
 *  接收输入小人的前进方向
 *
 *  @return 前进方向
 */
char enterDirection();

/**
 *  向左走
 */
void moveToLeft();

/**
 *  向上走
 */
void moveToUp();

/**
 *  向右走
 */
void moveToRigth();

/**
 *  向下走
 */
void moveToDown();
int main()
{
    while (1)
    {
        system("clear");
        //1.打印地图.
        showMap();
        //2.接收输入小人的前进方向.
        char dir = enterDirection();
        //3. 根据小人的前进方向,来移动小人或者推箱子.
        switch (dir)
        {
            case 'a':
            case 'A':
                //向左
                moveToLeft();
                break;
            case 'w':
            case 'W':
                //向上
                moveToUp();
                break;
            case 'd':
            case 'D':
                //向右
                moveToRigth();
                break;
            case 's':
            case 'S':
                //向下
                moveToDown();
                break;
                
            case 'q':
            case 'Q':
                printf("你的智商真低下!\n");
                return 0;
        }
        
    }
    
    return 0;
}


/**
 *  根据map数组打印地图.
 */
void showMap()
{
    for(int i = 0; i < ROWS; i++)
    {
        printf("%s\n",map[i]);
    }
}

/**
 *  接收输入小人的前进方向
 *
 *  @return 前进方向
 */
char enterDirection()
{
    printf("请输入小人的前进方向 a.左 w.上 d.右 s.下 q.结束\n");
    char dir = 'a';
    rewind(stdin);
    scanf("%c",&dir);
    return dir;
}
/**
 *  向左走
 */
void moveToLeft()
{
    //1. 下1个位置.
    int personNextRow = personCurrenRow;
    int personNextCol = personCurrentCol - 1;
    
    
    //3. 判断小人的上1个是1个什么格子.
    if(map[personNextRow][personNextCol] == ' ')
    {
        //说明是个路.小人移动.
        //将上1个格子赋值为O 小人现在的位置赋值为' '
        map[personNextRow][personNextCol] = 'O';
        map[personCurrenRow][personCurrentCol] = ' ';
        //小人已经移动了.那么就要更改小人的位置.
        personCurrenRow = personNextRow;
        personCurrentCol = personNextCol;
        
    }
    else if(map[personNextRow][personNextCol] == 'X')
    {
        //说明上1个格子是1个箱子.
        //判断能不能推.
        
        //1. 找到箱子上面的格子的坐标.
        //   计算出箱子上面的坐标.
        int boxNextRow = personNextRow;
        int boxNextCol = personNextCol - 1;
        
        //2. 判断箱子的上1个格子是什么类
        if(map[boxNextRow][boxNextCol] == ' ')
        {
            //推
            //a. 把当前箱子的上1个格子设置为箱子.
            map[boxNextRow][boxNextCol] = 'X';
            
            //b. 把当前箱子的格子设置为小人.
            map[personNextRow][personNextCol] = 'O';
            //c. 把小人的位置设置空格.
            map[personCurrenRow][personCurrentCol] = ' ';
            
            //推完之后 改小人的位置.
            personCurrenRow = personNextRow;
            personCurrentCol = personNextCol;
            
        }
    }

}

/**
 *  向上走
 */
void moveToUp()
{
    
    //1. 要把小人向上移动.
    //   就要知道小人的上1个格子的类型.
    //   如果上面是1个墙 不能动.
    //   如果上面是个路: 移动.
    //   如果上面是箱子. 判断箱子上面是什么
    //          路: 推
    //          墙: 不动.
    
    
    //2. 首先要拿到小人的上1个坐标.
    //   要拿到小人的上1个左边,就要拿到小人现在的坐标.
    //   根据小人现在的坐标 计算小人的上1个坐标.
    //   上1个坐标相对于小人的左边 行-1 列不变.
    int personNextRow = personCurrenRow - 1;
    int personNextCol = personCurrentCol;
    
    
    //3. 判断小人的上1个是1个什么格子.
    if(map[personNextRow][personNextCol] == ' ')
    {
        //说明是个路.小人移动.
        //将上1个格子赋值为O 小人现在的位置赋值为' '
        map[personNextRow][personNextCol] = 'O';
        map[personCurrenRow][personCurrentCol] = ' ';
        //小人已经移动了.那么就要更改小人的位置.
        personCurrenRow = personNextRow;
        personCurrentCol = personNextCol;
        
    }
    else if(map[personNextRow][personNextCol] == 'X')
    {
        //说明上1个格子是1个箱子.
        //判断能不能推.
        
        //1. 找到箱子上面的格子的坐标.
        //   计算出箱子上面的坐标.
        int boxNextRow = personNextRow - 1;
        int boxNextCol = personNextCol;
        
        //2. 判断箱子的上1个格子是什么类
        if(map[boxNextRow][boxNextCol] == ' ')
        {
            //推
            //a. 把当前箱子的上1个格子设置为箱子.
            map[boxNextRow][boxNextCol] = 'X';
            
            //b. 把当前箱子的格子设置为小人.
            map[personNextRow][personNextCol] = 'O';
            //c. 把小人的位置设置空格.
            map[personCurrenRow][personCurrentCol] = ' ';
            
            //推完之后 改小人的位置.
            personCurrenRow = personNextRow;
            personCurrentCol = personNextCol;
            
        }
    }
}
/**
 *  向右走
 */
void moveToRigth()
{
    int personNextRow = personCurrenRow;
    int personNextCol = personCurrentCol + 1;
    
    
    //3. 判断小人的上1个是1个什么格子.
    if(map[personNextRow][personNextCol] == ' ')
    {
        //说明是个路.小人移动.
        //将上1个格子赋值为O 小人现在的位置赋值为' '
        map[personNextRow][personNextCol] = 'O';
        map[personCurrenRow][personCurrentCol] = ' ';
        //小人已经移动了.那么就要更改小人的位置.
        personCurrenRow = personNextRow;
        personCurrentCol = personNextCol;
        
    }
    else if(map[personNextRow][personNextCol] == 'X')
    {
        //说明上1个格子是1个箱子.
        //判断能不能推.
        
        //1. 找到箱子上面的格子的坐标.
        //   计算出箱子上面的坐标.
        int boxNextRow = personNextRow;
        int boxNextCol = personNextCol + 1;
        
        //2. 判断箱子的上1个格子是什么类
        if(map[boxNextRow][boxNextCol] == ' ')
        {
            //推
            //a. 把当前箱子的上1个格子设置为箱子.
            map[boxNextRow][boxNextCol] = 'X';
            
            //b. 把当前箱子的格子设置为小人.
            map[personNextRow][personNextCol] = 'O';
            //c. 把小人的位置设置空格.
            map[personCurrenRow][personCurrentCol] = ' ';
            
            //推完之后 改小人的位置.
            personCurrenRow = personNextRow;
            personCurrentCol = personNextCol;
            
        }
    }

}

/**
 *  向下走
 */
void moveToDown()
{
    int personNextRow = personCurrenRow + 1;
    int personNextCol = personCurrentCol;
    
    
    //3. 判断小人的上1个是1个什么格子.
    if(map[personNextRow][personNextCol] == ' ')
    {
        //说明是个路.小人移动.
        //将上1个格子赋值为O 小人现在的位置赋值为' '
        map[personNextRow][personNextCol] = 'O';
        map[personCurrenRow][personCurrentCol] = ' ';
        //小人已经移动了.那么就要更改小人的位置.
        personCurrenRow = personNextRow;
        personCurrentCol = personNextCol;
        
    }
    else if(map[personNextRow][personNextCol] == 'X')
    {
        //说明上1个格子是1个箱子.
        //判断能不能推.
        
        //1. 找到箱子上面的格子的坐标.
        //   计算出箱子上面的坐标.
        int boxNextRow = personNextRow + 1;
        int boxNextCol = personNextCol;
        
        //2. 判断箱子的上1个格子是什么类
        if(map[boxNextRow][boxNextCol] == ' ')
        {
            //推
            //a. 把当前箱子的上1个格子设置为箱子.
            map[boxNextRow][boxNextCol] = 'X';
            
            //b. 把当前箱子的格子设置为小人.
            map[personNextRow][personNextCol] = 'O';
            //c. 把小人的位置设置空格.
            map[personCurrenRow][personCurrentCol] = ' ';
            
            //推完之后 改小人的位置.
            personCurrenRow = personNextRow;
            personCurrentCol = personNextCol;
            
        }
    }

}

 P289 推箱子优化

#include <stdio.h>
#include <stdlib.h>
#define ROWS 10
#define COLS 11


/**
 *  地图数组,用来保存地图上的每一个格子的类型.
 */
char map[ROWS][COLS] = {
    "##########",
    "#  ####  #",
    "# X####  #",
    "# O      #",
    "######   #",
    "#  ####  #",
    "#        #",
    "#   ######",
    "#         ",
    "##########"
};



//小人当前所在的行左边. 默认在第3行
int personCurrenRow = 3;
//小人当前所在的列坐标. 默认在第2列.
int personCurrentCol = 2;



/**
 *  根据map数组打印地图.
 */
void showMap();

/**
 *  接收输入小人的前进方向
 *
 *  @return 前进方向
 */
char enterDirection();



/**
 *  将小人向指定的方向移动
 *
 *  @param dir 指定的方向
 */
void movePerson(char dir);


/**
 *  根据小人的前进方向计算小人的下1个坐标
 *
 *  @param dir 前进方向
 */
void getNextPosition(char dir,int currentRow,int currentCol,int* pNextRow,int* pNextCol);


int main()
{
    while (1)
    {
        system("clear");
        //1.打印地图.
        showMap();
        //2.接收输入小人的前进方向.
        char dir = enterDirection();
        //3. 根据小人的前进方向,来移动小人或者推箱子.
        if(dir == 'q')
        {
            //结束
        }
        else
        {
            movePerson(dir);
        }
    }
    
    return 0;
}


/**
 *  根据map数组打印地图.
 */
void showMap()
{
    for(int i = 0; i < ROWS; i++)
    {
        printf("%s\n",map[i]);
    }
}

/**
 *  接收输入小人的前进方向
 *
 *  @return 前进方向
 */
char enterDirection()
{
    printf("请输入小人的前进方向 a.左 w.上 d.右 s.下 q.结束\n");
    char dir = 'a';
    rewind(stdin);
    scanf("%c",&dir);
    return dir;
}




/**
 *  将小人向指定的方向移动
 *
 *  @param dir 指定的方向
 */
void movePerson(char dir)
{
    //1. 拿到小人的下1个坐标.
    //   根据小人的前进方向而不同.
    //   这段代码做的事情:是根据小人的当前坐标和前进方向计算出小人的下个坐标.
    int personNextRow = 0;
    int personNextCol = 0;
    
    
    getNextPosition(dir, personCurrenRow, personCurrentCol, &personNextRow, &personNextCol);

    
    //2. 判断小人的下1个坐标是1个什么东西.
    if(map[personNextRow][personNextCol] == ' ')
    {
        //移动小人
        map[personNextRow][personNextCol] = 'O';
        map[personCurrenRow][personCurrentCol] = ' ';
        //改变小人的位置.
        personCurrenRow = personNextRow;
        personCurrentCol = personNextCol;
    }
    else if(map[personNextRow][personNextCol] == 'X')
    {
        //说明下1个是1个箱子.
        //还要判断箱子能不能推.
        //就要拿到这个箱子的下1个坐标.
        //可以根据箱子的坐标 计算出箱子的下1个坐标/
        //而现在箱子的坐标是 personNextRow personNextCol
        
        //这段代码是根据箱子现在的坐标 算出箱子的下1个坐标.
    
        
        int boxNextRow = 0;
        int boxNextCol = 0;
        
        getNextPosition(dir, personNextRow, personNextCol, &boxNextRow, &boxNextCol);
        
        //这段代码执行完毕之后 boxNextRow 和 boxNextCol 就是箱子的下1个坐标.
        //继续判断箱子的下1个坐标是路.
        if(map[boxNextRow][boxNextCol] == ' ')
        {
            map[boxNextRow][boxNextCol] = 'X';
            
            //b. 把当前箱子的格子设置为小人.
            map[personNextRow][personNextCol] = 'O';
            //c. 把小人的位置设置空格.
            map[personCurrenRow][personCurrentCol] = ' ';
            
            //推完之后 改小人的位置.
            personCurrenRow = personNextRow;
            personCurrentCol = personNextCol;

        }
    }
}


/**
 *  根据小人的前进方向计算小人的下1个坐标
 *
 *  @param dir 前进方向
 */
void getNextPosition(char dir,int currentRow,int currentCol,int* pNextRow,int* pNextCol)
{
    int nextRow = 0;
    int nextCol = 0;
    
    //函数中的基准坐标不一样
    
    switch (dir)
    {
        case 'a':
        case 'A':
            nextRow = currentRow;
            nextCol = currentCol -1;
            break;
        case 'w':
        case 'W':
            nextRow = currentRow -1;
            nextCol = currentCol;
            break;
        case 'd':
        case 'D':
            nextRow = currentRow;
            nextCol = currentCol + 1;
            break;
        case 's':
        case 'S':
            nextRow = currentRow + 1;
            nextCol = currentCol;
            break;
    }   
    *pNextRow = nextRow;
    *pNextCol = nextCol;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数字乡村和智慧农业的数字化转型是当前农业发展的新趋势,旨在通过应用数字技术,实现农业全流程的再造和全生命周期的管理服务。中国政府高度重视这一领域的发展,提出“数字中国”和“乡村振兴”战略,以提升国家治理能力,推动城乡融合发展。 数字乡村的建设面临乡村治理、基础设施、产业链条和公共服务等方面的问题,需要分阶段实施《数字乡村发展战略纲要》来解决。农业数字化转型的需求包括满足市民对优质农产品的需求、解决产销对接问题、形成优质优价机制、提高农业劳动力素质、打破信息孤岛、提高农业政策服务的精准度和有效性,以及解决农业融资难的问题。 数字乡村建设的关键在于构建“1+3+4+1”工程,即以新技术、新要素、新商业、新农民、新文化、新农村为核心,推进数据融合,强化农业大数据的汇集功能。数字农业大数据解决方案以农业数字底图和数据资源为基础,通过可视化监管,实现区域农业的全面数字化管理。 数字农业大数据架构基于大数据、区块链、GIS和物联网技术,构建农业大数据中心、农业物联网平台和农村综合服务指挥决策平台三大基础平台。农业大数据中心汇聚各类涉农信息资源和业务数据,支持大数据应用。信息采集系统覆盖市、县、乡、村多级,形成高效的农业大数据信息采集体系。 农业物联网平台包括环境监测系统、视频监控系统、预警预报系统和智能控制系统,通过收集和监测数据,实现对农业环境和生产过程的智能化管理。综合服务指挥决策平台利用数据分析和GIS技术,为农业决策提供支持。 数字乡村建设包括三大服务平台:治理服务平台、民生服务平台和产业服务平台。治理服务平台通过大数据和AI技术,实现乡村治理的数字化;民生服务平台利用互联网技术,提供各类民生服务;产业服务平台融合政企关系,支持农业产业发展。 数字乡村的应用场景广泛,包括农业生产过程、农产品流通、农业管理和农村社会服务。农业生产管理系统利用AIoT技术,实现农业生产的标准化和智能化。农产品智慧流通管理系统和溯源管理系统提高流通效率和产品追溯能力。智慧农业管理通过互联网+农业,提升农业管理的科学性和效率。农村社会服务则通过数字化手段,提高农村地区的公共服务水平。 总体而言,数字乡村和智慧农业的建设,不仅能够提升农业生产效率和管理水平,还能够促进农村地区的社会经济发展,实现城乡融合发展,是推动中国农业现代化的重要途径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值